linkml 1.8.3__py3-none-any.whl → 1.8.5__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/__init__.py +2 -0
- linkml/generators/common/ifabsent_processor.py +98 -21
- linkml/generators/common/naming.py +106 -0
- linkml/generators/docgen/index.md.jinja2 +6 -6
- linkml/generators/docgen.py +80 -21
- linkml/generators/golanggen.py +3 -1
- linkml/generators/graphqlgen.py +34 -2
- linkml/generators/jsonschemagen.py +4 -2
- linkml/generators/owlgen.py +36 -17
- linkml/generators/projectgen.py +13 -11
- linkml/generators/pydanticgen/array.py +340 -56
- linkml/generators/pydanticgen/build.py +4 -2
- linkml/generators/pydanticgen/pydanticgen.py +35 -16
- linkml/generators/pydanticgen/template.py +119 -3
- linkml/generators/pydanticgen/templates/imports.py.jinja +11 -3
- linkml/generators/pydanticgen/templates/module.py.jinja +1 -3
- linkml/generators/pydanticgen/templates/validator.py.jinja +2 -2
- linkml/generators/python/python_ifabsent_processor.py +1 -1
- linkml/generators/pythongen.py +135 -31
- linkml/generators/shaclgen.py +34 -10
- linkml/generators/sparqlgen.py +3 -1
- linkml/generators/sqlalchemygen.py +5 -3
- linkml/generators/sqltablegen.py +4 -2
- linkml/generators/typescriptgen.py +13 -6
- linkml/linter/linter.py +2 -1
- linkml/transformers/logical_model_transformer.py +3 -3
- linkml/transformers/relmodel_transformer.py +18 -4
- linkml/utils/converter.py +3 -1
- linkml/utils/exceptions.py +11 -0
- linkml/utils/execute_tutorial.py +22 -20
- linkml/utils/generator.py +6 -4
- linkml/utils/mergeutils.py +4 -2
- linkml/utils/schema_fixer.py +5 -5
- linkml/utils/schemaloader.py +5 -3
- linkml/utils/sqlutils.py +3 -1
- linkml/validator/plugins/pydantic_validation_plugin.py +1 -1
- linkml/validators/jsonschemavalidator.py +3 -1
- linkml/validators/sparqlvalidator.py +5 -3
- linkml/workspaces/example_runner.py +3 -1
- {linkml-1.8.3.dist-info → linkml-1.8.5.dist-info}/METADATA +3 -1
- {linkml-1.8.3.dist-info → linkml-1.8.5.dist-info}/RECORD +44 -42
- {linkml-1.8.3.dist-info → linkml-1.8.5.dist-info}/LICENSE +0 -0
- {linkml-1.8.3.dist-info → linkml-1.8.5.dist-info}/WHEEL +0 -0
- {linkml-1.8.3.dist-info → linkml-1.8.5.dist-info}/entry_points.txt +0 -0
linkml/generators/graphqlgen.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
+
import logging
|
1
2
|
import os
|
3
|
+
import re
|
2
4
|
from dataclasses import dataclass
|
3
5
|
|
4
6
|
import click
|
5
|
-
from linkml_runtime.linkml_model.meta import ClassDefinition, SlotDefinition
|
7
|
+
from linkml_runtime.linkml_model.meta import ClassDefinition, EnumDefinition, SlotDefinition
|
6
8
|
from linkml_runtime.utils.formatutils import camelcase, lcamelcase
|
7
9
|
|
8
10
|
from linkml._version import __version__
|
11
|
+
from linkml.generators.common.naming import NameCompatibility, NamingProfiles
|
9
12
|
from linkml.utils.generator import Generator, shared_arguments
|
10
13
|
|
11
14
|
|
@@ -19,6 +22,13 @@ class GraphqlGenerator(Generator):
|
|
19
22
|
uses_schemaloader = True
|
20
23
|
requires_metamodel = False
|
21
24
|
|
25
|
+
strict_naming: bool = False
|
26
|
+
_permissible_value_valid_characters = re.compile("^[_A-Za-z][_0-9A-Za-z]*?$")
|
27
|
+
|
28
|
+
def __post_init__(self):
|
29
|
+
self.name_compatiblity = NameCompatibility(profile=NamingProfiles.graphql, do_not_fix=self.strict_naming)
|
30
|
+
super().__post_init__()
|
31
|
+
|
22
32
|
def visit_schema(self, **kwargs) -> str:
|
23
33
|
return self.generate_header()
|
24
34
|
|
@@ -50,13 +60,35 @@ class GraphqlGenerator(Generator):
|
|
50
60
|
slotrange = slotrange + "!"
|
51
61
|
return f"\n {lcamelcase(aliased_slot_name)}: {slotrange}"
|
52
62
|
|
63
|
+
def visit_enum(self, enum: EnumDefinition):
|
64
|
+
if enum.permissible_values:
|
65
|
+
permissible_values = []
|
66
|
+
for value in enum.permissible_values:
|
67
|
+
permissible_values.append(self.name_compatiblity.compatible(value))
|
68
|
+
values = "\n ".join(permissible_values)
|
69
|
+
return f"enum {camelcase(enum.name).replace(' ','')}\n {{\n {values}\n }}\n\n"
|
70
|
+
else:
|
71
|
+
logging.warning(
|
72
|
+
f"Enumeration {enum.name} using `reachable_from` instead of `permissible_values` "
|
73
|
+
+ "to specify permissible values is not supported yet."
|
74
|
+
+ "Enumeration {enum.name} will be silently ignored!!"
|
75
|
+
)
|
76
|
+
return ""
|
77
|
+
|
53
78
|
|
54
79
|
@shared_arguments(GraphqlGenerator)
|
55
80
|
@click.command(name="graphql")
|
81
|
+
@click.option(
|
82
|
+
"--strict-naming",
|
83
|
+
is_flag=True,
|
84
|
+
show_default=True,
|
85
|
+
help="Treat warnings about invalid names or schema elements as errors.",
|
86
|
+
)
|
56
87
|
@click.version_option(__version__, "-V", "--version")
|
57
88
|
def cli(yamlfile, **args):
|
58
89
|
"""Generate graphql representation of a LinkML model"""
|
59
|
-
|
90
|
+
generator = GraphqlGenerator(yamlfile, **args)
|
91
|
+
print(generator.serialize(**args))
|
60
92
|
|
61
93
|
|
62
94
|
if __name__ == "__main__":
|
@@ -24,6 +24,8 @@ from linkml._version import __version__
|
|
24
24
|
from linkml.generators.common.type_designators import get_type_designator_value
|
25
25
|
from linkml.utils.generator import Generator, shared_arguments
|
26
26
|
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
27
29
|
# Map from underlying python data type to json equivalent
|
28
30
|
# Note: The underlying types are a union of any built-in python datatype + any type defined in
|
29
31
|
# linkml-runtime/utils/metamodelcore.py
|
@@ -222,14 +224,14 @@ class JsonSchemaGenerator(Generator):
|
|
222
224
|
|
223
225
|
def __post_init__(self):
|
224
226
|
if self.topClass:
|
225
|
-
|
227
|
+
logger.warning("topClass is deprecated - use top_class")
|
226
228
|
self.top_class = self.topClass
|
227
229
|
|
228
230
|
super().__post_init__()
|
229
231
|
|
230
232
|
if self.top_class:
|
231
233
|
if self.schemaview.get_class(self.top_class) is None:
|
232
|
-
|
234
|
+
logger.warning(f"No class in schema named {self.top_class}")
|
233
235
|
|
234
236
|
def start_schema(self, inline: bool = False) -> JsonSchema:
|
235
237
|
self.inline = inline
|
linkml/generators/owlgen.py
CHANGED
@@ -40,6 +40,8 @@ from linkml import METAMODEL_NAMESPACE_NAME
|
|
40
40
|
from linkml._version import __version__
|
41
41
|
from linkml.utils.generator import Generator, shared_arguments
|
42
42
|
|
43
|
+
logger = logging.getLogger(__name__)
|
44
|
+
|
43
45
|
OWL_TYPE = URIRef ## RDFS.Literal or OWL.Thing
|
44
46
|
|
45
47
|
SWRL = rdflib.Namespace("http://www.w3.org/2003/11/swrl#")
|
@@ -270,7 +272,7 @@ class OwlSchemaGenerator(Generator):
|
|
270
272
|
# if isinstance(v, str):
|
271
273
|
# obj = URIRef(msv.expand_curie(v))
|
272
274
|
# else:
|
273
|
-
#
|
275
|
+
# logger.debug(f"Skipping {uri} {metaslot_uri} => {v}")
|
274
276
|
else:
|
275
277
|
obj = Literal(v)
|
276
278
|
self.graph.add((uri, metaslot_uri, obj))
|
@@ -438,7 +440,7 @@ class OwlSchemaGenerator(Generator):
|
|
438
440
|
if slot:
|
439
441
|
own_slots.append(slot)
|
440
442
|
else:
|
441
|
-
|
443
|
+
logger.warning(f"Unknown top-level slot {slot_name}")
|
442
444
|
else:
|
443
445
|
own_slots = []
|
444
446
|
own_slots.extend(cls.slot_conditions.values())
|
@@ -503,9 +505,9 @@ class OwlSchemaGenerator(Generator):
|
|
503
505
|
owl_exprs.append(self._complement_of_union_of([self.transform_class_expression(x) for x in cls.none_of]))
|
504
506
|
for slot in own_slots:
|
505
507
|
if slot.name:
|
506
|
-
owltypes = self.slot_node_owltypes(sv.get_slot(slot.name))
|
508
|
+
owltypes = self.slot_node_owltypes(sv.get_slot(slot.name), owning_class=cls)
|
507
509
|
else:
|
508
|
-
owltypes = self.slot_node_owltypes(slot)
|
510
|
+
owltypes = self.slot_node_owltypes(slot, owning_class=cls)
|
509
511
|
x = self.transform_class_slot_expression(cls, slot, slot, owltypes)
|
510
512
|
if not x:
|
511
513
|
range = sv.schema.default_range
|
@@ -552,12 +554,28 @@ class OwlSchemaGenerator(Generator):
|
|
552
554
|
owl_exprs.append(self._some_values_from(slot_uri, has_member_expr))
|
553
555
|
return self._intersection_of(owl_exprs)
|
554
556
|
|
555
|
-
def slot_node_owltypes(
|
557
|
+
def slot_node_owltypes(
|
558
|
+
self,
|
559
|
+
slot: Union[SlotDefinition, AnonymousSlotExpression],
|
560
|
+
owning_class: Optional[Union[ClassDefinition, AnonymousClassExpression]] = None,
|
561
|
+
) -> Set[URIRef]:
|
562
|
+
"""
|
563
|
+
Determine the OWL types of a named slot or slot expression
|
564
|
+
|
565
|
+
The OWL type is either OWL.Thing or RDFS.Datatype
|
566
|
+
|
567
|
+
:param slot:
|
568
|
+
:param owning_class:
|
569
|
+
:return:
|
570
|
+
"""
|
556
571
|
sv = self.schemaview
|
557
572
|
node_types = set()
|
558
573
|
if isinstance(slot, SlotDefinition):
|
559
|
-
|
560
|
-
|
574
|
+
slot_range = slot.range
|
575
|
+
if isinstance(owning_class, ClassDefinition):
|
576
|
+
slot_range = sv.induced_slot(slot.name, owning_class.name).range
|
577
|
+
if slot_range in sv.all_classes():
|
578
|
+
range_class = sv.get_class(slot_range)
|
561
579
|
if not (range_class and range_class.class_uri == "linkml:Any"):
|
562
580
|
node_types.add(OWL.Thing)
|
563
581
|
if slot.range in sv.all_types():
|
@@ -565,7 +583,7 @@ class OwlSchemaGenerator(Generator):
|
|
565
583
|
for k in ["any_of", "all_of", "exactly_one_of", "none_of"]:
|
566
584
|
subslot = getattr(slot, k, None)
|
567
585
|
if subslot:
|
568
|
-
node_types.update(self.slot_node_owltypes(subslot))
|
586
|
+
node_types.update(self.slot_node_owltypes(subslot, owning_class=owning_class))
|
569
587
|
return node_types
|
570
588
|
|
571
589
|
def transform_class_slot_expression(
|
@@ -679,7 +697,7 @@ class OwlSchemaGenerator(Generator):
|
|
679
697
|
if element.equals_string is not None:
|
680
698
|
equals_string = element.equals_string
|
681
699
|
if is_literal is None:
|
682
|
-
|
700
|
+
logger.warning(f"ignoring equals_string={equals_string} as unable to tell if literal")
|
683
701
|
elif is_literal:
|
684
702
|
constraints[XSD.pattern] = equals_string
|
685
703
|
else:
|
@@ -688,7 +706,7 @@ class OwlSchemaGenerator(Generator):
|
|
688
706
|
if element.equals_string_in:
|
689
707
|
equals_string_in = element.equals_string_in
|
690
708
|
if is_literal is None:
|
691
|
-
|
709
|
+
logger.warning(f"ignoring equals_string={equals_string_in} as unable to tell if literal")
|
692
710
|
elif is_literal:
|
693
711
|
dt_exprs = [
|
694
712
|
self._datatype_restriction(XSD.string, [self._facet(XSD.pattern, s)]) for s in equals_string_in
|
@@ -754,7 +772,7 @@ class OwlSchemaGenerator(Generator):
|
|
754
772
|
if slot_uri == URIRef(att_uri):
|
755
773
|
n += 1
|
756
774
|
if n > 1:
|
757
|
-
|
775
|
+
logger.warning(f"Ambiguous attribute: {slot.name} {slot_uri}")
|
758
776
|
return
|
759
777
|
|
760
778
|
self.add_metadata(slot, slot_uri)
|
@@ -845,6 +863,7 @@ class OwlSchemaGenerator(Generator):
|
|
845
863
|
if not isinstance(v, list):
|
846
864
|
v = [v]
|
847
865
|
impls.extend(v)
|
866
|
+
impls.extend(element.implements)
|
848
867
|
for impl in impls:
|
849
868
|
if impl.startswith("owl:"):
|
850
869
|
return OWL[impl.split(":")[1]]
|
@@ -889,7 +908,7 @@ class OwlSchemaGenerator(Generator):
|
|
889
908
|
if pv_owl_type == RDFS.Literal:
|
890
909
|
pv_node = Literal(pv.text)
|
891
910
|
if pv.meaning:
|
892
|
-
|
911
|
+
logger.warning(f"Meaning on literal {pv.text} in {e.name} is ignored")
|
893
912
|
else:
|
894
913
|
pv_node = self._permissible_value_uri(pv, enum_uri, e)
|
895
914
|
pv_uris.append(pv_node)
|
@@ -950,7 +969,7 @@ class OwlSchemaGenerator(Generator):
|
|
950
969
|
def _add_rule(self, subject: Union[URIRef, BNode], rule: ClassRule, cls: ClassDefinition):
|
951
970
|
if not self.use_swrl:
|
952
971
|
return
|
953
|
-
|
972
|
+
logger.warning("SWRL support is experimental and incomplete")
|
954
973
|
head = []
|
955
974
|
body = []
|
956
975
|
for pre in rule.preconditions:
|
@@ -997,7 +1016,7 @@ class OwlSchemaGenerator(Generator):
|
|
997
1016
|
owltypes.update(x_owltypes)
|
998
1017
|
owltypes.update(current)
|
999
1018
|
if len(owltypes) > 1:
|
1000
|
-
|
1019
|
+
logger.warning(f"Multiple owl types {owltypes}")
|
1001
1020
|
# if self.target_profile == OWLProfile.dl:
|
1002
1021
|
return owltypes
|
1003
1022
|
|
@@ -1125,7 +1144,7 @@ class OwlSchemaGenerator(Generator):
|
|
1125
1144
|
) -> Optional[Union[BNode, URIRef]]:
|
1126
1145
|
graph = self.graph
|
1127
1146
|
if [x for x in exprs if x is None]:
|
1128
|
-
|
1147
|
+
logger.warning(f"Null expr in: {exprs} for {predicate} {node}")
|
1129
1148
|
exprs = [x for x in exprs if x is not None]
|
1130
1149
|
if len(exprs) == 0:
|
1131
1150
|
return None
|
@@ -1251,10 +1270,10 @@ class OwlSchemaGenerator(Generator):
|
|
1251
1270
|
return OWL.ObjectProperty
|
1252
1271
|
is_literal_vals = self.slot_is_literal_map[slot.name]
|
1253
1272
|
if len(is_literal_vals) > 1:
|
1254
|
-
|
1273
|
+
logger.warning(f"Ambiguous type for: {slot.name}")
|
1255
1274
|
if range is None:
|
1256
1275
|
if not is_literal_vals:
|
1257
|
-
|
1276
|
+
logger.warning(f"Guessing type for {slot.name}")
|
1258
1277
|
return OWL.ObjectProperty
|
1259
1278
|
if (list(is_literal_vals))[0]:
|
1260
1279
|
return OWL.DatatypeProperty
|
linkml/generators/projectgen.py
CHANGED
@@ -26,6 +26,8 @@ from linkml.generators.sqltablegen import SQLTableGenerator
|
|
26
26
|
from linkml.utils.cli_utils import log_level_option
|
27
27
|
from linkml.utils.generator import Generator
|
28
28
|
|
29
|
+
logger = logging.getLogger(__name__)
|
30
|
+
|
29
31
|
PATH_FSTRING = str
|
30
32
|
GENERATOR_NAME = str
|
31
33
|
ARG_DICT = Dict[str, Any]
|
@@ -63,14 +65,14 @@ GEN_MAP = {
|
|
63
65
|
|
64
66
|
@lru_cache()
|
65
67
|
def get_local_imports(schema_path: str, dir: str):
|
66
|
-
|
68
|
+
logger.info(f"GETTING IMPORTS = {schema_path}")
|
67
69
|
all_imports = [schema_path]
|
68
70
|
with open(schema_path) as stream:
|
69
71
|
with open(schema_path) as stream:
|
70
72
|
schema = yaml.safe_load(stream)
|
71
73
|
for imp in schema.get("imports", []):
|
72
74
|
imp_path = os.path.join(dir, imp) + ".yaml"
|
73
|
-
|
75
|
+
logger.info(f" IMP={imp} // path={imp_path}")
|
74
76
|
if os.path.isfile(imp_path):
|
75
77
|
all_imports += get_local_imports(imp_path, dir)
|
76
78
|
return all_imports
|
@@ -105,23 +107,23 @@ class ProjectGenerator:
|
|
105
107
|
all_schemas = [schema_path]
|
106
108
|
else:
|
107
109
|
all_schemas = get_local_imports(schema_path, os.path.dirname(schema_path))
|
108
|
-
|
110
|
+
logger.debug(f"ALL_SCHEMAS = {all_schemas}")
|
109
111
|
for gen_name, (gen_cls, gen_path_fmt, default_gen_args) in GEN_MAP.items():
|
110
112
|
if config.includes is not None and config.includes != [] and gen_name not in config.includes:
|
111
|
-
|
113
|
+
logger.info(f"Skipping {gen_name} as not in inclusion list: {config.includes}")
|
112
114
|
continue
|
113
115
|
if config.excludes is not None and gen_name in config.excludes:
|
114
|
-
|
116
|
+
logger.info(f"Skipping {gen_name} as it is in exclusion list")
|
115
117
|
continue
|
116
|
-
|
118
|
+
logger.info(f"Generating: {gen_name}")
|
117
119
|
for local_path in all_schemas:
|
118
|
-
|
120
|
+
logger.info(f" SCHEMA: {local_path}")
|
119
121
|
name = os.path.basename(local_path).replace(".yaml", "")
|
120
122
|
gen_path = gen_path_fmt.format(name=name)
|
121
123
|
gen_path_full = f"{config.directory}/{gen_path}"
|
122
124
|
parts = gen_path_full.split("/")
|
123
125
|
parent_dir = "/".join(parts[0:-1])
|
124
|
-
|
126
|
+
logger.info(f" PARENT={parent_dir}")
|
125
127
|
Path(parent_dir).mkdir(parents=True, exist_ok=True)
|
126
128
|
gen_path_full = "/".join(parts)
|
127
129
|
all_gen_args = {
|
@@ -143,13 +145,13 @@ class ProjectGenerator:
|
|
143
145
|
if isinstance(v, str):
|
144
146
|
v = v.format(name=name, parent=parent_dir)
|
145
147
|
serialize_args[k] = v
|
146
|
-
|
148
|
+
logger.info(f" {gen_name} ARGS: {serialize_args}")
|
147
149
|
gen_dump = gen.serialize(**serialize_args)
|
148
150
|
|
149
151
|
if gen_name != "excel":
|
150
152
|
if parts[-1] != "":
|
151
153
|
# markdowngen does not write to a file
|
152
|
-
|
154
|
+
logger.info(f" WRITING TO: {gen_path_full}")
|
153
155
|
with open(gen_path_full, "w", encoding="UTF-8") as stream:
|
154
156
|
stream.write(gen_dump)
|
155
157
|
else:
|
@@ -249,7 +251,7 @@ def cli(
|
|
249
251
|
project_config.generator_args = yaml.safe_load(generator_arguments)
|
250
252
|
except Exception:
|
251
253
|
raise Exception("Argument must be a valid YAML blob")
|
252
|
-
|
254
|
+
logger.info(f"generator args: {project_config.generator_args}")
|
253
255
|
if dir is not None:
|
254
256
|
project_config.directory = dir
|
255
257
|
project_config.mergeimports = mergeimports
|