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/utils/generator.py
CHANGED
@@ -21,34 +21,36 @@ import re
|
|
21
21
|
import sys
|
22
22
|
from contextlib import redirect_stdout
|
23
23
|
from dataclasses import dataclass, field
|
24
|
+
from functools import lru_cache
|
24
25
|
from io import StringIO
|
25
26
|
from pathlib import Path
|
26
|
-
from typing import
|
27
|
-
cast, Mapping, ClassVar)
|
27
|
+
from typing import Callable, ClassVar, Dict, List, Mapping, Optional, Set, TextIO, Type, Union, cast
|
28
28
|
|
29
29
|
import click
|
30
30
|
from click import Argument, Command, Option
|
31
31
|
from linkml_runtime import SchemaView
|
32
32
|
from linkml_runtime.dumpers import yaml_dumper
|
33
|
-
from linkml_runtime.linkml_model.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
33
|
+
from linkml_runtime.linkml_model.meta import (
|
34
|
+
ClassDefinition,
|
35
|
+
ClassDefinitionName,
|
36
|
+
Definition,
|
37
|
+
Element,
|
38
|
+
ElementName,
|
39
|
+
EnumDefinition,
|
40
|
+
EnumDefinitionName,
|
41
|
+
PrefixPrefixPrefix,
|
42
|
+
SchemaDefinition,
|
43
|
+
SlotDefinition,
|
44
|
+
SlotDefinitionName,
|
45
|
+
SubsetDefinition,
|
46
|
+
SubsetDefinitionName,
|
47
|
+
TypeDefinition,
|
48
|
+
TypeDefinitionName,
|
49
|
+
)
|
47
50
|
from linkml_runtime.utils.formatutils import camelcase, underscore
|
48
|
-
from linkml_runtime.utils.introspection import package_schemaview
|
49
51
|
from linkml_runtime.utils.namespaces import Namespaces
|
50
52
|
|
51
|
-
from linkml import LOCAL_METAMODEL_YAML_FILE
|
53
|
+
from linkml import LOCAL_METAMODEL_YAML_FILE
|
52
54
|
from linkml.utils.mergeutils import alias_root
|
53
55
|
from linkml.utils.schemaloader import SchemaLoader
|
54
56
|
from linkml.utils.typereferences import References
|
@@ -57,6 +59,23 @@ DEFAULT_LOG_LEVEL: str = "WARNING"
|
|
57
59
|
DEFAULT_LOG_LEVEL_INT: int = logging.WARNING
|
58
60
|
|
59
61
|
|
62
|
+
@lru_cache
|
63
|
+
def _resolved_metamodel(mergeimports):
|
64
|
+
if not os.path.exists(LOCAL_METAMODEL_YAML_FILE):
|
65
|
+
raise AssertionError(f"{LOCAL_METAMODEL_YAML_FILE} not found")
|
66
|
+
|
67
|
+
base_dir = str(Path(str(LOCAL_METAMODEL_YAML_FILE)).parent)
|
68
|
+
logging.debug(f"BASE={base_dir}")
|
69
|
+
metamodel = SchemaLoader(
|
70
|
+
LOCAL_METAMODEL_YAML_FILE,
|
71
|
+
importmap={"linkml": base_dir},
|
72
|
+
base_dir=base_dir,
|
73
|
+
mergeimports=mergeimports,
|
74
|
+
)
|
75
|
+
metamodel.resolve()
|
76
|
+
return metamodel
|
77
|
+
|
78
|
+
|
60
79
|
@dataclass
|
61
80
|
class Generator(metaclass=abc.ABCMeta):
|
62
81
|
"""
|
@@ -79,8 +98,8 @@ class Generator(metaclass=abc.ABCMeta):
|
|
79
98
|
uses_schemaloader: ClassVar[bool] = True
|
80
99
|
"""Old-style generator that uses the SchemaLoader and visitor pattern"""
|
81
100
|
|
82
|
-
#uses_schemaview: ClassVar[bool] = True
|
83
|
-
#"""New-style generator that uses SchemaView"""
|
101
|
+
# uses_schemaview: ClassVar[bool] = True
|
102
|
+
# """New-style generator that uses SchemaView"""
|
84
103
|
|
85
104
|
requires_metamodel: ClassVar[bool] = True
|
86
105
|
"""Generator queries an instance of the metamodel"""
|
@@ -104,7 +123,9 @@ class Generator(metaclass=abc.ABCMeta):
|
|
104
123
|
format: Optional[str] = None
|
105
124
|
"""expected output format"""
|
106
125
|
|
107
|
-
|
126
|
+
file_extension: ClassVar[str] = None
|
127
|
+
|
128
|
+
metadata: bool = field(default_factory=lambda: True)
|
108
129
|
"""True means include date, generator, etc. information in source header if appropriate"""
|
109
130
|
|
110
131
|
useuris: Optional[bool] = None
|
@@ -113,7 +134,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
113
134
|
log_level: int = DEFAULT_LOG_LEVEL_INT
|
114
135
|
"""Logging level, 0 is minimum"""
|
115
136
|
|
116
|
-
mergeimports: Optional[bool] = field(default_factory=
|
137
|
+
mergeimports: Optional[bool] = field(default_factory=lambda: True)
|
117
138
|
"""True means merge non-linkml sources into importing package. False means separate packages"""
|
118
139
|
|
119
140
|
source_file_date: Optional[str] = None
|
@@ -129,7 +150,8 @@ class Generator(metaclass=abc.ABCMeta):
|
|
129
150
|
"""Verbosity"""
|
130
151
|
|
131
152
|
output: Optional[str] = None
|
132
|
-
"""Path to output file. Note all generators may not implement this
|
153
|
+
"""Path to output file. Note all generators may not implement this
|
154
|
+
uniformly, see https://github.com/linkml/linkml/issues/923"""
|
133
155
|
|
134
156
|
namespaces: Optional[Namespaces] = None
|
135
157
|
"""All prefix expansions used"""
|
@@ -140,9 +162,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
140
162
|
base_dir: str = None # Base directory of schema
|
141
163
|
"""Working directory or base URL of sources"""
|
142
164
|
|
143
|
-
metamodel_name_map: Dict[
|
144
|
-
str, str
|
145
|
-
] = None
|
165
|
+
metamodel_name_map: Dict[str, str] = None
|
146
166
|
"""Allows mapping of names of metamodel elements such as slot, etc"""
|
147
167
|
|
148
168
|
importmap: Optional[Union[str, Optional[Mapping[str, str]]]] = None
|
@@ -170,29 +190,21 @@ class Generator(metaclass=abc.ABCMeta):
|
|
170
190
|
self.source_file_date = None
|
171
191
|
self.source_file_size = None
|
172
192
|
if self.requires_metamodel:
|
173
|
-
|
174
|
-
base_dir = str(Path(str(LOCAL_METAMODEL_YAML_FILE)).parent)
|
175
|
-
logging.debug(f"BASE={base_dir}")
|
176
|
-
self.metamodel = SchemaLoader(
|
177
|
-
LOCAL_METAMODEL_YAML_FILE,
|
178
|
-
importmap={"linkml": base_dir},
|
179
|
-
base_dir=base_dir,
|
180
|
-
mergeimports=self.mergeimports,
|
181
|
-
)
|
182
|
-
else:
|
183
|
-
raise AssertionError(f"{LOCAL_METAMODEL_YAML_FILE} not found")
|
184
|
-
self.metamodel.resolve()
|
193
|
+
self.metamodel = _resolved_metamodel(self.mergeimports)
|
185
194
|
schema = self.schema
|
186
195
|
# TODO: remove aliasing
|
187
196
|
self.emit_metadata = self.metadata
|
188
197
|
if self.uses_schemaloader:
|
189
198
|
self._initialize_using_schemaloader(schema)
|
190
199
|
else:
|
191
|
-
|
200
|
+
logging.info(f"Using SchemaView with im={self.importmap}")
|
201
|
+
self.schemaview = SchemaView(schema, importmap=self.importmap)
|
192
202
|
self.schema = self.schemaview.schema
|
193
203
|
self._init_namespaces()
|
194
204
|
|
195
|
-
def _initialize_using_schemaloader(
|
205
|
+
def _initialize_using_schemaloader(
|
206
|
+
self, schema: Union[str, TextIO, SchemaDefinition, "Generator"]
|
207
|
+
):
|
196
208
|
# currently generators are very liberal in what they accept, including
|
197
209
|
# other generators.
|
198
210
|
# See https://github.com/linkml/linkml/issues/923 for discussion on how
|
@@ -251,7 +263,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
251
263
|
"""
|
252
264
|
Generate output in the required format
|
253
265
|
|
254
|
-
:param kwargs:
|
266
|
+
:param kwargs: Generator specific parameters
|
255
267
|
:return: Generated output
|
256
268
|
"""
|
257
269
|
output = StringIO()
|
@@ -294,9 +306,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
294
306
|
):
|
295
307
|
if self.visit_class(cls):
|
296
308
|
for slot in (
|
297
|
-
self.all_slots(cls)
|
298
|
-
if self.visit_all_class_slots
|
299
|
-
else self.own_slots(cls)
|
309
|
+
self.all_slots(cls) if self.visit_all_class_slots else self.own_slots(cls)
|
300
310
|
):
|
301
311
|
self.visit_class_slot(cls, self.aliased_slot_name(slot), slot)
|
302
312
|
self.end_class(cls)
|
@@ -376,9 +386,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
376
386
|
# =============================
|
377
387
|
# Helper methods
|
378
388
|
# =============================
|
379
|
-
def own_slots(
|
380
|
-
self, cls: Union[ClassDefinitionName, ClassDefinition]
|
381
|
-
) -> List[SlotDefinition]:
|
389
|
+
def own_slots(self, cls: Union[ClassDefinitionName, ClassDefinition]) -> List[SlotDefinition]:
|
382
390
|
"""Return the list of slots owned the class definition. An "own slot" is any ``cls`` slot that does not appear
|
383
391
|
in the class is_a parent. Own_slots include:
|
384
392
|
|
@@ -440,9 +448,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
440
448
|
rval.append(slot)
|
441
449
|
seen.add(sname_base)
|
442
450
|
return rval + (
|
443
|
-
self.all_slots(parent, cls_slots_first=cls_slots_first, seen=seen)
|
444
|
-
if parent
|
445
|
-
else []
|
451
|
+
self.all_slots(parent, cls_slots_first=cls_slots_first, seen=seen) if parent else []
|
446
452
|
)
|
447
453
|
else:
|
448
454
|
for sname in cls.slots:
|
@@ -465,9 +471,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
465
471
|
else self.schema.slots[element.is_a]
|
466
472
|
)
|
467
473
|
|
468
|
-
def ancestors(
|
469
|
-
self, element: Union[ClassDefinition, SlotDefinition]
|
470
|
-
) -> List[ElementName]:
|
474
|
+
def ancestors(self, element: Union[ClassDefinition, SlotDefinition]) -> List[ElementName]:
|
471
475
|
"""Return an ordered list of ancestor names for the supplied slot or class
|
472
476
|
|
473
477
|
@param element: Slot or class name or definition
|
@@ -477,9 +481,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
477
481
|
[] if element.is_a is None else self.ancestors(self.parent(element))
|
478
482
|
)
|
479
483
|
|
480
|
-
def neighborhood(
|
481
|
-
self, elements: Union[str, ElementName, List[ElementName]]
|
482
|
-
) -> References:
|
484
|
+
def neighborhood(self, elements: Union[str, ElementName, List[ElementName]]) -> References:
|
483
485
|
"""Return a list of all slots, classes and types that touch any element in elements, including the element
|
484
486
|
itself
|
485
487
|
|
@@ -549,9 +551,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
549
551
|
if element in self.schema.subsets:
|
550
552
|
touches.subsetrefs.add(cast(SubsetDefinitionName, element))
|
551
553
|
if element in self.synopsis.subsetrefs:
|
552
|
-
touches.update(
|
553
|
-
self.synopsis.subsetrefs[cast(SubsetDefinitionName, element)]
|
554
|
-
)
|
554
|
+
touches.update(self.synopsis.subsetrefs[cast(SubsetDefinitionName, element)])
|
555
555
|
if not bool(touches):
|
556
556
|
self.logger.warning(f"neighborhood({element}) - {element} is undefined")
|
557
557
|
|
@@ -566,9 +566,9 @@ class Generator(metaclass=abc.ABCMeta):
|
|
566
566
|
"""
|
567
567
|
formatted_typ_name = self.class_or_type_name(typ.name)
|
568
568
|
if typ.typeof:
|
569
|
-
return self.range_type_path(
|
570
|
-
|
571
|
-
|
569
|
+
return self.range_type_path(self.schema.types[cast(TypeDefinitionName, typ.typeof)]) + [
|
570
|
+
formatted_typ_name
|
571
|
+
]
|
572
572
|
elif typ.repr:
|
573
573
|
return [typ.repr, formatted_typ_name]
|
574
574
|
else:
|
@@ -595,9 +595,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
595
595
|
return slotname
|
596
596
|
return None
|
597
597
|
|
598
|
-
def enum_identifier_path(
|
599
|
-
self, enum_or_enumname: Union[str, EnumDefinition]
|
600
|
-
) -> List[str]:
|
598
|
+
def enum_identifier_path(self, enum_or_enumname: Union[str, EnumDefinition]) -> List[str]:
|
601
599
|
"""Return an enum_identifier path"""
|
602
600
|
return [
|
603
601
|
"str",
|
@@ -656,9 +654,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
656
654
|
)
|
657
655
|
if slot.range in self.schema.types:
|
658
656
|
# Type
|
659
|
-
return self.range_type_path(
|
660
|
-
self.schema.types[cast(TypeDefinitionName, slot.range)]
|
661
|
-
)
|
657
|
+
return self.range_type_path(self.schema.types[cast(TypeDefinitionName, slot.range)])
|
662
658
|
elif slot.range in self.schema.enums:
|
663
659
|
return self.enum_identifier_path(slot.range)
|
664
660
|
else:
|
@@ -758,9 +754,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
758
754
|
else:
|
759
755
|
return None
|
760
756
|
|
761
|
-
def obj_for(
|
762
|
-
self, el_or_elname: str, is_range_name: bool = False
|
763
|
-
) -> Optional[Element]:
|
757
|
+
def obj_for(self, el_or_elname: str, is_range_name: bool = False) -> Optional[Element]:
|
764
758
|
if is_range_name:
|
765
759
|
return (
|
766
760
|
self.class_or_type_for(el_or_elname)
|
@@ -794,8 +788,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
794
788
|
return [
|
795
789
|
slot
|
796
790
|
for slot in [self.schema.slots[sn] for sn in cls.slots]
|
797
|
-
if cls.name in slot.domain_of
|
798
|
-
or (set(cls.mixins).intersection(slot.domain_of))
|
791
|
+
if cls.name in slot.domain_of or (set(cls.mixins).intersection(slot.domain_of))
|
799
792
|
]
|
800
793
|
|
801
794
|
def add_mappings(self, defn: Definition) -> None:
|
@@ -826,9 +819,7 @@ class Generator(metaclass=abc.ABCMeta):
|
|
826
819
|
else:
|
827
820
|
mapping = mcurie
|
828
821
|
if ":" not in mapping or len(mapping.split(":")) != 2:
|
829
|
-
raise ValueError(
|
830
|
-
f"Definition {defn.name} - unrecognized mapping: {mapping}"
|
831
|
-
)
|
822
|
+
raise ValueError(f"Definition {defn.name} - unrecognized mapping: {mapping}")
|
832
823
|
ns = mapping.split(":")[0]
|
833
824
|
logging.debug(f"Adding {ns} from {mapping} // {defn}")
|
834
825
|
if ns:
|
@@ -873,18 +864,13 @@ class Generator(metaclass=abc.ABCMeta):
|
|
873
864
|
slot_name_normalized_singular = re.sub(r"s$", "", slot_name_normalized)
|
874
865
|
if slot_name_normalized == "classes":
|
875
866
|
slot_name_normalized_singular = "class"
|
876
|
-
if
|
877
|
-
self.metamodel_name_map is not None
|
878
|
-
and slot_name_normalized in self.metamodel_name_map
|
879
|
-
):
|
867
|
+
if self.metamodel_name_map is not None and slot_name_normalized in self.metamodel_name_map:
|
880
868
|
return capitalize(self.metamodel_name_map[slot_name_normalized])
|
881
869
|
elif (
|
882
870
|
self.metamodel_name_map is not None
|
883
871
|
and slot_name_normalized_singular in self.metamodel_name_map
|
884
872
|
):
|
885
|
-
return capitalize(
|
886
|
-
f"{self.metamodel_name_map[slot_name_normalized_singular]}s"
|
887
|
-
)
|
873
|
+
return capitalize(f"{self.metamodel_name_map[slot_name_normalized_singular]}s")
|
888
874
|
else:
|
889
875
|
return slot_name
|
890
876
|
|
@@ -926,16 +912,14 @@ def shared_arguments(g: Type[Generator]) -> Callable[[Command], Command]:
|
|
926
912
|
logging.basicConfig(level=_log_level_string_to_int(value))
|
927
913
|
|
928
914
|
def decorator(f: Command) -> Command:
|
929
|
-
f.params.append(
|
930
|
-
Argument(("yamlfile",), type=click.Path(exists=True, dir_okay=False))
|
931
|
-
)
|
915
|
+
f.params.append(Argument(("yamlfile",), type=click.Path(exists=True, dir_okay=False)))
|
932
916
|
f.params.append(
|
933
917
|
Option(
|
934
918
|
("--format", "-f"),
|
935
919
|
type=click.Choice(g.valid_formats),
|
936
920
|
default=g.valid_formats[0],
|
937
921
|
show_default=True,
|
938
|
-
help=
|
922
|
+
help="Output format",
|
939
923
|
)
|
940
924
|
)
|
941
925
|
f.params.append(
|
@@ -955,15 +939,13 @@ def shared_arguments(g: Type[Generator]) -> Callable[[Command], Command]:
|
|
955
939
|
)
|
956
940
|
)
|
957
941
|
f.params.append(
|
958
|
-
Option(
|
959
|
-
("--importmap", "-im"), type=click.File(), help="Import mapping file"
|
960
|
-
)
|
942
|
+
Option(("--importmap", "-im"), type=click.File(), help="Import mapping file")
|
961
943
|
)
|
962
944
|
f.params.append(
|
963
945
|
Option(
|
964
946
|
("--log_level",),
|
965
947
|
type=click.Choice(_LOG_LEVEL_STRINGS),
|
966
|
-
help=
|
948
|
+
help="Logging level",
|
967
949
|
default=DEFAULT_LOG_LEVEL,
|
968
950
|
show_default=True,
|
969
951
|
callback=log_level_callback,
|
@@ -973,7 +955,7 @@ def shared_arguments(g: Type[Generator]) -> Callable[[Command], Command]:
|
|
973
955
|
Option(
|
974
956
|
("--verbose", "-v"),
|
975
957
|
count=True,
|
976
|
-
help=
|
958
|
+
help="verbosity",
|
977
959
|
callback=verbosity_callback,
|
978
960
|
)
|
979
961
|
)
|
linkml/utils/helpers.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import re
|
2
2
|
|
3
|
+
|
3
4
|
def remove_duplicates(lst):
|
4
5
|
"""Remove duplicate tuples from a list of tuples."""
|
5
6
|
return [t for t in (set(tuple(i) for i in lst))]
|
@@ -9,6 +10,7 @@ def write_to_file(file_path, data, mode="w", encoding="utf-8"):
|
|
9
10
|
with open(file_path, mode, encoding=encoding) as f:
|
10
11
|
f.write(data)
|
11
12
|
|
13
|
+
|
12
14
|
def convert_to_snake_case(str):
|
13
|
-
str = re.sub(r"(?<=[a-z])(?=[A-Z])|[^a-zA-Z]", " ", str).strip().replace(
|
14
|
-
return
|
15
|
+
str = re.sub(r"(?<=[a-z])(?=[A-Z])|[^a-zA-Z]", " ", str).strip().replace(" ", "_")
|
16
|
+
return "".join(str.lower())
|
@@ -13,20 +13,12 @@ def strval(txt: str) -> str:
|
|
13
13
|
|
14
14
|
|
15
15
|
def default_uri_for(loader: SchemaLoader) -> str:
|
16
|
-
dflt = (
|
17
|
-
loader.schema.default_prefix
|
18
|
-
if loader.schema.default_prefix
|
19
|
-
else sfx(loader.schema.id)
|
20
|
-
)
|
16
|
+
dflt = loader.schema.default_prefix if loader.schema.default_prefix else sfx(loader.schema.id)
|
21
17
|
return sfx(loader.namespaces.uri_for(dflt))
|
22
18
|
|
23
19
|
|
24
20
|
def default_curie_or_uri(loader: SchemaLoader) -> str:
|
25
|
-
dflt = (
|
26
|
-
loader.schema.default_prefix
|
27
|
-
if loader.schema.default_prefix
|
28
|
-
else sfx(loader.schema.id)
|
29
|
-
)
|
21
|
+
dflt = loader.schema.default_prefix if loader.schema.default_prefix else sfx(loader.schema.id)
|
30
22
|
if ":/" in dflt:
|
31
23
|
prefix = loader.namespaces.prefix_for(loader.schema.default_prefix)
|
32
24
|
if prefix:
|
@@ -65,6 +57,7 @@ def default_ns_for(loader: SchemaLoader, cls: ClassDefinition) -> str:
|
|
65
57
|
# cls_id = slotname
|
66
58
|
# return f"sfx(str(self.{cls_id}))" if cls_id else "None"
|
67
59
|
|
60
|
+
|
68
61
|
# Library of named default values -- this is here to prevent code injection
|
69
62
|
# Contents: Match text (as re),
|
70
63
|
# flag that indicates whether we're generating a default value expression or postinig code
|
@@ -80,12 +73,27 @@ default_library: List[
|
|
80
73
|
(r"[Tt]rue", False, lambda _, __, ___, ____: "True"),
|
81
74
|
(r"[Ff]alse", False, lambda _, __, ___, ____: "False"),
|
82
75
|
(r"int\(([-+]?[0-9]+)\)", False, lambda m, __, ___, ____: int(m[1])),
|
83
|
-
(
|
84
|
-
|
85
|
-
|
76
|
+
(
|
77
|
+
r"float\(([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\)",
|
78
|
+
False,
|
79
|
+
lambda m, __, ___, ____: float(m[1]),
|
80
|
+
),
|
81
|
+
(
|
82
|
+
r"date\((\d{4})-(\d{2})-(\d{2})\)",
|
83
|
+
False,
|
84
|
+
lambda m, __, ___, ____: f"datetime.date({m[1]}, {m[2]}, {m[3]})",
|
85
|
+
),
|
86
|
+
(
|
87
|
+
r"datetime\((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\)",
|
88
|
+
False,
|
89
|
+
lambda m, __, ___, ____: f"datetime.datetime({m[1]}, {m[2]}, {m[3]}, {m[4]}, {m[5]}, {m[6]})",
|
90
|
+
),
|
86
91
|
# TODO: We have to make the real URI available before any of these can work
|
87
|
-
# ("class_uri", True,
|
88
|
-
#
|
92
|
+
# ("class_uri", True,
|
93
|
+
# lambda _, loader, ___, ____: f'"{default_uri_for(loader)}" + camelcase(self.name)'),
|
94
|
+
# ("slot_uri", True,
|
95
|
+
# lambda _, loader, ___, ____: f'"{default_uri_for(loader)}" +
|
96
|
+
# underscore(self.alias if self.alias else self.name)'),
|
89
97
|
# ("class_curie", True, lambda _, loader, ___, ____: curie_for(loader, True)),
|
90
98
|
# ("slot_curie", True, lambda _, loader, ___, ____: curie_for(loader, False)),
|
91
99
|
("class_uri", True, lambda _, loader, ___, ____: "None"),
|
linkml/utils/mergeutils.py
CHANGED
@@ -3,12 +3,16 @@ import logging
|
|
3
3
|
from copy import deepcopy
|
4
4
|
from typing import Dict, List, Optional, Union, cast
|
5
5
|
|
6
|
-
from linkml_runtime.linkml_model.meta import (
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
from linkml_runtime.linkml_model.meta import (
|
7
|
+
ClassDefinition,
|
8
|
+
Element,
|
9
|
+
EnumDefinition,
|
10
|
+
SchemaDefinition,
|
11
|
+
SlotDefinition,
|
12
|
+
SlotDefinitionName,
|
13
|
+
TypeDefinition,
|
14
|
+
TypeDefinitionName,
|
15
|
+
)
|
12
16
|
from linkml_runtime.utils.formatutils import camelcase, underscore
|
13
17
|
from linkml_runtime.utils.namespaces import Namespaces
|
14
18
|
from linkml_runtime.utils.yamlutils import extended_str
|
@@ -45,26 +49,14 @@ def merge_schemas(
|
|
45
49
|
imported_from_uri = imported_from
|
46
50
|
else:
|
47
51
|
imported_from_uri = namespaces.uri_for(imported_from)
|
48
|
-
merge_dicts(
|
49
|
-
|
50
|
-
)
|
51
|
-
merge_dicts(
|
52
|
-
|
53
|
-
)
|
54
|
-
merge_dicts(
|
55
|
-
target.types, mergee.types, imported_from, imported_from_uri, merge_imports
|
56
|
-
)
|
57
|
-
merge_dicts(
|
58
|
-
target.subsets, mergee.subsets, imported_from, imported_from_uri, merge_imports
|
59
|
-
)
|
60
|
-
merge_dicts(
|
61
|
-
target.enums, mergee.enums, imported_from, imported_from_uri, merge_imports
|
62
|
-
)
|
52
|
+
merge_dicts(target.classes, mergee.classes, imported_from, imported_from_uri, merge_imports)
|
53
|
+
merge_dicts(target.slots, mergee.slots, imported_from, imported_from_uri, merge_imports)
|
54
|
+
merge_dicts(target.types, mergee.types, imported_from, imported_from_uri, merge_imports)
|
55
|
+
merge_dicts(target.subsets, mergee.subsets, imported_from, imported_from_uri, merge_imports)
|
56
|
+
merge_dicts(target.enums, mergee.enums, imported_from, imported_from_uri, merge_imports)
|
63
57
|
|
64
58
|
|
65
|
-
def merge_namespaces(
|
66
|
-
target: SchemaDefinition, mergee: SchemaDefinition, namespaces
|
67
|
-
) -> None:
|
59
|
+
def merge_namespaces(target: SchemaDefinition, mergee: SchemaDefinition, namespaces) -> None:
|
68
60
|
"""
|
69
61
|
Add the mergee namespace definitions to target
|
70
62
|
|
@@ -74,7 +66,6 @@ def merge_namespaces(
|
|
74
66
|
:return:
|
75
67
|
"""
|
76
68
|
for prefix in mergee.prefixes.values():
|
77
|
-
|
78
69
|
# Handle local prefixes special: we assume that these happen because we are in different (levels of) folders,
|
79
70
|
# and we assume that they reference the same linkml file
|
80
71
|
if "://" not in prefix.prefix_reference:
|
@@ -110,8 +101,7 @@ def merge_namespaces(
|
|
110
101
|
# target.prefixes[prefix.prefix_prefix] = prefix
|
111
102
|
if (
|
112
103
|
prefix.prefix_prefix in target.prefixes
|
113
|
-
and target.prefixes[prefix.prefix_prefix].prefix_reference
|
114
|
-
!= prefix.prefix_reference
|
104
|
+
and target.prefixes[prefix.prefix_prefix].prefix_reference != prefix.prefix_reference
|
115
105
|
):
|
116
106
|
raise ValueError(
|
117
107
|
f"Prefix: {prefix.prefix_prefix} mismatch between {target.name} and {mergee.name}"
|
@@ -177,11 +167,7 @@ def merge_slots(
|
|
177
167
|
if skip is None:
|
178
168
|
skip = []
|
179
169
|
for k, v in dataclasses.asdict(source).items():
|
180
|
-
if (
|
181
|
-
k not in skip
|
182
|
-
and v is not None
|
183
|
-
and (not inheriting or getattr(target, k, None) is None)
|
184
|
-
):
|
170
|
+
if k not in skip and v is not None and (not inheriting or getattr(target, k, None) is None):
|
185
171
|
if k in source._inherited_slots or not inheriting:
|
186
172
|
setattr(target, k, deepcopy(v))
|
187
173
|
else:
|
@@ -234,9 +220,7 @@ def merge_classes(
|
|
234
220
|
if slotbase in target.slot_usage:
|
235
221
|
slotname = slot_usage_name(slotbase, target)
|
236
222
|
if slotbase not in target_base_slots:
|
237
|
-
target.slots.append(slotname) if at_end else target.slots.insert(
|
238
|
-
0, slotname
|
239
|
-
)
|
223
|
+
target.slots.append(slotname) if at_end else target.slots.insert(0, slotname)
|
240
224
|
target_base_slots.add(slotbase)
|
241
225
|
|
242
226
|
|
linkml/utils/rawloader.py
CHANGED
@@ -6,9 +6,7 @@ from urllib.parse import urlparse
|
|
6
6
|
import yaml
|
7
7
|
from dateutil.parser import ParserError, parse
|
8
8
|
from hbreader import FileInfo, HBType, detect_type
|
9
|
-
from linkml_runtime.linkml_model.meta import
|
10
|
-
SchemaDefinition, SlotDefinition,
|
11
|
-
metamodel_version)
|
9
|
+
from linkml_runtime.linkml_model.meta import SchemaDefinition, metamodel_version
|
12
10
|
from linkml_runtime.loaders import yaml_loader
|
13
11
|
from linkml_runtime.utils.yamlutils import YAMLMark, YAMLRoot
|
14
12
|
|
@@ -56,9 +54,7 @@ def load_raw_schema(
|
|
56
54
|
|
57
55
|
# Passing a URL or file name
|
58
56
|
if detect_type(data, base_dir) not in (HBType.STRING, HBType.STRINGABLE):
|
59
|
-
assert
|
60
|
-
source_file is None
|
61
|
-
), "source_file parameter not allowed if data is a file or URL"
|
57
|
+
assert source_file is None, "source_file parameter not allowed if data is a file or URL"
|
62
58
|
assert (
|
63
59
|
source_file_date is None
|
64
60
|
), "source_file_date parameter not allowed if data is a file or URL"
|
linkml/utils/schema_builder.py
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
-
from dataclasses import dataclass
|
2
|
-
from typing import Dict, List,
|
3
|
-
|
4
|
-
from linkml_runtime.linkml_model import (
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Dict, List, Optional, Union
|
3
|
+
|
4
|
+
from linkml_runtime.linkml_model import (
|
5
|
+
ClassDefinition,
|
6
|
+
EnumDefinition,
|
7
|
+
PermissibleValue,
|
8
|
+
Prefix,
|
9
|
+
SchemaDefinition,
|
10
|
+
SlotDefinition,
|
11
|
+
TypeDefinition,
|
12
|
+
)
|
13
|
+
from linkml_runtime.utils.formatutils import underscore
|
9
14
|
from linkml_runtime.utils.schema_as_dict import schema_as_dict
|
10
15
|
|
11
16
|
|
@@ -69,6 +74,7 @@ class SchemaBuilder:
|
|
69
74
|
:param slot_usage: slots keyed by slot name
|
70
75
|
:param replace_if_present: if True, replace existing class if present
|
71
76
|
:param kwargs: additional ClassDefinition properties
|
77
|
+
:param use_attributes: if True, add slots as attributes
|
72
78
|
:return: builder
|
73
79
|
:raises ValueError: if class already exists and replace_if_present=False
|
74
80
|
"""
|
@@ -83,10 +89,12 @@ class SchemaBuilder:
|
|
83
89
|
for s in slots:
|
84
90
|
if isinstance(s, SlotDefinition):
|
85
91
|
cls.attributes[s.name] = s
|
92
|
+
elif isinstance(s, dict):
|
93
|
+
cls.attributes[s["name"]] = s
|
94
|
+
elif isinstance(s, str):
|
95
|
+
cls.attributes[s] = SlotDefinition(s)
|
86
96
|
else:
|
87
|
-
raise ValueError(
|
88
|
-
f"If use_attributes=True then slots must be SlotDefinitions"
|
89
|
-
)
|
97
|
+
raise ValueError("If use_attributes=True then slots must be SlotDefinitions")
|
90
98
|
else:
|
91
99
|
if slots is not None:
|
92
100
|
for s in slots:
|
@@ -103,7 +111,11 @@ class SchemaBuilder:
|
|
103
111
|
return self
|
104
112
|
|
105
113
|
def add_slot(
|
106
|
-
self,
|
114
|
+
self,
|
115
|
+
slot: Union[SlotDefinition, Dict, str],
|
116
|
+
class_name: str = None,
|
117
|
+
replace_if_present=False,
|
118
|
+
**kwargs,
|
107
119
|
) -> "SchemaBuilder":
|
108
120
|
"""
|
109
121
|
Adds the slot to the schema.
|
@@ -170,7 +182,7 @@ class SchemaBuilder:
|
|
170
182
|
enum_def.permissible_values[pv.text] = pv
|
171
183
|
return self
|
172
184
|
|
173
|
-
def add_prefix(self, prefix: str, url: str, replace_if_present
|
185
|
+
def add_prefix(self, prefix: str, url: str, replace_if_present=False) -> "SchemaBuilder":
|
174
186
|
"""
|
175
187
|
Adds a prefix for use with CURIEs
|
176
188
|
|
@@ -205,12 +217,12 @@ class SchemaBuilder:
|
|
205
217
|
return self
|
206
218
|
|
207
219
|
def add_type(
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
220
|
+
self,
|
221
|
+
type: Union[TypeDefinition, Dict, str],
|
222
|
+
typeof: str = None,
|
223
|
+
uri: str = None,
|
224
|
+
replace_if_present=False,
|
225
|
+
**kwargs,
|
214
226
|
) -> "SchemaBuilder":
|
215
227
|
"""
|
216
228
|
Adds the type to the schema
|