linkml 1.9.3rc1__py3-none-any.whl → 1.9.3rc3__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/PythonGenNotes.md +49 -49
- linkml/generators/README.md +2 -2
- linkml/generators/dbmlgen.py +0 -1
- linkml/generators/docgen/class.md.jinja2 +35 -11
- linkml/generators/docgen/class_diagram.md.jinja2 +15 -15
- linkml/generators/docgen/common_metadata.md.jinja2 +3 -5
- linkml/generators/docgen/enum.md.jinja2 +5 -6
- linkml/generators/docgen/index.md.jinja2 +5 -5
- linkml/generators/docgen/index.tex.jinja2 +1 -1
- linkml/generators/docgen/schema.md.jinja2 +1 -3
- linkml/generators/docgen/slot.md.jinja2 +6 -8
- linkml/generators/docgen/subset.md.jinja2 +4 -7
- linkml/generators/docgen/type.md.jinja2 +2 -3
- linkml/generators/docgen.py +94 -6
- linkml/generators/erdiagramgen.py +1 -1
- linkml/generators/graphqlgen.py +1 -1
- linkml/generators/javagen/example_template.java.jinja2 +0 -1
- linkml/generators/jsonldcontextgen.py +0 -1
- linkml/generators/jsonldgen.py +3 -1
- linkml/generators/jsonschemagen.py +2 -2
- linkml/generators/linkmlgen.py +1 -1
- linkml/generators/markdowngen.py +20 -9
- linkml/generators/mermaidclassdiagramgen.py +4 -0
- linkml/generators/projectgen.py +17 -20
- linkml/generators/pydanticgen/includes.py +5 -5
- linkml/generators/pydanticgen/pydanticgen.py +1 -2
- linkml/generators/pydanticgen/template.py +1 -1
- linkml/generators/pydanticgen/templates/base_model.py.jinja +1 -1
- linkml/generators/pydanticgen/templates/class.py.jinja +1 -1
- linkml/generators/pydanticgen/templates/conditional_import.py.jinja +1 -1
- linkml/generators/pydanticgen/templates/footer.py.jinja +1 -1
- linkml/generators/pydanticgen/templates/imports.py.jinja +1 -1
- linkml/generators/python/python_ifabsent_processor.py +1 -1
- linkml/generators/pythongen.py +9 -8
- linkml/generators/shacl/shacl_ifabsent_processor.py +0 -1
- linkml/generators/shaclgen.py +1 -1
- linkml/generators/sparqlgen.py +1 -1
- linkml/generators/sqlalchemy/__init__.py +0 -4
- linkml/generators/sqlalchemygen.py +9 -15
- linkml/generators/sqltablegen.py +70 -4
- linkml/generators/string_template.md +3 -4
- linkml/generators/terminusdbgen.py +1 -2
- linkml/linter/cli.py +3 -4
- linkml/linter/config/datamodel/config.py +286 -135
- linkml/linter/config/datamodel/config.yaml +26 -11
- linkml/linter/config/default.yaml +6 -0
- linkml/linter/config/recommended.yaml +6 -0
- linkml/linter/linter.py +10 -6
- linkml/linter/rules.py +144 -46
- linkml/transformers/relmodel_transformer.py +2 -1
- linkml/utils/generator.py +3 -0
- linkml/utils/logictools.py +5 -5
- linkml/utils/schemaloader.py +4 -4
- linkml/utils/schemasynopsis.py +11 -7
- linkml/workspaces/datamodel/workspaces.yaml +0 -5
- {linkml-1.9.3rc1.dist-info → linkml-1.9.3rc3.dist-info}/LICENSE +1 -1
- {linkml-1.9.3rc1.dist-info → linkml-1.9.3rc3.dist-info}/METADATA +2 -2
- {linkml-1.9.3rc1.dist-info → linkml-1.9.3rc3.dist-info}/RECORD +60 -60
- {linkml-1.9.3rc1.dist-info → linkml-1.9.3rc3.dist-info}/WHEEL +0 -0
- {linkml-1.9.3rc1.dist-info → linkml-1.9.3rc3.dist-info}/entry_points.txt +0 -0
@@ -20,7 +20,7 @@ classes:
|
|
20
20
|
Config:
|
21
21
|
tree_root: true
|
22
22
|
description: >-
|
23
|
-
This is the top-level representation of a LinkML linter configuration file. It allows
|
23
|
+
This is the top-level representation of a LinkML linter configuration file. It allows
|
24
24
|
defining a set of rules while also optionally extending a predefined set of rules.
|
25
25
|
attributes:
|
26
26
|
extends:
|
@@ -31,11 +31,11 @@ classes:
|
|
31
31
|
range: Rules
|
32
32
|
description: >-
|
33
33
|
This is where a configuration defines its rules and the configuration of those rules.
|
34
|
-
|
34
|
+
|
35
35
|
Rules:
|
36
36
|
description: >-
|
37
|
-
Each attribute of this class represents a rule that can be enabled and possibly
|
38
|
-
configured by a configuration file.
|
37
|
+
Each attribute of this class represents a rule that can be enabled and possibly
|
38
|
+
configured by a configuration file.
|
39
39
|
attributes:
|
40
40
|
no_empty_title:
|
41
41
|
range: NoEmptyTitleConfig
|
@@ -50,7 +50,7 @@ classes:
|
|
50
50
|
tree_root_class:
|
51
51
|
range: TreeRootClassRuleConfig
|
52
52
|
description: >-
|
53
|
-
Require a single class with `tree_root: true` and optionally verify that class's
|
53
|
+
Require a single class with `tree_root: true` and optionally verify that class's
|
54
54
|
name. Autofix will create a new class with `tree_root: true`, a name based on
|
55
55
|
the rule configuration, and slots for existing classes.
|
56
56
|
recommended:
|
@@ -61,18 +61,33 @@ classes:
|
|
61
61
|
no_xsd_int_type:
|
62
62
|
range: RuleConfig
|
63
63
|
description: >-
|
64
|
-
Disallow use of `uri: xsd:int` in type definitions. Autofix will change the
|
64
|
+
Disallow use of `uri: xsd:int` in type definitions. Autofix will change the
|
65
65
|
uri to xsd:integer.
|
66
66
|
no_invalid_slot_usage:
|
67
67
|
range: RuleConfig
|
68
68
|
description: >-
|
69
69
|
Disallow slot_usage definitions where the name of the slot does not refer
|
70
70
|
to an existing slot. Not auto-fixable.
|
71
|
+
no_undeclared_slots:
|
72
|
+
range: RuleConfig
|
73
|
+
description: >-
|
74
|
+
Disallow the use of slots in class specifications if the name of the slot
|
75
|
+
does not refer to an existing slot. Not auto-fixable.
|
76
|
+
no_undeclared_ranges:
|
77
|
+
range: RuleConfig
|
78
|
+
description: >-
|
79
|
+
Disallow the use of ranges in slot specifications if any of the ranges used
|
80
|
+
do not refer to an existing type, class, or enum. Not auto-fixable.
|
81
|
+
root_type_checks:
|
82
|
+
range: RuleConfig
|
83
|
+
description: >-
|
84
|
+
Ensure that root types, types with no `typeof` attributes, have the required slots, and
|
85
|
+
that values used in the `typeof` slot refer to valid types. Not auto-fixable.
|
71
86
|
standard_naming:
|
72
87
|
range: StandardNamingConfig
|
73
88
|
description: >-
|
74
89
|
Enforce standard naming conventions: CamelCase for classes, snake_case for slots,
|
75
|
-
CamelCase for enums, snake_case (default) or UPPER_SNAKE for permissible_values
|
90
|
+
CamelCase for enums, snake_case (default) or UPPER_SNAKE for permissible_values
|
76
91
|
(see permissible_values_upper_case option). This rule may conflict with the
|
77
92
|
permissible_values_format rule. Not auto-fixable.
|
78
93
|
canonical_prefixes:
|
@@ -84,7 +99,7 @@ classes:
|
|
84
99
|
|
85
100
|
RuleConfig:
|
86
101
|
description: >-
|
87
|
-
This is the base class for linter rules. It contains configuration options that are
|
102
|
+
This is the base class for linter rules. It contains configuration options that are
|
88
103
|
common to all rules.
|
89
104
|
attributes:
|
90
105
|
level:
|
@@ -188,8 +203,8 @@ classes:
|
|
188
203
|
multivalued: true
|
189
204
|
description: >-
|
190
205
|
Default: [merged]
|
191
|
-
The list of context names which will be loaded by the `prefixmaps` library to do the
|
192
|
-
validation. The order of names is meaningful and will be preserved. See:
|
206
|
+
The list of context names which will be loaded by the `prefixmaps` library to do the
|
207
|
+
validation. The order of names is meaningful and will be preserved. See:
|
193
208
|
https://github.com/linkml/prefixmaps#usage
|
194
209
|
|
195
210
|
NoEmptyTitleConfig:
|
@@ -202,7 +217,7 @@ classes:
|
|
202
217
|
multivalued: true
|
203
218
|
description: >-
|
204
219
|
Default: []
|
205
|
-
Elements of all types (ClassDefinition, SlotDefinition, EnumDefinition, PermissibleValue, etc.) except the types specified in this list will be checked
|
220
|
+
Elements of all types (ClassDefinition, SlotDefinition, EnumDefinition, PermissibleValue, etc.) except the types specified in this list will be checked
|
206
221
|
|
207
222
|
enums:
|
208
223
|
ExtendableConfigs:
|
@@ -18,6 +18,12 @@ rules:
|
|
18
18
|
level: disabled
|
19
19
|
no_invalid_slot_usage:
|
20
20
|
level: disabled
|
21
|
+
no_undeclared_slots:
|
22
|
+
level: disabled
|
23
|
+
no_undeclared_ranges:
|
24
|
+
level: disabled
|
25
|
+
root_type_checks:
|
26
|
+
level: disabled
|
21
27
|
standard_naming:
|
22
28
|
level: disabled
|
23
29
|
permissible_values_upper_case: false
|
linkml/linter/linter.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
"""Linkml linter."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
1
5
|
import inspect
|
2
6
|
from collections.abc import Iterable
|
3
7
|
from copy import deepcopy
|
4
8
|
from dataclasses import dataclass
|
5
9
|
from functools import lru_cache
|
6
10
|
from pathlib import Path
|
7
|
-
from typing import Any
|
11
|
+
from typing import Any
|
8
12
|
|
9
13
|
import jsonschema
|
10
14
|
import yaml
|
@@ -23,10 +27,10 @@ from .config.datamodel.config import Config, ExtendableConfigs, RuleLevel
|
|
23
27
|
@dataclass
|
24
28
|
class LinterProblem:
|
25
29
|
message: str
|
26
|
-
level:
|
27
|
-
schema_name:
|
28
|
-
schema_source:
|
29
|
-
rule_name:
|
30
|
+
level: RuleLevel | None = None
|
31
|
+
schema_name: str | None = None
|
32
|
+
schema_source: str | None = None
|
33
|
+
rule_name: str | None = None
|
30
34
|
|
31
35
|
|
32
36
|
@lru_cache
|
@@ -107,7 +111,7 @@ class Linter:
|
|
107
111
|
|
108
112
|
def lint(
|
109
113
|
self,
|
110
|
-
schema:
|
114
|
+
schema: str | SchemaDefinition,
|
111
115
|
fix: bool = False,
|
112
116
|
validate_schema: bool = False,
|
113
117
|
validate_only: bool = False,
|
linkml/linter/rules.py
CHANGED
@@ -1,23 +1,35 @@
|
|
1
|
+
"""Rule implementation for the linkml linter."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
1
5
|
import re
|
2
6
|
from abc import ABC, abstractmethod
|
3
|
-
from collections.abc import Iterable
|
7
|
+
from collections.abc import Callable, Iterable
|
4
8
|
from functools import cache
|
5
|
-
from typing import Callable
|
6
9
|
|
7
|
-
from linkml_runtime.linkml_model import
|
10
|
+
from linkml_runtime.linkml_model import (
|
11
|
+
ClassDefinition,
|
12
|
+
ClassDefinitionName,
|
13
|
+
Element,
|
14
|
+
ElementName,
|
15
|
+
EnumDefinition,
|
16
|
+
EnumDefinitionName,
|
17
|
+
SlotDefinition,
|
18
|
+
TypeDefinition,
|
19
|
+
TypeDefinitionName,
|
20
|
+
)
|
8
21
|
from linkml_runtime.utils.schemaview import SchemaView
|
9
22
|
from prefixmaps.io.parser import load_multi_context
|
10
23
|
|
11
24
|
from linkml import LOCAL_METAMODEL_YAML_FILE
|
12
|
-
|
13
|
-
from .config.datamodel.config import (
|
25
|
+
from linkml.linter.config.datamodel.config import (
|
14
26
|
CanonicalPrefixesConfig,
|
15
27
|
RecommendedRuleConfig,
|
16
28
|
RuleConfig,
|
17
29
|
StandardNamingConfig,
|
18
30
|
TreeRootClassRuleConfig,
|
19
31
|
)
|
20
|
-
from .linter import LinterProblem
|
32
|
+
from linkml.linter.linter import LinterProblem
|
21
33
|
|
22
34
|
|
23
35
|
class LinterRule(ABC):
|
@@ -91,7 +103,7 @@ class PermissibleValuesFormatRule(LinterRule):
|
|
91
103
|
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
92
104
|
pattern = self.PATTERNS.get(self.config.format, re.compile(self.config.format))
|
93
105
|
for enum_def in schema_view.all_enums(imports=False).values():
|
94
|
-
for value in enum_def.permissible_values
|
106
|
+
for value in enum_def.permissible_values:
|
95
107
|
if pattern.fullmatch(value) is None:
|
96
108
|
yield LinterProblem(f"{self.format_element(enum_def)} has permissible value '{value}'")
|
97
109
|
|
@@ -150,25 +162,23 @@ class TreeRootClassRule(LinterRule):
|
|
150
162
|
yield LinterProblem(message=f"Tree root class has an invalid name '{tree_root.name}'")
|
151
163
|
if len(tree_roots) > 1:
|
152
164
|
yield LinterProblem("Schema has more than one class with `tree_root: true`")
|
165
|
+
elif fix:
|
166
|
+
container = ClassDefinition(self.config.root_class_name, tree_root=True)
|
167
|
+
schema_view.add_class(container)
|
168
|
+
self.add_index_slots(schema_view, container.name)
|
153
169
|
else:
|
154
|
-
|
155
|
-
container = ClassDefinition(self.config.root_class_name, tree_root=True)
|
156
|
-
schema_view.add_class(container)
|
157
|
-
self.add_index_slots(schema_view, container.name)
|
158
|
-
else:
|
159
|
-
yield LinterProblem("Schema does not have class with `tree_root: true`")
|
170
|
+
yield LinterProblem("Schema does not have class with `tree_root: true`")
|
160
171
|
|
161
172
|
def add_index_slots(
|
162
173
|
self,
|
163
174
|
schema_view: SchemaView,
|
164
175
|
container_name: ClassDefinitionName,
|
165
|
-
inlined_as_list=False,
|
166
|
-
must_have_identifier=False,
|
167
|
-
slot_name_func: Callable = None,
|
168
|
-
convert_camel_case=False,
|
176
|
+
inlined_as_list: bool = False,
|
177
|
+
must_have_identifier: bool = False,
|
178
|
+
slot_name_func: Callable | None = None,
|
179
|
+
convert_camel_case: bool = False,
|
169
180
|
) -> list[SlotDefinition]:
|
170
|
-
"""
|
171
|
-
Adds index slots to a container pointing at all top-level classes
|
181
|
+
"""Add index slots to a container pointing at all top-level classes.
|
172
182
|
|
173
183
|
:param schema: input schema, will be modified in place
|
174
184
|
:param container_name:
|
@@ -178,14 +188,11 @@ class TreeRootClassRule(LinterRule):
|
|
178
188
|
:return: new slots
|
179
189
|
"""
|
180
190
|
container = schema_view.get_class(container_name)
|
181
|
-
ranges =
|
182
|
-
for cn in schema_view.all_classes():
|
183
|
-
for s in schema_view.class_induced_slots(cn):
|
184
|
-
ranges.add(s.range)
|
191
|
+
ranges = {s.range for cn in schema_view.all_classes() for s in schema_view.class_induced_slots(cn)}
|
185
192
|
top_level_classes = [c for c in schema_view.all_classes().values() if not c.tree_root and c.name not in ranges]
|
186
193
|
if must_have_identifier:
|
187
194
|
top_level_classes = [c for c in top_level_classes if schema_view.get_identifier_slot(c.name) is not None]
|
188
|
-
index_slots = []
|
195
|
+
index_slots: list[SlotDefinition] = []
|
189
196
|
for c in top_level_classes:
|
190
197
|
has_identifier = schema_view.get_identifier_slot(c.name)
|
191
198
|
if slot_name_func:
|
@@ -208,6 +215,30 @@ class TreeRootClassRule(LinterRule):
|
|
208
215
|
return index_slots
|
209
216
|
|
210
217
|
|
218
|
+
class NoUndeclaredSlotsRule(LinterRule):
|
219
|
+
"""Linter rule to check that all slots from a class have been declared in the `slots` section."""
|
220
|
+
|
221
|
+
id = "no_undeclared_slots"
|
222
|
+
|
223
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
224
|
+
"""Perform the check for undeclared slots.
|
225
|
+
|
226
|
+
:param schema_view: schema to be checked in a SchemaView object.
|
227
|
+
:type schema_view: SchemaView
|
228
|
+
:param fix: whether or not to fix, defaults to False
|
229
|
+
:type fix: bool, optional
|
230
|
+
:yield: iterable of error messages about non-compliant slots.
|
231
|
+
:rtype: Iterator[Iterable[LinterProblem]]
|
232
|
+
"""
|
233
|
+
all_slots = schema_view.all_slots()
|
234
|
+
for class_name in schema_view.all_classes():
|
235
|
+
for slot_name in schema_view.class_slots(class_name):
|
236
|
+
if slot_name not in all_slots:
|
237
|
+
yield LinterProblem(
|
238
|
+
f"Slot '{slot_name}' from class '{class_name}' not found in schema 'slots' declaration."
|
239
|
+
)
|
240
|
+
|
241
|
+
|
211
242
|
class NoInvalidSlotUsageRule(LinterRule):
|
212
243
|
id = "no_invalid_slot_usage"
|
213
244
|
|
@@ -217,11 +248,76 @@ class NoInvalidSlotUsageRule(LinterRule):
|
|
217
248
|
if not slot_usage:
|
218
249
|
continue
|
219
250
|
class_slots = schema_view.class_slots(class_name)
|
220
|
-
for slot_usage_name in slot_usage
|
251
|
+
for slot_usage_name in slot_usage:
|
221
252
|
if slot_usage_name not in class_slots:
|
222
253
|
yield LinterProblem(f"Slot '{slot_usage_name}' not found on class '{class_name}'")
|
223
254
|
|
224
255
|
|
256
|
+
class NoUndeclaredRangesRule(LinterRule):
|
257
|
+
id = "no_undeclared_ranges"
|
258
|
+
|
259
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
260
|
+
all_types: dict[TypeDefinitionName, TypeDefinition] = schema_view.all_types()
|
261
|
+
all_enums: dict[EnumDefinitionName, EnumDefinition] = schema_view.all_enums()
|
262
|
+
all_classes: dict[ClassDefinitionName, ClassDefinition] = schema_view.all_classes()
|
263
|
+
all_possible_ranges: set[TypeDefinitionName | EnumDefinitionName | ClassDefinitionName] = (
|
264
|
+
set(all_types) | set(all_enums) | set(all_classes)
|
265
|
+
)
|
266
|
+
|
267
|
+
# check that the default_range has a valid value
|
268
|
+
default_range = schema_view.schema.default_range
|
269
|
+
if default_range and default_range not in all_possible_ranges:
|
270
|
+
yield LinterProblem(f"Schema default_range '{default_range}' is not defined.")
|
271
|
+
|
272
|
+
for class_name in schema_view.all_classes():
|
273
|
+
for slot in schema_view.class_induced_slots(class_name):
|
274
|
+
slot_range: set[ElementName] = set(schema_view.slot_range_as_union(slot))
|
275
|
+
|
276
|
+
# check slot range is valid
|
277
|
+
for range_name in slot_range:
|
278
|
+
if range_name not in all_possible_ranges:
|
279
|
+
yield LinterProblem(
|
280
|
+
f"Class '{class_name}' slot '{slot.name}' range '{range_name}' is not defined."
|
281
|
+
)
|
282
|
+
|
283
|
+
|
284
|
+
class RootTypeChecks(LinterRule):
|
285
|
+
"""Performs basic checks on types.
|
286
|
+
|
287
|
+
Types can be defined as subtypes of existing types using the `typeof` attribute.
|
288
|
+
Root types are those types that do not inherit from other types using `typeof`.
|
289
|
+
|
290
|
+
- types used in 'typeof' statements must be defined in the types section
|
291
|
+
- types cannot be their own 'typeof' parent
|
292
|
+
- every root type must have a 'base'
|
293
|
+
- every root type must have a 'uri'
|
294
|
+
|
295
|
+
:param LinterRule: linter rule class
|
296
|
+
:type LinterRule: LinterRule
|
297
|
+
"""
|
298
|
+
|
299
|
+
id = "root_type_checks"
|
300
|
+
|
301
|
+
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
302
|
+
type_ix = schema_view.all_types()
|
303
|
+
for t, type_def in type_ix.items():
|
304
|
+
if type_def.typeof:
|
305
|
+
# This is a child type. Ensure that the parent exists.
|
306
|
+
if type_def.typeof not in schema_view.all_types():
|
307
|
+
yield LinterProblem(f"'{t}' has invalid typeof parent '{type_def.typeof}'")
|
308
|
+
# Ensure that a type is not its own parent.
|
309
|
+
# N.b. at some point, this can/should be extended to checking the full 'typeof' ancestor chain
|
310
|
+
# for circular inheritance.
|
311
|
+
if type_def.typeof == type_def.name:
|
312
|
+
yield LinterProblem(f"'{t}' has invalid circular typeof parent '{type_def.typeof}'")
|
313
|
+
continue
|
314
|
+
if not type_def.base:
|
315
|
+
yield LinterProblem(f"Root type '{t}' is missing the required 'base' attribute")
|
316
|
+
|
317
|
+
if not type_def.uri:
|
318
|
+
yield LinterProblem(f"Root type '{t}' is missing the required 'uri' attribute")
|
319
|
+
|
320
|
+
|
225
321
|
class StandardNamingRule(LinterRule):
|
226
322
|
id = "standard_naming"
|
227
323
|
|
@@ -247,23 +343,21 @@ class StandardNamingRule(LinterRule):
|
|
247
343
|
)
|
248
344
|
|
249
345
|
if "class_definition" not in excluded_types:
|
250
|
-
for class_name in schema_view.all_classes(imports=False)
|
346
|
+
for class_name in schema_view.all_classes(imports=False):
|
251
347
|
if class_pattern.fullmatch(class_name) is None:
|
252
348
|
yield LinterProblem(f"Class has name '{class_name}'")
|
253
349
|
|
254
350
|
if "slot_definition" not in excluded_types:
|
255
|
-
for slot_name in schema_view.all_slots(imports=False)
|
351
|
+
for slot_name in schema_view.all_slots(imports=False):
|
256
352
|
if slot_pattern.fullmatch(slot_name) is None:
|
257
353
|
yield LinterProblem(f"Slot has name '{slot_name}'")
|
258
354
|
|
259
355
|
for enum_name, enum_definition in schema_view.all_enums(imports=False).items():
|
260
|
-
|
261
|
-
|
262
|
-
if enum_pattern.fullmatch(enum_name) is None:
|
263
|
-
yield LinterProblem(f"Enum has name '{enum_name}'")
|
356
|
+
if "enum_definition" not in excluded_types and enum_pattern.fullmatch(enum_name) is None:
|
357
|
+
yield LinterProblem(f"Enum has name '{enum_name}'")
|
264
358
|
|
265
359
|
if "permissible_value" not in excluded_types:
|
266
|
-
for permissible_value_name in enum_definition.permissible_values
|
360
|
+
for permissible_value_name in enum_definition.permissible_values:
|
267
361
|
if permissible_value_pattern.fullmatch(permissible_value_name) is None:
|
268
362
|
yield LinterProblem(
|
269
363
|
f"Permissible value of {self.format_element(enum_definition)} "
|
@@ -282,17 +376,21 @@ class CanonicalPrefixesRule(LinterRule):
|
|
282
376
|
prefix_to_namespace = context.as_dict()
|
283
377
|
namespace_to_prefix = context.as_inverted_dict()
|
284
378
|
for prefix in schema_view.schema.prefixes.values():
|
285
|
-
if
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
379
|
+
if (
|
380
|
+
prefix.prefix_prefix in prefix_to_namespace
|
381
|
+
and prefix.prefix_reference != prefix_to_namespace[prefix.prefix_prefix]
|
382
|
+
):
|
383
|
+
yield LinterProblem(
|
384
|
+
f"Schema maps prefix '{prefix.prefix_prefix}' to namespace "
|
385
|
+
f"'{prefix.prefix_reference}' instead of namespace "
|
386
|
+
f"'{prefix_to_namespace[prefix.prefix_prefix]}'"
|
387
|
+
)
|
388
|
+
if (
|
389
|
+
prefix.prefix_reference in namespace_to_prefix
|
390
|
+
and prefix.prefix_prefix != namespace_to_prefix[prefix.prefix_reference]
|
391
|
+
):
|
392
|
+
yield LinterProblem(
|
393
|
+
f"Schema maps prefix '{prefix.prefix_prefix}' to namespace "
|
394
|
+
f"'{prefix.prefix_reference}' instead of using prefix "
|
395
|
+
f"'{namespace_to_prefix[prefix.prefix_reference]}'"
|
396
|
+
)
|
@@ -181,6 +181,7 @@ class RelationalModelTransformer:
|
|
181
181
|
types=source.types,
|
182
182
|
subsets=source.subsets,
|
183
183
|
enums=source.enums,
|
184
|
+
annotations=source.annotations,
|
184
185
|
)
|
185
186
|
target.prefixes["rr"] = Prefix("rr", "http://www.w3.org/ns/r2rml#")
|
186
187
|
|
@@ -196,6 +197,7 @@ class RelationalModelTransformer:
|
|
196
197
|
abstract=c.abstract,
|
197
198
|
description=c.description,
|
198
199
|
unique_keys=c.unique_keys,
|
200
|
+
annotations=c.annotations,
|
199
201
|
)
|
200
202
|
for slot in source_sv.class_induced_slots(cn):
|
201
203
|
tgt_slot = copy(slot)
|
@@ -209,7 +211,6 @@ class RelationalModelTransformer:
|
|
209
211
|
# this is required in case an attribute inherits from a slot
|
210
212
|
for sn in source_sv.all_slots(attributes=False):
|
211
213
|
slot = source_sv.get_slot(sn)
|
212
|
-
# target.slots[slot.name] = copy(slot)
|
213
214
|
target.classes[c.name] = c
|
214
215
|
|
215
216
|
target_sv = SchemaView(target)
|
linkml/utils/generator.py
CHANGED
@@ -344,6 +344,9 @@ class Generator(metaclass=abc.ABCMeta):
|
|
344
344
|
sub_out = self.end_schema(**kwargs)
|
345
345
|
if sub_out is not None:
|
346
346
|
out += sub_out
|
347
|
+
out = out.rstrip()
|
348
|
+
if not (out.count("\n") == 0 and out.startswith("http")):
|
349
|
+
out += "\n"
|
347
350
|
return out
|
348
351
|
|
349
352
|
def visit_schema(self, **kwargs) -> Optional[str]:
|
linkml/utils/logictools.py
CHANGED
@@ -125,11 +125,11 @@ class And(Expression):
|
|
125
125
|
self.operands: list[Expression] = list(operands)
|
126
126
|
|
127
127
|
def __str__(self):
|
128
|
-
return f
|
128
|
+
return f"({' & '.join(str(operand) for operand in self.operands)})"
|
129
129
|
|
130
130
|
def _ordered_str(self, **kwargs):
|
131
131
|
sorted_operands = sorted([op._ordered_str(**kwargs) for op in self.operands], key=str)
|
132
|
-
return f
|
132
|
+
return f"({' & '.join(sorted_operands)})"
|
133
133
|
|
134
134
|
|
135
135
|
class Or(Expression):
|
@@ -137,13 +137,13 @@ class Or(Expression):
|
|
137
137
|
self.operands = list(operands)
|
138
138
|
|
139
139
|
def __str__(self):
|
140
|
-
return f
|
140
|
+
return f"({' | '.join(str(operand) for operand in self.operands)})"
|
141
141
|
|
142
142
|
def _ordered_str(self, pairwise=False):
|
143
143
|
if pairwise and len(self.operands) > 2:
|
144
144
|
return Or(Or(*self.operands[0:2]), Or(*self.operands[2:]))._ordered_str(pairwise=True)
|
145
145
|
sorted_operands = sorted([op._ordered_str(pairwise=True) for op in self.operands], key=str)
|
146
|
-
return f
|
146
|
+
return f"({' | '.join(sorted_operands)})"
|
147
147
|
|
148
148
|
|
149
149
|
class Eq(Expression):
|
@@ -167,7 +167,7 @@ class Term(Expression):
|
|
167
167
|
if self.predicate in OPS:
|
168
168
|
return f"{str(self.operands[0])} {OPS[self.predicate]} {str(self.operands[1])}"
|
169
169
|
else:
|
170
|
-
return f
|
170
|
+
return f"{self.predicate}({', '.join(str(operand) for operand in self.operands)})"
|
171
171
|
|
172
172
|
def _ordered_str(self, **kwargs):
|
173
173
|
return str(self)
|
linkml/utils/schemaloader.py
CHANGED
@@ -737,7 +737,7 @@ class SchemaLoader:
|
|
737
737
|
for slot_usage in values(cls.slot_usage):
|
738
738
|
if slot_usage.alias:
|
739
739
|
self.raise_value_error(
|
740
|
-
f'Class: "{cls.name}" - alias not permitted in slot_usage slot:
|
740
|
+
f'Class: "{cls.name}" - alias not permitted in slot_usage slot: {slot_usage.alias}'
|
741
741
|
)
|
742
742
|
if not located_aliased_parent_slot(cls, slot_usage):
|
743
743
|
if slot_usage.name not in self.schema.slots:
|
@@ -765,7 +765,7 @@ class SchemaLoader:
|
|
765
765
|
for slotname, slot_usage in cls.slot_usage.items():
|
766
766
|
if slot_usage.alias:
|
767
767
|
self.raise_value_error(
|
768
|
-
f'Class: "{cls.name}" - alias not permitted in slot_usage slot:
|
768
|
+
f'Class: "{cls.name}" - alias not permitted in slot_usage slot: {slot_usage.alias}'
|
769
769
|
)
|
770
770
|
# Construct a new slot
|
771
771
|
# If we've already assigned a parent, use it
|
@@ -947,7 +947,7 @@ class SchemaLoader:
|
|
947
947
|
locs = "\n".join(TypedNode.yaml_loc(e, suffix="") for e in loc_str)
|
948
948
|
raise ValueError(f"{locs} {error}")
|
949
949
|
else:
|
950
|
-
raise ValueError(f
|
950
|
+
raise ValueError(f"{TypedNode.yaml_loc(loc_str, suffix='')} {error}")
|
951
951
|
|
952
952
|
def logger_warning(
|
953
953
|
self,
|
@@ -958,7 +958,7 @@ class SchemaLoader:
|
|
958
958
|
locs = "\n\t".join(TypedNode.yaml_loc(e, suffix="") for e in loc_str)
|
959
959
|
self.logger.warning(f"{warning}\n\t{locs}")
|
960
960
|
else:
|
961
|
-
self.logger.warning(f
|
961
|
+
self.logger.warning(f"{warning}\n\t{TypedNode.yaml_loc(loc_str, suffix='')}")
|
962
962
|
|
963
963
|
def _get_base_dir(self, stated_base: str) -> Optional[str]:
|
964
964
|
if stated_base:
|
linkml/utils/schemasynopsis.py
CHANGED
@@ -115,7 +115,11 @@ class SchemaSynopsis:
|
|
115
115
|
(
|
116
116
|
ClassType
|
117
117
|
if v.range in self.schema.classes
|
118
|
-
else EnumType
|
118
|
+
else EnumType
|
119
|
+
if v.range in self.schema.enums
|
120
|
+
else TypeType
|
121
|
+
if v.range in self.schema.types
|
122
|
+
else None
|
119
123
|
),
|
120
124
|
v.range,
|
121
125
|
)
|
@@ -244,19 +248,19 @@ class SchemaSynopsis:
|
|
244
248
|
rval = []
|
245
249
|
undefined_classes = set(self.classrefs.keys()) - set(self.schema.classes.keys())
|
246
250
|
if undefined_classes:
|
247
|
-
rval += [f"\tUndefined class references:
|
251
|
+
rval += [f"\tUndefined class references: {', '.join(format_undefineds(undefined_classes))}"]
|
248
252
|
undefined_slots = set(self.slotrefs.keys()) - set(self.schema.slots.keys())
|
249
253
|
if undefined_slots:
|
250
|
-
rval += [f"\tUndefined slot references:
|
254
|
+
rval += [f"\tUndefined slot references: {', '.join(format_undefineds(undefined_slots))}"]
|
251
255
|
undefined_types = set(self.typerefs.keys()) - set(self.schema.types.keys())
|
252
256
|
if undefined_types:
|
253
|
-
rval += [f"\tUndefined type references:
|
257
|
+
rval += [f"\tUndefined type references: {', '.join(format_undefineds(undefined_types))}"]
|
254
258
|
undefined_subsets = set(self.subsetrefs.keys()) - set(self.schema.subsets.keys())
|
255
259
|
if undefined_subsets:
|
256
|
-
rval += [f"\tUndefined subset references:
|
260
|
+
rval += [f"\tUndefined subset references: {', '.join(format_undefineds(undefined_subsets))}"]
|
257
261
|
undefined_enums = set(self.enumrefs.keys()) - set(self.schema.enums.keys())
|
258
262
|
if undefined_enums:
|
259
|
-
rval += [f"\tUndefined enun references:
|
263
|
+
rval += [f"\tUndefined enun references: {', '.join(format_undefineds(undefined_enums))}"]
|
260
264
|
|
261
265
|
# Inlined slots must be multivalued (not a inviolable rule, but we make assumptions about this elsewhere in
|
262
266
|
# the python generator
|
@@ -373,7 +377,7 @@ class SchemaSynopsis:
|
|
373
377
|
for slotname in sorted(domain_mismatches):
|
374
378
|
rval.append(
|
375
379
|
f'\t\t\tSlot: "{slotname}" declared domain: "{self.schema.slots[slotname].domain}" '
|
376
|
-
f
|
380
|
+
f"actual domain(s): {', '.join(self.slotclasses[slotname])}"
|
377
381
|
)
|
378
382
|
if unkdomains:
|
379
383
|
rval += [f"\t* Unknown domain: {', '.join(sorted(unkdomains))}"]
|
@@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software
|
|
10
10
|
distributed under the License is distributed on an "AS IS" BASIS,
|
11
11
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
See the License for the specific language governing permissions and
|
13
|
-
limitations under the License.
|
13
|
+
limitations under the License.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: linkml
|
3
|
-
Version: 1.9.
|
3
|
+
Version: 1.9.3rc3
|
4
4
|
Summary: Linked Open Data Modeling Language
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: schema,linked data,data modeling,rdf,owl,biolink
|
@@ -34,7 +34,7 @@ Requires-Dist: isodate (>=0.6.0)
|
|
34
34
|
Requires-Dist: jinja2 (>=3.1.0)
|
35
35
|
Requires-Dist: jsonasobj2 (>=1.0.3,<2.0.0)
|
36
36
|
Requires-Dist: jsonschema[format] (>=4.0.0)
|
37
|
-
Requires-Dist: linkml-runtime (>=1.9.
|
37
|
+
Requires-Dist: linkml-runtime (>=1.9.4,<2.0.0)
|
38
38
|
Requires-Dist: numpydantic (>=1.6.1) ; extra == "numpydantic"
|
39
39
|
Requires-Dist: numpydantic (>=1.6.1) ; extra == "tests"
|
40
40
|
Requires-Dist: openpyxl
|