diff --git a/nwb_linkml/src/nwb_linkml/adapters/adapter.py b/nwb_linkml/src/nwb_linkml/adapters/adapter.py index eaed721..f31f800 100644 --- a/nwb_linkml/src/nwb_linkml/adapters/adapter.py +++ b/nwb_linkml/src/nwb_linkml/adapters/adapter.py @@ -135,12 +135,15 @@ class Adapter(BaseModel): # do nothing, is a string or whatever pass - def walk_fields(self, input: BaseModel | list | dict, field: str | Tuple[str, ...]): + def walk_fields( + self, input: BaseModel | list | dict, field: str | Tuple[str, ...] + ) -> Generator[Any, None, None]: """ Recursively walk input for fields that match ``field`` Args: - input (:class:`pydantic.BaseModel`) : Model to walk (or a list or dictionary to walk too) + input (:class:`pydantic.BaseModel`) : Model to walk (or a list or dictionary + to walk too) field (str, Tuple[str, ...]): Returns: @@ -156,28 +159,36 @@ class Adapter(BaseModel): self, input: BaseModel | list | dict, field: str, value: Optional[Any] = None ) -> Generator[BaseModel, None, None]: """ - Recursively walk input for **models** that contain a ``field`` as a direct child with a value matching ``value`` + Recursively walk input for **models** that contain a ``field`` as a direct child + with a value matching ``value`` Args: input (:class:`pydantic.BaseModel`): Model to walk field (str): Name of field - unlike :meth:`.walk_fields`, only one field can be given - value (Any): Value to match for given field. If ``None`` , return models that have the field + value (Any): Value to match for given field. If ``None`` , + return models that have the field Returns: :class:`pydantic.BaseModel` the matching model """ for item in self.walk(input): - if isinstance(item, BaseModel): - if field in item.model_fields: - if value is None: - yield item - field_value = item.model_dump().get(field, None) - if value == field_value: - yield item + if isinstance(item, BaseModel) and field in item.model_fields: + if value is None: + yield item + field_value = item.model_dump().get(field, None) + if value == field_value: + yield item def walk_types( self, input: BaseModel | list | dict, 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 + + Args: + input (:class:`pydantic.BaseModel`, list, dict): Object to yield from + get_type (:class:`~typing.Type`, tuple[:class:`~typing.Type`]): Type to match + """ if not isinstance(get_type, (list, tuple)): get_type = [get_type] diff --git a/nwb_linkml/src/nwb_linkml/adapters/classes.py b/nwb_linkml/src/nwb_linkml/adapters/classes.py index cd40f76..5870d61 100644 --- a/nwb_linkml/src/nwb_linkml/adapters/classes.py +++ b/nwb_linkml/src/nwb_linkml/adapters/classes.py @@ -40,15 +40,19 @@ class ClassAdapter(Adapter): 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 + * 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) + * 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`, + * 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. + * A slot will also be made and returned with the BuildResult, + which the parent will then have as one of its attributes. """ # Build this class @@ -83,6 +87,15 @@ class ClassAdapter(Adapter): return res def build_attrs(self, cls: Dataset | Group) -> List[SlotDefinition]: + """ + Pack the class attributes into a list of SlotDefinitions + + Args: + cls: (:class:`.Dataset` | :class:`.Group`): Class to pack + + Returns: + list[:class:`.SlotDefinition`] + """ attrs = [ SlotDefinition( name=attr.name, @@ -153,6 +166,15 @@ class ClassAdapter(Adapter): @classmethod def handle_dtype(cls, dtype: DTypeType | None) -> str: + """ + Get the string form of a dtype + + Args: + dtype (:class:`.DTypeType`): Dtype to stringify + + Returns: + str + """ if isinstance(dtype, ReferenceDtype): return dtype.target_type elif dtype is None or dtype == []: diff --git a/nwb_linkml/src/nwb_linkml/adapters/dataset.py b/nwb_linkml/src/nwb_linkml/adapters/dataset.py index c3e2bb0..6ef6a94 100644 --- a/nwb_linkml/src/nwb_linkml/adapters/dataset.py +++ b/nwb_linkml/src/nwb_linkml/adapters/dataset.py @@ -16,10 +16,16 @@ from nwb_schema_language import Dataset class DatasetMap(Map): + """ + Abstract builder class for dataset elements + """ @classmethod @abstractmethod def check(c, cls: Dataset) -> bool: + """ + Check if this map applies + """ pass # pragma: no cover @classmethod @@ -27,6 +33,9 @@ class DatasetMap(Map): def apply( c, cls: Dataset, res: Optional[BuildResult] = None, name: Optional[str] = None ) -> BuildResult: + """ + Apply this mapping + """ pass # pragma: no cover @@ -94,22 +103,22 @@ class MapScalar(DatasetMap): - ``str`` """ - if ( + return ( cls.neurodata_type_inc != "VectorData" and not cls.neurodata_type_inc and not cls.attributes and not cls.dims and not cls.shape and cls.name - ): - return True - else: - return False + ) @classmethod def apply( c, cls: Dataset, res: Optional[BuildResult] = None, name: Optional[str] = None ) -> BuildResult: + """ + Map to a scalar value + """ this_slot = SlotDefinition( name=cls.name, description=cls.doc, @@ -147,22 +156,22 @@ class MapScalarAttributes(DatasetMap): - ``str`` """ - if ( + return ( cls.neurodata_type_inc != "VectorData" and not cls.neurodata_type_inc and cls.attributes and not cls.dims and not cls.shape and cls.name - ): - return True - else: - return False + ) @classmethod def apply( c, cls: Dataset, res: Optional[BuildResult] = None, name: Optional[str] = None ) -> BuildResult: + """ + Map to a scalar attribute with an adjoining "value" slot + """ value_slot = SlotDefinition( name="value", range=ClassAdapter.handle_dtype(cls.dtype), required=True ) @@ -177,23 +186,26 @@ class MapListlike(DatasetMap): @classmethod def check(c, cls: Dataset) -> bool: + """ + Check if we are a 1D dataset that isn't a normal datatype + """ dtype = ClassAdapter.handle_dtype(cls.dtype) - if is_1d(cls) and dtype != "AnyType" and dtype not in flat_to_linkml.keys(): - return True - else: - return False + return is_1d(cls) and dtype != "AnyType" and dtype not in flat_to_linkml @classmethod def apply( c, cls: Dataset, res: Optional[BuildResult] = None, name: Optional[str] = None ) -> BuildResult: + """ + Map to a list of the given class + """ dtype = camel_to_snake(ClassAdapter.handle_dtype(cls.dtype)) slot = SlotDefinition( name=dtype, multivalued=True, range=ClassAdapter.handle_dtype(cls.dtype), description=cls.doc, - required=False if cls.quantity in ("*", "?") else True, + required=cls.quantity not in ("*", "?"), ) res.classes[0].attributes[dtype] = slot return res @@ -209,15 +221,18 @@ class MapArraylike(DatasetMap): @classmethod def check(c, cls: Dataset) -> bool: - if cls.name and all([cls.dims, cls.shape]) and not has_attrs(cls): - return True - else: - return False + """ + Check if we're a plain array + """ + return cls.name and all([cls.dims, cls.shape]) and not has_attrs(cls) @classmethod def apply( c, cls: Dataset, res: Optional[BuildResult] = None, name: Optional[str] = None ) -> BuildResult: + """ + Map to an array class and the adjoining slot + """ array_class = make_arraylike(cls, name) name = camel_to_snake(cls.name) res = BuildResult( @@ -227,7 +242,7 @@ class MapArraylike(DatasetMap): multivalued=False, range=array_class.name, description=cls.doc, - required=False if cls.quantity in ("*", "?") else True, + required=cls.quantity not in ("*", "?"), ) ], classes=[array_class], @@ -254,22 +269,24 @@ class MapArrayLikeAttributes(DatasetMap): @classmethod def check(c, cls: Dataset) -> bool: + """ + Check that we're an array with some additional metadata + """ dtype = ClassAdapter.handle_dtype(cls.dtype) - if ( + return ( all([cls.dims, cls.shape]) and cls.neurodata_type_inc != "VectorData" and has_attrs(cls) and (dtype == "AnyType" or dtype in flat_to_linkml) - ): - return True - - else: - return False + ) @classmethod def apply( c, cls: Dataset, res: Optional[BuildResult] = None, name: Optional[str] = None ) -> BuildResult: + """ + Map to an arraylike class + """ array_class = make_arraylike(cls, name) # make a slot for the arraylike class array_slot = SlotDefinition(name="array", range=array_class.name) @@ -286,27 +303,30 @@ class MapArrayLikeAttributes(DatasetMap): class Map1DVector(DatasetMap): """ - ``VectorData`` is subclassed with a name but without dims or attributes, treat this as a normal 1D array - slot that replaces any class that would be built for this + ``VectorData`` is subclassed with a name but without dims or attributes, + treat this as a normal 1D array slot that replaces any class that would be built for this """ @classmethod def check(c, cls: Dataset) -> bool: - if ( + """ + Check that we're a 1d VectorData class + """ + return ( cls.neurodata_type_inc == "VectorData" and not cls.dims and not cls.shape and not cls.attributes and cls.name - ): - return True - else: - return False + ) @classmethod def apply( c, cls: Dataset, res: Optional[BuildResult] = None, name: Optional[str] = None ) -> BuildResult: + """ + Return a simple multivalued slot + """ this_slot = SlotDefinition( name=cls.name, description=cls.doc, @@ -328,21 +348,23 @@ class MapNVectors(DatasetMap): @classmethod def check(c, cls: Dataset) -> bool: - if ( + """ + Check for being an unnamed multivalued vector class + """ + return ( cls.name is None and cls.neurodata_type_def is None and cls.neurodata_type_inc and cls.quantity in ("*", "+") - ): - # cls.neurodata_type_inc in ('VectorIndex', 'VectorData') and \ - return True - else: - return False + ) @classmethod def apply( c, cls: Dataset, res: Optional[BuildResult] = None, name: Optional[str] = None ) -> BuildResult: + """ + Return a slot mapping to multiple values of the type + """ this_slot = SlotDefinition( name=camel_to_snake(cls.neurodata_type_inc), description=cls.doc, @@ -355,9 +377,15 @@ class MapNVectors(DatasetMap): class DatasetAdapter(ClassAdapter): + """ + Orchestrator class for datasets - calls the set of applicable mapping classes + """ cls: Dataset def build(self) -> BuildResult: + """ + Build the base result, and then apply the applicable mappings. + """ res = self.build_base() # find a map to use @@ -377,6 +405,11 @@ class DatasetAdapter(ClassAdapter): def make_arraylike(cls: Dataset, name: Optional[str] = None) -> ClassDefinition: + """ + Create a containing arraylike class + + This is likely deprecated so this docstring is a placeholder to satisfy the linter... + """ # The schema language doesn't have a way of specifying a dataset/group is "abstract" # and yet hdmf-common says you don't need a dtype if the dataset is "abstract" # so.... @@ -421,10 +454,7 @@ def make_arraylike(cls: Dataset, name: Optional[str] = None) -> ClassDefinition: required = False # use cardinality to do shape - if shape == "null": - cardinality = None - else: - cardinality = shape + cardinality = None if shape == "null" else shape slots.append( SlotDefinition( @@ -436,7 +466,8 @@ def make_arraylike(cls: Dataset, name: Optional[str] = None) -> ClassDefinition: ) ) - # and then the class is just a subclass of `Arraylist` (which is imported by default from `nwb.language.yaml`) + # and then the class is just a subclass of `Arraylist` + # (which is imported by default from `nwb.language.yaml`) if name: pass elif cls.neurodata_type_def: @@ -453,20 +484,20 @@ def make_arraylike(cls: Dataset, name: Optional[str] = None) -> ClassDefinition: def is_1d(cls: Dataset) -> bool: - if ( + """ + Check if the values of a dataset are 1-dimensional + """ + return ( not any([isinstance(dim, list) for dim in cls.dims]) and len(cls.dims) == 1 ) or ( # nested list all([isinstance(dim, list) for dim in cls.dims]) and len(cls.dims) == 1 and len(cls.dims[0]) == 1 - ): - return True - else: - return False + ) def has_attrs(cls: Dataset) -> bool: - if len(cls.attributes) > 0 and all([not a.value for a in cls.attributes]): - return True - else: - return False + """ + Check if a dataset has any attributes at all without defaults + """ + return len(cls.attributes) > 0 and all([not a.value for a in cls.attributes]) diff --git a/nwb_linkml/src/nwb_linkml/adapters/group.py b/nwb_linkml/src/nwb_linkml/adapters/group.py index 1b0a5bd..2a1c82e 100644 --- a/nwb_linkml/src/nwb_linkml/adapters/group.py +++ b/nwb_linkml/src/nwb_linkml/adapters/group.py @@ -13,9 +13,15 @@ from nwb_schema_language import Group class GroupAdapter(ClassAdapter): + """ + Adapt NWB Groups to LinkML Classes + """ cls: Group def build(self) -> BuildResult: + """ + Do the translation, yielding the BuildResult + """ # Handle container groups with only * quantity unnamed groups if len(self.cls.groups) > 0 and all( [self._check_if_container(g) for g in self.cls.groups] @@ -80,12 +86,7 @@ class GroupAdapter(ClassAdapter): # don't build subgroups as their own classes, just make a slot # that can contain them - if self.cls.name: - name = cls.name - # elif len(cls.groups) == 1: - # name = camel_to_snake(cls.groups[0].neurodata_type_inc) - else: - name = "children" + name = cls.name if self.cls.name else "children" slot = SlotDefinition( name=name, @@ -126,10 +127,7 @@ class GroupAdapter(ClassAdapter): doc: Optional additional table(s) for describing other experimental time intervals. quantity: '*' """ - if not self.cls.name: - name = camel_to_snake(self.cls.neurodata_type_inc) - else: - name = cls.name + name = camel_to_snake(self.cls.neurodata_type_inc) if not self.cls.name else cls.name return BuildResult( slots=[ @@ -163,7 +161,8 @@ class GroupAdapter(ClassAdapter): # Groups are a bit more complicated because they can also behave like # range declarations: - # eg. a group can have multiple groups with `neurodata_type_inc`, no name, and quantity of *, + # eg. a group can have multiple groups with `neurodata_type_inc`, no name, + # and quantity of *, # the group can then contain any number of groups of those included types as direct children group_res = BuildResult() @@ -191,7 +190,4 @@ class GroupAdapter(ClassAdapter): doc: Images objects containing images of presented stimuli. quantity: '*' """ - if not group.name and group.quantity in ("*", "+") and group.neurodata_type_inc: - return True - else: - return False + return not group.name and group.quantity in ("*", "+") and group.neurodata_type_inc \ No newline at end of file diff --git a/nwb_linkml/src/nwb_linkml/adapters/namespaces.py b/nwb_linkml/src/nwb_linkml/adapters/namespaces.py index e0f9c6c..47272fe 100644 --- a/nwb_linkml/src/nwb_linkml/adapters/namespaces.py +++ b/nwb_linkml/src/nwb_linkml/adapters/namespaces.py @@ -4,7 +4,7 @@ Namespaces adapter Wraps the :class:`nwb_schema_language.Namespaces` and other objects with convenience methods for extracting information and generating translated schema """ - +import contextlib from copy import copy from pathlib import Path from pprint import pformat @@ -22,14 +22,17 @@ from nwb_schema_language import Namespaces class NamespacesAdapter(Adapter): + """ + Translate a NWB Namespace to a LinkML Schema + """ namespaces: Namespaces schemas: List[SchemaAdapter] imported: List["NamespacesAdapter"] = Field(default_factory=list) _imports_populated: bool = PrivateAttr(False) - def __init__(self, **kwargs): - super(NamespacesAdapter, self).__init__(**kwargs) + def __init__(self, **kwargs: dict): + super().__init__(**kwargs) self._populate_schema_namespaces() @classmethod @@ -37,8 +40,8 @@ class NamespacesAdapter(Adapter): """ 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) + Also attempts to provide imported implicitly imported schema (using the namespace key, + rather than source, eg. with hdmf-common) """ from nwb_linkml.io import schema as schema_io from nwb_linkml.providers.git import DEFAULT_REPOS @@ -49,10 +52,10 @@ class NamespacesAdapter(Adapter): need_imports = [] for needed in ns_adapter.needed_imports.values(): - need_imports.extend([n for n in needed if n not in ns_adapter.needed_imports.keys()]) + need_imports.extend([n for n in needed if n not in ns_adapter.needed_imports]) for needed in need_imports: - if needed in DEFAULT_REPOS.keys(): + if needed in DEFAULT_REPOS: needed_source_ns = DEFAULT_REPOS[needed].provide_from_git() needed_adapter = NamespacesAdapter.from_yaml(needed_source_ns) ns_adapter.imported.append(needed_adapter) @@ -62,24 +65,23 @@ class NamespacesAdapter(Adapter): def build( self, skip_imports: bool = False, progress: Optional[AdapterProgress] = None ) -> BuildResult: + """ + Build the NWB namespace to the LinkML Schema + """ if not self._imports_populated and not skip_imports: self.populate_imports() sch_result = BuildResult() for sch in self.schemas: if progress is not None: - try: - progress.update(sch.namespace, action=sch.name) - except KeyError: # pragma: no cover + with contextlib.suppress(KeyError): # happens when we skip builds due to caching - pass + progress.update(sch.namespace, action=sch.name) sch_result += sch.build() if progress is not None: - try: - progress.update(sch.namespace, advance=1) - except KeyError: # pragma: no cover + with contextlib.suppress(KeyError): # happens when we skip builds due to caching - pass + progress.update(sch.namespace, advance=1) # recursive step if not skip_imports: @@ -125,8 +127,10 @@ class NamespacesAdapter(Adapter): return sch_result - def _populate_schema_namespaces(self): - # annotate for each schema which namespace imports it + def _populate_schema_namespaces(self) -> None: + """ + annotate for each schema which namespace imports it + """ for sch in self.schemas: # imports seem to always be from same folder, so we can just use name part sch_name = sch.path.name @@ -154,7 +158,7 @@ class NamespacesAdapter(Adapter): if len(internal_matches) > 1: raise KeyError( f"Found multiple schemas in namespace that define {name}:\ninternal:" - f" {pformat(internal_matches)}\nimported:{pformat(import_matches)}" + f" {pformat(internal_matches)}\nimported:{pformat(internal_matches)}" ) elif len(internal_matches) == 1: return internal_matches[0] @@ -176,7 +180,7 @@ class NamespacesAdapter(Adapter): else: raise KeyError(f"No schema found that define {name}") - def populate_imports(self): + def populate_imports(self) -> None: """ Populate the imports that are needed for each schema file @@ -199,7 +203,14 @@ class NamespacesAdapter(Adapter): self._imports_populated = True - def to_yaml(self, base_dir: Path): + def to_yaml(self, base_dir: Path) -> None: + """ + Build the schemas, saving them to ``yaml`` files according to + their ``name`` + + Args: + base_dir (:class:`.Path`): Directory to save ``yaml`` files + """ schemas = self.build().schemas base_dir = Path(base_dir) diff --git a/nwb_linkml/src/nwb_linkml/adapters/schema.py b/nwb_linkml/src/nwb_linkml/adapters/schema.py index 70f033f..3138ec0 100644 --- a/nwb_linkml/src/nwb_linkml/adapters/schema.py +++ b/nwb_linkml/src/nwb_linkml/adapters/schema.py @@ -4,7 +4,7 @@ to call them "schema" objects """ from pathlib import Path -from typing import List, NamedTuple, Optional, Type +from typing import List, Optional, Type from linkml_runtime.linkml_model import SchemaDefinition from pydantic import Field, PrivateAttr @@ -15,11 +15,6 @@ from nwb_linkml.adapters.group import GroupAdapter from nwb_schema_language import Dataset, Group -class SplitSchema(NamedTuple): - main: BuildResult - split: Optional[BuildResult] - - class SchemaAdapter(Adapter): """ An individual schema file in nwb_schema_language @@ -43,6 +38,9 @@ class SchemaAdapter(Adapter): @property def name(self) -> str: + """ + The namespace.schema name for a single schema + """ return ".".join([self.namespace, self.path.with_suffix("").name]) def __repr__(self): @@ -82,7 +80,7 @@ class SchemaAdapter(Adapter): if ( len(res.slots) > 0 - ): # pragma: no cover - hard to induce this error because the child classes don't fuck up like this + ): # pragma: no cover - hard to induce this because child classes don't fuck up like this raise RuntimeError( "Generated schema in this translation can only have classes, all slots should be" " attributes within a class" @@ -107,6 +105,9 @@ class SchemaAdapter(Adapter): @property def created_classes(self) -> List[Type[Group | Dataset]]: + """ + All the group and datasets created in this schema + """ if len(self._created_classes) == 0: self._created_classes = [ t diff --git a/nwb_linkml/src/nwb_linkml/annotations.py b/nwb_linkml/src/nwb_linkml/annotations.py index 422caae..6a0ef3d 100644 --- a/nwb_linkml/src/nwb_linkml/annotations.py +++ b/nwb_linkml/src/nwb_linkml/annotations.py @@ -3,10 +3,15 @@ Utility functions for introspection on python annotations """ import typing -from typing import Any, List +from typing import Any, List, Optional, Type, TypeVar + +T = TypeVar('T') -def unwrap_optional(annotation): +def unwrap_optional(annotation: Type[Optional[T]]) -> Type[T]: + """ + Get the inner type of an `Optional[T]` type + """ if typing.get_origin(annotation) == typing.Union: args = typing.get_args(annotation) @@ -15,7 +20,10 @@ def unwrap_optional(annotation): return annotation -def get_inner_types(annotation) -> List[Any]: +def get_inner_types(annotation: Type) -> List[Any]: + """ + Get the inner types in some nested type, recursively + """ types = [] args = typing.get_args(annotation) for arg in args: diff --git a/nwb_linkml/src/nwb_linkml/config.py b/nwb_linkml/src/nwb_linkml/config.py index c28c786..8fa84f7 100644 --- a/nwb_linkml/src/nwb_linkml/config.py +++ b/nwb_linkml/src/nwb_linkml/config.py @@ -54,6 +54,9 @@ class Config(BaseSettings): @field_validator("cache_dir", mode="before") @classmethod def folder_exists(cls, v: Path, info: FieldValidationInfo) -> Path: + """ + The base cache dir should exist before validating other paths + """ v = Path(v) v.mkdir(exist_ok=True) assert v.exists() @@ -61,7 +64,10 @@ class Config(BaseSettings): @model_validator(mode="after") def folders_exist(self) -> "Config": - for field, path in self.model_dump().items(): + """ + All folders, including computed folders, should exist. + """ + for path in self.model_dump().values(): if isinstance(path, Path): path.mkdir(exist_ok=True, parents=True) assert path.exists() diff --git a/nwb_linkml/src/nwb_linkml/generators/__init__.py b/nwb_linkml/src/nwb_linkml/generators/__init__.py index d95cc80..08f1701 100644 --- a/nwb_linkml/src/nwb_linkml/generators/__init__.py +++ b/nwb_linkml/src/nwb_linkml/generators/__init__.py @@ -1 +1,12 @@ +""" +Generate downstream output from LinkML schema + +Mostly for monkeypatching the pydantic generator from linkml with +changes that are unlikely to be useful upstream +""" + from nwb_linkml.generators.pydantic import PydanticGenerator + +__all__ = [ + 'PydanticGenerator' +] diff --git a/nwb_linkml/src/nwb_linkml/generators/pydantic.py b/nwb_linkml/src/nwb_linkml/generators/pydantic.py index b83bc16..9cd63fa 100644 --- a/nwb_linkml/src/nwb_linkml/generators/pydantic.py +++ b/nwb_linkml/src/nwb_linkml/generators/pydantic.py @@ -25,6 +25,9 @@ The `serialize` method: """ +# FIXME: Remove this after we refactor this generator +# ruff: noqa + import inspect import sys import warnings diff --git a/nwb_linkml/src/nwb_linkml/io/__init__.py b/nwb_linkml/src/nwb_linkml/io/__init__.py index 946cf39..95e5e95 100644 --- a/nwb_linkml/src/nwb_linkml/io/__init__.py +++ b/nwb_linkml/src/nwb_linkml/io/__init__.py @@ -1,2 +1,11 @@ +""" +Loading and dumping data from and to files +""" + from nwb_linkml.io import schema from nwb_linkml.io.hdf5 import HDF5IO + +__all__ = [ + "HDF5IO", + "schema" +] diff --git a/nwb_linkml/src/nwb_linkml/io/hdf5.py b/nwb_linkml/src/nwb_linkml/io/hdf5.py index 4ea7312..d45bfa2 100644 --- a/nwb_linkml/src/nwb_linkml/io/hdf5.py +++ b/nwb_linkml/src/nwb_linkml/io/hdf5.py @@ -1,5 +1,6 @@ """ -This is a sandbox file that should be split out to its own pydantic-hdf5 package, but just experimenting here to get our bearings +This is a sandbox file that should be split out to its own pydantic-hdf5 package, +but just experimenting here to get our bearings Notes: @@ -42,6 +43,9 @@ from nwb_linkml.providers.schema import SchemaProvider class HDF5IO: + """ + Read (and eventually write) from an NWB HDF5 file. + """ def __init__(self, path: Path): self.path = Path(path) @@ -59,7 +63,8 @@ class HDF5IO: The read process is in several stages: - * Use :meth:`.make_provider` to generate any needed LinkML Schema or Pydantic Classes using a :class:`.SchemaProvider` + * Use :meth:`.make_provider` to generate any needed LinkML Schema or Pydantic Classes + using a :class:`.SchemaProvider` * :func:`flatten_hdf` file into a :class:`.ReadQueue` of nodes. * Apply the queue's :class:`ReadPhases` : @@ -67,24 +72,29 @@ class HDF5IO: * ``read`` - load the actual data into temporary holding objects * ``construct`` - cast the read data into models. - Read is split into stages like this to handle references between objects, where the read result of one node - might depend on another having already been completed. It also allows us to parallelize the operations + Read is split into stages like this to handle references between objects, + where the read result of one node + might depend on another having already been completed. + It also allows us to parallelize the operations since each mapping operation is independent of the results of all the others in that pass. .. todo:: Implement reading, skipping arrays - they are fast to read with the ArrayProxy class and dask, but there are times when we might want to leave them out of the read entirely. - This might be better implemented as a filter on ``model_dump`` , but to investigate further - how best to support reading just metadata, or even some specific field value, or if + This might be better implemented as a filter on ``model_dump`` , + but to investigate further how best to support reading just metadata, + or even some specific field value, or if we should leave that to other implementations like eg. after we do SQL export then not rig up a whole query system ourselves. Args: - path (Optional[str]): If ``None`` (default), read whole file. Otherwise, read from specific (hdf5) path and its children + path (Optional[str]): If ``None`` (default), read whole file. + Otherwise, read from specific (hdf5) path and its children Returns: - ``NWBFile`` if ``path`` is ``None``, otherwise whatever Model or dictionary of models applies to the requested ``path`` + ``NWBFile`` if ``path`` is ``None``, + otherwise whatever Model or dictionary of models applies to the requested ``path`` """ provider = self.make_provider() diff --git a/nwb_linkml/src/nwb_linkml/maps/__init__.py b/nwb_linkml/src/nwb_linkml/maps/__init__.py index 5a89fd4..ffdba53 100644 --- a/nwb_linkml/src/nwb_linkml/maps/__init__.py +++ b/nwb_linkml/src/nwb_linkml/maps/__init__.py @@ -1,5 +1,18 @@ -# Import everything so it's defined, but shoudlnt' necessarily be used from here +""" +Mapping from one domain to another +""" + from nwb_linkml.maps.dtype import flat_to_linkml, flat_to_nptyping from nwb_linkml.maps.map import Map from nwb_linkml.maps.postload import MAP_HDMF_DATATYPE_DEF, MAP_HDMF_DATATYPE_INC from nwb_linkml.maps.quantity import QUANTITY_MAP + +__all__ = [ + "MAP_HDMF_DATATYPE_DEF", + "MAP_HDMF_DATATYPE_INC", + "QUANTITY_MAP", + "Map", + "flat_to_linkml", + "flat_to_nptyping" +] + diff --git a/nwb_schema_language/src/nwb_schema_language/__init__.py b/nwb_schema_language/src/nwb_schema_language/__init__.py index 4c93a08..baa0c36 100644 --- a/nwb_schema_language/src/nwb_schema_language/__init__.py +++ b/nwb_schema_language/src/nwb_schema_language/__init__.py @@ -16,8 +16,24 @@ try: ) DTypeType = Union[List[CompoundDtype], FlatDtype, ReferenceDtype] + + except (NameError, RecursionError): warnings.warn( "Error importing pydantic classes, passing because we might be in the process of patching" " them, but it is likely they are broken and you will be unable to use them!" ) + +__all__ = [ + "Attribute", + "CompoundDtype", + "Dataset", + "DTypeType", + "FlatDtype", + "Group", + "Link", + "Namespace", + "Namespaces", + "ReferenceDtype", + "Schema", +] diff --git a/pyproject.toml b/pyproject.toml index 93e076c..558165e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ ignore = [ fixable = ["ALL"] [tool.ruff.lint.per-file-ignores] -"**/tests/**.py" = ["D", "ANN"] +"**/tests/**.py" = ["D", "ANN", "E501", "F841", "F722"] [tool.mypy] plugins = [