fix imports being inside the any_of range, more manual inspection before finishing for the night

This commit is contained in:
sneakers-the-rat 2024-07-24 22:47:06 -07:00
parent 781b667952
commit af11bb61ec
Signed by untrusted user who does not match committer: jonny
GPG key ID: 6DCB96EF1E4D232D
4 changed files with 47 additions and 7 deletions

View file

@ -11,6 +11,8 @@ NWB schema translation
Cleanup Cleanup
- [ ] Update pydantic generator - [ ] Update pydantic generator
- [ ] Restore regressions from stripping the generator - [ ] Restore regressions from stripping the generator
- [x] Make any_of with array ranges work
- [ ] PR upstream `equals_string` and `ifabsent` (if existing PR doesnt fix)
- [ ] Use the class rather than a string in _get_class_slot_range_origin: - [ ] Use the class rather than a string in _get_class_slot_range_origin:
``` ```
or inlined_as_list or inlined_as_list
@ -43,6 +45,9 @@ Important things that are not implemented yet!
Remove monkeypatches/overrides once PRs are closed Remove monkeypatches/overrides once PRs are closed
- [ ] https://github.com/linkml/linkml-runtime/pull/330 - [ ] https://github.com/linkml/linkml-runtime/pull/330
Tests
- [ ] Ensure schemas and pydantic modules in repos are up to date
## Docs TODOs ## Docs TODOs
```{todolist} ```{todolist}

View file

@ -246,7 +246,6 @@ class ClassAdapter(Adapter):
ifabsent=f"string({name})", ifabsent=f"string({name})",
equals_string=equals_string, equals_string=equals_string,
range="string", range="string",
identifier=True,
) )
else: else:
name_slot = SlotDefinition(name="name", required=True, range="string", identifier=True) name_slot = SlotDefinition(name="name", required=True, range="string", identifier=True)

View file

@ -30,6 +30,7 @@ The `serialize` method:
import inspect import inspect
import pdb import pdb
import re
import sys import sys
import warnings import warnings
from copy import copy from copy import copy
@ -40,7 +41,8 @@ from typing import ClassVar, Dict, List, Optional, Tuple, Type, Union
from linkml.generators import PydanticGenerator from linkml.generators import PydanticGenerator
from linkml.generators.pydanticgen.build import SlotResult from linkml.generators.pydanticgen.build import SlotResult
from linkml.generators.pydanticgen.array import ArrayRepresentation from linkml.generators.pydanticgen.array import ArrayRepresentation, NumpydanticArray
from linkml.generators.pydanticgen.template import PydanticModule
from linkml_runtime.linkml_model.meta import ( from linkml_runtime.linkml_model.meta import (
Annotation, Annotation,
AnonymousSlotExpression, AnonymousSlotExpression,
@ -61,6 +63,8 @@ from pydantic import BaseModel
from nwb_linkml.maps import flat_to_nptyping from nwb_linkml.maps import flat_to_nptyping
from nwb_linkml.maps.naming import module_case, version_module_case from nwb_linkml.maps.naming import module_case, version_module_case
OPTIONAL_PATTERN = re.compile(r'Optional\[([\w\.]*)\]')
@dataclass @dataclass
class NWBPydanticGenerator(PydanticGenerator): class NWBPydanticGenerator(PydanticGenerator):
@ -86,7 +90,7 @@ class NWBPydanticGenerator(PydanticGenerator):
gen_slots: bool = True gen_slots: bool = True
skip_meta: ClassVar[Tuple[str]] = ('domain_of',) skip_meta: ClassVar[Tuple[str]] = ('domain_of','alias')
def _check_anyof( def _check_anyof(
self, s: SlotDefinition, sn: SlotDefinitionName, sv: SchemaView self, s: SlotDefinition, sn: SlotDefinitionName, sv: SchemaView
@ -111,18 +115,42 @@ class NWBPydanticGenerator(PydanticGenerator):
if not base_range_subsumes_any_of: if not base_range_subsumes_any_of:
raise ValueError("Slot cannot have both range and any_of defined") raise ValueError("Slot cannot have both range and any_of defined")
def before_generate_schema(self):
pass
def after_generate_slot(self, slot: SlotResult, sv: SchemaView) -> SlotResult: def after_generate_slot(self, slot: SlotResult, sv: SchemaView) -> SlotResult:
""" """
- strip unwanted metadata - strip unwanted metadata
- generate range with any_of
""" """
for key in self.skip_meta: for key in self.skip_meta:
if key in slot.attribute.meta: if key in slot.attribute.meta:
del slot.attribute.meta[key] del slot.attribute.meta[key]
# make array ranges in any_of
if 'any_of' in slot.attribute.meta:
any_ofs = slot.attribute.meta['any_of']
if all(['array' in expr for expr in any_ofs]):
ranges = []
is_optional = False
for expr in any_ofs:
# remove optional from inner type
pyrange = slot.attribute.range
is_optional = OPTIONAL_PATTERN.match(pyrange)
if is_optional:
pyrange = is_optional.groups()[0]
range_generator = NumpydanticArray(ArrayExpression(**expr['array']), pyrange)
ranges.append(range_generator.make().range)
slot.attribute.range = 'Union[' + ', '.join(ranges) + ']'
if is_optional:
slot.attribute.range = 'Optional[' + slot.attribute.range + ']'
del slot.attribute.meta['any_of']
return slot return slot
def before_render_template(self, template: PydanticModule, sv: SchemaView) -> PydanticModule:
if 'source_file' in template.meta:
del template.meta['source_file']
def compile_module( def compile_module(
self, module_path: Path = None, module_name: str = "test", **kwargs self, module_path: Path = None, module_name: str = "test", **kwargs
) -> ModuleType: # pragma: no cover - replaced with provider ) -> ModuleType: # pragma: no cover - replaced with provider

View file

@ -12,7 +12,7 @@ from types import ModuleType
from typing import List, Optional, Type from typing import List, Optional, Type
from linkml_runtime.linkml_model.meta import SchemaDefinition from linkml_runtime.linkml_model.meta import SchemaDefinition
from linkml.generators.pydanticgen.pydanticgen import SplitMode, _import_to_path from linkml.generators.pydanticgen.pydanticgen import SplitMode, _import_to_path, _ensure_inits
from pydantic import BaseModel from pydantic import BaseModel
from nwb_linkml.generators.pydantic import NWBPydanticGenerator from nwb_linkml.generators.pydantic import NWBPydanticGenerator
@ -148,7 +148,9 @@ class PydanticProvider(Provider):
force: bool, force: bool,
**kwargs **kwargs
) -> List[str]: ) -> List[str]:
# FIXME: This is messy as all fuck, we're just getting it to work again so we can start iterating on the models themselves
res = [] res = []
module_paths = []
# first make the namespace file we were given # first make the namespace file we were given
@ -170,6 +172,7 @@ class PydanticProvider(Provider):
if dump: if dump:
with open(ns_file, 'w') as ofile: with open(ns_file, 'w') as ofile:
ofile.write(serialized) ofile.write(serialized)
module_paths.append(ns_file)
else: else:
with open(ns_file, 'r') as ofile: with open(ns_file, 'r') as ofile:
serialized = ofile.read() serialized = ofile.read()
@ -203,6 +206,7 @@ class PydanticProvider(Provider):
if dump: if dump:
with open(import_file, 'w') as ofile: with open(import_file, 'w') as ofile:
ofile.write(serialized) ofile.write(serialized)
module_paths.append(import_file)
else: else:
with open(import_file, 'r') as ofile: with open(import_file, 'r') as ofile:
@ -210,6 +214,10 @@ class PydanticProvider(Provider):
res.append(serialized) res.append(serialized)
# make __init__.py files if we generated any files
if len(module_paths) > 0:
_ensure_inits(module_paths)
return res return res
def _make_inits(self, out_file: Path) -> None: def _make_inits(self, out_file: Path) -> None: