linkml 1.6.7__py3-none-any.whl → 1.6.9__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.
@@ -33,6 +33,10 @@ URI: {{ gen.uri_link(element) }}
33
33
  ```{{ gen.mermaid_directive() }}
34
34
  {{ gen.mermaid_diagram([element.name]) }}
35
35
  ```
36
+ {% elif diagram_type == "plantuml_class_diagram" %}
37
+ ```puml
38
+ {{ gen.mermaid_diagram([element.name]) }}
39
+ ```
36
40
  {% else %}
37
41
  {% include "class_diagram.md.jinja2" %}
38
42
  {% endif %}
@@ -13,7 +13,7 @@
13
13
  {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
14
14
  {{ gen.name(element) }} : {{gen.name(s)}}
15
15
  {% if s.range not in gen.all_type_object_names() %}
16
- {{ gen.name(element) }} --|> {{ s.range }} : {{ gen.name(s) }}
16
+ {{ gen.name(element) }} --> {{ s.range }} : {{ gen.name(s) }}
17
17
  {% endif %}
18
18
  {% endfor %}
19
19
  ```
@@ -27,7 +27,7 @@
27
27
  {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
28
28
  {{ gen.name(element) }} : {{gen.name(s)}}
29
29
  {% if s.range not in gen.all_type_object_names() %}
30
- {{ gen.name(element) }} --|> {{ s.range }} : {{ gen.name(s) }}
30
+ {{ gen.name(element) }} --> {{ s.range }} : {{ gen.name(s) }}
31
31
  {% endif %}
32
32
  {% endfor %}
33
33
  ```
@@ -41,7 +41,7 @@
41
41
  {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
42
42
  {{ gen.name(element) }} : {{gen.name(s)}}
43
43
  {% if s.range not in gen.all_type_object_names() %}
44
- {{ gen.name(element) }} --|> {{ s.range }} : {{ gen.name(s) }}
44
+ {{ gen.name(element) }} --> {{ s.range }} : {{ gen.name(s) }}
45
45
  {% endif %}
46
46
  {% endfor %}
47
47
  ```
@@ -52,7 +52,7 @@
52
52
  {% for s in schemaview.class_induced_slots(element.name)|sort(attribute='name') -%}
53
53
  {{ gen.name(element) }} : {{gen.name(s)}}
54
54
  {% if s.range not in gen.all_type_object_names() %}
55
- {{ gen.name(element) }} --|> {{ s.range }} : {{ gen.name(s) }}
55
+ {{ gen.name(element) }} --> {{ s.range }} : {{ gen.name(s) }}
56
56
  {% endif %}
57
57
  {% endfor %}
58
58
  ```
@@ -28,6 +28,7 @@ from linkml_runtime.utils.schemaview import SchemaView
28
28
 
29
29
  from linkml._version import __version__
30
30
  from linkml.generators.erdiagramgen import ERDiagramGenerator
31
+ from linkml.generators.plantumlgen import PlantumlGenerator
31
32
  from linkml.utils.generator import Generator, shared_arguments
32
33
  from linkml.workspaces.example_runner import ExampleRunner
33
34
 
@@ -38,7 +39,8 @@ class MarkdownDialect(Enum):
38
39
 
39
40
 
40
41
  class DiagramType(Enum):
41
- uml_class_diagram = "uml_class_diagram"
42
+ mermaid_class_diagram = "mermaid_class_diagram"
43
+ plantuml_class_diagram = "plantuml_class_diagram"
42
44
  er_diagram = "er_diagram"
43
45
 
44
46
 
@@ -593,8 +595,13 @@ class DocGenerator(Generator):
593
595
  return erdgen.serialize_classes(class_names, follow_references=True, max_hops=2)
594
596
  else:
595
597
  return erdgen.serialize()
596
- elif self.diagram_type.value == DiagramType.uml_class_diagram.value:
598
+ elif self.diagram_type.value == DiagramType.mermaid_class_diagram.value:
597
599
  self.logger.info("This is currently handled in the jinja templates")
600
+ elif self.diagram_type.value == DiagramType.plantuml_class_diagram.value:
601
+ plantumlgen = PlantumlGenerator(self.schema)
602
+ plantuml_diagram = plantumlgen.serialize(classes=class_names)
603
+ self.logger.debug(f"Created PlantUML diagram for class: {class_names}")
604
+ return plantuml_diagram
598
605
  else:
599
606
  raise NotImplementedError(f"Diagram type {self.diagram_type} not implemented")
600
607
 
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
- from collections import UserDict
5
4
  from copy import deepcopy
6
5
  from dataclasses import dataclass, field
7
6
  from typing import Any, Dict, List, Optional, Tuple, Union
@@ -43,7 +42,7 @@ json_schema_types: Dict[str, Tuple[str, Optional[str]]] = {
43
42
  }
44
43
 
45
44
 
46
- class JsonSchema(UserDict):
45
+ class JsonSchema(dict):
47
46
  OPTIONAL_IDENTIFIER_SUFFIX = "__identifier_optional"
48
47
 
49
48
  def __init__(self, *args, **kwargs):
@@ -83,7 +82,9 @@ class JsonSchema(UserDict):
83
82
  lax_cls["required"].remove(identifier_name)
84
83
  self["$defs"][canonical_name + self.OPTIONAL_IDENTIFIER_SUFFIX] = lax_cls
85
84
 
86
- def add_property(self, name: str, subschema: "JsonSchema", required: bool = False) -> None:
85
+ def add_property(
86
+ self, name: str, subschema: "JsonSchema", *, value_required: bool = False, value_disallowed: bool = False
87
+ ) -> None:
87
88
  canonical_name = underscore(name)
88
89
 
89
90
  if "properties" not in self:
@@ -91,12 +92,32 @@ class JsonSchema(UserDict):
91
92
 
92
93
  self["properties"][canonical_name] = subschema
93
94
 
94
- if required:
95
+ if value_required:
95
96
  if "required" not in self:
96
97
  self["required"] = []
97
98
 
98
99
  self["required"].append(canonical_name)
99
100
 
101
+ # JSON Schema does not have a very natural way to express that a property cannot be present.
102
+ # The apparent best way to do it is to use:
103
+ # {
104
+ # properties: {
105
+ # foo: ...
106
+ # },
107
+ # not: {
108
+ # required: ['foo']
109
+ # }
110
+ # }
111
+ # The {required: [foo]} subschema evaluates to true if the foo property is present with any
112
+ # value. Wrapping that in a `not` keyword inverts that condition.
113
+ if value_disallowed:
114
+ if "not" not in self:
115
+ self["not"] = {}
116
+ if "required" not in self["not"]:
117
+ self["not"]["required"] = []
118
+
119
+ self["not"]["required"].append(canonical_name)
120
+
100
121
  def add_keyword(self, keyword: str, value: Any):
101
122
  if value is None:
102
123
  return
@@ -112,7 +133,7 @@ class JsonSchema(UserDict):
112
133
  return self.get("type") == "object"
113
134
 
114
135
  def to_json(self, **kwargs) -> str:
115
- return json.dumps(self.data, default=lambda d: d.data, **kwargs)
136
+ return json.dumps(self, **kwargs)
116
137
 
117
138
  @classmethod
118
139
  def ref_for(cls, class_name: Union[str, List[str]], identifier_optional: bool = False):
@@ -190,7 +211,7 @@ class JsonSchemaGenerator(Generator):
190
211
 
191
212
  self.top_level_schema = JsonSchema(
192
213
  {
193
- "$schema": "http://json-schema.org/draft-07/schema#",
214
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
194
215
  "$id": self.schema.id,
195
216
  "metamodel_version": metamodel_version,
196
217
  "version": self.schema.version if self.schema.version else None,
@@ -204,13 +225,15 @@ class JsonSchemaGenerator(Generator):
204
225
  if cls.mixin or cls.abstract:
205
226
  return
206
227
 
228
+ subschema_type = "object"
207
229
  additional_properties = False
208
230
  if self.is_class_unconstrained(cls):
231
+ subschema_type = ["null", "boolean", "object", "number", "string"]
209
232
  additional_properties = True
210
233
 
211
234
  class_subschema = JsonSchema(
212
235
  {
213
- "type": "object",
236
+ "type": subschema_type,
214
237
  "additionalProperties": additional_properties,
215
238
  "description": be(cls.description),
216
239
  }
@@ -220,42 +243,44 @@ class JsonSchemaGenerator(Generator):
220
243
  self.handle_class_slot(subschema=class_subschema, cls=cls, slot=slot_definition)
221
244
 
222
245
  rule_subschemas = []
223
- for rule in cls.rules:
224
- subschema = JsonSchema()
246
+ for ancestor_class_name in self.schemaview.class_ancestors(cls.name):
247
+ ancestor_class = self.schemaview.get_class(ancestor_class_name)
248
+ for rule in ancestor_class.rules:
249
+ subschema = JsonSchema()
225
250
 
226
- open_world = rule.open_world
227
- if open_world is None:
228
- open_world = False
251
+ open_world = rule.open_world
252
+ if open_world is None:
253
+ open_world = False
229
254
 
230
- if_subschema = self.get_subschema_for_anonymous_class(rule.preconditions, properties_required=True)
231
- if if_subschema:
232
- subschema["if"] = if_subschema
255
+ if_subschema = self.get_subschema_for_anonymous_class(rule.preconditions, properties_required=True)
256
+ if if_subschema:
257
+ subschema["if"] = if_subschema
233
258
 
234
- then_subschema = self.get_subschema_for_anonymous_class(
235
- rule.postconditions, properties_required=not open_world
236
- )
237
- if then_subschema:
238
- subschema["then"] = then_subschema
259
+ then_subschema = self.get_subschema_for_anonymous_class(
260
+ rule.postconditions, properties_required=not open_world
261
+ )
262
+ if then_subschema:
263
+ subschema["then"] = then_subschema
239
264
 
240
- # same as required requirements as postconditions?
241
- else_subschema = self.get_subschema_for_anonymous_class(
242
- rule.elseconditions, properties_required=not open_world
243
- )
244
- if else_subschema:
245
- subschema["else"] = else_subschema
265
+ # same as required requirements as postconditions?
266
+ else_subschema = self.get_subschema_for_anonymous_class(
267
+ rule.elseconditions, properties_required=not open_world
268
+ )
269
+ if else_subschema:
270
+ subschema["else"] = else_subschema
246
271
 
247
- rule_subschemas.append(subschema)
272
+ rule_subschemas.append(subschema)
248
273
 
249
- if rule.bidirectional:
250
- inverse_subschema = JsonSchema()
274
+ if rule.bidirectional:
275
+ inverse_subschema = JsonSchema()
251
276
 
252
- if then_subschema:
253
- inverse_subschema["if"] = then_subschema
277
+ if then_subschema:
278
+ inverse_subschema["if"] = then_subschema
254
279
 
255
- if if_subschema:
256
- inverse_subschema["then"] = if_subschema
280
+ if if_subschema:
281
+ inverse_subschema["then"] = if_subschema
257
282
 
258
- rule_subschemas.append(inverse_subschema)
283
+ rule_subschemas.append(inverse_subschema)
259
284
 
260
285
  if len(rule_subschemas) == 1:
261
286
  class_subschema.update(rule_subschemas[0])
@@ -285,20 +310,20 @@ class JsonSchemaGenerator(Generator):
285
310
  subschema = JsonSchema()
286
311
  for slot in cls.slot_conditions.values():
287
312
  prop = self.get_subschema_for_slot(slot, omit_type=True)
313
+ value_required = False
314
+ value_disallowed = False
288
315
  if slot.value_presence:
289
316
  if slot.value_presence == PresenceEnum(PresenceEnum.PRESENT):
290
- this_properties_required = True
317
+ value_required = True
291
318
  elif slot.value_presence == PresenceEnum(PresenceEnum.ABSENT):
292
- this_properties_required = False
293
- # make the slot unsatisfiable
294
- prop["enum"] = []
295
- else:
296
- this_properties_required = False
319
+ value_disallowed = True
297
320
  elif slot.required is not None:
298
- this_properties_required = slot.required
321
+ value_required = slot.required
299
322
  else:
300
- this_properties_required = properties_required
301
- subschema.add_property(self.aliased_slot_name(slot), prop, this_properties_required)
323
+ value_required = properties_required
324
+ subschema.add_property(
325
+ self.aliased_slot_name(slot), prop, value_required=value_required, value_disallowed=value_disallowed
326
+ )
302
327
 
303
328
  if cls.any_of is not None and len(cls.any_of) > 0:
304
329
  subschema["anyOf"] = [self.get_subschema_for_anonymous_class(c, properties_required) for c in cls.any_of]
@@ -343,7 +368,9 @@ class JsonSchemaGenerator(Generator):
343
368
  enum_schema["enum"] = permissible_values_texts
344
369
  self.top_level_schema.add_def(enum.name, enum_schema)
345
370
 
346
- def get_type_info_for_slot_subschema(self, slot: AnonymousSlotExpression) -> Tuple[str, str, Union[str, List[str]]]:
371
+ def get_type_info_for_slot_subschema(
372
+ self, slot: Union[SlotDefinition, AnonymousSlotExpression]
373
+ ) -> Tuple[str, str, Union[str, List[str]]]:
347
374
  # JSON Schema type (https://json-schema.org/understanding-json-schema/reference/type.html)
348
375
  typ = None
349
376
  # Reference to a JSON schema entity (https://json-schema.org/understanding-json-schema/structuring.html#ref)
@@ -379,7 +406,7 @@ class JsonSchemaGenerator(Generator):
379
406
 
380
407
  return (typ, fmt, reference)
381
408
 
382
- def get_value_constraints_for_slot(self, slot: Union[AnonymousSlotExpression, None]) -> JsonSchema:
409
+ def get_value_constraints_for_slot(self, slot: Union[SlotDefinition, AnonymousSlotExpression, None]) -> JsonSchema:
383
410
  if slot is None:
384
411
  return JsonSchema()
385
412
 
@@ -397,11 +424,6 @@ class JsonSchemaGenerator(Generator):
397
424
  constraints.add_keyword("maximum", slot.maximum_value)
398
425
  constraints.add_keyword("const", slot.equals_string)
399
426
  constraints.add_keyword("const", slot.equals_number)
400
- if slot.value_presence:
401
- if slot.value_presence == PresenceEnum(PresenceEnum.PRESENT):
402
- constraints.add_keyword("required", True)
403
- elif slot.value_presence == PresenceEnum(PresenceEnum.ABSENT):
404
- constraints.add_keyword("enum", [])
405
427
  return constraints
406
428
 
407
429
  def get_subschema_for_slot(self, slot: SlotDefinition, omit_type: bool = False) -> JsonSchema:
@@ -503,17 +525,22 @@ class JsonSchemaGenerator(Generator):
503
525
 
504
526
  def handle_class_slot(self, subschema: JsonSchema, cls: ClassDefinition, slot: SlotDefinition) -> None:
505
527
  class_id_slot = self.schemaview.get_identifier_slot(cls.name, use_key=True)
506
- slot_is_required = slot.required or slot == class_id_slot
528
+ value_required = (
529
+ slot.required or slot == class_id_slot or slot.value_presence == PresenceEnum(PresenceEnum.PRESENT)
530
+ )
531
+ value_disallowed = slot.value_presence == PresenceEnum(PresenceEnum.ABSENT)
507
532
 
508
533
  aliased_slot_name = self.aliased_slot_name(slot)
509
534
  prop = self.get_subschema_for_slot(slot)
510
- subschema.add_property(aliased_slot_name, prop, slot_is_required)
535
+ subschema.add_property(
536
+ aliased_slot_name, prop, value_required=value_required, value_disallowed=value_disallowed
537
+ )
511
538
 
512
539
  if slot.designates_type:
513
540
  type_value = get_type_designator_value(self.schemaview, slot, cls)
514
541
  prop["enum"] = [type_value]
515
542
 
516
- def generate(self) -> dict:
543
+ def generate(self) -> JsonSchema:
517
544
  self.start_schema()
518
545
  for enum_definition in self.schemaview.all_enums().values():
519
546
  self.handle_enum(enum_definition)
@@ -43,6 +43,9 @@ def default_template(pydantic_ver: str = "1", extra_fields: str = "forbid") -> s
43
43
  from __future__ import annotations
44
44
  from datetime import datetime, date
45
45
  from enum import Enum
46
+ {% if uses_numpy -%}
47
+ import numpy as np
48
+ {%- endif %}
46
49
  from typing import List, Dict, Optional, Any, Union"""
47
50
  if pydantic_ver == "1":
48
51
  template += """
@@ -86,7 +89,9 @@ class ConfiguredBaseModel(BaseModel):
86
89
  extra = '{extra_fields}',
87
90
  arbitrary_types_allowed=True,
88
91
  use_enum_values = True)
92
+ pass
89
93
  """
94
+
90
95
  ### ENUMS ###
91
96
  template += """
92
97
  {% for e in enums.values() %}
@@ -157,7 +162,7 @@ class {{ c.name }}
157
162
  raise ValueError(f"Invalid {{attr.name}} format: {v}")
158
163
  return v
159
164
  {% endif -%}
160
- {% endfor %}
165
+ {% endfor %}
161
166
  {% endfor %}
162
167
  """
163
168
  elif pydantic_ver == "2":
@@ -209,7 +214,7 @@ class {{ c.name }}
209
214
  raise ValueError(f"Invalid {{attr.name}} format: {v}")
210
215
  return v
211
216
  {% endif -%}
212
- {% endfor %}
217
+ {% endfor %}
213
218
  {% endfor %}
214
219
  """
215
220
 
@@ -344,6 +349,8 @@ class PydanticGenerator(OOCodeGenerator):
344
349
  # Multivalued slots that are either not inlined (just an identifier) or are
345
350
  # inlined as lists should get default_factory list, if they're inlined but
346
351
  # not as a list, that means a dictionary
352
+ elif "linkml:elements" in slot.implements:
353
+ slot_values[camelcase(class_def.name)][slot.name] = None
347
354
  elif slot.multivalued:
348
355
  has_identifier_slot = self.range_class_has_identifier_slot(slot)
349
356
 
@@ -535,6 +542,8 @@ class PydanticGenerator(OOCodeGenerator):
535
542
  )
536
543
  enums = self.generate_enums(sv.all_enums())
537
544
 
545
+ uses_numpy = False
546
+
538
547
  sorted_classes = self.sort_classes(list(sv.all_classes().values()))
539
548
  self.sorted_class_names = [camelcase(c.name) for c in sorted_classes]
540
549
 
@@ -587,7 +596,13 @@ class PydanticGenerator(OOCodeGenerator):
587
596
  else:
588
597
  raise Exception(f"Could not generate python range for {class_name}.{s.name}")
589
598
 
590
- if s.multivalued:
599
+ if "linkml:elements" in s.implements:
600
+ # TODO add support for xarray
601
+ pyrange = "np.ndarray"
602
+ if "linkml:ColumnOrderedArray" in class_def.implements:
603
+ raise NotImplementedError("Cannot generate Pydantic code for ColumnOrderedArrays.")
604
+ uses_numpy = True
605
+ elif s.multivalued:
591
606
  if s.inlined or s.inlined_as_list:
592
607
  collection_key = self.generate_collection_key(slot_ranges, s, class_def)
593
608
  else:
@@ -609,6 +624,7 @@ class PydanticGenerator(OOCodeGenerator):
609
624
  metamodel_version=self.schema.metamodel_version,
610
625
  version=self.schema.version,
611
626
  class_isa_plus_mixins=self.get_class_isa_plus_mixins(),
627
+ uses_numpy=uses_numpy,
612
628
  )
613
629
  return code
614
630
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  """
4
4
  import os
5
+ import urllib.parse as urlparse
5
6
  from dataclasses import dataclass, field
6
7
  from typing import List, Optional, Union
7
8
 
@@ -139,7 +140,24 @@ class ShExGenerator(Generator):
139
140
  constraint.predicate = self.namespaces.uri_for(slot.slot_uri)
140
141
  constraint.min = int(bool(slot.required))
141
142
  constraint.max = 1 if not slot.multivalued else -1
142
- constraint.valueExpr = self._class_or_type_uri(slot.range)
143
+ if slot.range in self.schema.enums:
144
+ # Handle permissible values from enums
145
+ enum = self.schema.enums[slot.range]
146
+ values = []
147
+ for value in enum.permissible_values.values():
148
+ if value.meaning:
149
+ values.append(self.namespaces.uri_for(value.meaning))
150
+ else:
151
+ value_uri = f"{self._class_or_type_uri(enum.name)}#{urlparse.quote(value.text)}"
152
+ values.append(value_uri)
153
+ if values:
154
+ node_constraint = NodeConstraint(
155
+ # id=self._class_or_type_uri(slot.range),
156
+ values=values,
157
+ )
158
+ constraint.valueExpr = node_constraint
159
+ else:
160
+ constraint.valueExpr = self._class_or_type_uri(slot.range)
143
161
 
144
162
  def end_schema(self, output: Optional[str] = None, **_) -> None:
145
163
  self.shex.shapes = self.shapes if self.shapes else [Shape()]
@@ -84,11 +84,11 @@ export function is{{gen.name(c)}}(o: object): o is {{gen.name(c)}} {
84
84
  {%- set rcs = gen.required_slots(c) %}
85
85
  {%- set comp = "&&" if rcs else "||" %}
86
86
  {%- set cs = rcs if rcs else view.class_slots(c.name, direct=False) %}
87
- return {
87
+ return (
88
88
  {%- for sn in cs %}
89
89
  '{{sn}}' in o {%- if not loop.last %} {{comp}}{% endif -%}
90
90
  {%- endfor %}
91
- }
91
+ )
92
92
  }
93
93
 
94
94
  export function to{{gen.name(c)}}(o: {{gen.name(c)}}): {{gen.name(c)}} {
linkml/linter/cli.py CHANGED
@@ -16,7 +16,7 @@ YAML_SUFFIXES = [".yml", ".yaml"]
16
16
  DEFAULT_CONFIG_FILES = [".linkmllint.yaml", ".linkmllint.yml"]
17
17
 
18
18
 
19
- def get_yaml_files(root: Path) -> Iterable[str]:
19
+ def get_yaml_files(root: Path, accept_dot_files: bool) -> Iterable[str]:
20
20
  if root.is_file():
21
21
  if root.suffix not in YAML_SUFFIXES:
22
22
  raise click.UsageError("SCHEMA must be a YAML file")
@@ -26,6 +26,8 @@ def get_yaml_files(root: Path) -> Iterable[str]:
26
26
  for file in files:
27
27
  if file in DEFAULT_CONFIG_FILES:
28
28
  continue
29
+ if file.startswith(".") and not accept_dot_files:
30
+ continue
29
31
  path = Path(dir, file)
30
32
  if path.suffix in YAML_SUFFIXES:
31
33
  yield str(path)
@@ -36,6 +38,7 @@ def get_yaml_files(root: Path) -> Iterable[str]:
36
38
  "schema",
37
39
  type=click.Path(exists=True, dir_okay=True, file_okay=True, resolve_path=True, path_type=Path),
38
40
  )
41
+ @click.option("-a", "--all", is_flag=True, default=False, help="Process files that start with '.'.")
39
42
  @click.option(
40
43
  "-c",
41
44
  "--config",
@@ -82,6 +85,7 @@ def get_yaml_files(root: Path) -> Iterable[str]:
82
85
  def main(
83
86
  schema: Path,
84
87
  fix: bool,
88
+ all: bool,
85
89
  config: str,
86
90
  format: str,
87
91
  validate: bool,
@@ -124,7 +128,7 @@ def main(
124
128
  error_count = 0
125
129
  warning_count = 0
126
130
  formatter.start_report()
127
- for path in get_yaml_files(schema):
131
+ for path in get_yaml_files(schema, all):
128
132
  formatter.start_schema(path)
129
133
  report = linter.lint(path, fix=fix, validate_schema=validate, validate_only=validate_only)
130
134
  for problem in report:
linkml/linter/linter.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import inspect
2
- import json
3
2
  from copy import deepcopy
4
3
  from dataclasses import dataclass
5
4
  from functools import lru_cache
@@ -38,8 +37,9 @@ def get_named_config(name: str) -> Dict[str, Any]:
38
37
  @lru_cache
39
38
  def get_metamodel_validator() -> jsonschema.Validator:
40
39
  meta_json_gen = JsonSchemaGenerator(LOCAL_METAMODEL_YAML_FILE, not_closed=False)
41
- meta_json_schema = json.loads(meta_json_gen.serialize())
42
- validator = jsonschema.Draft7Validator(meta_json_schema)
40
+ meta_json_schema = meta_json_gen.generate()
41
+ validator_cls = jsonschema.validators.validator_for(meta_json_schema, default=jsonschema.Draft7Validator)
42
+ validator = validator_cls(meta_json_schema, format_checker=validator_cls.FORMAT_CHECKER)
43
43
  return validator
44
44
 
45
45
 
linkml/utils/datautils.py CHANGED
@@ -79,14 +79,14 @@ def infer_root_class(sv: SchemaView) -> Optional[ClassDefinitionName]:
79
79
  if c.tree_root:
80
80
  return c.name
81
81
  refs = defaultdict(int)
82
- for cn in sv.all_class().keys():
82
+ for cn in sv.all_classes().keys():
83
83
  for sn in sv.class_slots(cn):
84
84
  slot = sv.induced_slot(sn, cn)
85
85
  r = slot.range
86
- if r in sv.all_class():
86
+ if r in sv.all_classes():
87
87
  for a in sv.class_ancestors(r):
88
88
  refs[a] += 1
89
- candidates = [cn for cn in sv.all_class().keys() if cn not in refs]
89
+ candidates = [cn for cn in sv.all_classes().keys() if cn not in refs]
90
90
 
91
91
  # throw Exception if unambiguous root cannot be inferred
92
92
  if len(candidates) > 1:
@@ -106,7 +106,7 @@ def infer_index_slot(sv: SchemaView, root_class: ClassDefinitionName) -> Optiona
106
106
  index_slots = []
107
107
  for sn in sv.class_slots(root_class):
108
108
  slot = sv.induced_slot(sn, root_class)
109
- if slot.multivalued and slot.range in sv.all_class():
109
+ if slot.multivalued and slot.range in sv.all_classes():
110
110
  index_slots.append(sn)
111
111
  if len(index_slots) == 1:
112
112
  return index_slots[0]
@@ -1,7 +1,6 @@
1
1
  import os
2
2
  from typing import Any, Iterator, Optional
3
3
 
4
- import jsonschema
5
4
  from jsonschema.exceptions import best_match
6
5
 
7
6
  from linkml.validator.plugins.validation_plugin import ValidationPlugin
@@ -41,12 +40,11 @@ class JsonschemaValidationPlugin(ValidationPlugin):
41
40
  :return: Iterator over validation results
42
41
  :rtype: Iterator[ValidationResult]
43
42
  """
44
- json_schema = context.json_schema(
43
+ validator = context.json_schema_validator(
45
44
  closed=self.closed,
46
45
  include_range_class_descendants=self.include_range_class_descendants,
47
46
  path_override=self.json_schema_path,
48
47
  )
49
- validator = jsonschema.Draft7Validator(json_schema, format_checker=jsonschema.Draft7Validator.FORMAT_CHECKER)
50
48
  for error in validator.iter_errors(instance):
51
49
  best_error = best_match([error])
52
50
  yield ValidationResult(
@@ -3,6 +3,7 @@ import os
3
3
  from functools import lru_cache
4
4
  from typing import Optional
5
5
 
6
+ import jsonschema
6
7
  from linkml_runtime import SchemaView
7
8
  from linkml_runtime.linkml_model import SchemaDefinition
8
9
 
@@ -29,26 +30,29 @@ class ValidationContext:
29
30
  return self._target_class
30
31
 
31
32
  @lru_cache
32
- def json_schema(
33
+ def json_schema_validator(
33
34
  self,
34
35
  *,
35
36
  closed: bool,
36
37
  include_range_class_descendants: bool,
37
38
  path_override: Optional[os.PathLike] = None,
38
- ):
39
+ ) -> jsonschema.Validator:
39
40
  if path_override:
40
41
  with open(path_override) as json_schema_file:
41
- return json.load(json_schema_file)
42
+ json_schema = json.load(json_schema_file)
43
+ else:
44
+ not_closed = not closed
45
+ jsonschema_gen = JsonSchemaGenerator(
46
+ schema=self._schema,
47
+ mergeimports=True,
48
+ top_class=self._target_class,
49
+ not_closed=not_closed,
50
+ include_range_class_descendants=include_range_class_descendants,
51
+ )
52
+ json_schema = jsonschema_gen.generate()
42
53
 
43
- not_closed = not closed
44
- jsonschema_gen = JsonSchemaGenerator(
45
- schema=self._schema,
46
- mergeimports=True,
47
- top_class=self._target_class,
48
- not_closed=not_closed,
49
- include_range_class_descendants=include_range_class_descendants,
50
- )
51
- return jsonschema_gen.generate()
54
+ validator_cls = jsonschema.validators.validator_for(json_schema, default=jsonschema.Draft7Validator)
55
+ return validator_cls(json_schema, format_checker=validator_cls.FORMAT_CHECKER)
52
56
 
53
57
  def pydantic_model(self, *, closed: bool):
54
58
  module = self._pydantic_module(closed=closed)
@@ -105,7 +105,8 @@ class JsonSchemaDataValidator(DataValidator):
105
105
  jsonschema_obj = _generate_jsonschema(
106
106
  self._hashable_schema, target_class_name, closed, self.include_range_class_descendants
107
107
  )
108
- validator = jsonschema.Draft7Validator(jsonschema_obj, format_checker=jsonschema.Draft7Validator.FORMAT_CHECKER)
108
+ validator_cls = jsonschema.validators.validator_for(jsonschema_obj, default=jsonschema.Draft7Validator)
109
+ validator = validator_cls(jsonschema_obj, format_checker=validator_cls.FORMAT_CHECKER)
109
110
  for error in validator.iter_errors(data):
110
111
  best_error = best_match([error])
111
112
  # TODO: This should return some kind of standard validation result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml
3
- Version: 1.6.7
3
+ Version: 1.6.9
4
4
  Summary: Linked Open Data Modeling Language
5
5
  Home-page: https://linkml.io/linkml/
6
6
  Keywords: schema,linked data,data modeling,rdf,owl,biolink
@@ -18,7 +18,6 @@ Classifier: Programming Language :: Python :: 3.9
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.10
21
- Classifier: Programming Language :: Python :: 3.7
22
21
  Classifier: Programming Language :: Python :: 3.8
23
22
  Classifier: Programming Language :: Python :: 3.9
24
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -6,8 +6,8 @@ linkml/generators/__init__.py,sha256=qa22iTeBrcds6ndst_iTPVWLQtBF6NGyhVngBhbhIpc
6
6
  linkml/generators/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  linkml/generators/common/type_designators.py,sha256=lgVcIRJJ-yCvVGeP9U_gQZpm77UmJBQo9Bh3lGJITno,1956
8
8
  linkml/generators/csvgen.py,sha256=h-Mj-uNrzuETx_G3dW6eFbk1jj0NHSwqbehqR5tA6PM,2955
9
- linkml/generators/docgen/class.md.jinja2,sha256=05Kbl83ApOMzA7gZeRJjPU7jkZHCIsaKN1BYWNMRTfM,3275
10
- linkml/generators/docgen/class_diagram.md.jinja2,sha256=ShRhNnIae54Y_4zrfXuIF6BjX8oCHRZJnovE8G1fjOU,2542
9
+ linkml/generators/docgen/class.md.jinja2,sha256=_nQr8ge57ZkX3Hrh-cqPUm8Z7HCnJuuRCgYm0g21mS8,3381
10
+ linkml/generators/docgen/class_diagram.md.jinja2,sha256=lLU_0sQZBttOX5Wiup2oZwrxHuiQtbU9ZgLi66aJvOo,2538
11
11
  linkml/generators/docgen/common_metadata.md.jinja2,sha256=zy8Ua3gDtAAq8VA3e3O3ft9W7eJopVZaq5efP8LU_hU,1321
12
12
  linkml/generators/docgen/enum.md.jinja2,sha256=mXnUrRkleY2bOTEyAZ5c4pcUnqhs6BNa8a-4LVve-eo,1014
13
13
  linkml/generators/docgen/index.md.jinja2,sha256=wXUYTmayPLFltC0vbGE_Mf6m3GkkWav7FOEjCvEpHp4,1466
@@ -16,7 +16,7 @@ linkml/generators/docgen/schema.md.jinja2,sha256=xlENfnzNRYgPT_0tdqNFxgklVM4Qf5B
16
16
  linkml/generators/docgen/slot.md.jinja2,sha256=XVd0M4gKx9Q2fONcsUGBRj_bJivyN4P9jhj9IO496jQ,2817
17
17
  linkml/generators/docgen/subset.md.jinja2,sha256=fTNIpAkml5RKFbbtLore3IAzFN1cISVsyL1ru2-Z4oA,2665
18
18
  linkml/generators/docgen/type.md.jinja2,sha256=QmCMJZrFwP33eHkggBVtypbyrxTb-XZn9vHOYojVaYk,635
19
- linkml/generators/docgen.py,sha256=zsLzbXN2t9pafQubEnb7QshufMKBMqUWjdLE1OyFyq8,33296
19
+ linkml/generators/docgen.py,sha256=Kwg4ZIXL2e9G-q-_CUoj8Bgnpyyvvh5fKPnseDcTK04,33755
20
20
  linkml/generators/dotgen.py,sha256=CnbVY6CO1OMuiYXYnvxgNN2IW1mtOQW-J-QnwZlXkUI,5012
21
21
  linkml/generators/erdiagramgen.py,sha256=Gu-_nhLuEPTsYYaoV6tNS1V6cZ2dNJdm6YwxC0VGl7g,10315
22
22
  linkml/generators/excelgen.py,sha256=OhVzuQaDESYpAGR8Zv13hiWlDDJA8ugtSZatBJH0hzA,7737
@@ -28,7 +28,7 @@ linkml/generators/javagen/java_record_template.jinja2,sha256=OQZffLSy_xR3FIhQMlt
28
28
  linkml/generators/javagen.py,sha256=oZfUowKnBQNdklRqOM-yKptO4Te4y4N5ItIc5jTuWKU,5427
29
29
  linkml/generators/jsonldcontextgen.py,sha256=2TUzEzFBX7wDOYJ51Kg0hp2CVXcOzJhgIbq0CrzL2oc,7832
30
30
  linkml/generators/jsonldgen.py,sha256=KQYurjqp3gI0bevjzmrw4WDEgz4Yf4o4TJfZsqK4_vs,7575
31
- linkml/generators/jsonschemagen.py,sha256=WxwjuoBweicb14X0kThRl06LGV8AEdGcRVMunpDQXEc,24231
31
+ linkml/generators/jsonschemagen.py,sha256=8fzOoR5ZRgchg7r-QxZsbkxlnfD7ytrLw6qKjIRgmWo,25294
32
32
  linkml/generators/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  linkml/generators/linkmlgen.py,sha256=QhIPA1v2g_g5fien3ZKN-L6TkDk3t7puVFrcoEnwkwY,3540
34
34
  linkml/generators/markdowngen.py,sha256=ZPLahEPjWsrAsKq4CHbVDXeVd0n1NO-2STs068-g0Ac,32948
@@ -39,11 +39,11 @@ linkml/generators/plantumlgen.py,sha256=Vs__5x9ioiT4IBTbvZUpgT8MsYJ0amfBL64MB_nm
39
39
  linkml/generators/prefixmapgen.py,sha256=JJ7hgzuqKVfFZrbDV76Dk8dR2NHsmpp-eNUAspXkfwA,4626
40
40
  linkml/generators/projectgen.py,sha256=g3JR2oXPM_QXhWUGukP9ts1P7tqxIeABaRdv130gbo4,9578
41
41
  linkml/generators/protogen.py,sha256=9YfxBZkQdBWwsUbstxEUR4xRWNuAKSfz9zXPhgIYePU,2328
42
- linkml/generators/pydanticgen.py,sha256=lwdIJ81fRoDUs0AyYw6eS5xZ06Br8Q8xXb39qRBbB0I,24904
42
+ linkml/generators/pydanticgen.py,sha256=Ak0bpRoQsirnnBd091NiJvMZ-2d0qaQbhxADHy5ocwY,25529
43
43
  linkml/generators/pythongen.py,sha256=yGYlRJ4rNm2QQLFDjyuUnqCyKlzz-b3eSOhkSu8aCwI,52491
44
44
  linkml/generators/rdfgen.py,sha256=LxzYBaFEkV7rlf54nWv_6H6AGcWMRXwkaeVXq9VYEc8,2693
45
45
  linkml/generators/shaclgen.py,sha256=KxNmDZW2ciCuSqUhJ65TxLTjF8jME1FmN5SaWJCuW9k,8662
46
- linkml/generators/shexgen.py,sha256=Awtn5SyjS-TUcVCwMdT0F7hNO4K8VcSCYBaFru45Mwg,8994
46
+ linkml/generators/shexgen.py,sha256=E-R8jg5Z3JzzsVAIPu6Fs9FtFF7k-I5-5vMwG1jGW2U,9858
47
47
  linkml/generators/sparqlgen.py,sha256=xIT4abjYTjPvAjczZ2mkqfap5z8-AImK_jaCvgZyRGs,6120
48
48
  linkml/generators/sqlalchemy/__init__.py,sha256=mb9AC1rIFkSiNZhhG0TAk45ol9PjS1XvsrvCjgfVUpQ,249
49
49
  linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py,sha256=X_Ws1NUBikMI5HuNgEhl_PIeWM-B-c2B0W9KUBH4QTg,2542
@@ -55,11 +55,11 @@ linkml/generators/sssomgen.py,sha256=yur3q7so9uwIELWZaZRzkJwNbz_ppBL7IQki9XLIM3k
55
55
  linkml/generators/string_template.md,sha256=kRcfic6entgIaJdpSg6GF3jcjC9wbKsCVM6wVT2qipc,1788
56
56
  linkml/generators/summarygen.py,sha256=aeWAUeOaWhn1WHZKnJ3TcKVku_6psrw88ubMc-GQzEc,2924
57
57
  linkml/generators/terminusdbgen.py,sha256=_po9KnOz6Uoctb9Y4fJuU_voW4uA5pepvZTmHLQ2_DE,4470
58
- linkml/generators/typescriptgen.py,sha256=QYa8HGWFY3wL3bNZBax4LG_T62b2sXIAzDYsIp_ry3Y,8565
58
+ linkml/generators/typescriptgen.py,sha256=VDJ-97jk7zqYx6XlnIcxZmY8mCv885T7D2brKJHaihI,8565
59
59
  linkml/generators/yamlgen.py,sha256=bI4ebWe8vebR3xGVgto5TH0ZDej-RlZPTYqJ26BxKpg,1651
60
60
  linkml/generators/yumlgen.py,sha256=547atFkqJd9WtL_LdR-tUME9g7lWaFa3yHFvpnX6J1E,12145
61
61
  linkml/linter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
- linkml/linter/cli.py,sha256=RpZYyR_Re0E7yh1fifvQVJagi8KvLDaseF3Zgs2E3uw,4254
62
+ linkml/linter/cli.py,sha256=BpQiETcRfQlZsI1-JALyU9wXPgPOm_KE_n11p0iJaC8,4494
63
63
  linkml/linter/config/datamodel/.linkmllint.yaml,sha256=40rNhcL35FXQSjlVBMWnyPgplwEUqFT_sJmeZqUzxMw,292
64
64
  linkml/linter/config/datamodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
65
  linkml/linter/config/datamodel/config.py,sha256=4HtqHF2ZiYx0Izm50eUQRvygsm1xSnkc49w0nd9zbGk,16837
@@ -72,7 +72,7 @@ linkml/linter/formatters/json_formatter.py,sha256=rrOELuETUCFTl1Ewnrus9Rx9hl2g4u
72
72
  linkml/linter/formatters/markdown_formatter.py,sha256=sumF2MNJYL5oipLwCftCr3AiaCmVhd9V6di9aYpJ29I,2605
73
73
  linkml/linter/formatters/terminal_formatter.py,sha256=xmNwlRieVCssRt3fpnNP5WWw_YXGmaBsbxFoMWufy5M,2085
74
74
  linkml/linter/formatters/tsv_formatter.py,sha256=1VW-1Tv16mwhIDpHdExYpJJR3z42K5_XK2C-4mmYYdE,871
75
- linkml/linter/linter.py,sha256=Bh153eN1Gb1EL7kM20w15CTPovbJYJoravXd5zxiVHQ,4984
75
+ linkml/linter/linter.py,sha256=B4zPHkS_8qWV2WJY74HDiIroajsXRmfJLIlwYky-cyA,5101
76
76
  linkml/linter/rules.py,sha256=Bk87FMKgdF_aSBqeLe1uQartcTxV3-ZwUu-QRp2iK4s,11164
77
77
  linkml/reporting/__init__.py,sha256=Jo0V_nyEcnWhMukMW-bqW9dlbgCfaRlWm3CO-XzgU84,92
78
78
  linkml/reporting/model.py,sha256=-10yNfk8wuRC48ZI-akrWvtlJ9a6RYWET2TzlZV3XXo,8622
@@ -84,7 +84,7 @@ linkml/transformers/schema_renamer.py,sha256=Cr18TyktX64b5iFh5V6R_ILPVzXjbDYVDDZ
84
84
  linkml/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
85
  linkml/utils/cli_utils.py,sha256=MdVbox1qr6kDUHUCWAmFhV-D6ebs_nn8vVwB8KDQfew,742
86
86
  linkml/utils/converter.py,sha256=rdhCI7Tsjddr3o1rVBfMq5gQubk_GE6fqlBBmyxI5_M,6270
87
- linkml/utils/datautils.py,sha256=2XWM9LBSVp8v3SwIZECrX3SjDUYzdnP-syjp6YdL89E,3734
87
+ linkml/utils/datautils.py,sha256=QlbzwXykh5Fphfe5KxPo6_ekXfniLbHiEGJtLWjUrvY,3742
88
88
  linkml/utils/datavalidator.py,sha256=kBdWaVi8IZT1bOwEJgJYx-wZAb_PTBObB9nHpYORfKA,472
89
89
  linkml/utils/execute_tutorial.py,sha256=T4kHTSyz3ItJGEUZxVjR-3yLVKnOr5Ix4NMGE47-IuE,6912
90
90
  linkml/utils/generator.py,sha256=WAlP_gfZfAZYNklsh8l4GtiWZ338kjLg7xpQAANgUNg,38217
@@ -109,23 +109,23 @@ linkml/validator/loaders/loader.py,sha256=YKQXmSZhCymvvb2Dbr4lVCKVDt-lOIayzRD7CW
109
109
  linkml/validator/loaders/passthrough_loader.py,sha256=fQvBZqzGnpdwpZio4Mbehmsb6ePpzcacHPuR1QWuhFM,525
110
110
  linkml/validator/loaders/yaml_loader.py,sha256=ZsMz-oGDkX9oPEOVcKnFRGhU9q0fKoHcL6BLOkCeB4c,914
111
111
  linkml/validator/plugins/__init__.py,sha256=F7Un7bfdpSRXvk8LTb7Z4Q22rCtJJKjNkqUfIVLFtT8,702
112
- linkml/validator/plugins/jsonschema_validation_plugin.py,sha256=GII3zGI727rFJlqZ40Yf5L8aP3i1NiO3IRfoTyAbmlE,2656
112
+ linkml/validator/plugins/jsonschema_validation_plugin.py,sha256=hMPlCyAdMg_N_OHiHMaxcG0fsOA50V4oBNqorNz_oqE,2528
113
113
  linkml/validator/plugins/pydantic_validation_plugin.py,sha256=C-Vp74bz5oqp5V-iuhXW8p0PPDoFY8NCU5x36Ur5Og4,1985
114
114
  linkml/validator/plugins/recommended_slots_plugin.py,sha256=kOdoYQyye47nLA7BjorVmydS84nGpiVy3MoCbPq1Ymo,2308
115
115
  linkml/validator/plugins/validation_plugin.py,sha256=9SMHF8b2bgG9-8351e8bY676e0A4aEBJSXvMjMF5kXg,1548
116
116
  linkml/validator/report.py,sha256=kkkuh-IZF9--cO-2wGjwP3PDLvOcjjvC8AOlxXUIOAM,870
117
- linkml/validator/validation_context.py,sha256=MmOwLk4cF_Cy7fPdFK61Eti3c3dgzKSIu6r_PmkkoZs,2388
117
+ linkml/validator/validation_context.py,sha256=2BYbw0czAYepJUNoIINyTnmW6SrgK6et7SNvH5ejzgU,2700
118
118
  linkml/validator/validator.py,sha256=sdWbAOlnNYQWnZSEIuLpGQqH3W4cNq_8M_CdLtsNMH0,5648
119
119
  linkml/validators/__init__.py,sha256=43W3J5NPKhwa3ZFHLRYsJMocwQKWGYCF9Ki9r0ccGbc,202
120
- linkml/validators/jsonschemavalidator.py,sha256=_v0finzU2RGPC5xo0CylYge9XkY7oAigcly2SKLwFuI,7865
120
+ linkml/validators/jsonschemavalidator.py,sha256=_ku7MQGqA6KplbHMiyzDRqgRwwXGloobhLptuXqLCp4,7951
121
121
  linkml/validators/sparqlvalidator.py,sha256=JowuZ5KxmWkldgWIXAb8DJi7YCPm8x3it0QkgM4lSi0,4612
122
122
  linkml/workspaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
123
123
  linkml/workspaces/datamodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  linkml/workspaces/datamodel/workspaces.py,sha256=4HdkqweGNfMPqnB1_Onc9DcTfkhoagTRcqruh08nRoI,14905
125
125
  linkml/workspaces/datamodel/workspaces.yaml,sha256=EjVrwPpeRZqJRjuGyyDRxxFzuv55SiLIXPBRUG6HStU,4233
126
126
  linkml/workspaces/example_runner.py,sha256=uumXyPZ7xUJSZyRtjDP4TCCxgKSSOfebpufXc0_l0jY,11610
127
- linkml-1.6.7.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
128
- linkml-1.6.7.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
129
- linkml-1.6.7.dist-info/entry_points.txt,sha256=za8r49Z5gcz3rAYTZLbxw5EPZr1rGuxSe1uiRUpf8R0,2143
130
- linkml-1.6.7.dist-info/METADATA,sha256=O7e8VhzgYyaMkWRTvG4asdaG7nHdDYRdo4dnqAXghX0,3496
131
- linkml-1.6.7.dist-info/RECORD,,
127
+ linkml-1.6.9.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
128
+ linkml-1.6.9.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
129
+ linkml-1.6.9.dist-info/entry_points.txt,sha256=za8r49Z5gcz3rAYTZLbxw5EPZr1rGuxSe1uiRUpf8R0,2143
130
+ linkml-1.6.9.dist-info/METADATA,sha256=KsqVPfLcVUKxAlb-hz4AFdd6Pk5VGUjtmGx3wj5x7U8,3446
131
+ linkml-1.6.9.dist-info/RECORD,,
File without changes