Changeset View
Changeset View
Standalone View
Standalone View
swh/deposit/api/common.py
Show First 20 Lines • Show All 80 Lines • ▼ Show 20 Lines | class ParsedRequestHeaders: | ||||
content_md5sum = attr.ib(type=Optional[bytes]) | content_md5sum = attr.ib(type=Optional[bytes]) | ||||
packaging = attr.ib(type=Optional[str]) | packaging = attr.ib(type=Optional[str]) | ||||
slug = attr.ib(type=Optional[str]) | slug = attr.ib(type=Optional[str]) | ||||
on_behalf_of = attr.ib(type=Optional[str]) | on_behalf_of = attr.ib(type=Optional[str]) | ||||
metadata_relevant = attr.ib(type=Optional[str]) | metadata_relevant = attr.ib(type=Optional[str]) | ||||
swhid = attr.ib(type=Optional[str]) | swhid = attr.ib(type=Optional[str]) | ||||
@attr.s | |||||
class Receipt: | |||||
"""Data computed while handling the request body that will be served in the | |||||
Deposit Receipt.""" | |||||
deposit_id = attr.ib(type=int) | |||||
deposit_date = attr.ib(type=datetime.datetime) | |||||
status = attr.ib(type=str) | |||||
archive = attr.ib(type=Optional[str]) | |||||
def _compute_md5(filehandler: InMemoryUploadedFile) -> bytes: | def _compute_md5(filehandler: InMemoryUploadedFile) -> bytes: | ||||
h = hashlib.md5() | h = hashlib.md5() | ||||
for chunk in filehandler: | for chunk in filehandler: | ||||
h.update(chunk) # type: ignore | h.update(chunk) # type: ignore | ||||
return h.digest() | return h.digest() | ||||
class AuthenticatedAPIView(APIView): | class AuthenticatedAPIView(APIView): | ||||
▲ Show 20 Lines • Show All 270 Lines • ▼ Show 20 Lines | def _binary_upload( | ||||
self, | self, | ||||
request: Request, | request: Request, | ||||
headers: ParsedRequestHeaders, | headers: ParsedRequestHeaders, | ||||
collection_name: str, | collection_name: str, | ||||
deposit_id: Optional[int] = None, | deposit_id: Optional[int] = None, | ||||
replace_metadata: bool = False, | replace_metadata: bool = False, | ||||
replace_archives: bool = False, | replace_archives: bool = False, | ||||
check_slug_is_present: bool = False, | check_slug_is_present: bool = False, | ||||
) -> Dict[str, Any]: | ) -> Receipt: | ||||
"""Binary upload routine. | """Binary upload routine. | ||||
Other than such a request, a 415 response is returned. | Other than such a request, a 415 response is returned. | ||||
Args: | Args: | ||||
request (Request): the request holding information to parse | request (Request): the request holding information to parse | ||||
and inject in db | and inject in db | ||||
headers (ParsedRequestHeaders): parsed request headers | headers (ParsedRequestHeaders): parsed request headers | ||||
collection_name (str): the associated client | collection_name (str): the associated client | ||||
deposit_id (id): deposit identifier if provided | deposit_id (id): deposit identifier if provided | ||||
replace_metadata (bool): 'Update or add' request to existing | replace_metadata (bool): 'Update or add' request to existing | ||||
deposit. If False (default), this adds new metadata request to | deposit. If False (default), this adds new metadata request to | ||||
existing ones. Otherwise, this will replace existing metadata. | existing ones. Otherwise, this will replace existing metadata. | ||||
replace_archives (bool): 'Update or add' request to existing | replace_archives (bool): 'Update or add' request to existing | ||||
deposit. If False (default), this adds new archive request to | deposit. If False (default), this adds new archive request to | ||||
existing ones. Otherwise, this will replace existing archives. | existing ones. Otherwise, this will replace existing archives. | ||||
ones. | ones. | ||||
check_slug_is_present: Check for the slug header if True and raise | check_slug_is_present: Check for the slug header if True and raise | ||||
if not present | if not present | ||||
Returns: | Raises: | ||||
In the optimal case a dict with the following keys: | |||||
- deposit_id (int): Deposit identifier | |||||
- deposit_date (date): Deposit date | |||||
- archive: None (no archive is provided here) | |||||
Otherwise, a dictionary with the key error and the | |||||
associated failures, either: | |||||
- 400 (bad request) if the request is not providing an external | - 400 (bad request) if the request is not providing an external | ||||
identifier | identifier | ||||
- 413 (request entity too large) if the length of the | - 413 (request entity too large) if the length of the | ||||
archive exceeds the max size configured | archive exceeds the max size configured | ||||
- 412 (precondition failed) if the length or md5 hash provided | - 412 (precondition failed) if the length or md5 hash provided | ||||
mismatch the reality of the archive | mismatch the reality of the archive | ||||
- 415 (unsupported media type) if a wrong media type is provided | - 415 (unsupported media type) if a wrong media type is provided | ||||
▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | ) -> Receipt: | ||||
) | ) | ||||
self._deposit_request_put( | self._deposit_request_put( | ||||
deposit, | deposit, | ||||
{ARCHIVE_KEY: archive_metadata}, | {ARCHIVE_KEY: archive_metadata}, | ||||
replace_metadata=replace_metadata, | replace_metadata=replace_metadata, | ||||
replace_archives=replace_archives, | replace_archives=replace_archives, | ||||
) | ) | ||||
return { | return Receipt( | ||||
"deposit_id": deposit.id, | deposit_id=deposit.id, | ||||
"deposit_date": deposit.reception_date, | deposit_date=deposit.reception_date, | ||||
"status": deposit.status, | status=deposit.status, | ||||
"archive": filehandler.name, | archive=filehandler.name, | ||||
} | ) | ||||
def _read_metadata(self, metadata_stream) -> Tuple[bytes, Dict[str, Any]]: | def _read_metadata(self, metadata_stream) -> Tuple[bytes, Dict[str, Any]]: | ||||
"""Given a metadata stream, reads the metadata and returns both the | """Given a metadata stream, reads the metadata and returns both the | ||||
parsed and the raw metadata. | parsed and the raw metadata. | ||||
""" | """ | ||||
raw_metadata = metadata_stream.read() | raw_metadata = metadata_stream.read() | ||||
metadata = parse_xml(raw_metadata) | metadata = parse_xml(raw_metadata) | ||||
return raw_metadata, metadata | return raw_metadata, metadata | ||||
def _multipart_upload( | def _multipart_upload( | ||||
self, | self, | ||||
request: Request, | request: Request, | ||||
headers: ParsedRequestHeaders, | headers: ParsedRequestHeaders, | ||||
collection_name: str, | collection_name: str, | ||||
deposit_id: Optional[int] = None, | deposit_id: Optional[int] = None, | ||||
replace_metadata: bool = False, | replace_metadata: bool = False, | ||||
replace_archives: bool = False, | replace_archives: bool = False, | ||||
check_slug_is_present: bool = False, | check_slug_is_present: bool = False, | ||||
) -> Dict: | ) -> Receipt: | ||||
"""Multipart upload supported with exactly: | """Multipart upload supported with exactly: | ||||
- 1 archive (zip) | - 1 archive (zip) | ||||
- 1 atom entry | - 1 atom entry | ||||
Other than such a request, a 415 response is returned. | Other than such a request, a 415 response is returned. | ||||
Args: | Args: | ||||
request (Request): the request holding information to parse | request (Request): the request holding information to parse | ||||
and inject in db | and inject in db | ||||
headers: parsed request headers | headers: parsed request headers | ||||
collection_name: the associated client | collection_name: the associated client | ||||
deposit_id: deposit identifier if provided | deposit_id: deposit identifier if provided | ||||
replace_metadata: 'Update or add' request to existing | replace_metadata: 'Update or add' request to existing | ||||
deposit. If False (default), this adds new metadata request to | deposit. If False (default), this adds new metadata request to | ||||
existing ones. Otherwise, this will replace existing metadata. | existing ones. Otherwise, this will replace existing metadata. | ||||
replace_archives: 'Update or add' request to existing | replace_archives: 'Update or add' request to existing | ||||
deposit. If False (default), this adds new archive request to | deposit. If False (default), this adds new archive request to | ||||
existing ones. Otherwise, this will replace existing archives. | existing ones. Otherwise, this will replace existing archives. | ||||
ones. | ones. | ||||
check_slug_is_present: Check for the slug header if True and raise | check_slug_is_present: Check for the slug header if True and raise | ||||
if not present | if not present | ||||
Returns: | Raises: | ||||
In the optimal case a dict with the following keys: | |||||
- deposit_id (int): Deposit identifier | |||||
- deposit_date (date): Deposit date | |||||
- archive: None (no archive is provided here) | |||||
Otherwise, a dictionary with the key error and the | |||||
associated failures, either: | |||||
- 400 (bad request) if the request is not providing an external | - 400 (bad request) if the request is not providing an external | ||||
identifier | identifier | ||||
- 412 (precondition failed) if the potentially md5 hash provided | - 412 (precondition failed) if the potentially md5 hash provided | ||||
mismatch the reality of the archive | mismatch the reality of the archive | ||||
- 413 (request entity too large) if the length of the | - 413 (request entity too large) if the length of the | ||||
archive exceeds the max size configured | archive exceeds the max size configured | ||||
- 415 (unsupported media type) if a wrong media type is provided | - 415 (unsupported media type) if a wrong media type is provided | ||||
▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | ) -> Receipt: | ||||
METADATA_KEY: metadata, | METADATA_KEY: metadata, | ||||
RAW_METADATA_KEY: raw_metadata, | RAW_METADATA_KEY: raw_metadata, | ||||
} | } | ||||
self._deposit_request_put( | self._deposit_request_put( | ||||
deposit, deposit_request_data, replace_metadata, replace_archives | deposit, deposit_request_data, replace_metadata, replace_archives | ||||
) | ) | ||||
assert filehandler is not None | assert filehandler is not None | ||||
return { | return Receipt( | ||||
"deposit_id": deposit.id, | deposit_id=deposit.id, | ||||
"deposit_date": deposit.reception_date, | deposit_date=deposit.reception_date, | ||||
"archive": filehandler.name, | archive=filehandler.name, | ||||
"status": deposit.status, | status=deposit.status, | ||||
} | ) | ||||
def _store_metadata_deposit( | def _store_metadata_deposit( | ||||
self, | self, | ||||
deposit: Deposit, | deposit: Deposit, | ||||
swhid_reference: Union[str, SWHID], | swhid_reference: Union[str, SWHID], | ||||
metadata: Dict, | metadata: Dict, | ||||
raw_metadata: bytes, | raw_metadata: bytes, | ||||
deposit_origin: Optional[str] = None, | deposit_origin: Optional[str] = None, | ||||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | def _atom_entry( | ||||
self, | self, | ||||
request: Request, | request: Request, | ||||
headers: ParsedRequestHeaders, | headers: ParsedRequestHeaders, | ||||
collection_name: str, | collection_name: str, | ||||
deposit_id: Optional[int] = None, | deposit_id: Optional[int] = None, | ||||
replace_metadata: bool = False, | replace_metadata: bool = False, | ||||
replace_archives: bool = False, | replace_archives: bool = False, | ||||
check_slug_is_present: bool = False, | check_slug_is_present: bool = False, | ||||
) -> Dict[str, Any]: | ) -> Receipt: | ||||
"""Atom entry deposit. | """Atom entry deposit. | ||||
Args: | Args: | ||||
request: the request holding information to parse | request: the request holding information to parse | ||||
and inject in db | and inject in db | ||||
headers: parsed request headers | headers: parsed request headers | ||||
collection_name: the associated client | collection_name: the associated client | ||||
deposit_id: deposit identifier if provided | deposit_id: deposit identifier if provided | ||||
replace_metadata: 'Update or add' request to existing | replace_metadata: 'Update or add' request to existing | ||||
deposit. If False (default), this adds new metadata request to | deposit. If False (default), this adds new metadata request to | ||||
existing ones. Otherwise, this will replace existing metadata. | existing ones. Otherwise, this will replace existing metadata. | ||||
replace_archives: 'Update or add' request to existing | replace_archives: 'Update or add' request to existing | ||||
deposit. If False (default), this adds new archive request to | deposit. If False (default), this adds new archive request to | ||||
existing ones. Otherwise, this will replace existing archives. | existing ones. Otherwise, this will replace existing archives. | ||||
ones. | ones. | ||||
check_slug_is_present: Check for the slug header if True and raise | check_slug_is_present: Check for the slug header if True and raise | ||||
if not present | if not present | ||||
Returns: | Raises: | ||||
In the optimal case a dict with the following keys: | |||||
- deposit_id: deposit id associated to the deposit | |||||
- deposit_date: date of the deposit | |||||
- archive: None (no archive is provided here) | |||||
Otherwise, a dictionary with the key error and the | |||||
associated failures, either: | |||||
- 400 (bad request) if the request is not providing an external | - 400 (bad request) if the request is not providing an external | ||||
identifier | identifier | ||||
- 400 (bad request) if the request's body is empty | - 400 (bad request) if the request's body is empty | ||||
- 415 (unsupported media type) if a wrong media type is provided | - 415 (unsupported media type) if a wrong media type is provided | ||||
""" | """ | ||||
try: | try: | ||||
raw_metadata, metadata = self._read_metadata(request.data) | raw_metadata, metadata = self._read_metadata(request.data) | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | ) -> Receipt: | ||||
deposit.status = DEPOSIT_STATUS_LOAD_SUCCESS | deposit.status = DEPOSIT_STATUS_LOAD_SUCCESS | ||||
if isinstance(swhid_ref, SWHID): | if isinstance(swhid_ref, SWHID): | ||||
deposit.swhid = str(swhid) | deposit.swhid = str(swhid) | ||||
deposit.swhid_context = str(swhid_ref) | deposit.swhid_context = str(swhid_ref) | ||||
deposit.complete_date = depo_request.date | deposit.complete_date = depo_request.date | ||||
deposit.reception_date = depo_request.date | deposit.reception_date = depo_request.date | ||||
deposit.save() | deposit.save() | ||||
return { | return Receipt( | ||||
"deposit_id": deposit.id, | deposit_id=deposit.id, | ||||
"deposit_date": depo_request.date, | deposit_date=depo_request.date, | ||||
"status": deposit.status, | status=deposit.status, | ||||
"archive": None, | archive=None, | ||||
} | ) | ||||
self._deposit_request_put( | self._deposit_request_put( | ||||
deposit, | deposit, | ||||
{METADATA_KEY: metadata, RAW_METADATA_KEY: raw_metadata}, | {METADATA_KEY: metadata, RAW_METADATA_KEY: raw_metadata}, | ||||
replace_metadata, | replace_metadata, | ||||
replace_archives, | replace_archives, | ||||
) | ) | ||||
return { | return Receipt( | ||||
"deposit_id": deposit.id, | deposit_id=deposit.id, | ||||
"deposit_date": deposit.reception_date, | deposit_date=deposit.reception_date, | ||||
"archive": None, | status=deposit.status, | ||||
"status": deposit.status, | archive=None, | ||||
} | ) | ||||
def _empty_post( | def _empty_post( | ||||
self, | self, | ||||
request: Request, | request: Request, | ||||
headers: ParsedRequestHeaders, | headers: ParsedRequestHeaders, | ||||
collection_name: str, | collection_name: str, | ||||
deposit_id: int, | deposit_id: int, | ||||
) -> Dict[str, Any]: | ) -> Receipt: | ||||
"""Empty post to finalize an empty deposit. | """Empty post to finalize an empty deposit. | ||||
Args: | Args: | ||||
request: the request holding information to parse | request: the request holding information to parse | ||||
and inject in db | and inject in db | ||||
headers: parsed request headers | headers: parsed request headers | ||||
collection_name: the associated client | collection_name: the associated client | ||||
deposit_id: deposit identifier | deposit_id: deposit identifier | ||||
Returns: | |||||
Dictionary of result with the deposit's id, the date | |||||
it was completed and no archive. | |||||
""" | """ | ||||
deposit = Deposit.objects.get(pk=deposit_id) | deposit = Deposit.objects.get(pk=deposit_id) | ||||
deposit.complete_date = timezone.now() | deposit.complete_date = timezone.now() | ||||
deposit.status = DEPOSIT_STATUS_DEPOSITED | deposit.status = DEPOSIT_STATUS_DEPOSITED | ||||
deposit.save() | deposit.save() | ||||
return { | return Receipt( | ||||
"deposit_id": deposit_id, | deposit_id=deposit_id, | ||||
"deposit_date": deposit.complete_date, | deposit_date=deposit.complete_date, | ||||
"status": deposit.status, | status=deposit.status, | ||||
"archive": None, | archive=None, | ||||
} | ) | ||||
def additional_checks( | def additional_checks( | ||||
self, | self, | ||||
request: Request, | request: Request, | ||||
headers: ParsedRequestHeaders, | headers: ParsedRequestHeaders, | ||||
collection_name: str, | collection_name: str, | ||||
deposit_id: Optional[int] = None, | deposit_id: Optional[int] = None, | ||||
) -> Dict[str, Any]: | ) -> Dict[str, Any]: | ||||
▲ Show 20 Lines • Show All 151 Lines • ▼ Show 20 Lines | ) -> HttpResponse: | ||||
Returns: | Returns: | ||||
204 response when no error during routine occurred. | 204 response when no error during routine occurred. | ||||
400 if the deposit does not belong to the collection | 400 if the deposit does not belong to the collection | ||||
404 if the deposit or the collection does not exist | 404 if the deposit or the collection does not exist | ||||
""" | """ | ||||
headers = self.checks(request, collection_name, deposit_id) | headers = self.checks(request, collection_name, deposit_id) | ||||
status, iri_key, data = self.process_post( | status, iri_key, receipt = self.process_post( | ||||
request, headers, collection_name, deposit_id | request, headers, collection_name, deposit_id | ||||
) | ) | ||||
return self._make_deposit_receipt( | return self._make_deposit_receipt( | ||||
request, collection_name, status, iri_key, data, | request, collection_name, status, iri_key, receipt, | ||||
) | ) | ||||
def _make_deposit_receipt( | def _make_deposit_receipt( | ||||
self, | self, | ||||
request, | request, | ||||
collection_name: str, | collection_name: str, | ||||
status: int, | status: int, | ||||
iri_key: str, | iri_key: str, | ||||
data: Dict[str, Any], | receipt: Receipt, | ||||
) -> HttpResponse: | ) -> HttpResponse: | ||||
"""Returns an HttpResponse with a SWORD Deposit receipt as content.""" | """Returns an HttpResponse with a SWORD Deposit receipt as content.""" | ||||
# Build the IRIs in the receipt | # Build the IRIs in the receipt | ||||
args = [collection_name, data["deposit_id"]] | args = [collection_name, receipt.deposit_id] | ||||
iris = { | iris = { | ||||
iri: request.build_absolute_uri(reverse(iri, args=args)) | iri: request.build_absolute_uri(reverse(iri, args=args)) | ||||
for iri in [EM_IRI, EDIT_IRI, CONT_FILE_IRI, SE_IRI, STATE_IRI] | for iri in [EM_IRI, EDIT_IRI, CONT_FILE_IRI, SE_IRI, STATE_IRI] | ||||
} | } | ||||
data["packagings"] = ACCEPT_PACKAGINGS | context = { | ||||
data.update(iris) | **attr.asdict(receipt), | ||||
**iris, | |||||
"packagings": ACCEPT_PACKAGINGS, | |||||
} | |||||
response = render( | response = render( | ||||
request, | request, | ||||
"deposit/deposit_receipt.xml", | "deposit/deposit_receipt.xml", | ||||
context=data, | context=context, | ||||
content_type="application/xml", | content_type="application/xml", | ||||
status=status, | status=status, | ||||
) | ) | ||||
response._headers["location"] = "Location", data[iri_key] # type: ignore | response._headers["location"] = "Location", iris[iri_key] # type: ignore | ||||
return response | return response | ||||
@abstractmethod | @abstractmethod | ||||
def process_post( | def process_post( | ||||
self, | self, | ||||
request, | request, | ||||
headers: ParsedRequestHeaders, | headers: ParsedRequestHeaders, | ||||
collection_name: str, | collection_name: str, | ||||
deposit_id: Optional[int] = None, | deposit_id: Optional[int] = None, | ||||
) -> Tuple[int, str, Dict]: | ) -> Tuple[int, str, Receipt]: | ||||
"""Routine to deal with the deposit's processing. | """Routine to deal with the deposit's processing. | ||||
Returns | Returns | ||||
Tuple of: | Tuple of: | ||||
- response status code (200, 201, etc...) | - response status code (200, 201, etc...) | ||||
- key iri (EM_IRI, EDIT_IRI, etc...) | - key iri (EM_IRI, EDIT_IRI, etc...) | ||||
- dictionary of the processing result | - Receipt | ||||
""" | """ | ||||
pass | pass | ||||
class APIPut(APIBase, metaclass=ABCMeta): | class APIPut(APIBase, metaclass=ABCMeta): | ||||
"""Mixin for class to support PUT method. | """Mixin for class to support PUT method. | ||||
▲ Show 20 Lines • Show All 68 Lines • Show Last 20 Lines |