diff --git a/docs/getting-started.rst b/docs/getting-started.rst --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -36,43 +36,7 @@ following compression algorithm gzip (`.tar.gz`, `.tgz`), bzip2 (`.tar.bz2`) , or lzma (`.tar.lzma`) -* prepare a metadata file (`more details <./metadata.html>`__.): - - - specify metadata schema/vocabulary (CodeMeta is strongly recommended) - - specify *MUST* metadata (url, authors, software name and - the external\_identifier) - - add all available information under the compatible metadata term. - - Here is an example of an atom entry file with CodeMeta terms: - -.. code:: xml - - - - Je suis GPL - swh - je-suis-gpl - https://forge.softwareheritage.org/source/jesuisgpl/ - 2018-01-05 - Je suis GPL is a modified version of GNU Hello whose - sole purpose is to showcase the usage of - Software Heritage for license compliance purposes. - 0.1 - GNU/Linux - stable - C - - - GNU General Public License v3.0 or later - https://spdx.org/licenses/GPL-3.0-or-later.html - - - Stefano Zacchiroli - Maintainer - - - +* (Optional) prepare a metadata file (`more details <./metadata.html>`__.): Push deposit @@ -105,13 +69,16 @@ For this, we need to provide the: * arguments: ``--username 'name' --password 'pass'`` as credentials -* archive's path (example: ``--archive path/to/archive-name.tgz``) : +* archive's path (example: ``--archive path/to/archive-name.tgz``) +* (optionally) software's name (mandatory if no metadata filepath is specified + for a multipart or partial metadata deposit). +* (optionally) author's name (mandatory if no metadata filepath is specified + for a multipart or partial metadata deposit). + This can be specified multiple times in case of multiple authors. * (optionally) metadata file's path ``--metadata - path/to/file.metadata.xml``. If not provided, the archive's filename - will be used to determine the metadata file, e.g: - ``path/to/archive-name.tgz.metadata.xml`` -* (optionally) ``--slug 'your-id'`` argument, a reference to a - unique identifier the client uses for the software object. + path/to/file.metadata.xml``. +* (optionally) ``--slug 'your-id'`` argument, a reference to a unique identifier + the client uses for the software object. If not provided, this is generated. You can do this with the following command: @@ -120,6 +87,8 @@ .. code:: shell $ swh deposit upload --username name --password secret \ + --author "some@noone" --author "second@noone" \ + --name 'je-suis-gpl' \ --archive je-suis-gpl.tgz with client's external identifier (``slug``) @@ -127,6 +96,8 @@ .. code:: shell $ swh deposit upload --username name --password secret \ + --author "some@noone" \ + --name 'je-suis-gpl' \ --archive je-suis-gpl.tgz \ --slug je-suis-gpl @@ -135,11 +106,12 @@ .. code:: shell $ swh deposit upload --username name --password secret \ + --author "some@noone" \ + --name 'je-suis-gpl' \ --archive je-suis-gpl.tgz \ --collection 'second-collection' - You just posted a deposit to your collection on Software Heritage @@ -195,6 +167,8 @@ for the first step. You can continue adding content or metadata while you use the ``--partial`` argument. +To only add one new archive to the deposit: + .. code:: shell $ swh deposit upload --username name --password secret \ @@ -202,39 +176,30 @@ --deposit-id 42 \ --partial - -In case you want to add only one new archive without metadata: +To only add metadata to the deposit: .. code:: shell $ swh deposit upload --username name --password secret \ - --archive add-foo.tar.gz \ - --archive-deposit \ + --metadata add-foo.tar.gz.metadata.xml \ --deposit-id 42 \ --partial -If you want to add only metadata, use: - +or: .. code:: shell $ swh deposit upload --username name --password secret \ - --metadata add-foo.tar.gz.metadata.xml \ - --metadata-deposit \ + --name 'add-foo' --author 'someone' \ --deposit-id 42 \ --partial + 3. Finalize deposit ~~~~~~~~~~~~~~~~~~~ -On your last addition, by not declaring it as ``--partial``, the -deposit will be considered as completed and its status will be changed -to ``deposited``: -.. code:: shell - - $ swh deposit upload --username name --password secret \ - --metadata add-foo.tar.gz.metadata.xml \ - --metadata-deposit \ - --deposit-id 42 +On your last addition (same command as before), by not declaring it +``--partial``, the deposit will be considered completed. Its status will be +changed to ``deposited`` Update deposit @@ -278,9 +243,8 @@ .. code:: shell - $ swh deposit upload --username name --password secret \ - --deposit-id 11 \ - --status + $ swh deposit status --username name --password secret \ + --deposit-id 11 .. code:: json diff --git a/docs/metadata.rst b/docs/metadata.rst --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -21,7 +21,12 @@ -- **the url** representing the location of the source *MUST* be provided under +- **the name** of the software deposit *MUST* be provided [atom:title, + codemeta:name, dcterms:title] + +- **the authors** of the software deposit *MUST* be provided + +- **the url** representing the location of the source *MAY* be provided under the url tag. The url will be used for creating an origin object in the archive. @@ -33,18 +38,13 @@ or www.url-example.com -- **the external\_identifier** *MUST* be provided as an identifier - -- **the name** of the software deposit *MUST* be provided [atom:title, - codemeta:name, dcterms:title] - -- **the authors** of the software deposit *MUST* be provided +- **the external\_identifier** *MAY* be provided as an identifier - **the external\_identifier** *SHOULD* match the Slug external-identifier in the header - **the description** of the software deposit *SHOULD* be provided - [codemeta:description]: short or long description of the software + [codemeta:description]: short or long description of the software - **the license/s** of the software deposit *SHOULD* be provided [codemeta:license] diff --git a/swh/deposit/cli/__init__.py b/swh/deposit/cli/__init__.py --- a/swh/deposit/cli/__init__.py +++ b/swh/deposit/cli/__init__.py @@ -16,7 +16,12 @@ def deposit(ctx): """Deposit main command """ - logger.debug('deposit') + if hasattr(ctx, 'obj') and ctx.obj is not None: + log_level = ctx.obj['log_level'] + else: + log_level = logging.INFO + + logger.setLevel(log_level) ctx.ensure_object(dict) diff --git a/swh/deposit/cli/client.py b/swh/deposit/cli/client.py --- a/swh/deposit/cli/client.py +++ b/swh/deposit/cli/client.py @@ -90,30 +90,64 @@ 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, status, name, authors): + 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) needs both an - existing software archive and an existing metadata file + - A multipart deposit (create or update) needs both an existing + software archive and an existing metadata file (or at least + authors and name provided) - A binary deposit (create/update) needs an existing software archive - A metadata deposit (create/update) needs an existing - metadata file + metadata file (or at least authors and name provided) - A deposit update needs a deposit_id to be provided - This won't prevent all failure cases though. The remaining - errors are already dealt with the underlying api client. + 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 @@ -137,33 +171,17 @@ cleanup_tempfile = False try: - if status and not deposit_id: - raise InputError("Deposit id must be provided for status check") - - if status and deposit_id: # status is higher priority over deposit - archive_deposit = False - metadata_deposit = False - archive = None - metadata = None - if archive_deposit and metadata_deposit: # too many flags use, remove redundant ones (-> multipart deposit) archive_deposit = False metadata_deposit = False - if archive and not os.path.exists(archive): - raise InputError('Software Archive %s must exist!' % archive) - if not slug: # generate one as this is mandatory slug = generate_slug() - if archive and not metadata: # we need to have the metadata - if name and authors: - metadata = generate_metadata_file(name, slug, authors) - cleanup_tempfile = True - else: - raise InputError('Either metadata deposit file or (`--name` ' - ' and `--author`) fields must be provided') + if not metadata and name and authors: + metadata = generate_metadata_file(name, slug, authors) + cleanup_tempfile = True if metadata_deposit: archive = None @@ -173,38 +191,22 @@ if metadata_deposit and not metadata: raise InputError( - "Metadata deposit filepath must be provided for metadata " - "deposit") + "Metadata deposit must be provided for metadata " + "deposit (either a filepath or --name and --author)") - if metadata and not os.path.exists(metadata): - raise InputError('Software Archive metadata %s must exist!' % ( - metadata, )) - - if not status and not archive and not metadata: + if not archive and not metadata: raise InputError( 'Please provide an actionable command. See --help for more ' - 'information.') + 'information') if replace and not deposit_id: raise InputError( 'To update an existing deposit, you must provide its id') - client = PublicApiDepositClient({ - 'url': url, - 'auth': { - 'username': username, - 'password': password - }, - }) + client = _client(url, username, password) if not 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'] + collection = _collection(client) return { 'archive': archive, @@ -233,14 +235,6 @@ return {k: v for k, v in d.items() if k in keys} -def deposit_status(config, logger): - logger.debug('Status deposit') - keys = ('collection', 'deposit_id') - client = config['client'] - return client.deposit_status( - **_subdict(config, keys)) - - def deposit_create(config, logger): """Delegate the actual deposit to the deposit client. @@ -271,9 +265,9 @@ help="(Mandatory) User's name") @click.option('--password', required=1, help="(Mandatory) User's associated password") -@click.option('--archive', +@click.option('--archive', type=click.Path(exists=True), help='(Optional) Software archive to deposit') -@click.option('--metadata', +@click.option('--metadata', type=click.Path(exists=True), help="(Optional) Path to xml metadata file. If not provided, this will use a file named .metadata.xml") # noqa @click.option('--archive-deposit/--no-archive-deposit', default=False, help='(Optional) Software archive only deposit') @@ -291,8 +285,6 @@ help='(Optional) Update by replacing existing metadata to a deposit') # noqa @click.option('--url', default='https://deposit.softwareheritage.org/1', help="(Optional) Deposit server api endpoint. By default, https://deposit.softwareheritage.org/1") # noqa -@click.option('--status/--no-status', default=False, - help="(Optional) Deposit's status") @click.option('--verbose/--no-verbose', default=False, help='Verbose mode') @click.option('--name', @@ -305,13 +297,12 @@ username, password, archive=None, metadata=None, archive_deposit=False, metadata_deposit=False, collection=None, slug=None, partial=False, deposit_id=None, - replace=False, status=False, + replace=False, url='https://deposit.softwareheritage.org/1', verbose=False, name=None, author=None): """Software Heritage Public Deposit Client - Create/Update deposit through the command line or access its - status. + Create/Update deposit through the command line. More documentation can be found at https://docs.softwareheritage.org/devel/swh-deposit/getting-started.html. @@ -324,7 +315,7 @@ config = client_command_parse_input( username, password, archive, metadata, archive_deposit, metadata_deposit, collection, slug, partial, deposit_id, - replace, url, status, name, author) + replace, url, name, author) except InputError as e: msg = 'Problem during parsing options: %s' % e r = { @@ -340,14 +331,45 @@ deposit_id = config['deposit_id'] - if status and deposit_id: - r = deposit_status(config, logger) - elif not status and deposit_id: + if deposit_id: r = deposit_update(config, logger) - elif not status and not deposit_id: + else: r = deposit_create(config, logger) logger.info(r) finally: _cleanup_tempfile(config) + + +@deposit.command() +@click.option('--url', default='https://deposit.softwareheritage.org/1', + help="(Optional) Deposit server api endpoint. By default, " + "https://deposit.softwareheritage.org/1") +@click.option('--username', required=1, + help="(Mandatory) User's name") +@click.option('--password', required=1, + help="(Mandatory) User's associated password") +@click.option('--deposit-id', default=None, + required=1, + help="Deposit identifier.") +@click.pass_context +def status(ctx, url, username, password, deposit_id): + """Deposit's status + + """ + logger.debug('Status deposit') + try: + client = _client(url, username, password) + collection = _collection(client) + except InputError as e: + msg = 'Problem during parsing options: %s' % e + r = { + 'error': msg, + } + logger.info(r) + return 1 + + r = client.deposit_status( + collection=collection, deposit_id=deposit_id) + logger.info(r) diff --git a/swh/deposit/client/__init__.py b/swh/deposit/client/__init__.py --- a/swh/deposit/client/__init__.py +++ b/swh/deposit/client/__init__.py @@ -38,6 +38,8 @@ data = xmltodict.parse(stream, encoding=encoding, process_namespaces=False) if 'entry' in data: data = data['entry'] + if 'sword:error' in data: + data = data['sword:error'] return dict(data) @@ -253,7 +255,8 @@ 'detail': Some more detail about the error if any """ - return _parse_with_filter(xml_content, keys=['summary', 'detail']) + return _parse_with_filter(xml_content, keys=[ + 'summary', 'detail', 'sword:verboseDescription']) def do_execute(self, method, url, info): """Execute the http query to url using method and info information.