linkml 1.7.8__py3-none-any.whl → 1.7.10__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/generators/csvgen.py +15 -5
- linkml/generators/docgen/class_diagram.md.jinja2 +19 -4
- linkml/generators/docgen.py +59 -14
- linkml/generators/graphqlgen.py +14 -16
- linkml/generators/jsonldcontextgen.py +3 -3
- linkml/generators/jsonldgen.py +3 -2
- linkml/generators/jsonschemagen.py +9 -0
- linkml/generators/markdowngen.py +341 -301
- linkml/generators/owlgen.py +87 -20
- linkml/generators/plantumlgen.py +9 -8
- linkml/generators/prefixmapgen.py +15 -23
- linkml/generators/protogen.py +23 -18
- linkml/generators/pydanticgen/pydanticgen.py +11 -2
- linkml/generators/pythongen.py +2 -5
- linkml/generators/rdfgen.py +5 -4
- linkml/generators/shaclgen.py +8 -6
- linkml/generators/shexgen.py +9 -7
- linkml/generators/summarygen.py +8 -2
- linkml/generators/terminusdbgen.py +2 -2
- linkml/generators/yumlgen.py +2 -2
- linkml/utils/__init__.py +3 -0
- linkml/utils/deprecation.py +255 -0
- linkml/utils/generator.py +78 -56
- linkml/validator/cli.py +11 -0
- linkml/validator/plugins/jsonschema_validation_plugin.py +2 -0
- linkml/validator/report.py +1 -0
- linkml/workspaces/example_runner.py +2 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/METADATA +1 -1
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/RECORD +32 -31
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/LICENSE +0 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/WHEEL +0 -0
- {linkml-1.7.8.dist-info → linkml-1.7.10.dist-info}/entry_points.txt +0 -0
linkml/generators/owlgen.py
CHANGED
@@ -30,7 +30,7 @@ from linkml_runtime.linkml_model.meta import (
|
|
30
30
|
)
|
31
31
|
from linkml_runtime.utils.formatutils import camelcase, underscore
|
32
32
|
from linkml_runtime.utils.introspection import package_schemaview
|
33
|
-
from rdflib import OWL, RDF, XSD, BNode, Graph, Literal, URIRef
|
33
|
+
from rdflib import DCTERMS, OWL, RDF, XSD, BNode, Graph, Literal, URIRef
|
34
34
|
from rdflib.collection import Collection
|
35
35
|
from rdflib.namespace import RDFS, SKOS
|
36
36
|
from rdflib.plugin import Parser as rdflib_Parser
|
@@ -147,6 +147,8 @@ class OwlSchemaGenerator(Generator):
|
|
147
147
|
mixins_as_expressions: bool = None
|
148
148
|
"""EXPERIMENTAL: If True, use OWL existential restrictions to represent mixins"""
|
149
149
|
|
150
|
+
default_permissible_value_type: Union[str, URIRef] = field(default_factory=lambda: OWL.Class)
|
151
|
+
|
150
152
|
slot_is_literal_map: Mapping[str, Set[bool]] = field(default_factory=lambda: defaultdict(set))
|
151
153
|
"""DEPRECATED: use node_owltypes"""
|
152
154
|
|
@@ -830,6 +832,28 @@ class OwlSchemaGenerator(Generator):
|
|
830
832
|
if ixn:
|
831
833
|
self.graph.add((type_uri, OWL.equivalentClass, ixn))
|
832
834
|
|
835
|
+
def _get_metatype(
|
836
|
+
self, element: Union[Definition, PermissibleValue], default_value: Optional[Union[str, URIRef]] = None
|
837
|
+
) -> Optional[URIRef]:
|
838
|
+
impls = []
|
839
|
+
if isinstance(element, Definition):
|
840
|
+
impls.extend(element.implements)
|
841
|
+
if isinstance(element, PermissibleValue):
|
842
|
+
if "implements" in element.annotations:
|
843
|
+
ann = element.annotations["implements"]
|
844
|
+
v = ann.value
|
845
|
+
if not isinstance(v, list):
|
846
|
+
v = [v]
|
847
|
+
impls.extend(v)
|
848
|
+
for impl in impls:
|
849
|
+
if impl.startswith("owl:"):
|
850
|
+
return OWL[impl.split(":")[1]]
|
851
|
+
if impl.startswith("rdfs:"):
|
852
|
+
return RDFS[impl.split(":")[1]]
|
853
|
+
if isinstance(default_value, str):
|
854
|
+
return URIRef(default_value)
|
855
|
+
return default_value
|
856
|
+
|
833
857
|
def add_enum(self, e: EnumDefinition) -> None:
|
834
858
|
g = self.graph
|
835
859
|
enum_uri = self._enum_uri(e.name)
|
@@ -856,26 +880,36 @@ class OwlSchemaGenerator(Generator):
|
|
856
880
|
)
|
857
881
|
)
|
858
882
|
pv_uris = []
|
883
|
+
owl_types = []
|
884
|
+
enum_owl_type = self._get_metatype(e, self.default_permissible_value_type)
|
885
|
+
|
859
886
|
for pv in e.permissible_values.values():
|
860
|
-
|
861
|
-
|
862
|
-
if
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
887
|
+
pv_owl_type = self._get_metatype(pv, enum_owl_type)
|
888
|
+
owl_types.append(pv_owl_type)
|
889
|
+
if pv_owl_type == RDFS.Literal:
|
890
|
+
pv_node = Literal(pv.text)
|
891
|
+
if pv.meaning:
|
892
|
+
logging.warning(f"Meaning on literal {pv.text} in {e.name} is ignored")
|
893
|
+
else:
|
894
|
+
pv_node = self._permissible_value_uri(pv, enum_uri, e)
|
895
|
+
pv_uris.append(pv_node)
|
896
|
+
g.add(
|
897
|
+
(
|
898
|
+
enum_uri,
|
899
|
+
self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME]["permissible_values"],
|
900
|
+
pv_node,
|
872
901
|
)
|
902
|
+
)
|
903
|
+
if not isinstance(pv_node, Literal):
|
904
|
+
g.add((pv_node, RDF.type, pv_owl_type))
|
905
|
+
g.add((pv_node, RDFS.label, Literal(pv.text)))
|
906
|
+
# TODO: make this configurable
|
873
907
|
# self._add_element_properties(pv_uri, pv)
|
874
908
|
if self.metaclasses:
|
875
|
-
g.add((
|
909
|
+
g.add((pv_node, RDF.type, enum_uri))
|
876
910
|
has_parent = False
|
877
911
|
if pv.is_a:
|
878
|
-
self.graph.add((
|
912
|
+
self.graph.add((pv_node, RDFS.subClassOf, self._permissible_value_uri(pv.is_a, enum_uri, e)))
|
879
913
|
has_parent = True
|
880
914
|
for mixin in sorted(pv.mixins):
|
881
915
|
parent = self._permissible_value_uri(mixin, enum_uri, e)
|
@@ -885,13 +919,33 @@ class OwlSchemaGenerator(Generator):
|
|
885
919
|
has_parent = True
|
886
920
|
self.graph.add((enum_uri, RDFS.subClassOf, parent))
|
887
921
|
if not has_parent and self.add_root_classes:
|
888
|
-
self.graph.add((
|
922
|
+
self.graph.add((pv_node, RDFS.subClassOf, URIRef(PermissibleValue.class_class_uri)))
|
889
923
|
if all([pv is not None for pv in pv_uris]):
|
890
|
-
|
891
|
-
for
|
924
|
+
all_is_class = all([owl_type == OWL.Class for owl_type in owl_types])
|
925
|
+
all_is_individual = all([owl_type == OWL.NamedIndividual for owl_type in owl_types])
|
926
|
+
all_is_literal = all([owl_type == RDFS.Literal for owl_type in owl_types])
|
927
|
+
sub_pred = DCTERMS.isPartOf
|
928
|
+
combo_pred = None
|
929
|
+
if all_is_class or all_is_individual or all_is_literal:
|
930
|
+
if all_is_class:
|
931
|
+
combo_pred = OWL.unionOf
|
932
|
+
# self._union_of(pv_uris, node=enum_uri)
|
933
|
+
sub_pred = RDFS.subClassOf
|
934
|
+
elif all_is_individual:
|
935
|
+
combo_pred = OWL.oneOf
|
936
|
+
# self._object_one_of(pv_uris, node=enum_uri)
|
937
|
+
sub_pred = RDF.type
|
938
|
+
elif all_is_literal:
|
939
|
+
combo_pred = OWL.oneOf
|
940
|
+
# self._object_one_of(pv_uris, node=enum_uri)
|
941
|
+
sub_pred = RDF.type
|
942
|
+
if combo_pred:
|
943
|
+
self._boolean_expression(pv_uris, combo_pred, enum_uri, owl_types=set(owl_types))
|
944
|
+
for pv_node in pv_uris:
|
892
945
|
# this would normally be entailed, but we assert here so it is visible
|
893
946
|
# without reasoning
|
894
|
-
|
947
|
+
if not isinstance(pv_node, Literal):
|
948
|
+
g.add((pv_node, sub_pred, enum_uri))
|
895
949
|
|
896
950
|
def _add_rule(self, subject: Union[URIRef, BNode], rule: ClassRule, cls: ClassDefinition):
|
897
951
|
if not self.use_swrl:
|
@@ -1030,6 +1084,9 @@ class OwlSchemaGenerator(Generator):
|
|
1030
1084
|
def _union_of(self, exprs: List[Union[BNode, URIRef]], **kwargs) -> Optional[Union[BNode, URIRef]]:
|
1031
1085
|
return self._boolean_expression(exprs, OWL.unionOf, **kwargs)
|
1032
1086
|
|
1087
|
+
def _object_one_of(self, exprs: List[Union[BNode, URIRef]], **kwargs) -> Optional[Union[BNode, URIRef]]:
|
1088
|
+
return self._boolean_expression(exprs, OWL.oneOf, **kwargs)
|
1089
|
+
|
1033
1090
|
def _exactly_one_of(self, exprs: List[Union[BNode, URIRef]]) -> Optional[Union[BNode, URIRef]]:
|
1034
1091
|
if not exprs:
|
1035
1092
|
raise ValueError("Must pass at least one")
|
@@ -1165,7 +1222,7 @@ class OwlSchemaGenerator(Generator):
|
|
1165
1222
|
return URIRef(expanded)
|
1166
1223
|
|
1167
1224
|
def _permissible_value_uri(
|
1168
|
-
self, pv: Union[str,
|
1225
|
+
self, pv: Union[str, PermissibleValue], enum_uri: str, enum_def: EnumDefinition = None
|
1169
1226
|
) -> URIRef:
|
1170
1227
|
if isinstance(pv, str):
|
1171
1228
|
pv_name = pv
|
@@ -1182,6 +1239,10 @@ class OwlSchemaGenerator(Generator):
|
|
1182
1239
|
|
1183
1240
|
def slot_owl_type(self, slot: SlotDefinition) -> URIRef:
|
1184
1241
|
sv = self.schemaview
|
1242
|
+
if slot.implements:
|
1243
|
+
for t in ["owl:AnnotationProperty", "owl:ObjectProperty", "owl:DatatypeProperty"]:
|
1244
|
+
if t in slot.implements:
|
1245
|
+
return OWL[t.replace("owl:", "")]
|
1185
1246
|
if slot.range is None:
|
1186
1247
|
range = self.schemaview.schema.default_range
|
1187
1248
|
else:
|
@@ -1267,6 +1328,12 @@ class OwlSchemaGenerator(Generator):
|
|
1267
1328
|
show_default=True,
|
1268
1329
|
help="Use model URIs rather than class/slot URIs",
|
1269
1330
|
)
|
1331
|
+
@click.option(
|
1332
|
+
"--default-permissible-value-type",
|
1333
|
+
default=str(OWL.Class),
|
1334
|
+
show_default=True,
|
1335
|
+
help="Default OWL type for permissible values",
|
1336
|
+
)
|
1270
1337
|
@click.version_option(__version__, "-V", "--version")
|
1271
1338
|
def cli(yamlfile, metadata_profile: str, **kwargs):
|
1272
1339
|
"""Generate an OWL representation of a LinkML model
|
linkml/generators/plantumlgen.py
CHANGED
@@ -57,7 +57,7 @@ class PlantumlGenerator(Generator):
|
|
57
57
|
directory: Optional[str] = None,
|
58
58
|
load_image: bool = True,
|
59
59
|
**_,
|
60
|
-
) ->
|
60
|
+
) -> Optional[str]:
|
61
61
|
if directory:
|
62
62
|
os.makedirs(directory, exist_ok=True)
|
63
63
|
if classes is not None:
|
@@ -110,10 +110,13 @@ class PlantumlGenerator(Generator):
|
|
110
110
|
else:
|
111
111
|
self.logger.error(f"{resp.reason} accessing {plantuml_url}")
|
112
112
|
else:
|
113
|
-
|
114
|
-
"@startuml\
|
115
|
-
|
113
|
+
out = (
|
114
|
+
"@startuml\n"
|
115
|
+
"skinparam nodesep 10\n"
|
116
|
+
"hide circle\n"
|
117
|
+
"hide empty members\n" + "\n".join(dedup_plantumlclassdef) + "\n@enduml\n"
|
116
118
|
)
|
119
|
+
return out
|
117
120
|
|
118
121
|
def add_class(self, cn: ClassDefinitionName) -> str:
|
119
122
|
"""Define the class only if
|
@@ -130,13 +133,11 @@ class PlantumlGenerator(Generator):
|
|
130
133
|
if True or cn in slot.domain_of:
|
131
134
|
mod = self.prop_modifier(cls, slot)
|
132
135
|
slot_defs.append(
|
133
|
-
|
136
|
+
" {field} "
|
134
137
|
+ underscore(self.aliased_slot_name(slot))
|
135
|
-
+ '"'
|
136
138
|
+ mod
|
137
|
-
+
|
139
|
+
+ ": "
|
138
140
|
+ underscore(slot.range)
|
139
|
-
+ '"'
|
140
141
|
+ self.cardinality(slot)
|
141
142
|
)
|
142
143
|
self.class_generated.add(cn)
|
@@ -3,7 +3,6 @@ Generate JSON-LD contexts
|
|
3
3
|
|
4
4
|
"""
|
5
5
|
|
6
|
-
import csv
|
7
6
|
import os
|
8
7
|
from dataclasses import dataclass, field
|
9
8
|
from typing import Dict, Optional, Set, Union
|
@@ -59,7 +58,7 @@ class PrefixGenerator(Generator):
|
|
59
58
|
if self.default_ns:
|
60
59
|
self.emit_prefixes.add(self.default_ns)
|
61
60
|
|
62
|
-
def end_schema(self, base: Optional[Union[str, Namespace]] = None, output: Optional[str] = None, **_) ->
|
61
|
+
def end_schema(self, base: Optional[Union[str, Namespace]] = None, output: Optional[str] = None, **_) -> str:
|
63
62
|
context = JsonObj()
|
64
63
|
if base:
|
65
64
|
base = str(base)
|
@@ -74,31 +73,24 @@ class PrefixGenerator(Generator):
|
|
74
73
|
for k, v in self.slot_class_maps.items():
|
75
74
|
context[k] = v
|
76
75
|
|
77
|
-
if
|
78
|
-
|
76
|
+
if self.format == "tsv":
|
77
|
+
mapping: Dict = {} # prefix to IRI mapping
|
78
|
+
for prefix in sorted(self.emit_prefixes):
|
79
|
+
mapping[prefix] = self.namespaces[prefix]
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
items = []
|
82
|
+
for key, value in mapping.items():
|
83
|
+
items.append("\t".join([key, value]))
|
84
|
+
out = "\n".join(items)
|
84
85
|
|
85
|
-
with open(output, "w", encoding="UTF-8") as outf:
|
86
|
-
writer = csv.writer(outf, delimiter="\t")
|
87
|
-
for key, value in mapping.items():
|
88
|
-
writer.writerow([key, value])
|
89
|
-
else:
|
90
|
-
with open(output, "w", encoding="UTF-8") as outf:
|
91
|
-
outf.write(as_json(context))
|
92
86
|
else:
|
93
|
-
|
94
|
-
mapping: Dict = {} # prefix to IRI mapping
|
95
|
-
for prefix in sorted(self.emit_prefixes):
|
96
|
-
mapping[prefix] = self.namespaces[prefix]
|
87
|
+
out = str(as_json(context))
|
97
88
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
89
|
+
if output:
|
90
|
+
with open(output, "w", encoding="UTF-8") as outf:
|
91
|
+
outf.write(out)
|
92
|
+
|
93
|
+
return out
|
102
94
|
|
103
95
|
def visit_class(self, cls: ClassDefinition) -> bool:
|
104
96
|
class_def = {}
|
linkml/generators/protogen.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
from dataclasses import dataclass
|
3
|
+
from typing import Optional
|
3
4
|
|
4
5
|
import click
|
5
6
|
from linkml_runtime.linkml_model.meta import ClassDefinition, SlotDefinition
|
@@ -26,32 +27,36 @@ class ProtoGenerator(Generator):
|
|
26
27
|
# ObjectVars
|
27
28
|
relative_slot_num: int = 0
|
28
29
|
|
29
|
-
def
|
30
|
-
|
31
|
-
self.generate_header()
|
30
|
+
def visit_schema(self, **kwargs) -> Optional[str]:
|
31
|
+
return self.generate_header()
|
32
32
|
|
33
|
-
def generate_header(self):
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
def generate_header(self) -> str:
|
34
|
+
items = []
|
35
|
+
items.append(' syntax="proto3";')
|
36
|
+
items.append(" package")
|
37
|
+
items.append(f"// metamodel_version: {self.schema.metamodel_version}")
|
37
38
|
if self.schema.version:
|
38
|
-
|
39
|
+
items.append(f"// version: {self.schema.version}")
|
40
|
+
out = "\n".join(items) + "\n"
|
41
|
+
return out
|
39
42
|
|
40
|
-
def visit_class(self, cls: ClassDefinition) ->
|
43
|
+
def visit_class(self, cls: ClassDefinition) -> Optional[str]:
|
41
44
|
if cls.mixin or cls.abstract or not cls.slots:
|
42
|
-
return
|
45
|
+
return None
|
46
|
+
items = []
|
43
47
|
if cls.description:
|
44
48
|
for dline in cls.description.split("\n"):
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
items.append(f"// {dline}")
|
50
|
+
items.append(f"message {camelcase(cls.name)}")
|
51
|
+
items.append(" {")
|
52
|
+
out = "\n".join(items)
|
48
53
|
self.relative_slot_num = 0
|
49
|
-
return
|
54
|
+
return out
|
50
55
|
|
51
|
-
def end_class(self, cls: ClassDefinition) ->
|
52
|
-
|
56
|
+
def end_class(self, cls: ClassDefinition) -> str:
|
57
|
+
return "\n }\n"
|
53
58
|
|
54
|
-
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) ->
|
59
|
+
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> str:
|
55
60
|
qual = "repeated " if slot.multivalued else ""
|
56
61
|
slotname = lcamelcase(aliased_slot_name)
|
57
62
|
slot_range = camelcase(slot.range)
|
@@ -59,7 +64,7 @@ class ProtoGenerator(Generator):
|
|
59
64
|
# numbering of slots is important in the proto implementation
|
60
65
|
# and should be determined by the rank param.
|
61
66
|
slot.rank = 0
|
62
|
-
|
67
|
+
return f"\n {qual} {lcamelcase(slot_range)} {(slotname)} = {slot.rank}"
|
63
68
|
|
64
69
|
|
65
70
|
@shared_arguments(ProtoGenerator)
|
@@ -50,9 +50,13 @@ from linkml.generators.pydanticgen.template import (
|
|
50
50
|
PydanticModule,
|
51
51
|
TemplateModel,
|
52
52
|
)
|
53
|
+
from linkml.utils import deprecation_warning
|
53
54
|
from linkml.utils.generator import shared_arguments
|
54
55
|
from linkml.utils.ifabsent_functions import ifabsent_value_declaration
|
55
56
|
|
57
|
+
if int(PYDANTIC_VERSION[0]) == 1:
|
58
|
+
deprecation_warning("pydantic-v1")
|
59
|
+
|
56
60
|
|
57
61
|
def _get_pyrange(t: TypeDefinition, sv: SchemaView) -> str:
|
58
62
|
pyrange = t.repr if t is not None else None
|
@@ -215,6 +219,11 @@ class PydanticGenerator(OOCodeGenerator):
|
|
215
219
|
genmeta: bool = False
|
216
220
|
emit_metadata: bool = True
|
217
221
|
|
222
|
+
def __post_init__(self):
|
223
|
+
super().__post_init__()
|
224
|
+
if int(self.pydantic_version) == 1:
|
225
|
+
deprecation_warning("pydanticgen-v1")
|
226
|
+
|
218
227
|
def compile_module(self, **kwargs) -> ModuleType:
|
219
228
|
"""
|
220
229
|
Compiles generated python code to a module
|
@@ -714,7 +723,7 @@ Available templates to override:
|
|
714
723
|
@click.option(
|
715
724
|
"--pydantic-version",
|
716
725
|
type=click.IntRange(1, 2),
|
717
|
-
default=
|
726
|
+
default=int(PYDANTIC_VERSION[0]),
|
718
727
|
help="Pydantic version to use (1 or 2)",
|
719
728
|
)
|
720
729
|
@click.option(
|
@@ -741,7 +750,7 @@ def cli(
|
|
741
750
|
classvars=True,
|
742
751
|
slots=True,
|
743
752
|
array_representations=list("list"),
|
744
|
-
pydantic_version=
|
753
|
+
pydantic_version=int(PYDANTIC_VERSION[0]),
|
745
754
|
extra_fields: Literal["allow", "forbid", "ignore"] = "forbid",
|
746
755
|
**args,
|
747
756
|
):
|
linkml/generators/pythongen.py
CHANGED
@@ -185,11 +185,8 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
185
185
|
# Slots
|
186
186
|
{self.gen_slotdefs()}"""
|
187
187
|
|
188
|
-
def end_schema(self, **_):
|
189
|
-
|
190
|
-
re.sub(r" +\n", "\n", self.gen_schema().replace("\t", " ")).strip(" "),
|
191
|
-
end="",
|
192
|
-
)
|
188
|
+
def end_schema(self, **_) -> str:
|
189
|
+
return re.sub(r" +\n", "\n", self.gen_schema().replace("\t", " ")).strip(" ")
|
193
190
|
|
194
191
|
def gen_imports(self) -> str:
|
195
192
|
list_ents = [f"from {k} import {', '.join(v)}" for k, v in self.gen_import_list().items()]
|
linkml/generators/rdfgen.py
CHANGED
@@ -47,7 +47,7 @@ class RDFGenerator(Generator):
|
|
47
47
|
def _data(self, g: Graph) -> str:
|
48
48
|
return g.serialize(format="turtle" if self.format == "ttl" else self.format)
|
49
49
|
|
50
|
-
def end_schema(self, output: Optional[str] = None, context: str = None, **_) ->
|
50
|
+
def end_schema(self, output: Optional[str] = None, context: str = None, **_) -> str:
|
51
51
|
gen = JSONLDGenerator(
|
52
52
|
self.original_schema,
|
53
53
|
format=JSONLDGenerator.valid_formats[0],
|
@@ -67,11 +67,12 @@ class RDFGenerator(Generator):
|
|
67
67
|
base=str(self.namespaces._base),
|
68
68
|
prefix=True,
|
69
69
|
)
|
70
|
+
out = self._data(graph)
|
70
71
|
if output:
|
71
72
|
with open(output, "w", encoding="UTF-8") as outf:
|
72
|
-
outf.write(
|
73
|
-
|
74
|
-
|
73
|
+
outf.write(out)
|
74
|
+
|
75
|
+
return out
|
75
76
|
|
76
77
|
|
77
78
|
@shared_arguments(RDFGenerator)
|
linkml/generators/shaclgen.py
CHANGED
@@ -36,10 +36,11 @@ class ShaclGenerator(Generator):
|
|
36
36
|
super().__post_init__()
|
37
37
|
self.generate_header()
|
38
38
|
|
39
|
-
def generate_header(self):
|
40
|
-
|
39
|
+
def generate_header(self) -> str:
|
40
|
+
out = f"\n# metamodel_version: {self.schema.metamodel_version}"
|
41
41
|
if self.schema.version:
|
42
|
-
|
42
|
+
out += f"\n# version: {self.schema.version}"
|
43
|
+
return out
|
43
44
|
|
44
45
|
def serialize(self, **args) -> str:
|
45
46
|
g = self.as_graph()
|
@@ -129,7 +130,7 @@ class ShaclGenerator(Generator):
|
|
129
130
|
|
130
131
|
self._add_class(cl_node_pv, r)
|
131
132
|
range_list.append(class_node)
|
132
|
-
elif r in sv.all_types()
|
133
|
+
elif r in sv.all_types():
|
133
134
|
t_node = BNode()
|
134
135
|
|
135
136
|
def t_node_pv(p, v):
|
@@ -167,7 +168,7 @@ class ShaclGenerator(Generator):
|
|
167
168
|
prop_pv(SH.nodeKind, SH.IRI)
|
168
169
|
else:
|
169
170
|
prop_pv(SH.nodeKind, SH.BlankNodeOrIRI)
|
170
|
-
elif r in sv.all_types()
|
171
|
+
elif r in sv.all_types():
|
171
172
|
self._add_type(prop_pv, r)
|
172
173
|
elif r in sv.all_enums():
|
173
174
|
self._add_enum(g, prop_pv, r)
|
@@ -200,10 +201,11 @@ class ShaclGenerator(Generator):
|
|
200
201
|
func(SH["in"], pv_node)
|
201
202
|
|
202
203
|
def _add_type(self, func: Callable, r: ElementName) -> None:
|
204
|
+
func(SH.nodeKind, SH.Literal)
|
203
205
|
sv = self.schemaview
|
204
206
|
rt = sv.get_type(r)
|
205
207
|
if rt.uri:
|
206
|
-
func(SH.datatype, rt
|
208
|
+
func(SH.datatype, URIRef(sv.get_uri(rt, expand=True)))
|
207
209
|
else:
|
208
210
|
logging.error(f"No URI for type {rt.name}")
|
209
211
|
|
linkml/generators/shexgen.py
CHANGED
@@ -55,12 +55,12 @@ class ShExGenerator(Generator):
|
|
55
55
|
self.namespaces.join(self.namespaces[METAMODEL_NAMESPACE_NAME], "")
|
56
56
|
) # URI for the metamodel
|
57
57
|
self.base = Namespace(self.namespaces.join(self.namespaces._base, "")) # Base URI for what is being modeled
|
58
|
-
self.generate_header()
|
59
58
|
|
60
|
-
def generate_header(self):
|
61
|
-
|
59
|
+
def generate_header(self) -> str:
|
60
|
+
out = f"# metamodel_version: {self.schema.metamodel_version}\n"
|
62
61
|
if self.schema.version:
|
63
|
-
|
62
|
+
out += f"# version: {self.schema.version}\n"
|
63
|
+
return out
|
64
64
|
|
65
65
|
def visit_schema(self, **_):
|
66
66
|
# Adjust the schema context to include the base model URI
|
@@ -80,6 +80,8 @@ class ShExGenerator(Generator):
|
|
80
80
|
else:
|
81
81
|
typeof_uri = self._class_or_type_uri(typ.typeof)
|
82
82
|
self.shapes.append(Shape(id=model_uri, expression=typeof_uri))
|
83
|
+
if self.format != "json":
|
84
|
+
return self.generate_header()
|
83
85
|
|
84
86
|
def visit_class(self, cls: ClassDefinition) -> bool:
|
85
87
|
self.shape = Shape()
|
@@ -160,7 +162,7 @@ class ShExGenerator(Generator):
|
|
160
162
|
else:
|
161
163
|
constraint.valueExpr = self._class_or_type_uri(slot.range)
|
162
164
|
|
163
|
-
def end_schema(self, output: Optional[str] = None, **_) ->
|
165
|
+
def end_schema(self, output: Optional[str] = None, **_) -> str:
|
164
166
|
self.shex.shapes = self.shapes if self.shapes else [Shape()]
|
165
167
|
shex = as_json_1(self.shex)
|
166
168
|
if self.format == "rdf":
|
@@ -172,11 +174,11 @@ class ShExGenerator(Generator):
|
|
172
174
|
g = Graph()
|
173
175
|
self.namespaces.load_graph(g)
|
174
176
|
shex = str(ShExC(self.shex, base=sfx(self.namespaces._base), namespaces=g))
|
177
|
+
|
175
178
|
if output:
|
176
179
|
with open(output, "w", encoding="UTF-8") as outf:
|
177
180
|
outf.write(shex)
|
178
|
-
|
179
|
-
print(shex)
|
181
|
+
return shex
|
180
182
|
|
181
183
|
def _class_or_type_uri(
|
182
184
|
self,
|
linkml/generators/summarygen.py
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import os
|
6
|
-
import sys
|
7
6
|
from csv import DictWriter
|
8
7
|
from dataclasses import dataclass
|
8
|
+
from io import StringIO
|
9
9
|
from typing import Optional
|
10
10
|
|
11
11
|
import click
|
@@ -28,9 +28,12 @@ class SummaryGenerator(Generator):
|
|
28
28
|
slottab: Optional[DictWriter] = None
|
29
29
|
dialect: str = "excel-tab"
|
30
30
|
|
31
|
+
_str_io: Optional[StringIO] = None
|
32
|
+
|
31
33
|
def visit_schema(self, **_) -> None:
|
34
|
+
self._str_io = StringIO()
|
32
35
|
self.classtab = DictWriter(
|
33
|
-
|
36
|
+
self._str_io,
|
34
37
|
[
|
35
38
|
"Class Name",
|
36
39
|
"Parent Class",
|
@@ -79,6 +82,9 @@ class SummaryGenerator(Generator):
|
|
79
82
|
}
|
80
83
|
)
|
81
84
|
|
85
|
+
def end_schema(self, **kwargs) -> Optional[str]:
|
86
|
+
return self._str_io.getvalue()
|
87
|
+
|
82
88
|
|
83
89
|
@shared_arguments(SummaryGenerator)
|
84
90
|
@click.version_option(__version__, "-V", "--version")
|
@@ -71,8 +71,8 @@ class TerminusdbGenerator(Generator):
|
|
71
71
|
self.classes = []
|
72
72
|
self.raw_additions = []
|
73
73
|
|
74
|
-
def end_schema(self, **_) ->
|
75
|
-
|
74
|
+
def end_schema(self, **_) -> str:
|
75
|
+
return json.dumps(WQ().woql_and(*self.classes, *self.raw_additions).to_dict(), indent=2)
|
76
76
|
|
77
77
|
def visit_class(self, cls: ClassDefinition) -> bool:
|
78
78
|
self.clswq = WQ().add_class(camelcase(cls.name)).label(camelcase(cls.name)).description(be(cls.description))
|
linkml/generators/yumlgen.py
CHANGED
@@ -59,7 +59,7 @@ class YumlGenerator(Generator):
|
|
59
59
|
diagram_name: Optional[str] = None,
|
60
60
|
load_image: bool = True,
|
61
61
|
**_,
|
62
|
-
) ->
|
62
|
+
) -> Optional[str]:
|
63
63
|
if directory:
|
64
64
|
os.makedirs(directory, exist_ok=True)
|
65
65
|
if classes is not None:
|
@@ -106,7 +106,7 @@ class YumlGenerator(Generator):
|
|
106
106
|
else:
|
107
107
|
self.logger.error(f"{resp.reason} accessing {url}: {resp!r}")
|
108
108
|
else:
|
109
|
-
|
109
|
+
return str(YUML) + ",".join(yumlclassdef)
|
110
110
|
|
111
111
|
def class_box(self, cn: ClassDefinitionName) -> str:
|
112
112
|
"""Generate a box for the class. Populate its interior only if (a) it hasn't previously been generated and
|
linkml/utils/__init__.py
CHANGED