linkml 1.8.5__tar.gz → 1.8.7__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 (163) hide show
  1. {linkml-1.8.5 → linkml-1.8.7}/PKG-INFO +2 -2
  2. {linkml-1.8.5 → linkml-1.8.7}/linkml/cli/main.py +2 -0
  3. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/build.py +1 -2
  4. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/lifecycle.py +18 -2
  5. linkml-1.8.7/linkml/generators/dbmlgen.py +173 -0
  6. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/erdiagramgen.py +1 -0
  7. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/jsonldcontextgen.py +7 -1
  8. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/jsonldgen.py +1 -3
  9. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/jsonschemagen.py +84 -53
  10. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/linkmlgen.py +13 -1
  11. linkml-1.8.7/linkml/generators/mermaidclassdiagramgen.py +133 -0
  12. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/owlgen.py +16 -14
  13. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/plantumlgen.py +17 -10
  14. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/array.py +21 -61
  15. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/attribute.py.jinja +1 -1
  16. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shacl/shacl_ifabsent_processor.py +2 -1
  17. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shaclgen.py +16 -5
  18. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shexgen.py +1 -3
  19. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/summarygen.py +1 -3
  20. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/typescriptgen.py +3 -1
  21. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/yamlgen.py +1 -3
  22. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/rules.py +3 -1
  23. {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/logical_model_transformer.py +7 -5
  24. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/converter.py +17 -0
  25. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/deprecation.py +10 -0
  26. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/generator.py +11 -2
  27. linkml-1.8.7/linkml/utils/helpers.py +81 -0
  28. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/validation.py +2 -1
  29. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/__init__.py +2 -2
  30. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/jsonschema_validation_plugin.py +1 -0
  31. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/report.py +4 -1
  32. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/validator.py +4 -4
  33. {linkml-1.8.5 → linkml-1.8.7}/linkml/validators/jsonschemavalidator.py +10 -0
  34. {linkml-1.8.5 → linkml-1.8.7}/linkml/validators/sparqlvalidator.py +7 -0
  35. {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/example_runner.py +21 -4
  36. {linkml-1.8.5 → linkml-1.8.7}/pyproject.toml +12 -11
  37. {linkml-1.8.5 → linkml-1.8.7}/setup.py +5 -2
  38. linkml-1.8.5/linkml/utils/helpers.py +0 -16
  39. {linkml-1.8.5 → linkml-1.8.7}/LICENSE +0 -0
  40. {linkml-1.8.5 → linkml-1.8.7}/README.md +0 -0
  41. {linkml-1.8.5 → linkml-1.8.7}/linkml/__init__.py +0 -0
  42. {linkml-1.8.5 → linkml-1.8.7}/linkml/_version.py +0 -0
  43. {linkml-1.8.5 → linkml-1.8.7}/linkml/cli/__init__.py +0 -0
  44. {linkml-1.8.5 → linkml-1.8.7}/linkml/cli/__main__.py +0 -0
  45. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/PythonGenNotes.md +0 -0
  46. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/README.md +0 -0
  47. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/__init__.py +0 -0
  48. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/__init__.py +0 -0
  49. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/ifabsent_processor.py +0 -0
  50. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/naming.py +0 -0
  51. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/template.py +0 -0
  52. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/type_designators.py +0 -0
  53. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/csvgen.py +0 -0
  54. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/class.md.jinja2 +0 -0
  55. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/class_diagram.md.jinja2 +0 -0
  56. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/common_metadata.md.jinja2 +0 -0
  57. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/enum.md.jinja2 +0 -0
  58. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/index.md.jinja2 +0 -0
  59. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/index.tex.jinja2 +0 -0
  60. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/schema.md.jinja2 +0 -0
  61. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/slot.md.jinja2 +0 -0
  62. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/subset.md.jinja2 +0 -0
  63. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/type.md.jinja2 +0 -0
  64. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen.py +0 -0
  65. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/dotgen.py +0 -0
  66. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/excelgen.py +0 -0
  67. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/golanggen.py +0 -0
  68. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/golrgen.py +0 -0
  69. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/graphqlgen.py +0 -0
  70. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/javagen/example_template.java.jinja2 +0 -0
  71. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/javagen/java_record_template.jinja2 +0 -0
  72. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/javagen.py +0 -0
  73. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/legacy/__init__.py +0 -0
  74. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/markdowngen.py +0 -0
  75. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/namespacegen.py +0 -0
  76. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/oocodegen.py +0 -0
  77. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/prefixmapgen.py +0 -0
  78. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/projectgen.py +0 -0
  79. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/protogen.py +0 -0
  80. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/__init__.py +0 -0
  81. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/black.py +0 -0
  82. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/build.py +0 -0
  83. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/includes.py +0 -0
  84. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/pydanticgen.py +0 -0
  85. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/template.py +0 -0
  86. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -0
  87. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/class.py.jinja +0 -0
  88. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/conditional_import.py.jinja +0 -0
  89. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/enum.py.jinja +0 -0
  90. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/footer.py.jinja +0 -0
  91. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/imports.py.jinja +0 -0
  92. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/module.py.jinja +0 -0
  93. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/validator.py.jinja +0 -0
  94. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/python/__init__.py +0 -0
  95. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/python/python_ifabsent_processor.py +0 -0
  96. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pythongen.py +0 -0
  97. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/rdfgen.py +0 -0
  98. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shacl/__init__.py +0 -0
  99. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shacl/shacl_data_type.py +0 -0
  100. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sparqlgen.py +0 -0
  101. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqlalchemy/__init__.py +0 -0
  102. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +0 -0
  103. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +0 -0
  104. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqlalchemygen.py +0 -0
  105. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqltablegen.py +0 -0
  106. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sssomgen.py +0 -0
  107. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/string_template.md +0 -0
  108. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/terminusdbgen.py +0 -0
  109. {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/yumlgen.py +0 -0
  110. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/__init__.py +0 -0
  111. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/cli.py +0 -0
  112. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/datamodel/.linkmllint.yaml +0 -0
  113. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/datamodel/__init__.py +0 -0
  114. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/datamodel/config.py +0 -0
  115. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/datamodel/config.yaml +0 -0
  116. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/default.yaml +0 -0
  117. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/recommended.yaml +0 -0
  118. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/__init__.py +0 -0
  119. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/formatter.py +0 -0
  120. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/json_formatter.py +0 -0
  121. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/markdown_formatter.py +0 -0
  122. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/terminal_formatter.py +0 -0
  123. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/tsv_formatter.py +0 -0
  124. {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/linter.py +0 -0
  125. {linkml-1.8.5 → linkml-1.8.7}/linkml/reporting/__init__.py +0 -0
  126. {linkml-1.8.5 → linkml-1.8.7}/linkml/reporting/model.py +0 -0
  127. {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/__init__.py +0 -0
  128. {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/model_transformer.py +0 -0
  129. {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/relmodel_transformer.py +0 -0
  130. {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/schema_renamer.py +0 -0
  131. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/__init__.py +0 -0
  132. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/cli_utils.py +0 -0
  133. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/datautils.py +0 -0
  134. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/datavalidator.py +0 -0
  135. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/exceptions.py +0 -0
  136. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/execute_tutorial.py +0 -0
  137. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/logictools.py +0 -0
  138. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/mergeutils.py +0 -0
  139. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/rawloader.py +0 -0
  140. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/schema_builder.py +0 -0
  141. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/schema_fixer.py +0 -0
  142. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/schemaloader.py +0 -0
  143. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/schemasynopsis.py +0 -0
  144. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/sqlutils.py +0 -0
  145. {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/typereferences.py +0 -0
  146. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/cli.py +0 -0
  147. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/__init__.py +0 -0
  148. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/delimited_file_loader.py +0 -0
  149. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/json_loader.py +0 -0
  150. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/loader.py +0 -0
  151. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/passthrough_loader.py +0 -0
  152. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/yaml_loader.py +0 -0
  153. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/__init__.py +0 -0
  154. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/pydantic_validation_plugin.py +0 -0
  155. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/recommended_slots_plugin.py +0 -0
  156. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/shacl_validation_plugin.py +0 -0
  157. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/validation_plugin.py +0 -0
  158. {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/validation_context.py +0 -0
  159. {linkml-1.8.5 → linkml-1.8.7}/linkml/validators/__init__.py +0 -0
  160. {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/__init__.py +0 -0
  161. {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/datamodel/__init__.py +0 -0
  162. {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/datamodel/workspaces.py +0 -0
  163. {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/datamodel/workspaces.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml
3
- Version: 1.8.5
3
+ Version: 1.8.7
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
@@ -53,7 +53,7 @@ Requires-Dist: pyyaml
53
53
  Requires-Dist: rdflib (>=6.0.0)
54
54
  Requires-Dist: requests (>=2.22)
55
55
  Requires-Dist: sqlalchemy (>=1.4.31)
56
- Requires-Dist: typing-extensions (>=4.4.0) ; python_version < "3.9"
56
+ Requires-Dist: typing-extensions (>=4.6.0) ; python_version < "3.12"
57
57
  Requires-Dist: watchdog (>=0.9.0)
58
58
  Project-URL: Documentation, https://linkml.io/linkml/
59
59
  Project-URL: Repository, https://github.com/linkml/linkml
@@ -8,6 +8,7 @@ import click
8
8
 
9
9
  from linkml._version import __version__
10
10
  from linkml.generators.csvgen import cli as gen_csv
11
+ from linkml.generators.dbmlgen import cli as gen_dbml
11
12
  from linkml.generators.docgen import cli as gen_doc
12
13
  from linkml.generators.dotgen import cli as gen_graphviz
13
14
  from linkml.generators.erdiagramgen import cli as gen_erdiagram
@@ -125,6 +126,7 @@ generate.add_command(gen_project, name="project")
125
126
  generate.add_command(gen_excel, name="excel")
126
127
  generate.add_command(gen_sssom, name="sssom")
127
128
  generate.add_command(gen_linkml, name="linkml")
129
+ generate.add_command(gen_dbml, name="dbml")
128
130
 
129
131
  # Dev helpers
130
132
  dev.add_command(run_tutorial, name="tutorial")
@@ -5,7 +5,6 @@ Models for intermediate build results
5
5
  """
6
6
 
7
7
  import dataclasses
8
- from abc import abstractmethod
9
8
  from typing import Any, TypeVar
10
9
 
11
10
  try:
@@ -57,11 +56,11 @@ class BuildResult(BaseModel):
57
56
 
58
57
  model_config = ConfigDict(arbitrary_types_allowed=True)
59
58
 
60
- @abstractmethod
61
59
  def merge(self, other: T) -> T:
62
60
  """
63
61
  Build results should have some means of merging results of a like kind
64
62
  """
63
+ raise NotImplementedError("This build result doesn't know how to merge!")
65
64
 
66
65
 
67
66
  class SchemaResult(BuildResult):
@@ -13,7 +13,7 @@ from linkml_runtime.linkml_model.meta import (
13
13
  TypeDefinition,
14
14
  )
15
15
 
16
- from linkml.generators.common.build import ClassResult, RangeResult, SchemaResult, SlotResult, TypeResult
16
+ from linkml.generators.common.build import ClassResult, EnumResult, RangeResult, SchemaResult, SlotResult, TypeResult
17
17
  from linkml.generators.common.template import TemplateModel
18
18
 
19
19
  TSchema = TypeVar("TSchema", bound=SchemaResult)
@@ -21,7 +21,7 @@ TClass = TypeVar("TClass", bound=ClassResult)
21
21
  TSlot = TypeVar("TSlot", bound=SlotResult)
22
22
  TRange = TypeVar("TRange", bound=RangeResult)
23
23
  TType = TypeVar("TType", bound=TypeResult)
24
- TEnum = TypeVar("TEnum", bound=EnumDefinition)
24
+ TEnum = TypeVar("TEnum", bound=EnumResult)
25
25
  TTemplate = TypeVar("TTemplate", bound=TemplateModel)
26
26
 
27
27
 
@@ -93,6 +93,22 @@ class LifecycleMixin:
93
93
  def after_generate_slots(self, slot: Iterable[TSlot], sv: SchemaView) -> Iterable[TSlot]:
94
94
  return slot
95
95
 
96
+ def before_generate_class_slot(self, slot: SlotDefinition, cls: ClassDefinition, sv: SchemaView) -> SlotDefinition:
97
+ return slot
98
+
99
+ def after_generate_class_slot(self, slot: TSlot, cls: ClassDefinition, sv: SchemaView) -> TSlot:
100
+ return slot
101
+
102
+ def before_generate_class_slots(
103
+ self, slot: Iterable[SlotDefinition], cls: ClassDefinition, sv: SchemaView
104
+ ) -> Iterable[SlotDefinition]:
105
+ return slot
106
+
107
+ def after_generate_class_slots(
108
+ self, slot: Iterable[TSlot], cls: ClassDefinition, sv: SchemaView
109
+ ) -> Iterable[TSlot]:
110
+ return slot
111
+
96
112
  def before_generate_type(self, typ: TypeDefinition, sv: SchemaView) -> TypeDefinition:
97
113
  return typ
98
114
 
@@ -0,0 +1,173 @@
1
+ import logging
2
+
3
+ import click
4
+ from linkml_runtime.utils.formatutils import camelcase, underscore
5
+ from linkml_runtime.utils.schemaview import SchemaView
6
+
7
+ from linkml.utils.generator import Generator
8
+
9
+
10
+ def _map_range_to_dbml_type(range_name: str) -> str:
11
+ """
12
+ Map LinkML range types to DBML types.
13
+
14
+ :param range_name: LinkML range name
15
+ :return: Corresponding DBML type
16
+ """
17
+ type_mapping = {
18
+ "string": "varchar",
19
+ "integer": "int",
20
+ "float": "float",
21
+ "boolean": "boolean",
22
+ "date": "date",
23
+ "datetime": "datetime",
24
+ }
25
+ return type_mapping.get(range_name, "varchar") # Default to varchar
26
+
27
+
28
+ class DBMLGenerator(Generator):
29
+ """
30
+ A generator for converting a LinkML schema into DBML (Database Markup Language).
31
+ """
32
+
33
+ generatorname = "dbmlgen"
34
+ generatorversion = "0.2.0"
35
+ valid_formats = ["dbml"]
36
+
37
+ def __post_init__(self) -> None:
38
+ super().__post_init__()
39
+ self.logger = logging.getLogger(__name__)
40
+ self.schemaview = SchemaView(self.schema)
41
+
42
+ def serialize(self) -> str:
43
+ """
44
+ Generate DBML representation of the LinkML schema.
45
+
46
+ :return: DBML as a string
47
+ """
48
+ dbml_lines = [
49
+ "// DBML generated from LinkML schema\n",
50
+ f"Project {{\n name: '{self.schemaview.schema.name}'\n}}\n",
51
+ ]
52
+
53
+ for class_name, class_def in self.schemaview.all_classes().items():
54
+ dbml_lines.append(self._generate_table(class_name, class_def))
55
+
56
+ # Generate relationships if applicable
57
+ relationships = self._generate_relationships()
58
+ if relationships:
59
+ dbml_lines.append(relationships)
60
+
61
+ return "\n".join(dbml_lines)
62
+
63
+ def _generate_table(self, class_name: str, class_def) -> str:
64
+ """
65
+ Generate the DBML for a single class (table).
66
+
67
+ :param class_name: Name of the class
68
+ :param class_def: ClassDefinition object
69
+ :return: DBML representation of the class
70
+ """
71
+ dbml = [f"Table {camelcase(class_name)} {{"]
72
+
73
+ for slot_name in self.schemaview.class_induced_slots(class_name):
74
+ slot = self.schemaview.get_slot(slot_name.name)
75
+ dbml.append(self._generate_column(slot))
76
+
77
+ dbml.append("}\n")
78
+ return "\n".join(dbml)
79
+
80
+ def _generate_column(self, slot) -> str:
81
+ """
82
+ Generate the DBML for a single slot (column).
83
+
84
+ :param slot: SlotDefinition object
85
+ :return: DBML representation of the column
86
+ """
87
+ column_name = slot.name
88
+ data_type = _map_range_to_dbml_type(slot.range or "string")
89
+ constraints = []
90
+ constraints_str = ""
91
+
92
+ if slot.required:
93
+ constraints.append("not null")
94
+ if slot.identifier:
95
+ constraints.append("primary key")
96
+
97
+ if constraints:
98
+ constraints_str = f"{', '.join(constraints)}"
99
+ constraints_str = "[" + constraints_str + "]"
100
+
101
+ return f" {underscore(column_name)} {data_type} {constraints_str}".strip()
102
+
103
+ def _generate_relationships(self) -> str:
104
+ """
105
+ Generate DBML relationships based on slot ranges referencing other classes.
106
+
107
+ :return: DBML representation of relationships
108
+ """
109
+ relationships = []
110
+ for class_name, class_def in self.schemaview.all_classes().items():
111
+ for slot_name in self.schemaview.class_induced_slots(class_name):
112
+ slot = self.schemaview.get_slot(slot_name.name)
113
+
114
+ # Check if the slot references another class
115
+ if slot.range in self.schemaview.all_classes():
116
+
117
+ # Find the identifier slot of the referenced class
118
+ identifier_slot_name = next(
119
+ (
120
+ slot_name.name
121
+ for slot_name in self.schemaview.class_induced_slots(slot.range)
122
+ if self.schemaview.get_slot(slot_name.name).identifier
123
+ ),
124
+ None,
125
+ )
126
+
127
+ if identifier_slot_name is None:
128
+ raise ValueError(f"Referenced class '{slot.range}' does not have an identifier slot.")
129
+
130
+ # Generate the DBML relationship
131
+ relationships.append(
132
+ f"Ref: {camelcase(class_name)}.{underscore(slot.name)} > "
133
+ f"{camelcase(slot.range)}.{underscore(identifier_slot_name)}"
134
+ )
135
+ return "\n".join(relationships)
136
+
137
+
138
+ # CLI Definition
139
+ @click.command()
140
+ @click.option(
141
+ "--schema",
142
+ "-s",
143
+ required=True,
144
+ type=click.Path(exists=True, dir_okay=False, file_okay=True),
145
+ help="Path to the LinkML schema YAML file",
146
+ )
147
+ @click.option(
148
+ "--output",
149
+ "-o",
150
+ required=False,
151
+ type=click.Path(dir_okay=False, writable=True),
152
+ help="Path to save the generated DBML file. If not specified, DBML will be printed to stdout.",
153
+ )
154
+ def cli(schema, output):
155
+ """
156
+ CLI for LinkML to DBML generator.
157
+ """
158
+ generator = DBMLGenerator(schema)
159
+
160
+ # Generate the DBML
161
+ dbml_output = generator.serialize()
162
+
163
+ # Save to file or print to stdout
164
+ if output:
165
+ with open(output, "w", encoding="utf-8") as f:
166
+ f.write(dbml_output)
167
+ click.echo(f"DBML has been saved to {output}")
168
+ else:
169
+ click.echo(dbml_output)
170
+
171
+
172
+ if __name__ == "__main__":
173
+ cli()
@@ -308,6 +308,7 @@ class ERDiagramGenerator(Generator):
308
308
  default=False,
309
309
  help="If True, follow references even if not inlined",
310
310
  )
311
+ @click.option("--format", "-f", default="markdown", type=click.Choice(ERDiagramGenerator.valid_formats))
311
312
  @click.option("--max-hops", default=None, type=click.INT, help="Maximum number of hops")
312
313
  @click.option("--classes", "-c", multiple=True, help="List of classes to serialize")
313
314
  @click.option("--include-upstream", is_flag=True, help="Include upstream classes")
@@ -146,7 +146,13 @@ class ContextGenerator(Generator):
146
146
  slot_def = {}
147
147
  if not slot.usage_slot_name:
148
148
  any_of_ranges = [any_of_el.range for any_of_el in slot.any_of]
149
- if slot.range in self.schema.classes or any(rng in self.schema.classes for rng in any_of_ranges):
149
+ if slot.range in self.schema.classes:
150
+ range_class_uri = self.schema.classes[slot.range].class_uri
151
+ if range_class_uri and slot.inlined:
152
+ slot_def["@type"] = range_class_uri
153
+ else:
154
+ slot_def["@type"] = "@id"
155
+ elif any(rng in self.schema.classes for rng in any_of_ranges):
150
156
  slot_def["@type"] = "@id"
151
157
  elif slot.range in self.schema.enums:
152
158
  slot_def["@context"] = ENUM_CONTEXT
@@ -1,6 +1,4 @@
1
- """ Generate JSONld
2
-
3
- """
1
+ """Generate JSONld from a LinkML schema."""
4
2
 
5
3
  import os
6
4
  from copy import deepcopy
@@ -21,8 +21,11 @@ from linkml_runtime.linkml_model.meta import (
21
21
  from linkml_runtime.utils.formatutils import be, camelcase, underscore
22
22
 
23
23
  from linkml._version import __version__
24
+ from linkml.generators.common import build
25
+ from linkml.generators.common.lifecycle import LifecycleMixin
24
26
  from linkml.generators.common.type_designators import get_type_designator_value
25
27
  from linkml.utils.generator import Generator, shared_arguments
28
+ from linkml.utils.helpers import get_range_associated_slots
26
29
 
27
30
  logger = logging.getLogger(__name__)
28
31
 
@@ -177,8 +180,32 @@ class JsonSchema(dict):
177
180
  return JsonSchema(schema)
178
181
 
179
182
 
183
+ class SchemaResult(build.SchemaResult):
184
+ """Top-level result of building a json schema"""
185
+
186
+ schema_: JsonSchema
187
+
188
+
189
+ class EnumResult(build.EnumResult):
190
+ """A single built enum"""
191
+
192
+ schema_: JsonSchema
193
+
194
+
195
+ class ClassResult(build.ClassResult):
196
+ """A single built class"""
197
+
198
+ schema_: JsonSchema
199
+
200
+
201
+ class SlotResult(build.SlotResult):
202
+ """A slot within the context of a class"""
203
+
204
+ schema_: JsonSchema
205
+
206
+
180
207
  @dataclass
181
- class JsonSchemaGenerator(Generator):
208
+ class JsonSchemaGenerator(Generator, LifecycleMixin):
182
209
  """
183
210
  Generates JSONSchema documents from a LinkML SchemaDefinition
184
211
 
@@ -187,6 +214,21 @@ class JsonSchemaGenerator(Generator):
187
214
  - Composition not yet implemented
188
215
  - Enumerations treated as strings
189
216
  - Foreign key references are treated as semantics-free strings
217
+
218
+ This generator implements the following :class:`.LifecycleMixin` methods:
219
+
220
+ * :meth:`.LifecycleMixin.before_generate_schema`
221
+ * :meth:`.LifecycleMixin.after_generate_schema`
222
+ * :meth:`.LifecycleMixin.before_generate_classes`
223
+ * :meth:`.LifecycleMixin.before_generate_enums`
224
+ * :meth:`.LifecycleMixin.before_generate_class_slots`
225
+ * :meth:`.LifecycleMixin.before_generate_class`
226
+ * :meth:`.LifecycleMixin.after_generate_class`
227
+ * :meth:`.LifecycleMixin.before_generate_class_slot`
228
+ * :meth:`.LifecycleMixin.after_generate_class_slot`
229
+ * :meth:`.LifecycleMixin.before_generate_enum`
230
+ * :meth:`.LifecycleMixin.after_generate_enum`
231
+
190
232
  """
191
233
 
192
234
  # ClassVars
@@ -195,6 +237,7 @@ class JsonSchemaGenerator(Generator):
195
237
  valid_formats = ["json"]
196
238
  uses_schemaloader = False
197
239
  file_extension = "schema.json"
240
+ materialize_patterns: bool = False
198
241
 
199
242
  # @deprecated("Use top_class")
200
243
  topClass: Optional[str] = None
@@ -233,7 +276,7 @@ class JsonSchemaGenerator(Generator):
233
276
  if self.schemaview.get_class(self.top_class) is None:
234
277
  logger.warning(f"No class in schema named {self.top_class}")
235
278
 
236
- def start_schema(self, inline: bool = False) -> JsonSchema:
279
+ def start_schema(self, inline: bool = False):
237
280
  self.inline = inline
238
281
 
239
282
  self.top_level_schema = JsonSchema(
@@ -249,6 +292,8 @@ class JsonSchemaGenerator(Generator):
249
292
  )
250
293
 
251
294
  def handle_class(self, cls: ClassDefinition) -> None:
295
+ cls = self.before_generate_class(cls, self.schemaview)
296
+
252
297
  if cls.mixin or cls.abstract:
253
298
  return
254
299
 
@@ -268,7 +313,10 @@ class JsonSchemaGenerator(Generator):
268
313
  if self.title_from == "title" and cls.title:
269
314
  class_subschema["title"] = cls.title
270
315
 
271
- for slot_definition in self.schemaview.class_induced_slots(cls.name):
316
+ class_slots = self.before_generate_class_slots(
317
+ self.schemaview.class_induced_slots(cls.name), cls, self.schemaview
318
+ )
319
+ for slot_definition in class_slots:
272
320
  self.handle_class_slot(subschema=class_subschema, cls=cls, slot=slot_definition)
273
321
 
274
322
  rule_subschemas = []
@@ -318,6 +366,10 @@ class JsonSchemaGenerator(Generator):
318
366
  class_subschema["allOf"] = []
319
367
  class_subschema["allOf"].extend(rule_subschemas)
320
368
 
369
+ class_subschema = self.after_generate_class(
370
+ ClassResult.model_construct(schema_=class_subschema, source=cls), self.schemaview
371
+ ).schema_
372
+
321
373
  self.top_level_schema.add_def(cls.name, class_subschema)
322
374
 
323
375
  if (self.top_class is not None and camelcase(self.top_class) == camelcase(cls.name)) or (
@@ -375,6 +427,7 @@ class JsonSchemaGenerator(Generator):
375
427
  def handle_enum(self, enum: EnumDefinition) -> None:
376
428
  # TODO: this only works with explicitly permitted values. It will need to be extended to
377
429
  # support other pv_formula
430
+ enum = self.before_generate_enum(enum, self.schemaview)
378
431
 
379
432
  def extract_permissible_text(pv):
380
433
  if isinstance(pv, str):
@@ -398,6 +451,10 @@ class JsonSchemaGenerator(Generator):
398
451
 
399
452
  if permissible_values_texts:
400
453
  enum_schema["enum"] = permissible_values_texts
454
+
455
+ enum_schema = self.after_generate_enum(
456
+ EnumResult.model_construct(schema_=enum_schema, source=enum), self.schemaview
457
+ ).schema_
401
458
  self.top_level_schema.add_def(enum.name, enum_schema)
402
459
 
403
460
  def get_type_info_for_slot_subschema(
@@ -490,7 +547,7 @@ class JsonSchemaGenerator(Generator):
490
547
  range_id_slot,
491
548
  range_simple_dict_value_slot,
492
549
  range_required_slots,
493
- ) = self._get_range_associated_slots(slot)
550
+ ) = get_range_associated_slots(self.schemaview, slot.range)
494
551
  # if the range class has an ID and the slot is not inlined as a list, then we need to consider
495
552
  # various inlined as dict formats
496
553
  if range_id_slot is not None and not slot.inlined_as_list:
@@ -596,6 +653,7 @@ class JsonSchemaGenerator(Generator):
596
653
  return prop
597
654
 
598
655
  def handle_class_slot(self, subschema: JsonSchema, cls: ClassDefinition, slot: SlotDefinition) -> None:
656
+ slot = self.before_generate_class_slot(slot, cls, self.schemaview)
599
657
  class_id_slot = self.schemaview.get_identifier_slot(cls.name, use_key=True)
600
658
  value_required = (
601
659
  slot.required or slot == class_id_slot or slot.value_presence == PresenceEnum(PresenceEnum.PRESENT)
@@ -604,6 +662,9 @@ class JsonSchemaGenerator(Generator):
604
662
 
605
663
  aliased_slot_name = self.aliased_slot_name(slot)
606
664
  prop = self.get_subschema_for_slot(slot, include_null=self.include_null)
665
+ prop = self.after_generate_class_slot(
666
+ SlotResult.model_construct(schema_=prop, source=slot), cls, self.schemaview
667
+ ).schema_
607
668
  subschema.add_property(
608
669
  aliased_slot_name, prop, value_required=value_required, value_disallowed=value_disallowed
609
670
  )
@@ -613,64 +674,28 @@ class JsonSchemaGenerator(Generator):
613
674
  prop["enum"] = [type_value]
614
675
 
615
676
  def generate(self) -> JsonSchema:
677
+ self.schema = self.before_generate_schema(self.schema, self.schemaview)
616
678
  self.start_schema()
617
- for enum_definition in self.schemaview.all_enums().values():
679
+
680
+ all_enums = self.before_generate_enums(self.schemaview.all_enums().values(), self.schemaview)
681
+ for enum_definition in all_enums:
618
682
  self.handle_enum(enum_definition)
619
683
 
620
- for class_definition in self.schemaview.all_classes().values():
684
+ all_classes = self.before_generate_classes(self.schemaview.all_classes().values(), self.schemaview)
685
+ for class_definition in all_classes:
621
686
  self.handle_class(class_definition)
622
687
 
688
+ self.top_level_schema = self.after_generate_schema(
689
+ SchemaResult.model_construct(schema_=self.top_level_schema, source=self.schema), self.schemaview
690
+ ).schema_
623
691
  return self.top_level_schema
624
692
 
625
693
  def serialize(self, **kwargs) -> str:
626
- return self.generate().to_json(sort_keys=True, indent=self.indent if self.indent > 0 else None)
694
+ if self.materialize_patterns:
695
+ logger.info("Materializing patterns in the schema before serialization")
696
+ self.schemaview.materialize_patterns()
627
697
 
628
- def _get_range_associated_slots(
629
- self, slot: SlotDefinition
630
- ) -> Tuple[Union[SlotDefinition, None], Union[SlotDefinition, None], Union[List[SlotDefinition], None]]:
631
- range_class = self.schemaview.get_class(slot.range)
632
- if range_class is None:
633
- return None, None, None
634
-
635
- range_class_id_slot = self.schemaview.get_identifier_slot(range_class.name, use_key=True)
636
- if range_class_id_slot is None:
637
- return None, None, None
638
-
639
- non_id_slots = [
640
- s for s in self.schemaview.class_induced_slots(range_class.name) if s.name != range_class_id_slot.name
641
- ]
642
- non_id_required_slots = [s for s in non_id_slots if s.required]
643
-
644
- # Some lists of objects can be serialized as SimpleDicts.
645
- # A SimpleDict is serialized as simple key-value pairs where the value is atomic.
646
- # The key must be declared as a key, and the value must satisfy one of the following conditions:
647
- # 1. The value slot is the only other slot in the object other than the key
648
- # 2. The value slot is explicitly annotated as a simple_dict_value
649
- # 3. The value slot is the only non-key that is required
650
- # See also: https://github.com/linkml/linkml/issues/1250
651
- range_simple_dict_value_slot = None
652
- if len(non_id_slots) == 1:
653
- range_simple_dict_value_slot = non_id_slots[0]
654
- elif len(non_id_slots) > 1:
655
- candidate_non_id_slots = []
656
- for non_id_slot in non_id_slots:
657
- if isinstance(non_id_slot.annotations, dict):
658
- is_simple_dict_value = non_id_slot.annotations.get("simple_dict_value", False)
659
- else:
660
- is_simple_dict_value = getattr(non_id_slot.annotations, "simple_dict_value", False)
661
- if is_simple_dict_value:
662
- candidate_non_id_slots.append(non_id_slot)
663
- if len(candidate_non_id_slots) == 1:
664
- range_simple_dict_value_slot = candidate_non_id_slots[0]
665
- else:
666
- candidate_non_id_slots = []
667
- for non_id_slot in non_id_slots:
668
- if non_id_slot.required:
669
- candidate_non_id_slots.append(non_id_slot)
670
- if len(candidate_non_id_slots) == 1:
671
- range_simple_dict_value_slot = candidate_non_id_slots[0]
672
-
673
- return range_class_id_slot, range_simple_dict_value_slot, non_id_required_slots
698
+ return self.generate().to_json(sort_keys=True, indent=self.indent if self.indent > 0 else None)
674
699
 
675
700
 
676
701
  @shared_arguments(JsonSchemaGenerator)
@@ -732,6 +757,12 @@ Include LinkML Schema outside of imports mechanism. Helpful in including deprec
732
757
  YAML, and including it when necessary but not by default (e.g. in documentation or for backwards compatibility)
733
758
  """,
734
759
  )
760
+ @click.option(
761
+ "--materialize-patterns/--no-materialize-patterns",
762
+ default=True, # Default set to True
763
+ show_default=True,
764
+ help="If set, patterns will be materialized in the generated JSON Schema.",
765
+ )
735
766
  @click.version_option(__version__, "-V", "--version")
736
767
  def cli(yamlfile, **kwargs):
737
768
  """Generate JSON Schema representation of a LinkML model"""
@@ -78,6 +78,12 @@ class LinkmlGenerator(Generator):
78
78
 
79
79
 
80
80
  @shared_arguments(LinkmlGenerator)
81
+ @click.option(
82
+ "--materialize/--no-materialize",
83
+ default=True,
84
+ show_default=True,
85
+ help="Materialize both, induced slots as attributes and structured patterns as patterns",
86
+ )
81
87
  @click.option(
82
88
  "--materialize-attributes/--no-materialize-attributes",
83
89
  default=True,
@@ -86,7 +92,7 @@ class LinkmlGenerator(Generator):
86
92
  )
87
93
  @click.option(
88
94
  "--materialize-patterns/--no-materialize-patterns",
89
- default=False,
95
+ default=True,
90
96
  show_default=True,
91
97
  help="Materialize structured patterns as patterns",
92
98
  )
@@ -100,11 +106,17 @@ class LinkmlGenerator(Generator):
100
106
  @click.command(name="linkml")
101
107
  def cli(
102
108
  yamlfile,
109
+ materialize: bool,
103
110
  materialize_attributes: bool,
104
111
  materialize_patterns: bool,
105
112
  output: FILE_TYPE = None,
106
113
  **kwargs,
107
114
  ):
115
+ # You can use the `--materialize` / `--no-materialize` for control
116
+ # over both attribute and pattern materialization.
117
+ materialize_attributes = bool(materialize)
118
+ materialize_patterns = bool(materialize)
119
+
108
120
  gen = LinkmlGenerator(
109
121
  yamlfile,
110
122
  materialize_attributes=materialize_attributes,