linkml 1.7.4__py3-none-any.whl → 1.7.6__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 (54) hide show
  1. linkml/generators/__init__.py +2 -0
  2. linkml/generators/docgen/class.md.jinja2 +15 -2
  3. linkml/generators/docgen/slot.md.jinja2 +14 -1
  4. linkml/generators/docgen.py +32 -11
  5. linkml/generators/excelgen.py +5 -3
  6. linkml/generators/golanggen.py +6 -3
  7. linkml/generators/jsonldcontextgen.py +9 -6
  8. linkml/generators/jsonldgen.py +3 -1
  9. linkml/generators/oocodegen.py +6 -3
  10. linkml/generators/owlgen.py +30 -3
  11. linkml/generators/prefixmapgen.py +5 -4
  12. linkml/generators/projectgen.py +18 -5
  13. linkml/generators/pydanticgen/__init__.py +29 -0
  14. linkml/generators/{pydanticgen.py → pydanticgen/pydanticgen.py} +196 -301
  15. linkml/generators/pydanticgen/template.py +504 -0
  16. linkml/generators/pydanticgen/templates/attribute.py.jinja +10 -0
  17. linkml/generators/pydanticgen/templates/base_model.py.jinja +27 -0
  18. linkml/generators/pydanticgen/templates/class.py.jinja +21 -0
  19. linkml/generators/pydanticgen/templates/conditional_import.py.jinja +9 -0
  20. linkml/generators/pydanticgen/templates/enum.py.jinja +16 -0
  21. linkml/generators/pydanticgen/templates/footer.py.jinja +13 -0
  22. linkml/generators/pydanticgen/templates/imports.py.jinja +31 -0
  23. linkml/generators/pydanticgen/templates/module.py.jinja +27 -0
  24. linkml/generators/pydanticgen/templates/validator.py.jinja +15 -0
  25. linkml/generators/pythongen.py +19 -10
  26. linkml/generators/shacl/__init__.py +3 -0
  27. linkml/generators/shacl/ifabsent_processor.py +41 -0
  28. linkml/generators/shacl/shacl_data_type.py +40 -0
  29. linkml/generators/shaclgen.py +105 -82
  30. linkml/generators/shexgen.py +1 -1
  31. linkml/generators/sparqlgen.py +5 -4
  32. linkml/generators/sqlalchemygen.py +15 -14
  33. linkml/generators/sqltablegen.py +37 -25
  34. linkml/generators/terminusdbgen.py +7 -1
  35. linkml/generators/typescriptgen.py +2 -1
  36. linkml/linter/config/datamodel/config.py +8 -0
  37. linkml/linter/linter.py +2 -1
  38. linkml/linter/rules.py +11 -2
  39. linkml/transformers/logical_model_transformer.py +2 -1
  40. linkml/transformers/relmodel_transformer.py +4 -2
  41. linkml/transformers/schema_renamer.py +1 -1
  42. linkml/utils/generator.py +11 -8
  43. linkml/utils/ifabsent_functions.py +7 -9
  44. linkml/utils/schema_builder.py +1 -0
  45. linkml/utils/schema_fixer.py +3 -2
  46. linkml/utils/schemaloader.py +1 -9
  47. linkml/utils/sqlutils.py +39 -25
  48. linkml/validator/validation_context.py +2 -1
  49. {linkml-1.7.4.dist-info → linkml-1.7.6.dist-info}/METADATA +4 -1
  50. {linkml-1.7.4.dist-info → linkml-1.7.6.dist-info}/RECORD +53 -40
  51. {linkml-1.7.4.dist-info → linkml-1.7.6.dist-info}/entry_points.txt +0 -1
  52. linkml/generators/sqlddlgen.py +0 -559
  53. {linkml-1.7.4.dist-info → linkml-1.7.6.dist-info}/LICENSE +0 -0
  54. {linkml-1.7.4.dist-info → linkml-1.7.6.dist-info}/WHEEL +0 -0
@@ -0,0 +1,504 @@
1
+ from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generator, List, Literal, Optional, Union, overload
2
+
3
+ from jinja2 import Environment, PackageLoader
4
+ from pydantic import BaseModel, Field
5
+ from pydantic.version import VERSION as PYDANTIC_VERSION
6
+
7
+ if int(PYDANTIC_VERSION[0]) >= 2:
8
+ from pydantic import computed_field
9
+ else:
10
+ if TYPE_CHECKING: # pragma: no cover
11
+ from pydantic.fields import ModelField
12
+
13
+
14
+ class TemplateModel(BaseModel):
15
+ """
16
+ Metaclass to render pydantic models with jinja templates.
17
+
18
+ Each subclass needs to declare a :class:`typing.ClassVar` for a
19
+ jinja template within the `templates` directory.
20
+
21
+ Templates are written expecting each of the other TemplateModels
22
+ to already be rendered to strings - ie. rather than the ``class.py.jinja``
23
+ template receiving a full :class:`.PydanticAttribute` object or dictionary,
24
+ it receives it having already been rendered to a string. See the :meth:`.render` method.
25
+ """
26
+
27
+ template: ClassVar[str]
28
+ pydantic_ver: int = int(PYDANTIC_VERSION[0])
29
+
30
+ def render(self, environment: Optional[Environment] = None) -> str:
31
+ """
32
+ Recursively render a template model to a string.
33
+
34
+ For each field in the model, recurse through, rendering each :class:`.TemplateModel`
35
+ using the template set in :attr:`.TemplateModel.template` , but preserving the structure
36
+ of lists and dictionaries. Regular :class:`.BaseModel` s are rendered to dictionaries.
37
+ Any other value is passed through unchanged.
38
+ """
39
+ if environment is None:
40
+ environment = TemplateModel.environment()
41
+
42
+ if int(PYDANTIC_VERSION[0]) >= 2:
43
+ fields = {**self.model_fields, **self.model_computed_fields}
44
+ else:
45
+ fields = self.model_fields
46
+
47
+ data = {k: _render(getattr(self, k, None), environment) for k in fields}
48
+ template = environment.get_template(self.template)
49
+ return template.render(**data)
50
+
51
+ @classmethod
52
+ def environment(cls) -> Environment:
53
+ """
54
+ Default environment for Template models.
55
+
56
+ uses a :class:`jinja2.PackageLoader` for the templates directory within this module
57
+ with the ``trim_blocks`` and ``lstrip_blocks`` parameters set to ``True`` so that the
58
+ default templates could be written in a more readable way.
59
+ """
60
+ return Environment(
61
+ loader=PackageLoader("linkml.generators.pydanticgen", "templates"), trim_blocks=True, lstrip_blocks=True
62
+ )
63
+
64
+ if int(PYDANTIC_VERSION[0]) < 2:
65
+ # simulate pydantic 2's model_fields behavior
66
+ # without using classmethod + property decorators
67
+ # see:
68
+ # - https://docs.python.org/3/whatsnew/3.11.html#language-builtins
69
+ # - https://github.com/python/cpython/issues/89519
70
+ # and:
71
+ # - https://docs.python.org/3/reference/datamodel.html#customizing-class-creation
72
+ # for this version.
73
+ model_fields: ClassVar[Dict[str, "ModelField"]]
74
+
75
+ def __init_subclass__(cls, **kwargs):
76
+ super().__init_subclass__(**kwargs)
77
+ cls.model_fields = cls.__fields__
78
+
79
+ @overload
80
+ def model_dump(self, mode: Literal["python"] = "python") -> dict: ...
81
+
82
+ @overload
83
+ def model_dump(self, mode: Literal["json"] = "json") -> str: ...
84
+
85
+ def model_dump(self, mode: Literal["python", "json"] = "python", **kwargs) -> Union[dict, str]:
86
+ if mode == "json":
87
+ return self.json(**kwargs)
88
+ return self.dict(**kwargs)
89
+
90
+
91
+ def _render(
92
+ item: Union[TemplateModel, Any, List[Union[Any, TemplateModel]], Dict[str, Union[Any, TemplateModel]]],
93
+ environment: Environment,
94
+ ) -> Union[str, List[str], Dict[str, str]]:
95
+ if isinstance(item, TemplateModel):
96
+ return item.render(environment)
97
+ elif isinstance(item, list):
98
+ return [_render(i, environment) for i in item]
99
+ elif isinstance(item, dict):
100
+ return {k: _render(v, environment) for k, v in item.items()}
101
+ elif isinstance(item, BaseModel):
102
+ if int(PYDANTIC_VERSION[0]) >= 2:
103
+ fields = item.model_fields
104
+ else:
105
+ fields = item.__fields__
106
+ return {k: _render(getattr(item, k, None), environment) for k in fields.keys()}
107
+ else:
108
+ return item
109
+
110
+
111
+ class EnumValue(BaseModel):
112
+ """
113
+ A single value within an :class:`.Enum`
114
+ """
115
+
116
+ label: str
117
+ value: str
118
+ description: Optional[str] = None
119
+
120
+
121
+ class PydanticEnum(TemplateModel):
122
+ """
123
+ Model used to render a :class:`enum.Enum`
124
+ """
125
+
126
+ template: ClassVar[str] = "enum.py.jinja"
127
+
128
+ name: str
129
+ description: Optional[str] = None
130
+ values: Dict[str, EnumValue] = Field(default_factory=dict)
131
+
132
+
133
+ class PydanticBaseModel(TemplateModel):
134
+ """
135
+ Parameterization of the base model that generated pydantic classes inherit from
136
+ """
137
+
138
+ template: ClassVar[str] = "base_model.py.jinja"
139
+
140
+ default_name: ClassVar[str] = "ConfiguredBaseModel"
141
+ name: str = Field(default_factory=lambda: PydanticBaseModel.default_name)
142
+ extra_fields: Literal["allow", "forbid", "ignore"] = "forbid"
143
+ """
144
+ Sets the ``extra`` model for pydantic models
145
+ """
146
+ fields: Optional[List[str]] = None
147
+ """
148
+ Extra fields that are typically injected into the base model via
149
+ :attr:`~linkml.generators.pydanticgen.PydanticGenerator.injected_fields`
150
+ """
151
+
152
+
153
+ class PydanticAttribute(TemplateModel):
154
+ """
155
+ Reduced version of SlotDefinition that carries all and only the information
156
+ needed by the template
157
+ """
158
+
159
+ template: ClassVar[str] = "attribute.py.jinja"
160
+
161
+ name: str
162
+ required: bool = False
163
+ identifier: bool = False
164
+ key: bool = False
165
+ predefined: Optional[str] = None
166
+ """Fixed string to use in body of field"""
167
+ annotations: Optional[dict] = None
168
+ """
169
+ Of the form::
170
+
171
+ annotations = {'python_range': {'value': 'int'}}
172
+
173
+ .. todo::
174
+
175
+ simplify when refactoring pydanticgen, should just be a string or a model
176
+
177
+ """
178
+ title: Optional[str] = None
179
+ description: Optional[str] = None
180
+ equals_number: Optional[Union[int, float]] = None
181
+ minimum_value: Optional[Union[int, float]] = None
182
+ maximum_value: Optional[Union[int, float]] = None
183
+ pattern: Optional[str] = None
184
+
185
+ if int(PYDANTIC_VERSION[0]) >= 2:
186
+
187
+ @computed_field
188
+ def field(self) -> str:
189
+ """Computed value to use inside of the generated Field"""
190
+ if self.predefined:
191
+ return self.predefined
192
+ elif self.required or self.identifier or self.key:
193
+ return "..."
194
+ else:
195
+ return "None"
196
+
197
+ else:
198
+ field: Optional[str] = None
199
+
200
+ def __init__(self, **kwargs):
201
+ super(PydanticAttribute, self).__init__(**kwargs)
202
+ if self.predefined:
203
+ self.field = self.predefined
204
+ elif self.required or self.identifier or self.key:
205
+ self.field = "..."
206
+ else:
207
+ self.field = "None"
208
+
209
+
210
+ class PydanticValidator(PydanticAttribute):
211
+ """
212
+ Trivial subclass of :class:`.PydanticAttribute` that uses the ``validator.py.jinja`` template instead
213
+ """
214
+
215
+ template: ClassVar[str] = "validator.py.jinja"
216
+
217
+
218
+ class PydanticClass(TemplateModel):
219
+ """
220
+ Reduced version of ClassDefinition that carries all and only the information
221
+ needed by the template.
222
+
223
+ On instantiation and rendering, will create any additional :attr:`.validators`
224
+ that are implied by the given :attr:`.attributes`. Currently the only kind of
225
+ slot-level validators that are created are for those slots that have a ``pattern``
226
+ property.
227
+ """
228
+
229
+ template: ClassVar[str] = "class.py.jinja"
230
+
231
+ name: str
232
+ bases: Union[List[str], str] = PydanticBaseModel.default_name
233
+ description: Optional[str] = None
234
+ attributes: Optional[Dict[str, PydanticAttribute]] = None
235
+
236
+ def _validators(self) -> Optional[Dict[str, PydanticValidator]]:
237
+ if self.attributes is None:
238
+ return None
239
+
240
+ return {k: PydanticValidator(**v.model_dump()) for k, v in self.attributes.items() if v.pattern is not None}
241
+
242
+ if int(PYDANTIC_VERSION[0]) >= 2:
243
+
244
+ @computed_field
245
+ def validators(self) -> Optional[Dict[str, PydanticValidator]]:
246
+ return self._validators()
247
+
248
+ else:
249
+ validators: Optional[Dict[str, PydanticValidator]]
250
+
251
+ def __init__(self, **kwargs):
252
+ super(PydanticClass, self).__init__(**kwargs)
253
+ self.validators = self._validators()
254
+
255
+ def render(self, environment: Optional[Environment] = None) -> str:
256
+ """Overridden in pydantic 1 to ensure that validators are regenerated at rendering time"""
257
+ # refresh in case attributes have changed since init
258
+ self.validators = self._validators()
259
+ return super(PydanticClass, self).render(environment)
260
+
261
+
262
+ class ObjectImport(BaseModel):
263
+ """
264
+ An object to be imported from within a module.
265
+
266
+ See :class:`.Import` for examples
267
+ """
268
+
269
+ name: str
270
+ alias: Optional[str] = None
271
+
272
+
273
+ class Import(TemplateModel):
274
+ """
275
+ A python module, or module and classes to be imported.
276
+
277
+ Examples:
278
+
279
+ Module import:
280
+
281
+ .. code-block:: python
282
+
283
+ >>> Import(module='sys').render()
284
+ import sys
285
+ >>> Import(module='numpy', alias='np').render()
286
+ import numpy as np
287
+
288
+ Class import:
289
+
290
+ .. code-block:: python
291
+
292
+ >>> Import(module='pathlib', objects=[
293
+ >>> ObjectImport(name="Path"),
294
+ >>> ObjectImport(name="PurePath", alias="RenamedPurePath")
295
+ >>> ]).render()
296
+ from pathlib import (
297
+ Path,
298
+ PurePath as RenamedPurePath
299
+ )
300
+
301
+ """
302
+
303
+ template: ClassVar[str] = "imports.py.jinja"
304
+ module: str
305
+ alias: Optional[str] = None
306
+ objects: Optional[List[ObjectImport]] = None
307
+
308
+ def merge(self, other: "Import") -> List["Import"]:
309
+ """
310
+ Merge one import with another, see :meth:`.Imports` for an example.
311
+
312
+ * If module don't match, return both
313
+ * If one or the other are a :class:`.ConditionalImport`, return both
314
+ * If modules match, neither contain objects, but the other has an alias, return the other
315
+ * If modules match, one contains objects but the other doesn't, return both
316
+ * If modules match, both contain objects, merge the object lists, preferring objects with aliases
317
+ """
318
+ # return both if we are orthogonal
319
+ if self.module != other.module:
320
+ return [self, other]
321
+
322
+ # handle conditionals
323
+ if isinstance(self, ConditionalImport) or isinstance(other, ConditionalImport):
324
+ # we don't have a good way of combining conditionals, just return both
325
+ return [self, other]
326
+
327
+ # handle module vs. object imports
328
+ elif other.objects is None and self.objects is None:
329
+ # both are modules, return the other only if it updates the alias
330
+ if other.alias:
331
+ return [other]
332
+ else:
333
+ return [self]
334
+ elif other.objects is not None and self.objects is not None:
335
+ # both are object imports, merge and return
336
+ alias = self.alias if other.alias is None else other.alias
337
+ # FIXME: super awkward implementation
338
+ # keep ours if it has an alias and the other doesn't,
339
+ # otherwise take the other's version
340
+ self_objs = {obj.name: obj for obj in self.objects}
341
+ other_objs = {
342
+ obj.name: obj for obj in other.objects if obj.name not in self_objs or self_objs[obj.name].alias is None
343
+ }
344
+ self_objs.update(other_objs)
345
+
346
+ return [Import(module=self.module, alias=alias, objects=list(self_objs.values()))]
347
+ else:
348
+ # one is a module, the other imports objects, keep both
349
+ return [self, other]
350
+
351
+
352
+ class ConditionalImport(Import):
353
+ """
354
+ Import that depends on some condition in the environment, common when
355
+ using backported features or straddling dependency versions.
356
+
357
+ Make sure that everything that is needed to evaluate the condition is imported
358
+ before this is added to the injected imports!
359
+
360
+ Examples:
361
+
362
+ conditionally import Literal from ``typing_extensions`` if on python <= 3.8
363
+
364
+ .. code-block:: python
365
+ :force:
366
+
367
+ imports = (Imports() +
368
+ Import(module='sys') +
369
+ ConditionalImport(
370
+ module="typing",
371
+ objects=[ObjectImport(name="Literal")],
372
+ condition="sys.version_info >= (3, 8)",
373
+ alternative=Import(
374
+ module="typing_extensions",
375
+ objects=[ObjectImport(name="Literal")]
376
+ )
377
+ )
378
+
379
+ Renders to:
380
+
381
+ .. code-block:: python
382
+ :force:
383
+
384
+ import sys
385
+ if sys.version_info >= (3, 8):
386
+ from typing import Literal
387
+ else:
388
+ from typing_extensions import Literal
389
+
390
+ """
391
+
392
+ template: ClassVar[str] = "conditional_import.py.jinja"
393
+ condition: str
394
+ alternative: Import
395
+
396
+
397
+ class Imports(TemplateModel):
398
+ """
399
+ Container class for imports that can handle merging!
400
+
401
+ See :class:`.Import` and :class:`.ConditionalImport` for examples of declaring individual imports
402
+
403
+ Useful for generation, because each build stage will potentially generate
404
+ overlapping imports. This ensures that we can keep a collection of imports
405
+ without having many duplicates.
406
+
407
+ Defines methods for adding, iterating, and indexing from within the :attr:`Imports.imports` list.
408
+
409
+ Examples:
410
+
411
+ .. code-block:: python
412
+ :force:
413
+
414
+ imports = (Imports() +
415
+ Import(module="sys") +
416
+ Import(module="pathlib", objects=[ObjectImport(name="Path")]) +
417
+ Import(module="sys")
418
+ )
419
+
420
+ Renders to:
421
+
422
+ .. code-block:: python
423
+
424
+ from pathlib import Path
425
+ import sys
426
+
427
+ """
428
+
429
+ template: ClassVar[str] = "imports.py.jinja"
430
+
431
+ imports: List[Union[Import, ConditionalImport]] = Field(default_factory=list)
432
+
433
+ def __add__(self, other: Import) -> "Imports":
434
+ # check if we have one of these already
435
+ imports = self.imports.copy()
436
+ existing = [i for i in imports if i.module == other.module]
437
+
438
+ # if we have nothing importing from this module yet, add it!
439
+ if len(existing) == 0:
440
+ imports.append(other)
441
+ elif len(existing) == 1:
442
+ imports.remove(existing[0])
443
+ imports.extend(existing[0].merge(other))
444
+ else:
445
+ # we have both a conditional and at least one nonconditional already.
446
+ # If this is another conditional, we just add it, otherwise, we merge it
447
+ # with the single nonconditional
448
+ if isinstance(other, ConditionalImport):
449
+ imports.append(other)
450
+ else:
451
+ for e in existing:
452
+ if isinstance(e, Import):
453
+ imports.remove(e)
454
+ merged = e.merge(other)
455
+ imports.extend(merged)
456
+ break
457
+ return Imports(imports=imports)
458
+
459
+ def __len__(self) -> int:
460
+ return len(self.imports)
461
+
462
+ def __iter__(self) -> Generator[Import, None, None]:
463
+ for i in self.imports:
464
+ yield i
465
+
466
+ def __getitem__(self, item: int) -> Import:
467
+ return self.imports[item]
468
+
469
+
470
+ class PydanticModule(TemplateModel):
471
+ """
472
+ Top-level container model for generating a pydantic module :)
473
+ """
474
+
475
+ template: ClassVar[str] = "module.py.jinja"
476
+
477
+ metamodel_version: Optional[str] = None
478
+ version: Optional[str] = None
479
+ base_model: PydanticBaseModel = PydanticBaseModel()
480
+ injected_classes: Optional[List[str]] = None
481
+ imports: List[Union[Import, ConditionalImport]] = Field(default_factory=list)
482
+ enums: Dict[str, PydanticEnum] = Field(default_factory=dict)
483
+ classes: Dict[str, PydanticClass] = Field(default_factory=dict)
484
+
485
+ if int(PYDANTIC_VERSION[0]) >= 2:
486
+
487
+ @computed_field
488
+ def class_names(self) -> List[str]:
489
+ return [c.name for c in self.classes.values()]
490
+
491
+ else:
492
+ class_names: List[str] = Field(default_factory=list)
493
+
494
+ def __init__(self, **kwargs):
495
+ super(PydanticModule, self).__init__(**kwargs)
496
+ self.class_names = [c.name for c in self.classes.values()]
497
+
498
+ def render(self, environment: Optional[Environment] = None) -> str:
499
+ """
500
+ Trivial override of parent method for pydantic 1 to ensure that
501
+ :attr:`.class_names` are correct at render time
502
+ """
503
+ self.class_names = [c.name for c in self.classes.values()]
504
+ return super(PydanticModule, self).render(environment)
@@ -0,0 +1,10 @@
1
+ {{name}}: {{ annotations['python_range'].value }} = Field({{ field }}
2
+ {%- if title != None %}, title="{{title}}"{% endif -%}
3
+ {%- if description %}, description="""{{description}}"""{% endif -%}
4
+ {%- if equals_number != None %}
5
+ , le={{equals_number}}, ge={{equals_number}}
6
+ {%- else -%}
7
+ {%- if minimum_value != None %}, ge={{minimum_value}}{% endif -%}
8
+ {%- if maximum_value != None %}, le={{maximum_value}}{% endif -%}
9
+ {%- endif -%}
10
+ )
@@ -0,0 +1,27 @@
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
+ class {{ name }}(BaseModel):
14
+ model_config = ConfigDict(
15
+ validate_assignment = True,
16
+ validate_default = True,
17
+ extra = "{{ extra_fields }}",
18
+ arbitrary_types_allowed = True,
19
+ use_enum_values = True)
20
+ {% endif %}
21
+ {% if fields is not none %}
22
+ {% for field in fields %}
23
+ {{ field }}
24
+ {% endfor %}
25
+ {% else %}
26
+ {{ "pass" }}
27
+ {% endif %}
@@ -0,0 +1,21 @@
1
+ class {{ name }}({% if bases is string %}{{ bases }}{% else %}{{ bases | join(', ') }}{% endif %}):
2
+ {% if description %}
3
+ """
4
+ {{ description | indent(width=4) }}
5
+ """
6
+ {% endif -%}
7
+ {% if attributes or validators %}
8
+ {% if attributes %}
9
+ {% for attr in attributes.values() %}
10
+ {{ attr }}
11
+ {% endfor -%}
12
+ {% endif %}
13
+ {% if validators %}
14
+ {% for validator in validators.values() %}
15
+
16
+ {{ validator }}
17
+ {% endfor -%}
18
+ {% endif %}
19
+ {% else %}
20
+ pass
21
+ {% endif %}
@@ -0,0 +1,9 @@
1
+ {% from 'imports.py.jinja' import import_ %}
2
+ if {{ condition }}:
3
+ {% filter indent(width=4) %}
4
+ {{ import_(module, alias, objects) }}
5
+ {% endfilter %}
6
+ else:
7
+ {% filter indent(width=4) %}
8
+ {{ alternative }}
9
+ {% endfilter %}
@@ -0,0 +1,16 @@
1
+ class {{ name }}(str{% if values %}, Enum{% endif %}):
2
+ {% if description %}
3
+ """
4
+ {{ description }}
5
+ """
6
+ {% endif %}
7
+ {% if values %}
8
+ {% for pv in values.values() -%}
9
+ {% if pv.description %}
10
+ # {{pv.description}}
11
+ {% endif %}
12
+ {{pv.label}} = "{{pv.value}}"
13
+ {% endfor %}
14
+ {% else %}
15
+ pass
16
+ {% endif %}
@@ -0,0 +1,13 @@
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
+ # Model rebuild
9
+ # see https://pydantic-docs.helpmanual.io/usage/models/#rebuilding-a-model
10
+ {% for c in class_names -%}
11
+ {{ c }}.model_rebuild()
12
+ {% endfor %}
13
+ {% endif %}
@@ -0,0 +1,31 @@
1
+ {% macro import_(module, alias=None, objects = None) %}
2
+ {%- if objects is none and alias is none %}
3
+ import {{ module }}
4
+ {%- elif objects is none and alias is string %}
5
+ import {{ module }} as {{ alias }}
6
+ {%- else %}
7
+ {% if objects | length == 1 %}
8
+ from {{ module }} import {{ objects[0]['name'] }} {% if objects[0]['alias'] is not none %} as {{ objects[0]['alias'] }} {% endif %}
9
+ {%- else %}
10
+ from {{ module }} import (
11
+ {% for object in objects %}
12
+ {% if object['alias'] is string %}
13
+ {{ object['name'] }} as {{ object['alias'] }}
14
+ {%- else %}
15
+ {{ object['name'] }}
16
+ {%- endif %}
17
+ {% if not loop.last %},{{ '\n' }}{% else %}{{ '\n' }}{% endif %}
18
+ {% endfor %}
19
+ )
20
+ {%- endif %}
21
+ {%- endif %}
22
+ {% endmacro %}
23
+ {%- if module %}
24
+ {{ import_(module, alias, objects) }}
25
+ {% endif -%}
26
+ {#- For when used with Imports container -#}
27
+ {%- if imports -%}
28
+ {%- for i in imports -%}
29
+ {{ i }}
30
+ {%- endfor -%}
31
+ {% endif -%}
@@ -0,0 +1,27 @@
1
+ {% for import in imports %}
2
+ {{ import }}
3
+ {%- endfor -%}
4
+
5
+ metamodel_version = "{{metamodel_version}}"
6
+ version = "{{version if version else None}}"
7
+
8
+
9
+ {{ base_model }}
10
+ {% if enums %}
11
+ {% for e in enums.values() %}
12
+
13
+ {{ e }}
14
+ {% endfor %}
15
+ {% endif %}
16
+ {% if injected_classes %}
17
+ {% for c in injected_classes%}
18
+
19
+ {{ c }}
20
+ {% endfor %}
21
+ {% endif %}
22
+ {% for c in classes.values() %}
23
+
24
+ {{ c }}
25
+ {% endfor %}
26
+
27
+ {% include 'footer.py.jinja' %}
@@ -0,0 +1,15 @@
1
+ {% if pydantic_ver == 1 %}
2
+ @validator('{{name}}', allow_reuse=True)
3
+ {% else %}
4
+ @field_validator('{{name}}')
5
+ {% endif %}
6
+ def pattern_{{name}}(cls, v):
7
+ pattern=re.compile(r"{{pattern}}")
8
+ if isinstance(v,list):
9
+ for element in v:
10
+ if not pattern.match(element):
11
+ raise ValueError(f"Invalid {{name}} format: {element}")
12
+ elif isinstance(v,str):
13
+ if not pattern.match(v):
14
+ raise ValueError(f"Invalid {{name}} format: {v}")
15
+ return v