Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F9311634
client.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Subscribers
None
client.py
View Options
# Copyright (C) 2017-2019 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import
os
import
logging
import
sys
import
tempfile
import
uuid
import
click
import
xmltodict
from
swh.deposit.client
import
PublicApiDepositClient
from
swh.deposit.cli
import
deposit
logger
=
logging
.
getLogger
(
__name__
)
class
InputError
(
ValueError
):
"""Input script error
"""
pass
def
generate_slug
():
"""Generate a slug (sample purposes).
"""
return
str
(
uuid
.
uuid4
())
def
_url
(
url
):
"""Force the /1 api version at the end of the url (avoiding confusing
issues without it).
Args:
url (str): api url used by cli users
Returns:
Top level api url to actually request
"""
if
not
url
.
endswith
(
'/1'
):
url
=
'
%s
/1'
%
url
return
url
def
generate_metadata_file
(
name
,
external_id
,
authors
):
"""Generate a temporary metadata file with the minimum required metadata
This generates a xml file in a temporary location and returns the
path to that file.
This is up to the client of that function to clean up the
temporary file.
Args:
name (str): Software's name
external_id (str): External identifier (slug) or generated one
authors (List[str]): List of author names
Returns:
Filepath to the metadata generated file
"""
_
,
tmpfile
=
tempfile
.
mkstemp
(
prefix
=
'swh.deposit.cli.'
)
# generate a metadata file with the minimum required metadata
codemetadata
=
{
'entry'
:
{
'@xmlns'
:
"http://www.w3.org/2005/Atom"
,
'@xmlns:codemeta'
:
"https://doi.org/10.5063/SCHEMA/CODEMETA-2.0"
,
'codemeta:name'
:
name
,
'codemeta:identifier'
:
external_id
,
'codemeta:author'
:
[{
'codemeta:name'
:
author_name
}
for
author_name
in
authors
],
},
}
logging
.
debug
(
'Temporary file:
%s
'
,
tmpfile
)
logging
.
debug
(
'Metadata dict to generate as xml:
%s
'
,
codemetadata
)
s
=
xmltodict
.
unparse
(
codemetadata
,
pretty
=
True
)
logging
.
debug
(
'Metadata dict as xml generated:
%s
'
,
s
)
with
open
(
tmpfile
,
'w'
)
as
fp
:
fp
.
write
(
s
)
return
tmpfile
def
_cleanup_tempfile
(
config
):
"""Clean up the temporary metadata file generated.
Args:
config (Dict): A configuration dict with 2 important keys for
that routine, 'cleanup_tempfile' (bool) and 'metadata' (path
to eventually clean up)
"""
if
config
[
'cleanup_tempfile'
]:
path
=
config
[
'metadata'
]
if
os
.
path
.
exists
(
path
):
os
.
unlink
(
path
)
def
_client
(
url
,
username
,
password
):
"""Instantiate a client to access the deposit api server
Args:
url (str): Deposit api server
username (str): User
password (str): User's password
"""
client
=
PublicApiDepositClient
({
'url'
:
url
,
'auth'
:
{
'username'
:
username
,
'password'
:
password
},
})
return
client
def
_collection
(
client
):
"""Retrieve the client's collection
"""
# retrieve user's collection
sd_content
=
client
.
service_document
()
if
'error'
in
sd_content
:
raise
InputError
(
'Service document retrieval:
%s
'
%
(
sd_content
[
'error'
],
))
collection
=
sd_content
[
'service'
][
'workspace'
][
'collection'
][
'sword:name'
]
return
collection
def
client_command_parse_input
(
username
,
password
,
archive
,
metadata
,
archive_deposit
,
metadata_deposit
,
collection
,
slug
,
partial
,
deposit_id
,
replace
,
url
,
name
,
authors
):
"""Parse the client subcommand options and make sure the combination
is acceptable*. If not, an InputError exception is raised
explaining the issue.
By acceptable, we mean:
- A multipart deposit (create or update) requires:
- an existing software archive
- an existing metadata file or author(s) and name provided in
params
- A binary deposit (create/update) requires an existing software
archive
- A metadata deposit (create/update) requires an existing metadata
file or author(s) and name provided in params
- A deposit update requires a deposit_id
This will not prevent all failure cases though. The remaining
errors are already dealt with by the underlying api client.
Raises:
InputError explaining the issue
Returns:
dict with the following keys:
'archive': the software archive to deposit
'username': username
'password': associated password
'metadata': the metadata file to deposit
'collection': the username's associated client
'slug': the slug or external id identifying the deposit to make
'partial': if the deposit is partial or not
'client': instantiated class
'url': deposit's server main entry point
'deposit_type': deposit's type (binary, multipart, metadata)
'deposit_id': optional deposit identifier
"""
cleanup_tempfile
=
False
try
:
if
archive_deposit
and
metadata_deposit
:
# too many flags use, remove redundant ones (-> multipart deposit)
archive_deposit
=
False
metadata_deposit
=
False
if
not
slug
:
# generate one as this is mandatory
slug
=
generate_slug
()
if
not
metadata
and
name
and
authors
:
metadata
=
generate_metadata_file
(
name
,
slug
,
authors
)
cleanup_tempfile
=
True
if
metadata_deposit
:
archive
=
None
if
archive_deposit
:
metadata
=
None
if
metadata_deposit
and
not
metadata
:
raise
InputError
(
"Metadata deposit must be provided for metadata "
"deposit (either a filepath or --name and --author)"
)
if
not
archive
and
not
metadata
:
raise
InputError
(
'Please provide an actionable command. See --help for more '
'information'
)
if
replace
and
not
deposit_id
:
raise
InputError
(
'To update an existing deposit, you must provide its id'
)
client
=
_client
(
url
,
username
,
password
)
if
not
collection
:
collection
=
_collection
(
client
)
return
{
'archive'
:
archive
,
'username'
:
username
,
'password'
:
password
,
'metadata'
:
metadata
,
'cleanup_tempfile'
:
cleanup_tempfile
,
'collection'
:
collection
,
'slug'
:
slug
,
'in_progress'
:
partial
,
'client'
:
client
,
'url'
:
url
,
'deposit_id'
:
deposit_id
,
'replace'
:
replace
,
}
except
Exception
:
# to be clean, cleanup prior to raise
_cleanup_tempfile
({
'cleanup_tempfile'
:
cleanup_tempfile
,
'metadata'
:
metadata
})
raise
def
_subdict
(
d
,
keys
):
'return a dict from d with only given keys'
return
{
k
:
v
for
k
,
v
in
d
.
items
()
if
k
in
keys
}
def
deposit_create
(
config
,
logger
):
"""Delegate the actual deposit to the deposit client.
"""
logger
.
debug
(
'Create deposit'
)
client
=
config
[
'client'
]
keys
=
(
'collection'
,
'archive'
,
'metadata'
,
'slug'
,
'in_progress'
)
return
client
.
deposit_create
(
**
_subdict
(
config
,
keys
))
def
deposit_update
(
config
,
logger
):
"""Delegate the actual deposit to the deposit client.
"""
logger
.
debug
(
'Update deposit'
)
client
=
config
[
'client'
]
keys
=
(
'collection'
,
'deposit_id'
,
'archive'
,
'metadata'
,
'slug'
,
'in_progress'
,
'replace'
)
return
client
.
deposit_update
(
**
_subdict
(
config
,
keys
))
@deposit.command
()
@click.option
(
'--username'
,
required
=
True
,
help
=
"(Mandatory) User's name"
)
@click.option
(
'--password'
,
required
=
True
,
help
=
"(Mandatory) User's associated password"
)
@click.option
(
'--archive'
,
type
=
click
.
Path
(
exists
=
True
),
help
=
'(Optional) Software archive to deposit'
)
@click.option
(
'--metadata'
,
type
=
click
.
Path
(
exists
=
True
),
help
=
"(Optional) Path to xml metadata file. If not provided, this will use a file named <archive>.metadata.xml"
)
# noqa
@click.option
(
'--archive-deposit/--no-archive-deposit'
,
default
=
False
,
help
=
'(Optional) Software archive only deposit'
)
@click.option
(
'--metadata-deposit/--no-metadata-deposit'
,
default
=
False
,
help
=
'(Optional) Metadata only deposit'
)
@click.option
(
'--collection'
,
help
=
"(Optional) User's collection. If not provided, this will be fetched."
)
# noqa
@click.option
(
'--slug'
,
help
=
"""(Optional) External system information identifier. If not provided, it will be generated"""
)
# noqa
@click.option
(
'--partial/--no-partial'
,
default
=
False
,
help
=
'(Optional) The deposit will be partial, other deposits will have to take place to finalize it.'
)
# noqa
@click.option
(
'--deposit-id'
,
default
=
None
,
help
=
'(Optional) Update an existing partial deposit with its identifier'
)
# noqa
@click.option
(
'--replace/--no-replace'
,
default
=
False
,
help
=
'(Optional) Update by replacing existing metadata to a deposit'
)
# noqa
@click.option
(
'--url'
,
default
=
'https://deposit.softwareheritage.org'
,
help
=
"(Optional) Deposit server api endpoint. By default, https://deposit.softwareheritage.org/1"
)
# noqa
@click.option
(
'--verbose/--no-verbose'
,
default
=
False
,
help
=
'Verbose mode'
)
@click.option
(
'--name'
,
help
=
'Software name'
)
@click.option
(
'--author'
,
multiple
=
True
,
help
=
'Software author(s), this can be repeated as many times'
' as there are authors'
)
@click.pass_context
def
upload
(
ctx
,
username
,
password
,
archive
=
None
,
metadata
=
None
,
archive_deposit
=
False
,
metadata_deposit
=
False
,
collection
=
None
,
slug
=
None
,
partial
=
False
,
deposit_id
=
None
,
replace
=
False
,
url
=
'https://deposit.softwareheritage.org'
,
verbose
=
False
,
name
=
None
,
author
=
None
):
"""Software Heritage Public Deposit Client
Create/Update deposit through the command line.
More documentation can be found at
https://docs.softwareheritage.org/devel/swh-deposit/getting-started.html.
"""
url
=
_url
(
url
)
config
=
{}
try
:
logger
.
debug
(
'Parsing cli options'
)
config
=
client_command_parse_input
(
username
,
password
,
archive
,
metadata
,
archive_deposit
,
metadata_deposit
,
collection
,
slug
,
partial
,
deposit_id
,
replace
,
url
,
name
,
author
)
except
InputError
as
e
:
logger
.
error
(
'Problem during parsing options:
%s
'
,
e
)
sys
.
exit
(
1
)
try
:
if
verbose
:
logger
.
info
(
"Parsed configuration:
%s
"
%
(
config
,
))
deposit_id
=
config
[
'deposit_id'
]
if
deposit_id
:
r
=
deposit_update
(
config
,
logger
)
else
:
r
=
deposit_create
(
config
,
logger
)
logger
.
info
(
r
)
finally
:
_cleanup_tempfile
(
config
)
@deposit.command
()
@click.option
(
'--url'
,
default
=
'https://deposit.softwareheritage.org'
,
help
=
"(Optional) Deposit server api endpoint. By default, "
"https://deposit.softwareheritage.org/1"
)
@click.option
(
'--username'
,
required
=
True
,
help
=
"(Mandatory) User's name"
)
@click.option
(
'--password'
,
required
=
True
,
help
=
"(Mandatory) User's associated password"
)
@click.option
(
'--deposit-id'
,
default
=
None
,
required
=
True
,
help
=
"Deposit identifier."
)
@click.pass_context
def
status
(
ctx
,
url
,
username
,
password
,
deposit_id
):
"""Deposit's status
"""
url
=
_url
(
url
)
logger
.
debug
(
'Status deposit'
)
try
:
client
=
_client
(
url
,
username
,
password
)
collection
=
_collection
(
client
)
except
InputError
as
e
:
logger
.
error
(
'Problem during parsing options:
%s
'
,
e
)
sys
.
exit
(
1
)
r
=
client
.
deposit_status
(
collection
=
collection
,
deposit_id
=
deposit_id
)
logger
.
info
(
r
)
File Metadata
Details
Attached
Mime Type
text/x-python
Expires
Thu, Jul 3, 10:25 AM (2 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3452088
Attached To
rDDEP Push deposit
Event Timeline
Log In to Comment