linkml 1.8.1__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.
Files changed (70) hide show
  1. linkml/cli/__init__.py +0 -0
  2. linkml/cli/__main__.py +4 -0
  3. linkml/cli/main.py +126 -0
  4. linkml/generators/common/build.py +105 -0
  5. linkml/generators/common/lifecycle.py +124 -0
  6. linkml/generators/common/template.py +89 -0
  7. linkml/generators/csvgen.py +1 -1
  8. linkml/generators/docgen/slot.md.jinja2 +4 -0
  9. linkml/generators/docgen.py +1 -1
  10. linkml/generators/dotgen.py +1 -1
  11. linkml/generators/erdiagramgen.py +1 -1
  12. linkml/generators/excelgen.py +1 -1
  13. linkml/generators/golanggen.py +1 -1
  14. linkml/generators/golrgen.py +1 -1
  15. linkml/generators/graphqlgen.py +1 -1
  16. linkml/generators/javagen.py +1 -1
  17. linkml/generators/jsonldcontextgen.py +4 -4
  18. linkml/generators/jsonldgen.py +1 -1
  19. linkml/generators/jsonschemagen.py +69 -22
  20. linkml/generators/linkmlgen.py +1 -1
  21. linkml/generators/markdowngen.py +1 -1
  22. linkml/generators/namespacegen.py +1 -1
  23. linkml/generators/oocodegen.py +2 -1
  24. linkml/generators/owlgen.py +1 -1
  25. linkml/generators/plantumlgen.py +1 -1
  26. linkml/generators/prefixmapgen.py +1 -1
  27. linkml/generators/projectgen.py +1 -1
  28. linkml/generators/protogen.py +1 -1
  29. linkml/generators/pydanticgen/__init__.py +8 -3
  30. linkml/generators/pydanticgen/array.py +114 -194
  31. linkml/generators/pydanticgen/build.py +64 -25
  32. linkml/generators/pydanticgen/includes.py +1 -31
  33. linkml/generators/pydanticgen/pydanticgen.py +616 -274
  34. linkml/generators/pydanticgen/template.py +152 -184
  35. linkml/generators/pydanticgen/templates/attribute.py.jinja +9 -7
  36. linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -13
  37. linkml/generators/pydanticgen/templates/class.py.jinja +2 -2
  38. linkml/generators/pydanticgen/templates/footer.py.jinja +2 -10
  39. linkml/generators/pydanticgen/templates/module.py.jinja +2 -2
  40. linkml/generators/pydanticgen/templates/validator.py.jinja +0 -4
  41. linkml/generators/pythongen.py +12 -2
  42. linkml/generators/rdfgen.py +1 -1
  43. linkml/generators/shaclgen.py +6 -2
  44. linkml/generators/shexgen.py +1 -1
  45. linkml/generators/sparqlgen.py +1 -1
  46. linkml/generators/sqlalchemygen.py +1 -1
  47. linkml/generators/sqltablegen.py +1 -1
  48. linkml/generators/sssomgen.py +1 -1
  49. linkml/generators/summarygen.py +1 -1
  50. linkml/generators/terminusdbgen.py +7 -4
  51. linkml/generators/typescriptgen.py +1 -1
  52. linkml/generators/yamlgen.py +1 -1
  53. linkml/generators/yumlgen.py +1 -1
  54. linkml/linter/cli.py +1 -1
  55. linkml/transformers/logical_model_transformer.py +117 -18
  56. linkml/utils/converter.py +1 -1
  57. linkml/utils/execute_tutorial.py +2 -0
  58. linkml/utils/logictools.py +142 -29
  59. linkml/utils/schema_builder.py +7 -6
  60. linkml/utils/schema_fixer.py +1 -1
  61. linkml/utils/sqlutils.py +1 -1
  62. linkml/validator/cli.py +4 -1
  63. linkml/validators/jsonschemavalidator.py +1 -1
  64. linkml/validators/sparqlvalidator.py +1 -1
  65. linkml/workspaces/example_runner.py +1 -1
  66. {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/METADATA +2 -2
  67. {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/RECORD +70 -64
  68. {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/entry_points.txt +1 -1
  69. {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/LICENSE +0 -0
  70. {linkml-1.8.1.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, List, Optional, Type, TypeVar, Union, get_args
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
- if int(PYDANTIC_VERSION[0]) < 2:
11
- pass
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, core_schema
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 SlotResult
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
- if item_type is Any:
48
- # Before python 3.11, `Any` type was a special object without a __name__
49
- item_name = "Any"
50
- else:
51
- item_name = item_type.__name__
52
-
53
- array_ref = f"any-shape-array-{item_name}"
54
-
55
- schema = core_schema.definitions_schema(
56
- core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
57
- [
58
- core_schema.union_schema(
59
- [
60
- core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
61
- item_schema,
62
- ],
63
- ref=array_ref,
64
- )
65
- ],
66
- )
67
-
68
- return schema
69
-
70
- AnyShapeArray = Annotated[_RecursiveListType, AnyShapeArrayType]
71
-
72
- _AnyShapeArrayImports = (
73
- Imports()
74
- + Import(
75
- module="typing",
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
- # annotated types are special and inspect.getsource() can't stringify them
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
- class AnyShapeArray(Generic[_T]):
105
- type_: Type[Any] = Any
106
-
107
- def __class_getitem__(cls, item):
108
- alias = type(f"AnyShape_{str(item.__name__)}", (AnyShapeArray,), {"type_": item})
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=[ObjectImport(name="Generic"), ObjectImport(name="TypeVar"), ObjectImport(name="_GenericAlias")],
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
- _AnyShapeArrayInjects = [
171
- '_T = TypeVar("_T")',
172
- AnyShapeArray,
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 annotation.
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 an annotation for
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) -> SlotResult:
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) -> SlotResult:
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) -> SlotResult:
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) -> SlotResult:
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) -> SlotResult:
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) -> SlotResult:
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 SlotResult(annotation="List[" + dtype + "]")
217
+ return RangeResult(range="List[" + dtype + "]")
289
218
 
290
219
  items = []
291
- if int(PYDANTIC_VERSION[0]) >= 2:
292
- if dmin is not None:
293
- items.append(f"min_length={dmin}")
294
- if dmax is not None:
295
- items.append(f"max_length={dmax}")
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
- annotation = f"conlist({items})"
227
+ range = f"conlist({items})"
304
228
 
305
- return SlotResult(annotation=annotation, imports=_ConListImports)
229
+ return RangeResult(range=range, imports=_ConListImports)
306
230
 
307
- def any_shape(self, array: Optional[ArrayExpression] = None, with_inner_union: bool = False) -> SlotResult:
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
- annotation = "AnyShapeArray"
242
+ range = "AnyShapeArray"
319
243
  else:
320
- annotation = f"AnyShapeArray[{self.dtype}]"
244
+ range = f"AnyShapeArray[{self.dtype}]"
321
245
 
322
246
  if with_inner_union:
323
- annotation = f"Union[{annotation}, {self.dtype}]"
324
- return SlotResult(annotation=annotation, injected_classes=_AnyShapeArrayInjects, imports=_AnyShapeArrayImports)
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) -> SlotResult:
250
+ def bounded_dimensions(self, array: ArrayExpression) -> RangeResult:
327
251
  """
328
- A nested series of ``List[]`` annotations with :attr:`.dtype` at the center.
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 SlotResult(annotation=self._list_of_lists(exact_dims, self.dtype))
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, annotation = Union[List[List[dtype]], List[List[List[dtype]]]]
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
- annotations = [
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 SlotResult(annotation="Union[" + ", ".join(annotations) + "]")
272
+ return RangeResult(range="Union[" + ", ".join(ranges) + "]")
351
273
  else:
352
274
  # min specified with no max
353
- # e.g., if min = 3, annotation = List[List[AnyShapeArray[dtype]]]
354
- return SlotResult(
355
- annotation=self._list_of_lists(array.minimum_number_dimensions - 1, self.any_shape().annotation),
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) -> SlotResult:
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
- # annotation = conlist(min_length=3, item_type=conlist(min_length=2, item_type=dtype))
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).annotation
295
+ range = self._parameterized_dimension(dimension, range).range
374
296
 
375
- return SlotResult(annotation=range, imports=_ConListImports)
297
+ return RangeResult(range=range, imports=_ConListImports)
376
298
 
377
- def complex_dimensions(self, array: ArrayExpression) -> SlotResult:
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 annotation,
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 = SlotResult(annotation=self._list_of_lists(exact_dims - len(array.dimensions), self.dtype))
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.annotation = List[Union[AnyShapeArray[dtype], dtype]]
408
- # res.annotation will be wrapped with the 2 labeled dimensions later
409
- res.annotation = self._list_of_lists(
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.annotation = List[Union[AnyShapeArray[dtype], dtype]]
354
+ # and res.range = List[Union[AnyShapeArray[dtype], dtype]]
435
355
  # (min 3 dims, no max dims)
436
- # then the final annotation = conlist(
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.annotation))
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 pydantic import BaseModel
4
-
5
- from linkml.generators.pydanticgen.template import Import, Imports
6
-
7
- T = TypeVar("T", bound="BuildResult", covariant=True)
8
-
9
-
10
- class BuildResult(BaseModel):
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
- The result of any build phase for any linkML object
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:`.BuildResult`): A subclass of BuildResult, generic over whatever we have passed.
48
+ other (:class:`.PydanticBuildResult`): A subclass of PydanticBuildResult,
49
+ generic over whatever we have passed.
36
50
 
37
51
  Returns:
38
- :class:`.BuildResult`
52
+ :class:`.PydanticBuildResult`
39
53
  """
40
- self_copy = self.copy()
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.extend(other.injected_classes)
48
- self_copy.injected_classes = list(dict.fromkeys(self_copy.injected_classes))
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 SlotResult(BuildResult):
53
- annotation: str
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: "SlotResult") -> "SlotResult":
79
+ def merge(self, other: "RangeResult") -> "RangeResult":
59
80
  """
60
81
  Merge two SlotResults...
61
82
 
62
- - calling :meth:`.BuildResult.merge`
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(SlotResult, self).merge(other)
93
+ res = super(RangeResult, self).merge(other)
73
94
  # Replace with other's annotation
74
- res.annotation = other.annotation
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,32 +2,7 @@
2
2
  Classes to inject in generated pydantic models
3
3
  """
4
4
 
5
- from pydantic.version import VERSION
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
- def __contains__(self, key:str) -> bool:
24
- return key in self.__root__
25
-
26
- class Config:
27
- allow_mutation = False
28
- """
29
-
30
- LinkMLMeta_v2 = """
5
+ LinkMLMeta = """
31
6
  class LinkMLMeta(RootModel):
32
7
  root: Dict[str, Any] = {}
33
8
  model_config = ConfigDict(frozen=True)
@@ -45,8 +20,3 @@ class LinkMLMeta(RootModel):
45
20
  return key in self.root
46
21
 
47
22
  """
48
-
49
- # if PYDANTIC_VERSION >= 2:
50
- # LinkMLMeta = eval(LinkMLMeta_v2)
51
- # else:
52
- # LinkMLMeta = eval(LinkMLMeta_v1)