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