Skip to content

Commit 7366349

Browse files
rtibblesclaude
andcommitted
fix: return None from CompositeGCS.get_stored_object_md5 for missing objects
The resumable upload_url path calls get_stored_object_md5(filepath) to dedup against a stored object's md5. On CompositeGCS this delegated to _get_readable_backend(name), which raises FileNotFoundError when the object is in no backend -- i.e. every not-yet-uploaded file -- 500ing upload_url for all new resumable uploads. Catch FileNotFoundError and return None, matching GoogleCloudStorage.get_stored_object_md5 for a missing blob. Add regression tests for the found and not-found cases; the not-found test fails against the pre-fix code (FileNotFoundError). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ineqU2EGLAcWW2WPWR3mE
1 parent a01bd10 commit 7366349

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

contentcuration/contentcuration/tests/test_gcs_storage.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from google.cloud.storage.blob import Blob
99
from mixer.main import mixer
1010

11+
from contentcuration.utils.files import hex_to_base64
1112
from contentcuration.utils.gcs_storage import CompositeGCS
1213
from contentcuration.utils.gcs_storage import GoogleCloudStorage
1314

@@ -232,3 +233,21 @@ def test_get_created_time(self):
232233
self.storage.get_created_time("blob"),
233234
self.blob_cls.return_value.time_created,
234235
)
236+
237+
def test_get_stored_object_md5(self):
238+
mock_blob = self.blob_cls("blob", "blob")
239+
mock_blob.md5_hash = hex_to_base64("d41d8cd98f00b204e9800998ecf8427e")
240+
self.mock_default_bucket.get_blob.return_value = mock_blob
241+
self.assertEqual(
242+
self.storage.get_stored_object_md5("blob"),
243+
"d41d8cd98f00b204e9800998ecf8427e",
244+
)
245+
246+
def test_get_stored_object_md5__returns_none_if_not_found(self):
247+
# Regression: a not-yet-uploaded object is in no backend, so
248+
# _get_readable_backend raises FileNotFoundError. get_stored_object_md5
249+
# must swallow that and return None (else the resumable upload_url
250+
# endpoint 500s on every new file), not propagate the error.
251+
self.mock_default_bucket.get_blob.return_value = None
252+
self.mock_anon_bucket.get_blob.return_value = None
253+
self.assertIsNone(self.storage.get_stored_object_md5("blob"))

contentcuration/contentcuration/utils/gcs_storage.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,4 +302,11 @@ def create_resumable_upload_session(self, name, md5_b64, size):
302302
)
303303

304304
def get_stored_object_md5(self, name):
305-
return self._get_readable_backend(name).get_stored_object_md5(name)
305+
# A not-yet-uploaded object exists in no backend; treat that as "no
306+
# stored md5" (None) rather than raising, matching
307+
# GoogleCloudStorage.get_stored_object_md5 for a missing blob.
308+
try:
309+
backend = self._get_readable_backend(name)
310+
except FileNotFoundError:
311+
return None
312+
return backend.get_stored_object_md5(name)

0 commit comments

Comments
 (0)