diff --git a/synapseclient/api/entity_factory.py b/synapseclient/api/entity_factory.py index db3472641..be9207601 100644 --- a/synapseclient/api/entity_factory.py +++ b/synapseclient/api/entity_factory.py @@ -259,7 +259,12 @@ async def _handle_file_entity( entity_instance.file_handle = FileHandle().fill_from_dict( synapse_instance=file_handle ) - entity_instance._fill_from_file_handle() + # Synapse omits the file handle from the bundle when the caller lacks DOWNLOAD + # permission (e.g. unmet access requirements). Only copy file handle fields onto + # the entity when one was returned so that `dataFileHandleId` from the entity + # is preserved. + if file_handle: + entity_instance._fill_from_file_handle() if download_file: if file_handle: diff --git a/tests/unit/synapseclient/models/async/unit_test_file_async.py b/tests/unit/synapseclient/models/async/unit_test_file_async.py index 1d7478fc3..878a7b835 100644 --- a/tests/unit/synapseclient/models/async/unit_test_file_async.py +++ b/tests/unit/synapseclient/models/async/unit_test_file_async.py @@ -797,6 +797,32 @@ async def test_get_with_id(self) -> None: assert result.file_handle.is_preview assert result.file_handle.external_url == FILE_HANDLE_EXTERNAL_URL + async def test_get_with_unmet_access_requirement(self) -> None: + # GIVEN an example file + file = File(id=SYN_123, download_file=False) + + # AND an entity bundle where Synapse withheld the file handle because the + # caller has unmet access requirements + bundle = self.get_example_rest_api_file_output() + bundle["fileHandles"] = [] + bundle["restrictionInformation"] = {"hasUnmetAccessRequirement": True} + + # WHEN I get the example file + with patch( + "synapseclient.api.entity_factory.get_entity_id_bundle2", + new_callable=AsyncMock, + return_value=bundle, + ): + result = await file.get_async(synapse_client=self.syn) + + # THEN the data_file_handle_id from the entity should be preserved + assert result.id == SYN_123 + assert result.data_file_handle_id == DATA_FILE_HANDLE_ID + + # AND the file handle should have no metadata filled in + assert result.file_handle.id is None + assert result.content_md5 is None + async def test_get_with_path(self) -> None: # GIVEN an example file file = File(path=PATH, description=MODIFIED_DESCRIPTION) diff --git a/tests/unit/synapseclient/models/async/unit_test_recordset_async.py b/tests/unit/synapseclient/models/async/unit_test_recordset_async.py index 0b9e577e7..8e4fc1d2f 100644 --- a/tests/unit/synapseclient/models/async/unit_test_recordset_async.py +++ b/tests/unit/synapseclient/models/async/unit_test_recordset_async.py @@ -571,6 +571,33 @@ async def side_effect(**kwargs): assert result.id == SYN_123 assert result.name == RECORD_SET_NAME + async def test_get_async_with_unmet_access_requirement(self) -> None: + # GIVEN a RecordSet with an id + record_set = RecordSet(id=SYN_123, download_file=False) + + # AND an entity bundle where Synapse withheld the file handle because the + # caller has unmet access requirements + bundle = { + "entity": _get_record_set_entity_response(), + "fileHandles": [], + "restrictionInformation": {"hasUnmetAccessRequirement": True}, + } + + # WHEN I call get_async + with patch( + "synapseclient.api.entity_factory.get_entity_id_bundle2", + new_callable=AsyncMock, + return_value=bundle, + ): + result = await record_set.get_async(synapse_client=self.syn) + + # THEN the data_file_handle_id from the entity should be preserved + assert result.id == SYN_123 + assert result.data_file_handle_id == DATA_FILE_HANDLE_ID + + # AND the file handle should have no metadata filled in + assert result.file_handle.id is None + async def test_get_async_with_path(self) -> None: # GIVEN a RecordSet with a path (looking up by local file) record_set = RecordSet(path=PATH)