diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index e01eabf11..0cfddd376 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -607,7 +607,9 @@ def get_attr_value(self, **kwargs): if isinstance(attr_val, TermSetWrapper): attr_val = attr_val.value if attr_val is not None: - attr_val = self.__convert_string(attr_val, spec) + if not isinstance(attr_val, Data): + # NOTE: Do not process Data objects here + attr_val = self.__convert_string(attr_val, spec) spec_dt = self.__get_data_type(spec) if spec_dt is not None: try: @@ -640,6 +642,7 @@ def __apply_string_type(value, string_type): # NOTE: if a user passes a h5py.Dataset that is not wrapped with a hdmf.utils.StrDataset, # then this conversion may not be correct. Users should unpack their string h5py.Datasets # into a numpy array (or wrap them in StrDataset) before passing them to a container object. + # NOTE: this will convert datasets and arrays to lists of lists. if hasattr(value, '__iter__') and not isinstance(value, (str, bytes)): return [__apply_string_type(item, string_type) for item in value] else: @@ -653,8 +656,7 @@ def __apply_string_type(value, string_type): else: ret = str(value) elif isinstance(spec, DatasetSpec): - # TODO: make sure we can handle specs with data_type_inc set - if spec.data_type_inc is None and spec.dtype is not None: + if spec.dtype is not None: string_type = None if 'text' in spec.dtype: string_type = str @@ -826,6 +828,7 @@ def build(self, **kwargs): data = container.data.value else: data = container.data + data = self.__convert_string(data, spec) bldr_data, dtype = self.convert_dtype(spec, data, spec_dtype=spec_dtype) except Exception as ex: msg = f"could not resolve dtype for {type(container).__name__} '{container.name}'" diff --git a/tests/unit/build_tests/mapper_tests/test_build_datetime.py b/tests/unit/build_tests/mapper_tests/test_build_datetime.py index 9e2b5e84a..b2ab51509 100644 --- a/tests/unit/build_tests/mapper_tests/test_build_datetime.py +++ b/tests/unit/build_tests/mapper_tests/test_build_datetime.py @@ -1,5 +1,5 @@ from hdmf.utils import docval, getargs -from hdmf import Container +from hdmf import Container, Data from hdmf.spec import GroupSpec, DatasetSpec from hdmf.testing import TestCase from datetime import datetime, date @@ -25,6 +25,38 @@ def data(self): return self.__data +class Column(Data): + + @docval({'name': 'name', 'type': str, 'doc': 'the name of this Column'}, + {'name': 'data', 'type': ('data', 'array_data'), 'doc': 'some data'}) + def __init__(self, **kwargs): + name, data = getargs('name', 'data', kwargs) + super().__init__(name=name, data=data) + + @property + def data_type(self): + return 'Column' + + +class BarWithColumnData(Container): + + @docval({'name': 'name', 'type': str, 'doc': 'the name of this Bar'}, + {'name': 'data', 'type': Column, 'doc': 'some data'}) + def __init__(self, **kwargs): + name, data = getargs('name', 'data', kwargs) + super().__init__(name=name) + self.__data = data + data.parent = self + + @property + def data_type(self): + return 'Bar' + + @property + def data(self): + return self.__data + + class TestBuildDatasetDateTime(TestCase): """Test that building a dataset with dtype isodatetime works with datetime and date objects.""" @@ -83,3 +115,29 @@ def test_date_array(self): ret = builder.get('data') assert ret.data == [b'2023-07-09', b'2023-07-10'] assert ret.dtype == 'ascii' + + + def test_datetime_array_ext_spec(self): + column_spec = DatasetSpec(data_type_def='Column', doc='an example dataset', dims=(None,)) + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + data_type_inc='Column', + doc='an example dataset', + name='data', + dtype='isodatetime', + ), + ], + ) + type_map = create_test_type_map([bar_spec, column_spec], {'Bar': BarWithColumnData, 'Column': Column}) + + bar_inst = BarWithColumnData( + name='my_bar', + data=Column(name='data', data=[datetime(2023, 7, 9), datetime(2023, 7, 10)]), + ) + builder = type_map.build(bar_inst) + ret = builder.get('data') + assert ret.data == [b'2023-07-09T00:00:00', b'2023-07-10T00:00:00'] + assert ret.dtype == 'ascii'