nwb-linkml/nwb_linkml/tests/fixtures.py

317 lines
11 KiB
Python
Raw Normal View History

2024-07-02 04:44:35 +00:00
import shutil
2023-10-10 01:59:53 +00:00
from dataclasses import dataclass, field
2024-07-02 04:44:35 +00:00
from pathlib import Path
from types import ModuleType
2024-07-02 04:44:35 +00:00
from typing import Dict, Optional
2023-10-06 04:22:00 +00:00
2024-07-02 04:44:35 +00:00
import pytest
2023-10-06 04:22:00 +00:00
from linkml_runtime.dumpers import yaml_dumper
2024-07-02 04:23:31 +00:00
from linkml_runtime.linkml_model import (
ClassDefinition,
Prefix,
2024-07-02 04:44:35 +00:00
SchemaDefinition,
SlotDefinition,
2024-07-02 04:23:31 +00:00
TypeDefinition,
)
2024-07-02 04:44:35 +00:00
from nwb_linkml.adapters.namespaces import NamespacesAdapter
2024-07-31 08:27:45 +00:00
from nwb_linkml.io import schema as io
from nwb_linkml.providers import LinkMLProvider, PydanticProvider
from nwb_linkml.providers.linkml import LinkMLSchemaBuild
2024-07-02 04:44:35 +00:00
from nwb_schema_language import Attribute, Dataset, Group
2024-07-02 07:01:54 +00:00
__all__ = [
"NWBSchemaTest",
"TestSchemas",
"data_dir",
"linkml_schema",
"linkml_schema_bare",
"nwb_core_fixture",
"nwb_schema",
"tmp_output_dir",
"tmp_output_dir_func",
"tmp_output_dir_mod",
]
2024-07-02 04:23:31 +00:00
@pytest.fixture(scope="session")
def tmp_output_dir() -> Path:
2024-07-02 04:23:31 +00:00
path = Path(__file__).parent.resolve() / "__tmp__"
if path.exists():
for subdir in path.iterdir():
2024-07-09 10:32:37 +00:00
if subdir.name == "git":
# don't wipe out git repos every time, they don't rly change
continue
elif subdir.is_file() and subdir.parent != path:
continue
elif subdir.is_file():
subdir.unlink(missing_ok=True)
else:
shutil.rmtree(str(subdir))
path.mkdir(exist_ok=True)
return path
2024-07-02 04:23:31 +00:00
2023-10-06 04:22:00 +00:00
@pytest.fixture(scope="function")
def tmp_output_dir_func(tmp_output_dir) -> Path:
"""
tmp output dir that gets cleared between every function
cleans at the start rather than at cleanup in case the output is to be inspected
"""
2024-07-02 04:23:31 +00:00
subpath = tmp_output_dir / "__tmpfunc__"
2023-10-06 04:22:00 +00:00
if subpath.exists():
shutil.rmtree(str(subpath))
subpath.mkdir()
return subpath
2024-07-02 04:23:31 +00:00
2023-10-06 04:22:00 +00:00
@pytest.fixture(scope="module")
def tmp_output_dir_mod(tmp_output_dir) -> Path:
"""
tmp output dir that gets cleared between every function
cleans at the start rather than at cleanup in case the output is to be inspected
"""
2024-07-02 04:23:31 +00:00
subpath = tmp_output_dir / "__tmpmod__"
2023-10-06 04:22:00 +00:00
if subpath.exists():
shutil.rmtree(str(subpath))
subpath.mkdir()
return subpath
2024-07-04 03:39:49 +00:00
@pytest.fixture(scope="session", params=[{"core_version": "2.7.0", "hdmf_version": "1.8.0"}])
2023-10-09 22:06:53 +00:00
def nwb_core_fixture(request) -> NamespacesAdapter:
nwb_core = io.load_nwb_core(**request.param)
2024-07-02 04:23:31 +00:00
assert (
request.param["core_version"] in nwb_core.versions["core"]
) # 2.6.0 is actually 2.6.0-alpha
assert nwb_core.versions["hdmf-common"] == request.param["hdmf_version"]
return nwb_core
2024-07-31 08:17:39 +00:00
@pytest.fixture(scope="session")
def nwb_core_linkml(nwb_core_fixture, tmp_output_dir) -> LinkMLSchemaBuild:
provider = LinkMLProvider(tmp_output_dir, allow_repo=False, verbose=False)
result = provider.build(ns_adapter=nwb_core_fixture, force=True)
2024-07-31 08:17:39 +00:00
return result["core"]
@pytest.fixture(scope="session")
def nwb_core_module(nwb_core_linkml: LinkMLSchemaBuild, tmp_output_dir) -> ModuleType:
"""
Generated pydantic namespace from nwb core
"""
provider = PydanticProvider(tmp_output_dir, verbose=False)
result = provider.build(nwb_core_linkml.namespace, force=True)
2024-07-31 08:17:39 +00:00
mod = provider.get("core", version=nwb_core_linkml.version, allow_repo=False)
return mod
@pytest.fixture(scope="session")
def data_dir() -> Path:
2024-07-02 04:23:31 +00:00
path = Path(__file__).parent.resolve() / "data"
return path
2023-10-06 04:22:00 +00:00
2024-07-02 04:23:31 +00:00
2023-10-10 01:59:53 +00:00
@dataclass
2024-07-02 04:23:31 +00:00
class TestSchemas:
2023-10-10 01:59:53 +00:00
__test__ = False
2023-10-06 04:22:00 +00:00
core: SchemaDefinition
imported: SchemaDefinition
namespace: SchemaDefinition
2023-10-09 22:06:53 +00:00
core_path: Optional[Path] = None
imported_path: Optional[Path] = None
namespace_path: Optional[Path] = None
2023-10-06 04:22:00 +00:00
2024-07-02 04:23:31 +00:00
2023-10-06 04:22:00 +00:00
@pytest.fixture(scope="module")
2023-10-09 22:06:53 +00:00
def linkml_schema_bare() -> TestSchemas:
2023-10-06 04:22:00 +00:00
schema = TestSchemas(
core=SchemaDefinition(
name="core",
id="core",
version="1.0.1",
2024-07-02 04:23:31 +00:00
imports=["imported", "linkml:types"],
2023-10-06 04:22:00 +00:00
default_prefix="core",
2024-07-02 04:23:31 +00:00
prefixes={"linkml": Prefix("linkml", "https://w3id.org/linkml")},
2023-10-06 04:22:00 +00:00
description="Test core schema",
classes=[
ClassDefinition(
name="MainTopLevel",
description="The main class we are testing!",
is_a="MainThing",
tree_root=True,
attributes=[
SlotDefinition(
name="name",
description="A fixed property that should use Literal and be frozen",
range="string",
required=True,
ifabsent="string(toplevel)",
equals_string="toplevel",
2024-07-02 04:23:31 +00:00
identifier=True,
2023-10-06 04:22:00 +00:00
),
2024-07-02 04:23:31 +00:00
SlotDefinition(name="array", range="MainTopLevel__Array"),
2023-10-06 04:22:00 +00:00
SlotDefinition(
2024-07-02 04:23:31 +00:00
name="SkippableSlot", description="A slot that was meant to be skipped!"
2023-10-06 04:22:00 +00:00
),
SlotDefinition(
name="inline_dict",
description=(
"This should be inlined as a dictionary despite this class having"
" an identifier"
),
2023-10-06 04:22:00 +00:00
multivalued=True,
inlined=True,
inlined_as_list=False,
2024-07-02 04:23:31 +00:00
any_of=[{"range": "OtherClass"}, {"range": "StillAnotherClass"}],
),
],
2023-10-06 04:22:00 +00:00
),
ClassDefinition(
name="MainTopLevel__Array",
description="Main class's array",
is_a="Arraylike",
attributes=[
2024-07-02 04:23:31 +00:00
SlotDefinition(name="x", range="numeric", required=True),
SlotDefinition(name="y", range="numeric", required=True),
2023-10-06 04:22:00 +00:00
SlotDefinition(
name="z",
range="numeric",
required=False,
maximum_cardinality=3,
2024-07-02 04:23:31 +00:00
minimum_cardinality=3,
2023-10-06 04:22:00 +00:00
),
SlotDefinition(
name="a",
range="numeric",
required=False,
minimum_cardinality=4,
2024-07-02 04:23:31 +00:00
maximum_cardinality=4,
),
],
2023-10-06 04:22:00 +00:00
),
ClassDefinition(
name="skippable",
description="A class that lives to be skipped!",
),
ClassDefinition(
name="OtherClass",
description="Another class yno!",
attributes=[
2024-07-02 04:23:31 +00:00
SlotDefinition(name="name", range="string", required=True, identifier=True)
],
2023-10-06 04:22:00 +00:00
),
ClassDefinition(
name="StillAnotherClass",
description="And yet another!",
attributes=[
2024-07-02 04:23:31 +00:00
SlotDefinition(name="name", range="string", required=True, identifier=True)
],
),
2023-10-06 04:22:00 +00:00
],
2024-07-02 04:23:31 +00:00
types=[TypeDefinition(name="numeric", typeof="float")],
2023-10-06 04:22:00 +00:00
),
imported=SchemaDefinition(
name="imported",
id="imported",
version="1.4.5",
default_prefix="core",
2024-07-02 04:23:31 +00:00
imports=["linkml:types"],
prefixes={"linkml": Prefix("linkml", "https://w3id.org/linkml")},
classes=[
2023-10-06 04:22:00 +00:00
ClassDefinition(
name="MainThing",
description="Class imported by our main thing class!",
2024-07-02 04:23:31 +00:00
attributes=[SlotDefinition(name="meta_slot", range="string")],
2023-10-06 04:22:00 +00:00
),
2024-07-02 04:23:31 +00:00
ClassDefinition(name="Arraylike", abstract=True),
],
2023-10-06 04:22:00 +00:00
),
namespace=SchemaDefinition(
name="namespace",
id="namespace",
version="1.1.1",
default_prefix="namespace",
2024-07-02 04:23:31 +00:00
annotations=[
{"tag": "is_namespace", "value": "True"},
{"tag": "namespace", "value": "core"},
],
2023-10-06 04:22:00 +00:00
description="A namespace package that should import all other classes",
2024-07-02 04:23:31 +00:00
imports=["core", "imported"],
),
2023-10-06 04:22:00 +00:00
)
2023-10-09 22:06:53 +00:00
return schema
2024-07-02 04:23:31 +00:00
2023-10-09 22:06:53 +00:00
@pytest.fixture(scope="module")
def linkml_schema(tmp_output_dir_mod, linkml_schema_bare) -> TestSchemas:
"""
A test schema that includes
- Two schemas, one importing from the other
- Arraylike
- Required/static "name" field
- linkml metadata like tree_root
- skipping classes
"""
schema = linkml_schema_bare
2024-07-02 04:23:31 +00:00
test_schema_path = tmp_output_dir_mod / "test_schema"
2023-10-09 22:06:53 +00:00
test_schema_path.mkdir()
2024-07-02 04:23:31 +00:00
core_path = test_schema_path / "core.yaml"
imported_path = test_schema_path / "imported.yaml"
namespace_path = test_schema_path / "namespace.yaml"
2023-10-09 22:06:53 +00:00
2023-10-10 01:59:53 +00:00
schema.core_path = core_path
schema.imported_path = imported_path
schema.namespace_path = namespace_path
2023-10-09 22:06:53 +00:00
2023-10-06 04:22:00 +00:00
yaml_dumper.dump(schema.core, schema.core_path)
yaml_dumper.dump(schema.imported, schema.imported_path)
yaml_dumper.dump(schema.namespace, schema.namespace_path)
return schema
2023-10-10 01:59:53 +00:00
@dataclass
2024-07-02 04:23:31 +00:00
class NWBSchemaTest:
2023-10-10 01:59:53 +00:00
datasets: Dict[str, Dataset] = field(default_factory=dict)
groups: Dict[str, Group] = field(default_factory=dict)
2024-07-02 04:23:31 +00:00
2023-10-10 01:59:53 +00:00
@pytest.fixture()
def nwb_schema() -> NWBSchemaTest:
"""Minimal NWB schema for testing"""
image = Dataset(
neurodata_type_def="Image",
dtype="numeric",
neurodata_type_inc="NWBData",
2024-07-02 04:23:31 +00:00
dims=[["x", "y"], ["x", "y", "r, g, b"], ["x", "y", "r, g, b, a"]],
2023-10-10 01:59:53 +00:00
shape=[[None, None], [None, None, 3], [None, None, 4]],
doc="An image!",
2024-07-02 04:23:31 +00:00
attributes=[
2023-10-10 01:59:53 +00:00
Attribute(dtype="float32", name="resolution", doc="resolution!"),
2024-07-02 04:23:31 +00:00
Attribute(dtype="text", name="description", doc="Description!"),
],
2023-10-10 01:59:53 +00:00
)
images = Group(
2024-07-02 04:23:31 +00:00
neurodata_type_def="Images",
neurodata_type_inc="NWBDataInterface",
2023-10-10 01:59:53 +00:00
default_name="Images",
2024-07-02 04:23:31 +00:00
doc="Images!",
attributes=[Attribute(dtype="text", name="description", doc="description!")],
2023-10-10 01:59:53 +00:00
datasets=[
2024-07-02 04:23:31 +00:00
Dataset(neurodata_type_inc="Image", quantity="+", doc="images!"),
Dataset(
neurodata_type_inc="ImageReferences",
name="order_of_images",
doc="Image references!",
quantity="?",
),
],
2023-10-10 01:59:53 +00:00
)
2024-07-02 04:23:31 +00:00
return NWBSchemaTest(datasets={"image": image}, groups={"images": images})