Repository navigation
Upload a series manifest from DID's file cache after ingest (#306) - #307
stevevanhooser wants to merge 1 commit into
Conversation
The round-trip test in tests/test_cloud_file_series_round_trip.py caught a
real bug on TEST_USER_1 last night: uploadDataset silently sent no bytes
for a series manifest, and the following download 404'd on the manifest
uid ("Manifest for file series 'chunkdata.bin' (uid ...) is not a file
of this dataset"). The reconstruction paths on the download side both
land on ingest_locations empty, and DID's MembersNotLocatable guard
refuses the doc.
Root cause: file_uploads_for_document walks
files.file_info[].locations[].location and requires os.path.exists on
the recorded path. did.document.Document.add_file_series writes the
manifest to a tempfile with delete_original=True by default, so
database_add copies it into DID's FileDir/<uid> and removes the source.
By the time the file pass runs, the recorded location is gone; the
bytes are still on disk under the uid, but the upload never sees them.
Fix: teach file_uploads_for_document to fall back to
did.file.cached_path_for_uid(uid, additional_roots=[...]) when the
recorded path is gone. Callers pass the local dataset's DID FileDir
via _did_file_store_roots(dataset). Threaded through
uploadFilesForDatasetDocuments -> uploadDataset and every
_upload_binaries call site in sync.operations (uploadNew,
mirrorToRemote, twoWaySync).
The recorded location is still preferred when it resolves, so a doc
whose user-provided source path is still on disk uploads from there --
this fix only fires on ingested bytes whose source has moved.
Also a latent safety net: an ordinary file whose source path was
deleted or moved after add_file gets picked up from DID's cache too,
rather than being silently missing from the remote.
Offline coverage in tests/test_cloud_upload_uses_did_cache.py:
- fallback finds the file by uid in additional_roots;
- without roots, the fallback does not fire (backwards compatible);
- a live recorded path is preferred over the cache;
- no uid, or neither location resolves, yields nothing (no upload);
- a MATLAB-shaped bare-dict locations still works;
- end-to-end via ndi_dataset_dir + database_add on a demoNDISeries doc,
proving the recorded location IS gone post-ingest and the manifest is
recoverable from the FileDir root.
Closes #306.
Refs: #304, #305 (round-trip test that
caught this).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdGZ8fRPeY9JSpUHgXm9ao
|
Closing this without merging. The fix is at the wrong layer and only half-solves #306. Two problems with this approach:
Reshaping from scratch on a new branch to mirror MATLAB's pattern: use Generated by Claude Code |
Closes #306. Fixes the real bug the round-trip test PR #305 caught on TEST_USER_1 last night:
uploadDatasetsilently sends no bytes for a series manifest, so every follow-on download 404s on the manifest uid, both reconstruction paths land oningest_locationsempty, and DID'sMembersNotLocatableguard refuses the doc.Root cause
file_uploads_for_documentwalksfiles.file_info[].locations[].locationand requiresos.path.exists(candidate)on the recorded path.did.document.Document.add_file_serieswrites the manifest to atempfile.NamedTemporaryFile(prefix="did_manifest_", suffix=".manifest", delete=False), andadd_filedefaultsdelete_original=Trueon afilelocation. Sodatabase_addcopies the manifest into<FileDir>/<uid>and removes the tempfile. By the time the upload's file pass runs, the recordedlocationis gone; the bytes are still on disk keyed by uid, but the upload never sees them and the manifest never reaches the cloud.Reproduced locally:
The fix
Teach the upload path to fall back to
did.file.cached_path_for_uid(uid, additional_roots=[...])when the recorded location no longer resolves. Same helper the read path uses for on-demand file resolution; a pure function of the uid and the roots.src/ndi/cloud/upload.py:file_uploads_for_document(doc, *, additional_roots=None)anduploadFilesForDatasetDocuments(..., additional_roots=None)gain the parameter._cached_path_for_uidisolates the DID call so adid.fileimport failure never breaks the ordinary path.src/ndi/cloud/orchestration.py: adds_did_file_store_roots(dataset)which pullsdataset._session._database.binary_path(the DIDFileDir).uploadDatasetpasses it in during thesync_files=Truefile pass.src/ndi/cloud/sync/operations.py:_upload_binariesgains adatasetkwarg; all three call sites (uploadNew,mirrorToRemote,twoWaySync) pass theirdatasetthrough._did_file_store_rootsis reused.The recorded location is still preferred when it resolves. Only files whose source path has moved (ingested manifests, or any user file the caller deleted post-
add_file) reach the fallback. Ordinary docs whose source paths are still on disk upload from those paths, unchanged.Tests
New file
tests/test_cloud_upload_uses_did_cache.py, eight offline tests:TestTheFallbackFindsAnIngestedManifest-- purefile_uploads_for_documentshape:(uid, cached_path)whenadditional_rootscontains<uid>;locationsstill works.TestEndToEndAgainstARealDatabase:demoNDISeriesdoc viaadd_file_series,database_addit, confirm the recorded location is gone, confirm the manifest bytes are in DID'sFileDir, and confirmfile_uploads_for_document(doc, additional_roots=_did_file_store_roots(dataset))yields(manifest_uid, cached_path)._did_file_store_roots(None)/object()returns[]-- reads defensively so a stand-in dataset does not stop an upload.The live-cloud end-to-end coverage remains PR #305's
tests/test_cloud_file_series_round_trip.py; with this fix, it stops failing on the download side (the manifest bytes will actually be on the cloud now).Verification
73 passed. Bridge checks (139 tests) green with the local NDI-matlab checkout.
black --checkandruff checkclean.References
🤖 Generated with Claude Code
https://claude.ai/code/session_01AdGZ8fRPeY9JSpUHgXm9ao
Generated by Claude Code