Skip to content

Commit 55ce5bf

Browse files
committed
refactor(grz-common): extract get_metadata_upload_timestamp helper
Move the "head_object on $submission_id/metadata/metadata.json -> timestamp" operation into a free function in `grz_common.transfer`, alongside `init_s3_client`. `Worker.load_metadata_with_submission_date` calls it and narrows the result to a `date` for storage in the DB. Documents the inbox-vs-archive caveat: the S3 `LastModified` of the metadata.json object is the authoritative inbox-receipt timestamp, but once the submission has moved to the archive bucket the `LastModified` no longer reflects the time of submission. Callers operating against the archive (e.g. `grzctl db backfill`) must therefore not use this helper and must keep deferring to the in-file `submission.submissionDate` field. The existing `_backfill_submission` already adds "submission_date" to `ignore_fields`, so this is already the case in practice.
1 parent fe9d648 commit 55ce5bf

2 files changed

Lines changed: 33 additions & 6 deletions

File tree

packages/grz-common/src/grz_common/transfer.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Common methods for transferring data to and from GRZ buckets.
33
"""
44

5+
import datetime
56
import logging
67
from typing import TYPE_CHECKING
78

@@ -80,3 +81,26 @@ def init_s3_resource(s3_options: S3Options) -> S3ServiceResource:
8081
)
8182

8283
return s3_resource
84+
85+
86+
def get_metadata_upload_timestamp(
87+
s3_client: S3Client, bucket: str, submission_id: str
88+
) -> datetime.datetime:
89+
"""Return the S3 last-modified timestamp of a submission's ``metadata/metadata.json`` object.
90+
91+
This is the authoritative "received at the inbox" timestamp: it cannot be forged by
92+
the submitter (unlike ``submission.submissionDate`` inside the JSON itself) and is
93+
only meaningful while the object still lives in the inbox bucket. Do **not** call
94+
this against the archive bucket: the archive's ``LastModified`` reflects the time
95+
of archival, not the time of submission.
96+
97+
:param s3_client: boto3 S3 client pointed at the inbox bucket.
98+
:param bucket: Name of the inbox bucket.
99+
:param submission_id: Submission identifier (the top-level S3 prefix).
100+
:returns: ``LastModified`` (timezone-aware ``datetime``) for
101+
``<submission_id>/metadata/metadata.json``. Callers that only need the date
102+
portion should call ``.date()`` themselves.
103+
:raises botocore.exceptions.ClientError: If the object does not exist or S3 returns an error.
104+
"""
105+
response = s3_client.head_object(Bucket=bucket, Key=f"{submission_id}/metadata/metadata.json")
106+
return response["LastModified"]

packages/grz-common/src/grz_common/workers/worker.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from ..models.identifiers import IdentifiersModel
1818
from ..models.s3 import S3Options
1919
from ..progress import EncryptionState, FileProgressLogger, ValidationState
20-
from ..transfer import init_s3_client
20+
from ..transfer import get_metadata_upload_timestamp, init_s3_client
2121
from .download import S3BotoDownloadWorker
2222
from .submission import EncryptedSubmission, Submission, SubmissionValidationError
2323
from .upload import S3BotoUploadWorker, UploadError
@@ -318,13 +318,16 @@ def load_metadata_with_submission_date(
318318
) -> tuple[GrzSubmissionMetadata, datetime.date]:
319319
"""Load the downloaded metadata.json and fetch its S3 last-modified date.
320320
321-
Returns the parsed :class:`GrzSubmissionMetadata` plus the date the
322-
metadata.json was last modified on S3, suitable for feeding into
323-
:meth:`grz_db.models.submission.SubmissionDb.populate`.
321+
:param s3_options: S3 connection options for the inbox bucket.
322+
:param submission_id: Submission identifier (the top-level S3 prefix).
323+
:returns: The parsed :class:`GrzSubmissionMetadata` plus the inbox upload
324+
date derived from
325+
:func:`grz_common.transfer.get_metadata_upload_timestamp`, suitable
326+
for feeding into
327+
:meth:`grz_db.models.submission.SubmissionDb.populate`.
324328
"""
325329
s3_client = init_s3_client(s3_options)
326-
response = s3_client.head_object(Bucket=s3_options.bucket, Key=f"{submission_id}/metadata/metadata.json")
327-
submission_date = response["LastModified"].date()
330+
submission_date = get_metadata_upload_timestamp(s3_client, s3_options.bucket, submission_id).date()
328331

329332
metadata_file_path = self.metadata_dir / "metadata.json"
330333
with open(metadata_file_path) as fd:

0 commit comments

Comments
 (0)