diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ click -xmltodict iso8601 requests 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 @@ -15,10 +15,12 @@ import sys from typing import TYPE_CHECKING, Any, Collection, Dict, List, Optional import warnings +import xml.etree.ElementTree as ET import click from swh.deposit.cli import deposit +from swh.deposit.utils import NAMESPACES as NS logger = logging.getLogger(__name__) @@ -108,47 +110,37 @@ metadata xml string """ - import xmltodict - # generate a metadata file with the minimum required metadata - document = { - "atom:entry": { - "@xmlns:atom": "http://www.w3.org/2005/Atom", - "@xmlns:codemeta": "https://doi.org/10.5063/SCHEMA/CODEMETA-2.0", - "@xmlns:schema": "http://schema.org/", - "atom:updated": datetime.now(tz=timezone.utc), # mandatory, cf. docstring - "atom:author": deposit_client, # mandatory, cf. docstring - "atom:title": name, # mandatory, cf. docstring - "codemeta:name": name, # mandatory, cf. docstring - "codemeta:author": [ # mandatory, cf. docstring - {"codemeta:name": author_name} for author_name in authors - ], - }, - } + document = ET.Element(f"{{{NS['atom']}}}entry") + now = datetime.now(tz=timezone.utc) + ET.SubElement(document, f"{{{NS['atom']}}}updated").text = str(now) + ET.SubElement(document, f"{{{NS['atom']}}}author").text = deposit_client + ET.SubElement(document, f"{{{NS['atom']}}}title").text = name + ET.SubElement(document, f"{{{NS['codemeta']}}}name").text = name + for author_name in authors: + author = ET.SubElement(document, f"{{{NS['codemeta']}}}author") + ET.SubElement(author, f"{{{NS['codemeta']}}}name").text = author_name + if external_id: - document["atom:entry"]["codemeta:identifier"] = external_id + ET.SubElement(document, f"{{{NS['codemeta']}}}identifier").text = external_id - swh_deposit_dict: Dict = {} - if create_origin or metadata_provenance_url: - document["atom:entry"][ - "@xmlns:swh" - ] = "https://www.softwareheritage.org/schema/2018/deposit" + swh_deposit_elt = ET.Element(f"{{{NS['swh']}}}deposit") if create_origin: - swh_deposit_dict.update( - {"swh:create_origin": {"swh:origin": {"@url": create_origin}}} - ) + elt = ET.SubElement(swh_deposit_elt, f"{{{NS['swh']}}}create_origin") + ET.SubElement(elt, f"{{{NS['swh']}}}origin").set("url", create_origin) if metadata_provenance_url: - swh_deposit_dict.update( - {"swh:metadata-provenance": {"schema:url": metadata_provenance_url}} - ) + elt = ET.SubElement(swh_deposit_elt, f"{{{NS['swh']}}}metadata-provenance") + ET.SubElement(elt, f"{{{NS['schema']}}}url").text = metadata_provenance_url + + if len(swh_deposit_elt): + document.append(swh_deposit_elt) - if swh_deposit_dict: - document["atom:entry"]["swh:deposit"] = swh_deposit_dict + s = ET.tostring(document, encoding="utf-8").decode() - logging.debug("Atom entry dict to generate as xml: %s", document) - return xmltodict.unparse(document, pretty=True) + logging.debug("Atom entry dict to generate as xml: %s", s) + return s def _collection(client: PublicApiDepositClient) -> str: diff --git a/swh/deposit/tests/api/test_delete.py b/swh/deposit/tests/api/test_delete.py --- a/swh/deposit/tests/api/test_delete.py +++ b/swh/deposit/tests/api/test_delete.py @@ -5,10 +5,10 @@ from collections import defaultdict from typing import Dict, Mapping +import xml.etree.ElementTree as ET from django.urls import reverse_lazy as reverse from rest_framework import status -import xmltodict from swh.deposit.config import ( ARCHIVE_TYPE, @@ -18,6 +18,7 @@ METADATA_TYPE, ) from swh.deposit.models import Deposit, DepositRequest +from swh.deposit.utils import NAMESPACES def count_deposit_request_types(deposit_requests) -> Mapping[str, int]: @@ -83,7 +84,7 @@ # then assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( - xmltodict.parse(response.content)["sword:error"]["summary"] + ET.fromstring(response.content).findtext("atom:summary", namespaces=NAMESPACES) == "You can only act on deposit with status 'partial'" ) @@ -126,7 +127,7 @@ # then assert response.status_code == status.HTTP_400_BAD_REQUEST assert ( - xmltodict.parse(response.content)["sword:error"]["summary"] + ET.fromstring(response.content).findtext("atom:summary", namespaces=NAMESPACES) == "You can only act on deposit with status 'partial'" ) diff --git a/swh/deposit/tests/api/test_deposit_update_binary.py b/swh/deposit/tests/api/test_deposit_update_binary.py --- a/swh/deposit/tests/api/test_deposit_update_binary.py +++ b/swh/deposit/tests/api/test_deposit_update_binary.py @@ -6,11 +6,11 @@ """Tests updates on EM-IRI""" from io import BytesIO +import xml.etree.ElementTree as ET from django.core.files.uploadedfile import InMemoryUploadedFile from django.urls import reverse_lazy as reverse from rest_framework import status -import xmltodict from swh.deposit.config import COL_IRI, DEPOSIT_STATUS_DEPOSITED, EM_IRI, SE_IRI from swh.deposit.models import Deposit, DepositRequest @@ -328,7 +328,7 @@ assert r.status_code == status.HTTP_400_BAD_REQUEST assert ( - xmltodict.parse(r.content)["sword:error"]["summary"] + ET.fromstring(r.content).findtext("atom:summary", namespaces=NAMESPACES) == "You can only act on deposit with status 'partial'" ) @@ -344,7 +344,7 @@ assert r.status_code == status.HTTP_400_BAD_REQUEST assert ( - xmltodict.parse(r.content)["sword:error"]["summary"] + ET.fromstring(r.content).findtext("atom:summary", namespaces=NAMESPACES) == "You can only act on deposit with status 'partial'" ) @@ -360,7 +360,7 @@ assert r.status_code == status.HTTP_400_BAD_REQUEST assert ( - xmltodict.parse(r.content)["sword:error"]["summary"] + ET.fromstring(r.content).findtext("atom:summary", namespaces=NAMESPACES) == "You can only act on deposit with status 'partial'" ) @@ -376,7 +376,7 @@ assert r.status_code == status.HTTP_400_BAD_REQUEST assert ( - xmltodict.parse(r.content)["sword:error"]["summary"] + ET.fromstring(r.content).findtext("atom:summary", namespaces=NAMESPACES) == "You can only act on deposit with status 'partial'" ) @@ -409,7 +409,7 @@ assert r.status_code == status.HTTP_400_BAD_REQUEST assert ( - xmltodict.parse(r.content)["sword:error"]["summary"] + ET.fromstring(r.content).findtext("atom:summary", namespaces=NAMESPACES) == "You can only act on deposit with status 'partial'" ) @@ -423,6 +423,6 @@ assert r.status_code == status.HTTP_400_BAD_REQUEST assert ( - xmltodict.parse(r.content)["sword:error"]["summary"] + ET.fromstring(r.content).findtext("atom:summary", namespaces=NAMESPACES) == "You can only act on deposit with status 'partial'" )