Quickstart

Reading Data

To get the fun part out of the way, you can read ur data:

from pathlib import Path
from rich.pretty import pprint, pretty_repr
from rich.console import Console
from nwb_linkml.io import HDF5IO

# set up for pprinting in notebooks
console = Console(width=100)
print = console.print

# find sample data file and read
nwb_file = Path('../../nwb_linkml/tests/data/aibs.nwb')
data = HDF5IO(nwb_file).read()
print(data) 
Hide code cell output


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[1], line 12
     10 # find sample data file and read
     11 nwb_file = Path('../../nwb_linkml/tests/data/aibs.nwb')
---> 12 data = HDF5IO(nwb_file).read()
     13 print(data) 

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/io/hdf5.py:106, in HDF5IO.read(self, path)
     66 def read(self, path: Optional[str] = None) -> Union["NWBFile", BaseModel, Dict[str, BaseModel]]:
     67     """
     68     Read data into models from an NWB File.
     69 
   (...)
    103         otherwise whatever Model or dictionary of models applies to the requested ``path``
    104     """
--> 106     provider = self.make_provider()
    108     h5f = h5py.File(str(self.path))
    109     src = h5f.get(path) if path else h5f

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/io/hdf5.py:185, in HDF5IO.make_provider(self)
    182 provider = SchemaProvider(versions=versions)
    184 # build schema so we have them cached
--> 185 provider.build_from_dicts(schema)
    186 h5f.close()
    187 return provider

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/providers/linkml.py:132, in LinkMLProvider.build_from_dicts(self, schemas, **kwargs)
    130 res = {}
    131 for adapter in ns_adapters.values():
--> 132     res.update(self.build(adapter, **kwargs))
    134 return res

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/providers/schema.py:128, in SchemaProvider.build(self, ns_adapter, verbose, linkml_kwargs, pydantic_kwargs, **kwargs)
    125 linkml_provider = LinkMLProvider(path=self.path, verbose=verbose)
    126 pydantic_provider = PydanticProvider(path=self.path, verbose=verbose)
--> 128 linkml_res = linkml_provider.build(
    129     ns_adapter=ns_adapter, versions=self.versions, **linkml_kwargs
    130 )
    131 results = {}
    132 for ns, ns_result in linkml_res.items():

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/providers/linkml.py:181, in LinkMLProvider.build(self, ns_adapter, versions, dump, force)
    179     progress = AdapterProgress(ns_adapter)
    180     with progress:
--> 181         built = ns_adapter.build(progress=progress)
    182 else:
    183     built = ns_adapter.build()

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/namespaces.py:82, in NamespacesAdapter.build(self, skip_imports, progress)
     79     with contextlib.suppress(KeyError):
     80         # happens when we skip builds due to caching
     81         progress.update(sch.namespace, action=sch.name)
---> 82 sch_result += sch.build()
     83 if progress is not None:
     84     with contextlib.suppress(KeyError):
     85         # happens when we skip builds due to caching

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/schema.py:83, in SchemaAdapter.build(self)
     81     res += new_res
     82 for group in self.groups:
---> 83     new_res = GroupAdapter(cls=group).build()
     84     if len(new_res.slots) > 0:
     85         pdb.set_trace()

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/group.py:60, in GroupAdapter.build(self)
     52 if (
     53     len(self.cls.groups) == 0
     54     and len(self.cls.datasets) == 0
     55     and self.cls.neurodata_type_inc is not None
     56     and self.parent is not None
     57 ):
     58     return self.handle_container_slot(self.cls)
---> 60 nested_res = self.build_subclasses()
     61 # we don't propagate slots up to the next level since they are meant for this
     62 # level (ie. a way to refer to our children)
     63 res = self.build_base(extra_attrs=nested_res.slots)

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/group.py:162, in GroupAdapter.build_subclasses(self)
    158 for dset in self.cls.datasets:
    159     # if dset.name == 'timestamps':
    160     #     pdb.set_trace()
    161     dset_adapter = DatasetAdapter(cls=dset, parent=self)
--> 162     dataset_res += dset_adapter.build()
    164 # Actually i'm not sure we have to special case this, we could handle it in
    165 # i/o instead
    166 
   (...)
    170 # and quantity of *,
    171 # the group can then contain any number of groups of those included types as direct children
    173 group_res = BuildResult()

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/dataset.py:802, in DatasetAdapter.build(self)
    800 # apply matching maps
    801 if map is not None:
--> 802     res = map.apply(self.cls, res, self._get_full_name())
    804 return res

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/dataset.py:533, in MapArrayLikeAttributes.apply(c, cls, res, name)
    529 """
    530 Map to an arraylike class
    531 """
    532 array_adapter = ArrayAdapter(cls.dims, cls.shape)
--> 533 expressions = array_adapter.make_slot()
    534 # make a slot for the arraylike class
    535 array_slot = SlotDefinition(
    536     name="array", range=ClassAdapter.handle_dtype(cls.dtype), **expressions
    537 )

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/array.py:113, in ArrayAdapter.make_slot(self)
    103 def make_slot(
    104     self,
    105 ) -> Union[
    106     Dict[Literal["array"], ArrayExpression],
    107     Dict[Literal["any_of"], Dict[Literal["array"], List[ArrayExpression]]],
    108 ]:
    109     """
    110     Make the array expressions in a dict form that can be ``**kwarg``'d into a SlotDefinition,
    111     taking into account needing to use ``any_of`` for multiple array range specifications.
    112     """
--> 113     expressions = self.make()
    114     if len(expressions) == 1:
    115         return {"array": expressions[0]}

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/array.py:100, in ArrayAdapter.make(self)
     98 """Create an array specification from self.dims and self.shape"""
     99 shapes = self.pivot_dims()
--> 100 expressions = [self.make_expression(shape) for shape in shapes]
    101 return expressions

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/array.py:100, in <listcomp>(.0)
     98 """Create an array specification from self.dims and self.shape"""
     99 shapes = self.pivot_dims()
--> 100 expressions = [self.make_expression(shape) for shape in shapes]
    101 return expressions

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/array.py:91, in ArrayAdapter.make_expression(self, shape)
     87 def make_expression(self, shape: Shape) -> ArrayExpression:
     88     """
     89     Create the corresponding array specification from a shape
     90     """
---> 91     dims = [
     92         DimensionExpression(alias=snake_case(dim.dims), exact_cardinality=dim.shape)
     93         for dim in shape
     94     ]
     95     return ArrayExpression(dimensions=dims)

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/adapters/array.py:92, in <listcomp>(.0)
     87 def make_expression(self, shape: Shape) -> ArrayExpression:
     88     """
     89     Create the corresponding array specification from a shape
     90     """
     91     dims = [
---> 92         DimensionExpression(alias=snake_case(dim.dims), exact_cardinality=dim.shape)
     93         for dim in shape
     94     ]
     95     return ArrayExpression(dimensions=dims)

File ~/checkouts/readthedocs.org/user_builds/nwb-linkml/envs/linkml-arrays/lib/python3.11/site-packages/nwb_linkml/maps/naming.py:27, in snake_case(name)
     24 if name is None:
     25     return None
---> 27 name = name.strip()
     28 name = re.sub(r"\W+", "_", name)
     29 name = name.lower()

AttributeError: 'int' object has no attribute 'strip'

Load and manipulate NWB schemas

A git provider module can manage a repository to provide a given NWB namespace at a given version, and cast the schema into Pydantic models from nwb_schema_language.

For the nwb-core schema, loading first just the namespaces file (without adjoining schema):

from nwb_linkml.providers.git import NWB_CORE_REPO
from nwb_linkml.io.schema import load_namespaces

namespace_file: 'Path' = NWB_CORE_REPO.provide_from_git('2.6.0')
core_namespaces = load_namespaces(namespace_file)
print(core_namespaces)

Or for a schema file…

from nwb_linkml.io.schema import load_schema_file

base_schema_file =  namespace_file.parent / 'nwb.base.yaml'
nwb_core_base = load_schema_file(base_schema_file)
print(nwb_core_base)

And additional adapters are used to handle some of the implicit behavior in nwb schema files, like importing other namespaces at a specific version, and inter-schema class imports. Eg. the NamespacesAdapter finds the implicitly imported hdmf-common namespace (again provided by the git schema provider).

from nwb_linkml.adapters import NamespacesAdapter

core_ns = NamespacesAdapter.from_yaml(namespace_file)
print(core_ns.imported) 

The classes in nwb_schema_language are just pydantic models, so they can be used like any other to create new, validated schemas.

Translating to LinkML

adapters handle the conversion from NWB schema language to LinkML.

core_linkml = core_ns.build()
print(core_linkml)

The BuildResult class holds the LinkML representation of each of the schemas and their classes, which are now in linkml_runtime.linkml_model.SchemaDefinition and ClassDefinition classes:

print(core_linkml.schemas[0])

Generating Pydantic Models

Todo

Document Pydantic model generation

Caching Output with Providers

Todo

Document provider usage