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.
Files changed (81) hide show
  1. linkml/__init__.py +2 -6
  2. linkml/_version.py +1 -1
  3. linkml/generators/PythonGenNotes.md +4 -4
  4. linkml/generators/__init__.py +26 -5
  5. linkml/generators/common/type_designators.py +27 -22
  6. linkml/generators/csvgen.py +4 -10
  7. linkml/generators/docgen/class.md.jinja2 +7 -0
  8. linkml/generators/docgen/class_diagram.md.jinja2 +0 -6
  9. linkml/generators/docgen/subset.md.jinja2 +54 -13
  10. linkml/generators/docgen.py +94 -92
  11. linkml/generators/dotgen.py +5 -9
  12. linkml/generators/erdiagramgen.py +58 -53
  13. linkml/generators/excelgen.py +10 -16
  14. linkml/generators/golanggen.py +11 -21
  15. linkml/generators/golrgen.py +4 -13
  16. linkml/generators/graphqlgen.py +3 -11
  17. linkml/generators/javagen.py +8 -15
  18. linkml/generators/jsonldcontextgen.py +7 -36
  19. linkml/generators/jsonldgen.py +14 -12
  20. linkml/generators/jsonschemagen.py +183 -136
  21. linkml/generators/linkmlgen.py +1 -1
  22. linkml/generators/markdowngen.py +40 -89
  23. linkml/generators/namespacegen.py +1 -2
  24. linkml/generators/oocodegen.py +22 -25
  25. linkml/generators/owlgen.py +48 -49
  26. linkml/generators/prefixmapgen.py +6 -14
  27. linkml/generators/projectgen.py +7 -14
  28. linkml/generators/protogen.py +3 -5
  29. linkml/generators/pydanticgen.py +85 -73
  30. linkml/generators/pythongen.py +89 -157
  31. linkml/generators/rdfgen.py +5 -11
  32. linkml/generators/shaclgen.py +32 -18
  33. linkml/generators/shexgen.py +19 -24
  34. linkml/generators/sparqlgen.py +5 -13
  35. linkml/generators/sqlalchemy/__init__.py +3 -2
  36. linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +7 -7
  37. linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +3 -3
  38. linkml/generators/sqlalchemygen.py +29 -27
  39. linkml/generators/sqlddlgen.py +34 -26
  40. linkml/generators/sqltablegen.py +21 -21
  41. linkml/generators/sssomgen.py +11 -13
  42. linkml/generators/summarygen.py +2 -4
  43. linkml/generators/terminusdbgen.py +7 -19
  44. linkml/generators/typescriptgen.py +10 -18
  45. linkml/generators/yamlgen.py +0 -2
  46. linkml/generators/yumlgen.py +23 -71
  47. linkml/linter/cli.py +4 -11
  48. linkml/linter/config/datamodel/config.py +17 -47
  49. linkml/linter/linter.py +2 -4
  50. linkml/linter/rules.py +34 -48
  51. linkml/reporting/__init__.py +2 -0
  52. linkml/reporting/model.py +9 -24
  53. linkml/transformers/relmodel_transformer.py +20 -33
  54. linkml/transformers/schema_renamer.py +14 -10
  55. linkml/utils/converter.py +15 -15
  56. linkml/utils/datautils.py +9 -24
  57. linkml/utils/datavalidator.py +2 -2
  58. linkml/utils/execute_tutorial.py +10 -12
  59. linkml/utils/generator.py +74 -92
  60. linkml/utils/helpers.py +4 -2
  61. linkml/utils/ifabsent_functions.py +23 -15
  62. linkml/utils/mergeutils.py +19 -35
  63. linkml/utils/rawloader.py +2 -6
  64. linkml/utils/schema_builder.py +31 -19
  65. linkml/utils/schema_fixer.py +28 -18
  66. linkml/utils/schemaloader.py +44 -89
  67. linkml/utils/schemasynopsis.py +50 -73
  68. linkml/utils/sqlutils.py +40 -30
  69. linkml/utils/typereferences.py +9 -6
  70. linkml/utils/validation.py +4 -5
  71. linkml/validators/__init__.py +2 -0
  72. linkml/validators/jsonschemavalidator.py +104 -53
  73. linkml/validators/sparqlvalidator.py +5 -15
  74. linkml/workspaces/datamodel/workspaces.py +13 -30
  75. linkml/workspaces/example_runner.py +75 -68
  76. {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/METADATA +2 -2
  77. linkml-1.5.7.dist-info/RECORD +109 -0
  78. linkml-1.5.5.dist-info/RECORD +0 -109
  79. {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/LICENSE +0 -0
  80. {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/WHEEL +0 -0
  81. {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/entry_points.txt +0 -0
@@ -7,13 +7,17 @@ from dataclasses import dataclass, field
7
7
  from typing import Any, Dict, List, Optional, Tuple, Union
8
8
 
9
9
  import click
10
- from linkml_runtime.linkml_model.meta import (AnonymousClassExpression,
11
- AnonymousSlotExpression,
12
- ClassDefinition, EnumDefinition,
13
- PermissibleValue,
14
- PermissibleValueText,
15
- SlotDefinition, ClassDefinitionName,
16
- metamodel_version)
10
+ from linkml_runtime.linkml_model.meta import (
11
+ AnonymousClassExpression,
12
+ AnonymousSlotExpression,
13
+ ClassDefinition,
14
+ ClassDefinitionName,
15
+ EnumDefinition,
16
+ PermissibleValue,
17
+ PermissibleValueText,
18
+ SlotDefinition,
19
+ metamodel_version,
20
+ )
17
21
  from linkml_runtime.utils.formatutils import be, camelcase, underscore
18
22
 
19
23
  from linkml._version import __version__
@@ -36,8 +40,8 @@ json_schema_types: Dict[str, Tuple[str, Optional[str]]] = {
36
40
  "xsdtime": ("string", "time"),
37
41
  }
38
42
 
39
- class JsonSchema(UserDict):
40
43
 
44
+ class JsonSchema(UserDict):
41
45
  OPTIONAL_IDENTIFIER_SUFFIX = "__identifier_optional"
42
46
 
43
47
  def __init__(self, *args, **kwargs):
@@ -47,13 +51,13 @@ class JsonSchema(UserDict):
47
51
  def add_def(self, name: str, subschema: "JsonSchema") -> None:
48
52
  canonical_name = camelcase(name)
49
53
 
50
- if '$defs' not in self:
51
- self['$defs'] = {}
54
+ if "$defs" not in self:
55
+ self["$defs"] = {}
52
56
 
53
- if 'title' not in subschema:
54
- subschema['title'] = canonical_name
57
+ if "title" not in subschema:
58
+ subschema["title"] = canonical_name
55
59
 
56
- self['$defs'][canonical_name] = subschema
60
+ self["$defs"][canonical_name] = subschema
57
61
 
58
62
  if canonical_name in self._lax_forward_refs:
59
63
  identifier_name = self._lax_forward_refs.pop(canonical_name)
@@ -70,36 +74,36 @@ class JsonSchema(UserDict):
70
74
  self._lax_forward_refs[canonical_name] = identifier_name
71
75
  else:
72
76
  lax_cls = deepcopy(self["$defs"][canonical_name])
73
- lax_cls['required'].remove(identifier_name)
77
+ lax_cls["required"].remove(identifier_name)
74
78
  self["$defs"][canonical_name + self.OPTIONAL_IDENTIFIER_SUFFIX] = lax_cls
75
79
 
76
80
  def add_property(self, name: str, subschema: "JsonSchema", required: bool = False) -> None:
77
81
  canonical_name = underscore(name)
78
-
79
- if 'properties' not in self:
80
- self['properties'] = {}
81
-
82
- self['properties'][canonical_name] = subschema
82
+
83
+ if "properties" not in self:
84
+ self["properties"] = {}
85
+
86
+ self["properties"][canonical_name] = subschema
83
87
 
84
88
  if required:
85
- if 'required' not in self:
86
- self['required'] = []
87
-
88
- self['required'].append(canonical_name)
89
+ if "required" not in self:
90
+ self["required"] = []
91
+
92
+ self["required"].append(canonical_name)
89
93
 
90
94
  def add_keyword(self, keyword: str, value: Any):
91
95
  if value is None:
92
96
  return
93
-
97
+
94
98
  self[keyword] = value
95
99
 
96
100
  @property
97
101
  def is_array(self):
98
- return self.get('type') == 'array'
102
+ return self.get("type") == "array"
99
103
 
100
104
  @property
101
105
  def is_object(self):
102
- return self.get('type') == 'object'
106
+ return self.get("type") == "object"
103
107
 
104
108
  def to_json(self, **kwargs) -> str:
105
109
  return json.dumps(self.data, default=lambda d: d.data, **kwargs)
@@ -107,23 +111,18 @@ class JsonSchema(UserDict):
107
111
  @classmethod
108
112
  def ref_for(cls, class_name: Union[str, List[str]], identifier_optional: bool = False):
109
113
  def _ref(class_name):
110
- return JsonSchema({
111
- "$ref": f"#/$defs/{camelcase(class_name)}{cls.OPTIONAL_IDENTIFIER_SUFFIX if identifier_optional else ''}"
112
- })
113
-
114
+ def_name = camelcase(class_name)
115
+ def_suffix = cls.OPTIONAL_IDENTIFIER_SUFFIX if identifier_optional else ""
116
+ return JsonSchema({"$ref": f"#/$defs/{def_name}{def_suffix}"})
117
+
114
118
  if isinstance(class_name, list):
115
- return JsonSchema({
116
- "anyOf": [_ref(name) for name in class_name]
117
- })
119
+ return JsonSchema({"anyOf": [_ref(name) for name in class_name]})
118
120
  else:
119
121
  return _ref(class_name)
120
122
 
121
123
  @classmethod
122
124
  def array_of(cls, subschema: "JsonSchema") -> "JsonSchema":
123
- schema = {
124
- "type": "array",
125
- "items": subschema
126
- }
125
+ schema = {"type": "array", "items": subschema}
127
126
 
128
127
  return JsonSchema(schema)
129
128
 
@@ -145,8 +144,9 @@ class JsonSchemaGenerator(Generator):
145
144
  generatorversion = "0.0.3"
146
145
  valid_formats = ["json"]
147
146
  uses_schemaloader = False
147
+ file_extension = "schema.json"
148
148
 
149
- #@deprecated("Use top_class")
149
+ # @deprecated("Use top_class")
150
150
  topClass: Optional[str] = None
151
151
 
152
152
  not_closed: Optional[bool] = field(default_factory=lambda: True)
@@ -155,7 +155,7 @@ class JsonSchemaGenerator(Generator):
155
155
  indent: int = 4
156
156
 
157
157
  inline: bool = False
158
- top_class: Optional[ClassDefinitionName] = None ## JSON object is one instance of this
158
+ top_class: Optional[ClassDefinitionName] = None # JSON object is one instance of this
159
159
  """Class instantiated by the root node of the document tree"""
160
160
 
161
161
  include_range_class_descendants: bool = field(default_factory=lambda: False)
@@ -164,7 +164,7 @@ class JsonSchemaGenerator(Generator):
164
164
 
165
165
  def __post_init__(self):
166
166
  if self.topClass:
167
- logging.warning(f"topClass is deprecated - use top_class")
167
+ logging.warning("topClass is deprecated - use top_class")
168
168
  self.top_class = self.topClass
169
169
 
170
170
  super().__post_init__()
@@ -172,36 +172,36 @@ class JsonSchemaGenerator(Generator):
172
172
  def start_schema(self, inline: bool = False) -> JsonSchema:
173
173
  self.inline = inline
174
174
 
175
- self.top_level_schema = JsonSchema({
176
- "$schema": "http://json-schema.org/draft-07/schema#",
177
- "$id": self.schema.id,
178
- "metamodel_version": metamodel_version,
179
- "version": self.schema.version if self.schema.version else None,
180
- "title": self.schema.name,
181
- "type": "object",
182
- "additionalProperties": self.not_closed,
183
- })
175
+ self.top_level_schema = JsonSchema(
176
+ {
177
+ "$schema": "http://json-schema.org/draft-07/schema#",
178
+ "$id": self.schema.id,
179
+ "metamodel_version": metamodel_version,
180
+ "version": self.schema.version if self.schema.version else None,
181
+ "title": self.schema.name,
182
+ "type": "object",
183
+ "additionalProperties": self.not_closed,
184
+ }
185
+ )
184
186
 
185
187
  def handle_class(self, cls: ClassDefinition) -> None:
186
188
  if cls.mixin or cls.abstract:
187
189
  return
188
-
190
+
189
191
  additional_properties = False
190
192
  if self.is_class_unconstrained(cls):
191
193
  additional_properties = True
192
-
193
- class_subschema = JsonSchema({
194
- "type": "object",
195
- "additionalProperties": additional_properties,
196
- "description": be(cls.description),
197
- })
194
+
195
+ class_subschema = JsonSchema(
196
+ {
197
+ "type": "object",
198
+ "additionalProperties": additional_properties,
199
+ "description": be(cls.description),
200
+ }
201
+ )
198
202
 
199
203
  for slot_definition in self.schemaview.class_induced_slots(cls.name):
200
- self.handle_class_slot(
201
- subschema=class_subschema,
202
- cls=cls,
203
- slot=slot_definition
204
- )
204
+ self.handle_class_slot(subschema=class_subschema, cls=cls, slot=slot_definition)
205
205
 
206
206
  rule_subschemas = []
207
207
  for rule in cls.rules:
@@ -211,16 +211,22 @@ class JsonSchemaGenerator(Generator):
211
211
  if open_world is None:
212
212
  open_world = False
213
213
 
214
- if_subschema = self.get_subschema_for_anonymous_class(rule.preconditions, properties_required=True)
214
+ if_subschema = self.get_subschema_for_anonymous_class(
215
+ rule.preconditions, properties_required=True
216
+ )
215
217
  if if_subschema:
216
218
  subschema["if"] = if_subschema
217
-
218
- then_subschema = self.get_subschema_for_anonymous_class(rule.postconditions, properties_required=not open_world)
219
+
220
+ then_subschema = self.get_subschema_for_anonymous_class(
221
+ rule.postconditions, properties_required=not open_world
222
+ )
219
223
  if then_subschema:
220
224
  subschema["then"] = then_subschema
221
225
 
222
226
  # same as required requirements as postconditions?
223
- else_subschema = self.get_subschema_for_anonymous_class(rule.elseconditions, properties_required=not open_world)
227
+ else_subschema = self.get_subschema_for_anonymous_class(
228
+ rule.elseconditions, properties_required=not open_world
229
+ )
224
230
  if else_subschema:
225
231
  subschema["else"] = else_subschema
226
232
 
@@ -231,7 +237,7 @@ class JsonSchemaGenerator(Generator):
231
237
 
232
238
  if then_subschema:
233
239
  inverse_subschema["if"] = then_subschema
234
-
240
+
235
241
  if if_subschema:
236
242
  inverse_subschema["then"] = if_subschema
237
243
 
@@ -246,17 +252,19 @@ class JsonSchemaGenerator(Generator):
246
252
 
247
253
  self.top_level_schema.add_def(cls.name, class_subschema)
248
254
 
249
- if (
250
- self.top_class is not None and camelcase(self.top_class) == camelcase(cls.name)
251
- ) or (self.top_class is None and cls.tree_root):
255
+ if (self.top_class is not None and camelcase(self.top_class) == camelcase(cls.name)) or (
256
+ self.top_class is None and cls.tree_root
257
+ ):
252
258
  for key, value in class_subschema.items():
253
259
  # check this first to ensure we don't overwrite things like additionalProperties
254
- # or description on the root. But we do want to copy over properties, required,
260
+ # or description on the root. But we do want to copy over properties, required,
255
261
  # if, then, etc.
256
262
  if key not in self.top_level_schema:
257
263
  self.top_level_schema[key] = value
258
264
 
259
- def get_subschema_for_anonymous_class(self, cls: AnonymousClassExpression, properties_required: bool = False) -> Union[None, JsonSchema]:
265
+ def get_subschema_for_anonymous_class(
266
+ self, cls: AnonymousClassExpression, properties_required: bool = False
267
+ ) -> Union[None, JsonSchema]:
260
268
  if not cls:
261
269
  return None
262
270
 
@@ -266,32 +274,41 @@ class JsonSchemaGenerator(Generator):
266
274
  subschema.add_property(self.aliased_slot_name(slot), prop, properties_required)
267
275
 
268
276
  if cls.any_of is not None and len(cls.any_of) > 0:
269
- subschema["anyOf"] = [self.get_subschema_for_anonymous_class(c, properties_required) for c in cls.any_of]
277
+ subschema["anyOf"] = [
278
+ self.get_subschema_for_anonymous_class(c, properties_required) for c in cls.any_of
279
+ ]
270
280
 
271
281
  if cls.all_of is not None and len(cls.all_of) > 0:
272
- subschema["allOf"] = [self.get_subschema_for_anonymous_class(c, properties_required) for c in cls.all_of]
282
+ subschema["allOf"] = [
283
+ self.get_subschema_for_anonymous_class(c, properties_required) for c in cls.all_of
284
+ ]
273
285
 
274
286
  if cls.exactly_one_of is not None and len(cls.exactly_one_of) > 0:
275
- subschema["oneOf"] = [self.get_subschema_for_anonymous_class(c, properties_required) for c in cls.exactly_one_of]
287
+ subschema["oneOf"] = [
288
+ self.get_subschema_for_anonymous_class(c, properties_required)
289
+ for c in cls.exactly_one_of
290
+ ]
276
291
 
277
292
  if cls.none_of is not None and len(cls.none_of) > 0:
278
293
  subschema["not"] = {
279
- "anyOf": [self.get_subschema_for_anonymous_class(c, properties_required) for c in cls.any_of]
294
+ "anyOf": [
295
+ self.get_subschema_for_anonymous_class(c, properties_required)
296
+ for c in cls.any_of
297
+ ]
280
298
  }
281
299
 
282
300
  return subschema
283
301
 
284
-
285
302
  def handle_enum(self, enum: EnumDefinition) -> None:
286
303
  # TODO: this only works with explicitly permitted values. It will need to be extended to
287
304
  # support other pv_formula
288
305
 
289
306
  def extract_permissible_text(pv):
290
- if type(pv) is str:
307
+ if isinstance(pv, str):
291
308
  return pv
292
- if type(pv) is PermissibleValue:
309
+ if isinstance(pv, PermissibleValue):
293
310
  return pv.text.code
294
- if type(pv) is PermissibleValueText:
311
+ if isinstance(pv, PermissibleValueText):
295
312
  return pv
296
313
  raise ValueError(f"Invalid permissible value in enum {enum}: {pv}")
297
314
 
@@ -299,17 +316,25 @@ class JsonSchemaGenerator(Generator):
299
316
  map(extract_permissible_text, enum.permissible_values or [])
300
317
  )
301
318
 
302
- enum_schema = JsonSchema({
303
- "type": "string",
304
- "enum": permissible_values_texts,
305
- "description": be(enum.description),
306
- })
319
+ enum_schema = JsonSchema(
320
+ {
321
+ "type": "string",
322
+ "description": be(enum.description),
323
+ }
324
+ )
325
+ if permissible_values_texts:
326
+ enum_schema["enum"] = permissible_values_texts
307
327
  self.top_level_schema.add_def(enum.name, enum_schema)
308
328
 
309
- def get_type_info_for_slot_subschema(self, slot: AnonymousSlotExpression, parent_slot_is_inlined: bool) -> Tuple[str, str, Union[str, List[str]]]:
310
- typ = None # JSON Schema type (https://json-schema.org/understanding-json-schema/reference/type.html)
311
- reference = None # Reference to a JSON schema entity (https://json-schema.org/understanding-json-schema/structuring.html#ref)
312
- fmt = None # JSON Schema format (https://json-schema.org/understanding-json-schema/reference/string.html#format)
329
+ def get_type_info_for_slot_subschema(
330
+ self, slot: AnonymousSlotExpression
331
+ ) -> Tuple[str, str, Union[str, List[str]]]:
332
+ # JSON Schema type (https://json-schema.org/understanding-json-schema/reference/type.html)
333
+ typ = None
334
+ # Reference to a JSON schema entity (https://json-schema.org/understanding-json-schema/structuring.html#ref)
335
+ reference = None
336
+ # JSON Schema format (https://json-schema.org/understanding-json-schema/reference/string.html#format)
337
+ fmt = None
313
338
 
314
339
  slot_is_inlined = self.schemaview.is_inlined(slot)
315
340
  if slot.range in self.schemaview.all_types().keys():
@@ -318,41 +343,50 @@ class JsonSchemaGenerator(Generator):
318
343
  elif slot.range in self.schemaview.all_enums().keys():
319
344
  reference = slot.range
320
345
  elif slot.range in self.schemaview.all_classes().keys():
321
- if slot_is_inlined or parent_slot_is_inlined:
322
- descendants = [desc for desc in self.schemaview.class_descendants(slot.range)
323
- if not self.schemaview.get_class(desc).abstract]
346
+ if slot_is_inlined:
347
+ descendants = [
348
+ desc
349
+ for desc in self.schemaview.class_descendants(slot.range)
350
+ if not self.schemaview.get_class(desc).abstract
351
+ ]
324
352
  if descendants and self.include_range_class_descendants:
325
353
  reference = descendants
326
354
  else:
327
355
  reference = slot.range
328
356
  else:
329
357
  id_slot = self.schemaview.get_identifier_slot(slot.range)
330
- typ = id_slot.range or "string"
358
+ return self.get_type_info_for_slot_subschema(id_slot)
331
359
 
332
360
  return (typ, fmt, reference)
333
-
334
- def get_value_constraints_for_slot(self, slot: Union[AnonymousSlotExpression, None]) -> JsonSchema:
361
+
362
+ def get_value_constraints_for_slot(
363
+ self, slot: Union[AnonymousSlotExpression, None]
364
+ ) -> JsonSchema:
335
365
  if slot is None:
336
366
  return JsonSchema()
337
-
367
+
338
368
  constraints = JsonSchema()
339
- constraints.add_keyword('pattern', slot.pattern)
340
- constraints.add_keyword('minimum', slot.minimum_value)
341
- constraints.add_keyword('maximum', slot.maximum_value)
342
- constraints.add_keyword('const', slot.equals_string)
343
- constraints.add_keyword('const', slot.equals_number)
369
+ constraints.add_keyword("pattern", slot.pattern)
370
+ constraints.add_keyword("minimum", slot.minimum_value)
371
+ constraints.add_keyword("maximum", slot.maximum_value)
372
+ constraints.add_keyword("const", slot.equals_string)
373
+ constraints.add_keyword("const", slot.equals_number)
344
374
  return constraints
345
375
 
346
376
  def get_subschema_for_slot(self, slot: SlotDefinition, omit_type: bool = False) -> JsonSchema:
347
377
  prop = JsonSchema()
348
- slot_is_multivalued = 'multivalued' in slot and slot.multivalued
378
+ slot_is_multivalued = "multivalued" in slot and slot.multivalued
349
379
  slot_is_inlined = self.schemaview.is_inlined(slot)
350
380
  if not omit_type:
351
- typ, fmt, reference = self.get_type_info_for_slot_subschema(slot, slot_is_inlined)
381
+ typ, fmt, reference = self.get_type_info_for_slot_subschema(slot)
352
382
  if slot_is_inlined:
353
383
  # If inline we have to include redefined slots
354
384
  if slot_is_multivalued:
355
- range_id_slot, range_simple_dict_value_slot, range_required_slots = self._get_range_associated_slots(slot)
385
+ (
386
+ range_id_slot,
387
+ range_simple_dict_value_slot,
388
+ range_required_slots,
389
+ ) = self._get_range_associated_slots(slot)
356
390
  # if the range class has an ID and the slot is not inlined as a list, then we need to consider
357
391
  # various inlined as dict formats
358
392
  if range_id_slot is not None and not slot.inlined_as_list:
@@ -363,7 +397,9 @@ class JsonSchemaGenerator(Generator):
363
397
  # If the range can be collected as a simple dict, then we can also accept the value
364
398
  # of that simple dict directly.
365
399
  if range_simple_dict_value_slot is not None:
366
- additionalProps.append(self.get_subschema_for_slot(range_simple_dict_value_slot))
400
+ additionalProps.append(
401
+ self.get_subschema_for_slot(range_simple_dict_value_slot)
402
+ )
367
403
 
368
404
  # If the range has no required slots, then null is acceptable
369
405
  if len(range_required_slots) == 0:
@@ -374,14 +410,13 @@ class JsonSchemaGenerator(Generator):
374
410
  if len(additionalProps) == 1:
375
411
  additionalProps = additionalProps[0]
376
412
  else:
377
- additionalProps = JsonSchema({
378
- "anyOf": additionalProps
379
- })
380
- prop = JsonSchema({
381
- "type": "object",
382
- "additionalProperties": additionalProps
383
- })
384
- self.top_level_schema.add_lax_def(reference, self.aliased_slot_name(range_id_slot))
413
+ additionalProps = JsonSchema({"anyOf": additionalProps})
414
+ prop = JsonSchema(
415
+ {"type": "object", "additionalProperties": additionalProps}
416
+ )
417
+ self.top_level_schema.add_lax_def(
418
+ reference, self.aliased_slot_name(range_id_slot)
419
+ )
385
420
  else:
386
421
  prop = JsonSchema.array_of(JsonSchema.ref_for(reference))
387
422
  else:
@@ -397,15 +432,15 @@ class JsonSchemaGenerator(Generator):
397
432
  if slot_is_multivalued:
398
433
  prop = JsonSchema.array_of(prop)
399
434
 
400
- prop.add_keyword('description', slot.description)
435
+ prop.add_keyword("description", slot.description)
401
436
 
402
437
  own_constraints = self.get_value_constraints_for_slot(slot)
403
438
 
404
439
  if prop.is_array:
405
440
  all_element_constraints = self.get_value_constraints_for_slot(slot.all_members)
406
441
  any_element_constraints = self.get_value_constraints_for_slot(slot.has_member)
407
- prop.add_keyword('minItems', slot.minimum_cardinality)
408
- prop.add_keyword('maxItems', slot.maximum_cardinality)
442
+ prop.add_keyword("minItems", slot.minimum_cardinality)
443
+ prop.add_keyword("maxItems", slot.maximum_cardinality)
409
444
  prop["items"].update(own_constraints)
410
445
  prop["items"].update(all_element_constraints)
411
446
  if any_element_constraints:
@@ -414,37 +449,38 @@ class JsonSchemaGenerator(Generator):
414
449
  prop.update(own_constraints)
415
450
 
416
451
  if prop.is_object:
417
- prop.add_keyword('minProperties', slot.minimum_cardinality)
418
- prop.add_keyword('maxProperties', slot.maximum_cardinality)
452
+ prop.add_keyword("minProperties", slot.minimum_cardinality)
453
+ prop.add_keyword("maxProperties", slot.maximum_cardinality)
419
454
 
420
455
  bool_subschema = JsonSchema()
421
456
  if slot.any_of is not None and len(slot.any_of) > 0:
422
- bool_subschema['anyOf'] = [self.get_subschema_for_slot(s) for s in slot.any_of]
457
+ bool_subschema["anyOf"] = [self.get_subschema_for_slot(s) for s in slot.any_of]
423
458
 
424
459
  if slot.all_of is not None and len(slot.all_of) > 0:
425
- bool_subschema['allOf'] = [self.get_subschema_for_slot(s) for s in slot.all_of]
460
+ bool_subschema["allOf"] = [self.get_subschema_for_slot(s) for s in slot.all_of]
426
461
 
427
462
  if slot.exactly_one_of is not None and len(slot.exactly_one_of) > 0:
428
- bool_subschema['oneOf'] = [self.get_subschema_for_slot(s) for s in slot.exactly_one_of]
463
+ bool_subschema["oneOf"] = [self.get_subschema_for_slot(s) for s in slot.exactly_one_of]
429
464
 
430
465
  if slot.none_of is not None and len(slot.none_of) > 0:
431
- bool_subschema['not'] = {
432
- 'anyOf': [self.get_subschema_for_slot(s) for s in slot.none_of]
466
+ bool_subschema["not"] = {
467
+ "anyOf": [self.get_subschema_for_slot(s) for s in slot.none_of]
433
468
  }
434
469
 
435
470
  if bool_subschema:
436
471
  if prop.is_array:
437
- if 'items' not in prop:
438
- prop['items'] = {}
472
+ if "items" not in prop:
473
+ prop["items"] = {}
439
474
  prop["type"] = "array"
440
- prop['items'].update(bool_subschema)
475
+ prop["items"].update(bool_subschema)
441
476
  else:
442
477
  prop.update(bool_subschema)
443
478
 
444
479
  return prop
445
480
 
446
-
447
- def handle_class_slot(self, subschema: JsonSchema, cls: ClassDefinition, slot: SlotDefinition) -> None:
481
+ def handle_class_slot(
482
+ self, subschema: JsonSchema, cls: ClassDefinition, slot: SlotDefinition
483
+ ) -> None:
448
484
  class_id_slot = self.schemaview.get_identifier_slot(cls.name, use_key=True)
449
485
  slot_is_required = slot.required or slot == class_id_slot
450
486
 
@@ -452,7 +488,7 @@ class JsonSchemaGenerator(Generator):
452
488
  prop = self.get_subschema_for_slot(slot)
453
489
  subschema.add_property(aliased_slot_name, prop, slot_is_required)
454
490
 
455
- def serialize(self, **kwargs) -> str:
491
+ def generate(self) -> dict:
456
492
  self.start_schema()
457
493
  for enum_definition in self.schemaview.all_enums().values():
458
494
  self.handle_enum(enum_definition)
@@ -460,26 +496,37 @@ class JsonSchemaGenerator(Generator):
460
496
  for class_definition in self.schemaview.all_classes().values():
461
497
  self.handle_class(class_definition)
462
498
 
463
- return self.top_level_schema.to_json(sort_keys=True, indent=self.indent if self.indent > 0 else None)
464
-
465
- def _get_range_associated_slots(self, slot: SlotDefinition) -> Tuple[Union[SlotDefinition, None], Union[SlotDefinition, None], Union[List[SlotDefinition], None]]:
499
+ return self.top_level_schema
500
+
501
+ def serialize(self, **kwargs) -> str:
502
+ return self.generate().to_json(
503
+ sort_keys=True, indent=self.indent if self.indent > 0 else None
504
+ )
505
+
506
+ def _get_range_associated_slots(
507
+ self, slot: SlotDefinition
508
+ ) -> Tuple[
509
+ Union[SlotDefinition, None], Union[SlotDefinition, None], Union[List[SlotDefinition], None]
510
+ ]:
466
511
  range_class = self.schemaview.get_class(slot.range)
467
512
  if range_class is None:
468
513
  return None, None, None
469
-
514
+
470
515
  range_class_id_slot = self.schemaview.get_identifier_slot(range_class.name, use_key=True)
471
516
  if range_class_id_slot is None:
472
517
  return None, None, None
473
-
518
+
474
519
  non_id_slots = [
475
- s for s in self.schemaview.class_induced_slots(range_class.name) if s.name != range_class_id_slot.name
520
+ s
521
+ for s in self.schemaview.class_induced_slots(range_class.name)
522
+ if s.name != range_class_id_slot.name
476
523
  ]
477
524
  non_id_required_slots = [s for s in non_id_slots if s.required]
478
525
 
479
526
  range_simple_dict_value_slot = None
480
527
  if len(non_id_slots) == 1:
481
528
  range_simple_dict_value_slot = non_id_slots[0]
482
-
529
+
483
530
  return range_class_id_slot, range_simple_dict_value_slot, non_id_required_slots
484
531
 
485
532
 
@@ -524,7 +571,7 @@ When handling range constraints, include all descendants of the range class inst
524
571
  help="""
525
572
  If this is a positive number the resulting JSON will be pretty-printed with that indent level. Set to 0 to
526
573
  disable pretty-printing and return the most compact JSON representation
527
- """
574
+ """,
528
575
  )
529
576
  @click.version_option(__version__, "-V", "--version")
530
577
  def cli(yamlfile, **kwargs):
@@ -39,7 +39,7 @@ class LinkmlGenerator(Generator):
39
39
  self.schemaview = SchemaView(self.schema, merge_imports=self.mergeimports)
40
40
 
41
41
  def materialize_classes(self) -> None:
42
- """Materialize class slots from schema as attribues, in place"""
42
+ """Materialize class slots from schema as attributes, in place"""
43
43
  all_classes = self.schemaview.all_classes()
44
44
 
45
45
  for c_name, c_def in all_classes.items():