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
linkml/generators/pythongen.py
CHANGED
@@ -29,8 +29,10 @@ from rdflib import URIRef
|
|
29
29
|
|
30
30
|
import linkml
|
31
31
|
from linkml._version import __version__
|
32
|
+
from linkml.generators.python.python_ifabsent_processor import PythonIfAbsentProcessor
|
32
33
|
from linkml.utils.generator import Generator, shared_arguments
|
33
|
-
|
34
|
+
|
35
|
+
logger = logging.getLogger(__name__)
|
34
36
|
|
35
37
|
|
36
38
|
@dataclass
|
@@ -57,10 +59,10 @@ class PythonGenerator(Generator):
|
|
57
59
|
dataclass_repr: bool = False
|
58
60
|
"""
|
59
61
|
Whether generated dataclasses should also generate a default __repr__ method.
|
60
|
-
|
62
|
+
|
61
63
|
Default ``False`` so that the parent :class:`linkml_runtime.utils.yamlutils.YAMLRoot` 's
|
62
64
|
``__repr__`` method is inherited for model pretty printing.
|
63
|
-
|
65
|
+
|
64
66
|
References:
|
65
67
|
- https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass
|
66
68
|
"""
|
@@ -70,11 +72,12 @@ class PythonGenerator(Generator):
|
|
70
72
|
self.schema = str(self.schema)
|
71
73
|
self.sourcefile = self.schema
|
72
74
|
self.schemaview = SchemaView(self.schema, base_dir=self.base_dir)
|
75
|
+
self.ifabsent_processor = PythonIfAbsentProcessor(self.schemaview)
|
73
76
|
super().__post_init__()
|
74
77
|
if self.format is None:
|
75
78
|
self.format = self.valid_formats[0]
|
76
79
|
if self.schema.default_prefix == "linkml" and not self.genmeta:
|
77
|
-
|
80
|
+
logger.error("Generating metamodel without --genmeta is highly inadvisable!")
|
78
81
|
if not self.schema.source_file and isinstance(self.sourcefile, str) and "\n" not in self.sourcefile:
|
79
82
|
self.schema.source_file = os.path.basename(self.sourcefile)
|
80
83
|
|
@@ -87,8 +90,8 @@ class PythonGenerator(Generator):
|
|
87
90
|
try:
|
88
91
|
return compile_python(pycode)
|
89
92
|
except NameError as e:
|
90
|
-
|
91
|
-
|
93
|
+
logger.error(f"Code:\n{pycode}")
|
94
|
+
logger.error(f"Error compiling generated python code: {e}")
|
92
95
|
raise e
|
93
96
|
|
94
97
|
def visit_schema(self, **kwargs) -> None:
|
@@ -153,7 +156,7 @@ import re
|
|
153
156
|
from jsonasobj2 import JsonObj, as_dict
|
154
157
|
from typing import Optional, List, Union, Dict, ClassVar, Any
|
155
158
|
from dataclasses import dataclass
|
156
|
-
from datetime import date, datetime
|
159
|
+
from datetime import date, datetime, time
|
157
160
|
{enumimports}
|
158
161
|
from linkml_runtime.utils.slot import Slot
|
159
162
|
from linkml_runtime.utils.metamodelcore import empty_list, empty_dict, bnode
|
@@ -515,7 +518,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
515
518
|
|
516
519
|
return "\n\t".join(initializers)
|
517
520
|
|
518
|
-
def gen_class_variable(self, cls: ClassDefinition, slot: SlotDefinition, can_be_positional: bool) -> str:
|
521
|
+
def gen_class_variable(self, cls: ClassDefinition, slot: SlotDefinition, can_be_positional: bool = False) -> str:
|
519
522
|
"""
|
520
523
|
Generate a class variable declaration for the supplied slot. Note: the can_be_positional attribute works,
|
521
524
|
but it makes tag/value lists unduly complex, as you can't load them with tag=..., value=... -- you HAVE
|
@@ -527,12 +530,9 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
527
530
|
:param can_be_positional: True means that positional parameters are allowed.
|
528
531
|
:return: Initializer string
|
529
532
|
"""
|
530
|
-
can_be_positional = False # Force everything to be tag values
|
531
533
|
slotname = self.slot_name(slot.name)
|
532
534
|
slot_range, default_val = self.range_cardinality(slot, cls, can_be_positional)
|
533
|
-
ifabsent_text = (
|
534
|
-
ifabsent_value_declaration(slot.ifabsent, self, cls, slot) if slot.ifabsent is not None else None
|
535
|
-
)
|
535
|
+
ifabsent_text = self.ifabsent_processor.process_slot(slot, cls) if slot.ifabsent is not None else None
|
536
536
|
if ifabsent_text is not None:
|
537
537
|
default = f"= {ifabsent_text}"
|
538
538
|
else:
|
@@ -637,15 +637,6 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
637
637
|
|
638
638
|
def gen_postinits(self, cls: ClassDefinition) -> str:
|
639
639
|
"""Generate all the typing and existence checks post initialize"""
|
640
|
-
post_inits_pre_super = []
|
641
|
-
for slot in self.domain_slots(cls):
|
642
|
-
if slot.ifabsent:
|
643
|
-
dflt = ifabsent_postinit_declaration(slot.ifabsent, self, cls, slot)
|
644
|
-
|
645
|
-
if dflt and dflt != "None":
|
646
|
-
post_inits_pre_super.append(f"if self.{self.slot_name(slot.name)} is None:")
|
647
|
-
post_inits_pre_super.append(f"\tself.{self.slot_name(slot.name)} = {dflt}")
|
648
|
-
|
649
640
|
post_inits = []
|
650
641
|
if not (cls.mixin or cls.abstract):
|
651
642
|
pkeys = self.primary_keys_for(cls)
|
@@ -676,20 +667,17 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
676
667
|
if slot.name not in domain_slot_names and slot.designates_type:
|
677
668
|
post_inits_designators.append(self.gen_postinit(cls, slot))
|
678
669
|
|
679
|
-
post_inits_pre_super_line = "\n\t\t".join([p for p in post_inits_pre_super if p]) + (
|
680
|
-
"\n\t\t" if post_inits_pre_super else ""
|
681
|
-
)
|
682
670
|
post_inits_post_super_line = "\n\t\t".join(post_inits_designators)
|
683
671
|
post_inits_line = "\n\t\t".join([p for p in post_inits if p])
|
684
672
|
return (
|
685
673
|
(
|
686
674
|
f"""
|
687
675
|
def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
|
688
|
-
{
|
676
|
+
{post_inits_line}
|
689
677
|
super().__post_init__(**kwargs)
|
690
678
|
{post_inits_post_super_line}"""
|
691
679
|
)
|
692
|
-
if post_inits_line or
|
680
|
+
if post_inits_line or post_inits_post_super_line
|
693
681
|
else ""
|
694
682
|
)
|
695
683
|
|
@@ -958,11 +946,11 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
958
946
|
|
959
947
|
def forward_reference(self, slot_range: str, owning_class: str) -> bool:
|
960
948
|
"""Determine whether slot_range is a forward reference"""
|
961
|
-
#
|
949
|
+
# logger.info(f"CHECKING: {slot_range} {owning_class}")
|
962
950
|
if (slot_range in self.schema.classes and self.schema.classes[slot_range].imported_from) or (
|
963
951
|
slot_range in self.schema.enums and self.schema.enums[slot_range].imported_from
|
964
952
|
):
|
965
|
-
|
953
|
+
logger.info(
|
966
954
|
f"FALSE: FORWARD: {slot_range} {owning_class} // IMP={self.schema.classes[slot_range].imported_from}"
|
967
955
|
)
|
968
956
|
return False
|
@@ -971,10 +959,10 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
971
959
|
clist = [x.name for x in self._sort_classes(self.schema.classes.values())]
|
972
960
|
for cname in clist:
|
973
961
|
if cname == owning_class:
|
974
|
-
|
962
|
+
logger.info(f"TRUE: OCCURS SAME: {cname} == {slot_range} owning: {owning_class}")
|
975
963
|
return True # Occurs on or after
|
976
964
|
elif cname == slot_range:
|
977
|
-
|
965
|
+
logger.info(f"FALSE: OCCURS BEFORE: {cname} == {slot_range} owning: {owning_class}")
|
978
966
|
return False # Occurs before
|
979
967
|
return True
|
980
968
|
|
@@ -1228,7 +1216,7 @@ def cli(
|
|
1228
1216
|
)
|
1229
1217
|
if validate:
|
1230
1218
|
mod = gen.compile_module()
|
1231
|
-
|
1219
|
+
logger.info(f"Module {mod} compiled successfully")
|
1232
1220
|
print(gen.serialize(emit_metadata=head, **args))
|
1233
1221
|
|
1234
1222
|
|
@@ -20,7 +20,7 @@ class ShaclDataType(DataType, Enum):
|
|
20
20
|
FLOAT = ("float", URIRef("http://www.w3.org/2001/XMLSchema#float"))
|
21
21
|
DOUBLE = ("double", URIRef("http://www.w3.org/2001/XMLSchema#double"))
|
22
22
|
URI = ("uri", URIRef("http://www.w3.org/2001/XMLSchema#anyURI"))
|
23
|
-
|
23
|
+
CURIE = ("curi", URIRef("http://www.w3.org/2001/XMLSchema#string"))
|
24
24
|
NCNAME = ("ncname", URIRef("http://www.w3.org/2001/XMLSchema#string"))
|
25
25
|
OBJECT_IDENTIFIER = ("objectidentifier", URIRef("http://www.w3.org/ns/shex#iri"))
|
26
26
|
NODE_IDENTIFIER = ("nodeidentifier", URIRef("http://www.w3.org/ns/shex#nonLiteral"))
|
@@ -0,0 +1,89 @@
|
|
1
|
+
from linkml_runtime.linkml_model import ClassDefinition, EnumDefinitionName, SlotDefinition
|
2
|
+
from rdflib import Literal, URIRef
|
3
|
+
|
4
|
+
from linkml.generators.common.ifabsent_processor import IfAbsentProcessor
|
5
|
+
from linkml.generators.shacl.shacl_data_type import ShaclDataType
|
6
|
+
|
7
|
+
|
8
|
+
class ShaclIfAbsentProcessor(IfAbsentProcessor):
|
9
|
+
|
10
|
+
def map_custom_default_values(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition) -> (bool, str):
|
11
|
+
if default_value == "class_curie":
|
12
|
+
class_uri = self.schema_view.get_uri(cls, expand=True)
|
13
|
+
if class_uri:
|
14
|
+
return True, URIRef(class_uri)
|
15
|
+
return True, ""
|
16
|
+
|
17
|
+
return False, None
|
18
|
+
|
19
|
+
def map_enum_default_value(
|
20
|
+
self, enum_name: EnumDefinitionName, permissible_value_name: str, slot: SlotDefinition, cls: ClassDefinition
|
21
|
+
):
|
22
|
+
return Literal(permissible_value_name)
|
23
|
+
|
24
|
+
def map_string_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
25
|
+
return Literal(default_value, datatype=ShaclDataType.STRING.uri_ref)
|
26
|
+
|
27
|
+
def map_integer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
28
|
+
return Literal(default_value, datatype=ShaclDataType.INTEGER.uri_ref)
|
29
|
+
|
30
|
+
def map_boolean_true_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
|
31
|
+
return Literal(True, datatype=ShaclDataType.BOOLEAN.uri_ref)
|
32
|
+
|
33
|
+
def map_boolean_false_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
|
34
|
+
return Literal(False, datatype=ShaclDataType.BOOLEAN.uri_ref)
|
35
|
+
|
36
|
+
def map_float_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
37
|
+
return Literal(default_value, datatype=ShaclDataType.FLOAT.uri_ref)
|
38
|
+
|
39
|
+
def map_double_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
40
|
+
return Literal(default_value, datatype=ShaclDataType.DOUBLE.uri_ref)
|
41
|
+
|
42
|
+
def map_decimal_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
43
|
+
return Literal(default_value, datatype=ShaclDataType.DECIMAL.uri_ref)
|
44
|
+
|
45
|
+
def map_time_default_value(self, hour: str, minutes: str, seconds: str, slot: SlotDefinition, cls: ClassDefinition):
|
46
|
+
return Literal(f"{hour}:{minutes}:{seconds}", datatype=ShaclDataType.TIME.uri_ref)
|
47
|
+
|
48
|
+
def map_date_default_value(self, year: str, month: str, day: str, slot: SlotDefinition, cls: ClassDefinition):
|
49
|
+
return Literal(f"{year}-{month}-{day}", datatype=ShaclDataType.DATE.uri_ref)
|
50
|
+
|
51
|
+
def map_datetime_default_value(
|
52
|
+
self,
|
53
|
+
year: str,
|
54
|
+
month: str,
|
55
|
+
day: str,
|
56
|
+
hour: str,
|
57
|
+
minutes: str,
|
58
|
+
seconds: str,
|
59
|
+
slot: SlotDefinition,
|
60
|
+
cls: ClassDefinition,
|
61
|
+
):
|
62
|
+
return Literal(f"{year}-{month}-{day}T{hour}:{minutes}:{seconds}", datatype=ShaclDataType.DATETIME.uri_ref)
|
63
|
+
|
64
|
+
def map_uri_or_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
65
|
+
raise NotImplementedError()
|
66
|
+
|
67
|
+
def map_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
68
|
+
return Literal(default_value, datatype=ShaclDataType.CURIE.uri_ref)
|
69
|
+
|
70
|
+
def map_uri_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
71
|
+
return Literal(default_value, datatype=ShaclDataType.URI.uri_ref)
|
72
|
+
|
73
|
+
def map_nc_name_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
74
|
+
raise NotImplementedError()
|
75
|
+
|
76
|
+
def map_object_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
77
|
+
raise NotImplementedError()
|
78
|
+
|
79
|
+
def map_node_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
80
|
+
raise NotImplementedError()
|
81
|
+
|
82
|
+
def map_json_pointer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
83
|
+
raise NotImplementedError()
|
84
|
+
|
85
|
+
def map_json_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
86
|
+
raise NotImplementedError()
|
87
|
+
|
88
|
+
def map_sparql_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
89
|
+
raise NotImplementedError()
|
linkml/generators/shaclgen.py
CHANGED
@@ -5,19 +5,21 @@ from typing import Callable, List
|
|
5
5
|
|
6
6
|
import click
|
7
7
|
from jsonasobj2 import JsonObj, as_dict
|
8
|
-
from linkml_runtime.linkml_model.meta import ElementName
|
8
|
+
from linkml_runtime.linkml_model.meta import ClassDefinition, ElementName
|
9
9
|
from linkml_runtime.utils.formatutils import underscore
|
10
10
|
from linkml_runtime.utils.schemaview import SchemaView
|
11
11
|
from linkml_runtime.utils.yamlutils import TypedNode, extended_float, extended_int, extended_str
|
12
12
|
from rdflib import BNode, Graph, Literal, URIRef
|
13
13
|
from rdflib.collection import Collection
|
14
|
-
from rdflib.namespace import RDF,
|
14
|
+
from rdflib.namespace import RDF, SH, XSD
|
15
15
|
|
16
16
|
from linkml._version import __version__
|
17
|
-
from linkml.generators.shacl.ifabsent_processor import IfAbsentProcessor
|
18
17
|
from linkml.generators.shacl.shacl_data_type import ShaclDataType
|
18
|
+
from linkml.generators.shacl.shacl_ifabsent_processor import ShaclIfAbsentProcessor
|
19
19
|
from linkml.utils.generator import Generator, shared_arguments
|
20
20
|
|
21
|
+
logger = logging.getLogger(__name__)
|
22
|
+
|
21
23
|
|
22
24
|
@dataclass
|
23
25
|
class ShaclGenerator(Generator):
|
@@ -56,7 +58,7 @@ class ShaclGenerator(Generator):
|
|
56
58
|
g = Graph()
|
57
59
|
g.bind("sh", SH)
|
58
60
|
|
59
|
-
ifabsent_processor =
|
61
|
+
ifabsent_processor = ShaclIfAbsentProcessor(sv)
|
60
62
|
|
61
63
|
for pfx in self.schema.prefixes.values():
|
62
64
|
g.bind(str(pfx.prefix_prefix), pfx.prefix_reference)
|
@@ -74,9 +76,6 @@ class ShaclGenerator(Generator):
|
|
74
76
|
shape_pv(RDF.type, SH.NodeShape)
|
75
77
|
shape_pv(SH.targetClass, class_uri) # TODO
|
76
78
|
|
77
|
-
if c.is_a:
|
78
|
-
shape_pv(RDFS.subClassOf, URIRef(sv.get_uri(c.is_a, expand=True)))
|
79
|
-
|
80
79
|
if self.closed:
|
81
80
|
if c.mixin or c.abstract:
|
82
81
|
shape_pv(SH.closed, Literal(False))
|
@@ -88,9 +87,9 @@ class ShaclGenerator(Generator):
|
|
88
87
|
shape_pv(SH.name, Literal(c.title))
|
89
88
|
if c.description is not None:
|
90
89
|
shape_pv(SH.description, Literal(c.description))
|
91
|
-
|
92
|
-
|
93
|
-
|
90
|
+
|
91
|
+
shape_pv(SH.ignoredProperties, self._build_ignored_properties(g, c))
|
92
|
+
|
94
93
|
if c.annotations and self.include_annotations:
|
95
94
|
self._add_annotations(shape_pv, c)
|
96
95
|
order = 0
|
@@ -130,7 +129,7 @@ class ShaclGenerator(Generator):
|
|
130
129
|
# slot definition, as both are mapped to sh:in in SHACL
|
131
130
|
if s.equals_string or s.equals_string_in:
|
132
131
|
error = "'equals_string'/'equals_string_in' and 'any_of' are mutually exclusive"
|
133
|
-
raise ValueError(f'{TypedNode.yaml_loc(s, suffix="")} {error}')
|
132
|
+
raise ValueError(f'{TypedNode.yaml_loc(str(s), suffix="")} {error}')
|
134
133
|
|
135
134
|
or_node = BNode()
|
136
135
|
prop_pv(SH["or"], or_node)
|
@@ -208,7 +207,9 @@ class ShaclGenerator(Generator):
|
|
208
207
|
# Map equal_string and equal_string_in to sh:in
|
209
208
|
self._and_equals_string(g, prop_pv, s.equals_string_in)
|
210
209
|
|
211
|
-
ifabsent_processor.process_slot(
|
210
|
+
default_value = ifabsent_processor.process_slot(s, c)
|
211
|
+
if default_value:
|
212
|
+
prop_pv(SH.defaultValue, default_value)
|
212
213
|
|
213
214
|
return g
|
214
215
|
|
@@ -242,7 +243,7 @@ class ShaclGenerator(Generator):
|
|
242
243
|
if rt.annotations and self.include_annotations:
|
243
244
|
self._add_annotations(func, rt)
|
244
245
|
else:
|
245
|
-
|
246
|
+
logger.error(f"No URI for type {rt.name}")
|
246
247
|
|
247
248
|
def _and_equals_string(self, g: Graph, func: Callable, values: List) -> None:
|
248
249
|
pv_node = BNode()
|
@@ -299,6 +300,31 @@ class ShaclGenerator(Generator):
|
|
299
300
|
)
|
300
301
|
func(SH["in"], pv_node)
|
301
302
|
|
303
|
+
def _build_ignored_properties(self, g: Graph, c: ClassDefinition) -> BNode:
|
304
|
+
def collect_child_properties(class_name: str, output: set) -> None:
|
305
|
+
for childName in self.schemaview.class_children(class_name, imports=True, mixins=False, is_a=True):
|
306
|
+
output.update(
|
307
|
+
{
|
308
|
+
URIRef(self.schemaview.get_uri(prop, expand=True))
|
309
|
+
for prop in self.schemaview.class_slots(childName)
|
310
|
+
}
|
311
|
+
)
|
312
|
+
collect_child_properties(childName, output)
|
313
|
+
|
314
|
+
child_properties = set()
|
315
|
+
collect_child_properties(c.name, child_properties)
|
316
|
+
|
317
|
+
class_slot_uris = {
|
318
|
+
URIRef(self.schemaview.get_uri(prop, expand=True)) for prop in self.schemaview.class_slots(c.name)
|
319
|
+
}
|
320
|
+
ignored_properties = child_properties.difference(class_slot_uris)
|
321
|
+
|
322
|
+
list_node = BNode()
|
323
|
+
ignored_properties.add(RDF.type)
|
324
|
+
Collection(g, list_node, list(ignored_properties))
|
325
|
+
|
326
|
+
return list_node
|
327
|
+
|
302
328
|
|
303
329
|
def add_simple_data_type(func: Callable, r: ElementName) -> None:
|
304
330
|
for datatype in list(ShaclDataType):
|
linkml/generators/sparqlgen.py
CHANGED
@@ -14,6 +14,8 @@ from linkml_runtime.utils.schemaview import SchemaView
|
|
14
14
|
from linkml._version import __version__
|
15
15
|
from linkml.utils.generator import Generator, shared_arguments
|
16
16
|
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
17
19
|
template = """
|
18
20
|
{% for pfxn, pfx in schema.prefixes.items() -%}
|
19
21
|
PREFIX {{pfxn}}: <{{pfx.prefix_reference}}>
|
@@ -150,7 +152,7 @@ class SparqlGenerator(Generator):
|
|
150
152
|
extra = ""
|
151
153
|
if named_graphs is not None:
|
152
154
|
extra += f'FILTER( ?graph in ( {",".join(named_graphs)} ))'
|
153
|
-
|
155
|
+
logger.info(f"Named Graphs = {named_graphs} // extra={extra}")
|
154
156
|
if limit is not None and isinstance(limit, int):
|
155
157
|
limit = f"LIMIT {limit}"
|
156
158
|
else:
|
@@ -21,6 +21,8 @@ from linkml.generators.sqltablegen import SQLTableGenerator
|
|
21
21
|
from linkml.transformers.relmodel_transformer import ForeignKeyPolicy, RelationalModelTransformer
|
22
22
|
from linkml.utils.generator import Generator, shared_arguments
|
23
23
|
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
|
24
26
|
|
25
27
|
class TemplateEnum(Enum):
|
26
28
|
DECLARATIVE = "declarative"
|
@@ -84,7 +86,7 @@ class SQLAlchemyGenerator(Generator):
|
|
84
86
|
template_obj = Template(template_str)
|
85
87
|
if model_path is None:
|
86
88
|
model_path = self.schema.name
|
87
|
-
|
89
|
+
logger.info(f"Package for dataclasses == {model_path}")
|
88
90
|
backrefs = defaultdict(list)
|
89
91
|
for m in tr_result.mappings:
|
90
92
|
backrefs[m.source_class].append(m)
|
@@ -113,7 +115,7 @@ class SQLAlchemyGenerator(Generator):
|
|
113
115
|
is_join_table=lambda c: any(tag for tag in c.annotations.keys() if tag == "linkml:derived_from"),
|
114
116
|
classes=rel_schema_classes_ordered,
|
115
117
|
)
|
116
|
-
|
118
|
+
logger.debug(f"# Generated code:\n{code}")
|
117
119
|
return code
|
118
120
|
|
119
121
|
def serialize(self, **kwargs) -> str:
|
@@ -173,7 +175,7 @@ class SQLAlchemyGenerator(Generator):
|
|
173
175
|
def skip(cls: ClassDefinition) -> bool:
|
174
176
|
is_skip = len(cls.attributes) == 0
|
175
177
|
if is_skip:
|
176
|
-
|
178
|
+
logger.error(f"SKIPPING: {cls.name}")
|
177
179
|
return is_skip
|
178
180
|
|
179
181
|
# TODO: move this
|
linkml/generators/sqltablegen.py
CHANGED
@@ -16,6 +16,8 @@ from linkml.transformers.relmodel_transformer import ForeignKeyPolicy, Relationa
|
|
16
16
|
from linkml.utils.generator import Generator, shared_arguments
|
17
17
|
from linkml.utils.schemaloader import SchemaLoader
|
18
18
|
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
19
21
|
|
20
22
|
class SqlNamingPolicy(Enum):
|
21
23
|
preserve = "preserve"
|
@@ -262,12 +264,12 @@ class SQLTableGenerator(Generator):
|
|
262
264
|
elif range is None:
|
263
265
|
return Text()
|
264
266
|
else:
|
265
|
-
|
267
|
+
logger.error(f"Unknown range: {range} for {slot.name} = {slot.range}")
|
266
268
|
return Text()
|
267
269
|
if range_base in RANGEMAP:
|
268
270
|
return RANGEMAP[range_base]
|
269
271
|
else:
|
270
|
-
|
272
|
+
logger.error(f"UNKNOWN range base: {range_base} for {slot.name} = {slot.range}")
|
271
273
|
return Text()
|
272
274
|
|
273
275
|
@staticmethod
|
@@ -19,6 +19,9 @@ from linkml._version import __version__
|
|
19
19
|
from linkml.generators.oocodegen import OOCodeGenerator
|
20
20
|
from linkml.utils.generator import shared_arguments
|
21
21
|
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
|
22
25
|
type_map = {
|
23
26
|
"str": "string",
|
24
27
|
"int": "number",
|
@@ -120,7 +123,7 @@ class TypescriptGenerator(OOCodeGenerator):
|
|
120
123
|
gen_type_utils: bool = False
|
121
124
|
include_induced_slots: bool = False
|
122
125
|
|
123
|
-
def serialize(self) -> str:
|
126
|
+
def serialize(self, output=None) -> str:
|
124
127
|
"""Serialize a schema to typescript string"""
|
125
128
|
|
126
129
|
sv: SchemaView = self.schemaview
|
@@ -132,6 +135,9 @@ class TypescriptGenerator(OOCodeGenerator):
|
|
132
135
|
view=self.schemaview,
|
133
136
|
enums=enums,
|
134
137
|
)
|
138
|
+
if output is not None:
|
139
|
+
with open(output, "w") as out:
|
140
|
+
out.write(out_str)
|
135
141
|
return out_str
|
136
142
|
|
137
143
|
@staticmethod
|
@@ -207,7 +213,7 @@ class TypescriptGenerator(OOCodeGenerator):
|
|
207
213
|
elif t.typeof and t.typeof in type_map:
|
208
214
|
tsrange = type_map[t.typeof]
|
209
215
|
else:
|
210
|
-
|
216
|
+
logger.warning(f"Unknown type.base: {t.name}")
|
211
217
|
if slot.multivalued:
|
212
218
|
tsrange = f"{tsrange}[]"
|
213
219
|
return tsrange
|
@@ -241,7 +247,7 @@ class TypescriptGenerator(OOCodeGenerator):
|
|
241
247
|
elif t.typeof and t.typeof in type_map:
|
242
248
|
return type_init_map[t.typeof]
|
243
249
|
else:
|
244
|
-
|
250
|
+
logger.warning(f"Unknown type.base: {t.name}")
|
245
251
|
return "null"
|
246
252
|
return "null"
|
247
253
|
|
@@ -264,8 +270,9 @@ class TypescriptGenerator(OOCodeGenerator):
|
|
264
270
|
@click.version_option(__version__, "-V", "--version")
|
265
271
|
@click.option("--gen-type-utils/", "-u", help="Generate Type checking utils", is_flag=True)
|
266
272
|
@click.option("--include-induced-slots/", help="Generate slots induced through inheritance", is_flag=True)
|
267
|
-
@click.
|
268
|
-
|
273
|
+
@click.option("--output", type=click.Path(dir_okay=False))
|
274
|
+
@click.command()
|
275
|
+
def cli(yamlfile, gen_type_utils=False, include_induced_slots=False, output=None, **args):
|
269
276
|
"""Generate typescript interfaces and types
|
270
277
|
|
271
278
|
See https://github.com/linkml/linkml-runtime.js
|
@@ -273,7 +280,7 @@ def cli(yamlfile, gen_type_utils=False, include_induced_slots=False, **args):
|
|
273
280
|
gen = TypescriptGenerator(
|
274
281
|
yamlfile, gen_type_utils=gen_type_utils, include_induced_slots=include_induced_slots, **args
|
275
282
|
)
|
276
|
-
|
283
|
+
gen.serialize(output=output)
|
277
284
|
|
278
285
|
|
279
286
|
if __name__ == "__main__":
|
linkml/linter/linter.py
CHANGED
@@ -8,6 +8,7 @@ from typing import Any, Dict, Iterable, Union
|
|
8
8
|
import jsonschema
|
9
9
|
import yaml
|
10
10
|
from jsonschema.exceptions import best_match
|
11
|
+
from jsonschema.protocols import Validator
|
11
12
|
from linkml_runtime import SchemaView
|
12
13
|
from linkml_runtime.dumpers import yaml_dumper
|
13
14
|
from linkml_runtime.linkml_model import SchemaDefinition
|
@@ -35,7 +36,7 @@ def get_named_config(name: str) -> Dict[str, Any]:
|
|
35
36
|
|
36
37
|
|
37
38
|
@lru_cache
|
38
|
-
def get_metamodel_validator() ->
|
39
|
+
def get_metamodel_validator() -> Validator:
|
39
40
|
meta_json_gen = JsonSchemaGenerator(LOCAL_METAMODEL_YAML_FILE, not_closed=False)
|
40
41
|
meta_json_schema = meta_json_gen.generate()
|
41
42
|
validator_cls = jsonschema.validators.validator_for(meta_json_schema, default=jsonschema.Draft7Validator)
|
@@ -360,13 +360,13 @@ class LogicalModelTransformer(ModelTransformer):
|
|
360
360
|
target_schema = target_schemaview.schema
|
361
361
|
for tn, typ in target_schema.types.items():
|
362
362
|
ancs = sv.type_ancestors(tn, reflexive=False)
|
363
|
-
|
363
|
+
logger.debug(f"Unrolling type {tn}, merging {len(ancs)}")
|
364
364
|
if ancs:
|
365
365
|
for type_anc in ancs:
|
366
366
|
self._merge_type_ancestors(target=typ, source=sv.get_type(type_anc))
|
367
367
|
for sn, slot in target_schema.slots.items():
|
368
368
|
ancs = sv.slot_ancestors(sn, reflexive=False)
|
369
|
-
|
369
|
+
logger.debug(f"Unrolling slot {sn}, merging {len(ancs)}")
|
370
370
|
if ancs:
|
371
371
|
for slot_anc in ancs:
|
372
372
|
self._merge_slot_ancestors(target=slot, source=target_schema.slots[slot_anc])
|
@@ -379,7 +379,7 @@ class LogicalModelTransformer(ModelTransformer):
|
|
379
379
|
depth_first=False,
|
380
380
|
)
|
381
381
|
ancs = list(reversed(ancs))
|
382
|
-
|
382
|
+
logger.debug(f"Unrolling class {cn}, merging {len(ancs)}")
|
383
383
|
self._roll_down(target_schema, cn, ancs)
|
384
384
|
self.apply_defaults(target_schema)
|
385
385
|
if simplify:
|
@@ -12,9 +12,12 @@ from linkml_runtime.linkml_model import (
|
|
12
12
|
SchemaDefinition,
|
13
13
|
SlotDefinition,
|
14
14
|
)
|
15
|
+
from linkml_runtime.linkml_model.meta import UniqueKey
|
15
16
|
from linkml_runtime.utils.schemaview import SchemaView, SlotDefinitionName
|
16
17
|
from sqlalchemy import Enum
|
17
18
|
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
18
21
|
|
19
22
|
class RelationalAnnotations(Enum):
|
20
23
|
PRIMARY_KEY = "primary_key"
|
@@ -215,11 +218,11 @@ class RelationalModelTransformer:
|
|
215
218
|
for cn in target_sv.all_classes():
|
216
219
|
pk = self.get_direct_identifier_attribute(target_sv, cn)
|
217
220
|
if self.foreign_key_policy == ForeignKeyPolicy.NO_FOREIGN_KEYS:
|
218
|
-
|
221
|
+
logger.info(f"Will not inject any PKs, and policy == {self.foreign_key_policy}")
|
219
222
|
else:
|
220
223
|
if pk is None:
|
221
224
|
pk = self.add_primary_key(cn, target_sv)
|
222
|
-
|
225
|
+
logger.info(f"Added primary key {cn}.{pk.name}")
|
223
226
|
for link in links:
|
224
227
|
if link.target_class == cn:
|
225
228
|
link.target_slot = pk.name
|
@@ -233,7 +236,7 @@ class RelationalModelTransformer:
|
|
233
236
|
continue
|
234
237
|
pk_slot = self.get_direct_identifier_attribute(target_sv, cn)
|
235
238
|
# if self.is_skip(c) and len(incoming_links) == 0:
|
236
|
-
#
|
239
|
+
# logger.info(f'Skipping class: {c.name}')
|
237
240
|
# del target.classes[cn]
|
238
241
|
# continue
|
239
242
|
for src_slot in list(c.attributes.values()):
|
@@ -278,6 +281,17 @@ class RelationalModelTransformer:
|
|
278
281
|
target_slot=backref_slot.name,
|
279
282
|
)
|
280
283
|
)
|
284
|
+
backref_key_slots = [s for s in backref_class.attributes.values() if s.key]
|
285
|
+
if backref_key_slots:
|
286
|
+
if len(backref_key_slots) > 1:
|
287
|
+
raise ValueError(f"Multiple keys for {c.name}: {backref_key_slots}")
|
288
|
+
backref_key_slot = backref_key_slots[0]
|
289
|
+
unique_key_name = f"{c.name}_{backref_key_slot.name}"
|
290
|
+
backref_class.unique_keys[unique_key_name] = UniqueKey(
|
291
|
+
unique_key_name=unique_key_name,
|
292
|
+
unique_key_slots=[backref_slot.name, backref_key_slot.name],
|
293
|
+
)
|
294
|
+
|
281
295
|
else:
|
282
296
|
# MANY-TO-MANY
|
283
297
|
# create new linking table
|
@@ -375,7 +389,7 @@ class RelationalModelTransformer:
|
|
375
389
|
removed_ucs = []
|
376
390
|
for uc_name, uc in c.unique_keys.items():
|
377
391
|
if any(sn in multivalued_slots_original for sn in uc.unique_key_slots):
|
378
|
-
|
392
|
+
logger.warning(
|
379
393
|
f"Cannot represent uniqueness constraint {uc_name}. "
|
380
394
|
f"one of the slots {uc.unique_key_slots} is multivalued"
|
381
395
|
)
|
linkml/utils/converter.py
CHANGED
@@ -23,6 +23,8 @@ from linkml.utils.datautils import (
|
|
23
23
|
infer_root_class,
|
24
24
|
)
|
25
25
|
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
|
26
28
|
|
27
29
|
@click.command(name="convert")
|
28
30
|
@click.option("--module", "-m", help="Path to python datamodel module")
|
@@ -119,7 +121,7 @@ def cli(
|
|
119
121
|
sv.set_modified()
|
120
122
|
if target_class is None and target_class_from_path:
|
121
123
|
target_class = os.path.basename(input).split("-")[0]
|
122
|
-
|
124
|
+
logger.info(f"inferred target class = {target_class} from {input}")
|
123
125
|
if target_class is None:
|
124
126
|
target_class = infer_root_class(sv)
|
125
127
|
if target_class is None:
|