linkml 1.8.5__py3-none-any.whl → 1.8.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/cli/main.py +2 -0
- linkml/generators/common/build.py +1 -2
- linkml/generators/common/lifecycle.py +18 -2
- linkml/generators/dbmlgen.py +173 -0
- linkml/generators/erdiagramgen.py +1 -0
- linkml/generators/jsonldcontextgen.py +7 -1
- linkml/generators/jsonldgen.py +1 -3
- linkml/generators/jsonschemagen.py +84 -53
- linkml/generators/linkmlgen.py +13 -1
- linkml/generators/mermaidclassdiagramgen.py +133 -0
- linkml/generators/owlgen.py +16 -14
- linkml/generators/plantumlgen.py +17 -10
- linkml/generators/pydanticgen/array.py +21 -61
- linkml/generators/pydanticgen/templates/attribute.py.jinja +1 -1
- linkml/generators/shacl/shacl_ifabsent_processor.py +2 -1
- linkml/generators/shaclgen.py +16 -5
- linkml/generators/shexgen.py +1 -3
- linkml/generators/summarygen.py +1 -3
- linkml/generators/typescriptgen.py +3 -1
- linkml/generators/yamlgen.py +1 -3
- linkml/linter/rules.py +3 -1
- linkml/transformers/logical_model_transformer.py +7 -5
- linkml/utils/converter.py +17 -0
- linkml/utils/deprecation.py +10 -0
- linkml/utils/generator.py +11 -2
- linkml/utils/helpers.py +65 -0
- linkml/utils/validation.py +2 -1
- linkml/validator/__init__.py +2 -2
- linkml/validator/plugins/jsonschema_validation_plugin.py +1 -0
- linkml/validator/report.py +4 -1
- linkml/validator/validator.py +4 -4
- linkml/validators/jsonschemavalidator.py +10 -0
- linkml/validators/sparqlvalidator.py +7 -0
- linkml/workspaces/example_runner.py +21 -4
- {linkml-1.8.5.dist-info → linkml-1.8.7.dist-info}/METADATA +2 -2
- {linkml-1.8.5.dist-info → linkml-1.8.7.dist-info}/RECORD +39 -37
- {linkml-1.8.5.dist-info → linkml-1.8.7.dist-info}/entry_points.txt +2 -0
- {linkml-1.8.5.dist-info → linkml-1.8.7.dist-info}/LICENSE +0 -0
- {linkml-1.8.5.dist-info → linkml-1.8.7.dist-info}/WHEEL +0 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
import importlib
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import List, Optional
|
7
|
+
|
8
|
+
import click
|
9
|
+
from jinja2 import Environment, FileSystemLoader
|
10
|
+
from linkml_runtime.linkml_model.meta import Element, SlotDefinition
|
11
|
+
from linkml_runtime.utils.schemaview import SchemaView
|
12
|
+
|
13
|
+
from linkml.generators.docgen import DocGenerator, customize_environment
|
14
|
+
from linkml.utils.generator import Generator, shared_arguments
|
15
|
+
|
16
|
+
|
17
|
+
@dataclass
|
18
|
+
class MermaidClassDiagramGenerator(Generator):
|
19
|
+
"""This generator creates Mermaid Class diagrams (https://mermaid.js.org/syntax/classDiagram.html)
|
20
|
+
for individual classes in a given LinkML schema and outputs them as Markdown files. It uses
|
21
|
+
a jinja template to render these class diagrams, which can be customized by the user and pointed
|
22
|
+
to using the `--template-file` option. If no template is provided, then the default template
|
23
|
+
file provided by LinkML and present at linkml/generators/docgen/class_diagram.md.jinja2 will be used.
|
24
|
+
The generator also has the option of specifying a certain set of classes for which you want the
|
25
|
+
class diagrams to be generated by using the `--classes` option.
|
26
|
+
"""
|
27
|
+
|
28
|
+
generatorname = os.path.basename(__file__)
|
29
|
+
generatorversion = "0.0.1"
|
30
|
+
valid_formats = ["markdown"]
|
31
|
+
uses_schemaloader = False
|
32
|
+
requires_metamodel = False
|
33
|
+
|
34
|
+
directory: Optional[str] = None # output directory with generated markdown files
|
35
|
+
template_file: Optional[str] = None # custom/default jinja template for class diagrams
|
36
|
+
classes: List[str] = field(default_factory=list) # optional subset of classes
|
37
|
+
|
38
|
+
def __post_init__(self):
|
39
|
+
super().__post_init__()
|
40
|
+
self.logger = logging.getLogger(__name__)
|
41
|
+
self.schemaview = SchemaView(self.schema, merge_imports=self.mergeimports)
|
42
|
+
|
43
|
+
# set the output directory
|
44
|
+
self.output_directory = Path(self.directory)
|
45
|
+
|
46
|
+
# set the template file
|
47
|
+
if not self.template_file:
|
48
|
+
package_dir = os.path.dirname(importlib.util.find_spec("linkml").origin)
|
49
|
+
# Default location of the template file
|
50
|
+
self.template_file = os.path.join(package_dir, "generators", "docgen", "class_diagram.md.jinja2")
|
51
|
+
|
52
|
+
def generate_class_diagrams(self):
|
53
|
+
"""Generate Mermaid class diagrams for the specified subset of classes
|
54
|
+
or all classes if none are specified.
|
55
|
+
"""
|
56
|
+
self.output_directory.mkdir(parents=True, exist_ok=True)
|
57
|
+
|
58
|
+
template_folder = os.path.dirname(self.template_file)
|
59
|
+
template_name = os.path.basename(self.template_file)
|
60
|
+
loader = FileSystemLoader(template_folder)
|
61
|
+
env = Environment(loader=loader)
|
62
|
+
customize_environment(env)
|
63
|
+
|
64
|
+
template = env.get_template(template_name)
|
65
|
+
|
66
|
+
all_classes = self.schemaview.all_classes()
|
67
|
+
|
68
|
+
if self.classes:
|
69
|
+
class_items = [(cn, all_classes[cn]) for cn in self.classes if cn in all_classes]
|
70
|
+
else:
|
71
|
+
class_items = list(all_classes.items())
|
72
|
+
|
73
|
+
for cn, class_def in class_items:
|
74
|
+
self.logger.info(f"Generating Mermaid diagram for class: {cn}")
|
75
|
+
rendered = template.render(gen=self, element=class_def, schemaview=self.schemaview)
|
76
|
+
outfile = self.output_directory / f"{cn}.md"
|
77
|
+
with open(outfile, "w", encoding="utf-8") as f:
|
78
|
+
f.write(rendered)
|
79
|
+
|
80
|
+
def cardinality(self, slot: SlotDefinition) -> str:
|
81
|
+
"""Reuses the cardinality logic from DocGenerator."""
|
82
|
+
return DocGenerator.cardinality(slot)
|
83
|
+
|
84
|
+
def mermaid_directive(self) -> str:
|
85
|
+
"""Provides the code fence directive (e.g., `mermaid` or `{mermaid}`)."""
|
86
|
+
return "mermaid"
|
87
|
+
|
88
|
+
def name(self, element: Element) -> str:
|
89
|
+
"""Returns the canonical name for an element."""
|
90
|
+
return element.name
|
91
|
+
|
92
|
+
def all_type_object_names(self):
|
93
|
+
return list(self.schemaview.all_types().keys())
|
94
|
+
|
95
|
+
|
96
|
+
@shared_arguments(MermaidClassDiagramGenerator)
|
97
|
+
@click.command()
|
98
|
+
@click.option(
|
99
|
+
"--template-file",
|
100
|
+
"-t",
|
101
|
+
type=click.Path(exists=True),
|
102
|
+
default=None,
|
103
|
+
help="Path to Jinja template for class diagrams.",
|
104
|
+
)
|
105
|
+
@click.option(
|
106
|
+
"--directory",
|
107
|
+
"-d",
|
108
|
+
type=click.Path(),
|
109
|
+
required=True,
|
110
|
+
help="Folder in which to write the Markdown files.",
|
111
|
+
)
|
112
|
+
@click.option(
|
113
|
+
"--classes",
|
114
|
+
"-c",
|
115
|
+
multiple=True,
|
116
|
+
help="One or more classes in the schema for which to generate diagrams. "
|
117
|
+
"If omitted, diagrams for all classes are generated.",
|
118
|
+
)
|
119
|
+
@click.version_option(click.__version__, "-V", "--version")
|
120
|
+
def cli(yamlfile, template_file, directory, classes, **args):
|
121
|
+
logging.basicConfig(level=logging.INFO)
|
122
|
+
gen = MermaidClassDiagramGenerator(
|
123
|
+
schema=yamlfile,
|
124
|
+
template_file=template_file,
|
125
|
+
directory=directory,
|
126
|
+
classes=list(classes), # convert tuple to a list
|
127
|
+
**args,
|
128
|
+
)
|
129
|
+
gen.generate_class_diagrams()
|
130
|
+
|
131
|
+
|
132
|
+
if __name__ == "__main__":
|
133
|
+
cli()
|
linkml/generators/owlgen.py
CHANGED
@@ -170,6 +170,9 @@ class OwlSchemaGenerator(Generator):
|
|
170
170
|
default_factory=lambda: package_schemaview("linkml_runtime.linkml_model.meta")
|
171
171
|
)
|
172
172
|
|
173
|
+
enum_iri_separator: str = "#"
|
174
|
+
"""Separator for enum IRI. Can be overridden for example if your namespace IRI already contains a #"""
|
175
|
+
|
173
176
|
def as_graph(self) -> Graph:
|
174
177
|
"""
|
175
178
|
Generate an rdflib Graph from the LinkML schema.
|
@@ -705,18 +708,10 @@ class OwlSchemaGenerator(Generator):
|
|
705
708
|
owl_exprs.append(eq_uri)
|
706
709
|
if element.equals_string_in:
|
707
710
|
equals_string_in = element.equals_string_in
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
self._datatype_restriction(XSD.string, [self._facet(XSD.pattern, s)]) for s in equals_string_in
|
713
|
-
]
|
714
|
-
union_expr = self._union_of(dt_exprs, owl_types={RDFS.Literal})
|
715
|
-
owl_exprs.append(union_expr)
|
716
|
-
owl_types.add(RDFS.Literal)
|
717
|
-
else:
|
718
|
-
eq_uris = [URIRef(self.schemaview.expand_curie(s)) for s in equals_string_in]
|
719
|
-
owl_exprs.append(self._union_of(eq_uris))
|
711
|
+
literals = [Literal(s) for s in equals_string_in]
|
712
|
+
one_of_expr = self._boolean_expression(literals, OWL.oneOf, owl_types={RDFS.Literal})
|
713
|
+
owl_exprs.append(one_of_expr)
|
714
|
+
owl_types.add(RDFS.Literal)
|
720
715
|
for constraint_prop, constraint_val in constraints.items():
|
721
716
|
if is_literal is not None and not is_literal:
|
722
717
|
# In LinkML, it is permissible to have a literal constraints on slots that refer to
|
@@ -1137,7 +1132,7 @@ class OwlSchemaGenerator(Generator):
|
|
1137
1132
|
|
1138
1133
|
def _boolean_expression(
|
1139
1134
|
self,
|
1140
|
-
exprs: List[Union[BNode, URIRef]],
|
1135
|
+
exprs: List[Union[BNode, URIRef, Literal]],
|
1141
1136
|
predicate: URIRef,
|
1142
1137
|
node: Optional[URIRef] = None,
|
1143
1138
|
owl_types: Set[OWL_TYPE] = None,
|
@@ -1254,7 +1249,7 @@ class OwlSchemaGenerator(Generator):
|
|
1254
1249
|
if pv.meaning:
|
1255
1250
|
return URIRef(self.schemaview.expand_curie(pv.meaning))
|
1256
1251
|
else:
|
1257
|
-
return URIRef(enum_uri +
|
1252
|
+
return URIRef(enum_uri + self.enum_iri_separator + pv.text.replace(" ", "+"))
|
1258
1253
|
|
1259
1254
|
def slot_owl_type(self, slot: SlotDefinition) -> URIRef:
|
1260
1255
|
sv = self.schemaview
|
@@ -1353,6 +1348,13 @@ class OwlSchemaGenerator(Generator):
|
|
1353
1348
|
show_default=True,
|
1354
1349
|
help="Default OWL type for permissible values",
|
1355
1350
|
)
|
1351
|
+
@click.option(
|
1352
|
+
"--enum-iri-separator",
|
1353
|
+
default="#",
|
1354
|
+
is_flag=False,
|
1355
|
+
show_default=True,
|
1356
|
+
help="IRI separator for enums.",
|
1357
|
+
)
|
1356
1358
|
@click.version_option(__version__, "-V", "--version")
|
1357
1359
|
def cli(yamlfile, metadata_profile: str, **kwargs):
|
1358
1360
|
"""Generate an OWL representation of a LinkML model
|
linkml/generators/plantumlgen.py
CHANGED
@@ -49,14 +49,13 @@ class PlantumlGenerator(Generator):
|
|
49
49
|
classes: Set[ClassDefinitionName] = None
|
50
50
|
directory: Optional[str] = None
|
51
51
|
kroki_server: Optional[str] = "https://kroki.io"
|
52
|
-
load_image: bool = True
|
53
52
|
tooltips_flag: bool = False
|
53
|
+
dry_run: bool = False
|
54
54
|
|
55
55
|
def visit_schema(
|
56
56
|
self,
|
57
57
|
classes: Set[ClassDefinitionName] = None,
|
58
58
|
directory: Optional[str] = None,
|
59
|
-
load_image: bool = True,
|
60
59
|
**_,
|
61
60
|
) -> Optional[str]:
|
62
61
|
if directory:
|
@@ -96,20 +95,21 @@ class PlantumlGenerator(Generator):
|
|
96
95
|
b64_diagram = base64.urlsafe_b64encode(zlib.compress(plantuml_code.encode(), 9))
|
97
96
|
|
98
97
|
plantuml_url = self.kroki_server + "/plantuml/svg/" + b64_diagram.decode()
|
98
|
+
if self.dry_run:
|
99
|
+
return plantuml_url
|
99
100
|
if directory:
|
100
101
|
file_suffix = ".svg" if self.format == "puml" or self.format == "puml" else "." + self.format
|
101
102
|
self.output_file_name = os.path.join(
|
102
103
|
directory,
|
103
104
|
camelcase(sorted(classes)[0] if classes else self.schema.name) + file_suffix,
|
104
105
|
)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
self.logger.error(f"{resp.reason} accessing {plantuml_url}")
|
106
|
+
resp = requests.get(plantuml_url, stream=True, timeout=REQUESTS_TIMEOUT)
|
107
|
+
if resp.ok:
|
108
|
+
with open(self.output_file_name, "wb") as f:
|
109
|
+
for chunk in resp.iter_content(chunk_size=2048):
|
110
|
+
f.write(chunk)
|
111
|
+
else:
|
112
|
+
self.logger.error(f"{resp.reason} accessing {plantuml_url}")
|
113
113
|
else:
|
114
114
|
out = (
|
115
115
|
"@startuml\n"
|
@@ -352,6 +352,13 @@ class PlantumlGenerator(Generator):
|
|
352
352
|
help="URL of the Kroki server to use for diagram drawing",
|
353
353
|
default="https://kroki.io",
|
354
354
|
)
|
355
|
+
@click.option(
|
356
|
+
"--dry-run",
|
357
|
+
is_flag=True,
|
358
|
+
default=False,
|
359
|
+
show_default=True,
|
360
|
+
help="Print out Kroki URL calls instead of sending the real requests",
|
361
|
+
)
|
355
362
|
@click.version_option(__version__, "-V", "--version")
|
356
363
|
def cli(yamlfile, **args):
|
357
364
|
"""Generate a UML representation of a LinkML model"""
|
@@ -1,7 +1,15 @@
|
|
1
1
|
import sys
|
2
2
|
from abc import ABC, abstractmethod
|
3
3
|
from enum import Enum
|
4
|
-
from typing import
|
4
|
+
from typing import (
|
5
|
+
ClassVar,
|
6
|
+
Iterable,
|
7
|
+
List,
|
8
|
+
Optional,
|
9
|
+
Type,
|
10
|
+
TypeVar,
|
11
|
+
Union,
|
12
|
+
)
|
5
13
|
|
6
14
|
from linkml_runtime.linkml_model import Element
|
7
15
|
from linkml_runtime.linkml_model.meta import ArrayExpression, DimensionExpression
|
@@ -9,20 +17,15 @@ from pydantic import VERSION as PYDANTIC_VERSION
|
|
9
17
|
|
10
18
|
from linkml.utils.deprecation import deprecation_warning
|
11
19
|
|
12
|
-
if int(PYDANTIC_VERSION[0])
|
13
|
-
from pydantic_core import core_schema
|
14
|
-
else:
|
20
|
+
if int(PYDANTIC_VERSION[0]) < 2:
|
15
21
|
# Support for having pydantic 1 installed in the same environment will be dropped in 1.9.0
|
16
22
|
deprecation_warning("pydantic-v1")
|
17
23
|
|
18
|
-
if
|
19
|
-
from
|
20
|
-
from pydantic_core import CoreSchema
|
21
|
-
|
22
|
-
if sys.version_info.minor <= 8:
|
23
|
-
from typing_extensions import Annotated
|
24
|
+
if sys.version_info.minor < 12:
|
25
|
+
from typing_extensions import TypeAliasType
|
24
26
|
else:
|
25
|
-
from typing import
|
27
|
+
from typing import TypeAliasType
|
28
|
+
|
26
29
|
|
27
30
|
from linkml.generators.pydanticgen.build import RangeResult
|
28
31
|
from linkml.generators.pydanticgen.template import ConditionalImport, Import, Imports, ObjectImport
|
@@ -37,75 +40,32 @@ class ArrayRepresentation(Enum):
|
|
37
40
|
_BOUNDED_ARRAY_FIELDS = ("exact_number_dimensions", "minimum_number_dimensions", "maximum_number_dimensions")
|
38
41
|
|
39
42
|
_T = TypeVar("_T")
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
class AnyShapeArrayType(Generic[_T]):
|
44
|
-
@classmethod
|
45
|
-
def __get_pydantic_core_schema__(cls, source_type: Any, handler: "GetCoreSchemaHandler") -> "CoreSchema":
|
46
|
-
# double-nested parameterized types here
|
47
|
-
# source_type: List[Union[T,List[...]]]
|
48
|
-
item_type = (Any,) if get_args(get_args(source_type)[0])[0] is _T else get_args(get_args(source_type)[0])[:-1]
|
49
|
-
|
50
|
-
if len(item_type) == 1:
|
51
|
-
item_schema = handler.generate_schema(item_type[0])
|
52
|
-
else:
|
53
|
-
item_schema = core_schema.union_schema([handler.generate_schema(i) for i in item_type])
|
54
|
-
|
55
|
-
if all([getattr(i, "__module__", "") == "builtins" and i is not Any for i in item_type]):
|
56
|
-
item_schema["strict"] = True
|
57
|
-
|
58
|
-
# Before python 3.11, `Any` type was a special object without a __name__
|
59
|
-
item_name = "_".join(["Any" if i is Any else i.__name__ for i in item_type])
|
60
|
-
|
61
|
-
array_ref = f"any-shape-array-{item_name}"
|
62
|
-
|
63
|
-
schema = core_schema.definitions_schema(
|
64
|
-
core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
|
65
|
-
[
|
66
|
-
core_schema.union_schema(
|
67
|
-
[
|
68
|
-
core_schema.list_schema(core_schema.definition_reference_schema(array_ref)),
|
69
|
-
item_schema,
|
70
|
-
],
|
71
|
-
ref=array_ref,
|
72
|
-
)
|
73
|
-
],
|
74
|
-
)
|
75
|
-
|
76
|
-
return schema
|
77
|
-
|
78
|
-
|
79
|
-
AnyShapeArray = Annotated[_RecursiveListType, AnyShapeArrayType]
|
43
|
+
AnyShapeArray = TypeAliasType("AnyShapeArray", Iterable[Union[_T, Iterable["AnyShapeArray[_T]"]]], type_params=(_T,))
|
80
44
|
|
81
45
|
_AnyShapeArrayImports = (
|
82
46
|
Imports()
|
83
47
|
+ Import(
|
84
48
|
module="typing",
|
85
49
|
objects=[
|
86
|
-
ObjectImport(name="Generic"),
|
87
50
|
ObjectImport(name="Iterable"),
|
88
51
|
ObjectImport(name="TypeVar"),
|
89
52
|
ObjectImport(name="Union"),
|
90
|
-
ObjectImport(name="get_args"),
|
91
53
|
],
|
92
54
|
)
|
93
55
|
+ ConditionalImport(
|
94
|
-
condition="sys.version_info.minor
|
56
|
+
condition="sys.version_info.minor >= 12",
|
95
57
|
module="typing",
|
96
|
-
objects=[ObjectImport(name="
|
97
|
-
alternative=Import(module="typing_extensions", objects=[ObjectImport(name="
|
58
|
+
objects=[ObjectImport(name="TypeAliasType")],
|
59
|
+
alternative=Import(module="typing_extensions", objects=[ObjectImport(name="TypeAliasType")]),
|
98
60
|
)
|
99
|
-
+ Import(module="pydantic", objects=[ObjectImport(name="GetCoreSchemaHandler")])
|
100
|
-
+ Import(module="pydantic_core", objects=[ObjectImport(name="CoreSchema"), ObjectImport(name="core_schema")])
|
101
61
|
)
|
102
62
|
|
103
63
|
# annotated types are special and inspect.getsource() can't stringify them
|
104
64
|
_AnyShapeArrayInjects = [
|
105
65
|
'_T = TypeVar("_T")',
|
106
|
-
|
107
|
-
|
108
|
-
|
66
|
+
"""AnyShapeArray = TypeAliasType(
|
67
|
+
"AnyShapeArray", Iterable[Union[_T, Iterable["AnyShapeArray[_T]"]]], type_params=(_T,)
|
68
|
+
)""",
|
109
69
|
]
|
110
70
|
|
111
71
|
_ConListImports = Imports() + Import(module="pydantic", objects=[ObjectImport(name="conlist")])
|
@@ -62,7 +62,8 @@ class ShaclIfAbsentProcessor(IfAbsentProcessor):
|
|
62
62
|
return Literal(f"{year}-{month}-{day}T{hour}:{minutes}:{seconds}", datatype=ShaclDataType.DATETIME.uri_ref)
|
63
63
|
|
64
64
|
def map_uri_or_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
65
|
-
|
65
|
+
uri = URIRef(self.schema_view.expand_curie(default_value))
|
66
|
+
return Literal(uri, datatype=ShaclDataType.URI.uri_ref)
|
66
67
|
|
67
68
|
def map_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
|
68
69
|
return Literal(default_value, datatype=ShaclDataType.CURIE.uri_ref)
|
linkml/generators/shaclgen.py
CHANGED
@@ -116,10 +116,20 @@ class ShaclGenerator(Generator):
|
|
116
116
|
order += 1
|
117
117
|
prop_pv_literal(SH.name, s.title)
|
118
118
|
prop_pv_literal(SH.description, s.description)
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
# minCount
|
120
|
+
if s.minimum_cardinality:
|
121
|
+
prop_pv_literal(SH.minCount, s.minimum_cardinality)
|
122
|
+
elif s.exact_cardinality:
|
123
|
+
prop_pv_literal(SH.minCount, s.exact_cardinality)
|
124
|
+
elif s.required:
|
122
125
|
prop_pv_literal(SH.minCount, 1)
|
126
|
+
# maxCount
|
127
|
+
if s.maximum_cardinality:
|
128
|
+
prop_pv_literal(SH.maxCount, s.maximum_cardinality)
|
129
|
+
elif s.exact_cardinality:
|
130
|
+
prop_pv_literal(SH.maxCount, s.exact_cardinality)
|
131
|
+
elif not s.multivalued:
|
132
|
+
prop_pv_literal(SH.maxCount, 1)
|
123
133
|
prop_pv_literal(SH.minInclusive, s.minimum_value)
|
124
134
|
prop_pv_literal(SH.maxInclusive, s.maximum_value)
|
125
135
|
|
@@ -198,8 +208,6 @@ class ShaclGenerator(Generator):
|
|
198
208
|
add_simple_data_type(prop_pv, r)
|
199
209
|
if s.pattern:
|
200
210
|
prop_pv(SH.pattern, Literal(s.pattern))
|
201
|
-
if s.annotations and self.include_annotations:
|
202
|
-
self._add_annotations(prop_pv, s)
|
203
211
|
if s.equals_string:
|
204
212
|
# Map equal_string and equal_string_in to sh:in
|
205
213
|
self._and_equals_string(g, prop_pv, [s.equals_string])
|
@@ -207,6 +215,9 @@ class ShaclGenerator(Generator):
|
|
207
215
|
# Map equal_string and equal_string_in to sh:in
|
208
216
|
self._and_equals_string(g, prop_pv, s.equals_string_in)
|
209
217
|
|
218
|
+
if s.annotations and self.include_annotations:
|
219
|
+
self._add_annotations(prop_pv, s)
|
220
|
+
|
210
221
|
default_value = ifabsent_processor.process_slot(s, c)
|
211
222
|
if default_value:
|
212
223
|
prop_pv(SH.defaultValue, default_value)
|
linkml/generators/shexgen.py
CHANGED
linkml/generators/summarygen.py
CHANGED
@@ -280,7 +280,9 @@ def cli(yamlfile, gen_type_utils=False, include_induced_slots=False, output=None
|
|
280
280
|
gen = TypescriptGenerator(
|
281
281
|
yamlfile, gen_type_utils=gen_type_utils, include_induced_slots=include_induced_slots, **args
|
282
282
|
)
|
283
|
-
gen.serialize(output=output)
|
283
|
+
serialized = gen.serialize(output=output)
|
284
|
+
if output is None:
|
285
|
+
print(serialized)
|
284
286
|
|
285
287
|
|
286
288
|
if __name__ == "__main__":
|
linkml/generators/yamlgen.py
CHANGED
linkml/linter/rules.py
CHANGED
@@ -134,7 +134,9 @@ class TreeRootClassRule(LinterRule):
|
|
134
134
|
if self.config.validate_existing_class_name:
|
135
135
|
for tree_root in tree_roots:
|
136
136
|
if str(tree_root.name) != self.config.root_class_name:
|
137
|
-
yield LinterProblem(message=f"Tree root class has name '{tree_root.name}'")
|
137
|
+
yield LinterProblem(message=f"Tree root class has an invalid name '{tree_root.name}'")
|
138
|
+
if len(tree_roots) > 1:
|
139
|
+
yield LinterProblem("Schema has more than one class with `tree_root: true`")
|
138
140
|
else:
|
139
141
|
if fix:
|
140
142
|
container = ClassDefinition(self.config.root_class_name, tree_root=True)
|
@@ -409,11 +409,13 @@ class LogicalModelTransformer(ModelTransformer):
|
|
409
409
|
target_class_name: ClassDefinitionName,
|
410
410
|
ancestors: List[ClassDefinitionName],
|
411
411
|
):
|
412
|
-
anc_classes = [self.schemaview.get_class(
|
412
|
+
anc_classes = [self.schemaview.get_class(ancestor) for ancestor in ancestors]
|
413
413
|
attributes: Dict[SlotDefinitionName, SlotDefinition] = {}
|
414
|
-
for
|
415
|
-
top_level_slots = [(s, target_schema.slots[s]) for s in
|
416
|
-
for slot_name, slot_expr in
|
414
|
+
for ancestor_class in anc_classes:
|
415
|
+
top_level_slots = [(s, target_schema.slots[s]) for s in ancestor_class.slots]
|
416
|
+
for slot_name, slot_expr in (
|
417
|
+
list(ancestor_class.attributes.items()) + list(ancestor_class.slot_usage.items()) + top_level_slots
|
418
|
+
):
|
417
419
|
if slot_name not in attributes:
|
418
420
|
attributes[slot_name] = SlotDefinition(slot_name)
|
419
421
|
sx = attributes[slot_name]
|
@@ -644,7 +646,7 @@ class LogicalModelTransformer(ModelTransformer):
|
|
644
646
|
if slot_expression.all_of:
|
645
647
|
exprs.append(logictools.And(*[self._as_logical_expression(subx) for subx in slot_expression.all_of]))
|
646
648
|
if slot_expression.exactly_one_of:
|
647
|
-
# TODO:
|
649
|
+
# TODO: disjointedness
|
648
650
|
exprs.append(logictools.Or(*[self._as_logical_expression(subx) for subx in slot_expression.exactly_one_of]))
|
649
651
|
if slot_expression.none_of:
|
650
652
|
exprs.append(
|
linkml/utils/converter.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
|
+
import pathlib
|
3
4
|
import sys
|
4
5
|
|
5
6
|
import click
|
7
|
+
import yaml
|
6
8
|
from linkml_runtime.linkml_model import Prefix
|
7
9
|
from linkml_runtime.utils import inference_utils
|
8
10
|
from linkml_runtime.utils.compile_python import compile_python
|
@@ -55,6 +57,7 @@ logger = logging.getLogger(__name__)
|
|
55
57
|
@click.option("--index-slot", "-S", help="top level slot. Required for CSV dumping/loading")
|
56
58
|
@click.option("--schema", "-s", help="Path to schema specified as LinkML yaml")
|
57
59
|
@click.option("--prefix", "-P", multiple=True, help="Prefixmap base=URI pairs")
|
60
|
+
@click.option("--prefix-file", help="Path to yaml file containing base=URI pairs")
|
58
61
|
@click.option(
|
59
62
|
"--validate/--no-validate",
|
60
63
|
default=True,
|
@@ -79,6 +82,7 @@ def cli(
|
|
79
82
|
input_format=None,
|
80
83
|
output_format=None,
|
81
84
|
prefix=None,
|
85
|
+
prefix_file=None,
|
82
86
|
target_class_from_path=None,
|
83
87
|
schema=None,
|
84
88
|
validate=None,
|
@@ -99,6 +103,8 @@ def cli(
|
|
99
103
|
|
100
104
|
For more information, see https://linkml.io/linkml/data/index.html
|
101
105
|
"""
|
106
|
+
if prefix and prefix_file is not None:
|
107
|
+
raise Exception("Either set prefix OR prefix_file, not both.")
|
102
108
|
if prefix is None:
|
103
109
|
prefix = []
|
104
110
|
if module is None:
|
@@ -113,6 +119,17 @@ def cli(
|
|
113
119
|
for p in prefix:
|
114
120
|
base, uri = p.split("=")
|
115
121
|
prefix_map[base] = uri
|
122
|
+
if prefix_file is not None:
|
123
|
+
prefix_path = pathlib.Path(prefix_file).resolve()
|
124
|
+
if not prefix_path.exists():
|
125
|
+
raise Exception(f"Path {prefix_file} to prefix map does not exists.")
|
126
|
+
with open(prefix_path, "r") as prefix_stream:
|
127
|
+
raw_prefix_map = yaml.safe_load(prefix_stream)
|
128
|
+
prefix_file_map = raw_prefix_map.get("prefixes", None)
|
129
|
+
if prefix_file_map is None:
|
130
|
+
raise Exception("Provided prefix file does not contain the prefixes key.")
|
131
|
+
prefix_map = prefix_file_map
|
132
|
+
|
116
133
|
if schema is not None:
|
117
134
|
sv = SchemaView(schema)
|
118
135
|
if prefix_map:
|
linkml/utils/deprecation.py
CHANGED
@@ -222,6 +222,16 @@ DEPRECATIONS = (
|
|
222
222
|
recommendation="Update dependent packages to use pydantic>=2",
|
223
223
|
issue=1925,
|
224
224
|
),
|
225
|
+
Deprecation(
|
226
|
+
name="validators",
|
227
|
+
deprecated_in=SemVer.from_str("1.8.6"),
|
228
|
+
removed_in=SemVer.from_str("1.9.0"),
|
229
|
+
message=(
|
230
|
+
"linkml.validators and linkml.utils.validation are the older versions "
|
231
|
+
"of linkml.validator and have unmaintained, duplicated functionality"
|
232
|
+
),
|
233
|
+
recommendation="Update to use linkml.validator",
|
234
|
+
),
|
225
235
|
) # type: tuple[Deprecation, ...]
|
226
236
|
|
227
237
|
EMITTED = set() # type: set[str]
|
linkml/utils/generator.py
CHANGED
@@ -27,6 +27,7 @@ from typing import Callable, ClassVar, Dict, List, Mapping, Optional, Set, TextI
|
|
27
27
|
|
28
28
|
import click
|
29
29
|
from click import Argument, Command, Option
|
30
|
+
from jsonasobj2 import JsonObj
|
30
31
|
from linkml_runtime import SchemaView
|
31
32
|
from linkml_runtime.linkml_model.meta import (
|
32
33
|
ClassDefinition,
|
@@ -265,8 +266,16 @@ class Generator(metaclass=abc.ABCMeta):
|
|
265
266
|
def _init_namespaces(self):
|
266
267
|
if self.namespaces is None:
|
267
268
|
self.namespaces = Namespaces()
|
268
|
-
|
269
|
-
self.
|
269
|
+
if isinstance(self.schema.prefixes, dict):
|
270
|
+
for key, value in self.schema.prefixes.items():
|
271
|
+
self.namespaces[key] = value
|
272
|
+
elif isinstance(self.schema.prefixes, JsonObj):
|
273
|
+
prefixes = vars(self.schema.prefixes)
|
274
|
+
for key, value in prefixes.items():
|
275
|
+
self.namespaces[key] = value
|
276
|
+
else:
|
277
|
+
for prefix in self.schema.prefixes.values():
|
278
|
+
self.namespaces[prefix.prefix_prefix] = prefix.prefix_reference
|
270
279
|
|
271
280
|
def serialize(self, **kwargs) -> str:
|
272
281
|
"""
|