linkml 1.9.2rc1__py3-none-any.whl → 1.9.3__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 (60) hide show
  1. linkml/generators/PythonGenNotes.md +49 -49
  2. linkml/generators/README.md +2 -2
  3. linkml/generators/dbmlgen.py +0 -1
  4. linkml/generators/docgen/class.md.jinja2 +35 -11
  5. linkml/generators/docgen/class_diagram.md.jinja2 +15 -15
  6. linkml/generators/docgen/common_metadata.md.jinja2 +3 -5
  7. linkml/generators/docgen/enum.md.jinja2 +5 -6
  8. linkml/generators/docgen/index.md.jinja2 +5 -5
  9. linkml/generators/docgen/index.tex.jinja2 +1 -1
  10. linkml/generators/docgen/schema.md.jinja2 +1 -3
  11. linkml/generators/docgen/slot.md.jinja2 +6 -8
  12. linkml/generators/docgen/subset.md.jinja2 +4 -7
  13. linkml/generators/docgen/type.md.jinja2 +2 -3
  14. linkml/generators/docgen.py +94 -7
  15. linkml/generators/erdiagramgen.py +1 -1
  16. linkml/generators/graphqlgen.py +1 -1
  17. linkml/generators/javagen/example_template.java.jinja2 +0 -1
  18. linkml/generators/jsonldcontextgen.py +0 -1
  19. linkml/generators/jsonldgen.py +3 -1
  20. linkml/generators/jsonschemagen.py +2 -2
  21. linkml/generators/linkmlgen.py +1 -1
  22. linkml/generators/markdowngen.py +20 -9
  23. linkml/generators/mermaidclassdiagramgen.py +4 -0
  24. linkml/generators/projectgen.py +17 -20
  25. linkml/generators/pydanticgen/includes.py +5 -5
  26. linkml/generators/pydanticgen/pydanticgen.py +2 -3
  27. linkml/generators/pydanticgen/template.py +1 -1
  28. linkml/generators/pydanticgen/templates/base_model.py.jinja +1 -1
  29. linkml/generators/pydanticgen/templates/class.py.jinja +1 -1
  30. linkml/generators/pydanticgen/templates/conditional_import.py.jinja +1 -1
  31. linkml/generators/pydanticgen/templates/footer.py.jinja +1 -1
  32. linkml/generators/pydanticgen/templates/imports.py.jinja +1 -1
  33. linkml/generators/python/python_ifabsent_processor.py +1 -1
  34. linkml/generators/pythongen.py +9 -8
  35. linkml/generators/shacl/shacl_ifabsent_processor.py +0 -1
  36. linkml/generators/shaclgen.py +1 -1
  37. linkml/generators/sparqlgen.py +1 -1
  38. linkml/generators/sqlalchemy/__init__.py +0 -4
  39. linkml/generators/sqlalchemygen.py +9 -15
  40. linkml/generators/sqltablegen.py +70 -4
  41. linkml/generators/string_template.md +3 -4
  42. linkml/generators/terminusdbgen.py +1 -2
  43. linkml/linter/cli.py +3 -4
  44. linkml/linter/config/datamodel/config.py +286 -135
  45. linkml/linter/config/datamodel/config.yaml +26 -11
  46. linkml/linter/config/default.yaml +6 -0
  47. linkml/linter/config/recommended.yaml +6 -0
  48. linkml/linter/linter.py +10 -6
  49. linkml/linter/rules.py +144 -46
  50. linkml/transformers/relmodel_transformer.py +2 -1
  51. linkml/utils/generator.py +3 -0
  52. linkml/utils/logictools.py +5 -5
  53. linkml/utils/schemaloader.py +4 -4
  54. linkml/utils/schemasynopsis.py +11 -7
  55. linkml/workspaces/datamodel/workspaces.yaml +0 -5
  56. {linkml-1.9.2rc1.dist-info → linkml-1.9.3.dist-info}/LICENSE +1 -1
  57. {linkml-1.9.2rc1.dist-info → linkml-1.9.3.dist-info}/METADATA +2 -2
  58. {linkml-1.9.2rc1.dist-info → linkml-1.9.3.dist-info}/RECORD +60 -60
  59. {linkml-1.9.2rc1.dist-info → linkml-1.9.3.dist-info}/WHEEL +0 -0
  60. {linkml-1.9.2rc1.dist-info → linkml-1.9.3.dist-info}/entry_points.txt +0 -0
@@ -20,7 +20,7 @@ classes:
20
20
  Config:
21
21
  tree_root: true
22
22
  description: >-
23
- This is the top-level representation of a LinkML linter configuration file. It allows
23
+ This is the top-level representation of a LinkML linter configuration file. It allows
24
24
  defining a set of rules while also optionally extending a predefined set of rules.
25
25
  attributes:
26
26
  extends:
@@ -31,11 +31,11 @@ classes:
31
31
  range: Rules
32
32
  description: >-
33
33
  This is where a configuration defines its rules and the configuration of those rules.
34
-
34
+
35
35
  Rules:
36
36
  description: >-
37
- Each attribute of this class represents a rule that can be enabled and possibly
38
- configured by a configuration file.
37
+ Each attribute of this class represents a rule that can be enabled and possibly
38
+ configured by a configuration file.
39
39
  attributes:
40
40
  no_empty_title:
41
41
  range: NoEmptyTitleConfig
@@ -50,7 +50,7 @@ classes:
50
50
  tree_root_class:
51
51
  range: TreeRootClassRuleConfig
52
52
  description: >-
53
- Require a single class with `tree_root: true` and optionally verify that class's
53
+ Require a single class with `tree_root: true` and optionally verify that class's
54
54
  name. Autofix will create a new class with `tree_root: true`, a name based on
55
55
  the rule configuration, and slots for existing classes.
56
56
  recommended:
@@ -61,18 +61,33 @@ classes:
61
61
  no_xsd_int_type:
62
62
  range: RuleConfig
63
63
  description: >-
64
- Disallow use of `uri: xsd:int` in type definitions. Autofix will change the
64
+ Disallow use of `uri: xsd:int` in type definitions. Autofix will change the
65
65
  uri to xsd:integer.
66
66
  no_invalid_slot_usage:
67
67
  range: RuleConfig
68
68
  description: >-
69
69
  Disallow slot_usage definitions where the name of the slot does not refer
70
70
  to an existing slot. Not auto-fixable.
71
+ no_undeclared_slots:
72
+ range: RuleConfig
73
+ description: >-
74
+ Disallow the use of slots in class specifications if the name of the slot
75
+ does not refer to an existing slot. Not auto-fixable.
76
+ no_undeclared_ranges:
77
+ range: RuleConfig
78
+ description: >-
79
+ Disallow the use of ranges in slot specifications if any of the ranges used
80
+ do not refer to an existing type, class, or enum. Not auto-fixable.
81
+ root_type_checks:
82
+ range: RuleConfig
83
+ description: >-
84
+ Ensure that root types, types with no `typeof` attributes, have the required slots, and
85
+ that values used in the `typeof` slot refer to valid types. Not auto-fixable.
71
86
  standard_naming:
72
87
  range: StandardNamingConfig
73
88
  description: >-
74
89
  Enforce standard naming conventions: CamelCase for classes, snake_case for slots,
75
- CamelCase for enums, snake_case (default) or UPPER_SNAKE for permissible_values
90
+ CamelCase for enums, snake_case (default) or UPPER_SNAKE for permissible_values
76
91
  (see permissible_values_upper_case option). This rule may conflict with the
77
92
  permissible_values_format rule. Not auto-fixable.
78
93
  canonical_prefixes:
@@ -84,7 +99,7 @@ classes:
84
99
 
85
100
  RuleConfig:
86
101
  description: >-
87
- This is the base class for linter rules. It contains configuration options that are
102
+ This is the base class for linter rules. It contains configuration options that are
88
103
  common to all rules.
89
104
  attributes:
90
105
  level:
@@ -188,8 +203,8 @@ classes:
188
203
  multivalued: true
189
204
  description: >-
190
205
  Default: [merged]
191
- The list of context names which will be loaded by the `prefixmaps` library to do the
192
- validation. The order of names is meaningful and will be preserved. See:
206
+ The list of context names which will be loaded by the `prefixmaps` library to do the
207
+ validation. The order of names is meaningful and will be preserved. See:
193
208
  https://github.com/linkml/prefixmaps#usage
194
209
 
195
210
  NoEmptyTitleConfig:
@@ -202,7 +217,7 @@ classes:
202
217
  multivalued: true
203
218
  description: >-
204
219
  Default: []
205
- Elements of all types (ClassDefinition, SlotDefinition, EnumDefinition, PermissibleValue, etc.) except the types specified in this list will be checked
220
+ Elements of all types (ClassDefinition, SlotDefinition, EnumDefinition, PermissibleValue, etc.) except the types specified in this list will be checked
206
221
 
207
222
  enums:
208
223
  ExtendableConfigs:
@@ -18,6 +18,12 @@ rules:
18
18
  level: disabled
19
19
  no_invalid_slot_usage:
20
20
  level: disabled
21
+ no_undeclared_slots:
22
+ level: disabled
23
+ no_undeclared_ranges:
24
+ level: disabled
25
+ root_type_checks:
26
+ level: disabled
21
27
  standard_naming:
22
28
  level: disabled
23
29
  permissible_values_upper_case: false
@@ -5,6 +5,12 @@ rules:
5
5
  level: error
6
6
  no_invalid_slot_usage:
7
7
  level: error
8
+ no_undeclared_slots:
9
+ level: error
10
+ no_undeclared_ranges:
11
+ level: error
12
+ root_type_checks:
13
+ level: error
8
14
  standard_naming:
9
15
  level: warning
10
16
  canonical_prefixes:
linkml/linter/linter.py CHANGED
@@ -1,10 +1,14 @@
1
+ """Linkml linter."""
2
+
3
+ from __future__ import annotations
4
+
1
5
  import inspect
2
6
  from collections.abc import Iterable
3
7
  from copy import deepcopy
4
8
  from dataclasses import dataclass
5
9
  from functools import lru_cache
6
10
  from pathlib import Path
7
- from typing import Any, Union
11
+ from typing import Any
8
12
 
9
13
  import jsonschema
10
14
  import yaml
@@ -23,10 +27,10 @@ from .config.datamodel.config import Config, ExtendableConfigs, RuleLevel
23
27
  @dataclass
24
28
  class LinterProblem:
25
29
  message: str
26
- level: Union[RuleLevel, None] = None
27
- schema_name: Union[str, None] = None
28
- schema_source: Union[str, None] = None
29
- rule_name: Union[str, None] = None
30
+ level: RuleLevel | None = None
31
+ schema_name: str | None = None
32
+ schema_source: str | None = None
33
+ rule_name: str | None = None
30
34
 
31
35
 
32
36
  @lru_cache
@@ -107,7 +111,7 @@ class Linter:
107
111
 
108
112
  def lint(
109
113
  self,
110
- schema: Union[str, SchemaDefinition],
114
+ schema: str | SchemaDefinition,
111
115
  fix: bool = False,
112
116
  validate_schema: bool = False,
113
117
  validate_only: bool = False,
linkml/linter/rules.py CHANGED
@@ -1,23 +1,35 @@
1
+ """Rule implementation for the linkml linter."""
2
+
3
+ from __future__ import annotations
4
+
1
5
  import re
2
6
  from abc import ABC, abstractmethod
3
- from collections.abc import Iterable
7
+ from collections.abc import Callable, Iterable
4
8
  from functools import cache
5
- from typing import Callable
6
9
 
7
- from linkml_runtime.linkml_model import ClassDefinition, ClassDefinitionName, Element, SlotDefinition
10
+ from linkml_runtime.linkml_model import (
11
+ ClassDefinition,
12
+ ClassDefinitionName,
13
+ Element,
14
+ ElementName,
15
+ EnumDefinition,
16
+ EnumDefinitionName,
17
+ SlotDefinition,
18
+ TypeDefinition,
19
+ TypeDefinitionName,
20
+ )
8
21
  from linkml_runtime.utils.schemaview import SchemaView
9
22
  from prefixmaps.io.parser import load_multi_context
10
23
 
11
24
  from linkml import LOCAL_METAMODEL_YAML_FILE
12
-
13
- from .config.datamodel.config import (
25
+ from linkml.linter.config.datamodel.config import (
14
26
  CanonicalPrefixesConfig,
15
27
  RecommendedRuleConfig,
16
28
  RuleConfig,
17
29
  StandardNamingConfig,
18
30
  TreeRootClassRuleConfig,
19
31
  )
20
- from .linter import LinterProblem
32
+ from linkml.linter.linter import LinterProblem
21
33
 
22
34
 
23
35
  class LinterRule(ABC):
@@ -91,7 +103,7 @@ class PermissibleValuesFormatRule(LinterRule):
91
103
  def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
92
104
  pattern = self.PATTERNS.get(self.config.format, re.compile(self.config.format))
93
105
  for enum_def in schema_view.all_enums(imports=False).values():
94
- for value in enum_def.permissible_values.keys():
106
+ for value in enum_def.permissible_values:
95
107
  if pattern.fullmatch(value) is None:
96
108
  yield LinterProblem(f"{self.format_element(enum_def)} has permissible value '{value}'")
97
109
 
@@ -150,25 +162,23 @@ class TreeRootClassRule(LinterRule):
150
162
  yield LinterProblem(message=f"Tree root class has an invalid name '{tree_root.name}'")
151
163
  if len(tree_roots) > 1:
152
164
  yield LinterProblem("Schema has more than one class with `tree_root: true`")
165
+ elif fix:
166
+ container = ClassDefinition(self.config.root_class_name, tree_root=True)
167
+ schema_view.add_class(container)
168
+ self.add_index_slots(schema_view, container.name)
153
169
  else:
154
- if fix:
155
- container = ClassDefinition(self.config.root_class_name, tree_root=True)
156
- schema_view.add_class(container)
157
- self.add_index_slots(schema_view, container.name)
158
- else:
159
- yield LinterProblem("Schema does not have class with `tree_root: true`")
170
+ yield LinterProblem("Schema does not have class with `tree_root: true`")
160
171
 
161
172
  def add_index_slots(
162
173
  self,
163
174
  schema_view: SchemaView,
164
175
  container_name: ClassDefinitionName,
165
- inlined_as_list=False,
166
- must_have_identifier=False,
167
- slot_name_func: Callable = None,
168
- convert_camel_case=False,
176
+ inlined_as_list: bool = False,
177
+ must_have_identifier: bool = False,
178
+ slot_name_func: Callable | None = None,
179
+ convert_camel_case: bool = False,
169
180
  ) -> list[SlotDefinition]:
170
- """
171
- Adds index slots to a container pointing at all top-level classes
181
+ """Add index slots to a container pointing at all top-level classes.
172
182
 
173
183
  :param schema: input schema, will be modified in place
174
184
  :param container_name:
@@ -178,14 +188,11 @@ class TreeRootClassRule(LinterRule):
178
188
  :return: new slots
179
189
  """
180
190
  container = schema_view.get_class(container_name)
181
- ranges = set()
182
- for cn in schema_view.all_classes():
183
- for s in schema_view.class_induced_slots(cn):
184
- ranges.add(s.range)
191
+ ranges = {s.range for cn in schema_view.all_classes() for s in schema_view.class_induced_slots(cn)}
185
192
  top_level_classes = [c for c in schema_view.all_classes().values() if not c.tree_root and c.name not in ranges]
186
193
  if must_have_identifier:
187
194
  top_level_classes = [c for c in top_level_classes if schema_view.get_identifier_slot(c.name) is not None]
188
- index_slots = []
195
+ index_slots: list[SlotDefinition] = []
189
196
  for c in top_level_classes:
190
197
  has_identifier = schema_view.get_identifier_slot(c.name)
191
198
  if slot_name_func:
@@ -208,6 +215,30 @@ class TreeRootClassRule(LinterRule):
208
215
  return index_slots
209
216
 
210
217
 
218
+ class NoUndeclaredSlotsRule(LinterRule):
219
+ """Linter rule to check that all slots from a class have been declared in the `slots` section."""
220
+
221
+ id = "no_undeclared_slots"
222
+
223
+ def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
224
+ """Perform the check for undeclared slots.
225
+
226
+ :param schema_view: schema to be checked in a SchemaView object.
227
+ :type schema_view: SchemaView
228
+ :param fix: whether or not to fix, defaults to False
229
+ :type fix: bool, optional
230
+ :yield: iterable of error messages about non-compliant slots.
231
+ :rtype: Iterator[Iterable[LinterProblem]]
232
+ """
233
+ all_slots = schema_view.all_slots()
234
+ for class_name in schema_view.all_classes():
235
+ for slot_name in schema_view.class_slots(class_name):
236
+ if slot_name not in all_slots:
237
+ yield LinterProblem(
238
+ f"Slot '{slot_name}' from class '{class_name}' not found in schema 'slots' declaration."
239
+ )
240
+
241
+
211
242
  class NoInvalidSlotUsageRule(LinterRule):
212
243
  id = "no_invalid_slot_usage"
213
244
 
@@ -217,11 +248,76 @@ class NoInvalidSlotUsageRule(LinterRule):
217
248
  if not slot_usage:
218
249
  continue
219
250
  class_slots = schema_view.class_slots(class_name)
220
- for slot_usage_name in slot_usage.keys():
251
+ for slot_usage_name in slot_usage:
221
252
  if slot_usage_name not in class_slots:
222
253
  yield LinterProblem(f"Slot '{slot_usage_name}' not found on class '{class_name}'")
223
254
 
224
255
 
256
+ class NoUndeclaredRangesRule(LinterRule):
257
+ id = "no_undeclared_ranges"
258
+
259
+ def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
260
+ all_types: dict[TypeDefinitionName, TypeDefinition] = schema_view.all_types()
261
+ all_enums: dict[EnumDefinitionName, EnumDefinition] = schema_view.all_enums()
262
+ all_classes: dict[ClassDefinitionName, ClassDefinition] = schema_view.all_classes()
263
+ all_possible_ranges: set[TypeDefinitionName | EnumDefinitionName | ClassDefinitionName] = (
264
+ set(all_types) | set(all_enums) | set(all_classes)
265
+ )
266
+
267
+ # check that the default_range has a valid value
268
+ default_range = schema_view.schema.default_range
269
+ if default_range and default_range not in all_possible_ranges:
270
+ yield LinterProblem(f"Schema default_range '{default_range}' is not defined.")
271
+
272
+ for class_name in schema_view.all_classes():
273
+ for slot in schema_view.class_induced_slots(class_name):
274
+ slot_range: set[ElementName] = set(schema_view.slot_range_as_union(slot))
275
+
276
+ # check slot range is valid
277
+ for range_name in slot_range:
278
+ if range_name not in all_possible_ranges:
279
+ yield LinterProblem(
280
+ f"Class '{class_name}' slot '{slot.name}' range '{range_name}' is not defined."
281
+ )
282
+
283
+
284
+ class RootTypeChecks(LinterRule):
285
+ """Performs basic checks on types.
286
+
287
+ Types can be defined as subtypes of existing types using the `typeof` attribute.
288
+ Root types are those types that do not inherit from other types using `typeof`.
289
+
290
+ - types used in 'typeof' statements must be defined in the types section
291
+ - types cannot be their own 'typeof' parent
292
+ - every root type must have a 'base'
293
+ - every root type must have a 'uri'
294
+
295
+ :param LinterRule: linter rule class
296
+ :type LinterRule: LinterRule
297
+ """
298
+
299
+ id = "root_type_checks"
300
+
301
+ def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
302
+ type_ix = schema_view.all_types()
303
+ for t, type_def in type_ix.items():
304
+ if type_def.typeof:
305
+ # This is a child type. Ensure that the parent exists.
306
+ if type_def.typeof not in schema_view.all_types():
307
+ yield LinterProblem(f"'{t}' has invalid typeof parent '{type_def.typeof}'")
308
+ # Ensure that a type is not its own parent.
309
+ # N.b. at some point, this can/should be extended to checking the full 'typeof' ancestor chain
310
+ # for circular inheritance.
311
+ if type_def.typeof == type_def.name:
312
+ yield LinterProblem(f"'{t}' has invalid circular typeof parent '{type_def.typeof}'")
313
+ continue
314
+ if not type_def.base:
315
+ yield LinterProblem(f"Root type '{t}' is missing the required 'base' attribute")
316
+
317
+ if not type_def.uri:
318
+ yield LinterProblem(f"Root type '{t}' is missing the required 'uri' attribute")
319
+
320
+
225
321
  class StandardNamingRule(LinterRule):
226
322
  id = "standard_naming"
227
323
 
@@ -247,23 +343,21 @@ class StandardNamingRule(LinterRule):
247
343
  )
248
344
 
249
345
  if "class_definition" not in excluded_types:
250
- for class_name in schema_view.all_classes(imports=False).keys():
346
+ for class_name in schema_view.all_classes(imports=False):
251
347
  if class_pattern.fullmatch(class_name) is None:
252
348
  yield LinterProblem(f"Class has name '{class_name}'")
253
349
 
254
350
  if "slot_definition" not in excluded_types:
255
- for slot_name in schema_view.all_slots(imports=False).keys():
351
+ for slot_name in schema_view.all_slots(imports=False):
256
352
  if slot_pattern.fullmatch(slot_name) is None:
257
353
  yield LinterProblem(f"Slot has name '{slot_name}'")
258
354
 
259
355
  for enum_name, enum_definition in schema_view.all_enums(imports=False).items():
260
-
261
- if "enum_definition" not in excluded_types:
262
- if enum_pattern.fullmatch(enum_name) is None:
263
- yield LinterProblem(f"Enum has name '{enum_name}'")
356
+ if "enum_definition" not in excluded_types and enum_pattern.fullmatch(enum_name) is None:
357
+ yield LinterProblem(f"Enum has name '{enum_name}'")
264
358
 
265
359
  if "permissible_value" not in excluded_types:
266
- for permissible_value_name in enum_definition.permissible_values.keys():
360
+ for permissible_value_name in enum_definition.permissible_values:
267
361
  if permissible_value_pattern.fullmatch(permissible_value_name) is None:
268
362
  yield LinterProblem(
269
363
  f"Permissible value of {self.format_element(enum_definition)} "
@@ -282,17 +376,21 @@ class CanonicalPrefixesRule(LinterRule):
282
376
  prefix_to_namespace = context.as_dict()
283
377
  namespace_to_prefix = context.as_inverted_dict()
284
378
  for prefix in schema_view.schema.prefixes.values():
285
- if prefix.prefix_prefix in prefix_to_namespace:
286
- if prefix.prefix_reference != prefix_to_namespace[prefix.prefix_prefix]:
287
- yield LinterProblem(
288
- f"Schema maps prefix '{prefix.prefix_prefix}' to namespace "
289
- f"'{prefix.prefix_reference}' instead of namespace "
290
- f"'{prefix_to_namespace[prefix.prefix_prefix]}'"
291
- )
292
- if prefix.prefix_reference in namespace_to_prefix:
293
- if prefix.prefix_prefix != namespace_to_prefix[prefix.prefix_reference]:
294
- yield LinterProblem(
295
- f"Schema maps prefix '{prefix.prefix_prefix}' to namespace "
296
- f"'{prefix.prefix_reference}' instead of using prefix "
297
- f"'{namespace_to_prefix[prefix.prefix_reference]}'"
298
- )
379
+ if (
380
+ prefix.prefix_prefix in prefix_to_namespace
381
+ and prefix.prefix_reference != prefix_to_namespace[prefix.prefix_prefix]
382
+ ):
383
+ yield LinterProblem(
384
+ f"Schema maps prefix '{prefix.prefix_prefix}' to namespace "
385
+ f"'{prefix.prefix_reference}' instead of namespace "
386
+ f"'{prefix_to_namespace[prefix.prefix_prefix]}'"
387
+ )
388
+ if (
389
+ prefix.prefix_reference in namespace_to_prefix
390
+ and prefix.prefix_prefix != namespace_to_prefix[prefix.prefix_reference]
391
+ ):
392
+ yield LinterProblem(
393
+ f"Schema maps prefix '{prefix.prefix_prefix}' to namespace "
394
+ f"'{prefix.prefix_reference}' instead of using prefix "
395
+ f"'{namespace_to_prefix[prefix.prefix_reference]}'"
396
+ )
@@ -181,6 +181,7 @@ class RelationalModelTransformer:
181
181
  types=source.types,
182
182
  subsets=source.subsets,
183
183
  enums=source.enums,
184
+ annotations=source.annotations,
184
185
  )
185
186
  target.prefixes["rr"] = Prefix("rr", "http://www.w3.org/ns/r2rml#")
186
187
 
@@ -196,6 +197,7 @@ class RelationalModelTransformer:
196
197
  abstract=c.abstract,
197
198
  description=c.description,
198
199
  unique_keys=c.unique_keys,
200
+ annotations=c.annotations,
199
201
  )
200
202
  for slot in source_sv.class_induced_slots(cn):
201
203
  tgt_slot = copy(slot)
@@ -209,7 +211,6 @@ class RelationalModelTransformer:
209
211
  # this is required in case an attribute inherits from a slot
210
212
  for sn in source_sv.all_slots(attributes=False):
211
213
  slot = source_sv.get_slot(sn)
212
- # target.slots[slot.name] = copy(slot)
213
214
  target.classes[c.name] = c
214
215
 
215
216
  target_sv = SchemaView(target)
linkml/utils/generator.py CHANGED
@@ -344,6 +344,9 @@ class Generator(metaclass=abc.ABCMeta):
344
344
  sub_out = self.end_schema(**kwargs)
345
345
  if sub_out is not None:
346
346
  out += sub_out
347
+ out = out.rstrip()
348
+ if not (out.count("\n") == 0 and out.startswith("http")):
349
+ out += "\n"
347
350
  return out
348
351
 
349
352
  def visit_schema(self, **kwargs) -> Optional[str]:
@@ -125,11 +125,11 @@ class And(Expression):
125
125
  self.operands: list[Expression] = list(operands)
126
126
 
127
127
  def __str__(self):
128
- return f'({" & ".join(str(operand) for operand in self.operands)})'
128
+ return f"({' & '.join(str(operand) for operand in self.operands)})"
129
129
 
130
130
  def _ordered_str(self, **kwargs):
131
131
  sorted_operands = sorted([op._ordered_str(**kwargs) for op in self.operands], key=str)
132
- return f'({" & ".join(sorted_operands)})'
132
+ return f"({' & '.join(sorted_operands)})"
133
133
 
134
134
 
135
135
  class Or(Expression):
@@ -137,13 +137,13 @@ class Or(Expression):
137
137
  self.operands = list(operands)
138
138
 
139
139
  def __str__(self):
140
- return f'({" | ".join(str(operand) for operand in self.operands)})'
140
+ return f"({' | '.join(str(operand) for operand in self.operands)})"
141
141
 
142
142
  def _ordered_str(self, pairwise=False):
143
143
  if pairwise and len(self.operands) > 2:
144
144
  return Or(Or(*self.operands[0:2]), Or(*self.operands[2:]))._ordered_str(pairwise=True)
145
145
  sorted_operands = sorted([op._ordered_str(pairwise=True) for op in self.operands], key=str)
146
- return f'({" | ".join(sorted_operands)})'
146
+ return f"({' | '.join(sorted_operands)})"
147
147
 
148
148
 
149
149
  class Eq(Expression):
@@ -167,7 +167,7 @@ class Term(Expression):
167
167
  if self.predicate in OPS:
168
168
  return f"{str(self.operands[0])} {OPS[self.predicate]} {str(self.operands[1])}"
169
169
  else:
170
- return f'{self.predicate}({", ".join(str(operand) for operand in self.operands)})'
170
+ return f"{self.predicate}({', '.join(str(operand) for operand in self.operands)})"
171
171
 
172
172
  def _ordered_str(self, **kwargs):
173
173
  return str(self)
@@ -737,7 +737,7 @@ class SchemaLoader:
737
737
  for slot_usage in values(cls.slot_usage):
738
738
  if slot_usage.alias:
739
739
  self.raise_value_error(
740
- f'Class: "{cls.name}" - alias not permitted in slot_usage slot:' f" {slot_usage.alias}"
740
+ f'Class: "{cls.name}" - alias not permitted in slot_usage slot: {slot_usage.alias}'
741
741
  )
742
742
  if not located_aliased_parent_slot(cls, slot_usage):
743
743
  if slot_usage.name not in self.schema.slots:
@@ -765,7 +765,7 @@ class SchemaLoader:
765
765
  for slotname, slot_usage in cls.slot_usage.items():
766
766
  if slot_usage.alias:
767
767
  self.raise_value_error(
768
- f'Class: "{cls.name}" - alias not permitted in slot_usage slot:' f" {slot_usage.alias}"
768
+ f'Class: "{cls.name}" - alias not permitted in slot_usage slot: {slot_usage.alias}'
769
769
  )
770
770
  # Construct a new slot
771
771
  # If we've already assigned a parent, use it
@@ -947,7 +947,7 @@ class SchemaLoader:
947
947
  locs = "\n".join(TypedNode.yaml_loc(e, suffix="") for e in loc_str)
948
948
  raise ValueError(f"{locs} {error}")
949
949
  else:
950
- raise ValueError(f'{TypedNode.yaml_loc(loc_str, suffix="")} {error}')
950
+ raise ValueError(f"{TypedNode.yaml_loc(loc_str, suffix='')} {error}")
951
951
 
952
952
  def logger_warning(
953
953
  self,
@@ -958,7 +958,7 @@ class SchemaLoader:
958
958
  locs = "\n\t".join(TypedNode.yaml_loc(e, suffix="") for e in loc_str)
959
959
  self.logger.warning(f"{warning}\n\t{locs}")
960
960
  else:
961
- self.logger.warning(f'{warning}\n\t{TypedNode.yaml_loc(loc_str, suffix="")}')
961
+ self.logger.warning(f"{warning}\n\t{TypedNode.yaml_loc(loc_str, suffix='')}")
962
962
 
963
963
  def _get_base_dir(self, stated_base: str) -> Optional[str]:
964
964
  if stated_base:
@@ -115,7 +115,11 @@ class SchemaSynopsis:
115
115
  (
116
116
  ClassType
117
117
  if v.range in self.schema.classes
118
- else EnumType if v.range in self.schema.enums else TypeType if v.range in self.schema.types else None
118
+ else EnumType
119
+ if v.range in self.schema.enums
120
+ else TypeType
121
+ if v.range in self.schema.types
122
+ else None
119
123
  ),
120
124
  v.range,
121
125
  )
@@ -244,19 +248,19 @@ class SchemaSynopsis:
244
248
  rval = []
245
249
  undefined_classes = set(self.classrefs.keys()) - set(self.schema.classes.keys())
246
250
  if undefined_classes:
247
- rval += [f"\tUndefined class references: " f"{', '.join(format_undefineds(undefined_classes))}"]
251
+ rval += [f"\tUndefined class references: {', '.join(format_undefineds(undefined_classes))}"]
248
252
  undefined_slots = set(self.slotrefs.keys()) - set(self.schema.slots.keys())
249
253
  if undefined_slots:
250
- rval += [f"\tUndefined slot references: " f"{', '.join(format_undefineds(undefined_slots))}"]
254
+ rval += [f"\tUndefined slot references: {', '.join(format_undefineds(undefined_slots))}"]
251
255
  undefined_types = set(self.typerefs.keys()) - set(self.schema.types.keys())
252
256
  if undefined_types:
253
- rval += [f"\tUndefined type references: " f"{', '.join(format_undefineds(undefined_types))}"]
257
+ rval += [f"\tUndefined type references: {', '.join(format_undefineds(undefined_types))}"]
254
258
  undefined_subsets = set(self.subsetrefs.keys()) - set(self.schema.subsets.keys())
255
259
  if undefined_subsets:
256
- rval += [f"\tUndefined subset references: " f"{', '.join(format_undefineds(undefined_subsets))}"]
260
+ rval += [f"\tUndefined subset references: {', '.join(format_undefineds(undefined_subsets))}"]
257
261
  undefined_enums = set(self.enumrefs.keys()) - set(self.schema.enums.keys())
258
262
  if undefined_enums:
259
- rval += [f"\tUndefined enun references: " f"{', '.join(format_undefineds(undefined_enums))}"]
263
+ rval += [f"\tUndefined enun references: {', '.join(format_undefineds(undefined_enums))}"]
260
264
 
261
265
  # Inlined slots must be multivalued (not a inviolable rule, but we make assumptions about this elsewhere in
262
266
  # the python generator
@@ -373,7 +377,7 @@ class SchemaSynopsis:
373
377
  for slotname in sorted(domain_mismatches):
374
378
  rval.append(
375
379
  f'\t\t\tSlot: "{slotname}" declared domain: "{self.schema.slots[slotname].domain}" '
376
- f'actual domain(s): {", ".join(self.slotclasses[slotname])}'
380
+ f"actual domain(s): {', '.join(self.slotclasses[slotname])}"
377
381
  )
378
382
  if unkdomains:
379
383
  rval += [f"\t* Unknown domain: {', '.join(sorted(unkdomains))}"]
@@ -148,8 +148,3 @@ classes:
148
148
  description: >-
149
149
  If true, the workspace manager should immediately sync any changes made
150
150
  range: boolean
151
-
152
-
153
-
154
-
155
-
@@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software
10
10
  distributed under the License is distributed on an "AS IS" BASIS,
11
11
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  See the License for the specific language governing permissions and
13
- limitations under the License.
13
+ limitations under the License.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: linkml
3
- Version: 1.9.2rc1
3
+ Version: 1.9.3
4
4
  Summary: Linked Open Data Modeling Language
5
5
  License: Apache-2.0
6
6
  Keywords: schema,linked data,data modeling,rdf,owl,biolink
@@ -34,7 +34,7 @@ Requires-Dist: isodate (>=0.6.0)
34
34
  Requires-Dist: jinja2 (>=3.1.0)
35
35
  Requires-Dist: jsonasobj2 (>=1.0.3,<2.0.0)
36
36
  Requires-Dist: jsonschema[format] (>=4.0.0)
37
- Requires-Dist: linkml-runtime (>=1.9.2,<2.0.0)
37
+ Requires-Dist: linkml-runtime (>=1.9.4,<2.0.0)
38
38
  Requires-Dist: numpydantic (>=1.6.1) ; extra == "numpydantic"
39
39
  Requires-Dist: numpydantic (>=1.6.1) ; extra == "tests"
40
40
  Requires-Dist: openpyxl