What happened?
HERD.add_ref(container=..., attribute=...) and HERD.get_object_entities(container=..., attribute=...) resolve the attribute argument differently, so a reference added with an attribute cannot be read back with the same attribute.
add_ref routes through _validate_object (src/hdmf/common/resources.py:547-591), which handles three cases. For a non-DataType attribute (a scalar attribute such as DynamicTable.description), it stores the parent container with a computed relative_path (e.g. relative_path='description').
get_object_entities (src/hdmf/common/resources.py:883-887) instead does container[attribute] and passes the default relative_path=''.
Because container[attribute] uses __getitem__ (column/row lookup on DynamicTable), a scalar attribute name raises KeyError, and for containers that do not implement __getitem__ it raises TypeError. The attribute path in get_object_entities therefore only works for the narrow case where container[attribute] happens to return the same object that was stored (e.g. a DynamicTable column).
Steps to reproduce
from hdmf.common import DynamicTable
from hdmf.common.resources import HERD
from hdmf import Container, HERDManager
class FileC(Container, HERDManager):
__fields__ = ({'name': 'external_resources', 'child': True, 'required_name': 'external_resources'},)
table = DynamicTable(name='table', description='table')
table.add_column(name='col1', description='column')
table.add_row(id=0, col1='data')
file = FileC(name='file')
er = HERD()
er.add_ref(file=file, container=table, attribute='description',
key='key1', entity_id='entity_0', entity_uri='entity_0_uri')
print(er.objects.data[0])
# (0, '<uuid>', 'DynamicTable', 'description', '') <- stored with relative_path='description'
er.get_object_entities(file=file, container=table, attribute='description')
# KeyError: 'description'
# Workaround that does work, showing the data is retrievable:
er.get_object_entities(file=file, container=table, relative_path='description')
# [{'entity_id': 'entity_0', 'entity_uri': 'entity_0_uri'}]
Expected
get_object_entities(attribute=...) should resolve the object the same way add_ref(attribute=...) does (i.e. reuse the _validate_object logic, computing the same relative_path), so that a reference added with a given attribute can be retrieved with the same attribute. At minimum the two methods should be symmetric for every attribute case add_ref supports.
Notes
- The existing
test_get_obj_entities_attribute passes only because it uses a DynamicTable column (attribute='col1'), where both getattr and __getitem__ resolve to the same column object (the DataType-attribute case). It does not cover the non-DataType attribute case exercised by test_add_ref_nested.
Code of Conduct
What happened?
HERD.add_ref(container=..., attribute=...)andHERD.get_object_entities(container=..., attribute=...)resolve theattributeargument differently, so a reference added with anattributecannot be read back with the sameattribute.add_refroutes through_validate_object(src/hdmf/common/resources.py:547-591), which handles three cases. For a non-DataType attribute (a scalar attribute such asDynamicTable.description), it stores the parent container with a computedrelative_path(e.g.relative_path='description').get_object_entities(src/hdmf/common/resources.py:883-887) instead doescontainer[attribute]and passes the defaultrelative_path=''.Because
container[attribute]uses__getitem__(column/row lookup onDynamicTable), a scalar attribute name raisesKeyError, and for containers that do not implement__getitem__it raisesTypeError. Theattributepath inget_object_entitiestherefore only works for the narrow case wherecontainer[attribute]happens to return the same object that was stored (e.g. aDynamicTablecolumn).Steps to reproduce
Expected
get_object_entities(attribute=...)should resolve the object the same wayadd_ref(attribute=...)does (i.e. reuse the_validate_objectlogic, computing the samerelative_path), so that a reference added with a givenattributecan be retrieved with the sameattribute. At minimum the two methods should be symmetric for every attribute caseadd_refsupports.Notes
test_get_obj_entities_attributepasses only because it uses aDynamicTablecolumn (attribute='col1'), where bothgetattrand__getitem__resolve to the same column object (the DataType-attribute case). It does not cover the non-DataType attribute case exercised bytest_add_ref_nested.Code of Conduct