linkml 1.7.8__py3-none-any.whl → 1.7.9__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 +9 -0
- linkml/generators/pythongen.py +2 -5
- linkml/generators/rdfgen.py +5 -4
- linkml/generators/shaclgen.py +4 -3
- 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.9.dist-info}/METADATA +1 -1
- {linkml-1.7.8.dist-info → linkml-1.7.9.dist-info}/RECORD +32 -31
- {linkml-1.7.8.dist-info → linkml-1.7.9.dist-info}/LICENSE +0 -0
- {linkml-1.7.8.dist-info → linkml-1.7.9.dist-info}/WHEEL +0 -0
- {linkml-1.7.8.dist-info → linkml-1.7.9.dist-info}/entry_points.txt +0 -0
linkml/generators/csvgen.py
CHANGED
@@ -3,9 +3,9 @@ Generate CSVs
|
|
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 List, Optional, Set
|
10
10
|
|
11
11
|
import click
|
@@ -42,14 +42,20 @@ class CsvGenerator(Generator):
|
|
42
42
|
writer: Optional[DictWriter] = None
|
43
43
|
"""Python dictwriter"""
|
44
44
|
|
45
|
+
_str_io: Optional[StringIO] = None
|
46
|
+
"""String that the writer outputs to"""
|
47
|
+
|
45
48
|
def __post_init__(self):
|
46
49
|
super().__post_init__()
|
50
|
+
self._str_io = None
|
51
|
+
|
47
52
|
self.generate_header() # TODO: don't do this in initialization
|
48
53
|
|
49
|
-
def generate_header(self):
|
50
|
-
|
54
|
+
def generate_header(self) -> str:
|
55
|
+
out = f"# metamodel_version: {self.schema.metamodel_version}"
|
51
56
|
if self.schema.version:
|
52
|
-
|
57
|
+
out = "\n".join([out, f"# version: {self.schema.version}"])
|
58
|
+
return out
|
53
59
|
|
54
60
|
def visit_schema(self, classes: List[ClassDefinitionName] = None, **_) -> None:
|
55
61
|
# Note: classes comes from the "root" argument
|
@@ -65,8 +71,9 @@ class CsvGenerator(Generator):
|
|
65
71
|
else:
|
66
72
|
self.closure.update(self.ancestors(self.schema.classes[clsname]))
|
67
73
|
|
74
|
+
self._str_io = StringIO()
|
68
75
|
dialect: str = "excel" if self.format == "csv" else "excel-tab"
|
69
|
-
self.writer = DictWriter(
|
76
|
+
self.writer = DictWriter(self._str_io, ["id", "mappings", "description"], dialect=dialect)
|
70
77
|
self.writer.writeheader()
|
71
78
|
|
72
79
|
def visit_class(self, cls: ClassDefinition) -> bool:
|
@@ -83,6 +90,9 @@ class CsvGenerator(Generator):
|
|
83
90
|
return True
|
84
91
|
return False
|
85
92
|
|
93
|
+
def end_schema(self, **kwargs) -> str:
|
94
|
+
return self._str_io.getvalue()
|
95
|
+
|
86
96
|
|
87
97
|
@shared_arguments(CsvGenerator)
|
88
98
|
@click.command()
|
@@ -1,19 +1,29 @@
|
|
1
|
+
{% macro slot_relationship(element, slot) %}
|
2
|
+
{% set range_element = gen.name(schemaview.get_element(slot.range)) %}
|
3
|
+
{% set relation_label = gen.name(slot) %}
|
4
|
+
{{ gen.name(element) }} --> "{{ gen.cardinality(slot) }}" {{ range_element }} : {{ relation_label }}
|
5
|
+
click {{ range_element }} href "../{{ range_element }}"
|
6
|
+
{% endmacro %}
|
7
|
+
|
1
8
|
{% if schemaview.class_parents(element.name) and schemaview.class_children(element.name) %}
|
2
9
|
```{{ gen.mermaid_directive() }}
|
3
10
|
classDiagram
|
4
11
|
class {{ gen.name(element) }}
|
12
|
+
click {{ gen.name(element) }} href "../{{gen.name(element)}}"
|
5
13
|
{% for s in schemaview.class_parents(element.name)|sort(attribute='name') -%}
|
6
14
|
{{ gen.name(schemaview.get_element(s)) }} <|-- {{ gen.name(element) }}
|
15
|
+
click {{ gen.name(schemaview.get_element(s)) }} href "../{{gen.name(schemaview.get_element(s))}}"
|
7
16
|
{% endfor %}
|
8
17
|
|
9
18
|
{% for s in schemaview.class_children(element.name)|sort(attribute='name') -%}
|
10
19
|
{{ gen.name(element) }} <|-- {{ gen.name(schemaview.get_element(s)) }}
|
20
|
+
click {{ gen.name(schemaview.get_element(s)) }} href "../{{gen.name(schemaview.get_element(s))}}"
|
11
21
|
{% endfor %}
|
12
22
|
|
13
23
|
{% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
|
14
24
|
{{ gen.name(element) }} : {{gen.name(s)}}
|
15
25
|
{% if s.range not in gen.all_type_object_names() %}
|
16
|
-
{{
|
26
|
+
{{ slot_relationship(element, s) }}
|
17
27
|
{% endif %}
|
18
28
|
{% endfor %}
|
19
29
|
```
|
@@ -21,13 +31,15 @@
|
|
21
31
|
```{{ gen.mermaid_directive() }}
|
22
32
|
classDiagram
|
23
33
|
class {{ gen.name(element) }}
|
34
|
+
click {{ gen.name(element) }} href "../{{gen.name(element)}}"
|
24
35
|
{% for s in schemaview.class_parents(element.name)|sort(attribute='name') -%}
|
25
36
|
{{ gen.name(schemaview.get_element(s)) }} <|-- {{ gen.name(element) }}
|
37
|
+
click {{ gen.name(schemaview.get_element(s)) }} href "../{{gen.name(schemaview.get_element(s))}}"
|
26
38
|
{% endfor %}
|
27
39
|
{% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
|
28
40
|
{{ gen.name(element) }} : {{gen.name(s)}}
|
29
41
|
{% if s.range not in gen.all_type_object_names() %}
|
30
|
-
{{
|
42
|
+
{{ slot_relationship(element, s) }}
|
31
43
|
{% endif %}
|
32
44
|
{% endfor %}
|
33
45
|
```
|
@@ -35,13 +47,15 @@
|
|
35
47
|
```{{ gen.mermaid_directive() }}
|
36
48
|
classDiagram
|
37
49
|
class {{ gen.name(element) }}
|
50
|
+
click {{ gen.name(element) }} href "../{{gen.name(element)}}"
|
38
51
|
{% for s in schemaview.class_children(element.name)|sort(attribute='name') -%}
|
39
52
|
{{ gen.name(element) }} <|-- {{ gen.name(schemaview.get_element(s)) }}
|
53
|
+
click {{ gen.name(schemaview.get_element(s)) }} href "../{{gen.name(schemaview.get_element(s))}}"
|
40
54
|
{% endfor %}
|
41
55
|
{% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
|
42
56
|
{{ gen.name(element) }} : {{gen.name(s)}}
|
43
57
|
{% if s.range not in gen.all_type_object_names() %}
|
44
|
-
{{
|
58
|
+
{{ slot_relationship(element, s) }}
|
45
59
|
{% endif %}
|
46
60
|
{% endfor %}
|
47
61
|
```
|
@@ -49,10 +63,11 @@
|
|
49
63
|
```{{ gen.mermaid_directive() }}
|
50
64
|
classDiagram
|
51
65
|
class {{ gen.name(element) }}
|
66
|
+
click {{ gen.name(element) }} href "../{{gen.name(element)}}"
|
52
67
|
{% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
|
53
68
|
{{ gen.name(element) }} : {{gen.name(s)}}
|
54
69
|
{% if s.range not in gen.all_type_object_names() %}
|
55
|
-
{{
|
70
|
+
{{ slot_relationship(element, s) }}
|
56
71
|
{% endif %}
|
57
72
|
{% endfor %}
|
58
73
|
```
|
linkml/generators/docgen.py
CHANGED
@@ -567,23 +567,60 @@ class DocGenerator(Generator):
|
|
567
567
|
@staticmethod
|
568
568
|
def cardinality(slot: SlotDefinition) -> str:
|
569
569
|
"""
|
570
|
-
Render combination of required, multivalued, and
|
571
|
-
|
572
|
-
|
570
|
+
Render combination of required, multivalued, recommended, and exact_cardinality as a range,
|
571
|
+
according to Mermaid conventions. Considers 'required' and 'multivalued' to set defaults
|
572
|
+
for 'minimum_cardinality' and 'maximum_cardinality'.
|
573
|
+
|
574
|
+
Reference: https://mermaid.js.org/syntax/classDiagram.html#cardinality-multiplicity-on-relations
|
575
|
+
|
576
|
+
The different cardinality options are:
|
577
|
+
- 1 Only 1
|
578
|
+
- 0..1 Zero or One
|
579
|
+
- 1..* One or more
|
580
|
+
- * Many
|
581
|
+
- n n (where n>1)
|
582
|
+
- 0..n zero to n (where n>1)
|
583
|
+
- 1..n one to n (where n>1)
|
584
|
+
:param slot: SlotDefinition
|
585
|
+
:return: cardinality string as used in Mermaid diagrams
|
573
586
|
"""
|
574
|
-
if slot.
|
575
|
-
|
576
|
-
else:
|
577
|
-
min = "0"
|
578
|
-
if slot.multivalued:
|
579
|
-
max = "*"
|
587
|
+
if slot.exact_cardinality is not None:
|
588
|
+
cardinality = str(slot.exact_cardinality) # handles 'n' case
|
580
589
|
else:
|
581
|
-
|
590
|
+
if slot.required or slot.identifier:
|
591
|
+
min_card = "1"
|
592
|
+
else:
|
593
|
+
min_card = str(slot.minimum_cardinality) if slot.minimum_cardinality is not None else "0"
|
594
|
+
|
595
|
+
if slot.multivalued:
|
596
|
+
max_card = "*"
|
597
|
+
else:
|
598
|
+
max_card = str(slot.maximum_cardinality) if slot.maximum_cardinality is not None else "1"
|
599
|
+
|
600
|
+
if min_card == "0":
|
601
|
+
if max_card == "1":
|
602
|
+
cardinality = "0..1" # handles '0..1' case
|
603
|
+
elif max_card == "*":
|
604
|
+
cardinality = "*" # handles '*' case
|
605
|
+
else:
|
606
|
+
cardinality = f"0..{max_card}" # handles '0..n' case
|
607
|
+
elif min_card == "1":
|
608
|
+
if max_card == "1":
|
609
|
+
cardinality = "1" # handles '1' case
|
610
|
+
elif max_card == "*":
|
611
|
+
cardinality = "1..*" # handles '1..*' case
|
612
|
+
else:
|
613
|
+
cardinality = f"1..{max_card}" # handles '1..n' case
|
614
|
+
else:
|
615
|
+
if max_card == "*":
|
616
|
+
cardinality = f"{min_card}..*" # handles 'n..*' case
|
617
|
+
else:
|
618
|
+
cardinality = f"{min_card}..{max_card}" # handles 'n..m' case
|
619
|
+
|
582
620
|
if slot.recommended:
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
return f"{min}..{max}{info}"
|
621
|
+
cardinality += " _recommended_"
|
622
|
+
|
623
|
+
return cardinality
|
587
624
|
|
588
625
|
def mermaid_directive(self) -> str:
|
589
626
|
"""
|
@@ -932,6 +969,14 @@ class DocGenerator(Generator):
|
|
932
969
|
"--example-directory",
|
933
970
|
help="Folder in which example files are found. These are used to make inline examples",
|
934
971
|
)
|
972
|
+
@click.option(
|
973
|
+
"-d",
|
974
|
+
"--include",
|
975
|
+
help="""
|
976
|
+
Include LinkML Schema outside of imports mechanism. Helpful in including deprecated classes and slots in a separate
|
977
|
+
YAML, and including it when necessary but not by default (e.g. in documentation or for backwards compatibility)
|
978
|
+
""",
|
979
|
+
)
|
935
980
|
@click.version_option(__version__, "-V", "--version")
|
936
981
|
@click.command()
|
937
982
|
def cli(
|
linkml/generators/graphqlgen.py
CHANGED
@@ -19,28 +19,26 @@ class GraphqlGenerator(Generator):
|
|
19
19
|
uses_schemaloader = True
|
20
20
|
requires_metamodel = False
|
21
21
|
|
22
|
-
def
|
23
|
-
|
24
|
-
# TODO: move this
|
25
|
-
self.generate_header()
|
22
|
+
def visit_schema(self, **kwargs) -> str:
|
23
|
+
return self.generate_header()
|
26
24
|
|
27
|
-
def generate_header(self):
|
28
|
-
|
25
|
+
def generate_header(self) -> str:
|
26
|
+
out = f"# metamodel_version: {self.schema.metamodel_version}\n"
|
29
27
|
if self.schema.version:
|
30
|
-
|
28
|
+
out += f"# version: {self.schema.version}\n"
|
29
|
+
return out
|
31
30
|
|
32
|
-
def visit_class(self, cls: ClassDefinition) ->
|
31
|
+
def visit_class(self, cls: ClassDefinition) -> str:
|
33
32
|
etype = "interface" if (cls.abstract or cls.mixin) and not cls.mixins else "type"
|
34
33
|
mixins = ", ".join([camelcase(mixin) for mixin in cls.mixins])
|
35
|
-
|
36
|
-
|
37
|
-
return
|
34
|
+
out = f"{etype} {camelcase(cls.name)}" + (f" implements {mixins}" if mixins else "")
|
35
|
+
out = "\n".join([out, " {"])
|
36
|
+
return out
|
38
37
|
|
39
|
-
def end_class(self, cls: ClassDefinition) ->
|
40
|
-
|
41
|
-
print()
|
38
|
+
def end_class(self, cls: ClassDefinition) -> str:
|
39
|
+
return "\n }\n\n"
|
42
40
|
|
43
|
-
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) ->
|
41
|
+
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> str:
|
44
42
|
slotrange = (
|
45
43
|
camelcase(slot.range)
|
46
44
|
if slot.range in self.schema.classes or slot.range in self.schema.types or slot.range in self.schema.enums
|
@@ -50,7 +48,7 @@ class GraphqlGenerator(Generator):
|
|
50
48
|
slotrange = f"[{slotrange}]"
|
51
49
|
if slot.required:
|
52
50
|
slotrange = slotrange + "!"
|
53
|
-
|
51
|
+
return f"\n {lcamelcase(aliased_slot_name)}: {slotrange}"
|
54
52
|
|
55
53
|
|
56
54
|
@shared_arguments(GraphqlGenerator)
|
@@ -89,7 +89,7 @@ class ContextGenerator(Generator):
|
|
89
89
|
flatprefixes: Optional[bool] = False,
|
90
90
|
model: Optional[bool] = True,
|
91
91
|
**_,
|
92
|
-
) ->
|
92
|
+
) -> str:
|
93
93
|
if model is None:
|
94
94
|
model = self.model
|
95
95
|
context = JsonObj()
|
@@ -126,8 +126,8 @@ class ContextGenerator(Generator):
|
|
126
126
|
if output:
|
127
127
|
with open(output, "w", encoding="UTF-8") as outf:
|
128
128
|
outf.write(as_json(context))
|
129
|
-
|
130
|
-
|
129
|
+
|
130
|
+
return str(as_json(context)) + "\n"
|
131
131
|
|
132
132
|
def visit_class(self, cls: ClassDefinition) -> bool:
|
133
133
|
class_def = {}
|
linkml/generators/jsonldgen.py
CHANGED
@@ -151,7 +151,7 @@ class JSONLDGenerator(Generator):
|
|
151
151
|
def visit_subset(self, ss: SubsetDefinition) -> None:
|
152
152
|
self._visit(ss)
|
153
153
|
|
154
|
-
def end_schema(self, context: str = None, **_) ->
|
154
|
+
def end_schema(self, context: str = None, **_) -> str:
|
155
155
|
self._add_type(self.schema)
|
156
156
|
base_prefix = self.default_prefix()
|
157
157
|
|
@@ -186,8 +186,9 @@ class JSONLDGenerator(Generator):
|
|
186
186
|
if base_prefix:
|
187
187
|
self.schema["@context"].append({"@base": base_prefix})
|
188
188
|
# json_obj["@id"] = self.schema.id
|
189
|
-
|
189
|
+
out = str(as_json(self.schema, indent=" ")) + "\n"
|
190
190
|
self.schema = self.original_schema
|
191
|
+
return out
|
191
192
|
|
192
193
|
|
193
194
|
@shared_arguments(JSONLDGenerator)
|
@@ -185,6 +185,7 @@ class JsonSchemaGenerator(Generator):
|
|
185
185
|
indent: int = 4
|
186
186
|
|
187
187
|
inline: bool = False
|
188
|
+
|
188
189
|
top_class: Optional[Union[ClassDefinitionName, str]] = None # JSON object is one instance of this
|
189
190
|
"""Class instantiated by the root node of the document tree"""
|
190
191
|
|
@@ -662,6 +663,14 @@ disable pretty-printing and return the most compact JSON representation
|
|
662
663
|
Specify from which slot are JSON Schema 'title' annotations generated.
|
663
664
|
""",
|
664
665
|
)
|
666
|
+
@click.option(
|
667
|
+
"-d",
|
668
|
+
"--include",
|
669
|
+
help="""
|
670
|
+
Include LinkML Schema outside of imports mechanism. Helpful in including deprecated classes and slots in a separate
|
671
|
+
YAML, and including it when necessary but not by default (e.g. in documentation or for backwards compatibility)
|
672
|
+
""",
|
673
|
+
)
|
665
674
|
@click.version_option(__version__, "-V", "--version")
|
666
675
|
def cli(yamlfile, **kwargs):
|
667
676
|
"""Generate JSON Schema representation of a LinkML model"""
|