diff --git a/src/hdmf/spec/catalog.py b/src/hdmf/spec/catalog.py index 636eb3bc0..1645555ba 100644 --- a/src/hdmf/spec/catalog.py +++ b/src/hdmf/spec/catalog.py @@ -1,5 +1,6 @@ import copy from collections import OrderedDict +from pydantic import validate_call from .spec import BaseStorageSpec, GroupSpec from ..utils import docval, getargs diff --git a/src/hdmf/spec/namespace.py b/src/hdmf/spec/namespace.py index e7f5262a6..87cb98386 100644 --- a/src/hdmf/spec/namespace.py +++ b/src/hdmf/spec/namespace.py @@ -421,10 +421,20 @@ def __convert_spec_cls_keys(self, parent_cls, spec_cls, spec_dict): """Replace instances of data_type_def/inc in spec_dict with new values from spec_cls.""" # this is necessary because the def_key and inc_key may be different in each namespace # NOTE: this does not handle more than one custom set of keys + # breakpoint() if parent_cls.def_key() in spec_dict: spec_dict[spec_cls.def_key()] = spec_dict.pop(parent_cls.def_key()) if parent_cls.inc_key() in spec_dict: spec_dict[spec_cls.inc_key()] = spec_dict.pop(parent_cls.inc_key()) + # parent_def_key = parent_cls.__private_attributes__["_def_key"].get_default() + # if parent_def_key in spec_dict: + # spec_def_key = spec_cls.__private_attributes__["_def_key"].get_default() + # spec_dict[spec_def_key] = spec_dict.pop(parent_def_key) + # + # parent_inc_key = parent_cls.__private_attributes__["_inc_key"].get_default() + # if parent_inc_key in spec_dict: + # spec_inc_key = spec_cls.__private_attributes__["_inc_key"].get_default() + # spec_dict[spec_inc_key] = spec_dict.pop(parent_inc_key) def __resolve_includes(self, spec_cls, spec_dict, catalog): """Replace data type inc strings with the spec definition so the new spec is built with included fields. diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index 665be4a34..525a2c841 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -2,6 +2,8 @@ from abc import ABCMeta from collections import OrderedDict from warnings import warn +from pydantic import validate_call, BaseModel +from typing import Optional, Self from ..utils import docval, getargs, popargs, get_docval @@ -101,45 +103,13 @@ def build_spec(cls, spec_dict): return cls(**kwargs) -class Spec(ConstructableDict): +class Spec(BaseModel): ''' A base specification class ''' - - @docval({'name': 'doc', 'type': str, 'doc': 'a description about what this specification represents'}, - {'name': 'name', 'type': str, 'doc': 'The name of this attribute', 'default': None}, - {'name': 'required', 'type': bool, 'doc': 'whether or not this attribute is required', 'default': True}, - {'name': 'parent', 'type': 'hdmf.spec.spec.Spec', 'doc': 'the parent of this spec', 'default': None}) - def __init__(self, **kwargs): - name, doc, required, parent = getargs('name', 'doc', 'required', 'parent', kwargs) - super().__init__() - self['doc'] = doc - if name is not None: - self['name'] = name - if not required: - self['required'] = required - self._parent = parent - - @property - def doc(self): - ''' Documentation on what this Spec is specifying ''' - return self.get('doc', None) - - @property - def name(self): - ''' The name of the object being specified ''' - return self.get('name', None) - - @property - def parent(self): - ''' The parent specification of this specification ''' - return self._parent - - @parent.setter - def parent(self, spec): - ''' Set the parent of this specification ''' - if self._parent is not None: - raise AttributeError('Cannot re-assign parent.') - self._parent = spec + doc: str + name: Optional[str] = None + required: bool = True + parent: Optional[Self] = None @classmethod def build_const_args(cls, spec_dict): @@ -167,6 +137,69 @@ def path(self): # def __eq__(self, other): # return id(self) == id(other) +# class Spec(ConstructableDict): +# ''' A base specification class +# ''' +# +# @docval({'name': 'doc', 'type': str, 'doc': 'a description about what this specification represents'}, +# {'name': 'name', 'type': str, 'doc': 'The name of this attribute', 'default': None}, +# {'name': 'required', 'type': bool, 'doc': 'whether or not this attribute is required', 'default': True}, +# {'name': 'parent', 'type': 'hdmf.spec.spec.Spec', 'doc': 'the parent of this spec', 'default': None}) +# def __init__(self, **kwargs): +# name, doc, required, parent = getargs('name', 'doc', 'required', 'parent', kwargs) +# super().__init__() +# self['doc'] = doc +# if name is not None: +# self['name'] = name +# if not required: +# self['required'] = required +# self._parent = parent +# +# @property +# def doc(self): +# ''' Documentation on what this Spec is specifying ''' +# return self.get('doc', None) +# +# @property +# def name(self): +# ''' The name of the object being specified ''' +# return self.get('name', None) +# +# @property +# def parent(self): +# ''' The parent specification of this specification ''' +# return self._parent +# +# @parent.setter +# def parent(self, spec): +# ''' Set the parent of this specification ''' +# if self._parent is not None: +# raise AttributeError('Cannot re-assign parent.') +# self._parent = spec +# +# @classmethod +# def build_const_args(cls, spec_dict): +# ''' Build constructor arguments for this Spec class from a dictionary ''' +# ret = super().build_const_args(spec_dict) +# return ret +# +# def __hash__(self): +# return id(self) +# +# @property +# def path(self): +# stack = list() +# tmp = self +# while tmp is not None: +# name = tmp.name +# if name is None: +# name = tmp.data_type_def +# if name is None: +# name = tmp.data_type_inc +# stack.append(name) +# tmp = tmp.parent +# return "/".join(reversed(stack)) + _target_type_key = 'target_type' @@ -301,10 +334,10 @@ def build_const_args(cls, spec_dict): class BaseStorageSpec(Spec): ''' A specification for any object that can hold attributes. ''' - __inc_key = 'data_type_inc' - __def_key = 'data_type_def' - __type_key = 'data_type' - __id_key = 'object_id' + inc_key = 'data_type_inc' + def_key = 'data_type_def' + type_key = 'data_type' + id_key = 'object_id' @docval(*_attrbl_args) def __init__(self, **kwargs): @@ -460,45 +493,40 @@ def linkable(self): return self.get('linkable', True) @classmethod - def id_key(cls): - ''' Get the key used to store data ID on an instance - - Override this method to use a different name for 'object_id' + def def_key(cls): ''' - return cls.__id_key - - @classmethod - def type_key(cls): - ''' Get the key used to store data type on an instance - - Override this method to use a different name for 'data_type'. HDMF supports combining schema - that uses 'data_type' and at most one different name for 'data_type'. + Get the key used to define a data_type definition. ''' - return cls.__type_key + # Directly access the class attribute to avoid method conflict + return cls.__dict__.get("def_key", "data_type_def") @classmethod def inc_key(cls): - ''' Get the key used to define a data_type include. - - Override this method to use a different keyword for 'data_type_inc'. HDMF supports combining schema - that uses 'data_type_inc' and at most one different name for 'data_type_inc'. ''' - return cls.__inc_key + Get the key used to define a data_type include. + ''' + return cls.__dict__.get("inc_key", "data_type_inc") @classmethod - def def_key(cls): - ''' Get the key used to define a data_type definition. + def type_key(cls): + ''' + Get the key used to store data type on an instance. + ''' + return cls.__dict__.get("type_key", "data_type") - Override this method to use a different keyword for 'data_type_def' HDMF supports combining schema - that uses 'data_type_def' and at most one different name for 'data_type_def'. + @classmethod + def id_key(cls): + ''' + Get the key used to store data ID on an instance. ''' - return cls.__def_key + return cls.__dict__.get("id_key", "object_id") @property def data_type_inc(self): ''' The data type this specification inherits ''' return self.get(self.inc_key()) + @property def data_type_def(self): ''' The data type this specification defines '''