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 @@ -486,3 +486,53 @@ click.echo(yaml.dump(data)) else: logger.info(data) + + +@deposit.command("metadata-only") +@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( + "--metadata", + "metadata_path", + type=click.Path(exists=True), + required=True, + help="Path to xml metadata file", +) +@click.option( + "-f", + "--format", + "output_format", + default="logging", + type=click.Choice(["logging", "yaml", "json"]), + help="Output format results.", +) +@click.pass_context +def metadata_only(ctx, url, username, password, metadata_path, output_format): + """Deposit metadata only upload + + """ + from swh.deposit.client import PublicApiDepositClient + from swh.deposit.utils import parse_swh_reference, parse_xml + + # Parse to check for a swhid presence within the metadata file + with open(metadata_path, "r") as f: + metadata_raw = f.read() + actual_swhid = parse_swh_reference(parse_xml(metadata_raw)) + + if not actual_swhid: + raise InputError("A SWHID must be provided for a metadata-only deposit") + + with trap_and_report_exceptions(): + client = PublicApiDepositClient(url=_url(url), auth=(username, password)) + collection = _collection(client) + result = client.deposit_metadata_only(collection, metadata_path) + + print_result(result, output_format) diff --git a/swh/deposit/client.py b/swh/deposit/client.py --- a/swh/deposit/client.py +++ b/swh/deposit/client.py @@ -545,6 +545,28 @@ return "put" +class CreateMetadataOnlyDepositClient(BaseCreateDepositClient): + """Create metadata-only deposit.""" + + def compute_information(self, *args, **kwargs) -> Dict[str, Any]: + return { + "headers": {"CONTENT-TYPE": "application/atom+xml;type=entry",}, + "filepath": kwargs["metadata_path"], + } + + def parse_result_ok(self, xml_content): + """Given an xml content as string, returns a deposit dict. + + """ + data = parse_xml(xml_content) + keys = [ + "deposit_id", + "deposit_status", + "deposit_date", + ] + return {key: data.get("swh:" + key) for key in keys} + + class CreateMultipartDepositClient(BaseCreateDepositClient): """Create a multipart deposit client.""" @@ -712,3 +734,11 @@ if "error" in r: return r return self.deposit_status(collection, deposit_id) + + def deposit_metadata_only( + self, collection: str, metadata: Optional[str] = None, + ): + assert metadata is not None + return CreateMetadataOnlyDepositClient( + url=self.base_url, auth=self.auth + ).execute(collection, metadata_path=metadata) diff --git a/swh/deposit/tests/cli/test_client.py b/swh/deposit/tests/cli/test_client.py --- a/swh/deposit/tests/cli/test_client.py +++ b/swh/deposit/tests/cli/test_client.py @@ -25,6 +25,7 @@ ) from swh.deposit.client import MaintenanceError, PublicApiDepositClient from swh.deposit.parsers import parse_xml +from swh.model.exceptions import ValidationError from ..conftest import TEST_USER @@ -637,3 +638,103 @@ "deposit_status": "partial", "deposit_id": 321, } + + +def test_cli_metadata_only_deposit_full_metadata_file( + datadir, requests_mock_datadir, cli_runner, atom_dataset, tmp_path, +): + """Post metadata-only deposit through cli + + The metadata file posted by the client already contains the swhid + + """ + api_url_basename = "deposit.test.metadataonly" + swhid = "swh:1:dir:ef04a768181417fbc5eef4243e2507915f24deea" + metadata = atom_dataset["entry-data-with-swhid"].format(swhid=swhid) + metadata_path = os.path.join(tmp_path, "entry-data-with-swhid.xml") + with open(metadata_path, "w") as m: + m.write(metadata) + + expected_deposit_status = { + "deposit_id": "100", + "deposit_status": "done", + "deposit_date": "2020-10-08T13:52:34.509655", + } + + assert expected_deposit_status["deposit_status"] == "done" + + # fmt: off + result = cli_runner.invoke( + cli, + [ + "metadata-only", + "--url", f"https://{api_url_basename}/1", + "--username", TEST_USER["username"], + "--password", TEST_USER["password"], + "--metadata", metadata_path, + "--format", "json", + ], + ) + # fmt: on + assert result.exit_code == 0, result.output + actual_deposit_status = json.loads(result.output) + assert "error" not in actual_deposit_status + assert actual_deposit_status == expected_deposit_status + + +def test_cli_metadata_only_deposit_invalid_swhid( + datadir, requests_mock_datadir, cli_runner, atom_dataset, tmp_path, +): + """Post metadata-only deposit through cli with invalid swhid raises + + """ + api_url_basename = "deposit.test.metadataonly" + invalid_swhid = "ssh:2:sth:xxx" + metadata = atom_dataset["entry-data-with-swhid"].format(swhid=invalid_swhid) + metadata_path = os.path.join(tmp_path, "entry-data-with-swhid.xml") + with open(metadata_path, "w") as f: + f.write(metadata) + + # fmt: off + with pytest.raises(ValidationError, match="Invalid"): + cli_runner.invoke( + cli, + [ + "metadata-only", + "--url", f"https://{api_url_basename}/1", + "--username", TEST_USER["username"], + "--password", TEST_USER["password"], + "--metadata", metadata_path, + "--format", "json", + ], + catch_exceptions=False, + ) + # fmt: on + + +def test_cli_metadata_only_deposit_no_swhid( + datadir, requests_mock_datadir, cli_runner, atom_dataset, tmp_path, +): + """Post metadata-only deposit through cli with invalid swhid raises + + """ + api_url_basename = "deposit.test.metadataonly" + metadata = atom_dataset["entry-data-minimal"] + metadata_path = os.path.join(tmp_path, "entry-data-minimal.xml") + with open(metadata_path, "w") as f: + f.write(metadata) + + with pytest.raises(InputError, match="SWHID must be provided"): + cli_runner.invoke( + cli, + [ + "metadata-only", + "--url", f"https://{api_url_basename}/1", + "--username", TEST_USER["username"], + "--password", TEST_USER["password"], + "--metadata", metadata_path, + "--format", "json", + ], + catch_exceptions=False, + ) + # fmt: on diff --git a/swh/deposit/tests/data/https_deposit.test.metadataonly/1_servicedocument b/swh/deposit/tests/data/https_deposit.test.metadataonly/1_servicedocument new file mode 100644 --- /dev/null +++ b/swh/deposit/tests/data/https_deposit.test.metadataonly/1_servicedocument @@ -0,0 +1,26 @@ + + + + 2.0 + 209715200 + + + The Software Heritage (SWH) Archive + + test Software Collection + application/zip + application/x-tar + Collection Policy + Software Heritage Archive + Collect, Preserve, Share + false + false + http://purl.org/net/sword/package/SimpleZip + https://deposit.test.metadataonly/1/test/ + test + + + diff --git a/swh/deposit/tests/data/https_deposit.test.metadataonly/1_test b/swh/deposit/tests/data/https_deposit.test.metadataonly/1_test new file mode 100644 --- /dev/null +++ b/swh/deposit/tests/data/https_deposit.test.metadataonly/1_test @@ -0,0 +1,12 @@ + + + 100 + 2020-10-08T13:52:34.509655 + done + swh:1:dir:ef04a768181417fbc5eef4243e2507915f24deea + swh:1:dir:ef04a768181417fbc5eef4243e2507915f24deea;origin=https://www.softwareheritage.org/check-deposit-2020-10-08T13:52:34.509655;visit=swh:1:snp:c477c6ef51833127b13a86ece7d75e5b3cc4e93d;anchor=swh:1:rev:f26f3960c175f15f6e24200171d446b86f6f7230;path=/ + +