diff --git a/swh/deposit/api/collection.py b/swh/deposit/api/collection.py --- a/swh/deposit/api/collection.py +++ b/swh/deposit/api/collection.py @@ -14,7 +14,7 @@ SWHFileUploadZipParser, SWHMultiPartParser, ) -from .common import ACCEPT_ARCHIVE_CONTENT_TYPES, APIPost +from .common import ACCEPT_ARCHIVE_CONTENT_TYPES, APIPost, ParsedRequestHeaders class CollectionAPI(APIPost): @@ -36,7 +36,7 @@ def process_post( self, req, - headers: Dict[str, Any], + headers: ParsedRequestHeaders, collection_name: str, deposit_id: Optional[int] = None, ) -> Tuple[int, str, Dict[str, Any]]: diff --git a/swh/deposit/api/common.py b/swh/deposit/api/common.py --- a/swh/deposit/api/common.py +++ b/swh/deposit/api/common.py @@ -74,6 +74,20 @@ ACCEPT_ARCHIVE_CONTENT_TYPES = ["application/zip", "application/x-tar"] +@attr.s +class ParsedRequestHeaders: + content_type = attr.ib(type=str) + content_length = attr.ib(type=Optional[int]) + in_progress = attr.ib(type=bool) + content_disposition = attr.ib(type=Optional[str]) + content_md5sum = attr.ib(type=Optional[bytes]) + packaging = attr.ib(type=Optional[str]) + slug = attr.ib(type=Optional[str]) + on_behalf_of = attr.ib(type=Optional[str]) + metadata_relevant = attr.ib(type=Optional[str]) + swhid = attr.ib(type=Optional[str]) + + class AuthenticatedAPIView(APIView): """Mixin intended as a based API view to enforce the basic authentication check @@ -89,7 +103,7 @@ """ - def _read_headers(self, request: Request) -> Dict[str, Any]: + def _read_headers(self, request: Request) -> ParsedRequestHeaders: """Read and unify the necessary headers from the request (those are not stored in the same location or not properly formatted). @@ -109,14 +123,13 @@ """ meta = request._request.META - content_type = request.content_type + content_length = meta.get("CONTENT_LENGTH") if content_length and isinstance(content_length, str): content_length = int(content_length) # final deposit if not provided in_progress = meta.get("HTTP_IN_PROGRESS", False) - content_disposition = meta.get("HTTP_CONTENT_DISPOSITION") if isinstance(in_progress, str): in_progress = in_progress.lower() == "true" @@ -124,25 +137,18 @@ if content_md5sum: content_md5sum = bytes.fromhex(content_md5sum) - packaging = meta.get("HTTP_PACKAGING") - slug = meta.get("HTTP_SLUG") - on_behalf_of = meta.get("HTTP_ON_BEHALF_OF") - metadata_relevant = meta.get("HTTP_METADATA_RELEVANT") - - swhid = meta.get("HTTP_X_CHECK_SWHID") - - return { - "content-type": content_type, - "content-length": content_length, - "in-progress": in_progress, - "content-disposition": content_disposition, - "content-md5sum": content_md5sum, - "packaging": packaging, - "slug": slug, - "on-behalf-of": on_behalf_of, - "metadata-relevant": metadata_relevant, - "swhid": swhid, - } + return ParsedRequestHeaders( + content_type=request.content_type, + content_length=content_length, + in_progress=in_progress, + content_disposition=meta.get("HTTP_CONTENT_DISPOSITION"), + content_md5sum=content_md5sum, + packaging=meta.get("HTTP_PACKAGING"), + slug=meta.get("HTTP_SLUG"), + on_behalf_of=meta.get("HTTP_ON_BEHALF_OF"), + metadata_relevant=meta.get("HTTP_METADATA_RELEVANT"), + swhid=meta.get("HTTP_X_CHECK_SWHID"), + ) def _compute_md5(self, filehandler) -> bytes: """Compute uploaded file's md5 sum. @@ -334,7 +340,7 @@ return {} def _check_preconditions_on( - self, filehandler, md5sum: str, content_length: Optional[int] = None + self, filehandler, md5sum: Optional[bytes], content_length: Optional[int] = None ) -> Optional[Dict]: """Check preconditions on provided file are respected. That is the length and/or the md5sum hash match the file's content. @@ -379,7 +385,7 @@ def _binary_upload( self, request: Request, - headers: Dict[str, Any], + headers: ParsedRequestHeaders, collection_name: str, deposit_id: Optional[int] = None, replace_metadata: bool = False, @@ -393,7 +399,7 @@ Args: request (Request): the request holding information to parse and inject in db - headers (dict): request headers formatted + headers (ParsedRequestHeaders): parsed request headers collection_name (str): the associated client deposit_id (id): deposit identifier if provided replace_metadata (bool): 'Update or add' request to existing @@ -424,7 +430,7 @@ - 415 (unsupported media type) if a wrong media type is provided """ - content_length = headers["content-length"] + content_length = headers.content_length if not content_length: return make_error_dict( BAD_REQUEST, @@ -432,7 +438,7 @@ "For archive deposit, the CONTENT_LENGTH header must be sent.", ) - content_disposition = headers["content-disposition"] + content_disposition = headers.content_disposition if not content_disposition: return make_error_dict( BAD_REQUEST, @@ -440,7 +446,7 @@ "For archive deposit, the CONTENT_DISPOSITION header must be sent.", ) - packaging = headers["packaging"] + packaging = headers.packaging if packaging and packaging not in ACCEPT_PACKAGINGS: return make_error_dict( BAD_REQUEST, @@ -451,13 +457,13 @@ filehandler = request.FILES["file"] precondition_status_response = self._check_preconditions_on( - filehandler, headers["content-md5sum"], content_length + filehandler, headers.content_md5sum, content_length ) if precondition_status_response: return precondition_status_response - slug = headers.get("slug") + slug = headers.slug if check_slug_is_present and not slug: return make_missing_slug_error() @@ -466,7 +472,7 @@ deposit = self._deposit_put( request, deposit_id=deposit_id, - in_progress=headers["in-progress"], + in_progress=headers.in_progress, external_id=slug, ) self._deposit_request_put( @@ -495,7 +501,7 @@ def _multipart_upload( self, request: Request, - headers: Dict[str, Any], + headers: ParsedRequestHeaders, collection_name: str, deposit_id: Optional[int] = None, replace_metadata: bool = False, @@ -511,7 +517,7 @@ Args: request (Request): the request holding information to parse and inject in db - headers: request headers formatted + headers: parsed request headers collection_name: the associated client deposit_id: deposit identifier if provided replace_metadata: 'Update or add' request to existing @@ -542,7 +548,7 @@ - 415 (unsupported media type) if a wrong media type is provided """ - slug = headers.get("slug") + slug = headers.slug if check_slug_is_present and not slug: return make_missing_slug_error() @@ -587,7 +593,7 @@ filehandler = data["application/x-tar"] precondition_status_response = self._check_preconditions_on( - filehandler, headers["content-md5sum"] + filehandler, headers.content_md5sum ) if precondition_status_response: @@ -607,7 +613,7 @@ deposit = self._deposit_put( request, deposit_id=deposit_id, - in_progress=headers["in-progress"], + in_progress=headers.in_progress, external_id=slug, ) deposit_request_data = { @@ -722,7 +728,7 @@ def _atom_entry( self, request: Request, - headers: Dict[str, Any], + headers: ParsedRequestHeaders, collection_name: str, deposit_id: Optional[int] = None, replace_metadata: bool = False, @@ -734,7 +740,7 @@ Args: request: the request holding information to parse and inject in db - headers: request headers formatted + headers: parsed request headers collection_name: the associated client deposit_id: deposit identifier if provided replace_metadata: 'Update or add' request to existing @@ -788,9 +794,9 @@ return make_error_dict(PARSING_ERROR, "Invalid SWHID reference", str(e),) if swhid is not None: - external_id = metadata.get("external_identifier", headers.get("slug")) + external_id = metadata.get("external_identifier", headers.slug) else: - slug = headers.get("slug") + slug = headers.slug if check_slug_is_present and not slug: return make_missing_slug_error() @@ -799,7 +805,7 @@ deposit = self._deposit_put( request, deposit_id=deposit_id, - in_progress=headers["in-progress"], + in_progress=headers.in_progress, external_id=external_id, ) @@ -841,14 +847,18 @@ } def _empty_post( - self, request: Request, headers: Dict, collection_name: str, deposit_id: int + self, + request: Request, + headers: ParsedRequestHeaders, + collection_name: str, + deposit_id: int, ) -> Dict[str, Any]: """Empty post to finalize an empty deposit. Args: request: the request holding information to parse and inject in db - headers: request headers formatted + headers: parsed request headers collection_name: the associated client deposit_id: deposit identifier @@ -892,7 +902,7 @@ def additional_checks( self, request: Request, - headers: Dict[str, Any], + headers: ParsedRequestHeaders, collection_name: str, deposit_id: Optional[int] = None, ) -> Dict[str, Any]: @@ -948,7 +958,7 @@ if checks: return checks - if headers["on-behalf-of"]: + if headers.on_behalf_of: return make_error_dict(MEDIATION_NOT_ALLOWED, "Mediation is not supported.") checks = self.additional_checks(request, headers, collection_name, deposit_id) @@ -958,7 +968,7 @@ return {"headers": headers} def restrict_access( - self, request: Request, headers: Dict, deposit: Deposit + self, request: Request, headers: ParsedRequestHeaders, deposit: Deposit ) -> Dict[str, Any]: """Allow modifications on deposit with status 'partial' only, reject the rest. @@ -1094,7 +1104,7 @@ def process_post( self, request, - headers: Dict, + headers: ParsedRequestHeaders, collection_name: str, deposit_id: Optional[int] = None, ) -> Tuple[int, str, Dict]: @@ -1141,7 +1151,11 @@ @abstractmethod def process_put( - self, request: Request, headers: Dict, collection_name: str, deposit_id: int + self, + request: Request, + headers: ParsedRequestHeaders, + collection_name: str, + deposit_id: int, ) -> Dict[str, Any]: """Routine to deal with updating a deposit in some way. diff --git a/swh/deposit/api/edit.py b/swh/deposit/api/edit.py --- a/swh/deposit/api/edit.py +++ b/swh/deposit/api/edit.py @@ -13,7 +13,7 @@ from ..config import DEPOSIT_STATUS_LOAD_SUCCESS from ..errors import BAD_REQUEST, BadRequestError, ParserError, make_error_dict from ..parsers import SWHAtomEntryParser, SWHMultiPartParser -from .common import APIDelete, APIPut +from .common import APIDelete, APIPut, ParsedRequestHeaders class EditAPI(APIPut, APIDelete): @@ -28,7 +28,7 @@ parser_classes = (SWHMultiPartParser, SWHAtomEntryParser) def restrict_access( - self, request: Request, headers: Dict, deposit: Deposit + self, request: Request, headers: ParsedRequestHeaders, deposit: Deposit ) -> Dict[str, Any]: """Relax restriction access to allow metadata update on deposit with status "done" when a swhid is provided. @@ -36,7 +36,7 @@ """ if ( request.method == "PUT" - and headers["swhid"] is not None + and headers.swhid is not None and deposit.status == DEPOSIT_STATUS_LOAD_SUCCESS ): # Allow metadata update on deposit with status "done" when swhid provided @@ -45,7 +45,11 @@ return super().restrict_access(request, headers, deposit) def process_put( - self, request, headers: Dict, collection_name: str, deposit_id: int + self, + request, + headers: ParsedRequestHeaders, + collection_name: str, + deposit_id: int, ) -> Dict[str, Any]: """This allows the following scenarios: @@ -71,7 +75,7 @@ 204 No content """ # noqa - swhid = headers.get("swhid") + swhid = headers.swhid if swhid is None: if request.content_type.startswith("multipart/"): return self._multipart_upload( diff --git a/swh/deposit/api/edit_media.py b/swh/deposit/api/edit_media.py --- a/swh/deposit/api/edit_media.py +++ b/swh/deposit/api/edit_media.py @@ -10,7 +10,13 @@ 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 +from .common import ( + ACCEPT_ARCHIVE_CONTENT_TYPES, + APIDelete, + APIPost, + APIPut, + ParsedRequestHeaders, +) class EditMediaAPI(APIPost, APIPut, APIDelete): @@ -28,7 +34,7 @@ ) def process_put( - self, req, headers, collection_name: str, deposit_id: int + self, req, headers: ParsedRequestHeaders, collection_name: str, deposit_id: int ) -> Dict[str, Any]: """Replace existing content for the existing deposit. @@ -49,7 +55,11 @@ ) def process_post( - self, req, headers: Dict, collection_name: str, deposit_id: Optional[int] = None + self, + req, + headers: ParsedRequestHeaders, + collection_name: str, + deposit_id: Optional[int] = None, ) -> Tuple[int, str, Dict]: """Add new content to the existing deposit. diff --git a/swh/deposit/api/private/deposit_update_status.py b/swh/deposit/api/private/deposit_update_status.py --- a/swh/deposit/api/private/deposit_update_status.py +++ b/swh/deposit/api/private/deposit_update_status.py @@ -12,7 +12,7 @@ from . import APIPrivateView from ...errors import BAD_REQUEST, make_error_dict from ...models import DEPOSIT_STATUS_DETAIL, DEPOSIT_STATUS_LOAD_SUCCESS, Deposit -from ..common import APIPut +from ..common import APIPut, ParsedRequestHeaders MANDATORY_KEYS = ["origin_url", "revision_id", "directory_id", "snapshot_id"] @@ -26,7 +26,9 @@ parser_classes = (JSONParser,) - def additional_checks(self, request, headers, collection_name, deposit_id=None): + def additional_checks( + self, request, headers: ParsedRequestHeaders, collection_name, deposit_id=None + ): """Enrich existing checks to the default ones. New checks: @@ -64,7 +66,11 @@ return {} def process_put( - self, request, headers: Dict, collection_name: str, deposit_id: int + self, + request, + headers: ParsedRequestHeaders, + collection_name: str, + deposit_id: int, ) -> Dict: """Update the deposit with status and SWHIDs diff --git a/swh/deposit/api/sword_edit.py b/swh/deposit/api/sword_edit.py --- a/swh/deposit/api/sword_edit.py +++ b/swh/deposit/api/sword_edit.py @@ -12,7 +12,7 @@ from ..config import EDIT_IRI, EM_IRI from ..parsers import SWHAtomEntryParser, SWHMultiPartParser -from .common import APIPost +from .common import APIPost, ParsedRequestHeaders class SwordEditAPI(APIPost): @@ -35,7 +35,7 @@ def process_post( self, request, - headers: Dict, + headers: ParsedRequestHeaders, collection_name: str, deposit_id: Optional[int] = None, ) -> Tuple[int, str, Dict]: @@ -70,8 +70,8 @@ ) return (status.HTTP_201_CREATED, EM_IRI, data) - content_length = headers["content-length"] or 0 - if content_length == 0 and headers["in-progress"] is False: + content_length = headers.content_length or 0 + if content_length == 0 and headers.in_progress is False: # check for final empty post data = self._empty_post(request, headers, collection_name, deposit_id) return (status.HTTP_200_OK, EDIT_IRI, data)