linkml 1.8.0rc2__py3-none-any.whl → 1.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- linkml/cli/__init__.py +0 -0
- linkml/cli/__main__.py +4 -0
- linkml/cli/main.py +126 -0
- linkml/generators/common/build.py +105 -0
- linkml/generators/common/lifecycle.py +124 -0
- linkml/generators/common/template.py +89 -0
- linkml/generators/csvgen.py +1 -1
- linkml/generators/docgen/slot.md.jinja2 +4 -0
- linkml/generators/docgen.py +1 -1
- linkml/generators/dotgen.py +1 -1
- linkml/generators/erdiagramgen.py +1 -1
- linkml/generators/excelgen.py +1 -1
- linkml/generators/golanggen.py +1 -1
- linkml/generators/golrgen.py +1 -1
- linkml/generators/graphqlgen.py +1 -1
- linkml/generators/javagen.py +1 -1
- linkml/generators/jsonldcontextgen.py +4 -4
- linkml/generators/jsonldgen.py +1 -1
- linkml/generators/jsonschemagen.py +80 -21
- linkml/generators/linkmlgen.py +1 -1
- linkml/generators/markdowngen.py +1 -1
- linkml/generators/namespacegen.py +1 -1
- linkml/generators/oocodegen.py +2 -1
- linkml/generators/owlgen.py +1 -1
- linkml/generators/plantumlgen.py +24 -7
- linkml/generators/prefixmapgen.py +1 -1
- linkml/generators/projectgen.py +1 -1
- linkml/generators/protogen.py +1 -1
- linkml/generators/pydanticgen/__init__.py +9 -3
- linkml/generators/pydanticgen/array.py +114 -194
- linkml/generators/pydanticgen/build.py +64 -25
- linkml/generators/pydanticgen/includes.py +4 -28
- linkml/generators/pydanticgen/pydanticgen.py +635 -283
- linkml/generators/pydanticgen/template.py +153 -180
- linkml/generators/pydanticgen/templates/attribute.py.jinja +9 -7
- linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -13
- linkml/generators/pydanticgen/templates/class.py.jinja +2 -2
- linkml/generators/pydanticgen/templates/footer.py.jinja +2 -10
- linkml/generators/pydanticgen/templates/module.py.jinja +3 -3
- linkml/generators/pydanticgen/templates/validator.py.jinja +0 -4
- linkml/generators/pythongen.py +12 -2
- linkml/generators/rdfgen.py +1 -1
- linkml/generators/shaclgen.py +6 -2
- linkml/generators/shexgen.py +1 -1
- linkml/generators/sparqlgen.py +1 -1
- linkml/generators/sqlalchemygen.py +1 -1
- linkml/generators/sqltablegen.py +1 -1
- linkml/generators/sssomgen.py +1 -1
- linkml/generators/summarygen.py +1 -1
- linkml/generators/terminusdbgen.py +7 -4
- linkml/generators/typescriptgen.py +1 -1
- linkml/generators/yamlgen.py +1 -1
- linkml/generators/yumlgen.py +1 -1
- linkml/linter/cli.py +1 -1
- linkml/transformers/logical_model_transformer.py +117 -18
- linkml/utils/converter.py +1 -1
- linkml/utils/execute_tutorial.py +2 -0
- linkml/utils/logictools.py +142 -29
- linkml/utils/schema_builder.py +7 -6
- linkml/utils/schema_fixer.py +1 -1
- linkml/utils/sqlutils.py +1 -1
- linkml/validator/cli.py +4 -1
- linkml/validators/jsonschemavalidator.py +1 -1
- linkml/validators/sparqlvalidator.py +1 -1
- linkml/workspaces/example_runner.py +1 -1
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/METADATA +2 -2
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/RECORD +70 -64
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/entry_points.txt +1 -1
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/LICENSE +0 -0
- {linkml-1.8.0rc2.dist-info → linkml-1.8.2.dist-info}/WHEEL +0 -0
@@ -1,24 +1,30 @@
|
|
1
1
|
import sys
|
2
2
|
from abc import ABC, abstractmethod
|
3
3
|
from enum import Enum
|
4
|
-
from typing import Any, ClassVar, Generic, Iterable,
|
4
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Iterable, Optional, Type, TypeVar, Union, get_args
|
5
5
|
|
6
6
|
from linkml_runtime.linkml_model import Element
|
7
7
|
from linkml_runtime.linkml_model.meta import ArrayExpression, DimensionExpression
|
8
8
|
from pydantic import VERSION as PYDANTIC_VERSION
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
from linkml.utils.deprecation import deprecation_warning
|
11
|
+
|
12
|
+
if int(PYDANTIC_VERSION[0]) >= 2:
|
13
|
+
from pydantic_core import core_schema
|
12
14
|
else:
|
15
|
+
# Support for having pydantic 1 installed in the same environment will be dropped in 1.9.0
|
16
|
+
deprecation_warning("pydantic-v1")
|
17
|
+
|
18
|
+
if TYPE_CHECKING:
|
13
19
|
from pydantic import GetCoreSchemaHandler
|
14
|
-
from pydantic_core import CoreSchema
|
20
|
+
from pydantic_core import CoreSchema
|
15
21
|
|
16
22
|
if sys.version_info.minor <= 8:
|
17
23
|
from typing_extensions import Annotated
|
18
24
|
else:
|
19
25
|
from typing import Annotated
|
20
26
|
|
21
|
-
from linkml.generators.pydanticgen.build import
|
27
|
+
from linkml.generators.pydanticgen.build import RangeResult
|
22
28
|
from linkml.generators.pydanticgen.template import ConditionalImport, Import, Imports, ObjectImport
|
23
29
|
|
24
30
|
|
@@ -31,153 +37,81 @@ _BOUNDED_ARRAY_FIELDS = ("exact_number_dimensions", "minimum_number_dimensions",
|
|
31
37
|
|
32
38
|
_T = TypeVar("_T")
|
33
39
|
_RecursiveListType = Iterable[Union[_T, Iterable["_RecursiveListType"]]]
|
34
|
-
if int(PYDANTIC_VERSION[0]) >= 2:
|
35
|
-
|
36
|
-
class AnyShapeArrayType(Generic[_T]):
|
37
|
-
@classmethod
|
38
|
-
def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
|
39
|
-
# double-nested parameterized types here
|
40
|
-
# source_type: List[Union[T,List[...]]]
|
41
|
-
item_type = Any if get_args(get_args(source_type)[0])[0] is _T else get_args(get_args(source_type)[0])[0]
|
42
40
|
|
43
|
-
item_schema = handler.generate_schema(item_type)
|
44
|
-
if item_schema.get("type", "any") != "any":
|
45
|
-
item_schema["strict"] = True
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
objects=[
|
77
|
-
ObjectImport(name="Generic"),
|
78
|
-
ObjectImport(name="Iterable"),
|
79
|
-
ObjectImport(name="TypeVar"),
|
80
|
-
ObjectImport(name="Union"),
|
81
|
-
ObjectImport(name="get_args"),
|
42
|
+
class AnyShapeArrayType(Generic[_T]):
|
43
|
+
@classmethod
|
44
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: "GetCoreSchemaHandler") -> "CoreSchema":
|
45
|
+
# double-nested parameterized types here
|
46
|
+
# source_type: List[Union[T,List[...]]]
|
47
|
+
item_type = Any if get_args(get_args(source_type)[0])[0] is _T else get_args(get_args(source_type)[0])[0]
|
48
|
+
|
49
|
+
item_schema = handler.generate_schema(item_type)
|
50
|
+
if item_schema.get("type", "any") != "any":
|
51
|
+
item_schema["strict"] = True
|
52
|
+
|
53
|
+
if item_type is Any:
|
54
|
+
# Before python 3.11, `Any` type was a special object without a __name__
|
55
|
+
item_name = "Any"
|
56
|
+
else:
|
57
|
+
item_name = item_type.__name__
|
58
|
+
|
59
|
+
array_ref = f"any-shape-array-{item_name}"
|
60
|
+
|
61
|
+
schema = core_schema.definitions_schema(
|
62
|
+
core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
|
63
|
+
[
|
64
|
+
core_schema.union_schema(
|
65
|
+
[
|
66
|
+
core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
|
67
|
+
item_schema,
|
68
|
+
],
|
69
|
+
ref=array_ref,
|
70
|
+
)
|
82
71
|
],
|
83
72
|
)
|
84
|
-
+ ConditionalImport(
|
85
|
-
condition="sys.version_info.minor > 8",
|
86
|
-
module="typing",
|
87
|
-
objects=[ObjectImport(name="Annotated")],
|
88
|
-
alternative=Import(module="typing_extensions", objects=[ObjectImport(name="Annotated")]),
|
89
|
-
)
|
90
|
-
+ Import(module="pydantic", objects=[ObjectImport(name="GetCoreSchemaHandler")])
|
91
|
-
+ Import(module="pydantic_core", objects=[ObjectImport(name="CoreSchema"), ObjectImport(name="core_schema")])
|
92
|
-
)
|
93
73
|
|
94
|
-
|
95
|
-
_AnyShapeArrayInjects = [
|
96
|
-
'_T = TypeVar("_T")',
|
97
|
-
'_RecursiveListType = Iterable[Union[_T, Iterable["_RecursiveListType"]]]',
|
98
|
-
AnyShapeArrayType,
|
99
|
-
"AnyShapeArray = Annotated[_RecursiveListType, AnyShapeArrayType]",
|
100
|
-
]
|
74
|
+
return schema
|
101
75
|
|
102
|
-
else:
|
103
76
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
alias.type_ = item
|
110
|
-
return alias
|
111
|
-
|
112
|
-
@classmethod
|
113
|
-
def __get_validators__(cls):
|
114
|
-
yield cls.validate
|
115
|
-
|
116
|
-
@classmethod
|
117
|
-
def __modify_schema__(cls, field_schema):
|
118
|
-
try:
|
119
|
-
item_type = field_schema["allOf"][0]["type"]
|
120
|
-
type_schema = {"type": item_type}
|
121
|
-
del field_schema["allOf"]
|
122
|
-
except KeyError as e:
|
123
|
-
if "allOf" in str(e):
|
124
|
-
item_type = "Any"
|
125
|
-
type_schema = {}
|
126
|
-
else:
|
127
|
-
raise e
|
128
|
-
|
129
|
-
array_id = f"#any-shape-array-{item_type}"
|
130
|
-
field_schema["anyOf"] = [
|
131
|
-
type_schema,
|
132
|
-
{"type": "array", "items": {"$ref": array_id}},
|
133
|
-
]
|
134
|
-
field_schema["$id"] = array_id
|
135
|
-
|
136
|
-
@classmethod
|
137
|
-
def validate(cls, v: Union[List[_T], list]):
|
138
|
-
if str(type(v)) == "<class 'numpy.ndarray'>":
|
139
|
-
v = v.tolist()
|
140
|
-
|
141
|
-
if not isinstance(v, list):
|
142
|
-
raise TypeError(f"Must be a list of lists! got {v}")
|
143
|
-
|
144
|
-
def _validate(_v: Union[List[_T], list]):
|
145
|
-
for item in _v:
|
146
|
-
if isinstance(item, list):
|
147
|
-
_validate(item)
|
148
|
-
else:
|
149
|
-
try:
|
150
|
-
anytype = cls.type_.__name__ in ("AnyType", "Any")
|
151
|
-
except AttributeError:
|
152
|
-
# in python 3.8 and 3.9, `typing.Any` has no __name__
|
153
|
-
anytype = str(cls.type_).split(".")[-1] in ("AnyType", "Any")
|
154
|
-
|
155
|
-
if not anytype and not isinstance(item, cls.type_):
|
156
|
-
raise TypeError(
|
157
|
-
(
|
158
|
-
f"List items must be list of lists, or the type used in "
|
159
|
-
f"the subscript ({cls.type_}. Got item {item} and outer value {v}"
|
160
|
-
)
|
161
|
-
)
|
162
|
-
return _v
|
163
|
-
|
164
|
-
return _validate(v)
|
165
|
-
|
166
|
-
_AnyShapeArrayImports = Imports() + Import(
|
77
|
+
AnyShapeArray = Annotated[_RecursiveListType, AnyShapeArrayType]
|
78
|
+
|
79
|
+
_AnyShapeArrayImports = (
|
80
|
+
Imports()
|
81
|
+
+ Import(
|
167
82
|
module="typing",
|
168
|
-
objects=[
|
83
|
+
objects=[
|
84
|
+
ObjectImport(name="Generic"),
|
85
|
+
ObjectImport(name="Iterable"),
|
86
|
+
ObjectImport(name="TypeVar"),
|
87
|
+
ObjectImport(name="Union"),
|
88
|
+
ObjectImport(name="get_args"),
|
89
|
+
],
|
169
90
|
)
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
91
|
+
+ ConditionalImport(
|
92
|
+
condition="sys.version_info.minor > 8",
|
93
|
+
module="typing",
|
94
|
+
objects=[ObjectImport(name="Annotated")],
|
95
|
+
alternative=Import(module="typing_extensions", objects=[ObjectImport(name="Annotated")]),
|
96
|
+
)
|
97
|
+
+ Import(module="pydantic", objects=[ObjectImport(name="GetCoreSchemaHandler")])
|
98
|
+
+ Import(module="pydantic_core", objects=[ObjectImport(name="CoreSchema"), ObjectImport(name="core_schema")])
|
99
|
+
)
|
100
|
+
|
101
|
+
# annotated types are special and inspect.getsource() can't stringify them
|
102
|
+
_AnyShapeArrayInjects = [
|
103
|
+
'_T = TypeVar("_T")',
|
104
|
+
'_RecursiveListType = Iterable[Union[_T, Iterable["_RecursiveListType"]]]',
|
105
|
+
AnyShapeArrayType,
|
106
|
+
"AnyShapeArray = Annotated[_RecursiveListType, AnyShapeArrayType]",
|
107
|
+
]
|
174
108
|
|
175
109
|
_ConListImports = Imports() + Import(module="pydantic", objects=[ObjectImport(name="conlist")])
|
176
110
|
|
177
111
|
|
178
112
|
class ArrayRangeGenerator(ABC):
|
179
113
|
"""
|
180
|
-
Metaclass for generating a given format of array
|
114
|
+
Metaclass for generating a given format of array range.
|
181
115
|
|
182
116
|
See :ref:`array-forms` for more details on array range forms.
|
183
117
|
|
@@ -196,23 +130,18 @@ class ArrayRangeGenerator(ABC):
|
|
196
130
|
(unless that's what you intend to do ofc ;)
|
197
131
|
|
198
132
|
Attributes:
|
199
|
-
array (:class:`.ArrayExpression` ): Array to create
|
133
|
+
array (:class:`.ArrayExpression` ): Array to create a range for
|
200
134
|
dtype (Union[str, :class:`.Element` ): dtype of the entire array as a string
|
201
|
-
pydantic_ver (str): Pydantic version to generate array form for -
|
202
|
-
currently only pydantic 1 and 2 are differentiated, and pydantic 1 will be deprecated soon.
|
203
135
|
|
204
136
|
"""
|
205
137
|
|
206
138
|
REPR: ClassVar[ArrayRepresentation]
|
207
139
|
|
208
|
-
def __init__(
|
209
|
-
self, array: Optional[ArrayExpression], dtype: Union[str, Element], pydantic_ver: str = PYDANTIC_VERSION
|
210
|
-
):
|
140
|
+
def __init__(self, array: Optional[ArrayExpression], dtype: Union[str, Element]):
|
211
141
|
self.array = array
|
212
142
|
self.dtype = dtype
|
213
|
-
self.pydantic_ver = pydantic_ver
|
214
143
|
|
215
|
-
def make(self) ->
|
144
|
+
def make(self) -> RangeResult:
|
216
145
|
"""Create the string form of the array representation"""
|
217
146
|
if not self.array.dimensions and not self.has_bounded_dimensions:
|
218
147
|
# any-shaped array
|
@@ -238,22 +167,22 @@ class ArrayRangeGenerator(ABC):
|
|
238
167
|
raise ValueError(f"Generator for array representation {repr} not found!")
|
239
168
|
|
240
169
|
@abstractmethod
|
241
|
-
def any_shape(self, array: Optional[ArrayRepresentation] = None) ->
|
170
|
+
def any_shape(self, array: Optional[ArrayRepresentation] = None) -> RangeResult:
|
242
171
|
"""Any shaped array!"""
|
243
172
|
pass
|
244
173
|
|
245
174
|
@abstractmethod
|
246
|
-
def bounded_dimensions(self, array: ArrayExpression) ->
|
175
|
+
def bounded_dimensions(self, array: ArrayExpression) -> RangeResult:
|
247
176
|
"""Array shape specified numerically, without axis parameterization"""
|
248
177
|
pass
|
249
178
|
|
250
179
|
@abstractmethod
|
251
|
-
def parameterized_dimensions(self, array: ArrayExpression) ->
|
180
|
+
def parameterized_dimensions(self, array: ArrayExpression) -> RangeResult:
|
252
181
|
"""Array shape specified with ``dimensions`` without additional parameterized dimensions"""
|
253
182
|
pass
|
254
183
|
|
255
184
|
@abstractmethod
|
256
|
-
def complex_dimensions(self, array: ArrayExpression) ->
|
185
|
+
def complex_dimensions(self, array: ArrayExpression) -> RangeResult:
|
257
186
|
"""Array shape with both ``parameterized`` and ``bounded`` dimensions"""
|
258
187
|
pass
|
259
188
|
|
@@ -273,7 +202,7 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
273
202
|
return ("List[" * dimensions) + dtype + ("]" * dimensions)
|
274
203
|
|
275
204
|
@staticmethod
|
276
|
-
def _parameterized_dimension(dimension: DimensionExpression, dtype: str) ->
|
205
|
+
def _parameterized_dimension(dimension: DimensionExpression, dtype: str) -> RangeResult:
|
277
206
|
# TODO: Preserve label representation in some readable way! doing the MVP now of using conlist
|
278
207
|
if dimension.exact_cardinality and (dimension.minimum_cardinality or dimension.maximum_cardinality):
|
279
208
|
raise ValueError("Can only specify EITHER exact_cardinality OR minimum/maximum cardinality")
|
@@ -285,26 +214,21 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
285
214
|
dmax = dimension.maximum_cardinality
|
286
215
|
else:
|
287
216
|
# TODO: handle labels for labeled but unshaped arrays
|
288
|
-
return
|
217
|
+
return RangeResult(range="List[" + dtype + "]")
|
289
218
|
|
290
219
|
items = []
|
291
|
-
if
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
else:
|
297
|
-
if dmin is not None:
|
298
|
-
items.append(f"min_items={dmin}")
|
299
|
-
if dmax is not None:
|
300
|
-
items.append(f"max_items={dmax}")
|
220
|
+
if dmin is not None:
|
221
|
+
items.append(f"min_length={dmin}")
|
222
|
+
if dmax is not None:
|
223
|
+
items.append(f"max_length={dmax}")
|
224
|
+
|
301
225
|
items.append(f"item_type={dtype}")
|
302
226
|
items = ", ".join(items)
|
303
|
-
|
227
|
+
range = f"conlist({items})"
|
304
228
|
|
305
|
-
return
|
229
|
+
return RangeResult(range=range, imports=_ConListImports)
|
306
230
|
|
307
|
-
def any_shape(self, array: Optional[ArrayExpression] = None, with_inner_union: bool = False) ->
|
231
|
+
def any_shape(self, array: Optional[ArrayExpression] = None, with_inner_union: bool = False) -> RangeResult:
|
308
232
|
"""
|
309
233
|
An AnyShaped array (using :class:`.AnyShapeArray` )
|
310
234
|
|
@@ -315,17 +239,17 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
315
239
|
|
316
240
|
"""
|
317
241
|
if self.dtype in ("Any", "AnyType"):
|
318
|
-
|
242
|
+
range = "AnyShapeArray"
|
319
243
|
else:
|
320
|
-
|
244
|
+
range = f"AnyShapeArray[{self.dtype}]"
|
321
245
|
|
322
246
|
if with_inner_union:
|
323
|
-
|
324
|
-
return
|
247
|
+
range = f"Union[{range}, {self.dtype}]"
|
248
|
+
return RangeResult(range=range, injected_classes=_AnyShapeArrayInjects, imports=_AnyShapeArrayImports)
|
325
249
|
|
326
|
-
def bounded_dimensions(self, array: ArrayExpression) ->
|
250
|
+
def bounded_dimensions(self, array: ArrayExpression) -> RangeResult:
|
327
251
|
"""
|
328
|
-
A nested series of ``List[]``
|
252
|
+
A nested series of ``List[]`` ranges with :attr:`.dtype` at the center.
|
329
253
|
|
330
254
|
When an array expression allows for a range of dimensions, each set of ``List`` s is joined by a ``Union`` .
|
331
255
|
"""
|
@@ -335,29 +259,27 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
335
259
|
and array.minimum_number_dimensions == array.maximum_number_dimensions
|
336
260
|
):
|
337
261
|
exact_dims = array.exact_number_dimensions or array.minimum_number_dimensions
|
338
|
-
return
|
262
|
+
return RangeResult(range=self._list_of_lists(exact_dims, self.dtype))
|
339
263
|
elif not array.maximum_number_dimensions and (
|
340
264
|
array.minimum_number_dimensions is None or array.minimum_number_dimensions == 1
|
341
265
|
):
|
342
266
|
return self.any_shape()
|
343
267
|
elif array.maximum_number_dimensions:
|
344
|
-
# e.g., if min = 2, max = 3,
|
268
|
+
# e.g., if min = 2, max = 3, range = Union[List[List[dtype]], List[List[List[dtype]]]]
|
345
269
|
min_dims = array.minimum_number_dimensions if array.minimum_number_dimensions is not None else 1
|
346
|
-
|
347
|
-
self._list_of_lists(i, self.dtype) for i in range(min_dims, array.maximum_number_dimensions + 1)
|
348
|
-
]
|
270
|
+
ranges = [self._list_of_lists(i, self.dtype) for i in range(min_dims, array.maximum_number_dimensions + 1)]
|
349
271
|
# TODO: Format this nicely!
|
350
|
-
return
|
272
|
+
return RangeResult(range="Union[" + ", ".join(ranges) + "]")
|
351
273
|
else:
|
352
274
|
# min specified with no max
|
353
|
-
# e.g., if min = 3,
|
354
|
-
return
|
355
|
-
|
275
|
+
# e.g., if min = 3, range = List[List[AnyShapeArray[dtype]]]
|
276
|
+
return RangeResult(
|
277
|
+
range=self._list_of_lists(array.minimum_number_dimensions - 1, self.any_shape().range),
|
356
278
|
injected_classes=_AnyShapeArrayInjects,
|
357
279
|
imports=_AnyShapeArrayImports,
|
358
280
|
)
|
359
281
|
|
360
|
-
def parameterized_dimensions(self, array: ArrayExpression) ->
|
282
|
+
def parameterized_dimensions(self, array: ArrayExpression) -> RangeResult:
|
361
283
|
"""
|
362
284
|
Constrained shapes using :func:`pydantic.conlist`
|
363
285
|
|
@@ -367,20 +289,20 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
367
289
|
"""
|
368
290
|
# generate dimensions from inside out and then format
|
369
291
|
# e.g., if dimensions = [{min_card: 3}, {min_card: 2}],
|
370
|
-
#
|
292
|
+
# range = conlist(min_length=3, item_type=conlist(min_length=2, item_type=dtype))
|
371
293
|
range = self.dtype
|
372
294
|
for dimension in reversed(array.dimensions):
|
373
|
-
range = self._parameterized_dimension(dimension, range).
|
295
|
+
range = self._parameterized_dimension(dimension, range).range
|
374
296
|
|
375
|
-
return
|
297
|
+
return RangeResult(range=range, imports=_ConListImports)
|
376
298
|
|
377
|
-
def complex_dimensions(self, array: ArrayExpression) ->
|
299
|
+
def complex_dimensions(self, array: ArrayExpression) -> RangeResult:
|
378
300
|
"""
|
379
301
|
Mixture of parameterized dimensions with a max or min (or both) shape for anonymous dimensions.
|
380
302
|
|
381
303
|
A mixture of ``List`` , :class:`.conlist` , and :class:`.AnyShapeArray` .
|
382
304
|
"""
|
383
|
-
# first process any unlabeled dimensions which must be the innermost level of the
|
305
|
+
# first process any unlabeled dimensions which must be the innermost level of the range,
|
384
306
|
# then wrap that with labeled dimensions
|
385
307
|
if array.exact_number_dimensions or (
|
386
308
|
array.minimum_number_dimensions
|
@@ -389,7 +311,7 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
389
311
|
):
|
390
312
|
exact_dims = array.exact_number_dimensions or array.minimum_number_dimensions
|
391
313
|
if exact_dims > len(array.dimensions):
|
392
|
-
res =
|
314
|
+
res = RangeResult(range=self._list_of_lists(exact_dims - len(array.dimensions), self.dtype))
|
393
315
|
elif exact_dims == len(array.dimensions):
|
394
316
|
# equivalent to labeled shape
|
395
317
|
return self.parameterized_dimensions(array)
|
@@ -404,11 +326,9 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
404
326
|
|
405
327
|
if array.minimum_number_dimensions and array.minimum_number_dimensions > len(array.dimensions):
|
406
328
|
# some minimum anonymous dimensions but unlimited max dimensions
|
407
|
-
# e.g., if min = 3, len(dim) = 2, then res.
|
408
|
-
# res.
|
409
|
-
res.
|
410
|
-
array.minimum_number_dimensions - len(array.dimensions), res.annotation
|
411
|
-
)
|
329
|
+
# e.g., if min = 3, len(dim) = 2, then res.range = List[Union[AnyShapeArray[dtype], dtype]]
|
330
|
+
# res.range will be wrapped with the 2 labeled dimensions later
|
331
|
+
res.range = self._list_of_lists(array.minimum_number_dimensions - len(array.dimensions), res.range)
|
412
332
|
|
413
333
|
elif array.minimum_number_dimensions and array.maximum_number_dimensions is None:
|
414
334
|
raise ValueError(
|
@@ -431,9 +351,9 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
431
351
|
|
432
352
|
# Wrap inner dimension with labeled dimension
|
433
353
|
# e.g., if dimensions = [{min_card: 3}, {min_card: 2}]
|
434
|
-
# and res.
|
354
|
+
# and res.range = List[Union[AnyShapeArray[dtype], dtype]]
|
435
355
|
# (min 3 dims, no max dims)
|
436
|
-
# then the final
|
356
|
+
# then the final range = conlist(
|
437
357
|
# min_length=3,
|
438
358
|
# item_type=conlist(
|
439
359
|
# min_length=2,
|
@@ -441,7 +361,7 @@ class ListOfListsArray(ArrayRangeGenerator):
|
|
441
361
|
# )
|
442
362
|
# )
|
443
363
|
for dim in reversed(array.dimensions):
|
444
|
-
res = res.merge(self._parameterized_dimension(dim, dtype=res.
|
364
|
+
res = res.merge(self._parameterized_dimension(dim, dtype=res.range))
|
445
365
|
|
446
366
|
return res
|
447
367
|
|
@@ -1,20 +1,27 @@
|
|
1
|
+
from pathlib import Path
|
1
2
|
from typing import List, Optional, Type, TypeVar, Union
|
2
3
|
|
3
|
-
from
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
from linkml.generators.common.build import (
|
5
|
+
BuildResult,
|
6
|
+
SchemaResult,
|
7
|
+
)
|
8
|
+
from linkml.generators.common.build import (
|
9
|
+
ClassResult as ClassResult_,
|
10
|
+
)
|
11
|
+
from linkml.generators.common.build import (
|
12
|
+
RangeResult as RangeResult_,
|
13
|
+
)
|
14
|
+
from linkml.generators.common.build import (
|
15
|
+
SlotResult as SlotResult_,
|
16
|
+
)
|
17
|
+
from linkml.generators.pydanticgen.template import Import, Imports, PydanticAttribute, PydanticClass
|
18
|
+
|
19
|
+
T = TypeVar("T", bound="PydanticBuildResult", covariant=True)
|
20
|
+
|
21
|
+
|
22
|
+
class PydanticBuildResult(BuildResult):
|
11
23
|
"""
|
12
|
-
|
13
|
-
|
14
|
-
BuildResults are merged in the serialization process, and are used
|
15
|
-
to keep track of not only the particular representation
|
16
|
-
of the thing in question, but any "side effects" that need to happen
|
17
|
-
elsewhere in the generation process (like adding imports, injecting classes, etc.)
|
24
|
+
BuildResult parent class for pydantic generator
|
18
25
|
"""
|
19
26
|
|
20
27
|
imports: Optional[Union[List[Import], Imports]] = None
|
@@ -27,39 +34,53 @@ class BuildResult(BaseModel):
|
|
27
34
|
- Merges imports with :meth:`.Imports.__add__`
|
28
35
|
- Extends (with simple deduplication) injected classes
|
29
36
|
|
37
|
+
The top-level `merge` method is intended to just merge the "extra" parts of a result
|
38
|
+
- ie. propagating the imports and injected classes up from 'lower' level results.
|
39
|
+
It should *not* be used as a general "merge" method to eg. merge the results of two
|
40
|
+
slot ranges into a Union of those ranges, etc. Either override this method (preserving
|
41
|
+
the base behavior) or put that kind of merging logic into the generator.
|
42
|
+
|
30
43
|
.. note::
|
31
44
|
|
32
45
|
This returns a (shallow) copy of ``self``, so subclasses don't need to make additional copies.
|
33
46
|
|
34
47
|
Args:
|
35
|
-
other (:class:`.
|
48
|
+
other (:class:`.PydanticBuildResult`): A subclass of PydanticBuildResult,
|
49
|
+
generic over whatever we have passed.
|
36
50
|
|
37
51
|
Returns:
|
38
|
-
:class:`.
|
52
|
+
:class:`.PydanticBuildResult`
|
39
53
|
"""
|
40
|
-
self_copy = self.
|
54
|
+
self_copy = self.model_copy()
|
41
55
|
if other.imports:
|
42
56
|
if self.imports is not None:
|
43
57
|
self_copy.imports += other.imports
|
44
58
|
else:
|
45
59
|
self_copy.imports = other.imports
|
46
60
|
if other.injected_classes:
|
47
|
-
self_copy.injected_classes
|
48
|
-
|
61
|
+
if self_copy.injected_classes is not None:
|
62
|
+
self_copy.injected_classes.extend(other.injected_classes)
|
63
|
+
self_copy.injected_classes = list(dict.fromkeys(self_copy.injected_classes))
|
64
|
+
else:
|
65
|
+
self_copy.injected_classes = other.injected_classes
|
49
66
|
return self_copy
|
50
67
|
|
51
68
|
|
52
|
-
class
|
53
|
-
|
69
|
+
class RangeResult(PydanticBuildResult, RangeResult_):
|
70
|
+
"""
|
71
|
+
The result of building just the range part of a slot
|
72
|
+
"""
|
73
|
+
|
74
|
+
range: str
|
54
75
|
"""The type annotation used in the generated model"""
|
55
76
|
field_extras: Optional[dict] = None
|
56
77
|
"""Additional metadata for this slot to be held in the Field object"""
|
57
78
|
|
58
|
-
def merge(self, other: "
|
79
|
+
def merge(self, other: "RangeResult") -> "RangeResult":
|
59
80
|
"""
|
60
81
|
Merge two SlotResults...
|
61
82
|
|
62
|
-
- calling :meth:`.
|
83
|
+
- calling :meth:`.PydanticBuildResult.merge`
|
63
84
|
- replacing the existing annotation with that given by ``other`` .
|
64
85
|
- updating any ``field_extras`` with the other
|
65
86
|
|
@@ -69,11 +90,29 @@ class SlotResult(BuildResult):
|
|
69
90
|
Returns:
|
70
91
|
:class:`.SlotResult`
|
71
92
|
"""
|
72
|
-
res = super(
|
93
|
+
res = super(RangeResult, self).merge(other)
|
73
94
|
# Replace with other's annotation
|
74
|
-
res.
|
95
|
+
res.range = other.range
|
75
96
|
if other.field_extras is not None:
|
76
97
|
if res.field_extras is None:
|
77
98
|
res.field_extras = {}
|
78
99
|
res.field_extras.update(other.field_extras)
|
79
100
|
return res
|
101
|
+
|
102
|
+
|
103
|
+
class SlotResult(PydanticBuildResult, SlotResult_):
|
104
|
+
attribute: PydanticAttribute
|
105
|
+
|
106
|
+
|
107
|
+
class ClassResult(PydanticBuildResult, ClassResult_):
|
108
|
+
cls: PydanticClass
|
109
|
+
"""Constructed Template Model for class, including attributes/slots"""
|
110
|
+
|
111
|
+
|
112
|
+
class SplitResult(SchemaResult):
|
113
|
+
"""Build result when generating with :func:`.generate_split`"""
|
114
|
+
|
115
|
+
main: bool = False
|
116
|
+
path: Path
|
117
|
+
serialized_module: str
|
118
|
+
module_import: Optional[str] = None
|
@@ -2,29 +2,7 @@
|
|
2
2
|
Classes to inject in generated pydantic models
|
3
3
|
"""
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
PYDANTIC_VERSION = int(VERSION[0])
|
8
|
-
|
9
|
-
|
10
|
-
LinkMLMeta_v1 = """
|
11
|
-
class LinkMLMeta(BaseModel):
|
12
|
-
__root__: Dict[str, Any] = {}
|
13
|
-
|
14
|
-
def __getattr__(self, key:str):
|
15
|
-
return getattr(self.__root__, key)
|
16
|
-
|
17
|
-
def __getitem__(self, key:str):
|
18
|
-
return self.__root__[key]
|
19
|
-
|
20
|
-
def __setitem__(self, key:str, value):
|
21
|
-
self.__root__[key] = value
|
22
|
-
|
23
|
-
class Config:
|
24
|
-
allow_mutation = False
|
25
|
-
"""
|
26
|
-
|
27
|
-
LinkMLMeta_v2 = """
|
5
|
+
LinkMLMeta = """
|
28
6
|
class LinkMLMeta(RootModel):
|
29
7
|
root: Dict[str, Any] = {}
|
30
8
|
model_config = ConfigDict(frozen=True)
|
@@ -37,10 +15,8 @@ class LinkMLMeta(RootModel):
|
|
37
15
|
|
38
16
|
def __setitem__(self, key:str, value):
|
39
17
|
self.root[key] = value
|
18
|
+
|
19
|
+
def __contains__(self, key:str) -> bool:
|
20
|
+
return key in self.root
|
40
21
|
|
41
22
|
"""
|
42
|
-
|
43
|
-
# if PYDANTIC_VERSION >= 2:
|
44
|
-
# LinkMLMeta = eval(LinkMLMeta_v2)
|
45
|
-
# else:
|
46
|
-
# LinkMLMeta = eval(LinkMLMeta_v1)
|