Skip to content

Commit

Permalink
Merge pull request #14 from p2p-ld/roll-down
Browse files Browse the repository at this point in the history
roll down parent inheritance recursively
  • Loading branch information
sneakers-the-rat authored Oct 1, 2024
2 parents f94a144 + 2ce1367 commit ae37db3
Show file tree
Hide file tree
Showing 315 changed files with 21,727 additions and 5,817 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ jobs:
run: pytest
working-directory: nwb_linkml

- name: Run nwb_schema_language Tests
run: pytest
working-directory: nwb_schema_language

- name: Coveralls Parallel
uses: coverallsapp/[email protected]
if: runner.os != 'macOS'
Expand Down
3 changes: 3 additions & 0 deletions docs/meta/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
28 changes: 14 additions & 14 deletions nwb_linkml/pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions nwb_linkml/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies = [
"nwb-models>=0.2.0",
"pyyaml>=6.0",
"linkml-runtime>=1.7.7",
"nwb-schema-language>=0.1.3",
"nwb-schema-language>=0.2.0",
"rich>=13.5.2",
#"linkml>=1.7.10",
"linkml @ git+https://github.com/sneakers-the-rat/linkml@nwb-linkml",
Expand All @@ -22,7 +22,7 @@ dependencies = [
"pydantic-settings>=2.0.3",
"tqdm>=4.66.1",
'typing-extensions>=4.12.2;python_version<"3.11"',
"numpydantic>=1.5.0",
"numpydantic>=1.6.0",
"black>=24.4.2",
"pandas>=2.2.2",
"networkx>=3.3",
Expand Down
95 changes: 92 additions & 3 deletions nwb_linkml/src/nwb_linkml/adapters/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
SlotDefinition,
TypeDefinition,
)
from pydantic import BaseModel
from pydantic import BaseModel, PrivateAttr

from nwb_linkml.logging import init_logger
from nwb_linkml.maps.dtype import float_types, integer_types, string_types
from nwb_schema_language import Attribute, CompoundDtype, Dataset, Group, Schema

if sys.version_info.minor >= 11:
Expand Down Expand Up @@ -103,6 +104,7 @@ class Adapter(BaseModel):

_logger: Optional[Logger] = None
_debug: Optional[bool] = None
_nwb_classes: dict[str, Dataset | Group] = PrivateAttr(default_factory=dict)

@property
def debug(self) -> bool:
Expand Down Expand Up @@ -135,7 +137,10 @@ def get(self, name: str) -> Union[Group, Dataset]:
Convenience wrapper around :meth:`.walk_field_values`
"""
return next(self.walk_field_values(self, "neurodata_type_def", name))
if name not in self._nwb_classes:
cls = next(self.walk_field_values(self, "neurodata_type_def", name))
self._nwb_classes[name] = cls
return self._nwb_classes[name]

def get_model_with_field(self, field: str) -> Generator[Union[Group, Dataset], None, None]:
"""
Expand Down Expand Up @@ -170,6 +175,10 @@ def walk(
# so skip to avoid combinatoric walking
if key == "imports" and type(input).__name__ == "SchemaAdapter":
continue
# nwb_schema_language objects have a reference to their parent,
# which causes cycles
if key == "parent":
continue
val = getattr(input, key)
yield (key, val)
if isinstance(val, (BaseModel, dict, list)):
Expand Down Expand Up @@ -300,5 +309,85 @@ def has_attrs(cls: Dataset) -> bool:
return (
cls.attributes is not None
and len(cls.attributes) > 0
and all([not a.value for a in cls.attributes])
and any([not a.value for a in cls.attributes])
)


def defaults(cls: Dataset | Attribute) -> dict:
"""
Handle default values -
* If ``value`` is present, yield `equals_string` or `equals_number` depending on dtype
**as well as** an ``ifabsent`` value - we both constrain the possible values to 1
and also supply it as the default
* else, if ``default_value`` is present, yield an appropriate ``ifabsent`` value
* If neither, yield an empty dict
Unlike nwb_schema_language, when ``value`` is set, we yield both a ``equals_*`` constraint
and an ``ifabsent`` constraint, because an ``equals_*`` can be declared without a default
in order to validate that a value is correctly set as the constrained value, and fail
if a value isn't provided.
"""
ret = {}
if cls.value:
if cls.dtype in integer_types:
ret["equals_number"] = cls.value
ret["ifabsent"] = f"integer({cls.value})"
elif cls.dtype in float_types:
ret["equals_number"] = cls.value
ret["ifabsent"] = f"float({cls.value})"
elif cls.dtype in string_types:
ret["equals_string"] = cls.value
ret["ifabsent"] = f"string({cls.value})"
else:
ret["equals_string"] = cls.value
ret["ifabsent"] = cls.value

elif cls.default_value:
if cls.dtype in string_types:
ret["ifabsent"] = f"string({cls.default_value})"
elif cls.dtype in integer_types:
ret["ifabsent"] = f"int({cls.default_value})"
elif cls.dtype in float_types:
ret["ifabsent"] = f"float({cls.default_value})"
else:
ret["ifabsent"] = cls.default_value

return ret


def is_container(group: Group) -> bool:
"""
Check if a group is a container group.
i.e. a group that...
* has no name
* multivalued quantity
* has a ``neurodata_type_inc``
* has no ``neurodata_type_def``
* has no sub-groups
* has no datasets
* has no attributes
Examples:
.. code-block:: yaml
- 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: '*'
"""
return (
not group.name
and group.quantity == "*"
and group.neurodata_type_inc
and not group.neurodata_type_def
and not group.datasets
and not group.groups
and not group.attributes
)
44 changes: 3 additions & 41 deletions nwb_linkml/src/nwb_linkml/adapters/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,13 @@

from linkml_runtime.linkml_model.meta import SlotDefinition

from nwb_linkml.adapters.adapter import Adapter, BuildResult, is_1d
from nwb_linkml.adapters.adapter import Adapter, BuildResult, defaults, is_1d
from nwb_linkml.adapters.array import ArrayAdapter
from nwb_linkml.maps import Map
from nwb_linkml.maps.dtype import handle_dtype, inlined
from nwb_schema_language import Attribute


def _make_ifabsent(val: str | int | float | None) -> str | None:
if val is None:
return None
elif isinstance(val, str):
return f"string({val})"
elif isinstance(val, int):
return f"integer({val})"
elif isinstance(val, float):
return f"float({val})"
else:
return str(val)


class AttrDefaults(TypedDict):
"""Default fields for an attribute"""

Expand All @@ -38,31 +25,6 @@ class AttrDefaults(TypedDict):
class AttributeMap(Map):
"""Base class for attribute mapping transformations :)"""

@classmethod
def handle_defaults(cls, attr: Attribute) -> AttrDefaults:
"""
Construct arguments for linkml slot default metaslots from nwb schema lang attribute props
"""
equals_string = None
equals_number = None
default_value = None
if attr.value:
if isinstance(attr.value, (int, float)):
equals_number = attr.value
elif attr.value:
equals_string = str(attr.value)

if equals_number:
default_value = _make_ifabsent(equals_number)
elif equals_string:
default_value = _make_ifabsent(equals_string)
elif attr.default_value:
default_value = _make_ifabsent(attr.default_value)

return AttrDefaults(
equals_string=equals_string, equals_number=equals_number, ifabsent=default_value
)

@classmethod
@abstractmethod
def check(cls, attr: Attribute) -> bool:
Expand Down Expand Up @@ -105,7 +67,7 @@ def apply(cls, attr: Attribute, res: Optional[BuildResult] = None) -> BuildResul
description=attr.doc,
required=attr.required,
inlined=inlined(attr.dtype),
**cls.handle_defaults(attr),
**defaults(attr),
)
return BuildResult(slots=[slot])

Expand Down Expand Up @@ -154,7 +116,7 @@ def apply(cls, attr: Attribute, res: Optional[BuildResult] = None) -> BuildResul
required=attr.required,
inlined=inlined(attr.dtype),
**expressions,
**cls.handle_defaults(attr),
**defaults(attr),
)
return BuildResult(slots=[slot])

Expand Down
Loading

0 comments on commit ae37db3

Please sign in to comment.