mirror of
https://github.com/p2p-ld/nwb-linkml.git
synced 2025-01-09 21:54:27 +00:00
fix hdmf inheritance during testing, error handling
This commit is contained in:
parent
8993014832
commit
fc6f60ad61
9 changed files with 129 additions and 66 deletions
|
@ -5,7 +5,7 @@
|
||||||
groups = ["default", "dev", "plot", "tests"]
|
groups = ["default", "dev", "plot", "tests"]
|
||||||
strategy = ["inherit_metadata"]
|
strategy = ["inherit_metadata"]
|
||||||
lock_version = "4.5.0"
|
lock_version = "4.5.0"
|
||||||
content_hash = "sha256:1c297e11f6dc9e4f6b8d29df872177d2ce65bbd334c0b65aa5175dfb125c4d9f"
|
content_hash = "sha256:14dd3d0b396dc25e554b924825664346d2644f265e48346180f1cfdf833a8c92"
|
||||||
|
|
||||||
[[metadata.targets]]
|
[[metadata.targets]]
|
||||||
requires_python = ">=3.10,<3.13"
|
requires_python = ">=3.10,<3.13"
|
||||||
|
@ -1038,9 +1038,9 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpydantic"
|
name = "numpydantic"
|
||||||
version = "1.3.3"
|
version = "1.6.0"
|
||||||
requires_python = "<4.0,>=3.9"
|
requires_python = "<4.0,>=3.9"
|
||||||
summary = "Type and shape validation and serialization for numpy arrays in pydantic models"
|
summary = "Type and shape validation and serialization for arbitrary array types in pydantic models"
|
||||||
groups = ["default"]
|
groups = ["default"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"numpy>=1.24.0",
|
"numpy>=1.24.0",
|
||||||
|
@ -1048,13 +1048,13 @@ dependencies = [
|
||||||
"typing-extensions>=4.11.0; python_version < \"3.11\"",
|
"typing-extensions>=4.11.0; python_version < \"3.11\"",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "numpydantic-1.3.3-py3-none-any.whl", hash = "sha256:e002767252b1b77abb7715834ab7cbf58964baddae44863710f09e71b23287e4"},
|
{file = "numpydantic-1.6.0-py3-none-any.whl", hash = "sha256:72f3ef0bc8a5801bac6fb79920467d763d51cddec8476875efeb5064c11c04cf"},
|
||||||
{file = "numpydantic-1.3.3.tar.gz", hash = "sha256:1cc2744f7b5fbcecd51a64fafaf8c9a564bb296336a566a16be97ba7b1c28698"},
|
{file = "numpydantic-1.6.0.tar.gz", hash = "sha256:9785ba7eb5489b9e5438109e9b2dcd1cc0aa87d1b6b5df71fb906dc0708df83c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nwb-models"
|
name = "nwb-models"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
requires_python = ">=3.10"
|
requires_python = ">=3.10"
|
||||||
summary = "Pydantic/LinkML models for Neurodata Without Borders"
|
summary = "Pydantic/LinkML models for Neurodata Without Borders"
|
||||||
groups = ["default"]
|
groups = ["default"]
|
||||||
|
@ -1064,23 +1064,23 @@ dependencies = [
|
||||||
"pydantic>=2.3.0",
|
"pydantic>=2.3.0",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "nwb_models-0.1.0-py3-none-any.whl", hash = "sha256:d485422865f6762586e8f8389d67bce17a3e66d07f6273385a751145afbbbfea"},
|
{file = "nwb_models-0.2.0-py3-none-any.whl", hash = "sha256:72bb8a8879261488071d4e8eff35f2cbb20c44ac4bb7f67806c6329b4f8b2068"},
|
||||||
{file = "nwb_models-0.1.0.tar.gz", hash = "sha256:3c3ccfc6c2ac03dffe26ba7f180aecc650d6593c05d4f306f84b90fabc3ff2b8"},
|
{file = "nwb_models-0.2.0.tar.gz", hash = "sha256:7e7f280378c668e1695dd9d53b32073d85615e90fee0ec417888dd83bdb9cbb3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nwb-schema-language"
|
name = "nwb-schema-language"
|
||||||
version = "0.1.3"
|
version = "0.2.0"
|
||||||
requires_python = ">=3.9,<4.0"
|
requires_python = "<3.13,>=3.10"
|
||||||
summary = "Translation of the nwb-schema-language to LinkML"
|
summary = "Translation of the nwb-schema-language to LinkML"
|
||||||
groups = ["default"]
|
groups = ["default"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"linkml-runtime<2.0.0,>=1.1.24",
|
"linkml-runtime>=1.7.7",
|
||||||
"pydantic<3.0.0,>=2.3.0",
|
"pydantic>=2.3.0",
|
||||||
]
|
]
|
||||||
files = [
|
files = [
|
||||||
{file = "nwb_schema_language-0.1.3-py3-none-any.whl", hash = "sha256:2eb86aac6614d490f7ec3fa68634bb9dceb3834d9820f5afc5645a9f3b0c3401"},
|
{file = "nwb_schema_language-0.2.0-py3-none-any.whl", hash = "sha256:354afb0abfbc61a6d6b227695b9a4312df5030f2746b517fc5849ac085c8e5f2"},
|
||||||
{file = "nwb_schema_language-0.1.3.tar.gz", hash = "sha256:ad290e2896a9cde7e2f353bc3b8ddf42be865238d991167d397ff2e0d03c88ba"},
|
{file = "nwb_schema_language-0.2.0.tar.gz", hash = "sha256:59beda56ea52a55f4514d7e4b73e30ceaee1c60b7ddf4fc80afd48777acf9e50"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -22,7 +22,7 @@ dependencies = [
|
||||||
"pydantic-settings>=2.0.3",
|
"pydantic-settings>=2.0.3",
|
||||||
"tqdm>=4.66.1",
|
"tqdm>=4.66.1",
|
||||||
'typing-extensions>=4.12.2;python_version<"3.11"',
|
'typing-extensions>=4.12.2;python_version<"3.11"',
|
||||||
"numpydantic>=1.5.0",
|
"numpydantic>=1.6.0",
|
||||||
"black>=24.4.2",
|
"black>=24.4.2",
|
||||||
"pandas>=2.2.2",
|
"pandas>=2.2.2",
|
||||||
"networkx>=3.3",
|
"networkx>=3.3",
|
||||||
|
|
|
@ -9,7 +9,7 @@ import re
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Callable, ClassVar, Dict, List, Literal, Optional, Tuple
|
from typing import Callable, ClassVar, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from linkml.generators import PydanticGenerator
|
from linkml.generators import PydanticGenerator
|
||||||
from linkml.generators.pydanticgen.array import ArrayRepresentation, NumpydanticArray
|
from linkml.generators.pydanticgen.array import ArrayRepresentation, NumpydanticArray
|
||||||
|
@ -72,7 +72,7 @@ class NWBPydanticGenerator(PydanticGenerator):
|
||||||
emit_metadata: bool = True
|
emit_metadata: bool = True
|
||||||
gen_classvars: bool = True
|
gen_classvars: bool = True
|
||||||
gen_slots: bool = True
|
gen_slots: bool = True
|
||||||
extra_fields: Literal["allow", "forbid", "ignore"] = "allow"
|
# extra_fields: Literal["allow", "forbid", "ignore"] = "allow"
|
||||||
|
|
||||||
skip_meta: ClassVar[Tuple[str]] = ("domain_of", "alias")
|
skip_meta: ClassVar[Tuple[str]] = ("domain_of", "alias")
|
||||||
|
|
||||||
|
@ -269,7 +269,7 @@ class AfterGenerateClass:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if cls.cls.name == "DynamicTable":
|
if cls.cls.name == "DynamicTable":
|
||||||
cls.cls.bases = ["DynamicTableMixin", "ConfiguredBaseModel"]
|
cls.cls.bases = ["DynamicTableMixin"]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cls.injected_classes is None
|
cls.injected_classes is None
|
||||||
|
@ -287,18 +287,18 @@ class AfterGenerateClass:
|
||||||
else: # pragma: no cover - for completeness, shouldn't happen
|
else: # pragma: no cover - for completeness, shouldn't happen
|
||||||
cls.imports = DYNAMIC_TABLE_IMPORTS.model_copy()
|
cls.imports = DYNAMIC_TABLE_IMPORTS.model_copy()
|
||||||
elif cls.cls.name == "VectorData":
|
elif cls.cls.name == "VectorData":
|
||||||
cls.cls.bases = ["VectorDataMixin", "ConfiguredBaseModel"]
|
cls.cls.bases = ["VectorDataMixin"]
|
||||||
# make ``value`` generic on T
|
# make ``value`` generic on T
|
||||||
if "value" in cls.cls.attributes:
|
if "value" in cls.cls.attributes:
|
||||||
cls.cls.attributes["value"].range = "Optional[T]"
|
cls.cls.attributes["value"].range = "Optional[T]"
|
||||||
elif cls.cls.name == "VectorIndex":
|
elif cls.cls.name == "VectorIndex":
|
||||||
cls.cls.bases = ["VectorIndexMixin", "ConfiguredBaseModel"]
|
cls.cls.bases = ["VectorIndexMixin"]
|
||||||
elif cls.cls.name == "DynamicTableRegion":
|
elif cls.cls.name == "DynamicTableRegion":
|
||||||
cls.cls.bases = ["DynamicTableRegionMixin", "VectorData", "ConfiguredBaseModel"]
|
cls.cls.bases = ["DynamicTableRegionMixin", "VectorData"]
|
||||||
elif cls.cls.name == "AlignedDynamicTable":
|
elif cls.cls.name == "AlignedDynamicTable":
|
||||||
cls.cls.bases = ["AlignedDynamicTableMixin", "DynamicTable"]
|
cls.cls.bases = ["AlignedDynamicTableMixin", "DynamicTable"]
|
||||||
elif cls.cls.name == "ElementIdentifiers":
|
elif cls.cls.name == "ElementIdentifiers":
|
||||||
cls.cls.bases = ["ElementIdentifiersMixin", "Data", "ConfiguredBaseModel"]
|
cls.cls.bases = ["ElementIdentifiersMixin", "Data"]
|
||||||
# make ``value`` generic on T
|
# make ``value`` generic on T
|
||||||
if "value" in cls.cls.attributes:
|
if "value" in cls.cls.attributes:
|
||||||
cls.cls.attributes["value"].range = "Optional[T]"
|
cls.cls.attributes["value"].range = "Optional[T]"
|
||||||
|
|
|
@ -30,7 +30,8 @@ BASEMODEL_COERCE_VALUE = """
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"coerce_value: Could not use the value field of {type(v)} "
|
f"coerce_value: Could not use the value field of {type(v)} "
|
||||||
f"to construct {cls.__name__}.{info.field_name}, "
|
f"to construct {cls.__name__}.{info.field_name}, "
|
||||||
f"expected type: {cls.model_fields[info.field_name].annotation}"
|
f"expected type: {cls.model_fields[info.field_name].annotation}\\n"
|
||||||
|
f"inner error: {str(e1)}"
|
||||||
) from e1
|
) from e1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -48,7 +49,8 @@ BASEMODEL_CAST_WITH_VALUE = """
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"cast_with_value: Could not cast {type(v)} as value field for "
|
f"cast_with_value: Could not cast {type(v)} as value field for "
|
||||||
f"{cls.__name__}.{info.field_name},"
|
f"{cls.__name__}.{info.field_name},"
|
||||||
f" expected_type: {cls.model_fields[info.field_name].annotation}"
|
f" expected_type: {cls.model_fields[info.field_name].annotation}\\n"
|
||||||
|
f"inner error: {str(e1)}"
|
||||||
) from e1
|
) from e1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,30 @@ if TYPE_CHECKING: # pragma: no cover
|
||||||
T = TypeVar("T", bound=NDArray)
|
T = TypeVar("T", bound=NDArray)
|
||||||
T_INJECT = 'T = TypeVar("T", bound=NDArray)'
|
T_INJECT = 'T = TypeVar("T", bound=NDArray)'
|
||||||
|
|
||||||
|
if "pytest" in sys.modules:
|
||||||
|
from nwb_models.models import ConfiguredBaseModel
|
||||||
|
else:
|
||||||
|
|
||||||
class DynamicTableMixin(BaseModel):
|
class ConfiguredBaseModel(BaseModel):
|
||||||
|
"""
|
||||||
|
Dummy ConfiguredBaseModel (without its methods from :mod:`.includes.base` )
|
||||||
|
used so that the injected mixins inherit from the `ConfiguredBaseModel`
|
||||||
|
and we get a linear inheritance MRO (rather than needing to inherit
|
||||||
|
from the mixins *and* the configured base model) so that the
|
||||||
|
model_config is correctly resolved (ie. to allow extra args)
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_config = ConfigDict(
|
||||||
|
validate_assignment=True,
|
||||||
|
validate_default=True,
|
||||||
|
extra="forbid",
|
||||||
|
arbitrary_types_allowed=True,
|
||||||
|
use_enum_values=True,
|
||||||
|
strict=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicTableMixin(ConfiguredBaseModel):
|
||||||
"""
|
"""
|
||||||
Mixin to make DynamicTable subclasses behave like tables/dataframes
|
Mixin to make DynamicTable subclasses behave like tables/dataframes
|
||||||
|
|
||||||
|
@ -295,13 +317,19 @@ class DynamicTableMixin(BaseModel):
|
||||||
model[key] = to_cast(name=key, description="", value=val)
|
model[key] = to_cast(name=key, description="", value=val)
|
||||||
except ValidationError as e: # pragma: no cover
|
except ValidationError as e: # pragma: no cover
|
||||||
raise ValidationError.from_exception_data(
|
raise ValidationError.from_exception_data(
|
||||||
title=f"field {key} cannot be cast to VectorData from {val}",
|
title="cast_extra_columns",
|
||||||
line_errors=[
|
line_errors=[
|
||||||
{
|
{
|
||||||
"type": "ValueError",
|
"type": "value_error",
|
||||||
"loc": ("DynamicTableMixin", "cast_extra_columns"),
|
|
||||||
"input": val,
|
"input": val,
|
||||||
}
|
"loc": ("DynamicTableMixin", "cast_extra_columns"),
|
||||||
|
"ctx": {
|
||||||
|
"error": ValueError(
|
||||||
|
f"field {key} cannot be cast to {to_cast} from {val}"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
*e.errors(),
|
||||||
],
|
],
|
||||||
) from e
|
) from e
|
||||||
return model
|
return model
|
||||||
|
@ -364,18 +392,21 @@ class DynamicTableMixin(BaseModel):
|
||||||
# should pass if we're supposed to be a VectorData column
|
# should pass if we're supposed to be a VectorData column
|
||||||
# don't want to override intention here by insisting that it is
|
# don't want to override intention here by insisting that it is
|
||||||
# *actually* a VectorData column in case an NDArray has been specified for now
|
# *actually* a VectorData column in case an NDArray has been specified for now
|
||||||
|
description = cls.model_fields[info.field_name].description
|
||||||
|
description = description if description is not None else ""
|
||||||
|
|
||||||
return handler(
|
return handler(
|
||||||
annotation(
|
annotation(
|
||||||
val,
|
val,
|
||||||
name=info.field_name,
|
name=info.field_name,
|
||||||
description=cls.model_fields[info.field_name].description,
|
description=description,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise e from None
|
raise e from None
|
||||||
|
|
||||||
|
|
||||||
class VectorDataMixin(BaseModel, Generic[T]):
|
class VectorDataMixin(ConfiguredBaseModel, Generic[T]):
|
||||||
"""
|
"""
|
||||||
Mixin class to give VectorData indexing abilities
|
Mixin class to give VectorData indexing abilities
|
||||||
"""
|
"""
|
||||||
|
@ -426,7 +457,7 @@ class VectorDataMixin(BaseModel, Generic[T]):
|
||||||
return len(self.value)
|
return len(self.value)
|
||||||
|
|
||||||
|
|
||||||
class VectorIndexMixin(BaseModel, Generic[T]):
|
class VectorIndexMixin(ConfiguredBaseModel, Generic[T]):
|
||||||
"""
|
"""
|
||||||
Mixin class to give VectorIndex indexing abilities
|
Mixin class to give VectorIndex indexing abilities
|
||||||
"""
|
"""
|
||||||
|
@ -518,7 +549,7 @@ class VectorIndexMixin(BaseModel, Generic[T]):
|
||||||
return len(self.value)
|
return len(self.value)
|
||||||
|
|
||||||
|
|
||||||
class DynamicTableRegionMixin(BaseModel):
|
class DynamicTableRegionMixin(ConfiguredBaseModel):
|
||||||
"""
|
"""
|
||||||
Mixin to allow indexing references to regions of dynamictables
|
Mixin to allow indexing references to regions of dynamictables
|
||||||
"""
|
"""
|
||||||
|
@ -574,7 +605,7 @@ class DynamicTableRegionMixin(BaseModel):
|
||||||
) # pragma: no cover
|
) # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
class AlignedDynamicTableMixin(BaseModel):
|
class AlignedDynamicTableMixin(ConfiguredBaseModel):
|
||||||
"""
|
"""
|
||||||
Mixin to allow indexing multiple tables that are aligned on a common ID
|
Mixin to allow indexing multiple tables that are aligned on a common ID
|
||||||
|
|
||||||
|
@ -927,12 +958,18 @@ if "pytest" in sys.modules:
|
||||||
class VectorData(VectorDataMixin):
|
class VectorData(VectorDataMixin):
|
||||||
"""VectorData subclass for testing"""
|
"""VectorData subclass for testing"""
|
||||||
|
|
||||||
pass
|
name: str = Field(...)
|
||||||
|
description: str = Field(
|
||||||
|
..., description="""Description of what these vectors represent."""
|
||||||
|
)
|
||||||
|
|
||||||
class VectorIndex(VectorIndexMixin):
|
class VectorIndex(VectorIndexMixin):
|
||||||
"""VectorIndex subclass for testing"""
|
"""VectorIndex subclass for testing"""
|
||||||
|
|
||||||
pass
|
name: str = Field(...)
|
||||||
|
description: str = Field(
|
||||||
|
..., description="""Description of what these vectors represent."""
|
||||||
|
)
|
||||||
|
|
||||||
class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
|
class DynamicTableRegion(DynamicTableRegionMixin, VectorData):
|
||||||
"""DynamicTableRegion subclass for testing"""
|
"""DynamicTableRegion subclass for testing"""
|
||||||
|
|
|
@ -12,7 +12,7 @@ from linkml_runtime.linkml_model import (
|
||||||
TypeDefinition,
|
TypeDefinition,
|
||||||
)
|
)
|
||||||
|
|
||||||
from nwb_linkml.maps import flat_to_linkml
|
from nwb_linkml.maps import flat_to_linkml, linkml_reprs
|
||||||
|
|
||||||
|
|
||||||
def _make_dtypes() -> List[TypeDefinition]:
|
def _make_dtypes() -> List[TypeDefinition]:
|
||||||
|
@ -36,6 +36,7 @@ def _make_dtypes() -> List[TypeDefinition]:
|
||||||
name=nwbtype,
|
name=nwbtype,
|
||||||
minimum_value=amin,
|
minimum_value=amin,
|
||||||
typeof=linkmltype, # repr=repr_string
|
typeof=linkmltype, # repr=repr_string
|
||||||
|
repr=linkml_reprs.get(nwbtype, None),
|
||||||
)
|
)
|
||||||
DTypeTypes.append(atype)
|
DTypeTypes.append(atype)
|
||||||
return DTypeTypes
|
return DTypeTypes
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Mapping from one domain to another
|
Mapping from one domain to another
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from nwb_linkml.maps.dtype import flat_to_linkml, flat_to_np
|
from nwb_linkml.maps.dtype import flat_to_linkml, flat_to_np, linkml_reprs
|
||||||
from nwb_linkml.maps.map import Map
|
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.postload import MAP_HDMF_DATATYPE_DEF, MAP_HDMF_DATATYPE_INC
|
||||||
from nwb_linkml.maps.quantity import QUANTITY_MAP
|
from nwb_linkml.maps.quantity import QUANTITY_MAP
|
||||||
|
@ -14,4 +14,5 @@ __all__ = [
|
||||||
"Map",
|
"Map",
|
||||||
"flat_to_linkml",
|
"flat_to_linkml",
|
||||||
"flat_to_np",
|
"flat_to_np",
|
||||||
|
"linkml_reprs",
|
||||||
]
|
]
|
||||||
|
|
|
@ -39,6 +39,12 @@ flat_to_linkml = {
|
||||||
Map between the flat data types and the simpler linkml base types
|
Map between the flat data types and the simpler linkml base types
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
linkml_reprs = {"numeric": "float | int"}
|
||||||
|
"""
|
||||||
|
``repr`` fields used in the nwb language elements injected in every namespace
|
||||||
|
that give the nwb type a specific representation in the generated pydantic models
|
||||||
|
"""
|
||||||
|
|
||||||
flat_to_np = {
|
flat_to_np = {
|
||||||
"float": float,
|
"float": float,
|
||||||
"float32": np.float32,
|
"float32": np.float32,
|
||||||
|
|
|
@ -149,8 +149,8 @@ def test_dynamictable_mixin_colnames_index():
|
||||||
|
|
||||||
cols = {
|
cols = {
|
||||||
"existing_col": np.arange(10),
|
"existing_col": np.arange(10),
|
||||||
"new_col_1": hdmf.VectorData(value=np.arange(10)),
|
"new_col_1": hdmf.VectorData(name="new_col_1", description="", value=np.arange(10)),
|
||||||
"new_col_2": hdmf.VectorData(value=np.arange(10)),
|
"new_col_2": hdmf.VectorData(name="new_col_2", description="", value=np.arange(10)),
|
||||||
}
|
}
|
||||||
# explicit index with mismatching name
|
# explicit index with mismatching name
|
||||||
cols["weirdname_index"] = VectorIndexMixin(value=np.arange(10), target=cols["new_col_1"])
|
cols["weirdname_index"] = VectorIndexMixin(value=np.arange(10), target=cols["new_col_1"])
|
||||||
|
@ -171,9 +171,9 @@ def test_dynamictable_mixin_colnames_ordered():
|
||||||
|
|
||||||
cols = {
|
cols = {
|
||||||
"existing_col": np.arange(10),
|
"existing_col": np.arange(10),
|
||||||
"new_col_1": hdmf.VectorData(value=np.arange(10)),
|
"new_col_1": hdmf.VectorData(name="new_col_1", description="", value=np.arange(10)),
|
||||||
"new_col_2": hdmf.VectorData(value=np.arange(10)),
|
"new_col_2": hdmf.VectorData(name="new_col_2", description="", value=np.arange(10)),
|
||||||
"new_col_3": hdmf.VectorData(value=np.arange(10)),
|
"new_col_3": hdmf.VectorData(name="new_col_2", description="", value=np.arange(10)),
|
||||||
}
|
}
|
||||||
order = ["new_col_2", "existing_col", "new_col_1", "new_col_3"]
|
order = ["new_col_2", "existing_col", "new_col_1", "new_col_3"]
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ def test_dynamictable_mixin_getattr():
|
||||||
class MyDT(DynamicTableMixin):
|
class MyDT(DynamicTableMixin):
|
||||||
existing_col: hdmf.VectorData[NDArray[Shape["* col"], int]]
|
existing_col: hdmf.VectorData[NDArray[Shape["* col"], int]]
|
||||||
|
|
||||||
col = hdmf.VectorData(value=np.arange(10))
|
col = hdmf.VectorData(name="existing_col", description="", value=np.arange(10))
|
||||||
inst = MyDT(existing_col=col)
|
inst = MyDT(existing_col=col)
|
||||||
|
|
||||||
# regular lookup for attrs that exist
|
# regular lookup for attrs that exist
|
||||||
|
@ -257,13 +257,17 @@ def test_dynamictable_resolve_index():
|
||||||
|
|
||||||
cols = {
|
cols = {
|
||||||
"existing_col": np.arange(10),
|
"existing_col": np.arange(10),
|
||||||
"new_col_1": hdmf.VectorData(value=np.arange(10)),
|
"new_col_1": hdmf.VectorData(name="new_col_1", description="", value=np.arange(10)),
|
||||||
"new_col_2": hdmf.VectorData(value=np.arange(10)),
|
"new_col_2": hdmf.VectorData(name="new_col_2", description="", value=np.arange(10)),
|
||||||
}
|
}
|
||||||
# explicit index with mismatching name
|
# explicit index with mismatching name
|
||||||
cols["weirdname_index"] = hdmf.VectorIndex(value=np.arange(10), target=cols["new_col_1"])
|
cols["weirdname_index"] = hdmf.VectorIndex(
|
||||||
|
name="weirdname_index", description="", value=np.arange(10), target=cols["new_col_1"]
|
||||||
|
)
|
||||||
# implicit index with matching name
|
# implicit index with matching name
|
||||||
cols["new_col_2_index"] = hdmf.VectorIndex(value=np.arange(10))
|
cols["new_col_2_index"] = hdmf.VectorIndex(
|
||||||
|
name="new_col_2_index", description="", value=np.arange(10)
|
||||||
|
)
|
||||||
|
|
||||||
inst = MyDT(**cols)
|
inst = MyDT(**cols)
|
||||||
assert inst.weirdname_index.target is inst.new_col_1
|
assert inst.weirdname_index.target is inst.new_col_1
|
||||||
|
@ -282,14 +286,14 @@ def test_dynamictable_assert_equal_length():
|
||||||
|
|
||||||
cols = {
|
cols = {
|
||||||
"existing_col": np.arange(10),
|
"existing_col": np.arange(10),
|
||||||
"new_col_1": hdmf.VectorData(value=np.arange(11)),
|
"new_col_1": hdmf.VectorData(name="new_col_1", description="", value=np.arange(11)),
|
||||||
}
|
}
|
||||||
with pytest.raises(ValidationError, match="columns are not of equal length"):
|
with pytest.raises(ValidationError, match="columns are not of equal length"):
|
||||||
_ = MyDT(**cols)
|
_ = MyDT(**cols)
|
||||||
|
|
||||||
cols = {
|
cols = {
|
||||||
"existing_col": np.arange(11),
|
"existing_col": np.arange(11),
|
||||||
"new_col_1": hdmf.VectorData(value=np.arange(10)),
|
"new_col_1": hdmf.VectorData(name="new_col_1", description="", value=np.arange(10)),
|
||||||
}
|
}
|
||||||
with pytest.raises(ValidationError, match="columns are not of equal length"):
|
with pytest.raises(ValidationError, match="columns are not of equal length"):
|
||||||
_ = MyDT(**cols)
|
_ = MyDT(**cols)
|
||||||
|
@ -297,16 +301,20 @@ def test_dynamictable_assert_equal_length():
|
||||||
# wrong lengths are fine as long as the index is good
|
# wrong lengths are fine as long as the index is good
|
||||||
cols = {
|
cols = {
|
||||||
"existing_col": np.arange(10),
|
"existing_col": np.arange(10),
|
||||||
"new_col_1": hdmf.VectorData(value=np.arange(100)),
|
"new_col_1": hdmf.VectorData(name="new_col_1", description="", value=np.arange(100)),
|
||||||
"new_col_1_index": hdmf.VectorIndex(value=np.arange(0, 100, 10) + 10),
|
"new_col_1_index": hdmf.VectorIndex(
|
||||||
|
name="new_col_1_index", description="", value=np.arange(0, 100, 10) + 10
|
||||||
|
),
|
||||||
}
|
}
|
||||||
_ = MyDT(**cols)
|
_ = MyDT(**cols)
|
||||||
|
|
||||||
# but not fine if the index is not good
|
# but not fine if the index is not good
|
||||||
cols = {
|
cols = {
|
||||||
"existing_col": np.arange(10),
|
"existing_col": np.arange(10),
|
||||||
"new_col_1": hdmf.VectorData(value=np.arange(100)),
|
"new_col_1": hdmf.VectorData(name="new_col_1", description="", value=np.arange(100)),
|
||||||
"new_col_1_index": hdmf.VectorIndex(value=np.arange(0, 100, 5) + 5),
|
"new_col_1_index": hdmf.VectorIndex(
|
||||||
|
name="new_col_1_index", description="", value=np.arange(0, 100, 5) + 5
|
||||||
|
),
|
||||||
}
|
}
|
||||||
with pytest.raises(ValidationError, match="columns are not of equal length"):
|
with pytest.raises(ValidationError, match="columns are not of equal length"):
|
||||||
_ = MyDT(**cols)
|
_ = MyDT(**cols)
|
||||||
|
@ -321,8 +329,8 @@ def test_dynamictable_setattr():
|
||||||
existing_col: hdmf.VectorData[NDArray[Shape["* col"], int]]
|
existing_col: hdmf.VectorData[NDArray[Shape["* col"], int]]
|
||||||
|
|
||||||
cols = {
|
cols = {
|
||||||
"existing_col": hdmf.VectorData(value=np.arange(10)),
|
"existing_col": hdmf.VectorData(name="existing_col", description="", value=np.arange(10)),
|
||||||
"new_col_1": hdmf.VectorData(value=np.arange(10)),
|
"new_col_1": hdmf.VectorData(name="new_col_1", description="", value=np.arange(10)),
|
||||||
}
|
}
|
||||||
inst = MyDT(existing_col=cols["existing_col"])
|
inst = MyDT(existing_col=cols["existing_col"])
|
||||||
assert inst.colnames == ["existing_col"]
|
assert inst.colnames == ["existing_col"]
|
||||||
|
@ -335,7 +343,7 @@ def test_dynamictable_setattr():
|
||||||
|
|
||||||
# model validators should be called to ensure equal length
|
# model validators should be called to ensure equal length
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
inst.new_col_2 = hdmf.VectorData(value=np.arange(11))
|
inst.new_col_2 = hdmf.VectorData(name="new_col_2", description="", value=np.arange(11))
|
||||||
|
|
||||||
|
|
||||||
def test_vectordata_indexing():
|
def test_vectordata_indexing():
|
||||||
|
@ -346,7 +354,7 @@ def test_vectordata_indexing():
|
||||||
value_array, index_array = _ragged_array(n_rows)
|
value_array, index_array = _ragged_array(n_rows)
|
||||||
value_array = np.concatenate(value_array)
|
value_array = np.concatenate(value_array)
|
||||||
|
|
||||||
data = hdmf.VectorData(value=value_array)
|
data = hdmf.VectorData(name="data", description="", value=value_array)
|
||||||
|
|
||||||
# before we have an index, things should work as normal, indexing a 1D array
|
# before we have an index, things should work as normal, indexing a 1D array
|
||||||
assert data[0] == 0
|
assert data[0] == 0
|
||||||
|
@ -356,7 +364,7 @@ def test_vectordata_indexing():
|
||||||
data[0] = 0
|
data[0] = 0
|
||||||
|
|
||||||
# indexes by themselves are the same
|
# indexes by themselves are the same
|
||||||
index_notarget = hdmf.VectorIndex(value=index_array)
|
index_notarget = hdmf.VectorIndex(name="no_target_index", description="", value=index_array)
|
||||||
assert index_notarget[0] == index_array[0]
|
assert index_notarget[0] == index_array[0]
|
||||||
assert all(index_notarget[0:3] == index_array[0:3])
|
assert all(index_notarget[0:3] == index_array[0:3])
|
||||||
oldval = index_array[0]
|
oldval = index_array[0]
|
||||||
|
@ -364,7 +372,7 @@ def test_vectordata_indexing():
|
||||||
assert index_notarget[0] == 5
|
assert index_notarget[0] == 5
|
||||||
index_notarget[0] = oldval
|
index_notarget[0] = oldval
|
||||||
|
|
||||||
index = hdmf.VectorIndex(value=index_array, target=data)
|
index = hdmf.VectorIndex(name="data_index", description="", value=index_array, target=data)
|
||||||
data._index = index
|
data._index = index
|
||||||
|
|
||||||
# after an index, both objects should index raggedly
|
# after an index, both objects should index raggedly
|
||||||
|
@ -396,8 +404,10 @@ def test_vectordata_getattr():
|
||||||
"""
|
"""
|
||||||
VectorData and VectorIndex both forward getattr to ``value``
|
VectorData and VectorIndex both forward getattr to ``value``
|
||||||
"""
|
"""
|
||||||
data = hdmf.VectorData(value=np.arange(100))
|
data = hdmf.VectorData(name="data", description="", value=np.arange(100))
|
||||||
index = hdmf.VectorIndex(value=np.arange(10, 101, 10), target=data)
|
index = hdmf.VectorIndex(
|
||||||
|
name="data_index", description="", value=np.arange(10, 101, 10), target=data
|
||||||
|
)
|
||||||
|
|
||||||
# get attrs that we defined on the models
|
# get attrs that we defined on the models
|
||||||
# i.e. no attribute errors here
|
# i.e. no attribute errors here
|
||||||
|
@ -447,7 +457,9 @@ def test_dynamictable_region_indexing(basic_table):
|
||||||
|
|
||||||
index = np.array([9, 4, 8, 3, 7, 2, 6, 1, 5, 0])
|
index = np.array([9, 4, 8, 3, 7, 2, 6, 1, 5, 0])
|
||||||
|
|
||||||
table_region = hdmf.DynamicTableRegion(value=index, table=inst)
|
table_region = hdmf.DynamicTableRegion(
|
||||||
|
name="table_region", description="", value=index, table=inst
|
||||||
|
)
|
||||||
|
|
||||||
row = table_region[1]
|
row = table_region[1]
|
||||||
assert all(row.iloc[0] == index[1])
|
assert all(row.iloc[0] == index[1])
|
||||||
|
@ -499,10 +511,14 @@ def test_dynamictable_region_ragged():
|
||||||
timeseries_index=spike_idx,
|
timeseries_index=spike_idx,
|
||||||
)
|
)
|
||||||
region = hdmf.DynamicTableRegion(
|
region = hdmf.DynamicTableRegion(
|
||||||
|
name="region",
|
||||||
|
description="a table region what else would it be",
|
||||||
table=table,
|
table=table,
|
||||||
value=value,
|
value=value,
|
||||||
)
|
)
|
||||||
index = hdmf.VectorIndex(name="index", description="hgggggggjjjj", target=region, value=idx)
|
index = hdmf.VectorIndex(
|
||||||
|
name="region_index", description="hgggggggjjjj", target=region, value=idx
|
||||||
|
)
|
||||||
region._index = index
|
region._index = index
|
||||||
|
|
||||||
rows = region[1]
|
rows = region[1]
|
||||||
|
@ -594,8 +610,8 @@ def test_mixed_aligned_dynamictable(aligned_table):
|
||||||
value_array, index_array = _ragged_array(10)
|
value_array, index_array = _ragged_array(10)
|
||||||
value_array = np.concatenate(value_array)
|
value_array = np.concatenate(value_array)
|
||||||
|
|
||||||
data = hdmf.VectorData(value=value_array)
|
data = hdmf.VectorData(name="data", description="", value=value_array)
|
||||||
index = hdmf.VectorIndex(value=index_array)
|
index = hdmf.VectorIndex(name="data_index", description="", value=index_array)
|
||||||
|
|
||||||
atable = AlignedTable(**cols, extra_col=data, extra_col_index=index)
|
atable = AlignedTable(**cols, extra_col=data, extra_col_index=index)
|
||||||
atable[0]
|
atable[0]
|
||||||
|
|
Loading…
Reference in a new issue