linkml 1.8.1__py3-none-any.whl → 1.8.2__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/cli/__init__.py +0 -0
- linkml/cli/__main__.py +4 -0
- linkml/cli/main.py +126 -0
- linkml/generators/common/build.py +105 -0
- linkml/generators/common/lifecycle.py +124 -0
- linkml/generators/common/template.py +89 -0
- linkml/generators/csvgen.py +1 -1
- linkml/generators/docgen/slot.md.jinja2 +4 -0
- linkml/generators/docgen.py +1 -1
- linkml/generators/dotgen.py +1 -1
- linkml/generators/erdiagramgen.py +1 -1
- linkml/generators/excelgen.py +1 -1
- linkml/generators/golanggen.py +1 -1
- linkml/generators/golrgen.py +1 -1
- linkml/generators/graphqlgen.py +1 -1
- linkml/generators/javagen.py +1 -1
- linkml/generators/jsonldcontextgen.py +4 -4
- linkml/generators/jsonldgen.py +1 -1
- linkml/generators/jsonschemagen.py +69 -22
- linkml/generators/linkmlgen.py +1 -1
- linkml/generators/markdowngen.py +1 -1
- linkml/generators/namespacegen.py +1 -1
- linkml/generators/oocodegen.py +2 -1
- linkml/generators/owlgen.py +1 -1
- linkml/generators/plantumlgen.py +1 -1
- linkml/generators/prefixmapgen.py +1 -1
- linkml/generators/projectgen.py +1 -1
- linkml/generators/protogen.py +1 -1
- linkml/generators/pydanticgen/__init__.py +8 -3
- linkml/generators/pydanticgen/array.py +114 -194
- linkml/generators/pydanticgen/build.py +64 -25
- linkml/generators/pydanticgen/includes.py +1 -31
- linkml/generators/pydanticgen/pydanticgen.py +616 -274
- linkml/generators/pydanticgen/template.py +152 -184
- linkml/generators/pydanticgen/templates/attribute.py.jinja +9 -7
- linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -13
- linkml/generators/pydanticgen/templates/class.py.jinja +2 -2
- linkml/generators/pydanticgen/templates/footer.py.jinja +2 -10
- linkml/generators/pydanticgen/templates/module.py.jinja +2 -2
- linkml/generators/pydanticgen/templates/validator.py.jinja +0 -4
- linkml/generators/pythongen.py +12 -2
- linkml/generators/rdfgen.py +1 -1
- linkml/generators/shaclgen.py +6 -2
- linkml/generators/shexgen.py +1 -1
- linkml/generators/sparqlgen.py +1 -1
- linkml/generators/sqlalchemygen.py +1 -1
- linkml/generators/sqltablegen.py +1 -1
- linkml/generators/sssomgen.py +1 -1
- linkml/generators/summarygen.py +1 -1
- linkml/generators/terminusdbgen.py +7 -4
- linkml/generators/typescriptgen.py +1 -1
- linkml/generators/yamlgen.py +1 -1
- linkml/generators/yumlgen.py +1 -1
- linkml/linter/cli.py +1 -1
- linkml/transformers/logical_model_transformer.py +117 -18
- linkml/utils/converter.py +1 -1
- linkml/utils/execute_tutorial.py +2 -0
- linkml/utils/logictools.py +142 -29
- linkml/utils/schema_builder.py +7 -6
- linkml/utils/schema_fixer.py +1 -1
- linkml/utils/sqlutils.py +1 -1
- linkml/validator/cli.py +4 -1
- linkml/validators/jsonschemavalidator.py +1 -1
- linkml/validators/sparqlvalidator.py +1 -1
- linkml/workspaces/example_runner.py +1 -1
- {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/METADATA +2 -2
- {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/RECORD +70 -64
- {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/entry_points.txt +1 -1
- {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/LICENSE +0 -0
- {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/WHEEL +0 -0
@@ -126,7 +126,13 @@ class JsonSchema(dict):
|
|
126
126
|
|
127
127
|
@property
|
128
128
|
def is_array(self):
|
129
|
-
|
129
|
+
typ = self.get("type", False)
|
130
|
+
if isinstance(typ, str):
|
131
|
+
return typ == "array"
|
132
|
+
elif isinstance(typ, list):
|
133
|
+
return "array" in typ
|
134
|
+
else:
|
135
|
+
return False
|
130
136
|
|
131
137
|
@property
|
132
138
|
def is_object(self):
|
@@ -136,7 +142,7 @@ class JsonSchema(dict):
|
|
136
142
|
return json.dumps(self, **kwargs)
|
137
143
|
|
138
144
|
@classmethod
|
139
|
-
def ref_for(cls, class_name: Union[str, List[str]], identifier_optional: bool = False):
|
145
|
+
def ref_for(cls, class_name: Union[str, List[str]], identifier_optional: bool = False, required: bool = True):
|
140
146
|
def _ref(class_name):
|
141
147
|
def_name = camelcase(class_name)
|
142
148
|
def_suffix = cls.OPTIONAL_IDENTIFIER_SUFFIX if identifier_optional else ""
|
@@ -144,15 +150,27 @@ class JsonSchema(dict):
|
|
144
150
|
|
145
151
|
if isinstance(class_name, list):
|
146
152
|
if len(class_name) == 1:
|
147
|
-
|
153
|
+
ref = _ref(class_name[0])
|
148
154
|
else:
|
149
|
-
|
155
|
+
ref = JsonSchema({"anyOf": [_ref(name) for name in class_name]})
|
150
156
|
else:
|
151
|
-
|
157
|
+
ref = _ref(class_name)
|
158
|
+
|
159
|
+
if not required:
|
160
|
+
if "anyOf" in ref:
|
161
|
+
ref["anyOf"].append({"type": "null"})
|
162
|
+
else:
|
163
|
+
ref = JsonSchema({"anyOf": [ref, {"type": "null"}]})
|
164
|
+
return ref
|
152
165
|
|
153
166
|
@classmethod
|
154
|
-
def array_of(cls, subschema: "JsonSchema") -> "JsonSchema":
|
155
|
-
|
167
|
+
def array_of(cls, subschema: "JsonSchema", required: bool = True) -> "JsonSchema":
|
168
|
+
if required:
|
169
|
+
typ = "array"
|
170
|
+
else:
|
171
|
+
typ = ["array", "null"]
|
172
|
+
|
173
|
+
schema = {"type": typ, "items": subschema}
|
156
174
|
|
157
175
|
return JsonSchema(schema)
|
158
176
|
|
@@ -199,6 +217,9 @@ class JsonSchemaGenerator(Generator):
|
|
199
217
|
|
200
218
|
top_level_schema: JsonSchema = None
|
201
219
|
|
220
|
+
include_null: bool = True
|
221
|
+
"""Whether to include a "null" type in optional slots"""
|
222
|
+
|
202
223
|
def __post_init__(self):
|
203
224
|
if self.topClass:
|
204
225
|
logging.warning("topClass is deprecated - use top_class")
|
@@ -315,7 +336,7 @@ class JsonSchemaGenerator(Generator):
|
|
315
336
|
|
316
337
|
subschema = JsonSchema()
|
317
338
|
for slot in cls.slot_conditions.values():
|
318
|
-
prop = self.get_subschema_for_slot(slot, omit_type=True)
|
339
|
+
prop = self.get_subschema_for_slot(slot, omit_type=True, include_null=False)
|
319
340
|
value_required = False
|
320
341
|
value_disallowed = False
|
321
342
|
if slot.value_presence:
|
@@ -433,11 +454,17 @@ class JsonSchemaGenerator(Generator):
|
|
433
454
|
constraints.add_keyword("maximum", slot.maximum_value)
|
434
455
|
constraints.add_keyword("const", slot.equals_string)
|
435
456
|
constraints.add_keyword("const", slot.equals_number)
|
457
|
+
if slot.equals_string_in:
|
458
|
+
constraints.add_keyword("enum", slot.equals_string_in)
|
436
459
|
return constraints
|
437
460
|
|
438
461
|
def get_subschema_for_slot(
|
439
|
-
self, slot: Union[SlotDefinition, AnonymousSlotExpression], omit_type: bool = False
|
462
|
+
self, slot: Union[SlotDefinition, AnonymousSlotExpression], omit_type: bool = False, include_null: bool = True
|
440
463
|
) -> JsonSchema:
|
464
|
+
"""
|
465
|
+
Args:
|
466
|
+
include_null: Include ``type: null`` when generating ranges that are not required
|
467
|
+
"""
|
441
468
|
prop = JsonSchema()
|
442
469
|
if isinstance(slot, SlotDefinition) and slot.array:
|
443
470
|
# TODO: this is currently too lax, in that it will validate ANY array.
|
@@ -448,9 +475,10 @@ class JsonSchemaGenerator(Generator):
|
|
448
475
|
"additionalProperties": True,
|
449
476
|
}
|
450
477
|
)
|
451
|
-
return JsonSchema.array_of(prop)
|
478
|
+
return JsonSchema.array_of(prop, required=slot.required)
|
452
479
|
slot_is_multivalued = "multivalued" in slot and slot.multivalued
|
453
480
|
slot_is_inlined = self.schemaview.is_inlined(slot)
|
481
|
+
slot_is_boolean = any([slot.any_of, slot.all_of, slot.exactly_one_of, slot.none_of])
|
454
482
|
if not omit_type:
|
455
483
|
typ, fmt, reference = self.get_type_info_for_slot_subschema(slot)
|
456
484
|
if slot_is_inlined:
|
@@ -471,7 +499,9 @@ class JsonSchemaGenerator(Generator):
|
|
471
499
|
# If the range can be collected as a simple dict, then we can also accept the value
|
472
500
|
# of that simple dict directly.
|
473
501
|
if range_simple_dict_value_slot is not None:
|
474
|
-
additionalProps.append(
|
502
|
+
additionalProps.append(
|
503
|
+
self.get_subschema_for_slot(range_simple_dict_value_slot, include_null=False)
|
504
|
+
)
|
475
505
|
|
476
506
|
# If the range has no required slots, then null is acceptable
|
477
507
|
if len(range_required_slots) == 0:
|
@@ -483,12 +513,17 @@ class JsonSchemaGenerator(Generator):
|
|
483
513
|
additionalProps = additionalProps[0]
|
484
514
|
else:
|
485
515
|
additionalProps = JsonSchema({"anyOf": additionalProps})
|
486
|
-
|
516
|
+
if slot.required or not include_null:
|
517
|
+
typ = "object"
|
518
|
+
else:
|
519
|
+
typ = ["object", "null"]
|
520
|
+
prop = JsonSchema({"type": typ, "additionalProperties": additionalProps})
|
487
521
|
self.top_level_schema.add_lax_def(reference, self.aliased_slot_name(range_id_slot))
|
488
522
|
else:
|
489
|
-
prop = JsonSchema.array_of(JsonSchema.ref_for(reference))
|
523
|
+
prop = JsonSchema.array_of(JsonSchema.ref_for(reference), required=slot.required)
|
490
524
|
else:
|
491
|
-
prop = JsonSchema.ref_for(reference)
|
525
|
+
prop = JsonSchema.ref_for(reference, required=slot.required or not include_null)
|
526
|
+
|
492
527
|
else:
|
493
528
|
if reference is not None:
|
494
529
|
prop = JsonSchema.ref_for(reference)
|
@@ -498,7 +533,12 @@ class JsonSchemaGenerator(Generator):
|
|
498
533
|
prop = JsonSchema({"type": typ, "format": fmt})
|
499
534
|
|
500
535
|
if slot_is_multivalued:
|
501
|
-
prop = JsonSchema.array_of(prop)
|
536
|
+
prop = JsonSchema.array_of(prop, required=slot.required)
|
537
|
+
else:
|
538
|
+
# handle optionals - bools like any_of, etc. below as they call this method recursively
|
539
|
+
if not slot.required and not slot_is_boolean and include_null:
|
540
|
+
if "type" in prop:
|
541
|
+
prop["type"] = [prop["type"], "null"]
|
502
542
|
|
503
543
|
prop.add_keyword("description", slot.description)
|
504
544
|
if self.title_from == "title" and slot.title:
|
@@ -524,22 +564,29 @@ class JsonSchemaGenerator(Generator):
|
|
524
564
|
|
525
565
|
bool_subschema = JsonSchema()
|
526
566
|
if slot.any_of is not None and len(slot.any_of) > 0:
|
527
|
-
bool_subschema["anyOf"] = [self.get_subschema_for_slot(s) for s in slot.any_of]
|
567
|
+
bool_subschema["anyOf"] = [self.get_subschema_for_slot(s, include_null=False) for s in slot.any_of]
|
568
|
+
if not slot.required and not prop.is_array and include_null:
|
569
|
+
bool_subschema["anyOf"].append({"type": "null"})
|
528
570
|
|
529
571
|
if slot.all_of is not None and len(slot.all_of) > 0:
|
530
|
-
bool_subschema["allOf"] = [self.get_subschema_for_slot(s) for s in slot.all_of]
|
572
|
+
bool_subschema["allOf"] = [self.get_subschema_for_slot(s, include_null=False) for s in slot.all_of]
|
531
573
|
|
532
574
|
if slot.exactly_one_of is not None and len(slot.exactly_one_of) > 0:
|
533
|
-
bool_subschema["oneOf"] = [self.get_subschema_for_slot(s) for s in slot.exactly_one_of]
|
575
|
+
bool_subschema["oneOf"] = [self.get_subschema_for_slot(s, include_null=False) for s in slot.exactly_one_of]
|
534
576
|
|
535
577
|
if slot.none_of is not None and len(slot.none_of) > 0:
|
536
|
-
bool_subschema["not"] = {
|
578
|
+
bool_subschema["not"] = {
|
579
|
+
"anyOf": [self.get_subschema_for_slot(s, include_null=False) for s in slot.none_of]
|
580
|
+
}
|
537
581
|
|
538
582
|
if bool_subschema:
|
539
583
|
if prop.is_array:
|
540
584
|
if "items" not in prop:
|
541
585
|
prop["items"] = {}
|
542
|
-
|
586
|
+
if slot.required or not include_null:
|
587
|
+
prop["type"] = "array"
|
588
|
+
else:
|
589
|
+
prop["type"] = ["array", "null"]
|
543
590
|
prop["items"].update(bool_subschema)
|
544
591
|
else:
|
545
592
|
prop.update(bool_subschema)
|
@@ -554,7 +601,7 @@ class JsonSchemaGenerator(Generator):
|
|
554
601
|
value_disallowed = slot.value_presence == PresenceEnum(PresenceEnum.ABSENT)
|
555
602
|
|
556
603
|
aliased_slot_name = self.aliased_slot_name(slot)
|
557
|
-
prop = self.get_subschema_for_slot(slot)
|
604
|
+
prop = self.get_subschema_for_slot(slot, include_null=self.include_null)
|
558
605
|
subschema.add_property(
|
559
606
|
aliased_slot_name, prop, value_required=value_required, value_disallowed=value_disallowed
|
560
607
|
)
|
@@ -625,7 +672,7 @@ class JsonSchemaGenerator(Generator):
|
|
625
672
|
|
626
673
|
|
627
674
|
@shared_arguments(JsonSchemaGenerator)
|
628
|
-
@click.command()
|
675
|
+
@click.command(name="json-schema")
|
629
676
|
@click.option(
|
630
677
|
"-i",
|
631
678
|
"--inline",
|
linkml/generators/linkmlgen.py
CHANGED
@@ -97,7 +97,7 @@ class LinkmlGenerator(Generator):
|
|
97
97
|
help="Name of JSON or YAML file to be created",
|
98
98
|
)
|
99
99
|
@click.version_option(__version__, "-V", "--version")
|
100
|
-
@click.command()
|
100
|
+
@click.command(name="linkml")
|
101
101
|
def cli(
|
102
102
|
yamlfile,
|
103
103
|
materialize_attributes: bool,
|
linkml/generators/markdowngen.py
CHANGED
@@ -789,7 +789,7 @@ def pad_heading(text: str) -> str:
|
|
789
789
|
|
790
790
|
|
791
791
|
@shared_arguments(MarkdownGenerator)
|
792
|
-
@click.command()
|
792
|
+
@click.command(name="markdown")
|
793
793
|
@click.option("--dir", "-d", required=True, help="Output directory")
|
794
794
|
@click.option("--classes", "-c", multiple=True, help="Class(es) to emit")
|
795
795
|
@click.option("--map-fields", "-M", multiple=True, help="Map metamodel fields, e.g. slot=field")
|
@@ -195,7 +195,7 @@ def curie(identifier) -> str:
|
|
195
195
|
|
196
196
|
@shared_arguments(NamespaceGenerator)
|
197
197
|
@click.version_option(__version__, "-V", "--version")
|
198
|
-
@click.command()
|
198
|
+
@click.command(name="namespaces")
|
199
199
|
def cli(yamlfile, **args):
|
200
200
|
"""Generate a namespace manager for all of the prefixes represented in a LinkML model"""
|
201
201
|
print(NamespaceGenerator(yamlfile, **args).serialize(**args))
|
linkml/generators/oocodegen.py
CHANGED
@@ -76,6 +76,7 @@ class OOCodeGenerator(Generator):
|
|
76
76
|
visit_all_class_slots = False
|
77
77
|
uses_schemaloader = False
|
78
78
|
requires_metamodel = False
|
79
|
+
schemaview: SchemaView = None
|
79
80
|
|
80
81
|
template_file: str = None
|
81
82
|
"""Path to template"""
|
@@ -84,7 +85,7 @@ class OOCodeGenerator(Generator):
|
|
84
85
|
|
85
86
|
def __post_init__(self):
|
86
87
|
# TODO: consider moving up a level
|
87
|
-
self.schemaview = SchemaView(self.schema)
|
88
|
+
self.schemaview: SchemaView = SchemaView(self.schema)
|
88
89
|
super().__post_init__()
|
89
90
|
|
90
91
|
@abc.abstractmethod
|
linkml/generators/owlgen.py
CHANGED
@@ -1271,7 +1271,7 @@ class OwlSchemaGenerator(Generator):
|
|
1271
1271
|
|
1272
1272
|
|
1273
1273
|
@shared_arguments(OwlSchemaGenerator)
|
1274
|
-
@click.command()
|
1274
|
+
@click.command(name="owl")
|
1275
1275
|
@click.option("-o", "--output", help="Output file name")
|
1276
1276
|
@click.option(
|
1277
1277
|
"--metadata-profile",
|
linkml/generators/plantumlgen.py
CHANGED
@@ -339,7 +339,7 @@ class PlantumlGenerator(Generator):
|
|
339
339
|
|
340
340
|
|
341
341
|
@shared_arguments(PlantumlGenerator)
|
342
|
-
@click.command()
|
342
|
+
@click.command(name="plantuml")
|
343
343
|
@click.option("--classes", "-c", multiple=True, help="Class(es) to emit")
|
344
344
|
@click.option(
|
345
345
|
"--directory",
|
@@ -112,7 +112,7 @@ class PrefixGenerator(Generator):
|
|
112
112
|
|
113
113
|
|
114
114
|
@shared_arguments(PrefixGenerator)
|
115
|
-
@click.command()
|
115
|
+
@click.command(name="prefix-map")
|
116
116
|
@click.option("--base", help="Base URI for model")
|
117
117
|
@click.option("--output", "-o", help="Output file path")
|
118
118
|
@click.version_option(__version__, "-V", "--version")
|
linkml/generators/projectgen.py
CHANGED
linkml/generators/protogen.py
CHANGED
@@ -69,7 +69,7 @@ class ProtoGenerator(Generator):
|
|
69
69
|
|
70
70
|
@shared_arguments(ProtoGenerator)
|
71
71
|
@click.version_option(__version__, "-V", "--version")
|
72
|
-
@click.command()
|
72
|
+
@click.command(name="proto")
|
73
73
|
def cli(yamlfile, **args):
|
74
74
|
"""Generate proto representation of LinkML model"""
|
75
75
|
print(ProtoGenerator(yamlfile, **args).serialize(**args))
|
@@ -1,4 +1,9 @@
|
|
1
|
-
from linkml.generators.pydanticgen.pydanticgen import
|
1
|
+
from linkml.generators.pydanticgen.pydanticgen import (
|
2
|
+
DEFAULT_IMPORTS,
|
3
|
+
MetadataMode,
|
4
|
+
PydanticGenerator,
|
5
|
+
cli,
|
6
|
+
)
|
2
7
|
from linkml.generators.pydanticgen.template import (
|
3
8
|
ConditionalImport,
|
4
9
|
Import,
|
@@ -8,8 +13,8 @@ from linkml.generators.pydanticgen.template import (
|
|
8
13
|
PydanticClass,
|
9
14
|
PydanticEnum,
|
10
15
|
PydanticModule,
|
16
|
+
PydanticTemplateModel,
|
11
17
|
PydanticValidator,
|
12
|
-
TemplateModel,
|
13
18
|
)
|
14
19
|
|
15
20
|
__all__ = [
|
@@ -26,5 +31,5 @@ __all__ = [
|
|
26
31
|
"PydanticGenerator",
|
27
32
|
"PydanticModule",
|
28
33
|
"PydanticValidator",
|
29
|
-
"
|
34
|
+
"PydanticTemplateModel",
|
30
35
|
]
|