import pytest import os from typing import NamedTuple, Optional, List, Dict from dataclasses import dataclass, field from linkml_runtime.dumpers import yaml_dumper from nwb_linkml.io import schema as io from nwb_linkml.adapters.namespaces import NamespacesAdapter from nwb_schema_language import Schema, Group, Dataset, Attribute from linkml_runtime.linkml_model import SchemaDefinition, ClassDefinition, SlotDefinition, Prefix, TypeDefinition import shutil from pathlib import Path @pytest.fixture(scope="session") def tmp_output_dir() -> Path: path = Path(__file__).parent.resolve() / '__tmp__' if path.exists(): shutil.rmtree(str(path)) path.mkdir() 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(autouse=True, scope='session') def set_config_vars(tmp_output_dir): os.environ['NWB_LINKML_CACHE_DIR'] = str(tmp_output_dir) @pytest.fixture( scope="session", params=[ {'core_version': "2.6.0", 'hdmf_version': '1.5.0'} ]) def nwb_core_fixture(request) -> NamespacesAdapter: nwb_core = io.load_nwb_core(**request.param) nwb_core.populate_imports() return nwb_core @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={'namespace': {'tag': 'namespace', 'value': 'True'}}, 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], groups=[images])