linkml 1.7.10__tar.gz → 1.8.0__tar.gz

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 (150) hide show
  1. {linkml-1.7.10 → linkml-1.8.0}/PKG-INFO +2 -2
  2. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/class.md.jinja2 +0 -14
  3. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/common_metadata.md.jinja2 +14 -1
  4. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/erdiagramgen.py +37 -3
  5. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/golanggen.py +1 -6
  6. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/plantumlgen.py +1 -5
  7. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/array.py +1 -12
  8. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/black.py +1 -3
  9. linkml-1.8.0/linkml/generators/pydanticgen/includes.py +46 -0
  10. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/pydanticgen.py +109 -16
  11. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/template.py +26 -0
  12. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/attribute.py.jinja +7 -1
  13. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/class.py.jinja +4 -0
  14. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/module.py.jinja +12 -5
  15. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pythongen.py +1 -8
  16. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/shaclgen.py +95 -3
  17. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/sqlalchemygen.py +2 -10
  18. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/rules.py +1 -6
  19. {linkml-1.7.10 → linkml-1.8.0}/linkml/transformers/logical_model_transformer.py +6 -2
  20. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/schemaloader.py +27 -0
  21. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/schemasynopsis.py +1 -9
  22. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/__init__.py +1 -3
  23. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/plugins/__init__.py +1 -3
  24. {linkml-1.7.10 → linkml-1.8.0}/pyproject.toml +2 -2
  25. {linkml-1.7.10 → linkml-1.8.0}/setup.py +2 -2
  26. {linkml-1.7.10 → linkml-1.8.0}/LICENSE +0 -0
  27. {linkml-1.7.10 → linkml-1.8.0}/README.md +0 -0
  28. {linkml-1.7.10 → linkml-1.8.0}/linkml/__init__.py +0 -0
  29. {linkml-1.7.10 → linkml-1.8.0}/linkml/_version.py +0 -0
  30. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/PythonGenNotes.md +0 -0
  31. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/README.md +0 -0
  32. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/__init__.py +0 -0
  33. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/common/__init__.py +0 -0
  34. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/common/type_designators.py +0 -0
  35. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/csvgen.py +0 -0
  36. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/class_diagram.md.jinja2 +0 -0
  37. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/enum.md.jinja2 +0 -0
  38. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/index.md.jinja2 +0 -0
  39. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/index.tex.jinja2 +0 -0
  40. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/schema.md.jinja2 +0 -0
  41. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/slot.md.jinja2 +0 -0
  42. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/subset.md.jinja2 +0 -0
  43. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen/type.md.jinja2 +0 -0
  44. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/docgen.py +0 -0
  45. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/dotgen.py +0 -0
  46. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/excelgen.py +0 -0
  47. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/golrgen.py +0 -0
  48. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/graphqlgen.py +0 -0
  49. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/javagen/example_template.java.jinja2 +0 -0
  50. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/javagen/java_record_template.jinja2 +0 -0
  51. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/javagen.py +0 -0
  52. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/jsonldcontextgen.py +0 -0
  53. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/jsonldgen.py +0 -0
  54. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/jsonschemagen.py +0 -0
  55. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/legacy/__init__.py +0 -0
  56. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/linkmlgen.py +0 -0
  57. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/markdowngen.py +0 -0
  58. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/namespacegen.py +0 -0
  59. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/oocodegen.py +0 -0
  60. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/owlgen.py +0 -0
  61. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/prefixmapgen.py +0 -0
  62. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/projectgen.py +0 -0
  63. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/protogen.py +0 -0
  64. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/__init__.py +0 -0
  65. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/build.py +0 -0
  66. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -0
  67. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/conditional_import.py.jinja +0 -0
  68. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/enum.py.jinja +0 -0
  69. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/footer.py.jinja +0 -0
  70. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/imports.py.jinja +0 -0
  71. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/pydanticgen/templates/validator.py.jinja +0 -0
  72. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/rdfgen.py +0 -0
  73. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/shacl/__init__.py +0 -0
  74. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/shacl/ifabsent_processor.py +0 -0
  75. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/shacl/shacl_data_type.py +0 -0
  76. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/shexgen.py +0 -0
  77. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/sparqlgen.py +0 -0
  78. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/sqlalchemy/__init__.py +0 -0
  79. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +0 -0
  80. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +0 -0
  81. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/sqltablegen.py +0 -0
  82. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/sssomgen.py +0 -0
  83. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/string_template.md +0 -0
  84. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/summarygen.py +0 -0
  85. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/terminusdbgen.py +0 -0
  86. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/typescriptgen.py +0 -0
  87. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/yamlgen.py +0 -0
  88. {linkml-1.7.10 → linkml-1.8.0}/linkml/generators/yumlgen.py +0 -0
  89. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/__init__.py +0 -0
  90. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/cli.py +0 -0
  91. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/config/datamodel/.linkmllint.yaml +0 -0
  92. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/config/datamodel/__init__.py +0 -0
  93. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/config/datamodel/config.py +0 -0
  94. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/config/datamodel/config.yaml +0 -0
  95. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/config/default.yaml +0 -0
  96. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/config/recommended.yaml +0 -0
  97. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/formatters/__init__.py +0 -0
  98. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/formatters/formatter.py +0 -0
  99. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/formatters/json_formatter.py +0 -0
  100. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/formatters/markdown_formatter.py +0 -0
  101. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/formatters/terminal_formatter.py +0 -0
  102. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/formatters/tsv_formatter.py +0 -0
  103. {linkml-1.7.10 → linkml-1.8.0}/linkml/linter/linter.py +0 -0
  104. {linkml-1.7.10 → linkml-1.8.0}/linkml/reporting/__init__.py +0 -0
  105. {linkml-1.7.10 → linkml-1.8.0}/linkml/reporting/model.py +0 -0
  106. {linkml-1.7.10 → linkml-1.8.0}/linkml/transformers/__init__.py +0 -0
  107. {linkml-1.7.10 → linkml-1.8.0}/linkml/transformers/model_transformer.py +0 -0
  108. {linkml-1.7.10 → linkml-1.8.0}/linkml/transformers/relmodel_transformer.py +0 -0
  109. {linkml-1.7.10 → linkml-1.8.0}/linkml/transformers/schema_renamer.py +0 -0
  110. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/__init__.py +0 -0
  111. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/cli_utils.py +0 -0
  112. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/converter.py +0 -0
  113. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/datautils.py +0 -0
  114. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/datavalidator.py +0 -0
  115. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/deprecation.py +0 -0
  116. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/execute_tutorial.py +0 -0
  117. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/generator.py +0 -0
  118. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/helpers.py +0 -0
  119. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/ifabsent_functions.py +0 -0
  120. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/logictools.py +0 -0
  121. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/mergeutils.py +0 -0
  122. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/rawloader.py +0 -0
  123. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/schema_builder.py +0 -0
  124. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/schema_fixer.py +0 -0
  125. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/sqlutils.py +0 -0
  126. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/typereferences.py +0 -0
  127. {linkml-1.7.10 → linkml-1.8.0}/linkml/utils/validation.py +0 -0
  128. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/cli.py +0 -0
  129. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/loaders/__init__.py +0 -0
  130. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/loaders/delimited_file_loader.py +0 -0
  131. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/loaders/json_loader.py +0 -0
  132. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/loaders/loader.py +0 -0
  133. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/loaders/passthrough_loader.py +0 -0
  134. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/loaders/yaml_loader.py +0 -0
  135. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/plugins/jsonschema_validation_plugin.py +0 -0
  136. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/plugins/pydantic_validation_plugin.py +0 -0
  137. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/plugins/recommended_slots_plugin.py +0 -0
  138. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/plugins/shacl_validation_plugin.py +0 -0
  139. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/plugins/validation_plugin.py +0 -0
  140. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/report.py +0 -0
  141. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/validation_context.py +0 -0
  142. {linkml-1.7.10 → linkml-1.8.0}/linkml/validator/validator.py +0 -0
  143. {linkml-1.7.10 → linkml-1.8.0}/linkml/validators/__init__.py +0 -0
  144. {linkml-1.7.10 → linkml-1.8.0}/linkml/validators/jsonschemavalidator.py +0 -0
  145. {linkml-1.7.10 → linkml-1.8.0}/linkml/validators/sparqlvalidator.py +0 -0
  146. {linkml-1.7.10 → linkml-1.8.0}/linkml/workspaces/__init__.py +0 -0
  147. {linkml-1.7.10 → linkml-1.8.0}/linkml/workspaces/datamodel/__init__.py +0 -0
  148. {linkml-1.7.10 → linkml-1.8.0}/linkml/workspaces/datamodel/workspaces.py +0 -0
  149. {linkml-1.7.10 → linkml-1.8.0}/linkml/workspaces/datamodel/workspaces.yaml +0 -0
  150. {linkml-1.7.10 → linkml-1.8.0}/linkml/workspaces/example_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml
3
- Version: 1.7.10
3
+ Version: 1.8.0
4
4
  Summary: Linked Open Data Modeling Language
5
5
  Home-page: https://linkml.io/linkml/
6
6
  Keywords: schema,linked data,data modeling,rdf,owl,biolink
@@ -36,7 +36,7 @@ Requires-Dist: jinja2 (>=3.1.0)
36
36
  Requires-Dist: jsonasobj2 (>=1.0.3,<2.0.0)
37
37
  Requires-Dist: jsonschema[format] (>=4.0.0)
38
38
  Requires-Dist: linkml-dataops
39
- Requires-Dist: linkml-runtime (>=1.7.4)
39
+ Requires-Dist: linkml-runtime (==1.8.0)
40
40
  Requires-Dist: openpyxl
41
41
  Requires-Dist: parse
42
42
  Requires-Dist: prefixcommons (>=0.1.7)
@@ -99,20 +99,6 @@ URI: {{ gen.uri_link(element) }}
99
99
 
100
100
  {% include "common_metadata.md.jinja2" %}
101
101
 
102
-
103
- {% if schemaview.get_mappings(element.name).items() -%}
104
- ## Mappings
105
-
106
- | Mapping Type | Mapped Value |
107
- | --- | --- |
108
- {% for m, mt in schemaview.get_mappings(element.name).items() -%}
109
- {% if mt|length > 0 -%}
110
- | {{ m }} | {{ mt|join(', ') }} |
111
- {% endif -%}
112
- {% endfor %}
113
-
114
- {% endif -%}
115
-
116
102
  {% if gen.example_object_blobs(element.name) -%}
117
103
  ## Examples
118
104
  {% for name, blob in gen.example_object_blobs(element.name) -%}
@@ -75,4 +75,17 @@ Instances of this class *should* have identifiers with one of the following pref
75
75
  {% if element.imported_from %}
76
76
  * imported from: {{ element.imported_from }}
77
77
  {% endif %}
78
- {% endif %}
78
+ {% endif %}
79
+
80
+ {% if schemaview.get_mappings(element.name).items() -%}
81
+ ## Mappings
82
+
83
+ | Mapping Type | Mapped Value |
84
+ | --- | --- |
85
+ {% for m, mt in schemaview.get_mappings(element.name).items() -%}
86
+ {% if mt|length > 0 -%}
87
+ | {{ m }} | {{ mt|join(', ') }} |
88
+ {% endif -%}
89
+ {% endfor %}
90
+
91
+ {% endif -%}
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
- from typing import List, Optional, Union
4
+ from typing import List, Optional, Set, Union
5
5
 
6
6
  import click
7
7
  import pydantic
@@ -163,6 +163,7 @@ class ERDiagramGenerator(Generator):
163
163
  class_names: List[Union[str, ClassDefinitionName]],
164
164
  follow_references=False,
165
165
  max_hops: int = None,
166
+ include_upstream: bool = False,
166
167
  ) -> MERMAID_SERIALIZATION:
167
168
  """
168
169
  Serialize a list of classes as an ER Diagram.
@@ -195,6 +196,15 @@ class ERDiagramGenerator(Generator):
195
196
  if follow_references or sv.is_inlined(slot):
196
197
  if rng not in visited:
197
198
  stack.append((rng, depth + 1))
199
+
200
+ # Now Add upstream classes if needed
201
+ if include_upstream:
202
+ for sn in sv.all_slots():
203
+ slot = sv.schema.slots.get(sn)
204
+ if slot and slot.range in set(class_names):
205
+ for cl in sv.all_classes():
206
+ if slot.name in sv.get_class(cl).slots and cl not in visited:
207
+ self.add_upstream_class(cl, set(class_names), diagram)
198
208
  return self.serialize_diagram(diagram)
199
209
 
200
210
  def serialize_diagram(self, diagram: ERDiagram) -> str:
@@ -210,6 +220,15 @@ class ERDiagramGenerator(Generator):
210
220
  else:
211
221
  return er
212
222
 
223
+ def add_upstream_class(self, class_name: ClassDefinitionName, targets: Set[str], diagram: ERDiagram) -> None:
224
+ sv = self.schemaview
225
+ cls = sv.get_class(class_name)
226
+ entity = Entity(name=camelcase(cls.name))
227
+ diagram.entities.append(entity)
228
+ for slot in sv.class_induced_slots(class_name):
229
+ if slot.range in targets:
230
+ self.add_relationship(entity, slot, diagram)
231
+
213
232
  def add_class(self, class_name: ClassDefinitionName, diagram: ERDiagram) -> None:
214
233
  """
215
234
  Add a class to the ER Diagram.
@@ -291,9 +310,17 @@ class ERDiagramGenerator(Generator):
291
310
  )
292
311
  @click.option("--max-hops", default=None, type=click.INT, help="Maximum number of hops")
293
312
  @click.option("--classes", "-c", multiple=True, help="List of classes to serialize")
313
+ @click.option("--include-upstream", is_flag=True, help="Include upstream classes")
294
314
  @click.version_option(__version__, "-V", "--version")
295
315
  @click.command()
296
- def cli(yamlfile, classes: List[str], max_hops: Optional[int], follow_references: bool, **args):
316
+ def cli(
317
+ yamlfile,
318
+ classes: List[str],
319
+ max_hops: Optional[int],
320
+ follow_references: bool,
321
+ include_upstream: bool = False,
322
+ **args,
323
+ ):
297
324
  """Generate a mermaid ER diagram from a schema.
298
325
 
299
326
  By default, all entities traversable from the tree_root are included. If no tree_root is
@@ -306,7 +333,14 @@ def cli(yamlfile, classes: List[str], max_hops: Optional[int], follow_references
306
333
  **args,
307
334
  )
308
335
  if classes:
309
- print(gen.serialize_classes(classes, follow_references=follow_references, max_hops=max_hops))
336
+ print(
337
+ gen.serialize_classes(
338
+ classes,
339
+ follow_references=follow_references,
340
+ max_hops=max_hops,
341
+ include_upstream=include_upstream,
342
+ )
343
+ )
310
344
  else:
311
345
  print(gen.serialize())
312
346
 
@@ -5,12 +5,7 @@ from typing import List, Optional
5
5
 
6
6
  import click
7
7
  from jinja2 import Template
8
- from linkml_runtime.linkml_model.meta import (
9
- ClassDefinition,
10
- ClassDefinitionName,
11
- Element,
12
- SlotDefinition,
13
- )
8
+ from linkml_runtime.linkml_model.meta import ClassDefinition, ClassDefinitionName, Element, SlotDefinition
14
9
  from linkml_runtime.utils.formatutils import camelcase, underscore
15
10
 
16
11
  from linkml._version import __version__
@@ -12,11 +12,7 @@ from typing import Callable, List, Optional, Set, cast
12
12
 
13
13
  import click
14
14
  import requests
15
- from linkml_runtime.linkml_model.meta import (
16
- ClassDefinition,
17
- ClassDefinitionName,
18
- SlotDefinition,
19
- )
15
+ from linkml_runtime.linkml_model.meta import ClassDefinition, ClassDefinitionName, SlotDefinition
20
16
  from linkml_runtime.utils.formatutils import camelcase, underscore
21
17
 
22
18
  from linkml import REQUESTS_TIMEOUT
@@ -1,18 +1,7 @@
1
1
  import sys
2
2
  from abc import ABC, abstractmethod
3
3
  from enum import Enum
4
- from typing import (
5
- Any,
6
- ClassVar,
7
- Generic,
8
- Iterable,
9
- List,
10
- Optional,
11
- Type,
12
- TypeVar,
13
- Union,
14
- get_args,
15
- )
4
+ from typing import Any, ClassVar, Generic, Iterable, List, Optional, Type, TypeVar, Union, get_args
16
5
 
17
6
  from linkml_runtime.linkml_model import Element
18
7
  from linkml_runtime.linkml_model.meta import ArrayExpression, DimensionExpression
@@ -16,9 +16,7 @@ except ImportError:
16
16
 
17
17
 
18
18
  def _default_mode() -> "Mode":
19
- return Mode(
20
- target_versions={TargetVersion.PY311},
21
- )
19
+ return Mode(target_versions={TargetVersion.PY311}, line_length=120)
22
20
 
23
21
 
24
22
  def format_black(code: str, mode: Optional["Mode"] = None) -> str:
@@ -0,0 +1,46 @@
1
+ """
2
+ Classes to inject in generated pydantic models
3
+ """
4
+
5
+ from pydantic.version import VERSION
6
+
7
+ PYDANTIC_VERSION = int(VERSION[0])
8
+
9
+
10
+ LinkMLMeta_v1 = """
11
+ class LinkMLMeta(BaseModel):
12
+ __root__: Dict[str, Any] = {}
13
+
14
+ def __getattr__(self, key:str):
15
+ return getattr(self.__root__, key)
16
+
17
+ def __getitem__(self, key:str):
18
+ return self.__root__[key]
19
+
20
+ def __setitem__(self, key:str, value):
21
+ self.__root__[key] = value
22
+
23
+ class Config:
24
+ allow_mutation = False
25
+ """
26
+
27
+ LinkMLMeta_v2 = """
28
+ class LinkMLMeta(RootModel):
29
+ root: Dict[str, Any] = {}
30
+ model_config = ConfigDict(frozen=True)
31
+
32
+ def __getattr__(self, key:str):
33
+ return getattr(self.root, key)
34
+
35
+ def __getitem__(self, key:str):
36
+ return self.root[key]
37
+
38
+ def __setitem__(self, key:str, value):
39
+ self.root[key] = value
40
+
41
+ """
42
+
43
+ # if PYDANTIC_VERSION >= 2:
44
+ # LinkMLMeta = eval(LinkMLMeta_v2)
45
+ # else:
46
+ # LinkMLMeta = eval(LinkMLMeta_v1)
@@ -3,19 +3,12 @@ import logging
3
3
  import os
4
4
  import textwrap
5
5
  from collections import defaultdict
6
- from copy import deepcopy
6
+ from copy import copy, deepcopy
7
7
  from dataclasses import dataclass, field
8
+ from enum import Enum
8
9
  from pathlib import Path
9
10
  from types import ModuleType
10
- from typing import (
11
- Dict,
12
- List,
13
- Literal,
14
- Optional,
15
- Set,
16
- Type,
17
- Union,
18
- )
11
+ from typing import Dict, List, Literal, Optional, Set, Type, TypeVar, Union, overload
19
12
 
20
13
  import click
21
14
  from jinja2 import ChoiceLoader, Environment, FileSystemLoader
@@ -27,16 +20,14 @@ from linkml_runtime.linkml_model.meta import (
27
20
  TypeDefinition,
28
21
  )
29
22
  from linkml_runtime.utils.compile_python import compile_python
30
- from linkml_runtime.utils.formatutils import camelcase, underscore
23
+ from linkml_runtime.utils.formatutils import camelcase, remove_empty_items, underscore
31
24
  from linkml_runtime.utils.schemaview import SchemaView
32
25
  from pydantic.version import VERSION as PYDANTIC_VERSION
33
26
 
34
27
  from linkml._version import __version__
35
- from linkml.generators.common.type_designators import (
36
- get_accepted_type_designator_values,
37
- get_type_designator_value,
38
- )
28
+ from linkml.generators.common.type_designators import get_accepted_type_designator_values, get_type_designator_value
39
29
  from linkml.generators.oocodegen import OOCodeGenerator
30
+ from linkml.generators.pydanticgen import includes
40
31
  from linkml.generators.pydanticgen.array import ArrayRangeGenerator, ArrayRepresentation
41
32
  from linkml.generators.pydanticgen.build import SlotResult
42
33
  from linkml.generators.pydanticgen.template import (
@@ -85,6 +76,7 @@ DEFAULT_IMPORTS = (
85
76
  module="typing",
86
77
  objects=[
87
78
  ObjectImport(name="Any"),
79
+ ObjectImport(name="ClassVar"),
88
80
  ObjectImport(name="List"),
89
81
  ObjectImport(name="Literal"),
90
82
  ObjectImport(name="Dict"),
@@ -100,6 +92,7 @@ DEFAULT_IMPORTS = (
100
92
  ObjectImport(name="BaseModel"),
101
93
  ObjectImport(name="ConfigDict"),
102
94
  ObjectImport(name="Field"),
95
+ ObjectImport(name="RootModel"),
103
96
  ObjectImport(name="field_validator"),
104
97
  ],
105
98
  alternative=Import(
@@ -109,6 +102,33 @@ DEFAULT_IMPORTS = (
109
102
  )
110
103
  )
111
104
 
105
+ DEFAULT_INJECTS = {1: [includes.LinkMLMeta_v1], 2: [includes.LinkMLMeta_v2]}
106
+
107
+
108
+ class MetadataMode(str, Enum):
109
+ FULL = "full"
110
+ """
111
+ all metadata from the source schema will be included, even if it is represented by the template classes,
112
+ and even if it is represented by some child class (eg. "classes" will be included with schema metadata
113
+ """
114
+ EXCEPT_CHILDREN = "except_children"
115
+ """
116
+ all metadata from the source schema will be included, even if it is represented by the template classes,
117
+ except if it is represented by some child template class (eg. "classes" will be excluded from schema metadata)
118
+ """
119
+ AUTO = "auto"
120
+ """
121
+ Only the metadata that isn't represented by the template classes or excluded with ``meta_exclude`` will be included
122
+ """
123
+ NONE = None
124
+ """
125
+ No metadata will be included.
126
+ """
127
+
128
+
129
+ DefinitionType = TypeVar("DefinitionType", bound=Union[SchemaDefinition, ClassDefinition, SlotDefinition])
130
+ TemplateType = TypeVar("TemplateType", bound=Union[PydanticModule, PydanticClass, PydanticAttribute])
131
+
112
132
 
113
133
  @dataclass
114
134
  class PydanticGenerator(OOCodeGenerator):
@@ -212,6 +232,12 @@ class PydanticGenerator(OOCodeGenerator):
212
232
  from typing_extensions import Literal
213
233
 
214
234
  """
235
+ metadata_mode: Union[MetadataMode, str, None] = MetadataMode.AUTO
236
+ """
237
+ How to include schema metadata in generated pydantic models.
238
+
239
+ See :class:`.MetadataMode` for mode documentation
240
+ """
215
241
 
216
242
  # ObjectVars (identical to pythongen)
217
243
  gen_classvars: bool = True
@@ -530,6 +556,56 @@ class PydanticGenerator(OOCodeGenerator):
530
556
 
531
557
  return array_reps
532
558
 
559
+ @overload
560
+ def include_metadata(self, model: PydanticModule, source: SchemaDefinition) -> PydanticModule: ...
561
+
562
+ @overload
563
+ def include_metadata(self, model: PydanticClass, source: ClassDefinition) -> PydanticClass: ...
564
+
565
+ @overload
566
+ def include_metadata(self, model: PydanticAttribute, source: SlotDefinition) -> PydanticAttribute: ...
567
+
568
+ def include_metadata(self, model: TemplateType, source: DefinitionType) -> TemplateType:
569
+ """
570
+ Include metadata from the source schema that is otherwise not represented in the pydantic template models.
571
+
572
+ Metadata inclusion mode is dependent on :attr:`.metadata_mode` - see:
573
+
574
+ - :class:`.MetadataMode`
575
+ - :meth:`.TemplateModel.exclude_from_meta`
576
+
577
+ """
578
+ if self.metadata_mode is None or self.metadata_mode == MetadataMode.NONE:
579
+ return model
580
+ elif self.metadata_mode in (MetadataMode.AUTO, MetadataMode.AUTO.value):
581
+ meta = {k: v for k, v in remove_empty_items(source).items() if k not in model.exclude_from_meta()}
582
+ elif self.metadata_mode in (MetadataMode.EXCEPT_CHILDREN, MetadataMode.EXCEPT_CHILDREN.value):
583
+ meta = {}
584
+ for k, v in remove_empty_items(source).items():
585
+ if not hasattr(model, k):
586
+ meta[k] = v
587
+ elif isinstance(getattr(model, k), list) and not any(
588
+ [isinstance(item, TemplateModel) for item in getattr(model, k)]
589
+ ):
590
+ meta[k] = v
591
+ elif isinstance(getattr(model, k), dict) and not any(
592
+ [isinstance(item, TemplateModel) for item in getattr(model, k).values()]
593
+ ):
594
+ meta[k] = v
595
+ elif not isinstance(getattr(model, k), TemplateModel):
596
+ meta[k] = v
597
+
598
+ elif self.metadata_mode in (MetadataMode.FULL, MetadataMode.FULL.value):
599
+ meta = remove_empty_items(source)
600
+ else:
601
+ raise ValueError(
602
+ f"Unknown metadata mode '{self.metadata_mode}', needs to be one of "
603
+ f"{[mode.value for mode in MetadataMode]}"
604
+ )
605
+
606
+ model.meta = meta
607
+ return model
608
+
533
609
  def render(self) -> PydanticModule:
534
610
  sv: SchemaView
535
611
  sv = self.schemaview
@@ -540,7 +616,7 @@ class PydanticGenerator(OOCodeGenerator):
540
616
  description=schema.description.replace('"', '\\"') if schema.description else None,
541
617
  )
542
618
  enums = self.generate_enums(sv.all_enums())
543
- injected_classes = []
619
+ injected_classes = copy(DEFAULT_INJECTS[self.pydantic_version])
544
620
  if self.injected_classes is not None:
545
621
  injected_classes += self.injected_classes
546
622
 
@@ -665,15 +741,22 @@ class PydanticGenerator(OOCodeGenerator):
665
741
  predef_slot = str(predef_slot)
666
742
  new_fields["predefined"] = predef_slot
667
743
  new_fields["name"] = attr_name
744
+
668
745
  attrs[attr_name] = PydanticAttribute(**new_fields, pydantic_ver=self.pydantic_version)
746
+ attrs[attr_name] = self.include_metadata(attrs[attr_name], src_attr)
669
747
 
670
748
  new_class = PydanticClass(
671
749
  name=k, attributes=attrs, description=c.description, pydantic_ver=self.pydantic_version
672
750
  )
751
+ new_class = self.include_metadata(new_class, c)
673
752
  if k in bases:
674
753
  new_class.bases = bases[k]
675
754
  classes[k] = new_class
676
755
 
756
+ schema_meta = {
757
+ k: v for k, v in remove_empty_items(schema).items() if k not in PydanticModule.exclude_from_meta()
758
+ }
759
+
677
760
  module = PydanticModule(
678
761
  pydantic_ver=self.pydantic_version,
679
762
  metamodel_version=self.schema.metamodel_version,
@@ -683,7 +766,9 @@ class PydanticGenerator(OOCodeGenerator):
683
766
  injected_classes=injected_classes,
684
767
  enums=enums,
685
768
  classes=classes,
769
+ meta=schema_meta,
686
770
  )
771
+ module = self.include_metadata(module, pyschema)
687
772
  return module
688
773
 
689
774
  def serialize(self) -> str:
@@ -739,6 +824,12 @@ Available templates to override:
739
824
  default="forbid",
740
825
  help="How to handle extra fields in BaseModel.",
741
826
  )
827
+ @click.option(
828
+ "--black",
829
+ is_flag=True,
830
+ default=False,
831
+ help="Format generated models with black (must be present in the environment)",
832
+ )
742
833
  @click.version_option(__version__, "-V", "--version")
743
834
  @click.command()
744
835
  def cli(
@@ -752,6 +843,7 @@ def cli(
752
843
  array_representations=list("list"),
753
844
  pydantic_version=int(PYDANTIC_VERSION[0]),
754
845
  extra_fields: Literal["allow", "forbid", "ignore"] = "forbid",
846
+ black: bool = False,
755
847
  **args,
756
848
  ):
757
849
  """Generate pydantic classes to represent a LinkML model"""
@@ -777,6 +869,7 @@ def cli(
777
869
  gen_classvars=classvars,
778
870
  gen_slots=slots,
779
871
  template_dir=template_dir,
872
+ black=black,
780
873
  **args,
781
874
  )
782
875
  print(gen.serialize())
@@ -60,6 +60,7 @@ class TemplateModel(BaseModel):
60
60
  )
61
61
 
62
62
  pydantic_ver: int = int(PYDANTIC_VERSION[0])
63
+ meta_exclude: ClassVar[List[str]] = None
63
64
 
64
65
  def render(self, environment: Optional[Environment] = None, black: bool = False) -> str:
65
66
  """
@@ -132,6 +133,16 @@ class TemplateModel(BaseModel):
132
133
  return self.json(**kwargs)
133
134
  return self.dict(**kwargs)
134
135
 
136
+ @classmethod
137
+ def exclude_from_meta(cls: "TemplateModel") -> List[str]:
138
+ """
139
+ Attributes in the source definition to exclude from linkml_meta
140
+ """
141
+ ret = [*cls.model_fields.keys()]
142
+ if cls.meta_exclude is not None:
143
+ ret = ret + cls.meta_exclude
144
+ return ret
145
+
135
146
 
136
147
  def _render(
137
148
  item: Union[TemplateModel, Any, List[Union[Any, TemplateModel]], Dict[str, Union[Any, TemplateModel]]],
@@ -213,6 +224,7 @@ class PydanticAttribute(TemplateModel):
213
224
  """
214
225
 
215
226
  template: ClassVar[str] = "attribute.py.jinja"
227
+ meta_exclude: ClassVar[List[str]] = ["from_schema", "owner", "range", "multivalued", "inlined", "inlined_as_list"]
216
228
 
217
229
  name: str
218
230
  required: bool = False
@@ -237,6 +249,10 @@ class PydanticAttribute(TemplateModel):
237
249
  minimum_value: Optional[Union[int, float]] = None
238
250
  maximum_value: Optional[Union[int, float]] = None
239
251
  pattern: Optional[str] = None
252
+ meta: Optional[Dict[str, Any]] = None
253
+ """
254
+ Metadata for the slot to be included in a Field annotation
255
+ """
240
256
 
241
257
  if int(PYDANTIC_VERSION[0]) >= 2:
242
258
 
@@ -283,11 +299,16 @@ class PydanticClass(TemplateModel):
283
299
  """
284
300
 
285
301
  template: ClassVar[str] = "class.py.jinja"
302
+ meta_exclude: ClassVar[List[str]] = ["slots", "is_a"]
286
303
 
287
304
  name: str
288
305
  bases: Union[List[str], str] = PydanticBaseModel.default_name
289
306
  description: Optional[str] = None
290
307
  attributes: Optional[Dict[str, PydanticAttribute]] = None
308
+ meta: Optional[Dict[str, Any]] = None
309
+ """
310
+ Metadata for the class to be included in a linkml_meta class attribute
311
+ """
291
312
 
292
313
  def _validators(self) -> Optional[Dict[str, PydanticValidator]]:
293
314
  if self.attributes is None:
@@ -548,6 +569,7 @@ class PydanticModule(TemplateModel):
548
569
  """
549
570
 
550
571
  template: ClassVar[str] = "module.py.jinja"
572
+ meta_exclude: ClassVar[str] = ["slots"]
551
573
 
552
574
  metamodel_version: Optional[str] = None
553
575
  version: Optional[str] = None
@@ -556,6 +578,10 @@ class PydanticModule(TemplateModel):
556
578
  imports: List[Union[Import, ConditionalImport]] = Field(default_factory=list)
557
579
  enums: Dict[str, PydanticEnum] = Field(default_factory=dict)
558
580
  classes: Dict[str, PydanticClass] = Field(default_factory=dict)
581
+ meta: Optional[Dict[str, Any]] = None
582
+ """
583
+ Metadata for the schema to be included in a linkml_meta module-level instance of LinkMLMeta
584
+ """
559
585
 
560
586
  if int(PYDANTIC_VERSION[0]) >= 2:
561
587
 
@@ -7,4 +7,10 @@
7
7
  {%- if minimum_value != None %}, ge={{minimum_value}}{% endif -%}
8
8
  {%- if maximum_value != None %}, le={{maximum_value}}{% endif -%}
9
9
  {%- endif -%}
10
- )
10
+ {%- if meta != None -%}
11
+ {%- if pydantic_ver == 1 -%}
12
+ , linkml_meta = {{ meta | pprint | indent(width=8) }}
13
+ {% else -%}
14
+ , json_schema_extra = { "linkml_meta": {{ meta | pprint | indent(width=8) }} }
15
+ {%- endif -%}
16
+ {%- endif -%})
@@ -4,6 +4,10 @@ class {{ name }}({% if bases is string %}{{ bases }}{% else %}{{ bases | join(',
4
4
  {{ description | indent(width=4) }}
5
5
  """
6
6
  {% endif -%}
7
+ {% if meta != None %}
8
+ linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({% if pydantic_ver == 1 %}__root__={% endif %}{{ meta | pprint | indent(width=8) }})
9
+
10
+ {% endif %}
7
11
  {% if attributes or validators %}
8
12
  {% if attributes %}
9
13
  {% for attr in attributes.values() %}
@@ -7,18 +7,25 @@ version = "{{version if version else None}}"
7
7
 
8
8
 
9
9
  {{ base_model }}
10
- {% if enums %}
11
- {% for e in enums.values() %}
12
10
 
13
- {{ e }}
14
- {% endfor %}
15
- {% endif %}
16
11
  {% if injected_classes %}
17
12
  {% for c in injected_classes%}
18
13
 
19
14
  {{ c }}
20
15
  {% endfor %}
21
16
  {% endif %}
17
+ {% if meta != None %}
18
+ linkml_meta = LinkMLMeta({% if pydantic_ver == 1 %}__root__={% endif %}{{ meta | pprint | indent(width=4) }} )
19
+ {% else %}
20
+ linkml_meta = None
21
+ {% endif %}
22
+ {% if enums %}
23
+ {% for e in enums.values() %}
24
+
25
+ {{ e }}
26
+ {% endfor %}
27
+ {% endif %}
28
+
22
29
  {% for c in classes.values() %}
23
30
 
24
31
  {{ c }}
@@ -23,14 +23,7 @@ from linkml_runtime.linkml_model.meta import (
23
23
  TypeDefinition,
24
24
  )
25
25
  from linkml_runtime.utils.compile_python import compile_python
26
- from linkml_runtime.utils.formatutils import (
27
- be,
28
- camelcase,
29
- sfx,
30
- split_col,
31
- underscore,
32
- wrapped_annotation,
33
- )
26
+ from linkml_runtime.utils.formatutils import be, camelcase, sfx, split_col, underscore, wrapped_annotation
34
27
  from linkml_runtime.utils.metamodelcore import builtinnames
35
28
  from rdflib import URIRef
36
29