From 3641d33cc81a6c759ef3f8c9c976a196e52df90f Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Mon, 2 Sep 2024 13:40:46 -0700 Subject: [PATCH] split up fixtures and nwb fixture in particular, add --clean pytest option --- nwb_linkml/src/nwb_linkml/io/hdf5.py | 6 +- nwb_linkml/tests/conftest.py | 8 +- nwb_linkml/tests/fixtures/__init__.py | 28 ++ .../tests/{fixtures.py => fixtures/nwb.py} | 393 +++--------------- nwb_linkml/tests/fixtures/paths.py | 58 +++ nwb_linkml/tests/fixtures/schema.py | 251 +++++++++++ nwb_linkml/tests/test_includes/test_hdmf.py | 4 +- nwb_linkml/tests/test_io/test_io_hdf5.py | 2 +- .../nwb_models/models/pydantic/__init__.py | 1 - 9 files changed, 403 insertions(+), 348 deletions(-) create mode 100644 nwb_linkml/tests/fixtures/__init__.py rename nwb_linkml/tests/{fixtures.py => fixtures/nwb.py} (55%) create mode 100644 nwb_linkml/tests/fixtures/paths.py create mode 100644 nwb_linkml/tests/fixtures/schema.py diff --git a/nwb_linkml/src/nwb_linkml/io/hdf5.py b/nwb_linkml/src/nwb_linkml/io/hdf5.py index 2da77f4..ba1d017 100644 --- a/nwb_linkml/src/nwb_linkml/io/hdf5.py +++ b/nwb_linkml/src/nwb_linkml/io/hdf5.py @@ -117,11 +117,9 @@ def filter_dependency_graph(g: nx.DiGraph) -> nx.DiGraph: """ remove_nodes = [] node: str - for node in g.nodes.keys(): + for node in g.nodes: ndtype = g.nodes[node].get("neurodata_type", None) - if ndtype == "VectorData": - remove_nodes.append(node) - elif not ndtype and g.out_degree(node) == 0: + if ndtype == "VectorData" or not ndtype and g.out_degree(node) == 0: remove_nodes.append(node) g.remove_nodes_from(remove_nodes) diff --git a/nwb_linkml/tests/conftest.py b/nwb_linkml/tests/conftest.py index 6133148..7bb3632 100644 --- a/nwb_linkml/tests/conftest.py +++ b/nwb_linkml/tests/conftest.py @@ -9,10 +9,16 @@ from .fixtures import * # noqa: F403 def pytest_addoption(parser): + parser.addoption( + "--clean", + action="store_true", + default=False, + help="Don't reuse cached resources like cloned git repos or generated files", + ) parser.addoption( "--with-output", action="store_true", - help="dump output in compliance test for richer debugging information", + help="keep test outputs for richer debugging information", ) parser.addoption( "--without-cache", action="store_true", help="Don't use a sqlite cache for network requests" diff --git a/nwb_linkml/tests/fixtures/__init__.py b/nwb_linkml/tests/fixtures/__init__.py new file mode 100644 index 0000000..e0ff5bd --- /dev/null +++ b/nwb_linkml/tests/fixtures/__init__.py @@ -0,0 +1,28 @@ +from .nwb import nwb_file +from .paths import data_dir, tmp_output_dir, tmp_output_dir_func, tmp_output_dir_mod +from .schema import ( + NWBSchemaTest, + TestSchemas, + linkml_schema, + linkml_schema_bare, + nwb_core_fixture, + nwb_core_linkml, + nwb_core_module, + nwb_schema, +) + +__all__ = [ + "NWBSchemaTest", + "TestSchemas", + "data_dir", + "linkml_schema", + "linkml_schema_bare", + "nwb_core_fixture", + "nwb_core_linkml", + "nwb_core_module", + "nwb_file", + "nwb_schema", + "tmp_output_dir", + "tmp_output_dir_func", + "tmp_output_dir_mod", +] diff --git a/nwb_linkml/tests/fixtures.py b/nwb_linkml/tests/fixtures/nwb.py similarity index 55% rename from nwb_linkml/tests/fixtures.py rename to nwb_linkml/tests/fixtures/nwb.py index 1f31a5c..6c2390f 100644 --- a/nwb_linkml/tests/fixtures.py +++ b/nwb_linkml/tests/fixtures/nwb.py @@ -1,25 +1,13 @@ -import shutil -from dataclasses import dataclass, field from datetime import datetime from itertools import product from pathlib import Path -from types import ModuleType -from typing import Dict, Optional import numpy as np import pytest -from linkml_runtime.dumpers import yaml_dumper -from linkml_runtime.linkml_model import ( - ClassDefinition, - Prefix, - SchemaDefinition, - SlotDefinition, - TypeDefinition, -) +from hdmf.common import DynamicTable, VectorData from pynwb import NWBHDF5IO, NWBFile, TimeSeries from pynwb.base import TimeSeriesReference, TimeSeriesReferenceVectorData from pynwb.behavior import Position, SpatialSeries -from pynwb.core import DynamicTable, VectorData from pynwb.ecephys import LFP, ElectricalSeries from pynwb.file import Subject from pynwb.icephys import VoltageClampSeries, VoltageClampStimulusSeries @@ -35,323 +23,9 @@ from pynwb.ophys import ( TwoPhotonSeries, ) -from nwb_linkml.adapters.namespaces import NamespacesAdapter -from nwb_linkml.io import schema as io -from nwb_linkml.providers import LinkMLProvider, PydanticProvider -from nwb_linkml.providers.linkml import LinkMLSchemaBuild -from nwb_schema_language import Attribute, Dataset, Group - -__all__ = [ - "NWBSchemaTest", - "TestSchemas", - "data_dir", - "linkml_schema", - "linkml_schema_bare", - "nwb_core_fixture", - "nwb_file", - "nwb_schema", - "tmp_output_dir", - "tmp_output_dir_func", - "tmp_output_dir_mod", -] - @pytest.fixture(scope="session") -def tmp_output_dir() -> Path: - path = Path(__file__).parent.resolve() / "__tmp__" - if path.exists(): - for subdir in path.iterdir(): - 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 - - -@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 - """ - subpath = tmp_output_dir / "__tmpfunc__" - if subpath.exists(): - shutil.rmtree(str(subpath)) - subpath.mkdir() - return subpath - - -@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 - """ - subpath = tmp_output_dir / "__tmpmod__" - if subpath.exists(): - shutil.rmtree(str(subpath)) - subpath.mkdir() - return subpath - - -@pytest.fixture(scope="session", params=[{"core_version": "2.7.0", "hdmf_version": "1.8.0"}]) -def nwb_core_fixture(request) -> NamespacesAdapter: - nwb_core = io.load_nwb_core(**request.param) - 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 - - -@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) - 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) - mod = provider.get("core", version=nwb_core_linkml.version, allow_repo=False) - return mod - - -@pytest.fixture(scope="session") -def data_dir() -> Path: - path = Path(__file__).parent.resolve() / "data" - return path - - -@dataclass -class TestSchemas: - __test__ = False - core: SchemaDefinition - imported: SchemaDefinition - namespace: SchemaDefinition - core_path: Optional[Path] = None - imported_path: Optional[Path] = None - namespace_path: Optional[Path] = None - - -@pytest.fixture(scope="module") -def linkml_schema_bare() -> TestSchemas: - - schema = TestSchemas( - core=SchemaDefinition( - name="core", - id="core", - version="1.0.1", - imports=["imported", "linkml:types"], - default_prefix="core", - prefixes={"linkml": Prefix("linkml", "https://w3id.org/linkml")}, - 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", - identifier=True, - ), - SlotDefinition(name="array", range="MainTopLevel__Array"), - SlotDefinition( - name="SkippableSlot", description="A slot that was meant to be skipped!" - ), - SlotDefinition( - name="inline_dict", - description=( - "This should be inlined as a dictionary despite this class having" - " an identifier" - ), - multivalued=True, - inlined=True, - inlined_as_list=False, - any_of=[{"range": "OtherClass"}, {"range": "StillAnotherClass"}], - ), - ], - ), - ClassDefinition( - name="MainTopLevel__Array", - description="Main class's array", - is_a="Arraylike", - attributes=[ - SlotDefinition(name="x", range="numeric", required=True), - SlotDefinition(name="y", range="numeric", required=True), - SlotDefinition( - name="z", - range="numeric", - required=False, - maximum_cardinality=3, - minimum_cardinality=3, - ), - SlotDefinition( - name="a", - range="numeric", - required=False, - minimum_cardinality=4, - maximum_cardinality=4, - ), - ], - ), - ClassDefinition( - name="skippable", - description="A class that lives to be skipped!", - ), - ClassDefinition( - name="OtherClass", - description="Another class yno!", - attributes=[ - SlotDefinition(name="name", range="string", required=True, identifier=True) - ], - ), - ClassDefinition( - name="StillAnotherClass", - description="And yet another!", - attributes=[ - SlotDefinition(name="name", range="string", required=True, identifier=True) - ], - ), - ], - types=[TypeDefinition(name="numeric", typeof="float")], - ), - imported=SchemaDefinition( - name="imported", - id="imported", - version="1.4.5", - default_prefix="core", - imports=["linkml:types"], - prefixes={"linkml": Prefix("linkml", "https://w3id.org/linkml")}, - classes=[ - ClassDefinition( - name="MainThing", - description="Class imported by our main thing class!", - attributes=[SlotDefinition(name="meta_slot", range="string")], - ), - ClassDefinition(name="Arraylike", abstract=True), - ], - ), - namespace=SchemaDefinition( - name="namespace", - id="namespace", - version="1.1.1", - default_prefix="namespace", - annotations=[ - {"tag": "is_namespace", "value": "True"}, - {"tag": "namespace", "value": "core"}, - ], - description="A namespace package that should import all other classes", - imports=["core", "imported"], - ), - ) - return schema - - -@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 - - test_schema_path = tmp_output_dir_mod / "test_schema" - test_schema_path.mkdir() - - core_path = test_schema_path / "core.yaml" - imported_path = test_schema_path / "imported.yaml" - namespace_path = test_schema_path / "namespace.yaml" - - schema.core_path = core_path - schema.imported_path = imported_path - schema.namespace_path = namespace_path - - 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 - - -@dataclass -class NWBSchemaTest: - datasets: Dict[str, Dataset] = field(default_factory=dict) - groups: Dict[str, Group] = field(default_factory=dict) - - -@pytest.fixture() -def nwb_schema() -> NWBSchemaTest: - """Minimal NWB schema for testing""" - image = Dataset( - neurodata_type_def="Image", - dtype="numeric", - neurodata_type_inc="NWBData", - dims=[["x", "y"], ["x", "y", "r, g, b"], ["x", "y", "r, g, b, a"]], - shape=[[None, None], [None, None, 3], [None, None, 4]], - doc="An image!", - attributes=[ - Attribute(dtype="float32", name="resolution", doc="resolution!"), - Attribute(dtype="text", name="description", doc="Description!"), - ], - ) - images = Group( - neurodata_type_def="Images", - neurodata_type_inc="NWBDataInterface", - default_name="Images", - doc="Images!", - attributes=[Attribute(dtype="text", name="description", doc="description!")], - datasets=[ - Dataset(neurodata_type_inc="Image", quantity="+", doc="images!"), - Dataset( - neurodata_type_inc="ImageReferences", - name="order_of_images", - doc="Image references!", - quantity="?", - ), - ], - ) - return NWBSchemaTest(datasets={"image": image}, groups={"images": images}) - - -@pytest.fixture(scope="session") -def nwb_file(tmp_output_dir) -> Path: - """ - NWB File created with pynwb that uses all the weird language features - - Borrowing code from pynwb docs in one humonogous fixture function - since there's not really a reason to - """ - generator = np.random.default_rng() - - nwb_path = tmp_output_dir / "test_nwb.nwb" - if nwb_path.exists(): - return nwb_path - +def nwb_file_base() -> NWBFile: nwbfile = NWBFile( session_description="All that you touch, you change.", # required identifier="1111-1111-1111-1111", # required @@ -373,7 +47,10 @@ def nwb_file(tmp_output_dir) -> Path: sex="M", ) nwbfile.subject = subject + return nwbfile + +def _nwb_timeseries(nwbfile: NWBFile) -> NWBFile: data = np.arange(100, 200, 10) timestamps = np.arange(10.0) time_series_with_timestamps = TimeSeries( @@ -384,7 +61,10 @@ def nwb_file(tmp_output_dir) -> Path: timestamps=timestamps, ) nwbfile.add_acquisition(time_series_with_timestamps) + return nwbfile + +def _nwb_position(nwbfile: NWBFile) -> NWBFile: position_data = np.array([np.linspace(0, 10, 50), np.linspace(0, 8, 50)]).T position_timestamps = np.linspace(0, 50).astype(float) / 200 @@ -408,11 +88,15 @@ def nwb_file(tmp_output_dir) -> Path: ) nwbfile.add_trial(start_time=1.0, stop_time=5.0, correct=True) nwbfile.add_trial(start_time=6.0, stop_time=10.0, correct=False) + return nwbfile - # -------------------------------------------------- - # Extracellular Ephys - # https://pynwb.readthedocs.io/en/latest/tutorials/domain/ecephys.html - # -------------------------------------------------- + +def _nwb_ecephys(nwbfile: NWBFile) -> NWBFile: + """ + Extracellular Ephys + https://pynwb.readthedocs.io/en/latest/tutorials/domain/ecephys.html + """ + generator = np.random.default_rng() device = nwbfile.create_device(name="array", description="old reliable", manufacturer="diy") nwbfile.add_electrode_column(name="label", description="label of electrode") @@ -455,6 +139,7 @@ def nwb_file(tmp_output_dir) -> Path: # -------------------------------------------------- # LFP # -------------------------------------------------- + generator = np.random.default_rng() lfp_data = generator.standard_normal((50, 12)) lfp_electrical_series = ElectricalSeries( name="ElectricalSeries", @@ -470,6 +155,11 @@ def nwb_file(tmp_output_dir) -> Path: ) ecephys_module.add(lfp) + return nwbfile + + +def _nwb_units(nwbfile: NWBFile) -> NWBFile: + generator = np.random.default_rng() # Spike Times nwbfile.add_unit_column(name="quality", description="sorting quality") firing_rate = 20 @@ -479,10 +169,10 @@ def nwb_file(tmp_output_dir) -> Path: for _ in range(n_units): spike_times = np.where(generator.random(res * duration) < (firing_rate / res))[0] / res nwbfile.add_unit(spike_times=spike_times, quality="good") + return nwbfile - # -------------------------------------------------- - # Intracellular ephys - # -------------------------------------------------- + +def _nwb_icephys(nwbfile: NWBFile) -> NWBFile: device = nwbfile.create_device(name="Heka ITC-1600") electrode = nwbfile.create_icephys_electrode( name="elec0", description="a mock intracellular electrode", device=device @@ -602,11 +292,15 @@ def nwb_file(tmp_output_dir) -> Path: data=np.arange(1), description="integer tag for a experimental condition", ) + return nwbfile - # -------------------------------------------------- - # Calcium Imaging - # https://pynwb.readthedocs.io/en/latest/tutorials/domain/ophys.html - # -------------------------------------------------- + +def _nwb_ca_imaging(nwbfile: NWBFile) -> NWBFile: + """ + Calcium Imaging + https://pynwb.readthedocs.io/en/latest/tutorials/domain/ophys.html + """ + generator = np.random.default_rng() device = nwbfile.create_device( name="Microscope", description="My two-photon microscope", @@ -755,6 +449,27 @@ def nwb_file(tmp_output_dir) -> Path: ) fl = Fluorescence(roi_response_series=roi_resp_series) ophys_module.add(fl) + return nwbfile + + +@pytest.fixture(scope="session") +def nwb_file(tmp_output_dir, nwb_file_base, request: pytest.FixtureRequest) -> Path: + """ + NWB File created with pynwb that uses all the weird language features + + Borrowing code from pynwb docs in one humonogous fixture function + since there's not really a reason to + """ + nwb_path = tmp_output_dir / "test_nwb.nwb" + if nwb_path.exists() and not request.config.getoption('--clean'): + return nwb_path + + nwbfile = nwb_file_base + nwbfile = _nwb_timeseries(nwbfile) + nwbfile = _nwb_position(nwbfile) + nwbfile = _nwb_ecephys(nwbfile) + nwbfile = _nwb_units(nwbfile) + nwbfile = _nwb_icephys(nwbfile) with NWBHDF5IO(nwb_path, "w") as io: io.write(nwbfile) diff --git a/nwb_linkml/tests/fixtures/paths.py b/nwb_linkml/tests/fixtures/paths.py new file mode 100644 index 0000000..5a8958b --- /dev/null +++ b/nwb_linkml/tests/fixtures/paths.py @@ -0,0 +1,58 @@ +import shutil +from pathlib import Path + +import pytest + + +@pytest.fixture(scope="session") +def tmp_output_dir(request: pytest.FixtureRequest) -> Path: + path = Path(__file__).parent.resolve() / "__tmp__" + if path.exists(): + if request.config.getoption('--clean'): + shutil.rmtree(path) + else: + for subdir in path.iterdir(): + 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 + + +@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 + """ + subpath = tmp_output_dir / "__tmpfunc__" + if subpath.exists(): + shutil.rmtree(str(subpath)) + subpath.mkdir() + return subpath + + +@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 + """ + subpath = tmp_output_dir / "__tmpmod__" + if subpath.exists(): + shutil.rmtree(str(subpath)) + subpath.mkdir() + return subpath + + +@pytest.fixture(scope="session") +def data_dir() -> Path: + path = Path(__file__).parent.resolve() / "data" + return path diff --git a/nwb_linkml/tests/fixtures/schema.py b/nwb_linkml/tests/fixtures/schema.py new file mode 100644 index 0000000..788d12b --- /dev/null +++ b/nwb_linkml/tests/fixtures/schema.py @@ -0,0 +1,251 @@ +from dataclasses import dataclass, field +from pathlib import Path +from types import ModuleType +from typing import Dict, Optional + +import pytest +from linkml_runtime.dumpers import yaml_dumper +from linkml_runtime.linkml_model import ( + ClassDefinition, + Prefix, + SchemaDefinition, + SlotDefinition, + TypeDefinition, +) + +from nwb_linkml.adapters import NamespacesAdapter +from nwb_linkml.io import schema as io +from nwb_linkml.providers import LinkMLProvider, PydanticProvider +from nwb_linkml.providers.linkml import LinkMLSchemaBuild +from nwb_schema_language import Attribute, Dataset, Group + + +@pytest.fixture(scope="session", params=[{"core_version": "2.7.0", "hdmf_version": "1.8.0"}]) +def nwb_core_fixture(request) -> NamespacesAdapter: + nwb_core = io.load_nwb_core(**request.param) + 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 + + +@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) + 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) + mod = provider.get("core", version=nwb_core_linkml.version, allow_repo=False) + return mod + + +@dataclass +class TestSchemas: + __test__ = False + core: SchemaDefinition + imported: SchemaDefinition + namespace: SchemaDefinition + core_path: Optional[Path] = None + imported_path: Optional[Path] = None + namespace_path: Optional[Path] = None + + +@pytest.fixture(scope="module") +def linkml_schema_bare() -> TestSchemas: + + schema = TestSchemas( + core=SchemaDefinition( + name="core", + id="core", + version="1.0.1", + imports=["imported", "linkml:types"], + default_prefix="core", + prefixes={"linkml": Prefix("linkml", "https://w3id.org/linkml")}, + 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", + identifier=True, + ), + SlotDefinition(name="array", range="MainTopLevel__Array"), + SlotDefinition( + name="SkippableSlot", description="A slot that was meant to be skipped!" + ), + SlotDefinition( + name="inline_dict", + description=( + "This should be inlined as a dictionary despite this class having" + " an identifier" + ), + multivalued=True, + inlined=True, + inlined_as_list=False, + any_of=[{"range": "OtherClass"}, {"range": "StillAnotherClass"}], + ), + ], + ), + ClassDefinition( + name="MainTopLevel__Array", + description="Main class's array", + is_a="Arraylike", + attributes=[ + SlotDefinition(name="x", range="numeric", required=True), + SlotDefinition(name="y", range="numeric", required=True), + SlotDefinition( + name="z", + range="numeric", + required=False, + maximum_cardinality=3, + minimum_cardinality=3, + ), + SlotDefinition( + name="a", + range="numeric", + required=False, + minimum_cardinality=4, + maximum_cardinality=4, + ), + ], + ), + ClassDefinition( + name="skippable", + description="A class that lives to be skipped!", + ), + ClassDefinition( + name="OtherClass", + description="Another class yno!", + attributes=[ + SlotDefinition(name="name", range="string", required=True, identifier=True) + ], + ), + ClassDefinition( + name="StillAnotherClass", + description="And yet another!", + attributes=[ + SlotDefinition(name="name", range="string", required=True, identifier=True) + ], + ), + ], + types=[TypeDefinition(name="numeric", typeof="float")], + ), + imported=SchemaDefinition( + name="imported", + id="imported", + version="1.4.5", + default_prefix="core", + imports=["linkml:types"], + prefixes={"linkml": Prefix("linkml", "https://w3id.org/linkml")}, + classes=[ + ClassDefinition( + name="MainThing", + description="Class imported by our main thing class!", + attributes=[SlotDefinition(name="meta_slot", range="string")], + ), + ClassDefinition(name="Arraylike", abstract=True), + ], + ), + namespace=SchemaDefinition( + name="namespace", + id="namespace", + version="1.1.1", + default_prefix="namespace", + annotations=[ + {"tag": "is_namespace", "value": "True"}, + {"tag": "namespace", "value": "core"}, + ], + description="A namespace package that should import all other classes", + imports=["core", "imported"], + ), + ) + return schema + + +@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 + + test_schema_path = tmp_output_dir_mod / "test_schema" + test_schema_path.mkdir() + + core_path = test_schema_path / "core.yaml" + imported_path = test_schema_path / "imported.yaml" + namespace_path = test_schema_path / "namespace.yaml" + + schema.core_path = core_path + schema.imported_path = imported_path + schema.namespace_path = namespace_path + + 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 + + +@dataclass +class NWBSchemaTest: + datasets: Dict[str, Dataset] = field(default_factory=dict) + groups: Dict[str, Group] = field(default_factory=dict) + + +@pytest.fixture() +def nwb_schema() -> NWBSchemaTest: + """Minimal NWB schema for testing""" + image = Dataset( + neurodata_type_def="Image", + dtype="numeric", + neurodata_type_inc="NWBData", + dims=[["x", "y"], ["x", "y", "r, g, b"], ["x", "y", "r, g, b, a"]], + shape=[[None, None], [None, None, 3], [None, None, 4]], + doc="An image!", + attributes=[ + Attribute(dtype="float32", name="resolution", doc="resolution!"), + Attribute(dtype="text", name="description", doc="Description!"), + ], + ) + images = Group( + neurodata_type_def="Images", + neurodata_type_inc="NWBDataInterface", + default_name="Images", + doc="Images!", + attributes=[Attribute(dtype="text", name="description", doc="description!")], + datasets=[ + Dataset(neurodata_type_inc="Image", quantity="+", doc="images!"), + Dataset( + neurodata_type_inc="ImageReferences", + name="order_of_images", + doc="Image references!", + quantity="?", + ), + ], + ) + return NWBSchemaTest(datasets={"image": image}, groups={"images": images}) diff --git a/nwb_linkml/tests/test_includes/test_hdmf.py b/nwb_linkml/tests/test_includes/test_hdmf.py index 920e8b4..d50258c 100644 --- a/nwb_linkml/tests/test_includes/test_hdmf.py +++ b/nwb_linkml/tests/test_includes/test_hdmf.py @@ -344,7 +344,7 @@ def test_vectordata_indexing(): """ n_rows = 50 value_array, index_array = _ragged_array(n_rows) - value_array = np.concat(value_array) + value_array = np.concatenate(value_array) data = hdmf.VectorData(value=value_array) @@ -592,7 +592,7 @@ def test_mixed_aligned_dynamictable(aligned_table): AlignedTable, cols = aligned_table value_array, index_array = _ragged_array(10) - value_array = np.concat(value_array) + value_array = np.concatenate(value_array) data = hdmf.VectorData(value=value_array) index = hdmf.VectorIndex(value=index_array) diff --git a/nwb_linkml/tests/test_io/test_io_hdf5.py b/nwb_linkml/tests/test_io/test_io_hdf5.py index 82e2a36..2d587e5 100644 --- a/nwb_linkml/tests/test_io/test_io_hdf5.py +++ b/nwb_linkml/tests/test_io/test_io_hdf5.py @@ -4,7 +4,7 @@ import h5py import numpy as np import pytest -from nwb_linkml.io.hdf5 import HDF5IO, truncate_file, hdf_dependency_graph, filter_dependency_graph +from nwb_linkml.io.hdf5 import HDF5IO, filter_dependency_graph, hdf_dependency_graph, truncate_file @pytest.mark.skip() diff --git a/nwb_models/src/nwb_models/models/pydantic/__init__.py b/nwb_models/src/nwb_models/models/pydantic/__init__.py index fa3cf1e..e69de29 100644 --- a/nwb_models/src/nwb_models/models/pydantic/__init__.py +++ b/nwb_models/src/nwb_models/models/pydantic/__init__.py @@ -1 +0,0 @@ -from .pydantic.core.v2_7_0.namespace import *