linkml 1.8.7__py3-none-any.whl → 1.9.1rc2__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 (90) hide show
  1. linkml/generators/common/build.py +1 -7
  2. linkml/generators/common/ifabsent_processor.py +20 -20
  3. linkml/generators/common/lifecycle.py +2 -1
  4. linkml/generators/common/naming.py +1 -1
  5. linkml/generators/common/template.py +5 -5
  6. linkml/generators/common/type_designators.py +1 -3
  7. linkml/generators/csvgen.py +3 -3
  8. linkml/generators/docgen/class.md.jinja2 +1 -1
  9. linkml/generators/docgen/enum.md.jinja2 +1 -1
  10. linkml/generators/docgen/schema.md.jinja2 +1 -1
  11. linkml/generators/docgen/slot.md.jinja2 +4 -1
  12. linkml/generators/docgen/subset.md.jinja2 +1 -1
  13. linkml/generators/docgen/type.md.jinja2 +1 -1
  14. linkml/generators/docgen.py +20 -25
  15. linkml/generators/dotgen.py +4 -4
  16. linkml/generators/erdiagramgen.py +7 -7
  17. linkml/generators/excelgen.py +2 -3
  18. linkml/generators/golanggen.py +2 -2
  19. linkml/generators/golrgen.py +3 -3
  20. linkml/generators/jsonldcontextgen.py +4 -4
  21. linkml/generators/jsonschemagen.py +5 -5
  22. linkml/generators/linkmlgen.py +10 -2
  23. linkml/generators/markdowngen.py +8 -10
  24. linkml/generators/mermaidclassdiagramgen.py +2 -2
  25. linkml/generators/oocodegen.py +10 -10
  26. linkml/generators/owlgen.py +19 -18
  27. linkml/generators/plantumlgen.py +15 -15
  28. linkml/generators/prefixmapgen.py +5 -5
  29. linkml/generators/projectgen.py +10 -10
  30. linkml/generators/pydanticgen/array.py +15 -21
  31. linkml/generators/pydanticgen/build.py +4 -4
  32. linkml/generators/pydanticgen/includes.py +1 -1
  33. linkml/generators/pydanticgen/pydanticgen.py +24 -28
  34. linkml/generators/pydanticgen/template.py +36 -36
  35. linkml/generators/pythongen.py +21 -29
  36. linkml/generators/rdfgen.py +2 -2
  37. linkml/generators/shaclgen.py +19 -10
  38. linkml/generators/shexgen.py +3 -3
  39. linkml/generators/sparqlgen.py +3 -3
  40. linkml/generators/sqlalchemygen.py +2 -2
  41. linkml/generators/terminusdbgen.py +2 -3
  42. linkml/generators/typescriptgen.py +3 -3
  43. linkml/generators/yumlgen.py +13 -13
  44. linkml/linter/cli.py +1 -1
  45. linkml/linter/config/datamodel/config.py +207 -213
  46. linkml/linter/config/datamodel/config.yaml +51 -3
  47. linkml/linter/config/default.yaml +3 -0
  48. linkml/linter/formatters/markdown_formatter.py +2 -2
  49. linkml/linter/linter.py +4 -3
  50. linkml/linter/rules.py +38 -19
  51. linkml/reporting/model.py +11 -15
  52. linkml/transformers/logical_model_transformer.py +9 -8
  53. linkml/transformers/relmodel_transformer.py +6 -6
  54. linkml/transformers/schema_renamer.py +2 -2
  55. linkml/utils/converter.py +1 -1
  56. linkml/utils/deprecation.py +3 -3
  57. linkml/utils/execute_tutorial.py +5 -6
  58. linkml/utils/generator.py +17 -16
  59. linkml/utils/helpers.py +2 -2
  60. linkml/utils/logictools.py +5 -4
  61. linkml/utils/mergeutils.py +51 -5
  62. linkml/utils/schema_builder.py +8 -8
  63. linkml/utils/schema_fixer.py +8 -8
  64. linkml/utils/schemaloader.py +16 -15
  65. linkml/utils/schemasynopsis.py +29 -29
  66. linkml/utils/sqlutils.py +5 -5
  67. linkml/utils/typereferences.py +5 -6
  68. linkml/utils/validation.py +2 -2
  69. linkml/validator/cli.py +7 -6
  70. linkml/validator/loaders/delimited_file_loader.py +2 -1
  71. linkml/validator/loaders/json_loader.py +2 -1
  72. linkml/validator/loaders/loader.py +2 -1
  73. linkml/validator/loaders/passthrough_loader.py +2 -1
  74. linkml/validator/loaders/yaml_loader.py +2 -1
  75. linkml/validator/plugins/jsonschema_validation_plugin.py +2 -1
  76. linkml/validator/plugins/pydantic_validation_plugin.py +2 -1
  77. linkml/validator/plugins/recommended_slots_plugin.py +3 -2
  78. linkml/validator/plugins/shacl_validation_plugin.py +2 -1
  79. linkml/validator/plugins/validation_plugin.py +1 -1
  80. linkml/validator/report.py +3 -3
  81. linkml/validator/validator.py +3 -2
  82. linkml/validators/jsonschemavalidator.py +6 -5
  83. linkml/workspaces/datamodel/workspaces.py +21 -26
  84. linkml/workspaces/example_runner.py +7 -6
  85. {linkml-1.8.7.dist-info → linkml-1.9.1rc2.dist-info}/METADATA +6 -9
  86. linkml-1.9.1rc2.dist-info/RECORD +162 -0
  87. {linkml-1.8.7.dist-info → linkml-1.9.1rc2.dist-info}/WHEEL +1 -1
  88. linkml-1.8.7.dist-info/RECORD +0 -162
  89. {linkml-1.8.7.dist-info → linkml-1.9.1rc2.dist-info}/LICENSE +0 -0
  90. {linkml-1.8.7.dist-info → linkml-1.9.1rc2.dist-info}/entry_points.txt +0 -0
linkml/utils/generator.py CHANGED
@@ -20,10 +20,11 @@ import logging
20
20
  import os
21
21
  import re
22
22
  import sys
23
+ from collections.abc import Mapping
23
24
  from dataclasses import dataclass, field
24
25
  from functools import lru_cache
25
26
  from pathlib import Path
26
- from typing import Callable, ClassVar, Dict, List, Mapping, Optional, Set, TextIO, Type, Union, cast
27
+ from typing import Callable, ClassVar, Optional, TextIO, Union, cast
27
28
 
28
29
  import click
29
30
  from click import Argument, Command, Option
@@ -103,7 +104,7 @@ class Generator(metaclass=abc.ABCMeta):
103
104
  requires_metamodel: ClassVar[bool] = True
104
105
  """Generator queries an instance of the metamodel"""
105
106
 
106
- valid_formats: ClassVar[List[str]] = []
107
+ valid_formats: ClassVar[list[str]] = []
107
108
  """Allowed formats - first format is default"""
108
109
 
109
110
  visit_all_class_slots: ClassVar[bool] = False
@@ -162,13 +163,13 @@ class Generator(metaclass=abc.ABCMeta):
162
163
  """Working directory or base URL of sources.
163
164
  Setting this is necessary for correct retrieval of relative imports."""
164
165
 
165
- metamodel_name_map: Dict[str, str] = None
166
+ metamodel_name_map: dict[str, str] = None
166
167
  """Allows mapping of names of metamodel elements such as slot, etc"""
167
168
 
168
169
  importmap: Optional[Union[str, Optional[Mapping[str, str]]]] = None
169
170
  """File name of import mapping file -- maps import name/uri to target"""
170
171
 
171
- emit_prefixes: Set[str] = field(default_factory=lambda: set())
172
+ emit_prefixes: set[str] = field(default_factory=lambda: set())
172
173
  """Prefixes to emit, for RDF-based generators"""
173
174
 
174
175
  metamodel: SchemaLoader = None
@@ -416,7 +417,7 @@ class Generator(metaclass=abc.ABCMeta):
416
417
  # =============================
417
418
  # Helper methods
418
419
  # =============================
419
- def own_slots(self, cls: Union[ClassDefinitionName, ClassDefinition]) -> List[SlotDefinition]:
420
+ def own_slots(self, cls: Union[ClassDefinitionName, ClassDefinition]) -> list[SlotDefinition]:
420
421
  """Return the list of slots owned the class definition. An "own slot" is any ``cls`` slot that does not appear
421
422
  in the class is_a parent. Own_slots include:
422
423
 
@@ -442,7 +443,7 @@ class Generator(metaclass=abc.ABCMeta):
442
443
  seen.add(sname_base)
443
444
  return sorted(rval, key=lambda s: s.name) if self.sort_class_slots else rval
444
445
 
445
- def own_slot_names(self, cls: Union[ClassDefinitionName, ClassDefinition]) -> List[SlotDefinitionName]:
446
+ def own_slot_names(self, cls: Union[ClassDefinitionName, ClassDefinition]) -> list[SlotDefinitionName]:
446
447
  return [slot.name for slot in self.own_slots(cls)]
447
448
 
448
449
  def all_slots(
@@ -450,8 +451,8 @@ class Generator(metaclass=abc.ABCMeta):
450
451
  cls: Union[ClassDefinitionName, ClassDefinition],
451
452
  *,
452
453
  cls_slots_first: bool = False,
453
- seen: Optional[Set[ClassDefinitionName]] = None,
454
- ) -> List[SlotDefinition]:
454
+ seen: Optional[set[ClassDefinitionName]] = None,
455
+ ) -> list[SlotDefinition]:
455
456
  """Return all slots that are part of the class definition. This includes all is_a, mixin and apply_to slots
456
457
  but does NOT include slot_usage targets. If class B has a slot_usage entry for slot "s", only the slot
457
458
  definition for the redefined slot will be included, not its base. Slots are added in the order they appear
@@ -499,7 +500,7 @@ class Generator(metaclass=abc.ABCMeta):
499
500
  )
500
501
  )
501
502
 
502
- def ancestors(self, element: Union[ClassDefinition, SlotDefinition]) -> List[ElementName]:
503
+ def ancestors(self, element: Union[ClassDefinition, SlotDefinition]) -> list[ElementName]:
503
504
  """Return an ordered list of ancestor names for the supplied slot or class
504
505
 
505
506
  @param element: Slot or class name or definition
@@ -507,7 +508,7 @@ class Generator(metaclass=abc.ABCMeta):
507
508
  """
508
509
  return [element.name] + ([] if element.is_a is None else self.ancestors(self.parent(element)))
509
510
 
510
- def neighborhood(self, elements: Union[str, ElementName, List[ElementName]]) -> References:
511
+ def neighborhood(self, elements: Union[str, ElementName, list[ElementName]]) -> References:
511
512
  """Return a list of all slots, classes and types that touch any element in elements, including the element
512
513
  itself
513
514
 
@@ -581,7 +582,7 @@ class Generator(metaclass=abc.ABCMeta):
581
582
 
582
583
  return touches
583
584
 
584
- def range_type_path(self, typ: TypeDefinition) -> List[str]:
585
+ def range_type_path(self, typ: TypeDefinition) -> list[str]:
585
586
  """
586
587
  Return a formatted list of range types from the base up
587
588
 
@@ -618,14 +619,14 @@ class Generator(metaclass=abc.ABCMeta):
618
619
  return None
619
620
 
620
621
  @staticmethod
621
- def enum_identifier_path(enum_or_enumname: Union[str, EnumDefinition]) -> List[str]:
622
+ def enum_identifier_path(enum_or_enumname: Union[str, EnumDefinition]) -> list[str]:
622
623
  """Return an enum_identifier path"""
623
624
  return [
624
625
  "str",
625
626
  camelcase(enum_or_enumname.name if isinstance(enum_or_enumname, EnumDefinition) else enum_or_enumname),
626
627
  ]
627
628
 
628
- def class_identifier_path(self, cls_or_clsname: Union[str, ClassDefinition], force_non_key: bool) -> List[str]:
629
+ def class_identifier_path(self, cls_or_clsname: Union[str, ClassDefinition], force_non_key: bool) -> list[str]:
629
630
  """
630
631
  Return the path closure to a class identifier if the class has a key and force_non_key is false otherwise
631
632
  return a dictionary closure.
@@ -657,7 +658,7 @@ class Generator(metaclass=abc.ABCMeta):
657
658
  return self.class_identifier_path(cls.is_a, False) + [pathname]
658
659
  return self.slot_range_path(identifier_slot) + [pathname]
659
660
 
660
- def slot_range_path(self, slot_or_name: Union[str, SlotDefinition]) -> List[str]:
661
+ def slot_range_path(self, slot_or_name: Union[str, SlotDefinition]) -> list[str]:
661
662
  """
662
663
  Return an ordered list of slot ranges from distal to proximal
663
664
 
@@ -796,7 +797,7 @@ class Generator(metaclass=abc.ABCMeta):
796
797
  return self.schema.prefixes[PrefixPrefixPrefix(self.schema.default_prefix)].prefix_reference
797
798
 
798
799
  # TODO: add lru cache once we get identity into the classes
799
- def domain_slots(self, cls: ClassDefinition) -> List[SlotDefinition]:
800
+ def domain_slots(self, cls: ClassDefinition) -> list[SlotDefinition]:
800
801
  """Return all slots in the class definition that are owned by the class"""
801
802
  domain_slots = []
802
803
  for slot_name in cls.slots:
@@ -912,7 +913,7 @@ class Generator(metaclass=abc.ABCMeta):
912
913
  return cls.class_uri == "linkml:Any"
913
914
 
914
915
 
915
- def shared_arguments(g: Type[Generator]) -> Callable[[Command], Command]:
916
+ def shared_arguments(g: type[Generator]) -> Callable[[Command], Command]:
916
917
  def verbosity_callback(ctx, param, verbose):
917
918
  if verbose >= 2:
918
919
  logging.basicConfig(level=logging.DEBUG, force=True)
linkml/utils/helpers.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  from functools import lru_cache
3
- from typing import List, Tuple, Union
3
+ from typing import Union
4
4
 
5
5
  from linkml_runtime import SchemaView
6
6
  from linkml_runtime.linkml_model.meta import (
@@ -28,7 +28,7 @@ def convert_to_snake_case(str):
28
28
  @lru_cache(None)
29
29
  def get_range_associated_slots(
30
30
  schemaview: SchemaView, range_class: ClassDefinition
31
- ) -> Tuple[Union[SlotDefinition, None], Union[SlotDefinition, None], Union[List[SlotDefinition], None]]:
31
+ ) -> tuple[Union[SlotDefinition, None], Union[SlotDefinition, None], Union[list[SlotDefinition], None]]:
32
32
  if isinstance(range_class, ElementName):
33
33
  range_class = schemaview.get_class(range_class)
34
34
  if range_class is None:
@@ -1,7 +1,8 @@
1
1
  import operator
2
+ from collections.abc import Collection
2
3
  from copy import deepcopy
3
4
  from itertools import product
4
- from typing import Any, Collection, List, Optional, Tuple, Type
5
+ from typing import Any, Optional
5
6
 
6
7
 
7
8
  def member_of(item: Any, collection: Collection[Any]) -> bool:
@@ -121,7 +122,7 @@ class And(Expression):
121
122
  """Conjunction of expressions"""
122
123
 
123
124
  def __init__(self, *operands: Expression):
124
- self.operands: List[Expression] = list(operands)
125
+ self.operands: list[Expression] = list(operands)
125
126
 
126
127
  def __str__(self):
127
128
  return f'({" & ".join(str(operand) for operand in self.operands)})'
@@ -173,7 +174,7 @@ class Term(Expression):
173
174
 
174
175
 
175
176
  class IsIn(Term):
176
- def __init__(self, element: Expression, collection: List[Any]):
177
+ def __init__(self, element: Expression, collection: list[Any]):
177
178
  self.predicate = "in"
178
179
  self.operands = [element, collection]
179
180
 
@@ -640,7 +641,7 @@ def _unsat(x: Expression, y: Expression) -> bool:
640
641
  return False
641
642
 
642
643
 
643
- def compose_operators(boolean_op: Type[Expression], op1: str, v1: Any, op2: str, v2: Any) -> Optional[Tuple]:
644
+ def compose_operators(boolean_op: type[Expression], op1: str, v1: Any, op2: str, v2: Any) -> Optional[tuple]:
644
645
  """
645
646
  Compose two expressions.
646
647
 
@@ -1,7 +1,10 @@
1
1
  import dataclasses
2
2
  import logging
3
+ import os
3
4
  from copy import deepcopy
4
- from typing import Dict, List, Optional, Union, cast
5
+ from pathlib import Path
6
+ from typing import Optional, Union, cast
7
+ from urllib.parse import urlparse
5
8
 
6
9
  from linkml_runtime.linkml_model.meta import (
7
10
  ClassDefinition,
@@ -33,7 +36,7 @@ def merge_schemas(
33
36
  if target.license is None:
34
37
  target.license = mergee.license
35
38
 
36
- target.imports += [imp for imp in mergee.imports if imp not in target.imports]
39
+ resolve_merged_imports(target, mergee, imported_from)
37
40
  set_from_schema(mergee)
38
41
 
39
42
  if namespaces:
@@ -125,8 +128,8 @@ def set_from_schema(schema: SchemaDefinition) -> None:
125
128
 
126
129
 
127
130
  def merge_dicts(
128
- target: Dict[str, Element],
129
- source: Dict[str, Element],
131
+ target: dict[str, Element],
132
+ source: dict[str, Element],
130
133
  imported_from: str,
131
134
  imported_from_uri: str,
132
135
  merge_imports: bool,
@@ -150,7 +153,7 @@ def merge_dicts(
150
153
  def merge_slots(
151
154
  target: Union[SlotDefinition, TypeDefinition],
152
155
  source: Union[SlotDefinition, TypeDefinition],
153
- skip: List[Union[SlotDefinitionName, TypeDefinitionName]] = None,
156
+ skip: list[Union[SlotDefinitionName, TypeDefinitionName]] = None,
154
157
  inheriting: bool = True,
155
158
  ) -> None:
156
159
  """
@@ -232,3 +235,46 @@ def merge_enums(
232
235
  """
233
236
  # TODO: Finish enumeration merge code
234
237
  pass
238
+
239
+
240
+ def resolve_merged_imports(
241
+ target: SchemaDefinition, mergee: SchemaDefinition, imported_from: Optional[str] = None
242
+ ) -> None:
243
+ """Merge the imports in source into target
244
+
245
+ :param target: Containing schema
246
+ :param mergee: mergee
247
+ :param imported_from: file that the mergee was imported from
248
+ :param at_end: True means add mergee to the end. False to the front
249
+ """
250
+
251
+ for imp in mergee.imports:
252
+ if imp is None:
253
+ continue
254
+
255
+ # Skip if already imported
256
+ if imp in target.imports:
257
+ continue
258
+
259
+ # Leave as-is if the import has a scheme (e.g. http://)
260
+ elif urlparse(imp).scheme:
261
+ target.imports.append(imp)
262
+ continue
263
+
264
+ # Leave as-is if the import is an absolute path
265
+ elif Path(imp).is_absolute():
266
+ target.imports.append(imp)
267
+ continue
268
+
269
+ # Adjust relative imports to be relative to the importing file
270
+ elif imp.startswith("."):
271
+ if imported_from is None:
272
+ logger.warning(f"Cannot resolve relative import: {imp}")
273
+ target.imports.append(imp)
274
+ else:
275
+ resolved_imp = os.path.normpath(str(Path(imported_from).parent / Path(imp)))
276
+ target.imports.append(resolved_imp)
277
+
278
+ # By default, add the import as-is
279
+ else:
280
+ target.imports.append(imp)
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any, Dict, List, Optional, Union
2
+ from typing import Any, Optional, Union
3
3
 
4
4
  from linkml_runtime.linkml_model import (
5
5
  ClassDefinition,
@@ -59,9 +59,9 @@ class SchemaBuilder:
59
59
 
60
60
  def add_class(
61
61
  self,
62
- cls: Union[ClassDefinition, Dict, str],
63
- slots: Union[Dict, List[Union[str, SlotDefinition]]] = None,
64
- slot_usage: Union[Dict[str, SlotDefinition], Dict[str, Any], List[SlotDefinition]] = None,
62
+ cls: Union[ClassDefinition, dict, str],
63
+ slots: Union[dict, list[Union[str, SlotDefinition]]] = None,
64
+ slot_usage: Union[dict[str, SlotDefinition], dict[str, Any], list[SlotDefinition]] = None,
65
65
  replace_if_present=False,
66
66
  use_attributes=False,
67
67
  **kwargs,
@@ -125,7 +125,7 @@ class SchemaBuilder:
125
125
 
126
126
  def add_slot(
127
127
  self,
128
- slot: Union[SlotDefinition, Dict, str],
128
+ slot: Union[SlotDefinition, dict, str],
129
129
  class_name: str = None,
130
130
  replace_if_present=False,
131
131
  **kwargs,
@@ -167,7 +167,7 @@ class SchemaBuilder:
167
167
  def add_enum(
168
168
  self,
169
169
  enum_def: Union[EnumDefinition, dict, str],
170
- permissible_values: List[Union[str, PermissibleValue]] = None,
170
+ permissible_values: list[Union[str, PermissibleValue]] = None,
171
171
  replace_if_present=False,
172
172
  **kwargs,
173
173
  ) -> "SchemaBuilder":
@@ -232,7 +232,7 @@ class SchemaBuilder:
232
232
 
233
233
  def add_type(
234
234
  self,
235
- type: Union[TypeDefinition, Dict, str],
235
+ type: Union[TypeDefinition, dict, str],
236
236
  typeof: str = None,
237
237
  uri: str = None,
238
238
  replace_if_present=False,
@@ -262,7 +262,7 @@ class SchemaBuilder:
262
262
  setattr(type, k, v)
263
263
  return self
264
264
 
265
- def as_dict(self) -> Dict:
265
+ def as_dict(self) -> dict:
266
266
  """
267
267
  Returns the schema as a dictionary.
268
268
 
@@ -3,7 +3,7 @@ import re
3
3
  from collections import defaultdict
4
4
  from copy import copy
5
5
  from dataclasses import dataclass
6
- from typing import Any, Callable, Dict, List, Optional, Union
6
+ from typing import Any, Callable, Optional, Union
7
7
 
8
8
  import click
9
9
  import yaml
@@ -25,7 +25,7 @@ logger = logging.getLogger(__name__)
25
25
  pattern = re.compile(r"(?<!^)(?=[A-Z])")
26
26
 
27
27
 
28
- def yaml_rewrite(obj: Any, replacements: Dict[str, Any], include_keys=True) -> Any:
28
+ def yaml_rewrite(obj: Any, replacements: dict[str, Any], include_keys=True) -> Any:
29
29
  if isinstance(obj, YAMLRoot):
30
30
  obj2 = copy(obj)
31
31
  for k, v in vars(obj).items():
@@ -52,7 +52,7 @@ class SchemaFixer:
52
52
  Multiple methods for adding additional information to schemas
53
53
  """
54
54
 
55
- history: List[str] = None
55
+ history: list[str] = None
56
56
 
57
57
  def add_titles(self, schema: SchemaDefinition):
58
58
  """
@@ -111,7 +111,7 @@ class SchemaFixer:
111
111
  must_have_identifier=False,
112
112
  slot_name_func: Callable = None,
113
113
  convert_camel_case=False,
114
- ) -> List[SlotDefinition]:
114
+ ) -> list[SlotDefinition]:
115
115
  """
116
116
  Adds index slots to a container pointing at all top-level classes
117
117
 
@@ -272,7 +272,7 @@ class SchemaFixer:
272
272
  del cls.slot_usage[k]
273
273
 
274
274
  @staticmethod
275
- def implicit_slots(schema: SchemaDefinition) -> Dict[str, Dict]:
275
+ def implicit_slots(schema: SchemaDefinition) -> dict[str, dict]:
276
276
  """
277
277
  Find slots that are implicit in the schema from slot_usage
278
278
 
@@ -317,11 +317,11 @@ class SchemaFixer:
317
317
  @staticmethod
318
318
  def fix_element_names(
319
319
  schema: SchemaDefinition,
320
- schema_dict: Dict[str, Any] = None,
321
- rules: Dict[str, Callable] = None,
320
+ schema_dict: dict[str, Any] = None,
321
+ rules: dict[str, Callable] = None,
322
322
  imports=False,
323
323
  preserve_original_using: Optional[str] = None,
324
- ) -> Union[YAMLRoot, Dict]:
324
+ ) -> Union[YAMLRoot, dict]:
325
325
  """
326
326
  Changes element names to conform to naming conventions.
327
327
 
@@ -1,9 +1,10 @@
1
1
  import logging
2
2
  import os
3
3
  from collections import OrderedDict
4
+ from collections.abc import Iterator, Mapping
4
5
  from copy import deepcopy
5
6
  from pathlib import Path
6
- from typing import Dict, Iterator, List, Mapping, Optional, Set, TextIO, Tuple, Union, cast
7
+ from typing import Optional, TextIO, Union, cast
7
8
  from urllib.parse import urlparse
8
9
 
9
10
  from jsonasobj2 import values
@@ -71,7 +72,7 @@ class SchemaLoader:
71
72
  source_file_size=source_file_size,
72
73
  )
73
74
  # Map from URI to source and version tuple
74
- self.loaded: OrderedDict[str, Tuple[str, str]] = {
75
+ self.loaded: OrderedDict[str, tuple[str, str]] = {
75
76
  self.schema.id: (self.schema.source_file, self.schema.version)
76
77
  }
77
78
  self.base_dir = self._get_base_dir(base_dir)
@@ -82,7 +83,7 @@ class SchemaLoader:
82
83
  self.source_file_size = source_file_size
83
84
  self.synopsis: Optional[SchemaSynopsis] = None
84
85
  self.schema_location: Optional[str] = None
85
- self.schema_defaults: Dict[str, str] = {} # Map from schema URI to default namespace
86
+ self.schema_defaults: dict[str, str] = {} # Map from schema URI to default namespace
86
87
  self.merge_modules = mergeimports
87
88
  self.emit_metadata = emit_metadata
88
89
 
@@ -281,7 +282,7 @@ class SchemaLoader:
281
282
  self.raise_value_error(f"Slot {slot.name}.inverse ({slot.inverse}) is not defined")
282
283
 
283
284
  # Update slots with parental information
284
- merged_slots: List[SlotDefinitionName] = []
285
+ merged_slots: list[SlotDefinitionName] = []
285
286
  for slot in self.schema.slots.values():
286
287
  if not slot.from_schema:
287
288
  slot.from_schema = self.schema.id
@@ -306,12 +307,12 @@ class SchemaLoader:
306
307
  cls.from_schema = self.schema.id
307
308
 
308
309
  # Merge class with its mixins and the like
309
- merged_classes: List[ClassDefinitionName] = []
310
+ merged_classes: list[ClassDefinitionName] = []
310
311
  for cls in self.schema.classes.values():
311
312
  self.merge_class(cls, merged_classes)
312
313
 
313
314
  # Update types with parental information
314
- merged_types: List[TypeDefinitionName] = []
315
+ merged_types: list[TypeDefinitionName] = []
315
316
  for typ in self.schema.types.values():
316
317
  if not typ.base and not typ.typeof:
317
318
  self.raise_value_error(
@@ -466,7 +467,7 @@ class SchemaLoader:
466
467
  )
467
468
 
468
469
  # Check for duplicate class and type names
469
- def check_dups(s1: Set[ElementName], s2: Set[ElementName]) -> Tuple[List[ElementName], str]:
470
+ def check_dups(s1: set[ElementName], s2: set[ElementName]) -> tuple[list[ElementName], str]:
470
471
  if s1.isdisjoint(s2):
471
472
  return [], ""
472
473
 
@@ -616,13 +617,13 @@ class SchemaLoader:
616
617
  self.raise_value_error(f"Subset: {subset} is not defined", subset)
617
618
  return self.schema
618
619
 
619
- def validate_item_names(self, typ: str, names: List[str]) -> None:
620
+ def validate_item_names(self, typ: str, names: list[str]) -> None:
620
621
  # TODO: add a more rigorous syntax check for item names
621
622
  for name in names:
622
623
  if ":" in name:
623
624
  raise self.raise_value_error(f'{typ}: "{name}" - ":" not allowed in identifier', name)
624
625
 
625
- def merge_enum(self, enum: EnumDefinition, merged_enums: List[EnumDefinitionName]) -> None:
626
+ def merge_enum(self, enum: EnumDefinition, merged_enums: list[EnumDefinitionName]) -> None:
626
627
  """
627
628
  Merge parent enumeration information into target enum
628
629
 
@@ -641,7 +642,7 @@ class SchemaLoader:
641
642
  enum.is_a,
642
643
  )
643
644
 
644
- def merge_slot(self, slot: SlotDefinition, merged_slots: List[SlotDefinitionName]) -> None:
645
+ def merge_slot(self, slot: SlotDefinition, merged_slots: list[SlotDefinitionName]) -> None:
645
646
  """
646
647
  Merge parent slot information into target slot
647
648
 
@@ -673,7 +674,7 @@ class SchemaLoader:
673
674
  self.raise_value_error(f'Slot: "{slot.name}" - unknown mixin reference: {mixin}', mixin)
674
675
  merged_slots.append(slot.name)
675
676
 
676
- def merge_class(self, cls: ClassDefinition, merged_classes: List[ClassDefinitionName]) -> None:
677
+ def merge_class(self, cls: ClassDefinition, merged_classes: list[ClassDefinitionName]) -> None:
677
678
  """
678
679
  Merge parent class information into target class
679
680
 
@@ -704,8 +705,8 @@ class SchemaLoader:
704
705
  Slot usages can be used to completely define slots. Iterate over the class hierarchy finding all slot
705
706
  definitions that are introduced strictly as usages and add them to the slots component
706
707
  """
707
- visited: Set[ClassDefinitionName] = set()
708
- visited_usages: Set[SlotDefinitionName] = set() # Slots that are or will be mangled
708
+ visited: set[ClassDefinitionName] = set()
709
+ visited_usages: set[SlotDefinitionName] = set() # Slots that are or will be mangled
709
710
 
710
711
  def located_aliased_parent_slot(owning_class: ClassDefinition, usage_slot: SlotDefinition) -> bool:
711
712
  """Determine whether we are overriding an attributes style slot in the parent class
@@ -839,7 +840,7 @@ class SchemaLoader:
839
840
  if new_val:
840
841
  setattr(new_slot, metaslot_name, new_val)
841
842
 
842
- def merge_type(self, typ: TypeDefinition, merged_types: List[TypeDefinitionName]) -> None:
843
+ def merge_type(self, typ: TypeDefinition, merged_types: list[TypeDefinitionName]) -> None:
843
844
  """
844
845
  Merge parent type information into target type
845
846
  :param typ: target type
@@ -858,7 +859,7 @@ class SchemaLoader:
858
859
  )
859
860
  merged_types.append(typ.name)
860
861
 
861
- def schema_errors(self) -> List[str]:
862
+ def schema_errors(self) -> list[str]:
862
863
  return self.synopsis.errors() if self.synopsis else ["resolve() must be run before error check"]
863
864
 
864
865
  def slot_definition_for(self, slotname: SlotDefinitionName, cls: ClassDefinition) -> Optional[SlotDefinition]:
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Dict, List, Set, Union
2
+ from typing import Union
3
3
 
4
4
  from linkml_runtime.linkml_model.meta import (
5
5
  ClassDefinition,
@@ -36,45 +36,45 @@ class SchemaSynopsis:
36
36
  schema: SchemaDefinition = field(repr=False, compare=False)
37
37
 
38
38
  # References by type -- set by add_ref
39
- typerefs: Dict[TypeDefinitionName, References] = empty_dict() # Type name to all references
40
- slotrefs: Dict[SlotDefinitionName, References] = empty_dict() # Slot name to all references
41
- classrefs: Dict[ClassDefinitionName, References] = empty_dict() # Class name to all references
42
- subsetrefs: Dict[SubsetDefinitionName, References] = empty_dict() # Subset name to references
43
- enumrefs: Dict[EnumDefinitionName, References] = empty_dict() # Enum name to references
39
+ typerefs: dict[TypeDefinitionName, References] = empty_dict() # Type name to all references
40
+ slotrefs: dict[SlotDefinitionName, References] = empty_dict() # Slot name to all references
41
+ classrefs: dict[ClassDefinitionName, References] = empty_dict() # Class name to all references
42
+ subsetrefs: dict[SubsetDefinitionName, References] = empty_dict() # Subset name to references
43
+ enumrefs: dict[EnumDefinitionName, References] = empty_dict() # Enum name to references
44
44
 
45
45
  # Type specific
46
- typebases: Dict[str, Set[TypeDefinitionName]] = empty_dict() # Base referencing types (direct and indirect)
47
- typeofs: Dict[TypeDefinitionName, TypeDefinitionName] = empty_dict() # Type to specializations
46
+ typebases: dict[str, set[TypeDefinitionName]] = empty_dict() # Base referencing types (direct and indirect)
47
+ typeofs: dict[TypeDefinitionName, TypeDefinitionName] = empty_dict() # Type to specializations
48
48
 
49
49
  # Slot specific
50
- slotclasses: Dict[SlotDefinitionName, Set[ClassDefinitionName]] = empty_dict() # Slot to including classes
51
- definingslots: Dict[SlotDefinitionName, Set[ClassDefinitionName]] = empty_dict() # Slot to defining decls
52
- slotusages: Dict[SlotDefinitionName, Set[ClassDefinitionName]] = empty_dict() # Slot to overriding classes
53
- owners: Dict[SlotDefinitionName, Set[ClassDefinitionName]] = empty_dict() # Slot to owning classes (sb. 1)
54
- inverses: Dict[str, Set[str]] = empty_dict() # Slots declared as inverses of other slots
50
+ slotclasses: dict[SlotDefinitionName, set[ClassDefinitionName]] = empty_dict() # Slot to including classes
51
+ definingslots: dict[SlotDefinitionName, set[ClassDefinitionName]] = empty_dict() # Slot to defining decls
52
+ slotusages: dict[SlotDefinitionName, set[ClassDefinitionName]] = empty_dict() # Slot to overriding classes
53
+ owners: dict[SlotDefinitionName, set[ClassDefinitionName]] = empty_dict() # Slot to owning classes (sb. 1)
54
+ inverses: dict[str, set[str]] = empty_dict() # Slots declared as inverses of other slots
55
55
 
56
56
  # Class specific
57
- ownslots: Dict[ClassDefinitionName, Set[SlotDefinitionName]] = empty_dict() # Slots directly owned by class
57
+ ownslots: dict[ClassDefinitionName, set[SlotDefinitionName]] = empty_dict() # Slots directly owned by class
58
58
 
59
59
  # Enum specific
60
- codesets: Dict[URIRef, Set[EnumDefinitionName]] = empty_dict() # Code set URI to enumeration definition
60
+ codesets: dict[URIRef, set[EnumDefinitionName]] = empty_dict() # Code set URI to enumeration definition
61
61
 
62
62
  # Class to slot domains == class.slots
63
63
 
64
64
  # Slot or Class (Definition) specific
65
65
  roots: References = empty_references() # Definitions with no parents
66
- isarefs: Dict[DefinitionName, References] = empty_dict() # Definition to isa references
67
- mixinrefs: Dict[DefinitionName, References] = empty_dict() # Mixin to referencing classes or slots
66
+ isarefs: dict[DefinitionName, References] = empty_dict() # Definition to isa references
67
+ mixinrefs: dict[DefinitionName, References] = empty_dict() # Mixin to referencing classes or slots
68
68
  mixins: References = empty_references() # Definitions declared as mixin
69
69
  abstracts: References = empty_references() # Definitions declared as abstract
70
70
  applytos: References = empty_references() # Definitions that include applytos
71
- applytorefs: Dict[DefinitionName, References] = empty_dict() # Definition to applyier
71
+ applytorefs: dict[DefinitionName, References] = empty_dict() # Definition to applyier
72
72
 
73
73
  # Slot or Type specific
74
- rangerefs: Dict[ElementName, Set[SlotDefinitionName]] = empty_dict() # Type or class to range slot
74
+ rangerefs: dict[ElementName, set[SlotDefinitionName]] = empty_dict() # Type or class to range slot
75
75
 
76
76
  # Element - any type
77
- inschema: Dict[str, References] = empty_references() # Schema name to elements
77
+ inschema: dict[str, References] = empty_references() # Schema name to elements
78
78
 
79
79
  def __post_init__(self):
80
80
  for k, v in self.schema.slots.items():
@@ -237,8 +237,8 @@ class SchemaSynopsis:
237
237
  def _ancestor_is_owned(self, slot: SlotDefinition) -> bool:
238
238
  return bool(slot.is_a) and (slot.is_a in self.owners or self._ancestor_is_owned(self.schema.slots[slot.is_a]))
239
239
 
240
- def errors(self) -> List[str]:
241
- def format_undefineds(refs: Set[Union[str, TypedNode]]) -> List[str]:
240
+ def errors(self) -> list[str]:
241
+ def format_undefineds(refs: set[Union[str, TypedNode]]) -> list[str]:
242
242
  return [f"{TypedNode.yaml_loc(ref)}: {ref}" for ref in refs]
243
243
 
244
244
  rval = []
@@ -266,7 +266,7 @@ class SchemaSynopsis:
266
266
  return rval
267
267
 
268
268
  def summary(self) -> str:
269
- def summarize_refs(refs: Dict[ElementName, References]) -> str:
269
+ def summarize_refs(refs: dict[ElementName, References]) -> str:
270
270
  clsrefs, slotrefs, typerefs, enumrefs = set(), set(), set(), set()
271
271
  if refs is not None:
272
272
  for cr in refs.values():
@@ -279,7 +279,7 @@ class SchemaSynopsis:
279
279
  f"{len(typerefs)} types, {len(enumrefs)} enums "
280
280
  )
281
281
 
282
- def recurse_types(typ: TypeDefinitionName, indent: str = "\t\t\t") -> List[str]:
282
+ def recurse_types(typ: TypeDefinitionName, indent: str = "\t\t\t") -> list[str]:
283
283
  rval = [f"{indent}{typ}" + (":" if typ in self.typeofs else "")]
284
284
  if typ in sorted(self.typeofs):
285
285
  for tr in sorted(self.typeofs[typ]):
@@ -332,7 +332,7 @@ class SchemaSynopsis:
332
332
 
333
333
  # Slots that are defined but do not (directly) occur in any class
334
334
  n_unreferenced_descendants: int = 0
335
- unowned_slots: Set[SlotDefinitionName] = set()
335
+ unowned_slots: set[SlotDefinitionName] = set()
336
336
  for slotname, slot in sorted(self.schema.slots.items(), key=lambda e: e[0]):
337
337
  if slotname not in self.owners:
338
338
  if slot.domain:
@@ -345,10 +345,10 @@ class SchemaSynopsis:
345
345
  if unowned_slots:
346
346
  rval += [f"\t* Unowned slots: {', '.join(sorted(unowned_slots))}"]
347
347
 
348
- not_in_domain: Set[SlotDefinitionName] = set()
349
- domain_mismatches: Set[SlotDefinitionName] = set()
350
- unkdomains: Set[SlotDefinitionName] = set()
351
- emptydomains: Set[SlotDefinitionName] = set()
348
+ not_in_domain: set[SlotDefinitionName] = set()
349
+ domain_mismatches: set[SlotDefinitionName] = set()
350
+ unkdomains: set[SlotDefinitionName] = set()
351
+ emptydomains: set[SlotDefinitionName] = set()
352
352
 
353
353
  for slot in self.schema.slots.values():
354
354
  if not slot.domain: