linkml 1.8.2__py3-none-any.whl → 1.8.4__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/main.py +4 -0
- linkml/generators/__init__.py +2 -0
- linkml/generators/common/ifabsent_processor.py +286 -0
- linkml/generators/docgen/index.md.jinja2 +6 -6
- linkml/generators/docgen.py +64 -14
- linkml/generators/golanggen.py +3 -1
- linkml/generators/jsonldcontextgen.py +0 -1
- linkml/generators/jsonschemagen.py +4 -2
- linkml/generators/owlgen.py +36 -17
- linkml/generators/projectgen.py +13 -11
- linkml/generators/pydanticgen/array.py +340 -56
- linkml/generators/pydanticgen/build.py +4 -2
- linkml/generators/pydanticgen/pydanticgen.py +46 -24
- linkml/generators/pydanticgen/template.py +108 -3
- linkml/generators/pydanticgen/templates/imports.py.jinja +11 -3
- linkml/generators/pydanticgen/templates/module.py.jinja +1 -3
- linkml/generators/pydanticgen/templates/validator.py.jinja +2 -2
- linkml/generators/python/__init__.py +1 -0
- linkml/generators/python/python_ifabsent_processor.py +92 -0
- linkml/generators/pythongen.py +19 -31
- linkml/generators/shacl/__init__.py +1 -3
- linkml/generators/shacl/shacl_data_type.py +1 -1
- linkml/generators/shacl/shacl_ifabsent_processor.py +89 -0
- linkml/generators/shaclgen.py +39 -13
- linkml/generators/sparqlgen.py +3 -1
- linkml/generators/sqlalchemygen.py +5 -3
- linkml/generators/sqltablegen.py +4 -2
- linkml/generators/typescriptgen.py +13 -6
- linkml/linter/linter.py +2 -1
- linkml/transformers/logical_model_transformer.py +3 -3
- linkml/transformers/relmodel_transformer.py +18 -4
- linkml/utils/converter.py +3 -1
- linkml/utils/exceptions.py +11 -0
- linkml/utils/execute_tutorial.py +22 -20
- linkml/utils/generator.py +6 -4
- linkml/utils/mergeutils.py +4 -2
- linkml/utils/schema_fixer.py +5 -5
- linkml/utils/schemaloader.py +5 -3
- linkml/utils/sqlutils.py +3 -1
- linkml/validator/plugins/pydantic_validation_plugin.py +1 -1
- linkml/validators/jsonschemavalidator.py +3 -1
- linkml/validators/sparqlvalidator.py +5 -3
- linkml/workspaces/example_runner.py +3 -1
- {linkml-1.8.2.dist-info → linkml-1.8.4.dist-info}/METADATA +3 -1
- {linkml-1.8.2.dist-info → linkml-1.8.4.dist-info}/RECORD +48 -45
- linkml/generators/shacl/ifabsent_processor.py +0 -59
- linkml/utils/ifabsent_functions.py +0 -138
- {linkml-1.8.2.dist-info → linkml-1.8.4.dist-info}/LICENSE +0 -0
- {linkml-1.8.2.dist-info → linkml-1.8.4.dist-info}/WHEEL +0 -0
- {linkml-1.8.2.dist-info → linkml-1.8.4.dist-info}/entry_points.txt +0 -0
@@ -41,9 +41,12 @@ from linkml.generators.pydanticgen.template import (
|
|
41
41
|
PydanticModule,
|
42
42
|
PydanticTemplateModel,
|
43
43
|
)
|
44
|
+
from linkml.generators.python.python_ifabsent_processor import PythonIfAbsentProcessor
|
44
45
|
from linkml.utils import deprecation_warning
|
45
46
|
from linkml.utils.generator import shared_arguments
|
46
|
-
|
47
|
+
|
48
|
+
logger = logging.getLogger(__name__)
|
49
|
+
|
47
50
|
|
48
51
|
if int(PYDANTIC_VERSION[0]) == 1:
|
49
52
|
deprecation_warning("pydantic-v1")
|
@@ -67,7 +70,9 @@ def _get_pyrange(t: TypeDefinition, sv: SchemaView) -> str:
|
|
67
70
|
DEFAULT_IMPORTS = (
|
68
71
|
Imports()
|
69
72
|
+ Import(module="__future__", objects=[ObjectImport(name="annotations")])
|
70
|
-
+ Import(
|
73
|
+
+ Import(
|
74
|
+
module="datetime", objects=[ObjectImport(name="datetime"), ObjectImport(name="date"), ObjectImport(name="time")]
|
75
|
+
)
|
71
76
|
+ Import(module="decimal", objects=[ObjectImport(name="Decimal")])
|
72
77
|
+ Import(module="enum", objects=[ObjectImport(name="Enum")])
|
73
78
|
+ Import(module="re")
|
@@ -187,7 +192,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
187
192
|
template_dir: Optional[Union[str, Path]] = None
|
188
193
|
"""
|
189
194
|
Override templates for each PydanticTemplateModel.
|
190
|
-
|
195
|
+
|
191
196
|
Directory with templates that override the default :attr:`.PydanticTemplateModel.template`
|
192
197
|
for each class. If a matching template is not found in the override directory,
|
193
198
|
the default templates will be used.
|
@@ -216,7 +221,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
216
221
|
)
|
217
222
|
|
218
223
|
"""
|
219
|
-
imports: Optional[List[Import]] = None
|
224
|
+
imports: Optional[Union[List[Import], Imports]] = None
|
220
225
|
"""
|
221
226
|
Additional imports to inject into generated module.
|
222
227
|
|
@@ -264,6 +269,13 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
264
269
|
else:
|
265
270
|
from typing_extensions import Literal
|
266
271
|
|
272
|
+
"""
|
273
|
+
sort_imports: bool = True
|
274
|
+
"""
|
275
|
+
Before returning from :meth:`.PydanticGenerator.render`, sort imports with :meth:`.Imports.sort`
|
276
|
+
|
277
|
+
Default ``True``, but optional in case import order must be explicitly given,
|
278
|
+
eg. to avoid circular import errors in complex generator subclasses.
|
267
279
|
"""
|
268
280
|
metadata_mode: Union[MetadataMode, str, None] = MetadataMode.AUTO
|
269
281
|
"""
|
@@ -356,8 +368,8 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
356
368
|
try:
|
357
369
|
return compile_python(pycode)
|
358
370
|
except NameError as e:
|
359
|
-
|
360
|
-
|
371
|
+
logger.error(f"Code:\n{pycode}")
|
372
|
+
logger.error(f"Error compiling generated python code: {e}")
|
361
373
|
raise e
|
362
374
|
|
363
375
|
def _get_classes(self, sv: SchemaView) -> Tuple[List[ClassDefinition], Optional[List[ClassDefinition]]]:
|
@@ -446,11 +458,12 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
446
458
|
|
447
459
|
def generate_slot(self, slot: SlotDefinition, cls: ClassDefinition) -> SlotResult:
|
448
460
|
slot_args = {
|
449
|
-
k: slot
|
461
|
+
k: getattr(slot, k, None)
|
450
462
|
for k in PydanticAttribute.model_fields.keys()
|
451
|
-
if slot
|
463
|
+
if getattr(slot, k, None) is not None
|
452
464
|
}
|
453
|
-
|
465
|
+
slot_alias = slot.alias if slot.alias else slot.name
|
466
|
+
slot_args["name"] = underscore(slot_alias)
|
454
467
|
slot_args["description"] = slot.description.replace('"', '\\"') if slot.description is not None else None
|
455
468
|
predef = self.predefined_slot_values.get(camelcase(cls.name), {}).get(slot.name, None)
|
456
469
|
if predef is not None:
|
@@ -526,6 +539,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
526
539
|
"""
|
527
540
|
if self._predefined_slot_values is None:
|
528
541
|
sv = self.schemaview
|
542
|
+
ifabsent_processor = PythonIfAbsentProcessor(sv)
|
529
543
|
slot_values = defaultdict(dict)
|
530
544
|
for class_def in sv.all_classes().values():
|
531
545
|
for slot_name in sv.class_slots(class_def.name):
|
@@ -541,10 +555,10 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
541
555
|
slot.name
|
542
556
|
]
|
543
557
|
elif slot.ifabsent is not None:
|
544
|
-
value =
|
558
|
+
value = ifabsent_processor.process_slot(slot, class_def)
|
545
559
|
slot_values[camelcase(class_def.name)][slot.name] = value
|
546
560
|
|
547
|
-
|
561
|
+
self._predefined_slot_values = slot_values
|
548
562
|
|
549
563
|
return self._predefined_slot_values
|
550
564
|
|
@@ -662,7 +676,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
662
676
|
else:
|
663
677
|
# TODO: default ranges in schemagen
|
664
678
|
# pyrange = 'str'
|
665
|
-
#
|
679
|
+
# logger.error(f'range: {s.range} is unknown')
|
666
680
|
raise Exception(f"range: {slot_range}")
|
667
681
|
return pyrange
|
668
682
|
|
@@ -918,14 +932,20 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
918
932
|
return Import(module=module, objects=[ObjectImport(name=camelcase(class_name))], is_schema=True)
|
919
933
|
|
920
934
|
def render(self) -> PydanticModule:
|
935
|
+
"""
|
936
|
+
Render the schema to a :class:`PydanticModule` model
|
937
|
+
"""
|
921
938
|
sv: SchemaView
|
922
939
|
sv = self.schemaview
|
923
940
|
|
924
941
|
# imports
|
925
942
|
imports = DEFAULT_IMPORTS
|
926
943
|
if self.imports is not None:
|
927
|
-
|
928
|
-
imports +=
|
944
|
+
if isinstance(self.imports, Imports):
|
945
|
+
imports += self.imports
|
946
|
+
else:
|
947
|
+
for i in self.imports:
|
948
|
+
imports += i
|
929
949
|
if self.split_mode == SplitMode.FULL:
|
930
950
|
imports += self._get_imports()
|
931
951
|
|
@@ -962,13 +982,14 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
962
982
|
class_results = self.after_generate_classes(class_results, sv)
|
963
983
|
|
964
984
|
classes = {r.cls.name: r.cls for r in class_results}
|
965
|
-
|
966
985
|
injected_classes = self._clean_injected_classes(injected_classes)
|
967
986
|
|
987
|
+
imports.render_sorted = self.sort_imports
|
988
|
+
|
968
989
|
module = PydanticModule(
|
969
990
|
metamodel_version=self.schema.metamodel_version,
|
970
991
|
version=self.schema.version,
|
971
|
-
python_imports=imports
|
992
|
+
python_imports=imports,
|
972
993
|
base_model=base_model,
|
973
994
|
injected_classes=injected_classes,
|
974
995
|
enums=enums,
|
@@ -984,7 +1005,8 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
984
1005
|
|
985
1006
|
Args:
|
986
1007
|
rendered_module ( :class:`.PydanticModule` ): Optional, if schema was previously
|
987
|
-
rendered with :meth
|
1008
|
+
rendered with :meth:`~.PydanticGenerator.render` , use that,
|
1009
|
+
otherwise :meth:`~.PydanticGenerator.render` fresh.
|
988
1010
|
"""
|
989
1011
|
if rendered_module is not None:
|
990
1012
|
module = rendered_module
|
@@ -1061,7 +1083,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
1061
1083
|
# interpret all imported schema paths as relative to that
|
1062
1084
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
1063
1085
|
serialized = generator.serialize(rendered_module=rendered)
|
1064
|
-
with open(output_path, "w") as ofile:
|
1086
|
+
with open(output_path, "w", encoding="utf-8") as ofile:
|
1065
1087
|
ofile.write(serialized)
|
1066
1088
|
|
1067
1089
|
results.append(
|
@@ -1080,7 +1102,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
|
|
1080
1102
|
rel_path = _import_to_path(generated_import.module)
|
1081
1103
|
abs_path = (output_path.parent / rel_path).resolve()
|
1082
1104
|
abs_path.parent.mkdir(parents=True, exist_ok=True)
|
1083
|
-
with open(abs_path, "w") as ofile:
|
1105
|
+
with open(abs_path, "w", encoding="utf-8") as ofile:
|
1084
1106
|
ofile.write(serialized)
|
1085
1107
|
|
1086
1108
|
results.append(
|
@@ -1123,7 +1145,7 @@ def _ensure_inits(paths: List[Path]):
|
|
1123
1145
|
common_path = Path(os.path.commonpath(paths))
|
1124
1146
|
|
1125
1147
|
if not (ipath := (common_path / "__init__.py")).exists():
|
1126
|
-
with open(ipath, "w") as ifile:
|
1148
|
+
with open(ipath, "w", encoding="utf-8") as ifile:
|
1127
1149
|
ifile.write(" \n")
|
1128
1150
|
|
1129
1151
|
for path in paths:
|
@@ -1131,7 +1153,7 @@ def _ensure_inits(paths: List[Path]):
|
|
1131
1153
|
path = path.parent
|
1132
1154
|
while path != common_path:
|
1133
1155
|
if not (ipath := (path / "__init__.py")).exists():
|
1134
|
-
with open(ipath, "w") as ifile:
|
1156
|
+
with open(ipath, "w", encoding="utf-8") as ifile:
|
1135
1157
|
ifile.write(" \n")
|
1136
1158
|
path = path.parent
|
1137
1159
|
|
@@ -1144,9 +1166,9 @@ def _ensure_inits(paths: List[Path]):
|
|
1144
1166
|
help="""
|
1145
1167
|
Optional jinja2 template directory to use for class generation.
|
1146
1168
|
|
1147
|
-
Pass a directory containing templates with the same name as any of the default
|
1148
|
-
:class:`.PydanticTemplateModel` templates to override them. The given directory will be
|
1149
|
-
searched for matching templates, and use the default templates as a fallback
|
1169
|
+
Pass a directory containing templates with the same name as any of the default
|
1170
|
+
:class:`.PydanticTemplateModel` templates to override them. The given directory will be
|
1171
|
+
searched for matching templates, and use the default templates as a fallback
|
1150
1172
|
if an override is not found
|
1151
1173
|
|
1152
1174
|
Available templates to override:
|
@@ -1,5 +1,6 @@
|
|
1
|
+
import sys
|
1
2
|
from importlib.util import find_spec
|
2
|
-
from typing import Any, ClassVar, Dict, Generator, List, Literal, Optional, Union
|
3
|
+
from typing import Any, ClassVar, Dict, Generator, List, Literal, Optional, Tuple, Union, get_args
|
3
4
|
|
4
5
|
from jinja2 import Environment, PackageLoader
|
5
6
|
from pydantic import BaseModel, Field, field_validator
|
@@ -28,6 +29,14 @@ else:
|
|
28
29
|
return f
|
29
30
|
|
30
31
|
|
32
|
+
IMPORT_GROUPS = Literal["future", "stdlib", "thirdparty", "local", "conditional"]
|
33
|
+
"""
|
34
|
+
See :attr:`.Import.group` and :attr:`.Imports.sort`
|
35
|
+
|
36
|
+
Order of this literal is used in sort and therefore not arbitrary.
|
37
|
+
"""
|
38
|
+
|
39
|
+
|
31
40
|
class PydanticTemplateModel(TemplateModel):
|
32
41
|
"""
|
33
42
|
Metaclass to render pydantic models with jinja templates.
|
@@ -292,6 +301,28 @@ class Import(PydanticTemplateModel):
|
|
292
301
|
Used primarily in split schema generation, see :func:`.pydanticgen.generate_split` for example usage.
|
293
302
|
"""
|
294
303
|
|
304
|
+
@computed_field
|
305
|
+
def group(self) -> IMPORT_GROUPS:
|
306
|
+
"""
|
307
|
+
Import group used when sorting
|
308
|
+
|
309
|
+
* ``future`` - from `__future__` import...
|
310
|
+
* ``stdlib`` - ... the standard library
|
311
|
+
* ``thirdparty`` - other dependencies not in the standard library
|
312
|
+
* ``local`` - relative imports (eg. from split generation)
|
313
|
+
* ``conditional`` - a :class:`.ConditionalImport`
|
314
|
+
"""
|
315
|
+
if self.module == "__future__":
|
316
|
+
return "future"
|
317
|
+
elif sys.version_info.minor >= 10 and self.module in sys.stdlib_module_names:
|
318
|
+
return "stdlib"
|
319
|
+
elif sys.version_info.minor < 10 and self.module in _some_stdlib_module_names:
|
320
|
+
return "stdlib"
|
321
|
+
elif self.module.startswith("."):
|
322
|
+
return "local"
|
323
|
+
else:
|
324
|
+
return "thirdparty"
|
325
|
+
|
295
326
|
def merge(self, other: "Import") -> List["Import"]:
|
296
327
|
"""
|
297
328
|
Merge one import with another, see :meth:`.Imports` for an example.
|
@@ -346,6 +377,16 @@ class Import(PydanticTemplateModel):
|
|
346
377
|
# one is a module, the other imports objects, keep both
|
347
378
|
return [self, other]
|
348
379
|
|
380
|
+
def sort(self) -> None:
|
381
|
+
"""
|
382
|
+
Sort imported objects
|
383
|
+
|
384
|
+
* First by whether the first letter is capitalized or not,
|
385
|
+
* Then alphabetically (by object name rather than alias)
|
386
|
+
"""
|
387
|
+
if self.objects:
|
388
|
+
self.objects = sorted(self.objects, key=lambda obj: (obj.name[0].islower(), obj.name))
|
389
|
+
|
349
390
|
|
350
391
|
class ConditionalImport(Import):
|
351
392
|
"""
|
@@ -391,6 +432,17 @@ class ConditionalImport(Import):
|
|
391
432
|
condition: str
|
392
433
|
alternative: Import
|
393
434
|
|
435
|
+
@computed_field
|
436
|
+
def group(self) -> Literal["conditional"]:
|
437
|
+
return "conditional"
|
438
|
+
|
439
|
+
def sort(self) -> None:
|
440
|
+
"""
|
441
|
+
:meth:`.Import.sort` called for self and :attr:`.alternative`
|
442
|
+
"""
|
443
|
+
super(ConditionalImport, self).sort()
|
444
|
+
self.alternative.sort()
|
445
|
+
|
394
446
|
|
395
447
|
class Imports(PydanticTemplateModel):
|
396
448
|
"""
|
@@ -427,6 +479,10 @@ class Imports(PydanticTemplateModel):
|
|
427
479
|
template: ClassVar[str] = "imports.py.jinja"
|
428
480
|
|
429
481
|
imports: List[Union[Import, ConditionalImport]] = Field(default_factory=list)
|
482
|
+
group_order: Tuple[str, ...] = get_args(IMPORT_GROUPS)
|
483
|
+
"""Order in which to sort imports by their :attr:`.Import.group`"""
|
484
|
+
render_sorted: bool = True
|
485
|
+
"""When rendering, render in sorted groups"""
|
430
486
|
|
431
487
|
@classmethod
|
432
488
|
def _merge(
|
@@ -484,13 +540,17 @@ class Imports(PydanticTemplateModel):
|
|
484
540
|
break
|
485
541
|
|
486
542
|
# SPECIAL CASE - __future__ annotations must happen at the top of a file
|
543
|
+
# sort here outside of sort method because our imports are invalid without it,
|
544
|
+
# where calling ``sort`` should be optional.
|
487
545
|
imports = sorted(imports, key=lambda i: i.module == "__future__", reverse=True)
|
488
546
|
return imports
|
489
547
|
|
490
548
|
def __add__(self, other: Union[Import, "Imports", List[Import]]) -> "Imports":
|
491
549
|
imports = self.imports.copy()
|
492
550
|
imports = self._merge(imports, other)
|
493
|
-
return Imports.model_construct(
|
551
|
+
return Imports.model_construct(
|
552
|
+
imports=imports, **{k: getattr(self, k, None) for k in self.model_fields if k != "imports"}
|
553
|
+
)
|
494
554
|
|
495
555
|
def __len__(self) -> int:
|
496
556
|
return len(self.imports)
|
@@ -552,6 +612,36 @@ class Imports(PydanticTemplateModel):
|
|
552
612
|
merged_imports = cls._merge(merged_imports, i)
|
553
613
|
return merged_imports
|
554
614
|
|
615
|
+
@computed_field
|
616
|
+
def import_groups(self) -> List[IMPORT_GROUPS]:
|
617
|
+
"""
|
618
|
+
List of what group each import belongs to
|
619
|
+
"""
|
620
|
+
return [i.group for i in self.imports]
|
621
|
+
|
622
|
+
def sort(self) -> None:
|
623
|
+
"""
|
624
|
+
Sort imports recursively, mimicking isort:
|
625
|
+
|
626
|
+
* First by :attr:`.Import.group` according to :attr:`.Imports.group_order`
|
627
|
+
* Then by whether the :class:`.Import` has any objects
|
628
|
+
(``import module`` comes before ``from module import name``)
|
629
|
+
* Then alphabetically by module name
|
630
|
+
"""
|
631
|
+
|
632
|
+
def _sort_key(i: Import) -> Tuple[int, int, str]:
|
633
|
+
return (self.group_order.index(i.group), int(i.objects is not None), i.module)
|
634
|
+
|
635
|
+
imports = sorted(self.imports, key=_sort_key)
|
636
|
+
for i in imports:
|
637
|
+
i.sort()
|
638
|
+
self.imports = imports
|
639
|
+
|
640
|
+
def render(self, environment: Optional[Environment] = None, black: bool = False) -> str:
|
641
|
+
if self.render_sorted:
|
642
|
+
self.sort()
|
643
|
+
return super(Imports, self).render(environment=environment, black=black)
|
644
|
+
|
555
645
|
|
556
646
|
class PydanticModule(PydanticTemplateModel):
|
557
647
|
"""
|
@@ -565,7 +655,7 @@ class PydanticModule(PydanticTemplateModel):
|
|
565
655
|
version: Optional[str] = None
|
566
656
|
base_model: PydanticBaseModel = PydanticBaseModel()
|
567
657
|
injected_classes: Optional[List[str]] = None
|
568
|
-
python_imports: List[Union[Import, ConditionalImport]] =
|
658
|
+
python_imports: Union[Imports, List[Union[Import, ConditionalImport]]] = Imports()
|
569
659
|
enums: Dict[str, PydanticEnum] = Field(default_factory=dict)
|
570
660
|
classes: Dict[str, PydanticClass] = Field(default_factory=dict)
|
571
661
|
meta: Optional[Dict[str, Any]] = None
|
@@ -573,6 +663,21 @@ class PydanticModule(PydanticTemplateModel):
|
|
573
663
|
Metadata for the schema to be included in a linkml_meta module-level instance of LinkMLMeta
|
574
664
|
"""
|
575
665
|
|
666
|
+
@field_validator("python_imports", mode="after")
|
667
|
+
@classmethod
|
668
|
+
def cast_imports(cls, imports: Union[Imports, List[Union[Import, ConditionalImport]]]) -> Imports:
|
669
|
+
if isinstance(imports, list):
|
670
|
+
imports = Imports(imports=imports)
|
671
|
+
return imports
|
672
|
+
|
576
673
|
@computed_field
|
577
674
|
def class_names(self) -> List[str]:
|
578
675
|
return [c.name for c in self.classes.values()]
|
676
|
+
|
677
|
+
|
678
|
+
_some_stdlib_module_names = {"copy", "datetime", "decimal", "enum", "inspect", "os", "re", "sys", "typing"}
|
679
|
+
"""
|
680
|
+
sys.stdlib_module_names is only present in 3.10 and later
|
681
|
+
so we make a cheap copy of the stdlib modules that we commonly use here,
|
682
|
+
but this should be removed whenever support for 3.9 is dropped.
|
683
|
+
"""
|
@@ -20,12 +20,20 @@ from {{ module }} import (
|
|
20
20
|
{%- endif %}
|
21
21
|
{%- endif %}
|
22
22
|
{% endmacro %}
|
23
|
+
{#- For when used with Import model -#}
|
23
24
|
{%- if module %}
|
24
25
|
{{ import_(module, alias, objects) }}
|
25
26
|
{% endif -%}
|
26
27
|
{#- For when used with Imports container -#}
|
27
28
|
{%- if imports -%}
|
28
|
-
{%-
|
29
|
-
{
|
30
|
-
{
|
29
|
+
{%- if render_sorted -%}
|
30
|
+
{% for i in range(imports | length) %}
|
31
|
+
{{ imports[i] }}
|
32
|
+
{%- if not loop.last and import_groups[i] != import_groups[i+1] %}{{ '\n' }}{% endif -%}
|
33
|
+
{% endfor %}
|
34
|
+
{%- else -%}
|
35
|
+
{%- for import in imports -%}
|
36
|
+
{{ import }}
|
37
|
+
{%- endfor -%}
|
38
|
+
{%- endif -%}
|
31
39
|
{% endif -%}
|
@@ -3,9 +3,9 @@
|
|
3
3
|
pattern=re.compile(r"{{pattern}}")
|
4
4
|
if isinstance(v,list):
|
5
5
|
for element in v:
|
6
|
-
if not pattern.match(element):
|
6
|
+
if isinstance(v, str) and not pattern.match(element):
|
7
7
|
raise ValueError(f"Invalid {{name}} format: {element}")
|
8
8
|
elif isinstance(v,str):
|
9
9
|
if not pattern.match(v):
|
10
10
|
raise ValueError(f"Invalid {{name}} format: {v}")
|
11
|
-
return v
|
11
|
+
return v
|
@@ -0,0 +1 @@
|
|
1
|
+
__all__ = ["python_ifabsent_processor"]
|
@@ -0,0 +1,92 @@
|
|
1
|
+
from linkml_runtime.linkml_model import (
|
2
|
+
ClassDefinition,
|
3
|
+
EnumDefinitionName,
|
4
|
+
SlotDefinition,
|
5
|
+
)
|
6
|
+
|
7
|
+
from linkml.generators.common.ifabsent_processor import IfAbsentProcessor
|
8
|
+
|
9
|
+
|
10
|
+
class PythonIfAbsentProcessor(IfAbsentProcessor):
|
11
|
+
UNIMPLEMENTED_DEFAULT_VALUES = ["class_curie", "class_uri", "slot_uri", "slot_curie", "default_range", "default_ns"]
|
12
|
+
|
13
|
+
def map_custom_default_values(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition) -> (bool, str):
|
14
|
+
if default_value in self.UNIMPLEMENTED_DEFAULT_VALUES:
|
15
|
+
return True, None
|
16
|
+
|
17
|
+
if default_value == "bnode":
|
18
|
+
return True, "bnode()"
|
19
|
+
|
20
|
+
return False, None
|
21
|
+
|
22
|
+
def map_string_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
23
|
+
return self._strval(default_value)
|
24
|
+
|
25
|
+
def map_boolean_true_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
|
26
|
+
return "True"
|
27
|
+
|
28
|
+
def map_boolean_false_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
|
29
|
+
return "False"
|
30
|
+
|
31
|
+
def map_integer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
32
|
+
return default_value
|
33
|
+
|
34
|
+
def map_float_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
35
|
+
return default_value
|
36
|
+
|
37
|
+
def map_double_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
38
|
+
return default_value
|
39
|
+
|
40
|
+
def map_decimal_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
41
|
+
return default_value
|
42
|
+
|
43
|
+
def map_time_default_value(self, hour: str, minutes: str, seconds: str, slot: SlotDefinition, cls: ClassDefinition):
|
44
|
+
return f"time({int(hour)}, {int(minutes)}, {int(seconds)})"
|
45
|
+
|
46
|
+
def map_date_default_value(self, year: str, month: str, day: str, slot: SlotDefinition, cls: ClassDefinition):
|
47
|
+
return f"date({int(year)}, {int(month)}, {int(day)})"
|
48
|
+
|
49
|
+
def map_datetime_default_value(
|
50
|
+
self,
|
51
|
+
year: str,
|
52
|
+
month: str,
|
53
|
+
day: str,
|
54
|
+
hour: str,
|
55
|
+
minutes: str,
|
56
|
+
seconds: str,
|
57
|
+
slot: SlotDefinition,
|
58
|
+
cls: ClassDefinition,
|
59
|
+
):
|
60
|
+
return f"datetime({int(year)}, {int(month)}, {int(day)}, " f"{int(hour)}, {int(minutes)}, {int(seconds)})"
|
61
|
+
|
62
|
+
def map_uri_or_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
63
|
+
return self._uri_for(default_value)
|
64
|
+
|
65
|
+
def map_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
66
|
+
return self._uri_for(default_value)
|
67
|
+
|
68
|
+
def map_uri_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
69
|
+
return self._uri_for(default_value)
|
70
|
+
|
71
|
+
def map_enum_default_value(
|
72
|
+
self, enum_name: EnumDefinitionName, permissible_value_name: str, slot: SlotDefinition, cls: ClassDefinition
|
73
|
+
):
|
74
|
+
return f"{enum_name}.{permissible_value_name}"
|
75
|
+
|
76
|
+
def map_nc_name_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
77
|
+
raise NotImplementedError()
|
78
|
+
|
79
|
+
def map_object_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
80
|
+
raise NotImplementedError()
|
81
|
+
|
82
|
+
def map_node_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
83
|
+
raise NotImplementedError()
|
84
|
+
|
85
|
+
def map_json_pointer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
86
|
+
raise NotImplementedError()
|
87
|
+
|
88
|
+
def map_json_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
89
|
+
raise NotImplementedError()
|
90
|
+
|
91
|
+
def map_sparql_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
92
|
+
raise NotImplementedError()
|