From bb40b5acbc10d6d65d8bf859950f9da82de61b17 Mon Sep 17 00:00:00 2001 From: mavaylon1 Date: Wed, 10 Jan 2024 14:36:45 -0800 Subject: [PATCH] rough draft --- docs/gallery/example_config.yaml | 16 ++++++++++++++++ src/hdmf/__init__.py | 6 +++++- src/hdmf/build/manager.py | 4 +--- src/hdmf/common/__init__.py | 17 +++++++++++++++++ src/hdmf/container.py | 23 +++++++++++++++++++++++ src/hdmf/term_set.py | 25 +++++++++++++++++++++++++ tests/unit/common/test_table.py | 4 ++++ tests/unit/test_container.py | 4 ++++ 8 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 docs/gallery/example_config.yaml diff --git a/docs/gallery/example_config.yaml b/docs/gallery/example_config.yaml new file mode 100644 index 000000000..c6ace1765 --- /dev/null +++ b/docs/gallery/example_config.yaml @@ -0,0 +1,16 @@ +- data_type: VectorData + namespace: + namespace_version: + fields: + data: /Users/mavaylon/Research/NWB/hdmf2/hdmf/tests/unit/example_test_term_set.yaml + field2: ... +# - data_type: #Container2 +# namespace: +# namespace_version: +# fields: +# - name: +# doc: +# termset_path: +# - name: +# doc: +# termset_path: diff --git a/src/hdmf/__init__.py b/src/hdmf/__init__.py index 2699a28af..da1ad3b77 100644 --- a/src/hdmf/__init__.py +++ b/src/hdmf/__init__.py @@ -3,9 +3,13 @@ from .container import Container, Data, DataRegion, HERDManager from .region import ListSlicer from .utils import docval, getargs -from .term_set import TermSet, TermSetWrapper +from .term_set import TermSet, TermSetWrapper, TermSetConfigurator +# a global TermSetConfigurator +global TS_CONFIG +TS_CONFIG = TermSetConfigurator() + @docval( {"name": "dataset", "type": None, "doc": "the HDF5 dataset to slice"}, {"name": "region", "type": None, "doc": "the region reference to use to slice"}, diff --git a/src/hdmf/build/manager.py b/src/hdmf/build/manager.py index b50e9c93f..029c17efd 100644 --- a/src/hdmf/build/manager.py +++ b/src/hdmf/build/manager.py @@ -410,6 +410,7 @@ def __init__(self, **kwargs): self.__data_types = dict() self.__default_mapper_cls = mapper_cls self.__class_generator = ClassGenerator() + self.__load_termset_config = True self.register_generator(CustomClassGenerator) self.register_generator(MCIClassGenerator) @@ -480,9 +481,6 @@ def load_namespaces(self, **kwargs): self.register_container_type(new_ns, dt, container_cls) return deps - def load_config(self, **kwargs): - pass - @docval({"name": "namespace", "type": str, "doc": "the namespace containing the data_type"}, {"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"}, {"name": "autogen", "type": bool, "doc": "autogenerate class if one does not exist", "default": True}, diff --git a/src/hdmf/common/__init__.py b/src/hdmf/common/__init__.py index e0782effe..f18eb7497 100644 --- a/src/hdmf/common/__init__.py +++ b/src/hdmf/common/__init__.py @@ -107,6 +107,23 @@ def available_namespaces(): return __TYPE_MAP.namespace_catalog.namespaces +@docval({'name': 'config_path', 'type': str, 'doc': 'Path to the configuartion file.', + 'default': '/Users/mavaylon/Research/NWB/hdmf2/hdmf/docs/gallery/example_config.yaml'}) #update path +def load_termset_config(config_path: str): + """ + Load the configuration file for validation on the fields defined for the objects within the file. + By default, the curated configuration file is used, but can take in a custom file. + """ + return __TS_CONFIG.load_termset_config(config_path) + + +def unload_termset_config(): + """ + Remove validation according to termset configuration file. + """ + return __TS_CONFIG.unload_termset_config() + + # a function to get the container class for a give type @docval({'name': 'data_type', 'type': str, 'doc': 'the data_type to get the Container class for'}, diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 229e20083..4d8474700 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -5,6 +5,7 @@ from typing import Type from uuid import uuid4 from warnings import warn +import yaml import h5py import numpy as np @@ -13,6 +14,8 @@ from .data_utils import DataIO, append_data, extend_data from .utils import docval, get_docval, getargs, ExtenderMeta, get_data_shape, popargs, LabelledDict +from .term_set import TermSet, TermSetWrapper + def _set_exp(cls): """Set a class as being experimental""" @@ -232,6 +235,24 @@ def __init__(self, **kwargs): self.__read_io = None self.__obj = None + @docval({'name': 'fields', 'type': dict, 'doc': 'The fields/parameters/attibutes for the object.'}) + def init_validation(self, fields): + # load termset configuartion file from global Config + from . import TS_CONFIG #update path + # Before calling super().__init__() and before setting fields, check for config file for + # validation via TermSetWrapper. + with open(TS_CONFIG.path, 'r') as config: + termset_config = yaml.safe_load(config) + object_name = self.__class__.__name__ + + for obj_config in termset_config: + if obj_config['data_type'] == object_name: + for attr in obj_config['fields']: + if attr in fields: # make sure any custom fields are not handled (i.e., make an extension) + termset_path = obj_config['fields'][attr] + termset = TermSet(term_schema_path=termset_path) + fields[attr] = TermSetWrapper(value=fields[attr], termset=termset) + @property def read_io(self): """ @@ -785,6 +806,8 @@ class Data(AbstractContainer): @docval({'name': 'name', 'type': str, 'doc': 'the name of this container'}, {'name': 'data', 'type': ('scalar_data', 'array_data', 'data'), 'doc': 'the source of the data'}) def __init__(self, **kwargs): + self.init_validation(fields=kwargs) + breakpoint() data = popargs('data', kwargs) super().__init__(**kwargs) diff --git a/src/hdmf/term_set.py b/src/hdmf/term_set.py index f7169bdfd..4a196d5cc 100644 --- a/src/hdmf/term_set.py +++ b/src/hdmf/term_set.py @@ -304,3 +304,28 @@ def extend(self, arg): else: msg = ('"%s" is not in the term set.' % ', '.join([str(item) for item in bad_data])) raise ValueError(msg) + +class TermSetConfigurator: + """ + + """ + def __init__(self): + self.path = '/Users/mavaylon/Research/NWB/hdmf2/hdmf/docs/gallery/example_config.yaml' + + # @property + # def config_path(self): + # return self.__config_path + + @docval({'name': 'config_path', 'type': str, 'doc': 'Path to the configuartion file.'}) + def load_termset_config(config_path: str): + """ + Load the configuration file for validation on the fields defined for the objects within the file. + By default, the curated configuration file is used, but can take in a custom file. + """ + self.path = config_path + + def unload_termset_config(): + """ + Remove validation according to termset configuration file. + """ + self.path = None diff --git a/tests/unit/common/test_table.py b/tests/unit/common/test_table.py index 7246a8ba8..9112c4e5f 100644 --- a/tests/unit/common/test_table.py +++ b/tests/unit/common/test_table.py @@ -36,6 +36,10 @@ except ImportError: LINKML_INSTALLED = False +class TestVDConfig(TestCase): + def test_init_config(self): + vd = VectorData(name='data', description='',data=['Homo sapiens']) + class TestDynamicTable(TestCase): diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index b5a2d87e8..ec74d5cb0 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -32,6 +32,10 @@ def test_link_and_get_resources(self): er_get = em.get_linked_resources() self.assertEqual(er, er_get) +class TestContainerConfig(TestCase): + def test_init_config(self): + obj = Container('obj1') + class TestContainer(TestCase):