nwb-linkml/nwb_linkml/tests/test_adapters/test_adapter_classes.py

194 lines
6.6 KiB
Python
Raw Permalink Normal View History

2023-10-10 02:05:08 +00:00
import pdb
2023-10-10 01:59:53 +00:00
import pytest
from ..fixtures import linkml_schema_bare, linkml_schema, nwb_schema
2023-10-10 03:13:42 +00:00
from linkml_runtime.linkml_model import SlotDefinition
from nwb_linkml.adapters import DatasetAdapter, ClassAdapter, GroupAdapter
from nwb_schema_language import Group, Dataset, ReferenceDtype, CompoundDtype
2023-10-10 01:59:53 +00:00
def test_build_base(nwb_schema):
2023-10-10 03:13:42 +00:00
# simplest case, nothing special here. Should be same behavior between dataset and group
dset = DatasetAdapter(cls=nwb_schema.datasets['image'])
2023-10-10 03:13:42 +00:00
base = dset.build_base()
assert len(base.slots) == 0
assert len(base.classes) == 1
img = base.classes[0]
assert img.name == "Image"
# no parent class, tree_root should be true
2023-10-10 03:13:42 +00:00
assert img.tree_root
assert len(img.attributes) == 3
# now with parent class
groups = GroupAdapter(cls=nwb_schema.groups['images'])
dset.parent = groups
base = dset.build_base()
# we made a self-slot (will be tested elsewhere)
assert len(base.slots) == 1
assert len(base.classes) == 1
img = base.classes[0]
assert not img.tree_root
assert len(img.attributes) == 3
# now try adding an extra attribute
slot = SlotDefinition(name="newslot", range="string")
# should coerce single slot to a list within the method
base = dset.build_base(extra_attrs=slot)
assert len(base.slots) == 1
assert len(base.classes) == 1
img = base.classes[0]
assert len(img.attributes) == 4
assert img.attributes['newslot'] is slot
def test_get_attr_name():
"""Name method used by parentless classes"""
cls = Dataset(neurodata_type_def='MyClass', doc='a class')
adapter = DatasetAdapter(cls=cls)
# type_defs get their original name
assert adapter._get_attr_name() == 'MyClass'
# explicit names get that name, but only if there is no type_def
adapter.cls.name = 'MyClassName'
assert adapter._get_attr_name() == 'MyClass'
adapter.cls.neurodata_type_def = None
assert adapter._get_attr_name() == 'MyClassName'
# if neither, use the type inc
adapter.cls.neurodata_type_inc = 'MyThirdName'
assert adapter._get_attr_name() == 'MyClassName'
adapter.cls.name = None
assert adapter._get_attr_name() == 'MyThirdName'
# if none are present, raise a value error
adapter.cls.neurodata_type_inc = None
with pytest.raises(ValueError):
adapter._get_attr_name()
def test_get_full_name():
"""Name used by child classes"""
cls = Dataset(neurodata_type_def='Child', doc='a class')
parent = GroupAdapter(cls=Group(neurodata_type_def='Parent', doc='a class'))
adapter = DatasetAdapter(cls=cls, parent=parent)
# if child has its own type_def, use that
assert adapter._get_full_name() == 'Child'
# same thing with type_inc
adapter.cls.neurodata_type_def = None
adapter.cls.neurodata_type_inc = 'ChildInc'
assert adapter._get_full_name() == 'ChildInc'
# if it just has a name, it gets concatenated with its parents
adapter.cls.neurodata_type_inc = None
adapter.cls.name = 'ChildName'
assert adapter._get_full_name() == 'Parent__ChildName'
# this should work at any depth of nesting if the parent is not an independently defined class
grandparent = GroupAdapter(cls=Group(neurodata_type_def='Grandparent', doc='a class'))
parent.cls.neurodata_type_def = None
parent.cls.name = 'ParentName'
parent.parent = grandparent
assert adapter._get_full_name() == 'Grandparent__ParentName__ChildName'
# if it has none, raise value error
adapter.cls.name = None
with pytest.raises(ValueError):
adapter._get_full_name()
def test_self_slot():
"""
Slot that represents ourselves to our parent
Quantity map is tested elsewhere
"""
cls = Dataset(neurodata_type_def='ChildClass', doc='a class', quantity='?')
parent = GroupAdapter(cls=Group(neurodata_type_def='Parent', doc='a class'))
adapter = DatasetAdapter(cls=cls, parent=parent)
# base case - snake case a type def
slot = adapter.build_self_slot()
assert slot.name == 'child_class'
assert slot.range == 'ChildClass' == adapter._get_full_name()
# this should be the slot that gets build with the build_base method
base = adapter.build_base()
assert len(base.slots) == 1
assert base.slots[0] == slot
# if class has a unique name, use that without changing, but only if no type_def
adapter.cls.name = "FixedName"
slot = adapter.build_self_slot()
assert slot.name == 'child_class'
adapter.cls.neurodata_type_def = None
slot = adapter.build_self_slot()
assert slot.name == 'FixedName'
assert slot.range == adapter._get_full_name()
# type_inc works the same as type_def, but only if name and type_def are None
adapter.cls.neurodata_type_inc = 'IncName'
slot = adapter.build_self_slot()
assert slot.name == 'FixedName'
adapter.cls.name = None
slot = adapter.build_self_slot()
assert slot.name == 'inc_name'
assert slot.range == adapter._get_full_name()
# if we have nothing, raise value error
adapter.cls.neurodata_type_inc = None
with pytest.raises(ValueError):
adapter.build_self_slot()
def test_name_slot():
"""Classes with a fixed name should name slot with a fixed value"""
# no name
cls = DatasetAdapter(cls=Dataset(neurodata_type_def='MyClass', doc='a class'))
slot = cls.build_name_slot()
assert slot.name == 'name'
assert slot.required
assert slot.range == 'string'
assert slot.identifier
assert slot.ifabsent is None
assert slot.equals_string is None
cls.cls.name = 'FixedName'
slot = cls.build_name_slot()
assert slot.name == 'name'
assert slot.required
assert slot.range == 'string'
assert slot.identifier
assert slot.ifabsent == 'string(FixedName)'
assert slot.equals_string == 'FixedName'
def test_handle_dtype(nwb_schema):
"""
Dtypes should be translated from nwb schema language to linkml
Dtypes are validated by the nwb_schema_language classes, so we don't do that here
"""
cls = DatasetAdapter(cls=Dataset(neurodata_type_def='MyClass', doc='a class'))
reftype = ReferenceDtype(target_type='TargetClass', reftype='reference')
compoundtype = [
CompoundDtype(name='field_a', doc='field a!', dtype='int32'),
CompoundDtype(name='field_b', doc='field b!', dtype='text'),
CompoundDtype(name="reference", doc="reference!", dtype=reftype)
]
assert cls.handle_dtype(reftype) == 'TargetClass'
assert cls.handle_dtype(None) == 'AnyType'
assert cls.handle_dtype([]) == 'AnyType'
# handling compound types is currently TODO
assert cls.handle_dtype(compoundtype) == 'AnyType'
assert cls.handle_dtype('int32') == 'int32'
2023-10-10 01:59:53 +00:00