Beispiel #1
0
    def test_register_generator(self):
        """Test TypeMap.register_generator and ClassGenerator.register_generator."""
        class MyClassGenerator(CustomClassGenerator):
            @classmethod
            def apply_generator_to_field(cls, field_spec, bases, type_map):
                return True

            @classmethod
            def process_field_spec(cls, classdict, docval_args, parent_cls,
                                   attr_name, not_inherited_fields, type_map,
                                   spec):
                # append attr_name to classdict['__custom_fields__'] list
                classdict.setdefault('process_field_spec',
                                     list()).append(attr_name)

            @classmethod
            def post_process(cls, classdict, bases, docval_args, spec):
                classdict['post_process'] = True

        spec = GroupSpec(doc='A test group specification with a data type',
                         data_type_def='Baz',
                         attributes=[
                             AttributeSpec(name='attr1',
                                           doc='a string attribute',
                                           dtype='text')
                         ])

        spec_catalog = SpecCatalog()
        spec_catalog.register_spec(spec, 'test.yaml')
        namespace = SpecNamespace(doc='a test namespace',
                                  name=CORE_NAMESPACE,
                                  schema=[{
                                      'source': 'test.yaml'
                                  }],
                                  version='0.1.0',
                                  catalog=spec_catalog)
        namespace_catalog = NamespaceCatalog()
        namespace_catalog.add_namespace(CORE_NAMESPACE, namespace)
        type_map = TypeMap(namespace_catalog)
        type_map.register_generator(MyClassGenerator)
        cls = type_map.get_dt_container_cls('Baz', CORE_NAMESPACE)

        self.assertEqual(cls.process_field_spec, ['attr1'])
        self.assertTrue(cls.post_process)
Beispiel #2
0
class TestDynamicDynamicTable(TestCase):

    def setUp(self):
        self.dt_spec = GroupSpec(
            'A test extension that contains a dynamic table',
            data_type_def='TestTable',
            data_type_inc='DynamicTable',
            datasets=[
                DatasetSpec(
                    data_type_inc='VectorData',
                    name='my_col',
                    doc='a test column',
                    dtype='float'
                ),
                DatasetSpec(
                    data_type_inc='VectorData',
                    name='indexed_col',
                    doc='a test column',
                    dtype='float'
                ),
                DatasetSpec(
                    data_type_inc='VectorIndex',
                    name='indexed_col_index',
                    doc='a test column',
                ),
                DatasetSpec(
                    data_type_inc='VectorData',
                    name='optional_col1',
                    doc='a test column',
                    dtype='float',
                    quantity='?',
                ),
                DatasetSpec(
                    data_type_inc='VectorData',
                    name='optional_col2',
                    doc='a test column',
                    dtype='float',
                    quantity='?',
                )
            ]
        )

        self.dt_spec2 = GroupSpec(
            'A test extension that contains a dynamic table',
            data_type_def='TestDTRTable',
            data_type_inc='DynamicTable',
            datasets=[
                DatasetSpec(
                    data_type_inc='DynamicTableRegion',
                    name='ref_col',
                    doc='a test column',
                ),
                DatasetSpec(
                    data_type_inc='DynamicTableRegion',
                    name='indexed_ref_col',
                    doc='a test column',
                ),
                DatasetSpec(
                    data_type_inc='VectorIndex',
                    name='indexed_ref_col_index',
                    doc='a test column',
                ),
                DatasetSpec(
                    data_type_inc='DynamicTableRegion',
                    name='optional_ref_col',
                    doc='a test column',
                    quantity='?'
                ),
                DatasetSpec(
                    data_type_inc='DynamicTableRegion',
                    name='optional_indexed_ref_col',
                    doc='a test column',
                    quantity='?'
                ),
                DatasetSpec(
                    data_type_inc='VectorIndex',
                    name='optional_indexed_ref_col_index',
                    doc='a test column',
                    quantity='?'
                ),
                DatasetSpec(
                    data_type_inc='VectorData',
                    name='optional_col3',
                    doc='a test column',
                    dtype='float',
                    quantity='?',
                )
            ]
        )

        from hdmf.spec.write import YAMLSpecWriter
        writer = YAMLSpecWriter(outdir='.')

        self.spec_catalog = SpecCatalog()
        self.spec_catalog.register_spec(self.dt_spec, 'test.yaml')
        self.spec_catalog.register_spec(self.dt_spec2, 'test.yaml')
        self.namespace = SpecNamespace(
            'a test namespace', CORE_NAMESPACE,
            [
                dict(
                    namespace='hdmf-common',
                ),
                dict(source='test.yaml'),
            ],
            version='0.1.0',
            catalog=self.spec_catalog
        )

        self.test_dir = tempfile.mkdtemp()
        spec_fpath = os.path.join(self.test_dir, 'test.yaml')
        namespace_fpath = os.path.join(self.test_dir, 'test-namespace.yaml')
        writer.write_spec(dict(groups=[self.dt_spec, self.dt_spec2]), spec_fpath)
        writer.write_namespace(self.namespace, namespace_fpath)
        self.namespace_catalog = NamespaceCatalog()
        hdmf_typemap = get_type_map()
        self.type_map = TypeMap(self.namespace_catalog)
        self.type_map.merge(hdmf_typemap, ns_catalog=True)
        self.type_map.load_namespaces(namespace_fpath)
        self.manager = BuildManager(self.type_map)

        self.TestTable = self.type_map.get_dt_container_cls('TestTable', CORE_NAMESPACE)
        self.TestDTRTable = self.type_map.get_dt_container_cls('TestDTRTable', CORE_NAMESPACE)

    def tearDown(self) -> None:
        shutil.rmtree(self.test_dir)

    def test_dynamic_table(self):
        assert issubclass(self.TestTable, DynamicTable)

        assert self.TestTable.__columns__[0] == dict(
            name='my_col',
            description='a test column',
            required=True
        )

    def test_forbids_incorrect_col(self):
        test_table = self.TestTable(name='test_table', description='my test table')

        with self.assertRaises(ValueError):
            test_table.add_row(my_col=3.0, indexed_col=[1.0, 3.0], incorrect_col=5)

    def test_dynamic_column(self):
        test_table = self.TestTable(name='test_table', description='my test table')
        test_table.add_column('dynamic_column', 'this is a dynamic column')
        test_table.add_row(
            my_col=3.0, indexed_col=[1.0, 3.0], dynamic_column=4, optional_col2=.5,
        )
        test_table.add_row(
            my_col=4.0, indexed_col=[2.0, 4.0], dynamic_column=4, optional_col2=.5,
        )

        np.testing.assert_array_equal(test_table['indexed_col'].target.data, [1., 3., 2., 4.])
        np.testing.assert_array_equal(test_table['dynamic_column'].data, [4, 4])

    def test_optional_col(self):
        test_table = self.TestTable(name='test_table', description='my test table')
        test_table.add_row(my_col=3.0, indexed_col=[1.0, 3.0], optional_col2=.5)
        test_table.add_row(my_col=4.0, indexed_col=[2.0, 4.0], optional_col2=.5)

    def test_dynamic_table_region(self):
        test_table = self.TestTable(name='test_table', description='my test table')
        test_table.add_row(my_col=3.0, indexed_col=[1.0, 3.0], optional_col2=.5)
        test_table.add_row(my_col=4.0, indexed_col=[2.0, 4.0], optional_col2=.5)

        test_dtr_table = self.TestDTRTable(name='test_dtr_table', description='my table',
                                           target_tables={'ref_col': test_table,
                                                          'indexed_ref_col': test_table})
        self.assertIs(test_dtr_table['ref_col'].table, test_table)
        self.assertIs(test_dtr_table['indexed_ref_col'].target.table, test_table)

        test_dtr_table.add_row(ref_col=0, indexed_ref_col=[0, 1])
        test_dtr_table.add_row(ref_col=0, indexed_ref_col=[0, 1])

        np.testing.assert_array_equal(test_dtr_table['indexed_ref_col'].target.data, [0, 1, 0, 1])
        np.testing.assert_array_equal(test_dtr_table['ref_col'].data, [0, 0])

    def test_dynamic_table_region_optional(self):
        test_table = self.TestTable(name='test_table', description='my test table')
        test_table.add_row(my_col=3.0, indexed_col=[1.0, 3.0], optional_col2=.5)
        test_table.add_row(my_col=4.0, indexed_col=[2.0, 4.0], optional_col2=.5)

        test_dtr_table = self.TestDTRTable(name='test_dtr_table', description='my table',
                                           target_tables={'optional_ref_col': test_table,
                                                          'optional_indexed_ref_col': test_table})
        self.assertIs(test_dtr_table['optional_ref_col'].table, test_table)
        self.assertIs(test_dtr_table['optional_indexed_ref_col'].target.table, test_table)

        test_dtr_table.add_row(ref_col=0, indexed_ref_col=[0, 1],
                               optional_ref_col=0, optional_indexed_ref_col=[0, 1])
        test_dtr_table.add_row(ref_col=0, indexed_ref_col=[0, 1],
                               optional_ref_col=0, optional_indexed_ref_col=[0, 1])

        np.testing.assert_array_equal(test_dtr_table['optional_indexed_ref_col'].target.data, [0, 1, 0, 1])
        np.testing.assert_array_equal(test_dtr_table['optional_ref_col'].data, [0, 0])

    def test_dynamic_table_region_bad_target_col(self):
        test_table = self.TestTable(name='test_table', description='my test table')
        test_table.add_row(my_col=3.0, indexed_col=[1.0, 3.0], optional_col2=.5)
        test_table.add_row(my_col=4.0, indexed_col=[2.0, 4.0], optional_col2=.5)

        msg = r"^'bad' is not the name of a predefined column of table .*"
        with self.assertRaisesRegex(ValueError, msg):
            self.TestDTRTable(name='test_dtr_table', description='my table', target_tables={'bad': test_table})

    def test_dynamic_table_region_non_dtr_target(self):
        test_table = self.TestTable(name='test_table', description='my test table')
        test_table.add_row(my_col=3.0, indexed_col=[1.0, 3.0], optional_col2=.5)
        test_table.add_row(my_col=4.0, indexed_col=[2.0, 4.0], optional_col2=.5)

        msg = "Column 'optional_col3' must be a DynamicTableRegion to have a target table."
        with self.assertRaisesWith(ValueError, msg):
            self.TestDTRTable(name='test_dtr_table', description='my table',
                              target_tables={'optional_col3': test_table})

    def test_roundtrip(self):
        # NOTE this does not use H5RoundTripMixin because this requires custom validation
        test_table = self.TestTable(name='test_table', description='my test table')
        test_table.add_column('dynamic_column', 'this is a dynamic column')
        test_table.add_row(
            my_col=3.0, indexed_col=[1.0, 3.0], dynamic_column=4, optional_col2=.5,
        )
        self.filename = os.path.join(self.test_dir, 'test_TestTable.h5')

        with HDF5IO(self.filename, manager=self.manager, mode='w') as write_io:
            write_io.write(test_table, cache_spec=True)

        self.reader = HDF5IO(self.filename, manager=self.manager, mode='r')
        read_container = self.reader.read()

        self.assertIsNotNone(str(test_table))  # added as a test to make sure printing works
        self.assertIsNotNone(str(read_container))
        # make sure we get a completely new object
        self.assertNotEqual(id(test_table), id(read_container))
        # the name of the root container of a file is always 'root' (see h5tools.py ROOT_NAME)
        # thus, ignore the name of the container when comparing original container vs read container
        self.assertContainerEqual(read_container, test_table, ignore_name=True)

        builder = self.reader.read_builder()
        # TODO fix ValueError: No specification for 'Container' in namespace 'test_core'
        validator = ValidatorMap(self.manager.namespace_catalog.get_namespace(name=CORE_NAMESPACE))
        errors = validator.validate(builder)
        if errors:
            for err in errors:
                raise Exception(err)
        self.reader.close()
Beispiel #3
0
class TestGetClassSeparateNamespace(TestCase):
    def setUp(self):
        self.test_dir = tempfile.mkdtemp()
        if os.path.exists(self.test_dir):  # start clean
            self.tearDown()
        os.mkdir(self.test_dir)

        self.bar_spec = GroupSpec(
            doc='A test group specification with a data type',
            data_type_def='Bar',
            datasets=[DatasetSpec(name='data', doc='a dataset', dtype='int')],
            attributes=[
                AttributeSpec(name='attr1',
                              doc='a string attribute',
                              dtype='text'),
                AttributeSpec(name='attr2',
                              doc='an integer attribute',
                              dtype='int')
            ])
        self.type_map = TypeMap()
        create_load_namespace_yaml(namespace_name=CORE_NAMESPACE,
                                   specs=[self.bar_spec],
                                   output_dir=self.test_dir,
                                   incl_types=dict(),
                                   type_map=self.type_map)

    def tearDown(self):
        shutil.rmtree(self.test_dir)

    def test_get_class_separate_ns(self):
        """Test that get_class correctly sets the name and type hierarchy across namespaces."""
        self.type_map.register_container_type(CORE_NAMESPACE, 'Bar', Bar)
        baz_spec = GroupSpec(
            doc='A test extension',
            data_type_def='Baz',
            data_type_inc='Bar',
        )
        create_load_namespace_yaml(namespace_name='ndx-test',
                                   specs=[baz_spec],
                                   output_dir=self.test_dir,
                                   incl_types={CORE_NAMESPACE: ['Bar']},
                                   type_map=self.type_map)

        cls = self.type_map.get_dt_container_cls('Baz', 'ndx-test')
        self.assertEqual(cls.__name__, 'Baz')
        self.assertTrue(issubclass(cls, Bar))

    def _build_separate_namespaces(self):
        # create an empty extension to test ClassGenerator._get_container_type resolution
        # the Bar class has not been mapped yet to the bar spec
        qux_spec = DatasetSpec(doc='A test extension', data_type_def='Qux')
        spam_spec = DatasetSpec(doc='A test extension', data_type_def='Spam')
        create_load_namespace_yaml(namespace_name='ndx-qux',
                                   specs=[qux_spec, spam_spec],
                                   output_dir=self.test_dir,
                                   incl_types={},
                                   type_map=self.type_map)
        # resolve Spam first so that ndx-qux is resolved first
        self.type_map.get_dt_container_cls('Spam', 'ndx-qux')

        baz_spec = GroupSpec(doc='A test extension',
                             data_type_def='Baz',
                             data_type_inc='Bar',
                             groups=[
                                 GroupSpec(data_type_inc='Qux',
                                           doc='a qux',
                                           quantity='?'),
                                 GroupSpec(data_type_inc='Bar',
                                           doc='a bar',
                                           quantity='?')
                             ])
        create_load_namespace_yaml(namespace_name='ndx-test',
                                   specs=[baz_spec],
                                   output_dir=self.test_dir,
                                   incl_types={
                                       CORE_NAMESPACE: ['Bar'],
                                       'ndx-qux': ['Qux']
                                   },
                                   type_map=self.type_map)

    def _check_classes(self, baz_cls, bar_cls, bar_cls2, qux_cls, qux_cls2):
        self.assertEqual(qux_cls.__name__, 'Qux')
        self.assertEqual(baz_cls.__name__, 'Baz')
        self.assertEqual(bar_cls.__name__, 'Bar')
        self.assertIs(bar_cls,
                      bar_cls2)  # same class, two different namespaces
        self.assertIs(qux_cls, qux_cls2)
        self.assertTrue(issubclass(qux_cls, Data))
        self.assertTrue(issubclass(baz_cls, bar_cls))
        self.assertTrue(issubclass(bar_cls, Container))

        qux_inst = qux_cls(name='qux_name', data=[1])
        bar_inst = bar_cls(name='bar_name',
                           data=100,
                           attr1='a string',
                           attr2=10)
        baz_inst = baz_cls(name='baz_name',
                           qux=qux_inst,
                           bar=bar_inst,
                           data=100,
                           attr1='a string',
                           attr2=10)
        self.assertIs(baz_inst.qux, qux_inst)

    def test_get_class_include_from_separate_ns_1(self):
        """Test that get_class correctly sets the name and includes types correctly across namespaces.
        This is one of multiple tests carried out to ensure that order of which get_dt_container_cls is called
        does not impact the results

        first use EXTENSION namespace, then use ORIGINAL namespace
        """
        self._build_separate_namespaces()

        baz_cls = self.type_map.get_dt_container_cls(
            'Baz', 'ndx-test')  # Qux and Bar are not yet resolved
        bar_cls = self.type_map.get_dt_container_cls('Bar', 'ndx-test')
        bar_cls2 = self.type_map.get_dt_container_cls('Bar', CORE_NAMESPACE)
        qux_cls = self.type_map.get_dt_container_cls('Qux', 'ndx-test')
        qux_cls2 = self.type_map.get_dt_container_cls('Qux', 'ndx-qux')

        self._check_classes(baz_cls, bar_cls, bar_cls2, qux_cls, qux_cls2)

    def test_get_class_include_from_separate_ns_2(self):
        """Test that get_class correctly sets the name and includes types correctly across namespaces.
        This is one of multiple tests carried out to ensure that order of which get_dt_container_cls is called
        does not impact the results

        first use ORIGINAL namespace, then use EXTENSION namespace
        """
        self._build_separate_namespaces()

        baz_cls = self.type_map.get_dt_container_cls(
            'Baz', 'ndx-test')  # Qux and Bar are not yet resolved
        bar_cls2 = self.type_map.get_dt_container_cls('Bar', CORE_NAMESPACE)
        bar_cls = self.type_map.get_dt_container_cls('Bar', 'ndx-test')
        qux_cls = self.type_map.get_dt_container_cls('Qux', 'ndx-test')
        qux_cls2 = self.type_map.get_dt_container_cls('Qux', 'ndx-qux')

        self._check_classes(baz_cls, bar_cls, bar_cls2, qux_cls, qux_cls2)

    def test_get_class_include_from_separate_ns_3(self):
        """Test that get_class correctly sets the name and includes types correctly across namespaces.
        This is one of multiple tests carried out to ensure that order of which get_dt_container_cls is called
        does not impact the results

        first use EXTENSION namespace, then use EXTENSION namespace
        """
        self._build_separate_namespaces()

        baz_cls = self.type_map.get_dt_container_cls(
            'Baz', 'ndx-test')  # Qux and Bar are not yet resolved
        bar_cls = self.type_map.get_dt_container_cls('Bar', 'ndx-test')
        bar_cls2 = self.type_map.get_dt_container_cls('Bar', CORE_NAMESPACE)
        qux_cls2 = self.type_map.get_dt_container_cls('Qux', 'ndx-qux')
        qux_cls = self.type_map.get_dt_container_cls('Qux', 'ndx-test')

        self._check_classes(baz_cls, bar_cls, bar_cls2, qux_cls, qux_cls2)

    def test_get_class_include_from_separate_ns_4(self):
        """Test that get_class correctly sets the name and includes types correctly across namespaces.
        This is one of multiple tests carried out to ensure that order of which get_dt_container_cls is called
        does not impact the results

        first use ORIGINAL namespace, then use EXTENSION namespace
        """
        self._build_separate_namespaces()

        baz_cls = self.type_map.get_dt_container_cls(
            'Baz', 'ndx-test')  # Qux and Bar are not yet resolved
        bar_cls2 = self.type_map.get_dt_container_cls('Bar', CORE_NAMESPACE)
        bar_cls = self.type_map.get_dt_container_cls('Bar', 'ndx-test')
        qux_cls2 = self.type_map.get_dt_container_cls('Qux', 'ndx-qux')
        qux_cls = self.type_map.get_dt_container_cls('Qux', 'ndx-test')

        self._check_classes(baz_cls, bar_cls, bar_cls2, qux_cls, qux_cls2)