nwb-linkml/nwb_linkml/adapters/group.py

89 lines
3.2 KiB
Python
Raw Normal View History

"""
Adapter for NWB groups to linkml Classes
"""
import pdb
from typing import List
from linkml_runtime.linkml_model import ClassDefinition, SlotDefinition
from nwb_schema_language import Dataset, Group, ReferenceDtype, CompoundDtype, DTypeType
from nwb_linkml.adapters.classes import ClassAdapter
from nwb_linkml.adapters.dataset import DatasetAdapter
from nwb_linkml.adapters.adapter import BuildResult
from nwb_linkml.maps import QUANTITY_MAP
class GroupAdapter(ClassAdapter):
cls: Group
def build(self) -> BuildResult:
nested_res = self.build_subclasses()
# we don't propagate slots up to the next level since they are meant for this
# level (ie. a way to refer to our children)
res = self.build_base(extra_attrs=nested_res.slots)
# we do propagate classes tho
res.classes.extend(nested_res.classes)
return res
def handle_children(self, children: List[Group]) -> BuildResult:
"""
Make a special LinkML `children` slot that can
have any number of the objects that are of `neurodata_type_inc` class
Args:
children (List[:class:`.Group`]): Child groups
"""
child_slot = SlotDefinition(
name='children',
multivalued=True,
any_of=[{'range': cls.neurodata_type_inc} for cls in children]
)
return BuildResult(slots=[child_slot])
def build_subclasses(self) -> BuildResult:
"""
Build nested groups and datasets
Create ClassDefinitions for each, but then also create SlotDefinitions that
will be used as attributes linking the main class to the subclasses
"""
# Datasets are simple, they are terminal classes, and all logic
# for creating slots vs. classes is handled by the adapter class
dataset_res = BuildResult()
for dset in self.cls.datasets:
# if dset.name == 'timestamps':
# pdb.set_trace()
dset_adapter = DatasetAdapter(cls=dset, parent=self)
dataset_res += dset_adapter.build()
# Actually i'm not sure we have to special case this, we could handle it in
# i/o instead
# Groups are a bit more complicated because they can also behave like
# range declarations:
# eg. a group can have multiple groups with `neurodata_type_inc`, no name, and quantity of *,
# the group can then contain any number of groups of those included types as direct children
# group_res = BuildResult()
# children = []
# for group in self.cls.groups:
# if not group.name and \
# group.quantity == '*' and \
# group.neurodata_type_inc:
# children.append(group)
# else:
# group_adapter = GroupAdapter(cls=group, parent=self)
# group_res += group_adapter.build()
#
# group_res += self.handle_children(children)
group_res = BuildResult()
for group in self.cls.groups:
group_adapter = GroupAdapter(cls=group, parent=self)
group_res += group_adapter.build()
res = dataset_res + group_res
return res