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.
- {linkml-1.8.5 → linkml-1.8.7}/PKG-INFO +2 -2
- {linkml-1.8.5 → linkml-1.8.7}/linkml/cli/main.py +2 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/build.py +1 -2
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/lifecycle.py +18 -2
- linkml-1.8.7/linkml/generators/dbmlgen.py +173 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/erdiagramgen.py +1 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/jsonldcontextgen.py +7 -1
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/jsonldgen.py +1 -3
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/jsonschemagen.py +84 -53
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/linkmlgen.py +13 -1
- linkml-1.8.7/linkml/generators/mermaidclassdiagramgen.py +133 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/owlgen.py +16 -14
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/plantumlgen.py +17 -10
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/array.py +21 -61
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/attribute.py.jinja +1 -1
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shacl/shacl_ifabsent_processor.py +2 -1
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shaclgen.py +16 -5
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shexgen.py +1 -3
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/summarygen.py +1 -3
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/typescriptgen.py +3 -1
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/yamlgen.py +1 -3
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/rules.py +3 -1
- {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/logical_model_transformer.py +7 -5
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/converter.py +17 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/deprecation.py +10 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/generator.py +11 -2
- linkml-1.8.7/linkml/utils/helpers.py +81 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/validation.py +2 -1
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/__init__.py +2 -2
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/jsonschema_validation_plugin.py +1 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/report.py +4 -1
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/validator.py +4 -4
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validators/jsonschemavalidator.py +10 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validators/sparqlvalidator.py +7 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/example_runner.py +21 -4
- {linkml-1.8.5 → linkml-1.8.7}/pyproject.toml +12 -11
- {linkml-1.8.5 → linkml-1.8.7}/setup.py +5 -2
- linkml-1.8.5/linkml/utils/helpers.py +0 -16
- {linkml-1.8.5 → linkml-1.8.7}/LICENSE +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/README.md +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/_version.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/cli/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/cli/__main__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/PythonGenNotes.md +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/README.md +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/ifabsent_processor.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/naming.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/template.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/common/type_designators.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/csvgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/class.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/class_diagram.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/common_metadata.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/enum.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/index.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/index.tex.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/schema.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/slot.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/subset.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen/type.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/docgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/dotgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/excelgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/golanggen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/golrgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/graphqlgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/javagen/example_template.java.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/javagen/java_record_template.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/javagen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/legacy/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/markdowngen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/namespacegen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/oocodegen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/prefixmapgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/projectgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/protogen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/black.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/build.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/includes.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/pydanticgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/template.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/class.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/conditional_import.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/enum.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/footer.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/imports.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/module.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pydanticgen/templates/validator.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/python/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/python/python_ifabsent_processor.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/pythongen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/rdfgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shacl/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/shacl/shacl_data_type.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sparqlgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqlalchemy/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqlalchemygen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sqltablegen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/sssomgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/string_template.md +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/terminusdbgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/generators/yumlgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/cli.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/datamodel/.linkmllint.yaml +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/datamodel/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/datamodel/config.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/datamodel/config.yaml +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/default.yaml +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/config/recommended.yaml +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/json_formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/markdown_formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/terminal_formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/formatters/tsv_formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/linter/linter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/reporting/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/reporting/model.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/model_transformer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/relmodel_transformer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/transformers/schema_renamer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/cli_utils.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/datautils.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/datavalidator.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/exceptions.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/execute_tutorial.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/logictools.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/mergeutils.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/rawloader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/schema_builder.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/schema_fixer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/schemaloader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/schemasynopsis.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/sqlutils.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/utils/typereferences.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/cli.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/delimited_file_loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/json_loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/passthrough_loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/loaders/yaml_loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/pydantic_validation_plugin.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/recommended_slots_plugin.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/shacl_validation_plugin.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/plugins/validation_plugin.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validator/validation_context.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/validators/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/datamodel/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.7}/linkml/workspaces/datamodel/workspaces.py +0 -0
- {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.
|
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.
|
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=
|
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
|
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
|
@@ -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)
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
694
|
+
if self.materialize_patterns:
|
695
|
+
logger.info("Materializing patterns in the schema before serialization")
|
696
|
+
self.schemaview.materialize_patterns()
|
627
697
|
|
628
|
-
|
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=
|
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,
|