linkml 1.5.5__py3-none-any.whl → 1.5.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- linkml/__init__.py +2 -6
- linkml/_version.py +1 -1
- linkml/generators/PythonGenNotes.md +4 -4
- linkml/generators/__init__.py +26 -5
- linkml/generators/common/type_designators.py +27 -22
- linkml/generators/csvgen.py +4 -10
- linkml/generators/docgen/class.md.jinja2 +7 -0
- linkml/generators/docgen/class_diagram.md.jinja2 +0 -6
- linkml/generators/docgen/subset.md.jinja2 +54 -13
- linkml/generators/docgen.py +94 -92
- linkml/generators/dotgen.py +5 -9
- linkml/generators/erdiagramgen.py +58 -53
- linkml/generators/excelgen.py +10 -16
- linkml/generators/golanggen.py +11 -21
- linkml/generators/golrgen.py +4 -13
- linkml/generators/graphqlgen.py +3 -11
- linkml/generators/javagen.py +8 -15
- linkml/generators/jsonldcontextgen.py +7 -36
- linkml/generators/jsonldgen.py +14 -12
- linkml/generators/jsonschemagen.py +183 -136
- linkml/generators/linkmlgen.py +1 -1
- linkml/generators/markdowngen.py +40 -89
- linkml/generators/namespacegen.py +1 -2
- linkml/generators/oocodegen.py +22 -25
- linkml/generators/owlgen.py +48 -49
- linkml/generators/prefixmapgen.py +6 -14
- linkml/generators/projectgen.py +7 -14
- linkml/generators/protogen.py +3 -5
- linkml/generators/pydanticgen.py +85 -73
- linkml/generators/pythongen.py +89 -157
- linkml/generators/rdfgen.py +5 -11
- linkml/generators/shaclgen.py +32 -18
- linkml/generators/shexgen.py +19 -24
- linkml/generators/sparqlgen.py +5 -13
- linkml/generators/sqlalchemy/__init__.py +3 -2
- linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +7 -7
- linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +3 -3
- linkml/generators/sqlalchemygen.py +29 -27
- linkml/generators/sqlddlgen.py +34 -26
- linkml/generators/sqltablegen.py +21 -21
- linkml/generators/sssomgen.py +11 -13
- linkml/generators/summarygen.py +2 -4
- linkml/generators/terminusdbgen.py +7 -19
- linkml/generators/typescriptgen.py +10 -18
- linkml/generators/yamlgen.py +0 -2
- linkml/generators/yumlgen.py +23 -71
- linkml/linter/cli.py +4 -11
- linkml/linter/config/datamodel/config.py +17 -47
- linkml/linter/linter.py +2 -4
- linkml/linter/rules.py +34 -48
- linkml/reporting/__init__.py +2 -0
- linkml/reporting/model.py +9 -24
- linkml/transformers/relmodel_transformer.py +20 -33
- linkml/transformers/schema_renamer.py +14 -10
- linkml/utils/converter.py +15 -15
- linkml/utils/datautils.py +9 -24
- linkml/utils/datavalidator.py +2 -2
- linkml/utils/execute_tutorial.py +10 -12
- linkml/utils/generator.py +74 -92
- linkml/utils/helpers.py +4 -2
- linkml/utils/ifabsent_functions.py +23 -15
- linkml/utils/mergeutils.py +19 -35
- linkml/utils/rawloader.py +2 -6
- linkml/utils/schema_builder.py +31 -19
- linkml/utils/schema_fixer.py +28 -18
- linkml/utils/schemaloader.py +44 -89
- linkml/utils/schemasynopsis.py +50 -73
- linkml/utils/sqlutils.py +40 -30
- linkml/utils/typereferences.py +9 -6
- linkml/utils/validation.py +4 -5
- linkml/validators/__init__.py +2 -0
- linkml/validators/jsonschemavalidator.py +104 -53
- linkml/validators/sparqlvalidator.py +5 -15
- linkml/workspaces/datamodel/workspaces.py +13 -30
- linkml/workspaces/example_runner.py +75 -68
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/METADATA +2 -2
- linkml-1.5.7.dist-info/RECORD +109 -0
- linkml-1.5.5.dist-info/RECORD +0 -109
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/LICENSE +0 -0
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/WHEEL +0 -0
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/entry_points.txt +0 -0
linkml/linter/rules.py
CHANGED
@@ -3,17 +3,24 @@ from abc import ABC, abstractmethod
|
|
3
3
|
from functools import lru_cache
|
4
4
|
from typing import Callable, Iterable, List
|
5
5
|
|
6
|
-
from linkml_runtime.linkml_model import (
|
7
|
-
|
6
|
+
from linkml_runtime.linkml_model import (
|
7
|
+
ClassDefinition,
|
8
|
+
ClassDefinitionName,
|
9
|
+
Element,
|
10
|
+
SlotDefinition,
|
11
|
+
)
|
8
12
|
from linkml_runtime.utils.schemaview import SchemaView
|
9
13
|
from prefixmaps.io.parser import load_multi_context
|
10
14
|
|
11
15
|
from linkml import LOCAL_METAMODEL_YAML_FILE
|
12
16
|
|
13
|
-
from .config.datamodel.config import (
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
from .config.datamodel.config import (
|
18
|
+
CanonicalPrefixesConfig,
|
19
|
+
RecommendedRuleConfig,
|
20
|
+
RuleConfig,
|
21
|
+
StandardNamingConfig,
|
22
|
+
TreeRootClassRuleConfig,
|
23
|
+
)
|
17
24
|
from .linter import LinterProblem
|
18
25
|
|
19
26
|
|
@@ -53,18 +60,14 @@ class LinterRule(ABC):
|
|
53
60
|
class NoEmptyTitleRule(LinterRule):
|
54
61
|
id = "no_empty_title"
|
55
62
|
|
56
|
-
def check(
|
57
|
-
self, schema_view: SchemaView, fix: bool = False
|
58
|
-
) -> Iterable[LinterProblem]:
|
63
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
59
64
|
for e in schema_view.all_elements(imports=False).values():
|
60
65
|
if fix and e.title is None:
|
61
66
|
title = e.name.replace("_", " ")
|
62
67
|
title = self.uncamel(title).lower()
|
63
68
|
e.title = title
|
64
69
|
if e.title is None:
|
65
|
-
problem = LinterProblem(
|
66
|
-
message=f"{self.format_element(e)} has no title"
|
67
|
-
)
|
70
|
+
problem = LinterProblem(message=f"{self.format_element(e)} has no title")
|
68
71
|
yield problem
|
69
72
|
|
70
73
|
|
@@ -77,17 +80,13 @@ class NoXsdIntTypeRule(LinterRule):
|
|
77
80
|
if fix:
|
78
81
|
type_definition.uri = "xsd:integer"
|
79
82
|
else:
|
80
|
-
yield LinterProblem(
|
81
|
-
f"{self.format_element(type_definition)} has uri xsd:int"
|
82
|
-
)
|
83
|
+
yield LinterProblem(f"{self.format_element(type_definition)} has uri xsd:int")
|
83
84
|
|
84
85
|
|
85
86
|
class PermissibleValuesFormatRule(LinterRule):
|
86
87
|
id = "permissible_values_format"
|
87
88
|
|
88
|
-
def check(
|
89
|
-
self, schema_view: SchemaView, fix: bool = False
|
90
|
-
) -> Iterable[LinterProblem]:
|
89
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
91
90
|
pattern = self.PATTERNS.get(self.config.format, re.compile(self.config.format))
|
92
91
|
for enum_def in schema_view.all_enums(imports=False).values():
|
93
92
|
for value in enum_def.permissible_values.keys():
|
@@ -117,9 +116,7 @@ class RecommendedRule(LinterRule):
|
|
117
116
|
|
118
117
|
def check(self, schema_view: SchemaView, fix: bool = False):
|
119
118
|
recommended_meta_slots = _get_recommended_metamodel_slots()
|
120
|
-
for element_name, element_definition in schema_view.all_elements(
|
121
|
-
imports=False
|
122
|
-
).items():
|
119
|
+
for element_name, element_definition in schema_view.all_elements(imports=False).items():
|
123
120
|
if self.config.include and element_name not in self.config.include:
|
124
121
|
continue
|
125
122
|
if element_name in self.config.exclude:
|
@@ -138,19 +135,13 @@ class TreeRootClassRule(LinterRule):
|
|
138
135
|
def __init__(self, config: TreeRootClassRuleConfig) -> None:
|
139
136
|
super().__init__(config)
|
140
137
|
|
141
|
-
def check(
|
142
|
-
|
143
|
-
) -> Iterable[LinterProblem]:
|
144
|
-
tree_roots = [
|
145
|
-
c for c in schema_view.all_classes(imports=False).values() if c.tree_root
|
146
|
-
]
|
138
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
139
|
+
tree_roots = [c for c in schema_view.all_classes(imports=False).values() if c.tree_root]
|
147
140
|
if len(tree_roots) > 0:
|
148
141
|
if self.config.validate_existing_class_name:
|
149
142
|
for tree_root in tree_roots:
|
150
143
|
if str(tree_root.name) != self.config.root_class_name:
|
151
|
-
yield LinterProblem(
|
152
|
-
message=f"Tree root class has name '{tree_root.name}'"
|
153
|
-
)
|
144
|
+
yield LinterProblem(message=f"Tree root class has name '{tree_root.name}'")
|
154
145
|
else:
|
155
146
|
if fix:
|
156
147
|
container = ClassDefinition(self.config.root_class_name, tree_root=True)
|
@@ -190,9 +181,7 @@ class TreeRootClassRule(LinterRule):
|
|
190
181
|
]
|
191
182
|
if must_have_identifier:
|
192
183
|
top_level_classes = [
|
193
|
-
c
|
194
|
-
for c in top_level_classes
|
195
|
-
if schema_view.get_identifier_slot(c.name) is not None
|
184
|
+
c for c in top_level_classes if schema_view.get_identifier_slot(c.name) is not None
|
196
185
|
]
|
197
186
|
index_slots = []
|
198
187
|
for c in top_level_classes:
|
@@ -220,12 +209,8 @@ class TreeRootClassRule(LinterRule):
|
|
220
209
|
class NoInvalidSlotUsageRule(LinterRule):
|
221
210
|
id = "no_invalid_slot_usage"
|
222
211
|
|
223
|
-
def check(
|
224
|
-
|
225
|
-
) -> Iterable[LinterProblem]:
|
226
|
-
for class_name, class_definition in schema_view.all_classes(
|
227
|
-
imports=False
|
228
|
-
).items():
|
212
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
213
|
+
for class_name, class_definition in schema_view.all_classes(imports=False).items():
|
229
214
|
slot_usage = class_definition.slot_usage
|
230
215
|
if not slot_usage:
|
231
216
|
continue
|
@@ -243,9 +228,7 @@ class StandardNamingRule(LinterRule):
|
|
243
228
|
def __init__(self, config: StandardNamingConfig) -> None:
|
244
229
|
self.config = config
|
245
230
|
|
246
|
-
def check(
|
247
|
-
self, schema_view: SchemaView, fix: bool = False
|
248
|
-
) -> Iterable[LinterProblem]:
|
231
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
249
232
|
class_pattern = self.PATTERNS["uppercamel"]
|
250
233
|
slot_pattern = self.PATTERNS["snake"]
|
251
234
|
enum_pattern = self.PATTERNS["uppercamel"]
|
@@ -270,7 +253,8 @@ class StandardNamingRule(LinterRule):
|
|
270
253
|
for permissible_value_name in enum_definition.permissible_values.keys():
|
271
254
|
if permissible_value_pattern.fullmatch(permissible_value_name) is None:
|
272
255
|
yield LinterProblem(
|
273
|
-
f"Permissible value of {self.format_element(enum_definition)}
|
256
|
+
f"Permissible value of {self.format_element(enum_definition)} "
|
257
|
+
f"has name '{permissible_value_name}'"
|
274
258
|
)
|
275
259
|
|
276
260
|
|
@@ -280,9 +264,7 @@ class CanonicalPrefixesRule(LinterRule):
|
|
280
264
|
def __init__(self, config: CanonicalPrefixesConfig) -> None:
|
281
265
|
self.config = config
|
282
266
|
|
283
|
-
def check(
|
284
|
-
self, schema_view: SchemaView, fix: bool = False
|
285
|
-
) -> Iterable[LinterProblem]:
|
267
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
286
268
|
context = load_multi_context(self.config.prefixmaps_contexts)
|
287
269
|
prefix_to_namespace = context.as_dict()
|
288
270
|
namespace_to_prefix = context.as_inverted_dict()
|
@@ -290,10 +272,14 @@ class CanonicalPrefixesRule(LinterRule):
|
|
290
272
|
if prefix.prefix_prefix in prefix_to_namespace:
|
291
273
|
if prefix.prefix_reference != prefix_to_namespace[prefix.prefix_prefix]:
|
292
274
|
yield LinterProblem(
|
293
|
-
f"Schema maps prefix '{prefix.prefix_prefix}' to namespace
|
275
|
+
f"Schema maps prefix '{prefix.prefix_prefix}' to namespace "
|
276
|
+
f"'{prefix.prefix_reference}' instead of namespace "
|
277
|
+
f"'{prefix_to_namespace[prefix.prefix_prefix]}'"
|
294
278
|
)
|
295
279
|
if prefix.prefix_reference in namespace_to_prefix:
|
296
280
|
if prefix.prefix_prefix != namespace_to_prefix[prefix.prefix_reference]:
|
297
281
|
yield LinterProblem(
|
298
|
-
f"Schema maps prefix '{prefix.prefix_prefix}' to namespace
|
282
|
+
f"Schema maps prefix '{prefix.prefix_prefix}' to namespace "
|
283
|
+
f"'{prefix.prefix_reference}' instead of using prefix "
|
284
|
+
f"'{namespace_to_prefix[prefix.prefix_reference]}'"
|
299
285
|
)
|
linkml/reporting/__init__.py
CHANGED
linkml/reporting/model.py
CHANGED
@@ -7,27 +7,18 @@
|
|
7
7
|
# license: https://creativecommons.org/publicdomain/zero/1.0/
|
8
8
|
|
9
9
|
import dataclasses
|
10
|
-
import re
|
11
|
-
import sys
|
12
10
|
from dataclasses import dataclass
|
13
11
|
from typing import Any, ClassVar, Dict, List, Optional, Union
|
14
12
|
|
15
|
-
from jsonasobj2 import
|
16
|
-
from linkml_runtime.linkml_model.meta import
|
17
|
-
PvFormulaOptions)
|
18
|
-
from linkml_runtime.linkml_model.types import (Nodeidentifier, String,
|
19
|
-
Uriorcurie)
|
13
|
+
from jsonasobj2 import as_dict
|
14
|
+
from linkml_runtime.linkml_model.meta import EnumDefinition, PermissibleValue
|
20
15
|
from linkml_runtime.utils.curienamespace import CurieNamespace
|
21
|
-
from linkml_runtime.utils.dataclass_extensions_376 import
|
22
|
-
dataclasses_init_fn_with_kwargs
|
16
|
+
from linkml_runtime.utils.dataclass_extensions_376 import dataclasses_init_fn_with_kwargs
|
23
17
|
from linkml_runtime.utils.enumerations import EnumDefinitionImpl
|
24
|
-
from linkml_runtime.utils.
|
25
|
-
from linkml_runtime.utils.metamodelcore import (NodeIdentifier, URIorCURIE,
|
26
|
-
bnode, empty_dict, empty_list)
|
18
|
+
from linkml_runtime.utils.metamodelcore import NodeIdentifier, URIorCURIE, empty_list
|
27
19
|
from linkml_runtime.utils.slot import Slot
|
28
|
-
from linkml_runtime.utils.yamlutils import
|
29
|
-
|
30
|
-
from rdflib import Namespace, URIRef
|
20
|
+
from linkml_runtime.utils.yamlutils import YAMLRoot
|
21
|
+
from rdflib import URIRef
|
31
22
|
|
32
23
|
metamodel_version = "1.7.0"
|
33
24
|
|
@@ -73,8 +64,7 @@ class Report(YAMLRoot):
|
|
73
64
|
if not isinstance(self.results, list):
|
74
65
|
self.results = [self.results] if self.results is not None else []
|
75
66
|
self.results = [
|
76
|
-
v if isinstance(v, CheckResult) else CheckResult(**as_dict(v))
|
77
|
-
for v in self.results
|
67
|
+
v if isinstance(v, CheckResult) else CheckResult(**as_dict(v)) for v in self.results
|
78
68
|
]
|
79
69
|
|
80
70
|
super().__post_init__(**kwargs)
|
@@ -109,14 +99,10 @@ class CheckResult(YAMLRoot):
|
|
109
99
|
if self.subject is not None and not isinstance(self.subject, NodeIdentifier):
|
110
100
|
self.subject = NodeIdentifier(self.subject)
|
111
101
|
|
112
|
-
if self.instantiates is not None and not isinstance(
|
113
|
-
self.instantiates, NodeIdentifier
|
114
|
-
):
|
102
|
+
if self.instantiates is not None and not isinstance(self.instantiates, NodeIdentifier):
|
115
103
|
self.instantiates = NodeIdentifier(self.instantiates)
|
116
104
|
|
117
|
-
if self.predicate is not None and not isinstance(
|
118
|
-
self.predicate, NodeIdentifier
|
119
|
-
):
|
105
|
+
if self.predicate is not None and not isinstance(self.predicate, NodeIdentifier):
|
120
106
|
self.predicate = NodeIdentifier(self.predicate)
|
121
107
|
|
122
108
|
if self.object is not None and not isinstance(self.object, NodeIdentifier):
|
@@ -184,7 +170,6 @@ class ProblemSlotMissing(Problem):
|
|
184
170
|
|
185
171
|
# Enumerations
|
186
172
|
class SeverityOptions(EnumDefinitionImpl):
|
187
|
-
|
188
173
|
FATAL = PermissibleValue(text="FATAL")
|
189
174
|
ERROR = PermissibleValue(text="ERROR")
|
190
175
|
WARNING = PermissibleValue(text="WARNING")
|
@@ -1,16 +1,19 @@
|
|
1
1
|
import logging
|
2
2
|
from copy import copy
|
3
3
|
from dataclasses import dataclass, field
|
4
|
-
from enum import unique
|
5
4
|
from typing import Dict, List, Optional
|
6
5
|
|
7
|
-
from linkml_runtime.linkml_model import (
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
from linkml_runtime.linkml_model import (
|
7
|
+
Annotation,
|
8
|
+
ClassDefinition,
|
9
|
+
ClassDefinitionName,
|
10
|
+
Definition,
|
11
|
+
Prefix,
|
12
|
+
SchemaDefinition,
|
13
|
+
SlotDefinition,
|
14
|
+
)
|
12
15
|
from linkml_runtime.utils.schemaview import SchemaView, SlotDefinitionName
|
13
|
-
from sqlalchemy import
|
16
|
+
from sqlalchemy import Enum
|
14
17
|
|
15
18
|
|
16
19
|
class RelationalAnnotations(Enum):
|
@@ -99,9 +102,7 @@ class MultivaluedScalar(RelationalMapping):
|
|
99
102
|
mapping_type: str = "MultivaluedScalar"
|
100
103
|
|
101
104
|
|
102
|
-
def add_attribute(
|
103
|
-
attributes: Dict[str, SlotDefinition], tgt_slot: SlotDefinition
|
104
|
-
) -> None:
|
105
|
+
def add_attribute(attributes: Dict[str, SlotDefinition], tgt_slot: SlotDefinition) -> None:
|
105
106
|
attributes[tgt_slot.name] = tgt_slot
|
106
107
|
|
107
108
|
|
@@ -199,6 +200,7 @@ class RelationalModelTransformer:
|
|
199
200
|
tree_root=c.tree_root,
|
200
201
|
abstract=c.abstract,
|
201
202
|
description=c.description,
|
203
|
+
unique_keys=c.unique_keys,
|
202
204
|
)
|
203
205
|
for slot in source_sv.class_induced_slots(cn):
|
204
206
|
tgt_slot = copy(slot)
|
@@ -221,9 +223,7 @@ class RelationalModelTransformer:
|
|
221
223
|
for cn in target_sv.all_classes():
|
222
224
|
pk = self.get_direct_identifier_attribute(target_sv, cn)
|
223
225
|
if self.foreign_key_policy == ForeignKeyPolicy.NO_FOREIGN_KEYS:
|
224
|
-
logging.info(
|
225
|
-
f"Will not inject any PKs, and policy == {self.foreign_key_policy}"
|
226
|
-
)
|
226
|
+
logging.info(f"Will not inject any PKs, and policy == {self.foreign_key_policy}")
|
227
227
|
else:
|
228
228
|
if pk is None:
|
229
229
|
pk = self.add_primary_key(cn, target_sv)
|
@@ -238,7 +238,6 @@ class RelationalModelTransformer:
|
|
238
238
|
for cn, c in target_sv.all_classes().items():
|
239
239
|
if self.foreign_key_policy == ForeignKeyPolicy.NO_FOREIGN_KEYS:
|
240
240
|
continue
|
241
|
-
incoming_links = [link for link in links if link.target_class == cn]
|
242
241
|
pk_slot = self.get_direct_identifier_attribute(target_sv, cn)
|
243
242
|
# if self.is_skip(c) and len(incoming_links) == 0:
|
244
243
|
# logging.info(f'Skipping class: {c.name}')
|
@@ -248,24 +247,18 @@ class RelationalModelTransformer:
|
|
248
247
|
slot = copy(src_slot)
|
249
248
|
slot_range = slot.range
|
250
249
|
slot_range_is_class = slot_range in target_sv.all_classes()
|
251
|
-
links_to_range = [
|
252
|
-
link for link in links if link.target_class == slot_range
|
253
|
-
]
|
254
|
-
is_only_ref_to_range = len(links_to_range) == 1
|
255
250
|
is_shared = slot_range_is_class and (
|
256
251
|
slot.inlined or slot.inlined_as_list or "shared" in slot.annotations
|
257
252
|
)
|
258
253
|
if slot.multivalued:
|
259
254
|
slot.multivalued = False
|
260
255
|
slot_name = slot.name
|
261
|
-
sn_singular =
|
262
|
-
slot.singular_name if slot.singular_name else slot.name
|
263
|
-
)
|
256
|
+
sn_singular = slot.singular_name if slot.singular_name else slot.name
|
264
257
|
if pk_slot is None:
|
265
258
|
pk_slot = self.add_primary_key(c.name, target_sv)
|
266
259
|
backref_slot = SlotDefinition(
|
267
260
|
name=f"{c.name}_{pk_slot.name}",
|
268
|
-
description=
|
261
|
+
description="Autocreated FK slot",
|
269
262
|
range=c.name,
|
270
263
|
slot_uri="rdf:subject",
|
271
264
|
# close_mappings=[pk_slot.slot_uri],
|
@@ -361,13 +354,9 @@ class RelationalModelTransformer:
|
|
361
354
|
if a.range in target.classes:
|
362
355
|
tc = target.classes[a.range]
|
363
356
|
# tc_pk_slot = target_sv.get_identifier_slot(tc.name)
|
364
|
-
tc_pk_slot = self.get_direct_identifier_attribute(
|
365
|
-
target_sv, tc.name
|
366
|
-
)
|
357
|
+
tc_pk_slot = self.get_direct_identifier_attribute(target_sv, tc.name)
|
367
358
|
if tc_pk_slot is None:
|
368
|
-
raise ValueError(
|
369
|
-
f"No PK for attribute {a.name} range {a.range}"
|
370
|
-
)
|
359
|
+
raise ValueError(f"No PK for attribute {a.name} range {a.range}")
|
371
360
|
is_inlined = a.inlined or not source_sv.get_identifier_slot(tc.name)
|
372
361
|
if (
|
373
362
|
fk_policy == ForeignKeyPolicy.INJECT_FK_FOR_NESTED
|
@@ -449,9 +438,7 @@ class RelationalModelTransformer:
|
|
449
438
|
candidate_names = ["id", "uid", "identifier", "pk"]
|
450
439
|
valid_candidate_names = [n for n in candidate_names if n not in c.attributes]
|
451
440
|
if not valid_candidate_names:
|
452
|
-
raise ValueError(
|
453
|
-
f"Cannot add primary key to class {cn}: no valid candidate names"
|
454
|
-
)
|
441
|
+
raise ValueError(f"Cannot add primary key to class {cn}: no valid candidate names")
|
455
442
|
pk = SlotDefinition(name=valid_candidate_names[0], identifier=True, range="integer")
|
456
443
|
add_annotation(pk, "dcterms:conformsTo", "rr:BlankNode")
|
457
444
|
add_annotation(pk, "autoincrement", "true")
|
@@ -461,8 +448,8 @@ class RelationalModelTransformer:
|
|
461
448
|
)
|
462
449
|
# add PK to start of attributes
|
463
450
|
atts = copy(c.attributes)
|
464
|
-
c.attributes.clear()
|
465
|
-
add_attribute(c.attributes, pk)
|
451
|
+
c.attributes.clear() # See https://github.com/linkml/linkml/issues/370
|
452
|
+
add_attribute(c.attributes, pk) # add to start
|
466
453
|
c.attributes.update(atts)
|
467
454
|
sv.set_modified()
|
468
455
|
return pk
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import inspect
|
2
1
|
from copy import deepcopy
|
3
2
|
from dataclasses import dataclass, field
|
4
3
|
from typing import Any, Callable, Dict, Type
|
@@ -6,11 +5,18 @@ from typing import Any, Callable, Dict, Type
|
|
6
5
|
import click
|
7
6
|
from jsonasobj2 import as_dict
|
8
7
|
from linkml_runtime import SchemaView
|
9
|
-
from linkml_runtime.linkml_model import (
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
from linkml_runtime.linkml_model import (
|
9
|
+
ClassDefinition,
|
10
|
+
ClassDefinitionName,
|
11
|
+
ElementName,
|
12
|
+
EnumDefinition,
|
13
|
+
EnumDefinitionName,
|
14
|
+
SchemaDefinition,
|
15
|
+
SlotDefinition,
|
16
|
+
SlotDefinitionName,
|
17
|
+
TypeDefinition,
|
18
|
+
TypeDefinitionName,
|
19
|
+
)
|
14
20
|
from linkml_runtime.utils.formatutils import camelcase, lcamelcase, underscore
|
15
21
|
from linkml_runtime.utils.schema_as_dict import schema_as_yaml_dump
|
16
22
|
from linkml_runtime.utils.yamlutils import YAMLRoot
|
@@ -92,9 +98,7 @@ class SchemaRenamer:
|
|
92
98
|
return new_element
|
93
99
|
else:
|
94
100
|
try:
|
95
|
-
element_vars = {
|
96
|
-
k: v for k, v in vars(element).items() if not k.startswith("_")
|
97
|
-
}
|
101
|
+
element_vars = {k: v for k, v in vars(element).items() if not k.startswith("_")}
|
98
102
|
if len(element_vars) == 0:
|
99
103
|
return element
|
100
104
|
else:
|
@@ -134,7 +138,7 @@ def main(schema, output, class_names, slot_names):
|
|
134
138
|
if slot_names:
|
135
139
|
rename_map[SlotDefinition] = n2f(slot_names)
|
136
140
|
if not rename_map.keys():
|
137
|
-
raise ValueError(
|
141
|
+
raise ValueError("No transformations specified")
|
138
142
|
renamer = SchemaRenamer(rename_function_map=rename_map)
|
139
143
|
rschema = renamer.rename_elements(sv.schema)
|
140
144
|
ystr = schema_as_yaml_dump(rschema)
|
linkml/utils/converter.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
import sys
|
4
|
-
from pathlib import Path
|
5
4
|
from typing import List
|
6
5
|
|
7
6
|
import click
|
@@ -14,9 +13,16 @@ from linkml_runtime.utils.schemaview import SchemaView
|
|
14
13
|
from linkml._version import __version__
|
15
14
|
from linkml.generators.pythongen import PythonGenerator
|
16
15
|
from linkml.utils import datautils, validation
|
17
|
-
from linkml.utils.datautils import (
|
18
|
-
|
19
|
-
|
16
|
+
from linkml.utils.datautils import (
|
17
|
+
_get_context,
|
18
|
+
_get_format,
|
19
|
+
_is_xsv,
|
20
|
+
dumpers_loaders,
|
21
|
+
get_dumper,
|
22
|
+
get_loader,
|
23
|
+
infer_index_slot,
|
24
|
+
infer_root_class,
|
25
|
+
)
|
20
26
|
|
21
27
|
|
22
28
|
@click.command()
|
@@ -45,9 +51,7 @@ from linkml.utils.datautils import (_get_context, _get_format, _is_xsv,
|
|
45
51
|
show_default=True,
|
46
52
|
help="Infer the target class from the filename, should be ClassName-<other-chars>.{yaml,json,...}",
|
47
53
|
)
|
48
|
-
@click.option(
|
49
|
-
"--index-slot", "-S", help="top level slot. Required for CSV dumping/loading"
|
50
|
-
)
|
54
|
+
@click.option("--index-slot", "-S", help="top level slot. Required for CSV dumping/loading")
|
51
55
|
@click.option("--schema", "-s", help="Path to schema specified as LinkML yaml")
|
52
56
|
@click.option("--prefix", "-P", multiple=True, help="Prefixmap base=URI pairs")
|
53
57
|
@click.option(
|
@@ -118,7 +122,7 @@ def cli(
|
|
118
122
|
if target_class is None:
|
119
123
|
target_class = infer_root_class(sv)
|
120
124
|
if target_class is None:
|
121
|
-
raise Exception(
|
125
|
+
raise Exception("target class not specified and could not be inferred")
|
122
126
|
py_target_class = python_module.__dict__[target_class]
|
123
127
|
input_format = _get_format(input, input_format)
|
124
128
|
loader = get_loader(input_format)
|
@@ -127,7 +131,7 @@ def cli(
|
|
127
131
|
outargs = {}
|
128
132
|
if datautils._is_rdf_format(input_format):
|
129
133
|
if sv is None:
|
130
|
-
raise Exception(
|
134
|
+
raise Exception("Must pass schema arg")
|
131
135
|
inargs["schemaview"] = sv
|
132
136
|
inargs["fmt"] = input_format
|
133
137
|
if _is_xsv(input_format):
|
@@ -139,9 +143,7 @@ def cli(
|
|
139
143
|
inargs["schema"] = schema
|
140
144
|
obj = loader.load(source=input, target_class=py_target_class, **inargs)
|
141
145
|
if infer:
|
142
|
-
infer_config = inference_utils.Config(
|
143
|
-
use_expressions=True, use_string_serialization=True
|
144
|
-
)
|
146
|
+
infer_config = inference_utils.Config(use_expressions=True, use_string_serialization=True)
|
145
147
|
infer_all_slot_values(obj, schemaview=sv, config=infer_config)
|
146
148
|
if validate:
|
147
149
|
if schema is None:
|
@@ -159,11 +161,9 @@ def cli(
|
|
159
161
|
else:
|
160
162
|
raise Exception("Must pass in context OR schema for RDF output")
|
161
163
|
outargs["contexts"] = list(context)
|
162
|
-
outargs["fmt"] = "json-ld"
|
163
|
-
outargs["schemaview"] = sv
|
164
164
|
if output_format == "rdf" or output_format == "ttl":
|
165
165
|
if sv is None:
|
166
|
-
raise Exception(
|
166
|
+
raise Exception("Must pass schema arg")
|
167
167
|
outargs["schemaview"] = sv
|
168
168
|
if _is_xsv(output_format):
|
169
169
|
if index_slot is None:
|
linkml/utils/datautils.py
CHANGED
@@ -1,23 +1,12 @@
|
|
1
1
|
import os
|
2
2
|
from collections import defaultdict
|
3
|
-
from typing import Optional
|
4
|
-
|
5
|
-
from linkml_runtime.dumpers
|
6
|
-
from linkml_runtime.
|
7
|
-
from linkml_runtime.
|
8
|
-
from linkml_runtime.dumpers.rdflib_dumper import RDFLibDumper
|
9
|
-
from linkml_runtime.dumpers.yaml_dumper import YAMLDumper
|
10
|
-
from linkml_runtime.linkml_model.meta import (ClassDefinitionName,
|
11
|
-
SchemaDefinition,
|
12
|
-
SlotDefinitionName)
|
13
|
-
from linkml_runtime.loaders.csv_loader import CSVLoader
|
14
|
-
from linkml_runtime.loaders.json_loader import JSONLoader
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from linkml_runtime.dumpers import CSVDumper, JSONDumper, RDFLibDumper, TSVDumper, YAMLDumper
|
6
|
+
from linkml_runtime.linkml_model.meta import ClassDefinitionName, SlotDefinitionName
|
7
|
+
from linkml_runtime.loaders import CSVLoader, JSONLoader, RDFLibLoader, TSVLoader, YAMLLoader
|
15
8
|
from linkml_runtime.loaders.loader_root import Loader
|
16
|
-
from linkml_runtime.loaders.rdf_loader import RDFLoader
|
17
|
-
from linkml_runtime.loaders.rdflib_loader import RDFLibLoader
|
18
|
-
from linkml_runtime.loaders.yaml_loader import YAMLLoader
|
19
9
|
from linkml_runtime.utils.schemaview import SchemaView
|
20
|
-
from linkml_runtime.utils.yamlutils import YAMLRoot
|
21
10
|
|
22
11
|
from linkml.generators.jsonldcontextgen import ContextGenerator
|
23
12
|
|
@@ -27,9 +16,9 @@ dumpers_loaders = {
|
|
27
16
|
"json": (JSONDumper, JSONLoader),
|
28
17
|
"rdf": (RDFLibDumper, RDFLibLoader),
|
29
18
|
"ttl": (RDFLibDumper, RDFLibLoader),
|
30
|
-
"json-ld": (
|
19
|
+
"json-ld": (JSONDumper, JSONLoader),
|
31
20
|
"csv": (CSVDumper, CSVLoader),
|
32
|
-
"tsv": (
|
21
|
+
"tsv": (TSVDumper, TSVLoader),
|
33
22
|
}
|
34
23
|
|
35
24
|
aliases = {
|
@@ -42,9 +31,7 @@ def _get_format(path: str, specified_format: str = None, default=None):
|
|
42
31
|
if specified_format is None:
|
43
32
|
if path is None:
|
44
33
|
if default is None:
|
45
|
-
raise Exception(
|
46
|
-
f"Must pass format option OR pass a filename with known file suffix"
|
47
|
-
)
|
34
|
+
raise Exception("Must pass format option OR pass a filename with known file suffix")
|
48
35
|
else:
|
49
36
|
specified_format = default
|
50
37
|
else:
|
@@ -52,9 +39,7 @@ def _get_format(path: str, specified_format: str = None, default=None):
|
|
52
39
|
if ext is not None:
|
53
40
|
specified_format = ext.replace(".", "")
|
54
41
|
else:
|
55
|
-
raise Exception(
|
56
|
-
f"Must pass format option OR use known file suffix: {path}"
|
57
|
-
)
|
42
|
+
raise Exception(f"Must pass format option OR use known file suffix: {path}")
|
58
43
|
specified_format = specified_format.lower()
|
59
44
|
if specified_format in aliases:
|
60
45
|
specified_format = aliases[specified_format]
|
linkml/utils/datavalidator.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
2
|
from typing import Union
|
3
3
|
|
4
|
-
from linkml_runtime.linkml_model import
|
4
|
+
from linkml_runtime.linkml_model import ClassDefinitionName, SchemaDefinition
|
5
5
|
|
6
6
|
|
7
7
|
@dataclass
|
@@ -16,6 +16,6 @@ class DataValidator:
|
|
16
16
|
"""
|
17
17
|
|
18
18
|
def validate_dict(
|
19
|
-
|
19
|
+
self, data: dict, target_class: ClassDefinitionName = None, closed: bool = True
|
20
20
|
) -> None:
|
21
21
|
raise NotImplementedError
|
linkml/utils/execute_tutorial.py
CHANGED
@@ -10,15 +10,15 @@ import click
|
|
10
10
|
|
11
11
|
from linkml._version import __version__
|
12
12
|
|
13
|
-
re_decl = re.compile("^(
|
14
|
-
re_start_yaml = re.compile("^```(\w+)$")
|
15
|
-
re_end_yaml = re.compile("^```$")
|
16
|
-
re_html_comment = re.compile("^<!-- (.+) -->")
|
13
|
+
re_decl = re.compile(r"^(\S+):$")
|
14
|
+
re_start_yaml = re.compile(r"^```(\w+)$")
|
15
|
+
re_end_yaml = re.compile(r"^```$")
|
16
|
+
re_html_comment = re.compile(r"^<!-- (.+) -->")
|
17
17
|
|
18
18
|
|
19
19
|
@dataclass
|
20
20
|
class Block:
|
21
|
-
category: str = None
|
21
|
+
category: str = None # yaml, bash, python, ...
|
22
22
|
title: str = None
|
23
23
|
content: str = None
|
24
24
|
output: str = None
|
@@ -71,9 +71,7 @@ def execute_blocks(directory: str, blocks: List[Block]) -> List[str]:
|
|
71
71
|
outpath = cmd[pos + 1 :]
|
72
72
|
cmd = cmd[0:pos]
|
73
73
|
if len(outpath) > 1:
|
74
|
-
raise Exception(
|
75
|
-
f"Maximim 1 token after > in {block.content}. Got: {outpath}"
|
76
|
-
)
|
74
|
+
raise Exception(f"Maximum 1 token after > in {block.content}. Got: {outpath}")
|
77
75
|
outpath = str(Path(directory, *outpath))
|
78
76
|
logging.info(f"OUTPATH = {outpath}")
|
79
77
|
else:
|
@@ -91,7 +89,7 @@ def execute_blocks(directory: str, blocks: List[Block]) -> List[str]:
|
|
91
89
|
if r.returncode == 0:
|
92
90
|
err(f"Command unexpectedly succeeded: {cmd}")
|
93
91
|
else:
|
94
|
-
logging.info(
|
92
|
+
logging.info("Failed as expected")
|
95
93
|
if block.error:
|
96
94
|
logging.info(f"ERR [sample] = ...{block.error[-200:]}")
|
97
95
|
else:
|
@@ -100,7 +98,7 @@ def execute_blocks(directory: str, blocks: List[Block]) -> List[str]:
|
|
100
98
|
if r.returncode != 0:
|
101
99
|
err(f"Command failed: {cmd}")
|
102
100
|
else:
|
103
|
-
logging.info(
|
101
|
+
logging.info("Success!")
|
104
102
|
elif block.is_stdout():
|
105
103
|
if "compare_rdf" in block.annotations:
|
106
104
|
logging.warning(
|
@@ -110,7 +108,7 @@ def execute_blocks(directory: str, blocks: List[Block]) -> List[str]:
|
|
110
108
|
if last_block.output.strip() != block.content.strip():
|
111
109
|
err(f"Mismatch: {str(last_block.output)} != {block.content}")
|
112
110
|
else:
|
113
|
-
logging.info(
|
111
|
+
logging.info("Hurray! Contents match!")
|
114
112
|
else:
|
115
113
|
logging.info("No comparison performed")
|
116
114
|
else:
|
@@ -197,7 +195,7 @@ def cli(inputs, directory):
|
|
197
195
|
Example:
|
198
196
|
|
199
197
|
export PYTHONPATH=`pwd`
|
200
|
-
python -m linkml.utils.execute_tutorial -d /tmp/tutorial/
|
198
|
+
python -m linkml.utils.execute_tutorial -d /tmp/tutorial/ docs/intro/tutorial01.md
|
201
199
|
|
202
200
|
"""
|
203
201
|
logging.basicConfig(level=logging.INFO)
|