linkml 1.9.0rc1__py3-none-any.whl → 1.9.1rc3__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/common/build.py +1 -7
- linkml/generators/common/ifabsent_processor.py +20 -20
- linkml/generators/common/lifecycle.py +2 -1
- linkml/generators/common/naming.py +1 -1
- linkml/generators/common/template.py +5 -5
- linkml/generators/common/type_designators.py +1 -3
- linkml/generators/csvgen.py +3 -3
- linkml/generators/docgen.py +20 -25
- linkml/generators/dotgen.py +4 -4
- linkml/generators/erdiagramgen.py +7 -7
- linkml/generators/excelgen.py +2 -3
- linkml/generators/golanggen.py +2 -2
- linkml/generators/golrgen.py +3 -3
- linkml/generators/jsonldcontextgen.py +4 -4
- linkml/generators/jsonschemagen.py +5 -5
- linkml/generators/markdowngen.py +8 -10
- linkml/generators/mermaidclassdiagramgen.py +2 -2
- linkml/generators/oocodegen.py +10 -10
- linkml/generators/owlgen.py +19 -18
- linkml/generators/plantumlgen.py +15 -15
- linkml/generators/prefixmapgen.py +5 -5
- linkml/generators/projectgen.py +10 -10
- linkml/generators/pydanticgen/array.py +15 -21
- linkml/generators/pydanticgen/build.py +4 -4
- linkml/generators/pydanticgen/includes.py +1 -1
- linkml/generators/pydanticgen/pydanticgen.py +24 -28
- linkml/generators/pydanticgen/template.py +36 -36
- linkml/generators/pythongen.py +21 -29
- linkml/generators/rdfgen.py +2 -2
- linkml/generators/shaclgen.py +9 -9
- linkml/generators/shexgen.py +3 -3
- linkml/generators/sparqlgen.py +3 -3
- linkml/generators/sqlalchemygen.py +2 -2
- linkml/generators/terminusdbgen.py +2 -3
- linkml/generators/typescriptgen.py +3 -3
- linkml/generators/yumlgen.py +13 -13
- linkml/linter/cli.py +1 -1
- linkml/linter/config/datamodel/config.py +207 -213
- linkml/linter/config/datamodel/config.yaml +51 -3
- linkml/linter/config/default.yaml +3 -0
- linkml/linter/formatters/markdown_formatter.py +2 -2
- linkml/linter/linter.py +4 -3
- linkml/linter/rules.py +38 -19
- linkml/reporting/model.py +11 -15
- linkml/transformers/logical_model_transformer.py +9 -8
- linkml/transformers/relmodel_transformer.py +6 -6
- linkml/transformers/schema_renamer.py +2 -2
- linkml/utils/converter.py +1 -1
- linkml/utils/deprecation.py +3 -3
- linkml/utils/execute_tutorial.py +5 -6
- linkml/utils/generator.py +17 -16
- linkml/utils/helpers.py +2 -2
- linkml/utils/logictools.py +5 -4
- linkml/utils/mergeutils.py +51 -5
- linkml/utils/schema_builder.py +8 -8
- linkml/utils/schema_fixer.py +8 -8
- linkml/utils/schemaloader.py +16 -15
- linkml/utils/schemasynopsis.py +29 -29
- linkml/utils/sqlutils.py +5 -5
- linkml/utils/typereferences.py +5 -6
- linkml/utils/validation.py +2 -2
- linkml/validator/cli.py +7 -6
- linkml/validator/loaders/delimited_file_loader.py +2 -1
- linkml/validator/loaders/json_loader.py +2 -1
- linkml/validator/loaders/loader.py +2 -1
- linkml/validator/loaders/passthrough_loader.py +2 -1
- linkml/validator/loaders/yaml_loader.py +2 -1
- linkml/validator/plugins/jsonschema_validation_plugin.py +2 -1
- linkml/validator/plugins/pydantic_validation_plugin.py +2 -1
- linkml/validator/plugins/recommended_slots_plugin.py +3 -2
- linkml/validator/plugins/shacl_validation_plugin.py +2 -1
- linkml/validator/plugins/validation_plugin.py +1 -1
- linkml/validator/report.py +3 -3
- linkml/validator/validator.py +3 -2
- linkml/validators/jsonschemavalidator.py +6 -5
- linkml/workspaces/datamodel/workspaces.py +21 -26
- linkml/workspaces/example_runner.py +7 -6
- {linkml-1.9.0rc1.dist-info → linkml-1.9.1rc3.dist-info}/METADATA +6 -9
- {linkml-1.9.0rc1.dist-info → linkml-1.9.1rc3.dist-info}/RECORD +82 -82
- {linkml-1.9.0rc1.dist-info → linkml-1.9.1rc3.dist-info}/WHEEL +1 -1
- {linkml-1.9.0rc1.dist-info → linkml-1.9.1rc3.dist-info}/LICENSE +0 -0
- {linkml-1.9.0rc1.dist-info → linkml-1.9.1rc3.dist-info}/entry_points.txt +0 -0
linkml/utils/generator.py
CHANGED
@@ -20,10 +20,11 @@ import logging
|
|
20
20
|
import os
|
21
21
|
import re
|
22
22
|
import sys
|
23
|
+
from collections.abc import Mapping
|
23
24
|
from dataclasses import dataclass, field
|
24
25
|
from functools import lru_cache
|
25
26
|
from pathlib import Path
|
26
|
-
from typing import Callable, ClassVar,
|
27
|
+
from typing import Callable, ClassVar, Optional, TextIO, Union, cast
|
27
28
|
|
28
29
|
import click
|
29
30
|
from click import Argument, Command, Option
|
@@ -103,7 +104,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
103
104
|
requires_metamodel: ClassVar[bool] = True
|
104
105
|
"""Generator queries an instance of the metamodel"""
|
105
106
|
|
106
|
-
valid_formats: ClassVar[
|
107
|
+
valid_formats: ClassVar[list[str]] = []
|
107
108
|
"""Allowed formats - first format is default"""
|
108
109
|
|
109
110
|
visit_all_class_slots: ClassVar[bool] = False
|
@@ -162,13 +163,13 @@ class Generator(metaclass=abc.ABCMeta):
|
|
162
163
|
"""Working directory or base URL of sources.
|
163
164
|
Setting this is necessary for correct retrieval of relative imports."""
|
164
165
|
|
165
|
-
metamodel_name_map:
|
166
|
+
metamodel_name_map: dict[str, str] = None
|
166
167
|
"""Allows mapping of names of metamodel elements such as slot, etc"""
|
167
168
|
|
168
169
|
importmap: Optional[Union[str, Optional[Mapping[str, str]]]] = None
|
169
170
|
"""File name of import mapping file -- maps import name/uri to target"""
|
170
171
|
|
171
|
-
emit_prefixes:
|
172
|
+
emit_prefixes: set[str] = field(default_factory=lambda: set())
|
172
173
|
"""Prefixes to emit, for RDF-based generators"""
|
173
174
|
|
174
175
|
metamodel: SchemaLoader = None
|
@@ -416,7 +417,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
416
417
|
# =============================
|
417
418
|
# Helper methods
|
418
419
|
# =============================
|
419
|
-
def own_slots(self, cls: Union[ClassDefinitionName, ClassDefinition]) ->
|
420
|
+
def own_slots(self, cls: Union[ClassDefinitionName, ClassDefinition]) -> list[SlotDefinition]:
|
420
421
|
"""Return the list of slots owned the class definition. An "own slot" is any ``cls`` slot that does not appear
|
421
422
|
in the class is_a parent. Own_slots include:
|
422
423
|
|
@@ -442,7 +443,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
442
443
|
seen.add(sname_base)
|
443
444
|
return sorted(rval, key=lambda s: s.name) if self.sort_class_slots else rval
|
444
445
|
|
445
|
-
def own_slot_names(self, cls: Union[ClassDefinitionName, ClassDefinition]) ->
|
446
|
+
def own_slot_names(self, cls: Union[ClassDefinitionName, ClassDefinition]) -> list[SlotDefinitionName]:
|
446
447
|
return [slot.name for slot in self.own_slots(cls)]
|
447
448
|
|
448
449
|
def all_slots(
|
@@ -450,8 +451,8 @@ class Generator(metaclass=abc.ABCMeta):
|
|
450
451
|
cls: Union[ClassDefinitionName, ClassDefinition],
|
451
452
|
*,
|
452
453
|
cls_slots_first: bool = False,
|
453
|
-
seen: Optional[
|
454
|
-
) ->
|
454
|
+
seen: Optional[set[ClassDefinitionName]] = None,
|
455
|
+
) -> list[SlotDefinition]:
|
455
456
|
"""Return all slots that are part of the class definition. This includes all is_a, mixin and apply_to slots
|
456
457
|
but does NOT include slot_usage targets. If class B has a slot_usage entry for slot "s", only the slot
|
457
458
|
definition for the redefined slot will be included, not its base. Slots are added in the order they appear
|
@@ -499,7 +500,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
499
500
|
)
|
500
501
|
)
|
501
502
|
|
502
|
-
def ancestors(self, element: Union[ClassDefinition, SlotDefinition]) ->
|
503
|
+
def ancestors(self, element: Union[ClassDefinition, SlotDefinition]) -> list[ElementName]:
|
503
504
|
"""Return an ordered list of ancestor names for the supplied slot or class
|
504
505
|
|
505
506
|
@param element: Slot or class name or definition
|
@@ -507,7 +508,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
507
508
|
"""
|
508
509
|
return [element.name] + ([] if element.is_a is None else self.ancestors(self.parent(element)))
|
509
510
|
|
510
|
-
def neighborhood(self, elements: Union[str, ElementName,
|
511
|
+
def neighborhood(self, elements: Union[str, ElementName, list[ElementName]]) -> References:
|
511
512
|
"""Return a list of all slots, classes and types that touch any element in elements, including the element
|
512
513
|
itself
|
513
514
|
|
@@ -581,7 +582,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
581
582
|
|
582
583
|
return touches
|
583
584
|
|
584
|
-
def range_type_path(self, typ: TypeDefinition) ->
|
585
|
+
def range_type_path(self, typ: TypeDefinition) -> list[str]:
|
585
586
|
"""
|
586
587
|
Return a formatted list of range types from the base up
|
587
588
|
|
@@ -618,14 +619,14 @@ class Generator(metaclass=abc.ABCMeta):
|
|
618
619
|
return None
|
619
620
|
|
620
621
|
@staticmethod
|
621
|
-
def enum_identifier_path(enum_or_enumname: Union[str, EnumDefinition]) ->
|
622
|
+
def enum_identifier_path(enum_or_enumname: Union[str, EnumDefinition]) -> list[str]:
|
622
623
|
"""Return an enum_identifier path"""
|
623
624
|
return [
|
624
625
|
"str",
|
625
626
|
camelcase(enum_or_enumname.name if isinstance(enum_or_enumname, EnumDefinition) else enum_or_enumname),
|
626
627
|
]
|
627
628
|
|
628
|
-
def class_identifier_path(self, cls_or_clsname: Union[str, ClassDefinition], force_non_key: bool) ->
|
629
|
+
def class_identifier_path(self, cls_or_clsname: Union[str, ClassDefinition], force_non_key: bool) -> list[str]:
|
629
630
|
"""
|
630
631
|
Return the path closure to a class identifier if the class has a key and force_non_key is false otherwise
|
631
632
|
return a dictionary closure.
|
@@ -657,7 +658,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
657
658
|
return self.class_identifier_path(cls.is_a, False) + [pathname]
|
658
659
|
return self.slot_range_path(identifier_slot) + [pathname]
|
659
660
|
|
660
|
-
def slot_range_path(self, slot_or_name: Union[str, SlotDefinition]) ->
|
661
|
+
def slot_range_path(self, slot_or_name: Union[str, SlotDefinition]) -> list[str]:
|
661
662
|
"""
|
662
663
|
Return an ordered list of slot ranges from distal to proximal
|
663
664
|
|
@@ -796,7 +797,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
796
797
|
return self.schema.prefixes[PrefixPrefixPrefix(self.schema.default_prefix)].prefix_reference
|
797
798
|
|
798
799
|
# TODO: add lru cache once we get identity into the classes
|
799
|
-
def domain_slots(self, cls: ClassDefinition) ->
|
800
|
+
def domain_slots(self, cls: ClassDefinition) -> list[SlotDefinition]:
|
800
801
|
"""Return all slots in the class definition that are owned by the class"""
|
801
802
|
domain_slots = []
|
802
803
|
for slot_name in cls.slots:
|
@@ -912,7 +913,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
912
913
|
return cls.class_uri == "linkml:Any"
|
913
914
|
|
914
915
|
|
915
|
-
def shared_arguments(g:
|
916
|
+
def shared_arguments(g: type[Generator]) -> Callable[[Command], Command]:
|
916
917
|
def verbosity_callback(ctx, param, verbose):
|
917
918
|
if verbose >= 2:
|
918
919
|
logging.basicConfig(level=logging.DEBUG, force=True)
|
linkml/utils/helpers.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import re
|
2
2
|
from functools import lru_cache
|
3
|
-
from typing import
|
3
|
+
from typing import Union
|
4
4
|
|
5
5
|
from linkml_runtime import SchemaView
|
6
6
|
from linkml_runtime.linkml_model.meta import (
|
@@ -28,7 +28,7 @@ def convert_to_snake_case(str):
|
|
28
28
|
@lru_cache(None)
|
29
29
|
def get_range_associated_slots(
|
30
30
|
schemaview: SchemaView, range_class: ClassDefinition
|
31
|
-
) ->
|
31
|
+
) -> tuple[Union[SlotDefinition, None], Union[SlotDefinition, None], Union[list[SlotDefinition], None]]:
|
32
32
|
if isinstance(range_class, ElementName):
|
33
33
|
range_class = schemaview.get_class(range_class)
|
34
34
|
if range_class is None:
|
linkml/utils/logictools.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
import operator
|
2
|
+
from collections.abc import Collection
|
2
3
|
from copy import deepcopy
|
3
4
|
from itertools import product
|
4
|
-
from typing import Any,
|
5
|
+
from typing import Any, Optional
|
5
6
|
|
6
7
|
|
7
8
|
def member_of(item: Any, collection: Collection[Any]) -> bool:
|
@@ -121,7 +122,7 @@ class And(Expression):
|
|
121
122
|
"""Conjunction of expressions"""
|
122
123
|
|
123
124
|
def __init__(self, *operands: Expression):
|
124
|
-
self.operands:
|
125
|
+
self.operands: list[Expression] = list(operands)
|
125
126
|
|
126
127
|
def __str__(self):
|
127
128
|
return f'({" & ".join(str(operand) for operand in self.operands)})'
|
@@ -173,7 +174,7 @@ class Term(Expression):
|
|
173
174
|
|
174
175
|
|
175
176
|
class IsIn(Term):
|
176
|
-
def __init__(self, element: Expression, collection:
|
177
|
+
def __init__(self, element: Expression, collection: list[Any]):
|
177
178
|
self.predicate = "in"
|
178
179
|
self.operands = [element, collection]
|
179
180
|
|
@@ -640,7 +641,7 @@ def _unsat(x: Expression, y: Expression) -> bool:
|
|
640
641
|
return False
|
641
642
|
|
642
643
|
|
643
|
-
def compose_operators(boolean_op:
|
644
|
+
def compose_operators(boolean_op: type[Expression], op1: str, v1: Any, op2: str, v2: Any) -> Optional[tuple]:
|
644
645
|
"""
|
645
646
|
Compose two expressions.
|
646
647
|
|
linkml/utils/mergeutils.py
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
import dataclasses
|
2
2
|
import logging
|
3
|
+
import os
|
3
4
|
from copy import deepcopy
|
4
|
-
from
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Optional, Union, cast
|
7
|
+
from urllib.parse import urlparse
|
5
8
|
|
6
9
|
from linkml_runtime.linkml_model.meta import (
|
7
10
|
ClassDefinition,
|
@@ -33,7 +36,7 @@ def merge_schemas(
|
|
33
36
|
if target.license is None:
|
34
37
|
target.license = mergee.license
|
35
38
|
|
36
|
-
target
|
39
|
+
resolve_merged_imports(target, mergee, imported_from)
|
37
40
|
set_from_schema(mergee)
|
38
41
|
|
39
42
|
if namespaces:
|
@@ -125,8 +128,8 @@ def set_from_schema(schema: SchemaDefinition) -> None:
|
|
125
128
|
|
126
129
|
|
127
130
|
def merge_dicts(
|
128
|
-
target:
|
129
|
-
source:
|
131
|
+
target: dict[str, Element],
|
132
|
+
source: dict[str, Element],
|
130
133
|
imported_from: str,
|
131
134
|
imported_from_uri: str,
|
132
135
|
merge_imports: bool,
|
@@ -150,7 +153,7 @@ def merge_dicts(
|
|
150
153
|
def merge_slots(
|
151
154
|
target: Union[SlotDefinition, TypeDefinition],
|
152
155
|
source: Union[SlotDefinition, TypeDefinition],
|
153
|
-
skip:
|
156
|
+
skip: list[Union[SlotDefinitionName, TypeDefinitionName]] = None,
|
154
157
|
inheriting: bool = True,
|
155
158
|
) -> None:
|
156
159
|
"""
|
@@ -232,3 +235,46 @@ def merge_enums(
|
|
232
235
|
"""
|
233
236
|
# TODO: Finish enumeration merge code
|
234
237
|
pass
|
238
|
+
|
239
|
+
|
240
|
+
def resolve_merged_imports(
|
241
|
+
target: SchemaDefinition, mergee: SchemaDefinition, imported_from: Optional[str] = None
|
242
|
+
) -> None:
|
243
|
+
"""Merge the imports in source into target
|
244
|
+
|
245
|
+
:param target: Containing schema
|
246
|
+
:param mergee: mergee
|
247
|
+
:param imported_from: file that the mergee was imported from
|
248
|
+
:param at_end: True means add mergee to the end. False to the front
|
249
|
+
"""
|
250
|
+
|
251
|
+
for imp in mergee.imports:
|
252
|
+
if imp is None:
|
253
|
+
continue
|
254
|
+
|
255
|
+
# Skip if already imported
|
256
|
+
if imp in target.imports:
|
257
|
+
continue
|
258
|
+
|
259
|
+
# Leave as-is if the import has a scheme (e.g. http://)
|
260
|
+
elif urlparse(imp).scheme:
|
261
|
+
target.imports.append(imp)
|
262
|
+
continue
|
263
|
+
|
264
|
+
# Leave as-is if the import is an absolute path
|
265
|
+
elif Path(imp).is_absolute():
|
266
|
+
target.imports.append(imp)
|
267
|
+
continue
|
268
|
+
|
269
|
+
# Adjust relative imports to be relative to the importing file
|
270
|
+
elif imp.startswith("."):
|
271
|
+
if imported_from is None:
|
272
|
+
logger.warning(f"Cannot resolve relative import: {imp}")
|
273
|
+
target.imports.append(imp)
|
274
|
+
else:
|
275
|
+
resolved_imp = os.path.normpath(str(Path(imported_from).parent / Path(imp)))
|
276
|
+
target.imports.append(resolved_imp)
|
277
|
+
|
278
|
+
# By default, add the import as-is
|
279
|
+
else:
|
280
|
+
target.imports.append(imp)
|
linkml/utils/schema_builder.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
|
-
from typing import Any,
|
2
|
+
from typing import Any, Optional, Union
|
3
3
|
|
4
4
|
from linkml_runtime.linkml_model import (
|
5
5
|
ClassDefinition,
|
@@ -59,9 +59,9 @@ class SchemaBuilder:
|
|
59
59
|
|
60
60
|
def add_class(
|
61
61
|
self,
|
62
|
-
cls: Union[ClassDefinition,
|
63
|
-
slots: Union[
|
64
|
-
slot_usage: Union[
|
62
|
+
cls: Union[ClassDefinition, dict, str],
|
63
|
+
slots: Union[dict, list[Union[str, SlotDefinition]]] = None,
|
64
|
+
slot_usage: Union[dict[str, SlotDefinition], dict[str, Any], list[SlotDefinition]] = None,
|
65
65
|
replace_if_present=False,
|
66
66
|
use_attributes=False,
|
67
67
|
**kwargs,
|
@@ -125,7 +125,7 @@ class SchemaBuilder:
|
|
125
125
|
|
126
126
|
def add_slot(
|
127
127
|
self,
|
128
|
-
slot: Union[SlotDefinition,
|
128
|
+
slot: Union[SlotDefinition, dict, str],
|
129
129
|
class_name: str = None,
|
130
130
|
replace_if_present=False,
|
131
131
|
**kwargs,
|
@@ -167,7 +167,7 @@ class SchemaBuilder:
|
|
167
167
|
def add_enum(
|
168
168
|
self,
|
169
169
|
enum_def: Union[EnumDefinition, dict, str],
|
170
|
-
permissible_values:
|
170
|
+
permissible_values: list[Union[str, PermissibleValue]] = None,
|
171
171
|
replace_if_present=False,
|
172
172
|
**kwargs,
|
173
173
|
) -> "SchemaBuilder":
|
@@ -232,7 +232,7 @@ class SchemaBuilder:
|
|
232
232
|
|
233
233
|
def add_type(
|
234
234
|
self,
|
235
|
-
type: Union[TypeDefinition,
|
235
|
+
type: Union[TypeDefinition, dict, str],
|
236
236
|
typeof: str = None,
|
237
237
|
uri: str = None,
|
238
238
|
replace_if_present=False,
|
@@ -262,7 +262,7 @@ class SchemaBuilder:
|
|
262
262
|
setattr(type, k, v)
|
263
263
|
return self
|
264
264
|
|
265
|
-
def as_dict(self) ->
|
265
|
+
def as_dict(self) -> dict:
|
266
266
|
"""
|
267
267
|
Returns the schema as a dictionary.
|
268
268
|
|
linkml/utils/schema_fixer.py
CHANGED
@@ -3,7 +3,7 @@ import re
|
|
3
3
|
from collections import defaultdict
|
4
4
|
from copy import copy
|
5
5
|
from dataclasses import dataclass
|
6
|
-
from typing import Any, Callable,
|
6
|
+
from typing import Any, Callable, Optional, Union
|
7
7
|
|
8
8
|
import click
|
9
9
|
import yaml
|
@@ -25,7 +25,7 @@ logger = logging.getLogger(__name__)
|
|
25
25
|
pattern = re.compile(r"(?<!^)(?=[A-Z])")
|
26
26
|
|
27
27
|
|
28
|
-
def yaml_rewrite(obj: Any, replacements:
|
28
|
+
def yaml_rewrite(obj: Any, replacements: dict[str, Any], include_keys=True) -> Any:
|
29
29
|
if isinstance(obj, YAMLRoot):
|
30
30
|
obj2 = copy(obj)
|
31
31
|
for k, v in vars(obj).items():
|
@@ -52,7 +52,7 @@ class SchemaFixer:
|
|
52
52
|
Multiple methods for adding additional information to schemas
|
53
53
|
"""
|
54
54
|
|
55
|
-
history:
|
55
|
+
history: list[str] = None
|
56
56
|
|
57
57
|
def add_titles(self, schema: SchemaDefinition):
|
58
58
|
"""
|
@@ -111,7 +111,7 @@ class SchemaFixer:
|
|
111
111
|
must_have_identifier=False,
|
112
112
|
slot_name_func: Callable = None,
|
113
113
|
convert_camel_case=False,
|
114
|
-
) ->
|
114
|
+
) -> list[SlotDefinition]:
|
115
115
|
"""
|
116
116
|
Adds index slots to a container pointing at all top-level classes
|
117
117
|
|
@@ -272,7 +272,7 @@ class SchemaFixer:
|
|
272
272
|
del cls.slot_usage[k]
|
273
273
|
|
274
274
|
@staticmethod
|
275
|
-
def implicit_slots(schema: SchemaDefinition) ->
|
275
|
+
def implicit_slots(schema: SchemaDefinition) -> dict[str, dict]:
|
276
276
|
"""
|
277
277
|
Find slots that are implicit in the schema from slot_usage
|
278
278
|
|
@@ -317,11 +317,11 @@ class SchemaFixer:
|
|
317
317
|
@staticmethod
|
318
318
|
def fix_element_names(
|
319
319
|
schema: SchemaDefinition,
|
320
|
-
schema_dict:
|
321
|
-
rules:
|
320
|
+
schema_dict: dict[str, Any] = None,
|
321
|
+
rules: dict[str, Callable] = None,
|
322
322
|
imports=False,
|
323
323
|
preserve_original_using: Optional[str] = None,
|
324
|
-
) -> Union[YAMLRoot,
|
324
|
+
) -> Union[YAMLRoot, dict]:
|
325
325
|
"""
|
326
326
|
Changes element names to conform to naming conventions.
|
327
327
|
|
linkml/utils/schemaloader.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
from collections import OrderedDict
|
4
|
+
from collections.abc import Iterator, Mapping
|
4
5
|
from copy import deepcopy
|
5
6
|
from pathlib import Path
|
6
|
-
from typing import
|
7
|
+
from typing import Optional, TextIO, Union, cast
|
7
8
|
from urllib.parse import urlparse
|
8
9
|
|
9
10
|
from jsonasobj2 import values
|
@@ -71,7 +72,7 @@ class SchemaLoader:
|
|
71
72
|
source_file_size=source_file_size,
|
72
73
|
)
|
73
74
|
# Map from URI to source and version tuple
|
74
|
-
self.loaded: OrderedDict[str,
|
75
|
+
self.loaded: OrderedDict[str, tuple[str, str]] = {
|
75
76
|
self.schema.id: (self.schema.source_file, self.schema.version)
|
76
77
|
}
|
77
78
|
self.base_dir = self._get_base_dir(base_dir)
|
@@ -82,7 +83,7 @@ class SchemaLoader:
|
|
82
83
|
self.source_file_size = source_file_size
|
83
84
|
self.synopsis: Optional[SchemaSynopsis] = None
|
84
85
|
self.schema_location: Optional[str] = None
|
85
|
-
self.schema_defaults:
|
86
|
+
self.schema_defaults: dict[str, str] = {} # Map from schema URI to default namespace
|
86
87
|
self.merge_modules = mergeimports
|
87
88
|
self.emit_metadata = emit_metadata
|
88
89
|
|
@@ -281,7 +282,7 @@ class SchemaLoader:
|
|
281
282
|
self.raise_value_error(f"Slot {slot.name}.inverse ({slot.inverse}) is not defined")
|
282
283
|
|
283
284
|
# Update slots with parental information
|
284
|
-
merged_slots:
|
285
|
+
merged_slots: list[SlotDefinitionName] = []
|
285
286
|
for slot in self.schema.slots.values():
|
286
287
|
if not slot.from_schema:
|
287
288
|
slot.from_schema = self.schema.id
|
@@ -306,12 +307,12 @@ class SchemaLoader:
|
|
306
307
|
cls.from_schema = self.schema.id
|
307
308
|
|
308
309
|
# Merge class with its mixins and the like
|
309
|
-
merged_classes:
|
310
|
+
merged_classes: list[ClassDefinitionName] = []
|
310
311
|
for cls in self.schema.classes.values():
|
311
312
|
self.merge_class(cls, merged_classes)
|
312
313
|
|
313
314
|
# Update types with parental information
|
314
|
-
merged_types:
|
315
|
+
merged_types: list[TypeDefinitionName] = []
|
315
316
|
for typ in self.schema.types.values():
|
316
317
|
if not typ.base and not typ.typeof:
|
317
318
|
self.raise_value_error(
|
@@ -466,7 +467,7 @@ class SchemaLoader:
|
|
466
467
|
)
|
467
468
|
|
468
469
|
# Check for duplicate class and type names
|
469
|
-
def check_dups(s1:
|
470
|
+
def check_dups(s1: set[ElementName], s2: set[ElementName]) -> tuple[list[ElementName], str]:
|
470
471
|
if s1.isdisjoint(s2):
|
471
472
|
return [], ""
|
472
473
|
|
@@ -616,13 +617,13 @@ class SchemaLoader:
|
|
616
617
|
self.raise_value_error(f"Subset: {subset} is not defined", subset)
|
617
618
|
return self.schema
|
618
619
|
|
619
|
-
def validate_item_names(self, typ: str, names:
|
620
|
+
def validate_item_names(self, typ: str, names: list[str]) -> None:
|
620
621
|
# TODO: add a more rigorous syntax check for item names
|
621
622
|
for name in names:
|
622
623
|
if ":" in name:
|
623
624
|
raise self.raise_value_error(f'{typ}: "{name}" - ":" not allowed in identifier', name)
|
624
625
|
|
625
|
-
def merge_enum(self, enum: EnumDefinition, merged_enums:
|
626
|
+
def merge_enum(self, enum: EnumDefinition, merged_enums: list[EnumDefinitionName]) -> None:
|
626
627
|
"""
|
627
628
|
Merge parent enumeration information into target enum
|
628
629
|
|
@@ -641,7 +642,7 @@ class SchemaLoader:
|
|
641
642
|
enum.is_a,
|
642
643
|
)
|
643
644
|
|
644
|
-
def merge_slot(self, slot: SlotDefinition, merged_slots:
|
645
|
+
def merge_slot(self, slot: SlotDefinition, merged_slots: list[SlotDefinitionName]) -> None:
|
645
646
|
"""
|
646
647
|
Merge parent slot information into target slot
|
647
648
|
|
@@ -673,7 +674,7 @@ class SchemaLoader:
|
|
673
674
|
self.raise_value_error(f'Slot: "{slot.name}" - unknown mixin reference: {mixin}', mixin)
|
674
675
|
merged_slots.append(slot.name)
|
675
676
|
|
676
|
-
def merge_class(self, cls: ClassDefinition, merged_classes:
|
677
|
+
def merge_class(self, cls: ClassDefinition, merged_classes: list[ClassDefinitionName]) -> None:
|
677
678
|
"""
|
678
679
|
Merge parent class information into target class
|
679
680
|
|
@@ -704,8 +705,8 @@ class SchemaLoader:
|
|
704
705
|
Slot usages can be used to completely define slots. Iterate over the class hierarchy finding all slot
|
705
706
|
definitions that are introduced strictly as usages and add them to the slots component
|
706
707
|
"""
|
707
|
-
visited:
|
708
|
-
visited_usages:
|
708
|
+
visited: set[ClassDefinitionName] = set()
|
709
|
+
visited_usages: set[SlotDefinitionName] = set() # Slots that are or will be mangled
|
709
710
|
|
710
711
|
def located_aliased_parent_slot(owning_class: ClassDefinition, usage_slot: SlotDefinition) -> bool:
|
711
712
|
"""Determine whether we are overriding an attributes style slot in the parent class
|
@@ -839,7 +840,7 @@ class SchemaLoader:
|
|
839
840
|
if new_val:
|
840
841
|
setattr(new_slot, metaslot_name, new_val)
|
841
842
|
|
842
|
-
def merge_type(self, typ: TypeDefinition, merged_types:
|
843
|
+
def merge_type(self, typ: TypeDefinition, merged_types: list[TypeDefinitionName]) -> None:
|
843
844
|
"""
|
844
845
|
Merge parent type information into target type
|
845
846
|
:param typ: target type
|
@@ -858,7 +859,7 @@ class SchemaLoader:
|
|
858
859
|
)
|
859
860
|
merged_types.append(typ.name)
|
860
861
|
|
861
|
-
def schema_errors(self) ->
|
862
|
+
def schema_errors(self) -> list[str]:
|
862
863
|
return self.synopsis.errors() if self.synopsis else ["resolve() must be run before error check"]
|
863
864
|
|
864
865
|
def slot_definition_for(self, slotname: SlotDefinitionName, cls: ClassDefinition) -> Optional[SlotDefinition]:
|
linkml/utils/schemasynopsis.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from dataclasses import dataclass, field
|
2
|
-
from typing import
|
2
|
+
from typing import Union
|
3
3
|
|
4
4
|
from linkml_runtime.linkml_model.meta import (
|
5
5
|
ClassDefinition,
|
@@ -36,45 +36,45 @@ class SchemaSynopsis:
|
|
36
36
|
schema: SchemaDefinition = field(repr=False, compare=False)
|
37
37
|
|
38
38
|
# References by type -- set by add_ref
|
39
|
-
typerefs:
|
40
|
-
slotrefs:
|
41
|
-
classrefs:
|
42
|
-
subsetrefs:
|
43
|
-
enumrefs:
|
39
|
+
typerefs: dict[TypeDefinitionName, References] = empty_dict() # Type name to all references
|
40
|
+
slotrefs: dict[SlotDefinitionName, References] = empty_dict() # Slot name to all references
|
41
|
+
classrefs: dict[ClassDefinitionName, References] = empty_dict() # Class name to all references
|
42
|
+
subsetrefs: dict[SubsetDefinitionName, References] = empty_dict() # Subset name to references
|
43
|
+
enumrefs: dict[EnumDefinitionName, References] = empty_dict() # Enum name to references
|
44
44
|
|
45
45
|
# Type specific
|
46
|
-
typebases:
|
47
|
-
typeofs:
|
46
|
+
typebases: dict[str, set[TypeDefinitionName]] = empty_dict() # Base referencing types (direct and indirect)
|
47
|
+
typeofs: dict[TypeDefinitionName, TypeDefinitionName] = empty_dict() # Type to specializations
|
48
48
|
|
49
49
|
# Slot specific
|
50
|
-
slotclasses:
|
51
|
-
definingslots:
|
52
|
-
slotusages:
|
53
|
-
owners:
|
54
|
-
inverses:
|
50
|
+
slotclasses: dict[SlotDefinitionName, set[ClassDefinitionName]] = empty_dict() # Slot to including classes
|
51
|
+
definingslots: dict[SlotDefinitionName, set[ClassDefinitionName]] = empty_dict() # Slot to defining decls
|
52
|
+
slotusages: dict[SlotDefinitionName, set[ClassDefinitionName]] = empty_dict() # Slot to overriding classes
|
53
|
+
owners: dict[SlotDefinitionName, set[ClassDefinitionName]] = empty_dict() # Slot to owning classes (sb. 1)
|
54
|
+
inverses: dict[str, set[str]] = empty_dict() # Slots declared as inverses of other slots
|
55
55
|
|
56
56
|
# Class specific
|
57
|
-
ownslots:
|
57
|
+
ownslots: dict[ClassDefinitionName, set[SlotDefinitionName]] = empty_dict() # Slots directly owned by class
|
58
58
|
|
59
59
|
# Enum specific
|
60
|
-
codesets:
|
60
|
+
codesets: dict[URIRef, set[EnumDefinitionName]] = empty_dict() # Code set URI to enumeration definition
|
61
61
|
|
62
62
|
# Class to slot domains == class.slots
|
63
63
|
|
64
64
|
# Slot or Class (Definition) specific
|
65
65
|
roots: References = empty_references() # Definitions with no parents
|
66
|
-
isarefs:
|
67
|
-
mixinrefs:
|
66
|
+
isarefs: dict[DefinitionName, References] = empty_dict() # Definition to isa references
|
67
|
+
mixinrefs: dict[DefinitionName, References] = empty_dict() # Mixin to referencing classes or slots
|
68
68
|
mixins: References = empty_references() # Definitions declared as mixin
|
69
69
|
abstracts: References = empty_references() # Definitions declared as abstract
|
70
70
|
applytos: References = empty_references() # Definitions that include applytos
|
71
|
-
applytorefs:
|
71
|
+
applytorefs: dict[DefinitionName, References] = empty_dict() # Definition to applyier
|
72
72
|
|
73
73
|
# Slot or Type specific
|
74
|
-
rangerefs:
|
74
|
+
rangerefs: dict[ElementName, set[SlotDefinitionName]] = empty_dict() # Type or class to range slot
|
75
75
|
|
76
76
|
# Element - any type
|
77
|
-
inschema:
|
77
|
+
inschema: dict[str, References] = empty_references() # Schema name to elements
|
78
78
|
|
79
79
|
def __post_init__(self):
|
80
80
|
for k, v in self.schema.slots.items():
|
@@ -237,8 +237,8 @@ class SchemaSynopsis:
|
|
237
237
|
def _ancestor_is_owned(self, slot: SlotDefinition) -> bool:
|
238
238
|
return bool(slot.is_a) and (slot.is_a in self.owners or self._ancestor_is_owned(self.schema.slots[slot.is_a]))
|
239
239
|
|
240
|
-
def errors(self) ->
|
241
|
-
def format_undefineds(refs:
|
240
|
+
def errors(self) -> list[str]:
|
241
|
+
def format_undefineds(refs: set[Union[str, TypedNode]]) -> list[str]:
|
242
242
|
return [f"{TypedNode.yaml_loc(ref)}: {ref}" for ref in refs]
|
243
243
|
|
244
244
|
rval = []
|
@@ -266,7 +266,7 @@ class SchemaSynopsis:
|
|
266
266
|
return rval
|
267
267
|
|
268
268
|
def summary(self) -> str:
|
269
|
-
def summarize_refs(refs:
|
269
|
+
def summarize_refs(refs: dict[ElementName, References]) -> str:
|
270
270
|
clsrefs, slotrefs, typerefs, enumrefs = set(), set(), set(), set()
|
271
271
|
if refs is not None:
|
272
272
|
for cr in refs.values():
|
@@ -279,7 +279,7 @@ class SchemaSynopsis:
|
|
279
279
|
f"{len(typerefs)} types, {len(enumrefs)} enums "
|
280
280
|
)
|
281
281
|
|
282
|
-
def recurse_types(typ: TypeDefinitionName, indent: str = "\t\t\t") ->
|
282
|
+
def recurse_types(typ: TypeDefinitionName, indent: str = "\t\t\t") -> list[str]:
|
283
283
|
rval = [f"{indent}{typ}" + (":" if typ in self.typeofs else "")]
|
284
284
|
if typ in sorted(self.typeofs):
|
285
285
|
for tr in sorted(self.typeofs[typ]):
|
@@ -332,7 +332,7 @@ class SchemaSynopsis:
|
|
332
332
|
|
333
333
|
# Slots that are defined but do not (directly) occur in any class
|
334
334
|
n_unreferenced_descendants: int = 0
|
335
|
-
unowned_slots:
|
335
|
+
unowned_slots: set[SlotDefinitionName] = set()
|
336
336
|
for slotname, slot in sorted(self.schema.slots.items(), key=lambda e: e[0]):
|
337
337
|
if slotname not in self.owners:
|
338
338
|
if slot.domain:
|
@@ -345,10 +345,10 @@ class SchemaSynopsis:
|
|
345
345
|
if unowned_slots:
|
346
346
|
rval += [f"\t* Unowned slots: {', '.join(sorted(unowned_slots))}"]
|
347
347
|
|
348
|
-
not_in_domain:
|
349
|
-
domain_mismatches:
|
350
|
-
unkdomains:
|
351
|
-
emptydomains:
|
348
|
+
not_in_domain: set[SlotDefinitionName] = set()
|
349
|
+
domain_mismatches: set[SlotDefinitionName] = set()
|
350
|
+
unkdomains: set[SlotDefinitionName] = set()
|
351
|
+
emptydomains: set[SlotDefinitionName] = set()
|
352
352
|
|
353
353
|
for slot in self.schema.slots.values():
|
354
354
|
if not slot.domain:
|