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,11 +1,13 @@
|
|
1
|
-
from copy import copy
|
2
1
|
from importlib.util import find_spec
|
3
|
-
from typing import Any, ClassVar, Dict, Generator, List, Literal, Optional, Union
|
2
|
+
from typing import Any, ClassVar, Dict, Generator, List, Literal, Optional, Union
|
4
3
|
|
5
4
|
from jinja2 import Environment, PackageLoader
|
6
|
-
from pydantic import BaseModel, Field
|
5
|
+
from pydantic import BaseModel, Field, field_validator
|
7
6
|
from pydantic.version import VERSION as PYDANTIC_VERSION
|
8
7
|
|
8
|
+
from linkml.generators.common.template import TemplateModel
|
9
|
+
from linkml.utils.deprecation import deprecation_warning
|
10
|
+
|
9
11
|
try:
|
10
12
|
if find_spec("black") is not None:
|
11
13
|
from linkml.generators.pydanticgen.black import format_black
|
@@ -19,10 +21,14 @@ except ImportError:
|
|
19
21
|
if int(PYDANTIC_VERSION[0]) >= 2:
|
20
22
|
from pydantic import computed_field
|
21
23
|
else:
|
22
|
-
|
24
|
+
deprecation_warning("pydantic-v1")
|
25
|
+
|
26
|
+
def computed_field(f):
|
27
|
+
"""No-op decorator to allow this module to not break imports until 1.9.0"""
|
28
|
+
return f
|
23
29
|
|
24
30
|
|
25
|
-
class TemplateModel
|
31
|
+
class PydanticTemplateModel(TemplateModel):
|
26
32
|
"""
|
27
33
|
Metaclass to render pydantic models with jinja templates.
|
28
34
|
|
@@ -59,15 +65,14 @@ class TemplateModel(BaseModel):
|
|
59
65
|
loader=PackageLoader("linkml.generators.pydanticgen", "templates"), trim_blocks=True, lstrip_blocks=True
|
60
66
|
)
|
61
67
|
|
62
|
-
pydantic_ver: int = int(PYDANTIC_VERSION[0])
|
63
68
|
meta_exclude: ClassVar[List[str]] = None
|
64
69
|
|
65
70
|
def render(self, environment: Optional[Environment] = None, black: bool = False) -> str:
|
66
71
|
"""
|
67
72
|
Recursively render a template model to a string.
|
68
73
|
|
69
|
-
For each field in the model, recurse through, rendering each :class:`.
|
70
|
-
using the template set in :attr:`.
|
74
|
+
For each field in the model, recurse through, rendering each :class:`.PydanticTemplateModel`
|
75
|
+
using the template set in :attr:`.PydanticTemplateModel.template` , but preserving the structure
|
71
76
|
of lists and dictionaries. Regular :class:`.BaseModel` s are rendered to dictionaries.
|
72
77
|
Any other value is passed through unchanged.
|
73
78
|
|
@@ -76,16 +81,10 @@ class TemplateModel(BaseModel):
|
|
76
81
|
black (bool): if ``True`` , format template with black. (default False)
|
77
82
|
"""
|
78
83
|
if environment is None:
|
79
|
-
environment =
|
84
|
+
environment = self.environment()
|
80
85
|
|
81
|
-
|
82
|
-
fields = {**self.model_fields, **self.model_computed_fields}
|
83
|
-
else:
|
84
|
-
fields = self.model_fields
|
86
|
+
rendered = super().render(environment=environment)
|
85
87
|
|
86
|
-
data = {k: _render(getattr(self, k, None), environment) for k in fields}
|
87
|
-
template = environment.get_template(self.template)
|
88
|
-
rendered = template.render(**data)
|
89
88
|
if format_black is not None and black:
|
90
89
|
try:
|
91
90
|
return format_black(rendered)
|
@@ -97,72 +96,6 @@ class TemplateModel(BaseModel):
|
|
97
96
|
else:
|
98
97
|
return rendered
|
99
98
|
|
100
|
-
@classmethod
|
101
|
-
def environment(cls) -> Environment:
|
102
|
-
"""
|
103
|
-
Default environment for Template models.
|
104
|
-
uses a :class:`jinja2.PackageLoader` for the templates directory within this module
|
105
|
-
with the ``trim_blocks`` and ``lstrip_blocks`` parameters set to ``True`` so that the
|
106
|
-
default templates could be written in a more readable way.
|
107
|
-
"""
|
108
|
-
return copy(cls._environment)
|
109
|
-
|
110
|
-
if int(PYDANTIC_VERSION[0]) < 2:
|
111
|
-
# simulate pydantic 2's model_fields behavior
|
112
|
-
# without using classmethod + property decorators
|
113
|
-
# see:
|
114
|
-
# - https://docs.python.org/3/whatsnew/3.11.html#language-builtins
|
115
|
-
# - https://github.com/python/cpython/issues/89519
|
116
|
-
# and:
|
117
|
-
# - https://docs.python.org/3/reference/datamodel.html#customizing-class-creation
|
118
|
-
# for this version.
|
119
|
-
model_fields: ClassVar[Dict[str, "ModelField"]]
|
120
|
-
|
121
|
-
def __init_subclass__(cls, **kwargs):
|
122
|
-
super().__init_subclass__(**kwargs)
|
123
|
-
cls.model_fields = cls.__fields__
|
124
|
-
|
125
|
-
@overload
|
126
|
-
def model_dump(self, mode: Literal["python"] = "python") -> dict: ...
|
127
|
-
|
128
|
-
@overload
|
129
|
-
def model_dump(self, mode: Literal["json"] = "json") -> str: ...
|
130
|
-
|
131
|
-
def model_dump(self, mode: Literal["python", "json"] = "python", **kwargs) -> Union[dict, str]:
|
132
|
-
if mode == "json":
|
133
|
-
return self.json(**kwargs)
|
134
|
-
return self.dict(**kwargs)
|
135
|
-
|
136
|
-
@classmethod
|
137
|
-
def exclude_from_meta(cls: "TemplateModel") -> List[str]:
|
138
|
-
"""
|
139
|
-
Attributes in the source definition to exclude from linkml_meta
|
140
|
-
"""
|
141
|
-
ret = [*cls.model_fields.keys()]
|
142
|
-
if cls.meta_exclude is not None:
|
143
|
-
ret = ret + cls.meta_exclude
|
144
|
-
return ret
|
145
|
-
|
146
|
-
|
147
|
-
def _render(
|
148
|
-
item: Union[TemplateModel, Any, List[Union[Any, TemplateModel]], Dict[str, Union[Any, TemplateModel]]],
|
149
|
-
environment: Environment,
|
150
|
-
) -> Union[str, List[str], Dict[str, str]]:
|
151
|
-
if isinstance(item, TemplateModel):
|
152
|
-
return item.render(environment)
|
153
|
-
elif isinstance(item, list):
|
154
|
-
return [_render(i, environment) for i in item]
|
155
|
-
elif isinstance(item, dict):
|
156
|
-
return {k: _render(v, environment) for k, v in item.items()}
|
157
|
-
elif isinstance(item, BaseModel):
|
158
|
-
if int(PYDANTIC_VERSION[0]) >= 2:
|
159
|
-
fields = item.model_fields
|
160
|
-
else:
|
161
|
-
fields = item.__fields__
|
162
|
-
return {k: _render(getattr(item, k, None), environment) for k in fields.keys()}
|
163
|
-
else:
|
164
|
-
return item
|
165
|
-
|
166
99
|
|
167
100
|
class EnumValue(BaseModel):
|
168
101
|
"""
|
@@ -174,7 +107,7 @@ class EnumValue(BaseModel):
|
|
174
107
|
description: Optional[str] = None
|
175
108
|
|
176
109
|
|
177
|
-
class PydanticEnum(
|
110
|
+
class PydanticEnum(PydanticTemplateModel):
|
178
111
|
"""
|
179
112
|
Model used to render a :class:`enum.Enum`
|
180
113
|
"""
|
@@ -186,7 +119,7 @@ class PydanticEnum(TemplateModel):
|
|
186
119
|
values: Dict[str, EnumValue] = Field(default_factory=dict)
|
187
120
|
|
188
121
|
|
189
|
-
class PydanticBaseModel(
|
122
|
+
class PydanticBaseModel(PydanticTemplateModel):
|
190
123
|
"""
|
191
124
|
Parameterization of the base model that generated pydantic classes inherit from
|
192
125
|
"""
|
@@ -207,24 +140,24 @@ class PydanticBaseModel(TemplateModel):
|
|
207
140
|
strict: bool = False
|
208
141
|
"""
|
209
142
|
Enable strict mode in the base model.
|
210
|
-
|
143
|
+
|
211
144
|
.. note::
|
212
|
-
|
145
|
+
|
213
146
|
Pydantic 2 only! Pydantic 1 only has strict types, not strict mode. See: https://github.com/linkml/linkml/issues/1955
|
214
|
-
|
147
|
+
|
215
148
|
References:
|
216
149
|
https://docs.pydantic.dev/latest/concepts/strict_mode
|
217
150
|
"""
|
218
151
|
|
219
152
|
|
220
|
-
class PydanticAttribute(
|
153
|
+
class PydanticAttribute(PydanticTemplateModel):
|
221
154
|
"""
|
222
155
|
Reduced version of SlotDefinition that carries all and only the information
|
223
156
|
needed by the template
|
224
157
|
"""
|
225
158
|
|
226
159
|
template: ClassVar[str] = "attribute.py.jinja"
|
227
|
-
meta_exclude: ClassVar[List[str]] = ["from_schema", "owner", "range", "
|
160
|
+
meta_exclude: ClassVar[List[str]] = ["from_schema", "owner", "range", "inlined", "inlined_as_list"]
|
228
161
|
|
229
162
|
name: str
|
230
163
|
required: bool = False
|
@@ -232,51 +165,32 @@ class PydanticAttribute(TemplateModel):
|
|
232
165
|
key: bool = False
|
233
166
|
predefined: Optional[str] = None
|
234
167
|
"""Fixed string to use in body of field"""
|
235
|
-
|
236
|
-
"""
|
237
|
-
Of the form::
|
238
|
-
|
239
|
-
annotations = {'python_range': {'value': 'int'}}
|
240
|
-
|
241
|
-
.. todo::
|
242
|
-
|
243
|
-
simplify when refactoring pydanticgen, should just be a string or a model
|
244
|
-
|
245
|
-
"""
|
168
|
+
range: Optional[str] = None
|
169
|
+
"""Type annotation used for model field"""
|
246
170
|
title: Optional[str] = None
|
247
171
|
description: Optional[str] = None
|
248
172
|
equals_number: Optional[Union[int, float]] = None
|
249
173
|
minimum_value: Optional[Union[int, float]] = None
|
250
174
|
maximum_value: Optional[Union[int, float]] = None
|
175
|
+
exact_cardinality: Optional[int] = None
|
176
|
+
minimum_cardinality: Optional[int] = None
|
177
|
+
maximum_cardinality: Optional[int] = None
|
178
|
+
multivalued: Optional[bool] = None
|
251
179
|
pattern: Optional[str] = None
|
252
180
|
meta: Optional[Dict[str, Any]] = None
|
253
181
|
"""
|
254
182
|
Metadata for the slot to be included in a Field annotation
|
255
183
|
"""
|
256
184
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
else:
|
267
|
-
return "None"
|
268
|
-
|
269
|
-
else:
|
270
|
-
field: Optional[str] = None
|
271
|
-
|
272
|
-
def __init__(self, **kwargs):
|
273
|
-
super(PydanticAttribute, self).__init__(**kwargs)
|
274
|
-
if self.predefined:
|
275
|
-
self.field = self.predefined
|
276
|
-
elif self.required or self.identifier or self.key:
|
277
|
-
self.field = "..."
|
278
|
-
else:
|
279
|
-
self.field = "None"
|
185
|
+
@computed_field
|
186
|
+
def field(self) -> str:
|
187
|
+
"""Computed value to use inside of the generated Field"""
|
188
|
+
if self.predefined:
|
189
|
+
return self.predefined
|
190
|
+
elif self.required or self.identifier or self.key:
|
191
|
+
return "..."
|
192
|
+
else:
|
193
|
+
return "None"
|
280
194
|
|
281
195
|
|
282
196
|
class PydanticValidator(PydanticAttribute):
|
@@ -287,7 +201,7 @@ class PydanticValidator(PydanticAttribute):
|
|
287
201
|
template: ClassVar[str] = "validator.py.jinja"
|
288
202
|
|
289
203
|
|
290
|
-
class PydanticClass(
|
204
|
+
class PydanticClass(PydanticTemplateModel):
|
291
205
|
"""
|
292
206
|
Reduced version of ClassDefinition that carries all and only the information
|
293
207
|
needed by the template.
|
@@ -316,24 +230,14 @@ class PydanticClass(TemplateModel):
|
|
316
230
|
|
317
231
|
return {k: PydanticValidator(**v.model_dump()) for k, v in self.attributes.items() if v.pattern is not None}
|
318
232
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
def validators(self) -> Optional[Dict[str, PydanticValidator]]:
|
323
|
-
return self._validators()
|
324
|
-
|
325
|
-
else:
|
326
|
-
validators: Optional[Dict[str, PydanticValidator]]
|
233
|
+
@computed_field
|
234
|
+
def validators(self) -> Optional[Dict[str, PydanticValidator]]:
|
235
|
+
return self._validators()
|
327
236
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
def render(self, environment: Optional[Environment] = None, black: bool = False) -> str:
|
333
|
-
"""Overridden in pydantic 1 to ensure that validators are regenerated at rendering time"""
|
334
|
-
# refresh in case attributes have changed since init
|
335
|
-
self.validators = self._validators()
|
336
|
-
return super(PydanticClass, self).render(environment, black)
|
237
|
+
@computed_field
|
238
|
+
def slots(self) -> Optional[Dict[str, PydanticAttribute]]:
|
239
|
+
"""alias of attributes"""
|
240
|
+
return self.attributes
|
337
241
|
|
338
242
|
|
339
243
|
class ObjectImport(BaseModel):
|
@@ -347,7 +251,7 @@ class ObjectImport(BaseModel):
|
|
347
251
|
alias: Optional[str] = None
|
348
252
|
|
349
253
|
|
350
|
-
class Import(
|
254
|
+
class Import(PydanticTemplateModel):
|
351
255
|
"""
|
352
256
|
A python module, or module and classes to be imported.
|
353
257
|
|
@@ -381,6 +285,12 @@ class Import(TemplateModel):
|
|
381
285
|
module: str
|
382
286
|
alias: Optional[str] = None
|
383
287
|
objects: Optional[List[ObjectImport]] = None
|
288
|
+
is_schema: bool = False
|
289
|
+
"""
|
290
|
+
Whether or not this ``Import`` is importing another schema imported by the main schema --
|
291
|
+
ie. that it is not expected to be provided by the environment, but imported locally from within the package.
|
292
|
+
Used primarily in split schema generation, see :func:`.pydanticgen.generate_split` for example usage.
|
293
|
+
"""
|
384
294
|
|
385
295
|
def merge(self, other: "Import") -> List["Import"]:
|
386
296
|
"""
|
@@ -424,7 +334,14 @@ class Import(TemplateModel):
|
|
424
334
|
}
|
425
335
|
self_objs.update(other_objs)
|
426
336
|
|
427
|
-
return [
|
337
|
+
return [
|
338
|
+
Import(
|
339
|
+
module=self.module,
|
340
|
+
alias=alias,
|
341
|
+
objects=list(self_objs.values()),
|
342
|
+
is_schema=self.is_schema or other.is_schema,
|
343
|
+
)
|
344
|
+
]
|
428
345
|
else:
|
429
346
|
# one is a module, the other imports objects, keep both
|
430
347
|
return [self, other]
|
@@ -475,7 +392,7 @@ class ConditionalImport(Import):
|
|
475
392
|
alternative: Import
|
476
393
|
|
477
394
|
|
478
|
-
class Imports(
|
395
|
+
class Imports(PydanticTemplateModel):
|
479
396
|
"""
|
480
397
|
Container class for imports that can handle merging!
|
481
398
|
|
@@ -511,19 +428,38 @@ class Imports(TemplateModel):
|
|
511
428
|
|
512
429
|
imports: List[Union[Import, ConditionalImport]] = Field(default_factory=list)
|
513
430
|
|
514
|
-
|
431
|
+
@classmethod
|
432
|
+
def _merge(
|
433
|
+
cls, imports: List[Union[Import, ConditionalImport]], other: Union[Import, "Imports", List[Import]]
|
434
|
+
) -> List[Union[Import, ConditionalImport]]:
|
435
|
+
"""
|
436
|
+
Add a new import to an existing imports list, handling deduplication and flattening.
|
437
|
+
|
438
|
+
Mutates and returns ``imports``
|
439
|
+
|
440
|
+
Generally will prefer the imports in ``other`` , updating those in ``imports``.
|
441
|
+
If ``other`` ...
|
442
|
+
- doesn't match any ``module`` in ``imports``, add it!
|
443
|
+
- matches a single ``module`` in imports, :meth:`.Import.merge` the object imports
|
444
|
+
- matches multiple ``module``s in imports, then there must have been another
|
445
|
+
:class:`.ConditionalImport` already present, so we :meth:`.Import.merge` the existing
|
446
|
+
:class:`.Import` if it is one, and if it's a :class:`.ConditionalImport` just YOLO
|
447
|
+
and append it since there isn't a principled way to merge them from strings.
|
448
|
+
- is :class:`.Imports` or a list of :class:`.Import` s, call this recursively for each
|
449
|
+
item.
|
450
|
+
|
451
|
+
Since imports can be merged in an undefined order depending on the generator configuration,
|
452
|
+
default behavior for imports with matching ``module`` is to remove them and append to the
|
453
|
+
end of the imports list (rather than keeping it in the position of the existing
|
454
|
+
:class:`.Import` ). :class:`.ConditionalImports` make it possible to have namespace
|
455
|
+
conflicts, so in imperative import style we assume the most recently added :class:`.Import`
|
456
|
+
is the one that should prevail.
|
457
|
+
"""
|
458
|
+
#
|
515
459
|
if isinstance(other, Imports) or (isinstance(other, list) and all([isinstance(i, Import) for i in other])):
|
516
|
-
if hasattr(self, "model_copy"):
|
517
|
-
self_copy = self.model_copy(deep=True)
|
518
|
-
else:
|
519
|
-
self_copy = self.copy()
|
520
|
-
|
521
460
|
for i in other:
|
522
|
-
|
523
|
-
return
|
524
|
-
|
525
|
-
# check if we have one of these already
|
526
|
-
imports = self.imports.copy()
|
461
|
+
imports = cls._merge(imports, i)
|
462
|
+
return imports
|
527
463
|
|
528
464
|
existing = [i for i in imports if i.module == other.module]
|
529
465
|
|
@@ -549,8 +485,12 @@ class Imports(TemplateModel):
|
|
549
485
|
|
550
486
|
# SPECIAL CASE - __future__ annotations must happen at the top of a file
|
551
487
|
imports = sorted(imports, key=lambda i: i.module == "__future__", reverse=True)
|
488
|
+
return imports
|
552
489
|
|
553
|
-
|
490
|
+
def __add__(self, other: Union[Import, "Imports", List[Import]]) -> "Imports":
|
491
|
+
imports = self.imports.copy()
|
492
|
+
imports = self._merge(imports, other)
|
493
|
+
return Imports.model_construct(imports=imports)
|
554
494
|
|
555
495
|
def __len__(self) -> int:
|
556
496
|
return len(self.imports)
|
@@ -559,11 +499,61 @@ class Imports(TemplateModel):
|
|
559
499
|
for i in self.imports:
|
560
500
|
yield i
|
561
501
|
|
562
|
-
def __getitem__(self, item: int) -> Import:
|
563
|
-
|
502
|
+
def __getitem__(self, item: Union[int, str]) -> Import:
|
503
|
+
if isinstance(item, int):
|
504
|
+
return self.imports[item]
|
505
|
+
elif isinstance(item, str):
|
506
|
+
# the name of the module
|
507
|
+
an_import = [i for i in self.imports if i.module == item]
|
508
|
+
if len(an_import) == 0:
|
509
|
+
raise KeyError(f"No import with module {item} was found.\nWe have: {self.imports}")
|
510
|
+
return an_import[0]
|
511
|
+
else:
|
512
|
+
raise TypeError(f"Can only index with an int or a string as the name of the module,\nGot: {type(item)}")
|
513
|
+
|
514
|
+
def __contains__(self, item: Union[Import, "Imports", List[Import]]) -> bool:
|
515
|
+
"""
|
516
|
+
Check if all the objects are imported from the given module(s)
|
517
|
+
|
518
|
+
If the import is a bare module import (ie its :attr:`~.Import.objects` is ``None`` )
|
519
|
+
then we must also have a bare module import in this Imports (because even if
|
520
|
+
we import from the module, unless we import it specifically its name won't be
|
521
|
+
available in the namespace.
|
564
522
|
|
523
|
+
:attr:`.Import.alias` must always match for the same reason.
|
524
|
+
"""
|
525
|
+
if isinstance(item, Imports):
|
526
|
+
return all([i in self for i in item.imports])
|
527
|
+
elif isinstance(item, list):
|
528
|
+
return all([i in self for i in item])
|
529
|
+
elif isinstance(item, Import):
|
530
|
+
try:
|
531
|
+
an_import = self[item.module]
|
532
|
+
except KeyError:
|
533
|
+
return False
|
534
|
+
if item.objects is None:
|
535
|
+
return an_import == item
|
536
|
+
else:
|
537
|
+
return all([obj in an_import.objects for obj in item.objects])
|
538
|
+
else:
|
539
|
+
raise TypeError("Imports only contains single Import objects or other Imports\n" f"Got: {type(item)}")
|
565
540
|
|
566
|
-
|
541
|
+
@field_validator("imports", mode="after")
|
542
|
+
@classmethod
|
543
|
+
def imports_are_merged(
|
544
|
+
cls, imports: List[Union[Import, ConditionalImport]]
|
545
|
+
) -> List[Union[Import, ConditionalImport]]:
|
546
|
+
"""
|
547
|
+
When creating from a list of imports, construct model as if we have done so by iteratively
|
548
|
+
constructing with __add__ calls
|
549
|
+
"""
|
550
|
+
merged_imports = []
|
551
|
+
for i in imports:
|
552
|
+
merged_imports = cls._merge(merged_imports, i)
|
553
|
+
return merged_imports
|
554
|
+
|
555
|
+
|
556
|
+
class PydanticModule(PydanticTemplateModel):
|
567
557
|
"""
|
568
558
|
Top-level container model for generating a pydantic module :)
|
569
559
|
"""
|
@@ -575,7 +565,7 @@ class PydanticModule(TemplateModel):
|
|
575
565
|
version: Optional[str] = None
|
576
566
|
base_model: PydanticBaseModel = PydanticBaseModel()
|
577
567
|
injected_classes: Optional[List[str]] = None
|
578
|
-
|
568
|
+
python_imports: List[Union[Import, ConditionalImport]] = Field(default_factory=list)
|
579
569
|
enums: Dict[str, PydanticEnum] = Field(default_factory=dict)
|
580
570
|
classes: Dict[str, PydanticClass] = Field(default_factory=dict)
|
581
571
|
meta: Optional[Dict[str, Any]] = None
|
@@ -583,23 +573,6 @@ class PydanticModule(TemplateModel):
|
|
583
573
|
Metadata for the schema to be included in a linkml_meta module-level instance of LinkMLMeta
|
584
574
|
"""
|
585
575
|
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
def class_names(self) -> List[str]:
|
590
|
-
return [c.name for c in self.classes.values()]
|
591
|
-
|
592
|
-
else:
|
593
|
-
class_names: List[str] = Field(default_factory=list)
|
594
|
-
|
595
|
-
def __init__(self, **kwargs):
|
596
|
-
super(PydanticModule, self).__init__(**kwargs)
|
597
|
-
self.class_names = [c.name for c in self.classes.values()]
|
598
|
-
|
599
|
-
def render(self, environment: Optional[Environment] = None, black: bool = False) -> str:
|
600
|
-
"""
|
601
|
-
Trivial override of parent method for pydantic 1 to ensure that
|
602
|
-
:attr:`.class_names` are correct at render time
|
603
|
-
"""
|
604
|
-
self.class_names = [c.name for c in self.classes.values()]
|
605
|
-
return super(PydanticModule, self).render(environment, black)
|
576
|
+
@computed_field
|
577
|
+
def class_names(self) -> List[str]:
|
578
|
+
return [c.name for c in self.classes.values()]
|
@@ -1,4 +1,4 @@
|
|
1
|
-
{{name}}: {{
|
1
|
+
{{name}}: {{ range }} = Field({{ field }}
|
2
2
|
{%- if title != None %}, title="{{title}}"{% endif -%}
|
3
3
|
{%- if description %}, description="""{{description}}"""{% endif -%}
|
4
4
|
{%- if equals_number != None %}
|
@@ -7,10 +7,12 @@
|
|
7
7
|
{%- if minimum_value != None %}, ge={{minimum_value}}{% endif -%}
|
8
8
|
{%- if maximum_value != None %}, le={{maximum_value}}{% endif -%}
|
9
9
|
{%- endif -%}
|
10
|
-
{%- if
|
11
|
-
{
|
12
|
-
|
13
|
-
{%
|
10
|
+
{%- if multivalued and exact_cardinality != None -%}
|
11
|
+
, min_length={{exact_cardinality}}, max_length={{exact_cardinality}}
|
12
|
+
{%- elif multivalued -%}
|
13
|
+
{%- if minimum_cardinality != None %}, min_length={{minimum_cardinality}}{% endif -%}
|
14
|
+
{%- if maximum_cardinality != None %}, max_length={{maximum_cardinality}}{% endif -%}
|
15
|
+
{%- endif -%}
|
16
|
+
{%- if meta -%}
|
14
17
|
, json_schema_extra = { "linkml_meta": {{ meta | pprint | indent(width=8) }} }
|
15
|
-
|
16
|
-
{%- endif -%})
|
18
|
+
{%- endif -%})
|
@@ -1,15 +1,3 @@
|
|
1
|
-
{% if pydantic_ver == 1 %}
|
2
|
-
class WeakRefShimBaseModel(BaseModel):
|
3
|
-
__slots__ = '__weakref__'
|
4
|
-
|
5
|
-
class {{ name }}(WeakRefShimBaseModel,
|
6
|
-
validate_assignment = True,
|
7
|
-
validate_all = True,
|
8
|
-
underscore_attrs_are_private = True,
|
9
|
-
extra = "{{ extra_fields }}",
|
10
|
-
arbitrary_types_allowed = True,
|
11
|
-
use_enum_values = True):
|
12
|
-
{% else %}
|
13
1
|
class {{ name }}(BaseModel):
|
14
2
|
model_config = ConfigDict(
|
15
3
|
validate_assignment = True,
|
@@ -19,7 +7,6 @@ class {{ name }}(BaseModel):
|
|
19
7
|
use_enum_values = True,
|
20
8
|
strict = {{ strict }},
|
21
9
|
)
|
22
|
-
{% endif %}
|
23
10
|
{% if fields is not none %}
|
24
11
|
{% for field in fields %}
|
25
12
|
{{ field }}
|
@@ -4,8 +4,8 @@ class {{ name }}({% if bases is string %}{{ bases }}{% else %}{{ bases | join(',
|
|
4
4
|
{{ description | indent(width=4) }}
|
5
5
|
"""
|
6
6
|
{% endif -%}
|
7
|
-
{% if meta
|
8
|
-
linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({
|
7
|
+
{% if meta %}
|
8
|
+
linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({{ meta | pprint | indent(width=8) }})
|
9
9
|
|
10
10
|
{% endif %}
|
11
11
|
{% if attributes or validators %}
|
@@ -1,13 +1,5 @@
|
|
1
|
-
{% if pydantic_ver == 1 %}
|
2
|
-
# Update forward refs
|
3
|
-
# see https://pydantic-docs.helpmanual.io/usage/postponed_annotations/
|
4
|
-
{% for c in class_names -%}
|
5
|
-
{{ c }}.update_forward_refs()
|
6
|
-
{% endfor %}
|
7
|
-
{% else %}
|
8
1
|
# Model rebuild
|
9
2
|
# see https://pydantic-docs.helpmanual.io/usage/models/#rebuilding-a-model
|
10
|
-
|
3
|
+
{% for c in class_names -%}
|
11
4
|
{{ c }}.model_rebuild()
|
12
|
-
|
13
|
-
{% endif %}
|
5
|
+
{% endfor %}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
{% for import in
|
1
|
+
{% for import in python_imports %}
|
2
2
|
{{ import }}
|
3
3
|
{%- endfor -%}
|
4
4
|
|
@@ -14,8 +14,8 @@ version = "{{version if version else None}}"
|
|
14
14
|
{{ c }}
|
15
15
|
{% endfor %}
|
16
16
|
{% endif %}
|
17
|
-
{% if meta
|
18
|
-
linkml_meta = LinkMLMeta({
|
17
|
+
{% if meta %}
|
18
|
+
linkml_meta = LinkMLMeta({{ meta | pprint | indent(width=4) }} )
|
19
19
|
{% else %}
|
20
20
|
linkml_meta = None
|
21
21
|
{% endif %}
|
linkml/generators/pythongen.py
CHANGED
@@ -54,6 +54,16 @@ class PythonGenerator(Generator):
|
|
54
54
|
gen_slots: bool = True
|
55
55
|
genmeta: bool = False
|
56
56
|
emit_metadata: bool = True
|
57
|
+
dataclass_repr: bool = False
|
58
|
+
"""
|
59
|
+
Whether generated dataclasses should also generate a default __repr__ method.
|
60
|
+
|
61
|
+
Default ``False`` so that the parent :class:`linkml_runtime.utils.yamlutils.YAMLRoot` 's
|
62
|
+
``__repr__`` method is inherited for model pretty printing.
|
63
|
+
|
64
|
+
References:
|
65
|
+
- https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass
|
66
|
+
"""
|
57
67
|
|
58
68
|
def __post_init__(self) -> None:
|
59
69
|
if isinstance(self.schema, Path):
|
@@ -388,7 +398,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
388
398
|
return f"\n{self.class_or_type_name(cls.name)} = Any"
|
389
399
|
|
390
400
|
cd_str = (
|
391
|
-
("\n@dataclass" if slotdefs else "")
|
401
|
+
(f"\n@dataclass(repr={self.dataclass_repr})" if slotdefs else "")
|
392
402
|
+ f"\nclass {self.class_or_type_name(cls.name)}{parentref}:{wrapped_description}"
|
393
403
|
+ f"{self.gen_inherited_slots(cls)}"
|
394
404
|
+ f"{self.gen_class_meta(cls)}"
|
@@ -1171,7 +1181,7 @@ class {enum_name}(EnumDefinitionImpl):
|
|
1171
1181
|
|
1172
1182
|
|
1173
1183
|
@shared_arguments(PythonGenerator)
|
1174
|
-
@click.command()
|
1184
|
+
@click.command(name="python")
|
1175
1185
|
@click.option("--head/--no-head", default=True, show_default=True, help="Emit metadata heading")
|
1176
1186
|
@click.option(
|
1177
1187
|
"--genmeta/--no-genmeta",
|