Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: get_class can't work recursively #794

Open
3 tasks done
bendichter opened this issue Nov 22, 2022 · 4 comments · May be fixed by #796
Open
3 tasks done

[Bug]: get_class can't work recursively #794

bendichter opened this issue Nov 22, 2022 · 4 comments · May be fixed by #796
Labels
category: bug errors in the code or code behavior priority: low alternative solution already working and/or relevant to only specific user(s) topic: extension issues related to extensions or dynamic class generation

Comments

@bendichter
Copy link
Contributor

What happened?

If you try to define a neurodata_type that references itself, e.g.

sample = NWBGroupSpec(
    neurodata_type_def="Sample",
    neurodata_type_inc="NWBDataInterface",
    doc="Description of the sample that was studied.",
    groups=[
        NWBGroupSpec(
            quantity="*",
            neurodata_type_inc="Sample",
            doc="sample",
        )
    ],
)

then when you run get_class you get a RecursionError:

---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
Cell In [1], line 1
----> 1 from ndx_biosample import Sample

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/ndx_biosample/__init__.py:27
     22 load_namespaces(ndx_biosample_specpath)
     24 # TODO: import your classes here or define your class using get_class to make
     25 # them accessible at the package level
     26 #BaseType = get_class('BaseType', 'ndx-biosample')
---> 27 Sample = get_class('Sample', 'ndx-biosample')

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:649, in docval.<locals>.dec.<locals>.func_call(*args, **kwargs)
    647 def func_call(*args, **kwargs):
    648     pargs = _check_args(args, kwargs)
--> 649     return func(**pargs)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/pynwb/__init__.py:185, in get_class(**kwargs)
    162 """
    163 Parse the YAML file for a given neurodata_type that is a subclass of NWBContainer and automatically generate its
    164 python API. This will work for most containers, but is known to not work for descendants of MultiContainerInterface
   (...)
    182 
    183 """
    184 neurodata_type, namespace = getargs('neurodata_type', 'namespace', kwargs)
--> 185 return __TYPE_MAP.get_dt_container_cls(neurodata_type, namespace)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:645, in docval.<locals>.dec.<locals>.func_call(*args, **kwargs)
    643 def func_call(*args, **kwargs):
    644     pargs = _check_args(args, kwargs)
--> 645     return func(args[0], **pargs)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:518, in TypeMap.get_dt_container_cls(self, **kwargs)
    516 if cls is None and autogen:  # dynamically generate a class
    517     spec = self.__ns_catalog.get_spec(namespace, data_type)
--> 518     self.__check_dependent_types(spec, namespace)
    519     parent_cls = self.__get_parent_cls(namespace, data_type, spec)
    520     attr_names = self.__default_mapper_cls.get_attr_names(spec)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:544, in TypeMap.__check_dependent_types(self, spec, namespace)
    542 if isinstance(spec, GroupSpec):
    543     for child_spec in (spec.groups + spec.datasets + spec.links):
--> 544         __check_dependent_types_helper(child_spec, namespace)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:531, in TypeMap.__check_dependent_types.<locals>.__check_dependent_types_helper(spec, namespace)
    529 if isinstance(spec, (GroupSpec, DatasetSpec)):
    530     if spec.data_type_inc is not None:
--> 531         self.get_dt_container_cls(spec.data_type_inc, namespace)  # TODO handle recursive definitions
    532     if spec.data_type_def is not None:  # nested type definition
    533         self.get_dt_container_cls(spec.data_type_def, namespace)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:645, in docval.<locals>.dec.<locals>.func_call(*args, **kwargs)
    643 def func_call(*args, **kwargs):
    644     pargs = _check_args(args, kwargs)
--> 645     return func(args[0], **pargs)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:518, in TypeMap.get_dt_container_cls(self, **kwargs)
    516 if cls is None and autogen:  # dynamically generate a class
    517     spec = self.__ns_catalog.get_spec(namespace, data_type)
--> 518     self.__check_dependent_types(spec, namespace)
    519     parent_cls = self.__get_parent_cls(namespace, data_type, spec)
    520     attr_names = self.__default_mapper_cls.get_attr_names(spec)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:544, in TypeMap.__check_dependent_types(self, spec, namespace)
    542 if isinstance(spec, GroupSpec):
    543     for child_spec in (spec.groups + spec.datasets + spec.links):
--> 544         __check_dependent_types_helper(child_spec, namespace)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:531, in TypeMap.__check_dependent_types.<locals>.__check_dependent_types_helper(spec, namespace)
    529 if isinstance(spec, (GroupSpec, DatasetSpec)):
    530     if spec.data_type_inc is not None:
--> 531         self.get_dt_container_cls(spec.data_type_inc, namespace)  # TODO handle recursive definitions
    532     if spec.data_type_def is not None:  # nested type definition
    533         self.get_dt_container_cls(spec.data_type_def, namespace)

    [... skipping similar frames: docval.<locals>.dec.<locals>.func_call at line 645 (735 times), TypeMap.__check_dependent_types at line 544 (734 times), TypeMap.__check_dependent_types.<locals>.__check_dependent_types_helper at line 531 (734 times), TypeMap.get_dt_container_cls at line 518 (734 times)]

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:518, in TypeMap.get_dt_container_cls(self, **kwargs)
    516 if cls is None and autogen:  # dynamically generate a class
    517     spec = self.__ns_catalog.get_spec(namespace, data_type)
--> 518     self.__check_dependent_types(spec, namespace)
    519     parent_cls = self.__get_parent_cls(namespace, data_type, spec)
    520     attr_names = self.__default_mapper_cls.get_attr_names(spec)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:544, in TypeMap.__check_dependent_types(self, spec, namespace)
    542 if isinstance(spec, GroupSpec):
    543     for child_spec in (spec.groups + spec.datasets + spec.links):
--> 544         __check_dependent_types_helper(child_spec, namespace)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:531, in TypeMap.__check_dependent_types.<locals>.__check_dependent_types_helper(spec, namespace)
    529 if isinstance(spec, (GroupSpec, DatasetSpec)):
    530     if spec.data_type_inc is not None:
--> 531         self.get_dt_container_cls(spec.data_type_inc, namespace)  # TODO handle recursive definitions
    532     if spec.data_type_def is not None:  # nested type definition
    533         self.get_dt_container_cls(spec.data_type_def, namespace)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:645, in docval.<locals>.dec.<locals>.func_call(*args, **kwargs)
    643 def func_call(*args, **kwargs):
    644     pargs = _check_args(args, kwargs)
--> 645     return func(args[0], **pargs)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/build/manager.py:517, in TypeMap.get_dt_container_cls(self, **kwargs)
    515 cls = self.__get_container_cls(namespace, data_type)
    516 if cls is None and autogen:  # dynamically generate a class
--> 517     spec = self.__ns_catalog.get_spec(namespace, data_type)
    518     self.__check_dependent_types(spec, namespace)
    519     parent_cls = self.__get_parent_cls(namespace, data_type, spec)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:645, in docval.<locals>.dec.<locals>.func_call(*args, **kwargs)
    643 def func_call(*args, **kwargs):
    644     pargs = _check_args(args, kwargs)
--> 645     return func(args[0], **pargs)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/spec/namespace.py:316, in NamespaceCatalog.get_spec(self, **kwargs)
    314 if namespace not in self.__namespaces:
    315     raise KeyError("'%s' not a namespace" % namespace)
--> 316 return self.__namespaces[namespace].get_spec(data_type)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:645, in docval.<locals>.dec.<locals>.func_call(*args, **kwargs)
    643 def func_call(*args, **kwargs):
    644     pargs = _check_args(args, kwargs)
--> 645     return func(args[0], **pargs)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/spec/namespace.py:148, in SpecNamespace.get_spec(self, **kwargs)
    146 """Get the Spec object for the given data type"""
    147 data_type = getargs('data_type', kwargs)
--> 148 spec = self.__catalog.get_spec(data_type)
    149 if spec is None:
    150     raise ValueError("No specification for '%s' in namespace '%s'" % (data_type, self.name))

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:644, in docval.<locals>.dec.<locals>.func_call(*args, **kwargs)
    643 def func_call(*args, **kwargs):
--> 644     pargs = _check_args(args, kwargs)
    645     return func(args[0], **pargs)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:616, in docval.<locals>.dec.<locals>._check_args(args, kwargs)
    613 """Parse and check arguments to decorated function. Raise warnings and errors as appropriate."""
    614 # this function was separated from func_call() in order to make stepping through lines of code using pdb
    615 # easier
--> 616 parsed = __parse_args(
    617     loc_val,
    618     args[1:] if is_method else args,
    619     kwargs,
    620     enforce_type=enforce_type,
    621     enforce_shape=enforce_shape,
    622     allow_extra=allow_extra,
    623     allow_positional=allow_positional
    624 )
    626 parse_warnings = parsed.get('future_warnings')
    627 if parse_warnings:

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/site-packages/hdmf/utils.py:221, in __parse_args(validator, args, kwargs, enforce_type, enforce_shape, allow_extra, allow_positional)
    218 try:
    219     # check for duplicates in docval
    220     names = [x['name'] for x in validator]
--> 221     duplicated = [item for item, count in collections.Counter(names).items()
    222                   if count > 1]
    223     if duplicated:
    224         raise ValueError(
    225             'The following names are duplicated: {}'.format(duplicated))

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/collections/__init__.py:593, in Counter.__init__(self, iterable, **kwds)
    582 '''Create a new, empty Counter object.  And if given, count elements
    583 from an input iterable.  Or, initialize the count from another mapping
    584 of elements to their counts.
   (...)
    590 
    591 '''
    592 super().__init__()
--> 593 self.update(iterable, **kwds)

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/collections/__init__.py:670, in Counter.update(self, iterable, **kwds)
    662 # The regular dict.update() operation makes no sense here because the
    663 # replace behavior results in the some of original untouched counts
    664 # being mixed-in with all of the other counts for a mismash that
    665 # doesn't have a straight-forward interpretation in most counting
    666 # contexts.  Instead, we implement straight-addition.  Both the inputs
    667 # and outputs are allowed to contain zero and negative counts.
    669 if iterable is not None:
--> 670     if isinstance(iterable, _collections_abc.Mapping):
    671         if self:
    672             self_get = self.get

File ~/opt/miniconda3/envs/conda_py3.9/lib/python3.9/abc.py:119, in ABCMeta.__instancecheck__(cls, instance)
    117 def __instancecheck__(cls, instance):
    118     """Override for isinstance(instance, cls)."""
--> 119     return _abc_instancecheck(cls, instance)

RecursionError: maximum recursion depth exceeded in comparison

Steps to Reproduce

see above

Traceback

see above

Operating System

macOS

Python Executable

Conda

Python Version

3.9

Package Versions

No response

Code of Conduct

@bendichter
Copy link
Contributor Author

@rly I think it's possible to create an API for this manually, correct? Would it be possible to detect this and fix it?

@t-b
Copy link
Contributor

t-b commented Nov 22, 2022

I've reported something similiar in NeurodataWithoutBorders/pynwb#1299 a while ago.

@bendichter
Copy link
Contributor Author

ah yes, I was looking for this! Thanks for the pointer, @t-b!

@rly rly linked a pull request Nov 29, 2022 that will close this issue
7 tasks
@bendichter
Copy link
Contributor Author

The solution should work for attributes as well. It should be possible to specify that a neurodata type has a an attribute that is a reference to a object of the same type.

@rly rly added category: bug errors in the code or code behavior priority: low alternative solution already working and/or relevant to only specific user(s) topic: extension issues related to extensions or dynamic class generation labels Jan 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category: bug errors in the code or code behavior priority: low alternative solution already working and/or relevant to only specific user(s) topic: extension issues related to extensions or dynamic class generation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants