Adapters

Adapters translate NWB Schema Language to LinkML Schema.

  • Adapter - Base Adapter Classes

  • Namespaces - Top-level container of NWB namespace indices and schema

  • Schema - Individual NWB Schema files within a namespace

  • Classes - Root methods shared between classes and groups

Adapter classes for translating from NWB schema language to LinkML

class Adapter

Abstract base class for adapters

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

abstract build() BuildResult

Generate the corresponding linkML element for this adapter

get(name: str) Group | Dataset

Get the first item whose neurodata_type_def matches name

Convenience wrapper around walk_field_values()

get_model_with_field(field: str) Generator[Group | Dataset, None, None]

Yield models that have a non-None value in the given field.

Useful during development to find all the ways that a given field is used.

Parameters:

field (str) – Field to search for

walk(input: BaseModel | dict | list) Generator[BaseModel | Any | None, None, None]

Iterate through all items in the given model.

Could be a staticmethod or a function, but bound to adapters to make it available to them :)

walk_fields(input: BaseModel | dict | list, field: str | Tuple[str, ...]) Generator[Any, None, None]

Recursively walk input for fields that match field

Parameters:

Returns:

model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_fields: ClassVar[dict[str, FieldInfo]] = {}

Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

This replaces Model.__fields__ from Pydantic V1.

walk_field_values(input: BaseModel | dict | list, field: str, value: Any | None = None) Generator[BaseModel, None, None]

Recursively walk input for models that contain a field as a direct child with a value matching value

Parameters:
  • input (pydantic.BaseModel) – Model to walk

  • field (str) – Name of field - unlike walk_fields(), only one field can be given

  • value (Any) – Value to match for given field. If None , return models that have the field

Returns:

pydantic.BaseModel the matching model

walk_types(input: BaseModel | dict | list, get_type: Type[T] | Tuple[Type[T], Type[Unpack[Ts]]]) Generator[T | Ts, None, None]

Walk a model, yielding items that are the same type as the given type

Parameters:
class ArrayAdapter(dims: List[str | None] | List[List[str | None]], shape: List[str | None] | List[List[str | None]])

Adapter that generates a ArrayExpression (or set of them) from a NWB dims/shape declaration

pivot_dims(dims: List[str | None] | List[List[str | None]] | None = None, shape: List[str | None] | List[List[str | None]] | None = None) List[Shape]

Pivot from a list of dims and a list of shape to a list of (dim, shape) tuples

make_expression(shape: Shape) ArrayExpression

Create the corresponding array specification from a shape

make() List[ArrayExpression]

Create an array specification from self.dims and self.shape

make_slot() Dict[Literal['array'], ArrayExpression] | Dict[Literal['any_of'], Dict[Literal['array'], List[ArrayExpression]]]

Make the array expressions in a dict form that can be **kwarg’d into a SlotDefinition, taking into account needing to use any_of for multiple array range specifications.

class BuildResult(schemas: ~typing.List[~linkml_runtime.linkml_model.meta.SchemaDefinition] = <factory>, classes: ~typing.List[~linkml_runtime.linkml_model.meta.ClassDefinition] = <factory>, slots: ~typing.List[~linkml_runtime.linkml_model.meta.SlotDefinition] = <factory>, types: ~typing.List[~linkml_runtime.linkml_model.meta.TypeDefinition] = <factory>, **_kwargs)

Container class for propagating nested build results back up to caller

schemas: List[SchemaDefinition]
classes: List[ClassDefinition]
slots: List[SlotDefinition]
types: List[TypeDefinition]
as_linkml() str

Print build results as linkml-style YAML.

Note that only non-schema results will be included, as a schema usually contains all the other types.

class ClassAdapter(*, TYPE: T, cls: TI, parent: ClassAdapter | None = None)

Abstract adapter to class-like things in linkml, holds methods common to both DatasetAdapter and GroupAdapter

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

TYPE: T

The type that this adapter class handles

cls: TI
parent: ClassAdapter | None
classmethod cast_from_string(value: str | TI) TI

Cast from YAML string to desired class

abstract build() BuildResult

Make this abstract so it can’t be instantiated directly.

Subclasses call build_base() to get the basics true of both groups and datasets

build_base(extra_attrs: List[SlotDefinition] | None = None) BuildResult

Build the basic class and attributes before adding any specific modifications for groups or datasets.

The main distinction in behavior for this method is whether this class has a parent class - ie this is one of the anonymous nested child datasets or groups within another group.

If the class has no parent, then…

  • Its name is inferred from its neurodata_type_def, fixed name, or neurodata_type_inc in that order

  • It is just built as normal class!

  • It will be indicated as a tree_root (which will primarily be used to invert the translation for write operations)

If the class has a parent, then…

  • If it has a neurodata_type_def or inc, that will be used as its name, otherwise concatenate parent__child, eg. TimeSeries__TimeSeriesData

  • A slot will also be made and returned with the BuildResult, which the parent will then have as one of its attributes.

build_attrs(cls: Dataset | Group) List[SlotDefinition]

Pack the class attributes into a list of SlotDefinitions

Parameters:

cls – (Dataset | Group): Class to pack

Returns:

list[SlotDefinition]

classmethod handle_dtype(dtype: List[CompoundDtype] | FlatDtype | ReferenceDtype | None) str

Get the string form of a dtype

Parameters:

dtype (DTypeType) – Dtype to stringify

Returns:

str

build_name_slot() SlotDefinition

If a class has a name, then that name should be a slot with a fixed value.

If a class does not have a name, then name should be a required attribute

References

https://github.com/NeurodataWithoutBorders/nwb-schema/issues/552#issuecomment-1700319001

Returns:

build_self_slot() SlotDefinition

If we are a child class, we make a slot so our parent can refer to us

model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_fields: ClassVar[dict[str, FieldInfo]] = {'TYPE': FieldInfo(annotation=~T, required=True), 'cls': FieldInfo(annotation=~TI, required=True), 'parent': FieldInfo(annotation=Union[ClassAdapter, NoneType], required=False, default=None)}

Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

This replaces Model.__fields__ from Pydantic V1.

class DatasetAdapter(*, cls: Dataset, parent: ClassAdapter | None = None)

Orchestrator class for datasets - calls the set of applicable mapping classes

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

TYPE

alias of Dataset

cls: Dataset
build() BuildResult

Build the base result, and then apply the applicable mappings.

match() Type[DatasetMap] | None

Find the map class that applies to this class

Returns:

DatasetMap

Raises:

RuntimeError - if more than one map matches

model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_fields: ClassVar[dict[str, FieldInfo]] = {'cls': FieldInfo(annotation=Dataset, required=True), 'parent': FieldInfo(annotation=Union[ClassAdapter, NoneType], required=False, default=None)}

Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

This replaces Model.__fields__ from Pydantic V1.

parent: 'ClassAdapter' | None
class GroupAdapter(*, TYPE: ~typing.Type = <class 'nwb_schema_language.datamodel.nwb_schema_pydantic.Group'>, cls: ~nwb_schema_language.datamodel.nwb_schema_pydantic.Group, parent: ~nwb_linkml.adapters.classes.ClassAdapter | None = None)

Adapt NWB Groups to LinkML Classes

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

TYPE: Type

The type that this adapter class handles

cls: Group
build() BuildResult

Do the translation, yielding the BuildResult

handle_container_group(cls: Group) BuildResult

Make a special LinkML children slot that can have any number of the objects that are of neurodata_type_inc class

Examples

- name: templates
  groups:
  - neurodata_type_inc: TimeSeries
    doc: TimeSeries objects containing template data of presented stimuli.
    quantity: '*'
  - neurodata_type_inc: Images
    doc: Images objects containing images of presented stimuli.
    quantity: '*'
Parameters:

children (List[Group]) – Child groups

handle_container_slot(cls: Group) BuildResult

Handle subgroups that contain arbitrarily numbered classes,

eg. each of the groups in

Examples

  • name: trials neurodata_type_inc: TimeIntervals doc: Repeated experimental events that have a logical grouping. quantity: ‘?’

  • name: invalid_times neurodata_type_inc: TimeIntervals doc: Time intervals that should be removed from analysis. quantity: ‘?’

  • neurodata_type_inc: TimeIntervals doc: Optional additional table(s) for describing other experimental time intervals. quantity: ‘*’

build_subclasses() BuildResult

Build nested groups and datasets

Create ClassDefinitions for each, but then also create SlotDefinitions that will be used as attributes linking the main class to the subclasses

model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_fields: ClassVar[dict[str, FieldInfo]] = {'TYPE': FieldInfo(annotation=Type, required=False, default=<class 'nwb_schema_language.datamodel.nwb_schema_pydantic.Group'>), 'cls': FieldInfo(annotation=Group, required=True), 'parent': FieldInfo(annotation=Union[ClassAdapter, NoneType], required=False, default=None)}

Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

This replaces Model.__fields__ from Pydantic V1.

parent: 'ClassAdapter' | None
class NamespacesAdapter(*, namespaces: Namespaces, schemas: List[SchemaAdapter], imported: List[NamespacesAdapter] = None)

Translate a NWB Namespace to a LinkML Schema

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

namespaces: Namespaces
schemas: List[SchemaAdapter]
imported: List[NamespacesAdapter]
classmethod from_yaml(path: Path) NamespacesAdapter

Create a NamespacesAdapter from a nwb schema language namespaces yaml file.

Also attempts to provide imported implicitly imported schema (using the namespace key, rather than source, eg. with hdmf-common)

build(skip_imports: bool = False, progress: AdapterProgress | None = None) BuildResult

Build the NWB namespace to the LinkML Schema

find_type_source(name: str) SchemaAdapter

Given some neurodata_type_inc, find the schema that it’s defined in.

Rather than returning as soon as a match is found, check all

populate_imports() None

Populate the imports that are needed for each schema file

This function adds a string version of imported schema assuming the generated schema will live in the same directory. If the path to the imported schema needs to be adjusted, that should happen elsewhere (eg in LinkMLProvider) because we shouldn’t know about directory structure or anything like that here.

to_yaml(base_dir: Path) None

Build the schemas, saving them to yaml files according to their name

Parameters:

base_dir (Path) – Directory to save yaml files

property needed_imports: Dict[str, List[str]]

List of other, external namespaces that we need to import. Usually provided as schema with a namespace but not a source

Returns:

[‘needed_import_0’, …]}

Return type:

{‘namespace_name’

property versions: Dict[str, str]

versions for each namespace

namespace_schemas(name: str) List[str]

Get the schemas that are defined in a given namespace

schema_namespace(name: str) str | None

Inverse of namespace_schemas() - given a schema name, get the namespace it’s in

model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_fields: ClassVar[dict[str, FieldInfo]] = {'imported': FieldInfo(annotation=List[NamespacesAdapter], required=False, default_factory=list), 'namespaces': FieldInfo(annotation=Namespaces, required=True), 'schemas': FieldInfo(annotation=List[SchemaAdapter], required=True)}

Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

This replaces Model.__fields__ from Pydantic V1.

model_post_init(context: Any, /) None

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that’s what pydantic-core passes when calling it.

Parameters:
  • self – The BaseModel instance.

  • context – The context.

class SchemaAdapter(*, path: Path, groups: List[Group] = None, datasets: List[Dataset] = None, imports: List[SchemaAdapter | str] = None, namespace: str | None = None, version: str | None = None)

An individual schema file in nwb_schema_language

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

path: Path
groups: List[Group]
datasets: List[Dataset]
imports: List[SchemaAdapter | str]
namespace: str | None
version: str | None
property name: str

The namespace.schema name for a single schema

build() BuildResult

Make the LinkML representation for this schema file

Things that will be populated later - id (but need to have a placeholder to instantiate) - version

property created_classes: List[Type[Group | Dataset]]

All the group and datasets created in this schema

property needed_imports: List[str]

Classes that need to be imported from other namespaces

TODO: - Need to also check classes used in links/references

model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}

A dictionary of computed field names and their corresponding ComputedFieldInfo objects.

model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_fields: ClassVar[dict[str, FieldInfo]] = {'datasets': FieldInfo(annotation=List[Dataset], required=False, default_factory=list), 'groups': FieldInfo(annotation=List[Group], required=False, default_factory=list), 'imports': FieldInfo(annotation=List[Union[SchemaAdapter, str]], required=False, default_factory=list), 'namespace': FieldInfo(annotation=Union[str, NoneType], required=False, default=None, description='String of containing namespace. Populated by NamespacesAdapter'), 'path': FieldInfo(annotation=Path, required=True), 'version': FieldInfo(annotation=Union[str, NoneType], required=False, default=None, description='Version of schema, populated by NamespacesAdapter since individual schema files dont know their version in NWB Schema Lang')}

Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].

This replaces Model.__fields__ from Pydantic V1.

model_post_init(context: Any, /) None

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that’s what pydantic-core passes when calling it.

Parameters:
  • self – The BaseModel instance.

  • context – The context.