linkml 1.5.5__py3-none-any.whl → 1.5.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.
- linkml/__init__.py +2 -6
- linkml/_version.py +1 -1
- linkml/generators/PythonGenNotes.md +4 -4
- linkml/generators/__init__.py +26 -5
- linkml/generators/common/type_designators.py +27 -22
- linkml/generators/csvgen.py +4 -10
- linkml/generators/docgen/class.md.jinja2 +7 -0
- linkml/generators/docgen/class_diagram.md.jinja2 +0 -6
- linkml/generators/docgen/subset.md.jinja2 +54 -13
- linkml/generators/docgen.py +94 -92
- linkml/generators/dotgen.py +5 -9
- linkml/generators/erdiagramgen.py +58 -53
- linkml/generators/excelgen.py +10 -16
- linkml/generators/golanggen.py +11 -21
- linkml/generators/golrgen.py +4 -13
- linkml/generators/graphqlgen.py +3 -11
- linkml/generators/javagen.py +8 -15
- linkml/generators/jsonldcontextgen.py +7 -36
- linkml/generators/jsonldgen.py +14 -12
- linkml/generators/jsonschemagen.py +183 -136
- linkml/generators/linkmlgen.py +1 -1
- linkml/generators/markdowngen.py +40 -89
- linkml/generators/namespacegen.py +1 -2
- linkml/generators/oocodegen.py +22 -25
- linkml/generators/owlgen.py +48 -49
- linkml/generators/prefixmapgen.py +6 -14
- linkml/generators/projectgen.py +7 -14
- linkml/generators/protogen.py +3 -5
- linkml/generators/pydanticgen.py +85 -73
- linkml/generators/pythongen.py +89 -157
- linkml/generators/rdfgen.py +5 -11
- linkml/generators/shaclgen.py +32 -18
- linkml/generators/shexgen.py +19 -24
- linkml/generators/sparqlgen.py +5 -13
- linkml/generators/sqlalchemy/__init__.py +3 -2
- linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +7 -7
- linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +3 -3
- linkml/generators/sqlalchemygen.py +29 -27
- linkml/generators/sqlddlgen.py +34 -26
- linkml/generators/sqltablegen.py +21 -21
- linkml/generators/sssomgen.py +11 -13
- linkml/generators/summarygen.py +2 -4
- linkml/generators/terminusdbgen.py +7 -19
- linkml/generators/typescriptgen.py +10 -18
- linkml/generators/yamlgen.py +0 -2
- linkml/generators/yumlgen.py +23 -71
- linkml/linter/cli.py +4 -11
- linkml/linter/config/datamodel/config.py +17 -47
- linkml/linter/linter.py +2 -4
- linkml/linter/rules.py +34 -48
- linkml/reporting/__init__.py +2 -0
- linkml/reporting/model.py +9 -24
- linkml/transformers/relmodel_transformer.py +20 -33
- linkml/transformers/schema_renamer.py +14 -10
- linkml/utils/converter.py +15 -15
- linkml/utils/datautils.py +9 -24
- linkml/utils/datavalidator.py +2 -2
- linkml/utils/execute_tutorial.py +10 -12
- linkml/utils/generator.py +74 -92
- linkml/utils/helpers.py +4 -2
- linkml/utils/ifabsent_functions.py +23 -15
- linkml/utils/mergeutils.py +19 -35
- linkml/utils/rawloader.py +2 -6
- linkml/utils/schema_builder.py +31 -19
- linkml/utils/schema_fixer.py +28 -18
- linkml/utils/schemaloader.py +44 -89
- linkml/utils/schemasynopsis.py +50 -73
- linkml/utils/sqlutils.py +40 -30
- linkml/utils/typereferences.py +9 -6
- linkml/utils/validation.py +4 -5
- linkml/validators/__init__.py +2 -0
- linkml/validators/jsonschemavalidator.py +104 -53
- linkml/validators/sparqlvalidator.py +5 -15
- linkml/workspaces/datamodel/workspaces.py +13 -30
- linkml/workspaces/example_runner.py +75 -68
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/METADATA +2 -2
- linkml-1.5.7.dist-info/RECORD +109 -0
- linkml-1.5.5.dist-info/RECORD +0 -109
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/LICENSE +0 -0
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/WHEEL +0 -0
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/entry_points.txt +0 -0
linkml/generators/pythongen.py
CHANGED
@@ -8,26 +8,37 @@ from typing import Callable, Dict, Iterator, List, Optional, Set, Tuple, Union
|
|
8
8
|
|
9
9
|
import click
|
10
10
|
from linkml_runtime.linkml_model import linkml_files
|
11
|
-
from linkml_runtime.linkml_model.meta import (
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
from linkml_runtime.linkml_model.meta import (
|
12
|
+
ClassDefinition,
|
13
|
+
ClassDefinitionName,
|
14
|
+
DefinitionName,
|
15
|
+
Element,
|
16
|
+
EnumDefinition,
|
17
|
+
PermissibleValue,
|
18
|
+
SlotDefinition,
|
19
|
+
SlotDefinitionName,
|
20
|
+
TypeDefinition,
|
21
|
+
)
|
18
22
|
from linkml_runtime.utils.compile_python import compile_python
|
19
|
-
from linkml_runtime.utils.formatutils import (
|
20
|
-
|
23
|
+
from linkml_runtime.utils.formatutils import (
|
24
|
+
be,
|
25
|
+
camelcase,
|
26
|
+
sfx,
|
27
|
+
split_col,
|
28
|
+
underscore,
|
29
|
+
wrapped_annotation,
|
30
|
+
)
|
21
31
|
from linkml_runtime.utils.metamodelcore import builtinnames
|
22
32
|
from rdflib import URIRef
|
23
33
|
|
24
34
|
import linkml
|
25
35
|
from linkml._version import __version__
|
26
|
-
from linkml.generators import PYTHON_GEN_VERSION
|
27
36
|
from linkml.utils.generator import Generator, shared_arguments
|
28
|
-
from linkml.utils.ifabsent_functions import (
|
29
|
-
|
30
|
-
|
37
|
+
from linkml.utils.ifabsent_functions import (
|
38
|
+
default_curie_or_uri,
|
39
|
+
ifabsent_postinit_declaration,
|
40
|
+
ifabsent_value_declaration,
|
41
|
+
)
|
31
42
|
|
32
43
|
|
33
44
|
@dataclass
|
@@ -40,8 +51,9 @@ class PythonGenerator(Generator):
|
|
40
51
|
|
41
52
|
# ClassVars
|
42
53
|
generatorname = os.path.basename(__file__)
|
43
|
-
generatorversion =
|
54
|
+
generatorversion = "0.0.1"
|
44
55
|
valid_formats = ["py"]
|
56
|
+
file_extension = "py"
|
45
57
|
visit_all_class_slots = False
|
46
58
|
uses_schemaloader = True
|
47
59
|
|
@@ -57,9 +69,7 @@ class PythonGenerator(Generator):
|
|
57
69
|
if self.format is None:
|
58
70
|
self.format = self.valid_formats[0]
|
59
71
|
if self.schema.default_prefix == "linkml" and not self.genmeta:
|
60
|
-
logging.error(
|
61
|
-
f"Generating metamodel without --genmeta is highly inadvisable!"
|
62
|
-
)
|
72
|
+
logging.error("Generating metamodel without --genmeta is highly inadvisable!")
|
63
73
|
if (
|
64
74
|
not self.schema.source_file
|
65
75
|
and isinstance(self.sourcefile, str)
|
@@ -82,18 +92,14 @@ class PythonGenerator(Generator):
|
|
82
92
|
|
83
93
|
def visit_schema(self, **kwargs) -> None:
|
84
94
|
# Add explicitly declared prefixes
|
85
|
-
self.emit_prefixes.update(
|
86
|
-
[p.prefix_prefix for p in self.schema.prefixes.values()]
|
87
|
-
)
|
95
|
+
self.emit_prefixes.update([p.prefix_prefix for p in self.schema.prefixes.values()])
|
88
96
|
|
89
97
|
# Add all emit statements
|
90
98
|
self.emit_prefixes.update(self.schema.emit_prefixes)
|
91
99
|
|
92
100
|
# Add the default prefix
|
93
101
|
if self.schema.default_prefix:
|
94
|
-
self.emit_prefixes.add(
|
95
|
-
self.namespaces.prefix_for(self.schema.default_prefix)
|
96
|
-
)
|
102
|
+
self.emit_prefixes.add(self.namespaces.prefix_for(self.schema.default_prefix))
|
97
103
|
|
98
104
|
def visit_class(self, cls: ClassDefinition) -> bool:
|
99
105
|
if not cls.imported_from:
|
@@ -123,9 +129,7 @@ class PythonGenerator(Generator):
|
|
123
129
|
if self.genmeta
|
124
130
|
else "from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue, PvFormulaOptions\n"
|
125
131
|
)
|
126
|
-
handlerimport =
|
127
|
-
"from linkml_runtime.utils.enumerations import EnumDefinitionImpl"
|
128
|
-
)
|
132
|
+
handlerimport = "from linkml_runtime.utils.enumerations import EnumDefinitionImpl"
|
129
133
|
split_description = ""
|
130
134
|
if self.schema.description:
|
131
135
|
split_description = "\n# ".join(
|
@@ -191,9 +195,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
191
195
|
)
|
192
196
|
|
193
197
|
def gen_imports(self) -> str:
|
194
|
-
listents = [
|
195
|
-
f"from {k} import {', '.join(v)}" for k, v in self.gen_import_list().items()
|
196
|
-
]
|
198
|
+
listents = [f"from {k} import {', '.join(v)}" for k, v in self.gen_import_list().items()]
|
197
199
|
return "\n".join(listents)
|
198
200
|
|
199
201
|
def gen_import_list(self) -> Dict[str, List[str]]:
|
@@ -220,17 +222,13 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
220
222
|
model_base + path[len(linkml_files.LINKML_NAMESPACE) :], set()
|
221
223
|
).add(name)
|
222
224
|
elif path == linkml.BIOLINK_MODEL_URI:
|
223
|
-
innerself.v.setdefault(linkml.BIOLINK_MODEL_PYTHON_LOC, set()).add(
|
224
|
-
name
|
225
|
-
)
|
225
|
+
innerself.v.setdefault(linkml.BIOLINK_MODEL_PYTHON_LOC, set()).add(name)
|
226
226
|
elif "://" in path:
|
227
|
-
raise ValueError(
|
228
|
-
f"Cannot map {path} into a python import statement"
|
229
|
-
)
|
227
|
+
raise ValueError(f"Cannot map {path} into a python import statement")
|
230
228
|
elif "/" in path:
|
231
|
-
innerself.v.setdefault(
|
232
|
-
|
233
|
-
)
|
229
|
+
innerself.v.setdefault(path.replace("./", ".").replace("/", "."), set()).add(
|
230
|
+
name
|
231
|
+
)
|
234
232
|
elif "." in path:
|
235
233
|
innerself.v.setdefault(path, set()).add(name)
|
236
234
|
else:
|
@@ -262,9 +260,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
262
260
|
cls = self.schema.classes[slot.range]
|
263
261
|
if cls.imported_from:
|
264
262
|
if self.class_identifier(cls):
|
265
|
-
identifier_range = self.class_identifier_path(cls, False)[
|
266
|
-
-1
|
267
|
-
]
|
263
|
+
identifier_range = self.class_identifier_path(cls, False)[-1]
|
268
264
|
if identifier_range in self.schema.types:
|
269
265
|
add_type_ref(TypeDefinition(identifier_range))
|
270
266
|
else:
|
@@ -313,7 +309,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
313
309
|
)
|
314
310
|
return "\n".join(
|
315
311
|
[
|
316
|
-
f"{pfx.upper().replace('.', '_').replace('-', '_')} = CurieNamespace('{pfx.replace('.', '_')}', '{self.namespaces[pfx]}')"
|
312
|
+
f"{pfx.upper().replace('.', '_').replace('-', '_')} = CurieNamespace('{pfx.replace('.', '_')}', '{self.namespaces[pfx]}')" # noqa: E501
|
317
313
|
for pfx in sorted(self.emit_prefixes)
|
318
314
|
if pfx in self.namespaces
|
319
315
|
]
|
@@ -328,22 +324,15 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
328
324
|
pkeys = self.primary_keys_for(cls)
|
329
325
|
if pkeys:
|
330
326
|
for pk in pkeys:
|
331
|
-
classname = camelcase(cls.name) + camelcase(
|
332
|
-
self.aliased_slot_name(pk)
|
333
|
-
)
|
327
|
+
classname = camelcase(cls.name) + camelcase(self.aliased_slot_name(pk))
|
334
328
|
# If we've got a parent slot and the range of the parent is the range of the child, the
|
335
329
|
# child slot is a subclass of the parent. Otherwise, the child range has been overridden,
|
336
|
-
# so the
|
337
|
-
parent_pk = (
|
338
|
-
|
339
|
-
)
|
340
|
-
parent_pk_slot = (
|
341
|
-
self.schema.slots[parent_pk] if parent_pk else None
|
342
|
-
)
|
330
|
+
# so the inheritance chain has been broken
|
331
|
+
parent_pk = self.class_identifier(cls.is_a) if cls.is_a else None
|
332
|
+
parent_pk_slot = self.schema.slots[parent_pk] if parent_pk else None
|
343
333
|
pk_slot = self.schema.slots[pk]
|
344
334
|
if parent_pk_slot and (
|
345
|
-
parent_pk_slot.name == pk
|
346
|
-
or pk_slot.range == parent_pk_slot.range
|
335
|
+
parent_pk_slot.name == pk or pk_slot.range == parent_pk_slot.range
|
347
336
|
):
|
348
337
|
parents = self.class_identifier_path(cls.is_a, False)
|
349
338
|
else:
|
@@ -360,29 +349,25 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
360
349
|
def gen_typedefs(self) -> str:
|
361
350
|
"""Generate python type declarations for all defined types"""
|
362
351
|
rval = []
|
363
|
-
defs_to_generate = [
|
364
|
-
x for x in self.schema.types.values() if not x.imported_from
|
365
|
-
]
|
352
|
+
defs_to_generate = [x for x in self.schema.types.values() if not x.imported_from]
|
366
353
|
emitted_types = []
|
367
|
-
|
368
|
-
emitted_types.extend(
|
369
|
-
[x.name for x in self.schema.types.values() if x.imported_from]
|
370
|
-
)
|
354
|
+
# all imported_from types are already considered generated
|
355
|
+
emitted_types.extend([x.name for x in self.schema.types.values() if x.imported_from])
|
371
356
|
for typ in [x for x in defs_to_generate if not x.typeof]:
|
372
357
|
self._gen_typedef(typ, typ.base.rsplit(".")[-1], rval, emitted_types)
|
373
358
|
|
374
359
|
while True:
|
375
360
|
defs_to_generate_typeof = [
|
376
|
-
x for x in defs_to_generate if x.typeof and
|
361
|
+
x for x in defs_to_generate if x.typeof and x.name not in emitted_types
|
377
362
|
]
|
378
363
|
if len(defs_to_generate_typeof) == 0:
|
379
364
|
break
|
380
|
-
defs_can_generate = [
|
381
|
-
x for x in defs_to_generate_typeof if x.typeof in emitted_types
|
382
|
-
]
|
365
|
+
defs_can_generate = [x for x in defs_to_generate_typeof if x.typeof in emitted_types]
|
383
366
|
if len(defs_can_generate) == 0:
|
384
367
|
raise ValueError(
|
385
|
-
|
368
|
+
"Cannot generate type definition for "
|
369
|
+
f"{[f'{x.name} of {x.typeof}' for x in defs_to_generate_typeof]}. "
|
370
|
+
"Forgot a link in the type hierarchy chain?"
|
386
371
|
)
|
387
372
|
for typ in defs_can_generate:
|
388
373
|
self._gen_typedef(typ, camelcase(typ.typeof), rval, emitted_types)
|
@@ -395,9 +380,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
395
380
|
if typ.description:
|
396
381
|
description = typ.description.replace('"""', "---")
|
397
382
|
desc = f'\n\t""" {description} """'
|
398
|
-
rval.append(
|
399
|
-
f"class {typname}({superclass}):{desc}\n\t{self.gen_type_meta(typ)}\n\n"
|
400
|
-
)
|
383
|
+
rval.append(f"class {typname}({superclass}):{desc}\n\t{self.gen_type_meta(typ)}\n\n")
|
401
384
|
emitted_types.append(typ.name)
|
402
385
|
|
403
386
|
def gen_classdefs(self) -> str:
|
@@ -459,9 +442,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
459
442
|
class_class_curie = None
|
460
443
|
if class_class_curie:
|
461
444
|
class_class_curie = f'"{class_class_curie}"'
|
462
|
-
class_class_uri = (
|
463
|
-
cls_python_uri if cls_python_uri else f'URIRef("{class_class_uri}")'
|
464
|
-
)
|
445
|
+
class_class_uri = cls_python_uri if cls_python_uri else f'URIRef("{class_class_uri}")'
|
465
446
|
class_model_uri = self.namespaces.uri_or_curie_for(
|
466
447
|
self.schema.default_prefix or "DEFAULT_", camelcase(cls.name)
|
467
448
|
)
|
@@ -493,9 +474,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
493
474
|
type_class_curie = None
|
494
475
|
if type_class_curie:
|
495
476
|
type_class_curie = f'"{type_class_curie}"'
|
496
|
-
type_class_uri = (
|
497
|
-
type_python_uri if type_python_uri else f'URIRef("{type_class_uri}")'
|
498
|
-
)
|
477
|
+
type_class_uri = type_python_uri if type_python_uri else f'URIRef("{type_class_uri}")'
|
499
478
|
type_model_uri = self.namespaces.uri_or_curie_for(
|
500
479
|
self.schema.default_prefix, camelcase(typ.name)
|
501
480
|
)
|
@@ -517,7 +496,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
517
496
|
"""
|
518
497
|
Generate the variable declarations for a dataclass.
|
519
498
|
|
520
|
-
:param cls: class containing variables to be rendered in
|
499
|
+
:param cls: class containing variables to be rendered in inheritance hierarchy
|
521
500
|
:return: variable declarations for target class and its ancestors
|
522
501
|
"""
|
523
502
|
initializers = []
|
@@ -532,9 +511,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
532
511
|
lambda slot: (slot.identifier or slot.key) and not slot.ifabsent,
|
533
512
|
first_hit_only=True,
|
534
513
|
)
|
535
|
-
initializers += [
|
536
|
-
self.gen_class_variable(cls, slot, not is_root) for slot in slot_variables
|
537
|
-
]
|
514
|
+
initializers += [self.gen_class_variable(cls, slot, not is_root) for slot in slot_variables]
|
538
515
|
|
539
516
|
# Required slots
|
540
517
|
slot_variables = self._slot_iter(
|
@@ -544,25 +521,17 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
544
521
|
and not slot.key
|
545
522
|
and not slot.ifabsent,
|
546
523
|
)
|
547
|
-
initializers += [
|
548
|
-
self.gen_class_variable(cls, slot, not is_root) for slot in slot_variables
|
549
|
-
]
|
524
|
+
initializers += [self.gen_class_variable(cls, slot, not is_root) for slot in slot_variables]
|
550
525
|
|
551
526
|
# Required or key slots with default values
|
552
|
-
slot_variables = self._slot_iter(
|
553
|
-
|
554
|
-
)
|
555
|
-
initializers += [
|
556
|
-
self.gen_class_variable(cls, slot, False) for slot in slot_variables
|
557
|
-
]
|
527
|
+
slot_variables = self._slot_iter(cls, lambda slot: slot.ifabsent and slot.required)
|
528
|
+
initializers += [self.gen_class_variable(cls, slot, False) for slot in slot_variables]
|
558
529
|
|
559
530
|
# Followed by everything else
|
560
531
|
slot_variables = self._slot_iter(
|
561
532
|
cls, lambda slot: not slot.required and slot in domain_slots
|
562
533
|
)
|
563
|
-
initializers += [
|
564
|
-
self.gen_class_variable(cls, slot, False) for slot in slot_variables
|
565
|
-
]
|
534
|
+
initializers += [self.gen_class_variable(cls, slot, False) for slot in slot_variables]
|
566
535
|
|
567
536
|
return "\n\t".join(initializers)
|
568
537
|
|
@@ -618,9 +587,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
618
587
|
pkey = self.class_identifier(slot.range)
|
619
588
|
# Special case, inlined, identified range
|
620
589
|
if pkey and slot.inlined and slot.multivalued:
|
621
|
-
base_key = self.gen_class_reference(
|
622
|
-
self.class_identifier_path(slot.range, False)
|
623
|
-
)
|
590
|
+
base_key = self.gen_class_reference(self.class_identifier_path(slot.range, False))
|
624
591
|
num_elements = len(self.schema.classes[slot.range].slots)
|
625
592
|
dflt = None if slot.required and positional_allowed else "empty_dict()"
|
626
593
|
if num_elements == 1:
|
@@ -709,12 +676,8 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
709
676
|
if slot.ifabsent:
|
710
677
|
dflt = ifabsent_postinit_declaration(slot.ifabsent, self, cls, slot)
|
711
678
|
if dflt and dflt != "None":
|
712
|
-
post_inits_pre_super.append(
|
713
|
-
|
714
|
-
)
|
715
|
-
post_inits_pre_super.append(
|
716
|
-
f"\tself.{self.slot_name(slot.name)} = {dflt}"
|
717
|
-
)
|
679
|
+
post_inits_pre_super.append(f"if self.{self.slot_name(slot.name)} is None:")
|
680
|
+
post_inits_pre_super.append(f"\tself.{self.slot_name(slot.name)} = {dflt}")
|
718
681
|
|
719
682
|
post_inits = []
|
720
683
|
if not (cls.mixin or cls.abstract):
|
@@ -737,9 +700,9 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
737
700
|
if slot.name not in pkeys and (not slot.ifabsent or True):
|
738
701
|
post_inits.append(self.gen_postinit(cls, slot))
|
739
702
|
|
740
|
-
post_inits_pre_super_line = "\n\t\t".join(
|
741
|
-
|
742
|
-
)
|
703
|
+
post_inits_pre_super_line = "\n\t\t".join([p for p in post_inits_pre_super if p]) + (
|
704
|
+
"\n\t\t" if post_inits_pre_super else ""
|
705
|
+
)
|
743
706
|
post_inits_line = "\n\t\t".join([p for p in post_inits if p])
|
744
707
|
return (
|
745
708
|
(
|
@@ -771,9 +734,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
771
734
|
del clist[i]
|
772
735
|
break
|
773
736
|
if not can_add:
|
774
|
-
raise (
|
775
|
-
f"could not find suitable element in {clist} that does not ref {slist}"
|
776
|
-
)
|
737
|
+
raise (f"could not find suitable element in {clist} that does not ref {slist}")
|
777
738
|
return slist
|
778
739
|
|
779
740
|
def is_key_value_class(self, range_name: DefinitionName) -> bool:
|
@@ -801,8 +762,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
801
762
|
aliased_slot_name = self.slot_name(
|
802
763
|
slot.name
|
803
764
|
) # Mangled name by which the slot is known in python
|
804
|
-
|
805
|
-
slot_identifier = self.class_identifier(slot.range)
|
765
|
+
_, _, base_type_name = self.class_reference_type(slot, cls)
|
806
766
|
|
807
767
|
# Generate existence check for required slots. Note that inherited classes have to do post init checks because
|
808
768
|
# You can't have required elements after optional elements in the parent class
|
@@ -811,23 +771,17 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
811
771
|
rlines.append(f'\tself.MissingRequiredField("{aliased_slot_name}")')
|
812
772
|
|
813
773
|
# Generate the type co-orcion for the various types.
|
814
|
-
indent = len(f"self.{aliased_slot_name} = [") * " "
|
815
774
|
# NOTE: if you set this to true, we will cast all types. This may be what we really want
|
816
775
|
if not slot.multivalued:
|
817
776
|
if slot.required:
|
818
|
-
rlines.append(
|
819
|
-
f"if not isinstance(self.{aliased_slot_name}, {base_type_name}):"
|
820
|
-
)
|
777
|
+
rlines.append(f"if not isinstance(self.{aliased_slot_name}, {base_type_name}):")
|
821
778
|
else:
|
822
779
|
rlines.append(
|
823
780
|
f"if self.{aliased_slot_name} is not None and "
|
824
781
|
f"not isinstance(self.{aliased_slot_name}, {base_type_name}):"
|
825
782
|
)
|
826
|
-
# A really
|
827
|
-
if
|
828
|
-
slot.range in self.schema.classes
|
829
|
-
and not self.schema.classes[slot.range].slots
|
830
|
-
):
|
783
|
+
# A really weird case -- a class that has no properties
|
784
|
+
if slot.range in self.schema.classes and not self.schema.classes[slot.range].slots:
|
831
785
|
rlines.append(f"\tself.{aliased_slot_name} = {base_type_name}()")
|
832
786
|
else:
|
833
787
|
if (
|
@@ -928,8 +882,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
928
882
|
return [
|
929
883
|
slot_name
|
930
884
|
for slot_name in cls.slots
|
931
|
-
if self.schema.slots[slot_name].key
|
932
|
-
or self.schema.slots[slot_name].identifier
|
885
|
+
if self.schema.slots[slot_name].key or self.schema.slots[slot_name].identifier
|
933
886
|
]
|
934
887
|
|
935
888
|
def key_name_for(self, class_name: ClassDefinitionName) -> Optional[str]:
|
@@ -949,12 +902,8 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
949
902
|
"""Determine whether slot_range is a forward reference"""
|
950
903
|
# logging.info(f"CHECKING: {slot_range} {owning_class}")
|
951
904
|
if (
|
952
|
-
slot_range in self.schema.classes
|
953
|
-
|
954
|
-
) or (
|
955
|
-
slot_range in self.schema.enums
|
956
|
-
and self.schema.enums[slot_range].imported_from
|
957
|
-
):
|
905
|
+
slot_range in self.schema.classes and self.schema.classes[slot_range].imported_from
|
906
|
+
) or (slot_range in self.schema.enums and self.schema.enums[slot_range].imported_from):
|
958
907
|
logging.info(
|
959
908
|
f"FALSE: FORWARD: {slot_range} {owning_class} // IMP={self.schema.classes[slot_range].imported_from}"
|
960
909
|
)
|
@@ -963,9 +912,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
963
912
|
return True
|
964
913
|
for cname in self.schema.classes:
|
965
914
|
if cname == owning_class:
|
966
|
-
logging.info(
|
967
|
-
f"TRUE: OCCURS SAME: {cname} == {slot_range} owning: {owning_class}"
|
968
|
-
)
|
915
|
+
logging.info(f"TRUE: OCCURS SAME: {cname} == {slot_range} owning: {owning_class}")
|
969
916
|
return True # Occurs on or after
|
970
917
|
elif cname == slot_range:
|
971
918
|
logging.info(
|
@@ -974,9 +921,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
974
921
|
return False # Occurs before
|
975
922
|
return True
|
976
923
|
|
977
|
-
def python_uri_for(
|
978
|
-
self, uriorcurie: Union[str, URIRef]
|
979
|
-
) -> Tuple[str, Optional[str]]:
|
924
|
+
def python_uri_for(self, uriorcurie: Union[str, URIRef]) -> Tuple[str, Optional[str]]:
|
980
925
|
"""Return the python form of uriorcurie
|
981
926
|
:param uriorcurie:
|
982
927
|
:return: URI and CURIE form
|
@@ -985,7 +930,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
985
930
|
if ns == "":
|
986
931
|
ns = "DEFAULT_"
|
987
932
|
if ns is None:
|
988
|
-
return
|
933
|
+
return '"str(uriorcurie)"', None
|
989
934
|
return (
|
990
935
|
ns.upper() + (f".{ln}" if ln.isidentifier() else f"['{ln}']"),
|
991
936
|
ns.upper() + f".curie('{ln}')",
|
@@ -1007,9 +952,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
1007
952
|
python_slot_name = underscore(slot.name)
|
1008
953
|
slot_uri, slot_curie = self.python_uri_for(slot.slot_uri)
|
1009
954
|
slot_model_uri, slot_model_curie = self.python_uri_for(
|
1010
|
-
self.namespaces.uri_or_curie_for(
|
1011
|
-
self.schema.default_prefix, python_slot_name
|
1012
|
-
)
|
955
|
+
self.namespaces.uri_or_curie_for(self.schema.default_prefix, python_slot_name)
|
1013
956
|
)
|
1014
957
|
domain = (
|
1015
958
|
camelcase(slot.domain)
|
@@ -1038,20 +981,14 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
1038
981
|
else:
|
1039
982
|
mappings = ""
|
1040
983
|
pattern = (
|
1041
|
-
f",\n pattern=re.compile(r'{slot.pattern}')"
|
1042
|
-
if slot.pattern
|
1043
|
-
else ""
|
984
|
+
f",\n pattern=re.compile(r'{slot.pattern}')" if slot.pattern else ""
|
1044
985
|
)
|
1045
986
|
return f"""slots.{python_slot_name} = Slot(uri={slot_uri}, name="{slot.name}", curie={slot_curie},
|
1046
987
|
model_uri={slot_model_uri}, domain={domain}, range={rnge}{mappings}{pattern})"""
|
1047
988
|
|
1048
989
|
def gen_enumerations(self) -> str:
|
1049
990
|
return "\n\n".join(
|
1050
|
-
[
|
1051
|
-
self.gen_enum(enum)
|
1052
|
-
for enum in self.schema.enums.values()
|
1053
|
-
if not enum.imported_from
|
1054
|
-
]
|
991
|
+
[self.gen_enum(enum) for enum in self.schema.enums.values() if not enum.imported_from]
|
1055
992
|
)
|
1056
993
|
|
1057
994
|
def gen_enum(self, enum: EnumDefinition) -> str:
|
@@ -1088,17 +1025,16 @@ class {enum_name}(EnumDefinitionImpl):
|
|
1088
1025
|
else None
|
1089
1026
|
)
|
1090
1027
|
desc = f"{enum_desc},\n" if enum.description else ""
|
1091
|
-
|
1092
|
-
|
1028
|
+
enum_code_set = (
|
1029
|
+
self.namespaces.curie_for(
|
1030
|
+
self.namespaces.uri_for(enum.code_set), default_ok=False, pythonform=True
|
1031
|
+
)
|
1093
1032
|
if enum.code_set
|
1094
|
-
else
|
1033
|
+
else None
|
1095
1034
|
)
|
1035
|
+
cs = f"\t\tcode_set={enum_code_set},\n" if enum_code_set else ""
|
1096
1036
|
tag = f'\t\tcode_set_tag="{enum.code_set_tag}",\n' if enum.code_set_tag else ""
|
1097
|
-
ver =
|
1098
|
-
f'\t\tcode_set_version="{enum.code_set_version}",\n'
|
1099
|
-
if enum.code_set_version
|
1100
|
-
else ""
|
1101
|
-
)
|
1037
|
+
ver = f'\t\tcode_set_version="{enum.code_set_version}",\n' if enum.code_set_version else ""
|
1102
1038
|
vf = (
|
1103
1039
|
f"\t\tpv_formula=PvFormulaOptions.{enum.pv_formula.code.text},\n"
|
1104
1040
|
if enum.pv_formula
|
@@ -1147,9 +1083,7 @@ class {enum_name}(EnumDefinitionImpl):
|
|
1147
1083
|
indent_str = indent * " "
|
1148
1084
|
pv_text = pv.text.replace('"', '\\"').replace(r"\n", r"\\n")
|
1149
1085
|
pv_parts = self.gen_pv_constructor(pv, indent)
|
1150
|
-
init_list.append(
|
1151
|
-
f' setattr(cls, "{pv_text}",\n{indent_str}{pv_parts})'
|
1152
|
-
)
|
1086
|
+
init_list.append(f' setattr(cls, "{pv_text}",\n{indent_str}{pv_parts})')
|
1153
1087
|
|
1154
1088
|
add_vals_text = "\n".join(init_list).rstrip()
|
1155
1089
|
|
@@ -1212,9 +1146,7 @@ class {enum_name}(EnumDefinitionImpl):
|
|
1212
1146
|
|
1213
1147
|
@shared_arguments(PythonGenerator)
|
1214
1148
|
@click.command()
|
1215
|
-
@click.option(
|
1216
|
-
"--head/--no-head", default=True, show_default=True, help="Emit metadata heading"
|
1217
|
-
)
|
1149
|
+
@click.option("--head/--no-head", default=True, show_default=True, help="Emit metadata heading")
|
1218
1150
|
@click.option(
|
1219
1151
|
"--genmeta/--no-genmeta",
|
1220
1152
|
default=False,
|
linkml/generators/rdfgen.py
CHANGED
@@ -7,10 +7,9 @@ Generate a JSON LD representation of the model
|
|
7
7
|
import os
|
8
8
|
import urllib.parse as urlparse
|
9
9
|
from dataclasses import dataclass, field
|
10
|
-
from typing import
|
10
|
+
from typing import List, Optional
|
11
11
|
|
12
12
|
import click
|
13
|
-
from linkml_runtime.linkml_model.meta import SchemaDefinition
|
14
13
|
from rdflib import Graph
|
15
14
|
from rdflib.plugin import Parser as rdflib_Parser
|
16
15
|
from rdflib.plugin import plugins as rdflib_plugins
|
@@ -23,11 +22,11 @@ from linkml.utils.generator import Generator, shared_arguments
|
|
23
22
|
|
24
23
|
@dataclass
|
25
24
|
class RDFGenerator(Generator):
|
26
|
-
|
27
25
|
# ClassVars
|
28
26
|
generatorname = os.path.basename(__file__)
|
29
27
|
generatorversion = "0.1.1"
|
30
|
-
# TODO: we leave ttl as default for backwards compatibility but nt is
|
28
|
+
# TODO: we leave ttl as default for backwards compatibility but nt is
|
29
|
+
# recommended, see https://github.com/linkml/linkml/issues/163
|
31
30
|
valid_formats = ["ttl"] + sorted(
|
32
31
|
[x.name for x in rdflib_plugins(None, rdflib_Parser) if "/" not in str(x.name)]
|
33
32
|
)
|
@@ -38,15 +37,10 @@ class RDFGenerator(Generator):
|
|
38
37
|
emit_metadata: bool = field(default_factory=lambda: False)
|
39
38
|
context: List[str] = None
|
40
39
|
|
41
|
-
|
42
40
|
def _data(self, g: Graph) -> str:
|
43
|
-
return g.serialize(
|
44
|
-
format="turtle" if self.format == "ttl" else self.format
|
45
|
-
).decode()
|
41
|
+
return g.serialize(format="turtle" if self.format == "ttl" else self.format).decode()
|
46
42
|
|
47
|
-
def end_schema(
|
48
|
-
self, output: Optional[str] = None, context: str = None, **_
|
49
|
-
) -> None:
|
43
|
+
def end_schema(self, output: Optional[str] = None, context: str = None, **_) -> None:
|
50
44
|
gen = JSONLDGenerator(
|
51
45
|
self,
|
52
46
|
format=JSONLDGenerator.valid_formats[0],
|
linkml/generators/shaclgen.py
CHANGED
@@ -1,28 +1,32 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
|
-
from copy import copy, deepcopy
|
4
|
-
from dataclasses import field
|
5
|
-
from typing import (Callable, Dict, Iterator, List, Optional, Set, TextIO,
|
6
|
-
Tuple, Union)
|
7
3
|
|
8
4
|
import click
|
9
|
-
from linkml_runtime.
|
10
|
-
SchemaDefinition, TypeDefinition)
|
11
|
-
from linkml_runtime.utils.formatutils import camelcase, underscore
|
5
|
+
from linkml_runtime.utils.formatutils import underscore
|
12
6
|
from linkml_runtime.utils.schemaview import SchemaView
|
13
7
|
from rdflib import BNode, Graph, Literal, URIRef
|
14
8
|
from rdflib.collection import Collection
|
15
|
-
from rdflib.namespace import RDF,
|
9
|
+
from rdflib.namespace import RDF, SH
|
16
10
|
|
17
11
|
from linkml._version import __version__
|
18
12
|
from linkml.utils.generator import Generator, shared_arguments
|
19
13
|
|
14
|
+
LINK_ML_TYPES_STRING = URIRef("http://www.w3.org/2001/XMLSchema#string")
|
15
|
+
LINK_ML_TYPES_BOOL = URIRef("http://www.w3.org/2001/XMLSchema#boolean")
|
16
|
+
LINK_ML_TYPES_DECIMAL = URIRef("http://www.w3.org/2001/XMLSchema#decimal")
|
17
|
+
LINK_ML_TYPES_INTEGER = URIRef("http://www.w3.org/2001/XMLSchema#integer")
|
18
|
+
LINK_ML_TYPES_DURATION = URIRef("http://www.w3.org/2001/XMLSchema#duration")
|
19
|
+
LINK_ML_TYPES_DATETIME = URIRef("http://www.w3.org/2001/XMLSchema#dateTime")
|
20
|
+
LINK_ML_TYPES_DATE = URIRef("http://www.w3.org/2001/XMLSchema#date")
|
21
|
+
LINK_ML_TYPES_TIME = URIRef("http://www.w3.org/2001/XMLSchema#time")
|
22
|
+
|
20
23
|
|
21
24
|
class ShaclGenerator(Generator):
|
22
25
|
# ClassVars
|
23
26
|
generatorname = os.path.basename(__file__)
|
24
27
|
generatorversion = "0.0.1"
|
25
28
|
valid_formats = ["ttl"]
|
29
|
+
file_extension = "shacl.ttl"
|
26
30
|
visit_all_class_slots = False
|
27
31
|
uses_schemaloader = True
|
28
32
|
|
@@ -58,7 +62,7 @@ class ShaclGenerator(Generator):
|
|
58
62
|
|
59
63
|
class_uri = URIRef(sv.get_uri(c, expand=True))
|
60
64
|
shape_pv(RDF.type, SH.NodeShape)
|
61
|
-
shape_pv(SH.targetClass, class_uri)
|
65
|
+
shape_pv(SH.targetClass, class_uri) # TODO
|
62
66
|
if c.mixin or c.abstract:
|
63
67
|
shape_pv(SH.closed, Literal(False))
|
64
68
|
else:
|
@@ -68,9 +72,8 @@ class ShaclGenerator(Generator):
|
|
68
72
|
if c.description is not None:
|
69
73
|
shape_pv(SH.description, Literal(c.description))
|
70
74
|
list_node = BNode()
|
71
|
-
|
75
|
+
Collection(g, list_node, [RDF.type])
|
72
76
|
shape_pv(SH.ignoredProperties, list_node)
|
73
|
-
type_designator = None
|
74
77
|
order = 0
|
75
78
|
for s in sv.class_induced_slots(c.name):
|
76
79
|
# fixed in linkml-runtime 1.1.3
|
@@ -118,23 +121,34 @@ class ShaclGenerator(Generator):
|
|
118
121
|
elif r in sv.all_enums():
|
119
122
|
e = sv.get_enum(r)
|
120
123
|
pv_node = BNode()
|
121
|
-
|
124
|
+
Collection(
|
122
125
|
g,
|
123
126
|
pv_node,
|
124
127
|
[
|
125
|
-
URIRef(sv.expand_curie(pv.meaning))
|
126
|
-
if pv.meaning
|
127
|
-
else Literal(pv_name)
|
128
|
+
URIRef(sv.expand_curie(pv.meaning)) if pv.meaning else Literal(pv_name)
|
128
129
|
for pv_name, pv in e.permissible_values.items()
|
129
130
|
],
|
130
131
|
)
|
131
132
|
prop_pv(SH["in"], pv_node)
|
132
133
|
else:
|
133
|
-
|
134
|
+
if r == "string":
|
135
|
+
prop_pv(SH.datatype, LINK_ML_TYPES_STRING)
|
136
|
+
elif r == "boolean":
|
137
|
+
prop_pv(SH.datatype, LINK_ML_TYPES_BOOL)
|
138
|
+
elif r == "duration":
|
139
|
+
prop_pv(SH.datatype, LINK_ML_TYPES_DURATION)
|
140
|
+
elif r == "datetime":
|
141
|
+
prop_pv(SH.datatype, LINK_ML_TYPES_DATETIME)
|
142
|
+
elif r == "date":
|
143
|
+
prop_pv(SH.datatype, LINK_ML_TYPES_DATE)
|
144
|
+
elif r == "time":
|
145
|
+
prop_pv(SH.datatype, LINK_ML_TYPES_TIME)
|
146
|
+
elif r == "decimal":
|
147
|
+
prop_pv(SH.datatype, LINK_ML_TYPES_DECIMAL)
|
148
|
+
elif r == "integer":
|
149
|
+
prop_pv(SH.datatype, LINK_ML_TYPES_INTEGER)
|
134
150
|
if s.pattern:
|
135
151
|
prop_pv(SH.pattern, Literal(s.pattern))
|
136
|
-
if s.designates_type:
|
137
|
-
type_designator = s
|
138
152
|
|
139
153
|
return g
|
140
154
|
|