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.
Files changed (70) hide show
  1. linkml/cli/__init__.py +0 -0
  2. linkml/cli/__main__.py +4 -0
  3. linkml/cli/main.py +126 -0
  4. linkml/generators/common/build.py +105 -0
  5. linkml/generators/common/lifecycle.py +124 -0
  6. linkml/generators/common/template.py +89 -0
  7. linkml/generators/csvgen.py +1 -1
  8. linkml/generators/docgen/slot.md.jinja2 +4 -0
  9. linkml/generators/docgen.py +1 -1
  10. linkml/generators/dotgen.py +1 -1
  11. linkml/generators/erdiagramgen.py +1 -1
  12. linkml/generators/excelgen.py +1 -1
  13. linkml/generators/golanggen.py +1 -1
  14. linkml/generators/golrgen.py +1 -1
  15. linkml/generators/graphqlgen.py +1 -1
  16. linkml/generators/javagen.py +1 -1
  17. linkml/generators/jsonldcontextgen.py +4 -4
  18. linkml/generators/jsonldgen.py +1 -1
  19. linkml/generators/jsonschemagen.py +69 -22
  20. linkml/generators/linkmlgen.py +1 -1
  21. linkml/generators/markdowngen.py +1 -1
  22. linkml/generators/namespacegen.py +1 -1
  23. linkml/generators/oocodegen.py +2 -1
  24. linkml/generators/owlgen.py +1 -1
  25. linkml/generators/plantumlgen.py +1 -1
  26. linkml/generators/prefixmapgen.py +1 -1
  27. linkml/generators/projectgen.py +1 -1
  28. linkml/generators/protogen.py +1 -1
  29. linkml/generators/pydanticgen/__init__.py +8 -3
  30. linkml/generators/pydanticgen/array.py +114 -194
  31. linkml/generators/pydanticgen/build.py +64 -25
  32. linkml/generators/pydanticgen/includes.py +1 -31
  33. linkml/generators/pydanticgen/pydanticgen.py +616 -274
  34. linkml/generators/pydanticgen/template.py +152 -184
  35. linkml/generators/pydanticgen/templates/attribute.py.jinja +9 -7
  36. linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -13
  37. linkml/generators/pydanticgen/templates/class.py.jinja +2 -2
  38. linkml/generators/pydanticgen/templates/footer.py.jinja +2 -10
  39. linkml/generators/pydanticgen/templates/module.py.jinja +2 -2
  40. linkml/generators/pydanticgen/templates/validator.py.jinja +0 -4
  41. linkml/generators/pythongen.py +12 -2
  42. linkml/generators/rdfgen.py +1 -1
  43. linkml/generators/shaclgen.py +6 -2
  44. linkml/generators/shexgen.py +1 -1
  45. linkml/generators/sparqlgen.py +1 -1
  46. linkml/generators/sqlalchemygen.py +1 -1
  47. linkml/generators/sqltablegen.py +1 -1
  48. linkml/generators/sssomgen.py +1 -1
  49. linkml/generators/summarygen.py +1 -1
  50. linkml/generators/terminusdbgen.py +7 -4
  51. linkml/generators/typescriptgen.py +1 -1
  52. linkml/generators/yamlgen.py +1 -1
  53. linkml/generators/yumlgen.py +1 -1
  54. linkml/linter/cli.py +1 -1
  55. linkml/transformers/logical_model_transformer.py +117 -18
  56. linkml/utils/converter.py +1 -1
  57. linkml/utils/execute_tutorial.py +2 -0
  58. linkml/utils/logictools.py +142 -29
  59. linkml/utils/schema_builder.py +7 -6
  60. linkml/utils/schema_fixer.py +1 -1
  61. linkml/utils/sqlutils.py +1 -1
  62. linkml/validator/cli.py +4 -1
  63. linkml/validators/jsonschemavalidator.py +1 -1
  64. linkml/validators/sparqlvalidator.py +1 -1
  65. linkml/workspaces/example_runner.py +1 -1
  66. {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/METADATA +2 -2
  67. {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/RECORD +70 -64
  68. {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/entry_points.txt +1 -1
  69. {linkml-1.8.1.dist-info → linkml-1.8.2.dist-info}/LICENSE +0 -0
  70. {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
- return self.get("type") == "array"
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
- return _ref(class_name[0])
153
+ ref = _ref(class_name[0])
148
154
  else:
149
- return JsonSchema({"anyOf": [_ref(name) for name in class_name]})
155
+ ref = JsonSchema({"anyOf": [_ref(name) for name in class_name]})
150
156
  else:
151
- return _ref(class_name)
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
- schema = {"type": "array", "items": subschema}
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(self.get_subschema_for_slot(range_simple_dict_value_slot))
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
- prop = JsonSchema({"type": "object", "additionalProperties": additionalProps})
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"] = {"anyOf": [self.get_subschema_for_slot(s) for s in slot.none_of]}
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
- prop["type"] = "array"
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",
@@ -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,
@@ -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))
@@ -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
@@ -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",
@@ -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")
@@ -159,7 +159,7 @@ class ProjectGenerator:
159
159
  gen.serialize(**serialize_args)
160
160
 
161
161
 
162
- @click.command()
162
+ @click.command(name="project")
163
163
  @click.option(
164
164
  "--dir",
165
165
  "-d",
@@ -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 DEFAULT_IMPORTS, MetadataMode, PydanticGenerator, cli
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
- "TemplateModel",
34
+ "PydanticTemplateModel",
30
35
  ]