diff --git a/swh/deposit/api/deposit.py b/swh/deposit/api/collection.py rename from swh/deposit/api/deposit.py rename to swh/deposit/api/collection.py --- a/swh/deposit/api/deposit.py +++ b/swh/deposit/api/collection.py @@ -17,10 +17,10 @@ from .common import ACCEPT_ARCHIVE_CONTENT_TYPES, APIPost -class APIPostDeposit(APIPost): +class CollectionAPI(APIPost): """Deposit request class defining api endpoints for sword deposit. - What's known as 'Col IRI' in the sword specification. + What's known as 'Col-IRI' in the sword specification. HTTP verbs supported: POST diff --git a/swh/deposit/api/deposit_content.py b/swh/deposit/api/content.py rename from swh/deposit/api/deposit_content.py rename to swh/deposit/api/content.py --- a/swh/deposit/api/deposit_content.py +++ b/swh/deposit/api/content.py @@ -12,7 +12,15 @@ from .common import APIBase -class APIContent(APIBase): +class ContentAPI(APIBase): + """Deposit request class defining api endpoints for sword deposit. + + What's known as 'Cont-IRI' and 'File-IRI' in the sword specification. + + HTTP verbs supported: GET + + """ + def get(self, req, collection_name: str, deposit_id: int) -> HttpResponse: checks = self.checks(req, collection_name, deposit_id) if "error" in checks: diff --git a/swh/deposit/api/deposit_update.py b/swh/deposit/api/edit.py rename from swh/deposit/api/deposit_update.py rename to swh/deposit/api/edit.py --- a/swh/deposit/api/deposit_update.py +++ b/swh/deposit/api/edit.py @@ -11,95 +11,20 @@ from swh.deposit.models import Deposit from swh.model.identifiers import parse_swhid -from ..config import CONT_FILE_IRI, DEPOSIT_STATUS_LOAD_SUCCESS, EDIT_SE_IRI, EM_IRI -from ..errors import BAD_REQUEST, BadRequestError, ParserError, make_error_dict -from ..parsers import ( - SWHAtomEntryParser, - SWHFileUploadTarParser, - SWHFileUploadZipParser, - SWHMultiPartParser, +from ..config import ( + DEPOSIT_STATUS_LOAD_SUCCESS, + EDIT_SE_IRI, + EM_IRI, ) -from .common import ACCEPT_ARCHIVE_CONTENT_TYPES, APIDelete, APIPost, APIPut - - -class APIUpdateArchive(APIPost, APIPut, APIDelete): - """Deposit request class defining api endpoints for sword deposit. - - What's known as 'EM IRI' in the sword specification. - - HTTP verbs supported: PUT, POST, DELETE - - """ - - parser_classes = ( - SWHFileUploadZipParser, - SWHFileUploadTarParser, - ) - - def process_put( - self, req, headers, collection_name: str, deposit_id: int - ) -> Dict[str, Any]: - """Replace existing content for the existing deposit. - - source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#protocoloperations_editingcontent_binary # noqa - - Returns: - 204 No content - - """ - if req.content_type not in ACCEPT_ARCHIVE_CONTENT_TYPES: - msg = "Packaging format supported is restricted to %s" % ( - ", ".join(ACCEPT_ARCHIVE_CONTENT_TYPES) - ) - return make_error_dict(BAD_REQUEST, msg) - - return self._binary_upload( - req, headers, collection_name, deposit_id=deposit_id, replace_archives=True - ) - - def process_post( - self, req, headers: Dict, collection_name: str, deposit_id: Optional[int] = None - ) -> Tuple[int, str, Dict]: - """Add new content to the existing deposit. - - source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#protocoloperations_addingcontent_mediaresource # noqa - - Returns: - 201 Created - Headers: Location: [Cont-File-IRI] - - Body: [optional Deposit Receipt] - - """ - if req.content_type not in ACCEPT_ARCHIVE_CONTENT_TYPES: - msg = "Packaging format supported is restricted to %s" % ( - ", ".join(ACCEPT_ARCHIVE_CONTENT_TYPES) - ) - unused = 0 - return unused, "unused", make_error_dict(BAD_REQUEST, msg) - - return ( - status.HTTP_201_CREATED, - CONT_FILE_IRI, - self._binary_upload(req, headers, collection_name, deposit_id), - ) - - def process_delete(self, req, collection_name: str, deposit_id: int) -> Dict: - """Delete content (archives) from existing deposit. - - source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#protocoloperations_deletingcontent # noqa - - Returns: - 204 Created - - """ - return self._delete_archives(collection_name, deposit_id) +from ..errors import BAD_REQUEST, BadRequestError, ParserError, make_error_dict +from ..parsers import SWHAtomEntryParser, SWHMultiPartParser +from .common import APIDelete, APIPost, APIPut -class APIUpdateMetadata(APIPost, APIPut, APIDelete): +class EditAPI(APIPost, APIPut, APIDelete): """Deposit request class defining api endpoints for sword deposit. - What's known as 'Edit IRI' (and SE IRI) in the sword specification. + What's known as 'Edit-IRI' and 'SE-IRI' in the sword specification. HTTP verbs supported: POST (SE IRI), PUT (Edit IRI), DELETE diff --git a/swh/deposit/api/edit_media.py b/swh/deposit/api/edit_media.py new file mode 100644 --- /dev/null +++ b/swh/deposit/api/edit_media.py @@ -0,0 +1,87 @@ +# Copyright (C) 2017-2020 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 + +from typing import Any, Dict, Optional, Tuple + +from rest_framework import status + +from ..config import CONT_FILE_IRI +from ..errors import BAD_REQUEST, make_error_dict +from ..parsers import SWHFileUploadTarParser, SWHFileUploadZipParser +from .common import ACCEPT_ARCHIVE_CONTENT_TYPES, APIDelete, APIPost, APIPut + + +class EditMediaAPI(APIPost, APIPut, APIDelete): + """Deposit request class defining api endpoints for sword deposit. + + What's known as 'EM IRI' in the sword specification. + + HTTP verbs supported: PUT, POST, DELETE + + """ + + parser_classes = ( + SWHFileUploadZipParser, + SWHFileUploadTarParser, + ) + + def process_put( + self, req, headers, collection_name: str, deposit_id: int + ) -> Dict[str, Any]: + """Replace existing content for the existing deposit. + + source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#protocoloperations_editingcontent_binary # noqa + + Returns: + 204 No content + + """ + if req.content_type not in ACCEPT_ARCHIVE_CONTENT_TYPES: + msg = "Packaging format supported is restricted to %s" % ( + ", ".join(ACCEPT_ARCHIVE_CONTENT_TYPES) + ) + return make_error_dict(BAD_REQUEST, msg) + + return self._binary_upload( + req, headers, collection_name, deposit_id=deposit_id, replace_archives=True + ) + + def process_post( + self, req, headers: Dict, collection_name: str, deposit_id: Optional[int] = None + ) -> Tuple[int, str, Dict]: + """Add new content to the existing deposit. + + source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#protocoloperations_addingcontent_mediaresource # noqa + + Returns: + 201 Created + Headers: Location: [Cont-File-IRI] + + Body: [optional Deposit Receipt] + + """ + if req.content_type not in ACCEPT_ARCHIVE_CONTENT_TYPES: + msg = "Packaging format supported is restricted to %s" % ( + ", ".join(ACCEPT_ARCHIVE_CONTENT_TYPES) + ) + unused = 0 + return unused, "unused", make_error_dict(BAD_REQUEST, msg) + + return ( + status.HTTP_201_CREATED, + CONT_FILE_IRI, + self._binary_upload(req, headers, collection_name, deposit_id), + ) + + def process_delete(self, req, collection_name: str, deposit_id: int) -> Dict: + """Delete content (archives) from existing deposit. + + source: http://swordapp.github.io/SWORDv2-Profile/SWORDProfile.html#protocoloperations_deletingcontent # noqa + + Returns: + 204 Created + + """ + return self._delete_archives(collection_name, deposit_id) diff --git a/swh/deposit/api/service_document.py b/swh/deposit/api/service_document.py --- a/swh/deposit/api/service_document.py +++ b/swh/deposit/api/service_document.py @@ -11,7 +11,7 @@ from .common import ACCEPT_ARCHIVE_CONTENT_TYPES, ACCEPT_PACKAGINGS, APIBase -class APIServiceDocument(APIBase): +class ServiceDocumentAPI(APIBase): def get(self, req, *args, **kwargs): client = DepositClient.objects.get(username=req.user) diff --git a/swh/deposit/api/deposit_status.py b/swh/deposit/api/state.py rename from swh/deposit/api/deposit_status.py rename to swh/deposit/api/state.py --- a/swh/deposit/api/deposit_status.py +++ b/swh/deposit/api/state.py @@ -13,10 +13,10 @@ from .converters import convert_status_detail -class APIStatus(APIBase): +class StateAPI(APIBase): """Deposit status. - What's known as 'State IRI' in the sword specification. + What's known as 'State-IRI' in the sword specification. HTTP verbs supported: GET diff --git a/swh/deposit/api/urls.py b/swh/deposit/api/urls.py --- a/swh/deposit/api/urls.py +++ b/swh/deposit/api/urls.py @@ -11,11 +11,12 @@ from django.shortcuts import render from ..config import COL_IRI, CONT_FILE_IRI, EDIT_SE_IRI, EM_IRI, SD_IRI, STATE_IRI -from .deposit import APIPostDeposit -from .deposit_content import APIContent -from .deposit_status import APIStatus -from .deposit_update import APIUpdateArchive, APIUpdateMetadata -from .service_document import APIServiceDocument +from .collection import CollectionAPI +from .content import ContentAPI +from .edit import EditAPI +from .edit_media import EditMediaAPI +from .service_document import ServiceDocumentAPI +from .state import StateAPI def api_view(req): @@ -28,16 +29,16 @@ url(r"^$", api_view, name="api"), # SD IRI - Service Document IRI # -> GET - url(r"^servicedocument/", APIServiceDocument.as_view(), name=SD_IRI), - # Col IRI - Collection IRI + url(r"^servicedocument/", ServiceDocumentAPI.as_view(), name=SD_IRI), + # Col-IRI - Collection IRI # -> POST - url(r"^(?P[^/]+)/$", APIPostDeposit.as_view(), name=COL_IRI), + url(r"^(?P[^/]+)/$", CollectionAPI.as_view(), name=COL_IRI), # EM IRI - Atom Edit Media IRI (update archive IRI) # -> PUT (update-in-place existing archive) # -> POST (add new archive) url( r"^(?P[^/]+)/(?P[^/]+)/media/$", - APIUpdateArchive.as_view(), + EditMediaAPI.as_view(), name=EM_IRI, ), # Edit IRI - Atom Entry Edit IRI (update metadata IRI) @@ -46,23 +47,23 @@ # -> POST (add new metadata) url( r"^(?P[^/]+)/(?P[^/]+)/metadata/$", - APIUpdateMetadata.as_view(), + EditAPI.as_view(), name=EDIT_SE_IRI, ), # State IRI # -> GET url( r"^(?P[^/]+)/(?P[^/]+)/status/$", - APIStatus.as_view(), + StateAPI.as_view(), name=STATE_IRI, ), - # Cont/File IRI + # Cont-IRI # -> GET url( r"^(?P[^/]+)/(?P[^/]+)/content/$", - APIContent.as_view(), + ContentAPI.as_view(), name=CONT_FILE_IRI, ), # specification is not clear about - # FILE-IRI, we assume it's the same as - # the CONT-IRI one + # File-IRI, we assume it's the same as + # the Cont-IRI one ] diff --git a/swh/deposit/tests/api/test_deposit.py b/swh/deposit/tests/api/test_collection.py rename from swh/deposit/tests/api/test_deposit.py rename to swh/deposit/tests/api/test_collection.py diff --git a/swh/deposit/tests/api/test_deposit_atom.py b/swh/deposit/tests/api/test_collection_post_atom.py rename from swh/deposit/tests/api/test_deposit_atom.py rename to swh/deposit/tests/api/test_collection_post_atom.py --- a/swh/deposit/tests/api/test_deposit_atom.py +++ b/swh/deposit/tests/api/test_collection_post_atom.py @@ -3,6 +3,8 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +"""Tests the handling of the Atom content when doing a POST Col-IRI.""" + from io import BytesIO from django.urls import reverse diff --git a/swh/deposit/tests/api/test_deposit_binary.py b/swh/deposit/tests/api/test_collection_post_binary.py rename from swh/deposit/tests/api/test_deposit_binary.py rename to swh/deposit/tests/api/test_collection_post_binary.py --- a/swh/deposit/tests/api/test_deposit_binary.py +++ b/swh/deposit/tests/api/test_collection_post_binary.py @@ -3,6 +3,8 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +"""Tests the handling of the binary content when doing a POST Col-IRI.""" + from io import BytesIO from django.core.files.uploadedfile import InMemoryUploadedFile diff --git a/swh/deposit/tests/api/test_deposit_metadata.py b/swh/deposit/tests/api/test_collection_post_metadata.py rename from swh/deposit/tests/api/test_deposit_metadata.py rename to swh/deposit/tests/api/test_collection_post_metadata.py --- a/swh/deposit/tests/api/test_deposit_metadata.py +++ b/swh/deposit/tests/api/test_collection_post_metadata.py @@ -3,6 +3,8 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +"""Tests metadata is loaded when sent via a POST Col-IRI""" + from io import BytesIO import attr diff --git a/swh/deposit/tests/api/test_deposit_multipart.py b/swh/deposit/tests/api/test_collection_post_multipart.py rename from swh/deposit/tests/api/test_deposit_multipart.py rename to swh/deposit/tests/api/test_collection_post_multipart.py --- a/swh/deposit/tests/api/test_deposit_multipart.py +++ b/swh/deposit/tests/api/test_collection_post_multipart.py @@ -3,6 +3,8 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +"""Tests handling of multipart requests to POST Col-IRI.""" + from io import BytesIO from django.core.files.uploadedfile import InMemoryUploadedFile diff --git a/swh/deposit/tests/api/test_deposit_delete.py b/swh/deposit/tests/api/test_delete.py rename from swh/deposit/tests/api/test_deposit_delete.py rename to swh/deposit/tests/api/test_delete.py diff --git a/swh/deposit/tests/api/test_deposit_content.py b/swh/deposit/tests/api/test_get_file.py rename from swh/deposit/tests/api/test_deposit_content.py rename to swh/deposit/tests/api/test_get_file.py --- a/swh/deposit/tests/api/test_deposit_content.py +++ b/swh/deposit/tests/api/test_get_file.py @@ -3,6 +3,8 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +"""Tests 'GET File-IRI'.""" + from django.urls import reverse from rest_framework import status