From 911a3ddb61b77539c857f08fda7a74ee2a33621c Mon Sep 17 00:00:00 2001 From: sneakers-the-rat Date: Wed, 25 Sep 2024 21:18:09 -0700 Subject: [PATCH] cast to value in container classes --- docs/meta/todo.md | 3 +++ nwb_linkml/src/nwb_linkml/adapters/group.py | 2 +- nwb_linkml/src/nwb_linkml/includes/base.py | 5 ++++- nwb_linkml/src/nwb_linkml/io/hdf5.py | 21 +++++++++++++++++++-- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/meta/todo.md b/docs/meta/todo.md index dd9f750..9199d22 100644 --- a/docs/meta/todo.md +++ b/docs/meta/todo.md @@ -53,6 +53,9 @@ Loading - [ ] Top-level containers are still a little janky, eg. how `ProcessingModule` just accepts extra args rather than properly abstracting `value` as a `__getitem__(self, key) -> T:` +Changes to linkml +- [ ] Allow parameterizing "extra" fields, so we don't have to stuff things into `value` dicts + ## Docs TODOs ```{todolist} diff --git a/nwb_linkml/src/nwb_linkml/adapters/group.py b/nwb_linkml/src/nwb_linkml/adapters/group.py index f0e44ea..fb919d0 100644 --- a/nwb_linkml/src/nwb_linkml/adapters/group.py +++ b/nwb_linkml/src/nwb_linkml/adapters/group.py @@ -129,7 +129,7 @@ class GroupAdapter(ClassAdapter): # We are a top-level container class like ProcessingModule base = self.build_base() # remove all the attributes and replace with child slot - base.classes[0].attributes = [slot] + base.classes[0].attributes.append(slot) return base def handle_container_slot(self, cls: Group) -> BuildResult: diff --git a/nwb_linkml/src/nwb_linkml/includes/base.py b/nwb_linkml/src/nwb_linkml/includes/base.py index d77a759..2a6e84d 100644 --- a/nwb_linkml/src/nwb_linkml/includes/base.py +++ b/nwb_linkml/src/nwb_linkml/includes/base.py @@ -65,7 +65,10 @@ BASEMODEL_COERCE_CHILD = """ annotation = annotation.__args__[0] try: if issubclass(annotation, type(v)) and annotation is not type(v): - v = annotation(**{**v.__dict__, **v.__pydantic_extra__}) + if v.__pydantic_extra__: + v = annotation(**{**v.__dict__, **v.__pydantic_extra__}) + else: + v = annotation(**v.__dict__) except TypeError: # fine, annotation is a non-class type like a TypeVar pass diff --git a/nwb_linkml/src/nwb_linkml/io/hdf5.py b/nwb_linkml/src/nwb_linkml/io/hdf5.py index bf4fbe6..1691a46 100644 --- a/nwb_linkml/src/nwb_linkml/io/hdf5.py +++ b/nwb_linkml/src/nwb_linkml/io/hdf5.py @@ -35,7 +35,7 @@ import h5py import networkx as nx import numpy as np from numpydantic.interface.hdf5 import H5ArrayPath -from pydantic import BaseModel +from pydantic import BaseModel, ValidationError from tqdm import tqdm from nwb_linkml.maps.hdf5 import ( @@ -167,7 +167,24 @@ def _load_node( if "neurodata_type" in obj.attrs: model = provider.get_class(obj.attrs["namespace"], obj.attrs["neurodata_type"]) - return model(**args) + try: + return model(**args) + except ValidationError as e1: + # try to restack extra fields into ``value`` + if "value" in model.model_fields: + value_dict = { + key: val for key, val in args.items() if key not in model.model_fields + } + for k in value_dict: + del args[k] + args["value"] = value_dict + try: + return model(**args) + except Exception as e2: + raise e2 from e1 + else: + raise e1 + else: if "name" in args: del args["name"]