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
@@ -1,4 +1,4 @@
1
- # Subset: {{ gen.name(element) }} {% if element.deprecated %} <span style="color: red;"><strong> (DEPRECATED) </strong> {% endif %}
1
+ # Subset: {{ gen.name(element) }} {% if element.deprecated %} <span style="color: red;"><strong> (DEPRECATED) </strong></span> {% endif %}
2
2
 
3
3
  {%- if header -%}
4
4
  {{ header }}
@@ -45,7 +45,7 @@ URI: {{ gen.link(element) }}
45
45
  | --- | --- |
46
46
  {% for c in classes_in_subset -%}
47
47
  {%- if element.name in c.in_subset -%}
48
- | {{gen.link(c)}} | {{c.description|enshorten}} |
48
+ | {{ gen.link(c) }} | {{ c.description|enshorten }} |
49
49
  {% endif -%}
50
50
  {% endfor %}
51
51
 
@@ -67,17 +67,15 @@ URI: {{ gen.link(element) }}
67
67
  | Name | Cardinality and Range | Description |
68
68
  | --- | --- | --- |
69
69
  {% for s in filtered_slots -%}
70
- | {{gen.link(s)}} | {{ gen.cardinality(s) }} <br/> {{gen.link(s.range)}} | {{s.description|enshorten}} {% if s.identifier %}**identifier**{% endif %} |
70
+ | {{ gen.link(s) }} | {{ gen.cardinality(s) }} <br/> {{ gen.link(s.range) }} | {{ s.description|enshorten }} {% if s.identifier %}**identifier**{% endif %} |
71
71
  {% endfor %}
72
72
  {%- endif %}
73
73
 
74
-
75
74
  {%- endif %}
76
75
  {% endfor %}
77
76
 
78
77
  {%- endif %}
79
78
 
80
-
81
79
  {% if slots_in_subset %}
82
80
  ## Slots in subset
83
81
 
@@ -91,14 +89,13 @@ URI: {{ gen.link(element) }}
91
89
 
92
90
  {%- endif %}
93
91
 
94
-
95
92
  {% if enums_in_subset %}
96
93
  ## Enumerations in subset
97
94
 
98
95
  | Enumeration | Description |
99
96
  | --- | --- |
100
97
  {% for e in enums_in_subset|sort(attribute='name') -%}
101
- {%- if element.name in e.in_subset %}
98
+ {% if element.name in e.in_subset -%}
102
99
  | {{ gen.link(e) }} | {{ e.description|enshorten }} |
103
100
  {%- endif %}
104
101
  {% endfor %}
@@ -1,4 +1,4 @@
1
- # Type: {{ gen.name(element) }} {% if element.deprecated %} <span style="color: red;"><strong> (DEPRECATED) </strong> {% endif %}
1
+ # Type: {{ gen.name(element) }} {% if element.deprecated %} <span style="color: red;"><strong> (DEPRECATED) </strong></span> {% endif %}
2
2
 
3
3
  {% if element.description %}
4
4
  {% set element_description_lines = element.description.split('\n') %}
@@ -15,8 +15,7 @@ URI: {{ gen.uri_link(element) }}
15
15
  {{ gen.bullet(element, "typeof") }}
16
16
  {{ gen.bullet(element, "pattern", backquote=True) }}
17
17
  {% if gen.number_value_range(element) %}
18
- * Numeric Value Range: {{gen.number_value_range(element)}}
18
+ * Numeric Value Range: {{ gen.number_value_range(element) }}
19
19
  {% endif %}
20
20
 
21
21
  {% include "common_metadata.md.jinja2" %}
22
-
@@ -6,11 +6,11 @@ from copy import deepcopy
6
6
  from dataclasses import dataclass, field
7
7
  from enum import Enum
8
8
  from pathlib import Path
9
- from typing import Optional, Union
9
+ from typing import Any, Optional, Union
10
10
 
11
11
  import click
12
12
  from jinja2 import Environment, FileSystemLoader, Template
13
- from linkml_runtime.dumpers import yaml_dumper
13
+ from linkml_runtime.dumpers import json_dumper, yaml_dumper
14
14
  from linkml_runtime.linkml_model.meta import (
15
15
  ClassDefinition,
16
16
  ClassDefinitionName,
@@ -387,8 +387,8 @@ class DocGenerator(Generator):
387
387
  :param element:
388
388
  :return:
389
389
  """
390
- if isinstance(element, (EnumDefinition, SubsetDefinition)):
391
- # TODO: fix schema view to handle URIs for enums and subsets
390
+ if isinstance(element, SubsetDefinition):
391
+ # Subsets have no uri attribute
392
392
  return self.name(element)
393
393
 
394
394
  if self.subfolder_type_separation:
@@ -410,6 +410,21 @@ class DocGenerator(Generator):
410
410
  curie = self.uri(element, expand=False)
411
411
  return f"[{curie}]({uri})"
412
412
 
413
+ def link_mermaid(self, e: Union[Definition, DefinitionName]) -> str:
414
+ """
415
+ Return link to insert in mermaid diagrams for a given element
416
+
417
+ :param e: element to be linked
418
+ :return: string with link
419
+ """
420
+ # Reuse markdown link generation to avoid code duplication.
421
+ md_link = self.link(e)
422
+ if not md_link.endswith(")"):
423
+ return md_link
424
+ link = md_link.rsplit("(")[-1][:-1]
425
+ link = link.removesuffix(".md")
426
+ return f"../{link}/"
427
+
413
428
  def link(self, e: Union[Definition, DefinitionName], index_link: bool = False) -> str:
414
429
  """
415
430
  Render an element as a hyperlink
@@ -485,14 +500,13 @@ class DocGenerator(Generator):
485
500
  return self._is_external(t) and not self.schemaview.schema.id.startswith("https://w3id.org/linkml/")
486
501
 
487
502
  def _is_external(self, element: Element) -> bool:
488
- # note: this is currently incomplete. See: https://github.com/linkml/linkml/issues/782
489
503
  if element.from_schema == "https://w3id.org/linkml/types" and not self.genmeta:
490
504
  return True
491
505
  else:
492
506
  return False
493
507
 
494
508
  @staticmethod
495
- def _markdown_link(n: str, name: str = None, subfolder: str = None) -> str:
509
+ def _markdown_link(n: str, name: str = "", subfolder: str = "") -> str:
496
510
  if subfolder:
497
511
  rel_path = f"{subfolder}/{n}"
498
512
  else:
@@ -965,6 +979,80 @@ class DocGenerator(Generator):
965
979
  objs.append((stem, f.read()))
966
980
  return objs
967
981
 
982
+ def _remove_name_keys(self, obj):
983
+ """Recursively removes 'name' keys from a JSON object representation.
984
+
985
+ This is used to clean up the output of ClassRule objects. For example, if we had a rule like:
986
+ rules:
987
+ - title: calibration_standard_if_rt
988
+ preconditions:
989
+ slot_conditions:
990
+ calibration_target:
991
+ equals_string: retention_index
992
+
993
+ The JSON representation would include redundant "name" keys:
994
+ {
995
+ "slot_conditions": {
996
+ "name": "slot_conditions"
997
+ "calibration_target": {
998
+ "name": "calibration_target"
999
+ "equals_string": "retention_index",
1000
+ },
1001
+ }
1002
+ }
1003
+
1004
+ This function removes those redundant "name" keys to make the output cleaner.
1005
+
1006
+ :param obj: The object to clean (dict, list, or primitive value)
1007
+ :return: The same object with all "name" keys removed from dicts
1008
+ """
1009
+ if isinstance(obj, dict):
1010
+ return {k: self._remove_name_keys(v) for k, v in obj.items() if k != "name"}
1011
+ elif isinstance(obj, list):
1012
+ return [self._remove_name_keys(item) for item in obj]
1013
+ else:
1014
+ return obj
1015
+
1016
+ def classrule_to_dict_view(self, element: ClassDefinition) -> list[dict[str, Any]]:
1017
+ """Process all rules (of type ClassRule) asserted on a class.
1018
+
1019
+ This method iterates through all rules asserted on a class and returns a list of
1020
+ dictionaries, each containing four pieces of information about a rule:
1021
+ _title_, _preconditions_, _postconditions_, and _elseconditions_.
1022
+ These values will be read in the jinja template and formatted into a tabular
1023
+ view for users to understand the rules applied to the class.
1024
+
1025
+ Note: This method removes redundant "name" keys which are asserted on some
1026
+ classes in the Python representation of classes from the metamodel.
1027
+
1028
+ :param element: LinkML class object with `rules` asserted on it
1029
+ :return: List of dictionaries with title and "sanitized" conditions for each rule
1030
+ """
1031
+ if not element.rules:
1032
+ return []
1033
+
1034
+ rule_dicts = []
1035
+
1036
+ for rule in element.rules:
1037
+ # TODO: expand this list of ClassRule metaslots based on use case
1038
+ rule_dict = {
1039
+ "title": rule.title or "",
1040
+ "preconditions": None,
1041
+ "postconditions": None,
1042
+ "elseconditions": None,
1043
+ }
1044
+
1045
+ for key in ["preconditions", "postconditions", "elseconditions"]:
1046
+ condition_obj = getattr(rule, key, None)
1047
+ if condition_obj:
1048
+ json_obj = json_dumper.to_dict(condition_obj)
1049
+ sanitized_condition = self._remove_name_keys(json_obj)
1050
+ rule_dict[key] = sanitized_condition
1051
+
1052
+ rule_dicts.append(rule_dict)
1053
+
1054
+ return rule_dicts
1055
+
968
1056
  def customize_environment(self, env: Environment):
969
1057
  if self.truncate_descriptions:
970
1058
  env.filters["enshorten"] = enshorten
@@ -1031,7 +1119,6 @@ class DocGenerator(Generator):
1031
1119
  help="Folder in which example files are found. These are used to make inline examples",
1032
1120
  )
1033
1121
  @click.option(
1034
- "-d",
1035
1122
  "--include",
1036
1123
  help="""
1037
1124
  Include LinkML Schema outside of imports mechanism. Helpful in including deprecated classes and slots in a separate
@@ -23,7 +23,7 @@ class Attribute(pydantic.BaseModel):
23
23
 
24
24
  def __str__(self):
25
25
  cmt = f'"{self.comment}"' if self.comment else ""
26
- return f' {self.datatype} {self.name} {self.key if self.key else ""} {cmt}'
26
+ return f" {self.datatype} {self.name} {self.key if self.key else ''} {cmt}"
27
27
 
28
28
 
29
29
  class IdentifyingType(str, Enum):
@@ -66,7 +66,7 @@ class GraphqlGenerator(Generator):
66
66
  for value in enum.permissible_values:
67
67
  permissible_values.append(self.name_compatiblity.compatible(value))
68
68
  values = "\n ".join(permissible_values)
69
- return f"enum {camelcase(enum.name).replace(' ','')}\n {{\n {values}\n }}\n\n"
69
+ return f"enum {camelcase(enum.name).replace(' ', '')}\n {{\n {values}\n }}\n\n"
70
70
  else:
71
71
  logging.warning(
72
72
  f"Enumeration {enum.name} using `reachable_from` instead of `permissible_values` "
@@ -19,4 +19,3 @@ public class {{ cls.name }} {% if cls.is_a -%} extends {{ cls.is_a }} {%- endif
19
19
  {%- endfor %}
20
20
 
21
21
  }
22
-
@@ -197,7 +197,6 @@ class ContextGenerator(Generator):
197
197
  elif not uri_prefix or is_default_namespace:
198
198
  definition["@id"] = uri_suffix
199
199
  else:
200
-
201
200
  definition["@id"] = (uri_prefix + ":" + uri_suffix) if uri_prefix else uri
202
201
 
203
202
  if uri_prefix and not is_default_namespace:
@@ -106,7 +106,9 @@ class JSONLDGenerator(Generator):
106
106
  else (
107
107
  SubsetDefinitionName(camelcase(node))
108
108
  if node in self.schema.subsets
109
- else TypeDefinitionName(underscore(node)) if node in self.schema.types else None
109
+ else TypeDefinitionName(underscore(node))
110
+ if node in self.schema.types
111
+ else None
110
112
  )
111
113
  )
112
114
  )
@@ -694,8 +694,8 @@ class JsonSchemaGenerator(Generator, LifecycleMixin):
694
694
  if self.materialize_patterns:
695
695
  logger.info("Materializing patterns in the schema before serialization")
696
696
  self.schemaview.materialize_patterns()
697
-
698
- return self.generate().to_json(sort_keys=True, indent=self.indent if self.indent > 0 else None)
697
+ result = self.generate().to_json(sort_keys=True, indent=self.indent if self.indent > 0 else None)
698
+ return result.rstrip() + "\n"
699
699
 
700
700
 
701
701
  @shared_arguments(JsonSchemaGenerator)
@@ -73,7 +73,7 @@ class LinkmlGenerator(Generator):
73
73
  return yaml_str
74
74
  else:
75
75
  raise ValueError(
76
- f"{self.format} is an invalid format. Use one of the following " f"formats: {self.valid_formats}"
76
+ f"{self.format} is an invalid format. Use one of the following formats: {self.valid_formats}"
77
77
  )
78
78
 
79
79
 
@@ -149,6 +149,7 @@ class MarkdownGenerator(Generator):
149
149
  items = [i for i in items if i is not None]
150
150
  out = "\n".join(items) + "\n"
151
151
  out = pad_heading(out)
152
+ out = out.rstrip() + "\n"
152
153
  ixfile.write(out)
153
154
  return out
154
155
 
@@ -179,7 +180,7 @@ class MarkdownGenerator(Generator):
179
180
  yg = YumlGenerator(self)
180
181
  img_url = (
181
182
  yg.serialize(classes=[cls.name]).replace("?", "%3F").replace(" ", "%20").replace("|", "&#124;")
182
- )
183
+ ).rstrip()
183
184
 
184
185
  items.append(f"[![img]({img_url})]({img_url})")
185
186
 
@@ -204,7 +205,7 @@ class MarkdownGenerator(Generator):
204
205
  if cls.name in self.synopsis.mixinrefs:
205
206
  items.append(self.header(2, f"{mixin_local_name} for"))
206
207
  for mixin in sorted(self.synopsis.mixinrefs[cls.name].classrefs):
207
- items.append(self.bullet(f'{self.class_link(mixin, use_desc=True, after_link="(mixin)")}'))
208
+ items.append(self.bullet(f"{self.class_link(mixin, use_desc=True, after_link='(mixin)')}"))
208
209
 
209
210
  if cls.name in self.synopsis.classrefs:
210
211
  items.append(self.header(2, f"Referenced by {class_local_name}"))
@@ -253,6 +254,7 @@ class MarkdownGenerator(Generator):
253
254
  items.append(self.element_properties(cls))
254
255
  out = "\n".join(items)
255
256
  out = pad_heading(out)
257
+ out = out.rstrip() + "\n"
256
258
  clsfile.write(out)
257
259
  return out
258
260
 
@@ -272,6 +274,7 @@ class MarkdownGenerator(Generator):
272
274
  out += self.element_properties(typ)
273
275
  out += "\n"
274
276
  out = pad_heading(out)
277
+ out = out.rstrip() + "\n"
275
278
  typefile.write(out)
276
279
  return out
277
280
 
@@ -312,6 +315,7 @@ class MarkdownGenerator(Generator):
312
315
  items.append(self.element_properties(slot))
313
316
  out = "\n".join(items)
314
317
  out = pad_heading(out)
318
+ out = out.rstrip() + "\n"
315
319
  slotfile.write(out)
316
320
  return out
317
321
 
@@ -324,6 +328,7 @@ class MarkdownGenerator(Generator):
324
328
  items.append(self.element_properties(enum))
325
329
  out = "\n".join(items)
326
330
  out = pad_heading(out)
331
+ out = out.rstrip() + "\n"
327
332
  enumfile.write(out)
328
333
  return out
329
334
 
@@ -359,6 +364,7 @@ class MarkdownGenerator(Generator):
359
364
  items.append(self.element_properties(subset))
360
365
  out = "\n".join(items)
361
366
  out = pad_heading(out)
367
+ out = out.rstrip() + "\n"
362
368
  subsetfile.write(out)
363
369
  return out
364
370
 
@@ -513,7 +519,9 @@ class MarkdownGenerator(Generator):
513
519
  else (
514
520
  underscore(obj.name)
515
521
  if isinstance(obj, SlotDefinition)
516
- else underscore(obj.name) if isinstance(obj, EnumDefinition) else camelcase(obj.name)
522
+ else underscore(obj.name)
523
+ if isinstance(obj, EnumDefinition)
524
+ else camelcase(obj.name)
517
525
  )
518
526
  )
519
527
  subdir = "/types" if isinstance(obj, TypeDefinition) and not self.no_types_dir else ""
@@ -548,7 +556,7 @@ class MarkdownGenerator(Generator):
548
556
  for example in slot.examples:
549
557
  items.append(
550
558
  self.bullet(
551
- f'Example: {getattr(example, "value", " ")} {getattr(example, "description", " ")}',
559
+ f"Example: {getattr(example, 'value', ' ')} {getattr(example, 'description', ' ')}",
552
560
  level=1,
553
561
  )
554
562
  )
@@ -599,7 +607,7 @@ class MarkdownGenerator(Generator):
599
607
 
600
608
  def header(self, level: int, txt: str) -> str:
601
609
  txt = self.get_metamodel_slot_name(txt)
602
- out = f'\n{"#" * level} {txt}\n'
610
+ out = f"\n{'#' * level} {txt}\n"
603
611
  return out
604
612
 
605
613
  @staticmethod
@@ -608,7 +616,7 @@ class MarkdownGenerator(Generator):
608
616
 
609
617
  @staticmethod
610
618
  def bullet(txt: str, level=0) -> str:
611
- return f'{" " * level} * {txt}'
619
+ return f"{' ' * level} * {txt}"
612
620
 
613
621
  def frontmatter(self, thingtype: str, layout="default") -> str:
614
622
  return self.header(1, thingtype)
@@ -674,9 +682,12 @@ class MarkdownGenerator(Generator):
674
682
  link_name = obj.name
675
683
  link_ref = link_name
676
684
  desc = self.desc_for(obj, use_desc)
677
- return f"[{link_name}]" f"({link_ref}.{self.format})" + (f" {after_link} " if after_link else "") + (
678
- f" - {desc.split(nl)[0]}" if desc else ""
679
- )
685
+ return (
686
+ f"[{link_name}]"
687
+ f"({link_ref}.{self.format})"
688
+ + (f" {after_link} " if after_link else "")
689
+ + (f" - {desc.split(nl)[0]}" if desc else "")
690
+ ).rstrip()
680
691
 
681
692
  def type_link(
682
693
  self,
@@ -90,6 +90,10 @@ class MermaidClassDiagramGenerator(Generator):
90
90
  """Returns the canonical name for an element."""
91
91
  return element.name
92
92
 
93
+ def link_mermaid(self, element):
94
+ """Generates a link for the given element."""
95
+ return f"../{self.name(element)}"
96
+
93
97
  def all_type_object_names(self):
94
98
  return list(self.schemaview.all_types().keys())
95
99
 
@@ -1,5 +1,4 @@
1
1
  import logging
2
- import os
3
2
  from collections import defaultdict
4
3
  from dataclasses import dataclass, field
5
4
  from functools import lru_cache
@@ -45,7 +44,7 @@ GEN_MAP = {
45
44
  "markdown": (
46
45
  MarkdownGenerator,
47
46
  "docs/",
48
- {"directory": "{parent}", "index_file": "{name}.md"},
47
+ {"directory": "{parent}/docs", "index_file": "{name}.md"},
49
48
  ),
50
49
  "owl": (OwlSchemaGenerator, "owl/{name}.owl.ttl", {}),
51
50
  "prefixmap": (PrefixGenerator, "prefixmap/{name}.yaml", {}),
@@ -64,17 +63,16 @@ GEN_MAP = {
64
63
 
65
64
 
66
65
  @lru_cache
67
- def get_local_imports(schema_path: str, dir: str):
66
+ def get_local_imports(schema_path: Path, dir: Path):
68
67
  logger.info(f"GETTING IMPORTS = {schema_path}")
69
68
  all_imports = [schema_path]
70
- with open(schema_path) as stream:
71
- with open(schema_path) as stream:
72
- schema = yaml.safe_load(stream)
73
- for imp in schema.get("imports", []):
74
- imp_path = os.path.join(dir, imp) + ".yaml"
75
- logger.info(f" IMP={imp} // path={imp_path}")
76
- if os.path.isfile(imp_path):
77
- all_imports += get_local_imports(imp_path, dir)
69
+ with open(schema_path, encoding="utf-8") as stream:
70
+ schema = yaml.safe_load(stream)
71
+ for imp in schema.get("imports", []):
72
+ imp_path = dir / f"{imp}.yaml"
73
+ logger.info(f" IMP={imp} // path={imp_path}")
74
+ if imp_path.is_file():
75
+ all_imports += get_local_imports(imp_path, dir)
78
76
  return all_imports
79
77
 
80
78
 
@@ -102,11 +100,12 @@ class ProjectGenerator:
102
100
  def generate(schema_path: str, config: ProjectConfiguration = ProjectConfiguration()):
103
101
  if config.directory is None:
104
102
  raise Exception("Must pass directory")
105
- Path(config.directory).mkdir(parents=True, exist_ok=True)
103
+ output_dir = Path(config.directory)
104
+ output_dir.mkdir(parents=True, exist_ok=True)
106
105
  if config.mergeimports:
107
106
  all_schemas = [schema_path]
108
107
  else:
109
- all_schemas = get_local_imports(schema_path, os.path.dirname(schema_path))
108
+ all_schemas = get_local_imports(schema_path, Path(schema_path).parent)
110
109
  logger.debug(f"ALL_SCHEMAS = {all_schemas}")
111
110
  for gen_name, (gen_cls, gen_path_fmt, default_gen_args) in GEN_MAP.items():
112
111
  if config.includes is not None and config.includes != [] and gen_name not in config.includes:
@@ -118,14 +117,12 @@ class ProjectGenerator:
118
117
  logger.info(f"Generating: {gen_name}")
119
118
  for local_path in all_schemas:
120
119
  logger.info(f" SCHEMA: {local_path}")
121
- name = os.path.basename(local_path).replace(".yaml", "")
120
+ name = Path(local_path).stem
122
121
  gen_path = gen_path_fmt.format(name=name)
123
- gen_path_full = f"{config.directory}/{gen_path}"
124
- parts = gen_path_full.split("/")
125
- parent_dir = "/".join(parts[0:-1])
122
+ gen_path_full = output_dir / gen_path
123
+ parent_dir = gen_path_full.parent
126
124
  logger.info(f" PARENT={parent_dir}")
127
- Path(parent_dir).mkdir(parents=True, exist_ok=True)
128
- gen_path_full = "/".join(parts)
125
+ parent_dir.mkdir(parents=True, exist_ok=True)
129
126
  all_gen_args = {
130
127
  **default_gen_args,
131
128
  **config.generator_args.get(gen_name, {}),
@@ -149,7 +146,7 @@ class ProjectGenerator:
149
146
  gen_dump = gen.serialize(**serialize_args)
150
147
 
151
148
  if gen_name != "excel":
152
- if parts[-1] != "":
149
+ if gen_path_full.suffix != "":
153
150
  # markdowngen does not write to a file
154
151
  logger.info(f" WRITING TO: {gen_path_full}")
155
152
  with open(gen_path_full, "w", encoding="UTF-8") as stream:
@@ -6,17 +6,17 @@ LinkMLMeta = """
6
6
  class LinkMLMeta(RootModel):
7
7
  root: dict[str, Any] = {}
8
8
  model_config = ConfigDict(frozen=True)
9
-
9
+
10
10
  def __getattr__(self, key:str):
11
11
  return getattr(self.root, key)
12
-
12
+
13
13
  def __getitem__(self, key:str):
14
14
  return self.root[key]
15
-
15
+
16
16
  def __setitem__(self, key:str, value):
17
17
  self.root[key] = value
18
-
18
+
19
19
  def __contains__(self, key:str) -> bool:
20
20
  return key in self.root
21
-
21
+
22
22
  """
@@ -832,8 +832,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
832
832
  meta = remove_empty_items(source)
833
833
  else:
834
834
  raise ValueError(
835
- f"Unknown metadata mode '{self.metadata_mode}', needs to be one of "
836
- f"{[mode for mode in MetadataMode]}"
835
+ f"Unknown metadata mode '{self.metadata_mode}', needs to be one of {[mode for mode in MetadataMode]}"
837
836
  )
838
837
 
839
838
  model.meta = meta
@@ -1196,7 +1195,7 @@ Available templates to override:
1196
1195
  )
1197
1196
  @click.option(
1198
1197
  "--meta",
1199
- type=click.Choice([k for k in MetadataMode]),
1198
+ type=click.Choice([k.value for k in MetadataMode]),
1200
1199
  default="auto",
1201
1200
  help="How to include linkml schema metadata in generated pydantic classes. "
1202
1201
  "See docs for MetadataMode for full description of choices. "
@@ -596,7 +596,7 @@ class Imports(PydanticTemplateModel):
596
596
  else:
597
597
  return all([obj in an_import.objects for obj in item.objects])
598
598
  else:
599
- raise TypeError("Imports only contains single Import objects or other Imports\n" f"Got: {type(item)}")
599
+ raise TypeError(f"Imports only contains single Import objects or other Imports\nGot: {type(item)}")
600
600
 
601
601
  @field_validator("imports", mode="after")
602
602
  @classmethod
@@ -13,4 +13,4 @@ class {{ name }}(BaseModel):
13
13
  {% endfor %}
14
14
  {% else %}
15
15
  {{ "pass" }}
16
- {% endif %}
16
+ {% endif %}
@@ -22,4 +22,4 @@ class {{ name }}({% if bases is string %}{{ bases }}{% else %}{{ bases | join(',
22
22
  {% endif %}
23
23
  {% else %}
24
24
  pass
25
- {% endif %}
25
+ {% endif %}
@@ -6,4 +6,4 @@ if {{ condition }}:
6
6
  else:
7
7
  {% filter indent(width=4) %}
8
8
  {{ alternative }}
9
- {% endfilter %}
9
+ {% endfilter %}
@@ -2,4 +2,4 @@
2
2
  # see https://pydantic-docs.helpmanual.io/usage/models/#rebuilding-a-model
3
3
  {% for c in class_names -%}
4
4
  {{ c }}.model_rebuild()
5
- {% endfor %}
5
+ {% endfor %}
@@ -36,4 +36,4 @@ from {{ module }} import (
36
36
  {{ import }}
37
37
  {%- endfor -%}
38
38
  {%- endif -%}
39
- {% endif -%}
39
+ {% endif -%}
@@ -57,7 +57,7 @@ class PythonIfAbsentProcessor(IfAbsentProcessor):
57
57
  slot: SlotDefinition,
58
58
  cls: ClassDefinition,
59
59
  ):
60
- return f"datetime({int(year)}, {int(month)}, {int(day)}, " f"{int(hour)}, {int(minutes)}, {int(seconds)})"
60
+ return f"datetime({int(year)}, {int(month)}, {int(day)}, {int(hour)}, {int(minutes)}, {int(seconds)})"
61
61
 
62
62
  def map_uri_or_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
63
63
  return self._uri_for(default_value)