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
|
|
|
|
2024-07-02 04:23:31 +00:00
|
|
|
|
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
|
2024-07-02 04:23:31 +00:00
|
|
|
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"
|
2024-04-17 19:59:53 +00:00
|
|
|
# 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
|
2024-07-02 04:23:31 +00:00
|
|
|
groups = GroupAdapter(cls=nwb_schema.groups["images"])
|
2023-10-10 03:13:42 +00:00
|
|
|
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
|
2024-07-02 04:23:31 +00:00
|
|
|
assert img.attributes["newslot"] is slot
|
|
|
|
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
def test_get_attr_name():
|
|
|
|
"""Name method used by parentless classes"""
|
2024-07-02 04:23:31 +00:00
|
|
|
cls = Dataset(neurodata_type_def="MyClass", doc="a class")
|
2023-10-10 03:13:42 +00:00
|
|
|
adapter = DatasetAdapter(cls=cls)
|
|
|
|
# type_defs get their original name
|
2024-07-02 04:23:31 +00:00
|
|
|
assert adapter._get_attr_name() == "MyClass"
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
# explicit names get that name, but only if there is no type_def
|
2024-07-02 04:23:31 +00:00
|
|
|
adapter.cls.name = "MyClassName"
|
|
|
|
assert adapter._get_attr_name() == "MyClass"
|
2023-10-10 03:13:42 +00:00
|
|
|
adapter.cls.neurodata_type_def = None
|
2024-07-02 04:23:31 +00:00
|
|
|
assert adapter._get_attr_name() == "MyClassName"
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
# if neither, use the type inc
|
2024-07-02 04:23:31 +00:00
|
|
|
adapter.cls.neurodata_type_inc = "MyThirdName"
|
|
|
|
assert adapter._get_attr_name() == "MyClassName"
|
2023-10-10 03:13:42 +00:00
|
|
|
adapter.cls.name = None
|
2024-07-02 04:23:31 +00:00
|
|
|
assert adapter._get_attr_name() == "MyThirdName"
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
# if none are present, raise a value error
|
|
|
|
adapter.cls.neurodata_type_inc = None
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
adapter._get_attr_name()
|
|
|
|
|
2024-07-02 04:23:31 +00:00
|
|
|
|
2023-10-10 03:13:42 +00:00
|
|
|
def test_get_full_name():
|
|
|
|
"""Name used by child classes"""
|
2024-07-02 04:23:31 +00:00
|
|
|
cls = Dataset(neurodata_type_def="Child", doc="a class")
|
|
|
|
parent = GroupAdapter(cls=Group(neurodata_type_def="Parent", doc="a class"))
|
2023-10-10 03:13:42 +00:00
|
|
|
adapter = DatasetAdapter(cls=cls, parent=parent)
|
|
|
|
|
|
|
|
# if child has its own type_def, use that
|
2024-07-02 04:23:31 +00:00
|
|
|
assert adapter._get_full_name() == "Child"
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
# same thing with type_inc
|
|
|
|
adapter.cls.neurodata_type_def = None
|
2024-07-02 04:23:31 +00:00
|
|
|
adapter.cls.neurodata_type_inc = "ChildInc"
|
|
|
|
assert adapter._get_full_name() == "ChildInc"
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
# if it just has a name, it gets concatenated with its parents
|
|
|
|
adapter.cls.neurodata_type_inc = None
|
2024-07-02 04:23:31 +00:00
|
|
|
adapter.cls.name = "ChildName"
|
|
|
|
assert adapter._get_full_name() == "Parent__ChildName"
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
# this should work at any depth of nesting if the parent is not an independently defined class
|
2024-07-02 04:23:31 +00:00
|
|
|
grandparent = GroupAdapter(cls=Group(neurodata_type_def="Grandparent", doc="a class"))
|
2023-10-10 03:13:42 +00:00
|
|
|
parent.cls.neurodata_type_def = None
|
2024-07-02 04:23:31 +00:00
|
|
|
parent.cls.name = "ParentName"
|
2023-10-10 03:13:42 +00:00
|
|
|
parent.parent = grandparent
|
2024-07-02 04:23:31 +00:00
|
|
|
assert adapter._get_full_name() == "Grandparent__ParentName__ChildName"
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
# if it has none, raise value error
|
|
|
|
adapter.cls.name = None
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
adapter._get_full_name()
|
|
|
|
|
2024-07-02 04:23:31 +00:00
|
|
|
|
2023-10-10 03:13:42 +00:00
|
|
|
def test_self_slot():
|
|
|
|
"""
|
|
|
|
Slot that represents ourselves to our parent
|
|
|
|
|
|
|
|
Quantity map is tested elsewhere
|
|
|
|
"""
|
2024-07-02 04:23:31 +00:00
|
|
|
cls = Dataset(neurodata_type_def="ChildClass", doc="a class", quantity="?")
|
|
|
|
parent = GroupAdapter(cls=Group(neurodata_type_def="Parent", doc="a class"))
|
2023-10-10 03:13:42 +00:00
|
|
|
adapter = DatasetAdapter(cls=cls, parent=parent)
|
|
|
|
|
|
|
|
# base case - snake case a type def
|
|
|
|
slot = adapter.build_self_slot()
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.name == "child_class"
|
|
|
|
assert slot.range == "ChildClass" == adapter._get_full_name()
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
# 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()
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.name == "child_class"
|
2023-10-10 03:13:42 +00:00
|
|
|
adapter.cls.neurodata_type_def = None
|
|
|
|
slot = adapter.build_self_slot()
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.name == "FixedName"
|
2023-10-10 03:13:42 +00:00
|
|
|
assert slot.range == adapter._get_full_name()
|
|
|
|
|
|
|
|
# type_inc works the same as type_def, but only if name and type_def are None
|
2024-07-02 04:23:31 +00:00
|
|
|
adapter.cls.neurodata_type_inc = "IncName"
|
2023-10-10 03:13:42 +00:00
|
|
|
slot = adapter.build_self_slot()
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.name == "FixedName"
|
2023-10-10 03:13:42 +00:00
|
|
|
adapter.cls.name = None
|
|
|
|
slot = adapter.build_self_slot()
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.name == "inc_name"
|
2023-10-10 03:13:42 +00:00
|
|
|
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
|
2024-07-02 04:23:31 +00:00
|
|
|
cls = DatasetAdapter(cls=Dataset(neurodata_type_def="MyClass", doc="a class"))
|
2023-10-10 03:13:42 +00:00
|
|
|
slot = cls.build_name_slot()
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.name == "name"
|
2023-10-10 03:13:42 +00:00
|
|
|
assert slot.required
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.range == "string"
|
2023-10-10 03:13:42 +00:00
|
|
|
assert slot.identifier
|
|
|
|
assert slot.ifabsent is None
|
|
|
|
assert slot.equals_string is None
|
|
|
|
|
2024-07-02 04:23:31 +00:00
|
|
|
cls.cls.name = "FixedName"
|
2023-10-10 03:13:42 +00:00
|
|
|
slot = cls.build_name_slot()
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.name == "name"
|
2023-10-10 03:13:42 +00:00
|
|
|
assert slot.required
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.range == "string"
|
2023-10-10 03:13:42 +00:00
|
|
|
assert slot.identifier
|
2024-07-02 04:23:31 +00:00
|
|
|
assert slot.ifabsent == "string(FixedName)"
|
|
|
|
assert slot.equals_string == "FixedName"
|
2023-10-10 03:13:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
2024-07-02 04:23:31 +00:00
|
|
|
cls = DatasetAdapter(cls=Dataset(neurodata_type_def="MyClass", doc="a class"))
|
2023-10-10 03:13:42 +00:00
|
|
|
|
2024-07-02 04:23:31 +00:00
|
|
|
reftype = ReferenceDtype(target_type="TargetClass", reftype="reference")
|
2023-10-10 03:13:42 +00:00
|
|
|
compoundtype = [
|
2024-07-02 04:23:31 +00:00
|
|
|
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),
|
2023-10-10 03:13:42 +00:00
|
|
|
]
|
|
|
|
|
2024-07-02 04:23:31 +00:00
|
|
|
assert cls.handle_dtype(reftype) == "TargetClass"
|
|
|
|
assert cls.handle_dtype(None) == "AnyType"
|
|
|
|
assert cls.handle_dtype([]) == "AnyType"
|
2023-10-10 03:13:42 +00:00
|
|
|
# handling compound types is currently TODO
|
2024-07-02 04:23:31 +00:00
|
|
|
assert cls.handle_dtype(compoundtype) == "AnyType"
|
|
|
|
assert cls.handle_dtype("int32") == "int32"
|