Schema#
Class for managing, building, and caching built schemas.
The nwb.core and hdmf-common schema are statically built and stored in this repository, but to make it feasible to use arbitrary schema, eg. those stored inside of an NWB file, we need a bit of infrastructure for generating and caching pydantic models on the fly.
Relationship to other modules:
* adapters
manage the conversion from NWB schema language to linkML.
* generators
create models like pydantic models from the linkML schema
* providers
then use adapters
and generators
to provide models from generated schema!
Providers create a set of directories with namespaces and versions, so eg. for the linkML and pydantic providers:
cache_dir
- linkml
- nwb_core
- v0_2_0
- namespace.yaml
- nwb.core.file.yaml
- ...
- v0_2_1
- namespace.yaml
- ...
- my_schema
- v0_1_0
- ...
- pydantic
- nwb_core
- v0_2_0
- namespace.py
- ...
- v0_2_1
- namespace.py
- ...
- class Provider(path: Path | None = None, allow_repo: bool = True, verbose: bool = True)#
Metaclass for different kind of providers!
- Parameters:
path (
pathlib.Path
) – Override the temporary directory configured by the environment-wideConfig
object as the base directory that the subclasses provide to.verbose (bool) – If
True
, print things like progress bars to stdout :)
- config#
Configuration for the directories used by this provider, unless overridden by
path
- Type:
Config
- allow_repo#
Allow the pathfinder to return the installed repository/package, useful to enforce building into temporary directories, decoupling finding a path during loading vs. building. Building into the repo is still possible if both namespace and version are provided (ie. the path is fully qualified) and
config
’s path is the repository path.- Type:
- cache_dir#
The main cache directory under which the other providers will store the things they provide
- Type:
- PROVIDES_CLASS: P = None#
- abstract get(*args: Any) Any #
Get a cached item.
Optionally, try any build it if it’s possible to do so
- namespace_path(namespace: str, version: str | None = None, allow_repo: bool | None = None) Path #
Get the location for a given namespace of this type.
Note that we don’t check for existence, because this method should also be used when generating schema — this is the canonical location
- Parameters:
namespace (str) – Namespace to get!
version (str) – Optional, version of namespace. If
None
, either get the most recent version built, or ifnamespace
iscore
orhdmf-common
, use the modules provided with the package. We do not use the most recent version, but the most recently generated version because it’s assumed that’s the one you want if you’re just gesturally reaching for one.allow_repo (bool) – Optional - override instance-level
allow_repo
attr
- class LinkMLSchemaBuild#
Build result from
LinkMLProvider.build()
- result: BuildResult | None#
- class LinkMLProvider(path: Path | None = None, allow_repo: bool = True, verbose: bool = True)#
Provider for conversions from nwb schema language to linkML.
By default, generate and manage a nest of temporary cache directories (as configured by
Config
) for each version of a given namespace.Like other
Provider
classes, this model is not a singleton but behaves a bit like one in that when instantiated without arguments it is stateless (except for configuration by environment-level variables). So we don’t use@classmethod
s here, but instantiating the class should remain cheap.Namespaces can be built from:
namespace .yaml files:
build_from_yaml()
dictionaries, as are usually packaged in nwb files:
build_from_dicts()
All of which feed into…
NamespacesAdapter
used throughout the rest ofnwb_linkml
-build()
After a namespace is built, it can be accessed using
LinkMLProvider.get()
, which can also be consumed by other providers, so a given namespace and version should only need to be built once.Note
At the moment there is no checking (eg. by comparing hashes) of different sources that purport to be a given version of a namespace. When ambiguous, the class prefers to build sets of namespaces together and use the most recently built ones since there is no formal system for linking versions of namespaced schemas in nwb schema language.
Examples
>>> provider = LinkMLProvider() >>> # Simplest case, get the core nwb schema from the default NWB core repo >>> core = provider.get('core') >>> # Get a specific version of the core schema >>> core_other_version = provider.get('core', '2.2.0') >>> # Build a custom schema and then get it >>> # provider.build_from_yaml('myschema.yaml') >>> # my_schema = provider.get('myschema')
- PROVIDES_CLASS#
alias of
SchemaDefinition
- build_from_yaml(path: Path, **kwargs)#
Build a namespace’s schema
- Parameters:
path (
pathlib.Path
) – Path to the namespace .yamlkwargs – passed to
build()
- build_from_dicts(schemas: Dict[str, dict], **kwargs) Dict[str | SchemaDefinitionName, LinkMLSchemaBuild] #
Build from schema dictionaries, eg. as come from nwb files
- Parameters:
schemas (dict) – A dictionary of
{'schema_name': {:schema_definition}}
. The “namespace” schema should have the keynamespace
, which is used to infer version and schema name. Post-load maps should have already been applied
- build(ns_adapter: NamespacesAdapter, versions: dict | None = None, dump: bool = True, force: bool = False) Dict[str | SchemaDefinitionName, LinkMLSchemaBuild] #
- Parameters:
namespaces (
NamespacesAdapter
) – Adapter (populated with any necessary imported namespaces) to buildversions (dict) – Dict of specific versions to use for cross-namespace imports. as
{'namespace': 'version'}
If none is provided, use the most recent version available.dump (bool) – If
True
(default), dump generated schema to YAML. otherwise just returnforce (bool) – If
False
(default), don’t build schema that already exist. IfTrue
, clear directory and rebuild
- Returns:
Dict[str, LinkMLSchemaBuild]. For normal builds,
LinkMLSchemaBuild.result
will be populated with results of the build. Ifforce == False
and the schema already exist, it will beNone
- get(namespace: str, version: str | None = None) SchemaView #
Get a schema view over the namespace.
If a matching path for the namespace and version exists in the
path
, then return the SchemaView over that namespace.Otherwise, try and find a source using our
providers.git.DEFAULT_REPOS
.If none is found, then you need to build and cache the (probably custom) schema first with
build()
- class PydanticProvider(path: Path | None = None, verbose: bool = True)#
Provider for pydantic models built from linkml-style nwb schema (ie. as provided by
LinkMLProvider
)- build(namespace: str | Path, out_file: Path | None = None, version: str | None = None, versions: dict | None = None, split: bool = True, dump: bool = True, force: bool = False, **kwargs) str | List[str] #
Notes
We currently infer namespace and version from the path when
namespace
is a Path, which is a patently Bad Thing To Do. This is a temporary measure until we decide on a permanent means by which we want to cache built artifacts <3. Hierarchies of folders is not the target design.- Parameters:
namespace (Union[str,
pathlib.Path
]) – If a string, use aLinkMLProvider
to get the converted schema. If a path, assume we have been given an explicitnamespace.yaml
from a converted NWB -> LinkML schema to load from.out_file (Optional[Path]) – Optionally override the output file. If
None
, generate from namespace and versionversion (Optional[str]) – The version of the schema to build, if present. Works similarly to
version
inLinkMLProvider
. Ignored ifnamespace
is a Path.versions (Optional[dict]) – An explicit mapping of namespaces and versions to use when building the combined pydantic namespace.py file. Since NWB doesn’t have an explicit version dependency system between schema, there is intrinsic ambiguity between which version of which schema should be used when imported from another. This mapping allows those ambiguities to be resolved. See
NWBPydanticGenerator
‘sversions
argument for more information.split (bool) – If
False
(default), generate a singlenamespace.py
file, otherwise generate a python file for each schema in the namespace in addition to anamespace.py
that imports from themdump (bool) – If
True
(default), dump the model to the cache, otherwise just return the serialized string of built pydantic modelforce (bool) – If
False
(default), don’t build the model if it already exists, ifTrue
, delete and rebuild any model**kwargs – Passed to
NWBPydanticGenerator
- Returns:
The built model file as returned from
NWBPydanticGenerator.serialize()
- Return type:
- import_module(namespace: str, version: str | None = None) module #
Import a module within the temporary directory from its namespace and version
In most cases, you’re looking for
PydanticProvider.get()
, this method is made available in case you don’t want to accidentally build something or invoke the rest of the provisioning system.- Parameters:
- Returns:
- get(namespace: str, version: str | None = None, allow_repo: bool | None = None) module #
Get the imported module for a given namespace and version.
A given namespace will be stored in
sys.modules
asnwb_linkml.models.{namespace}
, so first check if there is any already-imported module, and return that if so.Then we check in the temporary directory for an already-built
namespace.py
fileOtherwise we pass arguments to
PydanticProvider.build()
and attempt to build them before returning.Notes
The imported modules shadow the “actual”
nwb_linkml.models
module as would be imported from the usual location within the package directory. This is intentional, as models can then be used as if they were integrated parts of the package, and also so the active version of a namespace can be cleanly accessed (ie. withoutfrom nwb_linkml.models.core import v2_2_0 as core
). Accordingly, we assume that people will only be using a single version of NWB in a given Python session.- Parameters:
namespace (str) – Name of namespace to import. Must have either been previously built with
PydanticProvider.build()
or a matching namespace/version combo must be available to theLinkMLProvider
version (Optional[str]) – Version to import. If
None
, get the most recently build moduleallow_repo (bool) – Allow getting modules provided within
nwb_linkml.models.pydantic
- Returns:
The imported
types.ModuleType
object that has all the built classes at the root level.
- class EctopicModelFinder(path: Path, *args, **kwargs)#
A meta path finder that allows the import machinery to find a model package even if it might be outside the actual nwb_linkml namespace, as occurs when building split models in a temporary directory.
References
https://docs.python.org/3/reference/import.html#the-meta-path
https://docs.python.org/3/library/importlib.html#importlib.abc.MetaPathFinder
- MODEL_STEM = 'nwb_linkml.models.pydantic'#
- find_spec(fullname, path, target=None)#
- class SchemaProvider(versions: Dict[str, str] | None = None, **kwargs)#
Class to manage building and caching linkml and pydantic models generated from nwb schema language. Combines
LinkMLProvider
andPydanticProvider
Behaves like a singleton without needing to be one - since we’re working off caches on disk that are indexed by hash in most “normal” conditions you should be able to use this anywhere, though no file-level locks are present to ensure consistency.
Store each generated schema in a directory structure indexed by schema namespace name and version
- Parameters:
- build_from_yaml(path: Path, **kwargs)#
Alias for
LinkMLProvider.build_from_yaml()
that also builds a pydantic model
- build_from_dicts(schemas: Dict[str, dict], **kwargs) Dict[str | SchemaDefinitionName, LinkMLSchemaBuild] #
Alias for
LinkMLProvider.build_from_dicts()
that also builds a pydantic model
- build(ns_adapter: NamespacesAdapter, verbose: bool = True, linkml_kwargs: dict | None = None, pydantic_kwargs: dict | None = None, **kwargs) Dict[str, str] #
Build a namespace, storing its linkML and pydantic models.
- Parameters:
ns_adapter –
verbose (bool) – If
True
(default), show progress barslinkml_kwargs (Optional[dict]) – Dictionary of kwargs optionally passed to
LinkMLProvider.build()
pydantic_kwargs (Optional[dict]) – Dictionary of kwargs optionally passed to
PydanticProvider.build()
**kwargs – Common options added to both
linkml_kwargs
andpydantic_kwargs
- Returns:
Dict[str,str] mapping namespaces to built pydantic sources
- get(namespace: str, version: str | None = None) module #
Get a built pydantic model for a given namespace and version.
Wrapper around
PydanticProvider.get()