Repository navigation
[SYNPY-1941] Preserve data_file_handle_id when file handle is withheld - #1474
Merged
Merged
Conversation
Synapse omits the file handle from the entity bundle when the caller lacks DOWNLOAD permission (e.g. unmet access requirements). _handle_file_entity still called _fill_from_file_handle() with an empty FileHandle, which overwrote the entity's dataFileHandleId with None. This caused File.get() followed by store() (e.g. moving a file) to fail with "Cannot store file". Only copy file handle fields onto the entity when a file handle was returned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`if self.file_handle:` is always true for a FileHandle dataclass instance, so an empty placeholder FileHandle (left when Synapse withholds the file handle due to missing DOWNLOAD permission) overwrote data_file_handle_id, content_type, content_size, and external_url with None. Guard on `file_handle.id` in File and RecordSet `_fill_from_file_handle` so every caller is protected, and revert the now-redundant guard in entity_factory._handle_file_entity. Add RecordSet regression test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restore the guard in entity_factory._handle_file_entity and revert the _fill_from_file_handle changes in File and RecordSet. _handle_file_entity is the only caller that can pass an empty placeholder FileHandle and it handles both File and RecordSet, so a single guard there is the smaller fix. Keep both File and RecordSet regression tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
2 open issues
What changed in this PR
Preserves dataFileHandleId on file-like entities when Synapse withholds file handle metadata due to missing DOWNLOAD permission (e.g., unmet access requirements), preventing client-side failures on subsequent store() operations.
Changes:
- Guard
_fill_from_file_handle()so it only runs when a file handle is actually returned in the bundle. - Add async unit tests asserting
data_file_handle_idis preserved whenfileHandlesis empty due to unmet access requirements.
| File | Description |
|---|---|
| synapseclient/api/entity_factory.py | Avoids overwriting entity-level file handle fields when Synapse omits file handle metadata. |
| tests/unit/synapseclient/models/async/unit_test_file_async.py | Adds regression test for File.get_async() to preserve data_file_handle_id when access requirements are unmet. |
| tests/unit/synapseclient/models/async/unit_test_recordset_async.py | Adds similar regression test for RecordSet.get_async() under unmet access requirements. |
Review effort: Lite
Copilot stopped reviewing on behalf of
thomasyu888 due to an error
October 26, 2026 23:15
andrewelamb
approved these changes
Oct 28, 2026
Member
Author
|
Note, i have to test this specifically around moving data from a folder with unsatisfied AR to another folder with the same AR I tested |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Problem:
File(id=..., download_file=False).get()returnsdata_file_handle_id=None, even though Synapse includesdataFileHandleIdon the entity in thePOST /entity/{id}/bundle2response.store(), such as moving the file by changingparent_id, fails client-side withValueError: Cannot store file. The file must have one of: .... The same move works in the web UI."fileHandles": []in the bundle when the caller can't download the file._handle_file_entity()insynapseclient/api/entity_factory.pythen setsfile_handleto an empty placeholderFileHandle()and calls_fill_from_file_handle(). That method checksif self.file_handle:, which is always true for a dataclass instance, so the placeholder overwrotedata_file_handle_id,content_type,content_sizeandexternal_urlwithNone.Solution:
_handle_file_entity(), only call_fill_from_file_handle()when the bundle actually contained a matching file handle, so thedataFileHandleIdfrom the entity is preserved._handle_file_entity()handles bothFileandRecordSet, and it's the only caller that can pass an empty placeholder. The other callers (fill_from_dictwhen_file_handleis present, and_upload_file) always have a real handle. So a single guard here covers every affected path. I also tried guarding inside_fill_from_file_handle(), but it's duplicated inFileandRecordSet, so that meant changing two copies for no practical gain.file_handleis still set to an emptyFileHandle()rather thanNone. Existing code readsfile_handle.<attr>without aNonecheck (e.g._get_file_migration_status()inmodels/services/migration.py) and would raiseAttributeErroron restricted files.Testing:
test_get_with_unmet_access_requirement(File) andtest_get_async_with_unmet_access_requirement(RecordSet). Both go through the real entity factory with a mocked bundle containingfileHandles: []and an unmet access requirement. Both fail without the fix (assert None == 888/assert None == '888') and pass with it.boto3,jsonschema); they don't touch this code path.get()now returns the correctdata_file_handle_id.store()with a newparent_id) on a restricted entity.🤖 Generated with Claude Code