linkml 1.8.5__tar.gz → 1.8.6__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.6}/PKG-INFO +2 -2
- {linkml-1.8.5 → linkml-1.8.6}/linkml/cli/main.py +2 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/common/build.py +1 -2
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/common/lifecycle.py +18 -2
- linkml-1.8.6/linkml/generators/dbmlgen.py +173 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/erdiagramgen.py +1 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/jsonldcontextgen.py +7 -1
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/jsonschemagen.py +73 -53
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/linkmlgen.py +13 -1
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/owlgen.py +11 -1
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/plantumlgen.py +17 -10
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/array.py +21 -61
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/attribute.py.jinja +1 -1
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/shaclgen.py +16 -5
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/typescriptgen.py +3 -1
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/rules.py +3 -1
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/converter.py +17 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/deprecation.py +10 -0
- linkml-1.8.6/linkml/utils/helpers.py +81 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/validation.py +2 -1
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/__init__.py +2 -2
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/plugins/jsonschema_validation_plugin.py +1 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/report.py +4 -1
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/validator.py +4 -4
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validators/jsonschemavalidator.py +10 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validators/sparqlvalidator.py +7 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/workspaces/example_runner.py +20 -1
- {linkml-1.8.5 → linkml-1.8.6}/pyproject.toml +9 -10
- {linkml-1.8.5 → linkml-1.8.6}/setup.py +3 -2
- linkml-1.8.5/linkml/utils/helpers.py +0 -16
- {linkml-1.8.5 → linkml-1.8.6}/LICENSE +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/README.md +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/_version.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/cli/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/cli/__main__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/PythonGenNotes.md +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/README.md +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/common/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/common/ifabsent_processor.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/common/naming.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/common/template.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/common/type_designators.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/csvgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/class.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/class_diagram.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/common_metadata.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/enum.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/index.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/index.tex.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/schema.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/slot.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/subset.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen/type.md.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/docgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/dotgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/excelgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/golanggen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/golrgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/graphqlgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/javagen/example_template.java.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/javagen/java_record_template.jinja2 +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/javagen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/jsonldgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/legacy/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/markdowngen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/namespacegen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/oocodegen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/prefixmapgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/projectgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/protogen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/black.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/build.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/includes.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/pydanticgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/template.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/base_model.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/class.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/conditional_import.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/enum.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/footer.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/imports.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/module.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pydanticgen/templates/validator.py.jinja +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/python/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/python/python_ifabsent_processor.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/pythongen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/rdfgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/shacl/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/shacl/shacl_data_type.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/shacl/shacl_ifabsent_processor.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/shexgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/sparqlgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/sqlalchemy/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/sqlalchemygen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/sqltablegen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/sssomgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/string_template.md +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/summarygen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/terminusdbgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/yamlgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/generators/yumlgen.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/cli.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/config/datamodel/.linkmllint.yaml +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/config/datamodel/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/config/datamodel/config.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/config/datamodel/config.yaml +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/config/default.yaml +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/config/recommended.yaml +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/formatters/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/formatters/formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/formatters/json_formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/formatters/markdown_formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/formatters/terminal_formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/formatters/tsv_formatter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/linter/linter.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/reporting/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/reporting/model.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/transformers/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/transformers/logical_model_transformer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/transformers/model_transformer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/transformers/relmodel_transformer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/transformers/schema_renamer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/cli_utils.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/datautils.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/datavalidator.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/exceptions.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/execute_tutorial.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/generator.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/logictools.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/mergeutils.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/rawloader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/schema_builder.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/schema_fixer.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/schemaloader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/schemasynopsis.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/sqlutils.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/utils/typereferences.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/cli.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/loaders/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/loaders/delimited_file_loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/loaders/json_loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/loaders/loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/loaders/passthrough_loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/loaders/yaml_loader.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/plugins/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/plugins/pydantic_validation_plugin.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/plugins/recommended_slots_plugin.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/plugins/shacl_validation_plugin.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/plugins/validation_plugin.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validator/validation_context.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/validators/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/workspaces/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/workspaces/datamodel/__init__.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/linkml/workspaces/datamodel/workspaces.py +0 -0
- {linkml-1.8.5 → linkml-1.8.6}/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.6
|
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
|
@@ -233,7 +275,7 @@ class JsonSchemaGenerator(Generator):
|
|
233
275
|
if self.schemaview.get_class(self.top_class) is None:
|
234
276
|
logger.warning(f"No class in schema named {self.top_class}")
|
235
277
|
|
236
|
-
def start_schema(self, inline: bool = False)
|
278
|
+
def start_schema(self, inline: bool = False):
|
237
279
|
self.inline = inline
|
238
280
|
|
239
281
|
self.top_level_schema = JsonSchema(
|
@@ -249,6 +291,8 @@ class JsonSchemaGenerator(Generator):
|
|
249
291
|
)
|
250
292
|
|
251
293
|
def handle_class(self, cls: ClassDefinition) -> None:
|
294
|
+
cls = self.before_generate_class(cls, self.schemaview)
|
295
|
+
|
252
296
|
if cls.mixin or cls.abstract:
|
253
297
|
return
|
254
298
|
|
@@ -268,7 +312,10 @@ class JsonSchemaGenerator(Generator):
|
|
268
312
|
if self.title_from == "title" and cls.title:
|
269
313
|
class_subschema["title"] = cls.title
|
270
314
|
|
271
|
-
|
315
|
+
class_slots = self.before_generate_class_slots(
|
316
|
+
self.schemaview.class_induced_slots(cls.name), cls, self.schemaview
|
317
|
+
)
|
318
|
+
for slot_definition in class_slots:
|
272
319
|
self.handle_class_slot(subschema=class_subschema, cls=cls, slot=slot_definition)
|
273
320
|
|
274
321
|
rule_subschemas = []
|
@@ -318,6 +365,10 @@ class JsonSchemaGenerator(Generator):
|
|
318
365
|
class_subschema["allOf"] = []
|
319
366
|
class_subschema["allOf"].extend(rule_subschemas)
|
320
367
|
|
368
|
+
class_subschema = self.after_generate_class(
|
369
|
+
ClassResult.model_construct(schema_=class_subschema, source=cls), self.schemaview
|
370
|
+
).schema_
|
371
|
+
|
321
372
|
self.top_level_schema.add_def(cls.name, class_subschema)
|
322
373
|
|
323
374
|
if (self.top_class is not None and camelcase(self.top_class) == camelcase(cls.name)) or (
|
@@ -375,6 +426,7 @@ class JsonSchemaGenerator(Generator):
|
|
375
426
|
def handle_enum(self, enum: EnumDefinition) -> None:
|
376
427
|
# TODO: this only works with explicitly permitted values. It will need to be extended to
|
377
428
|
# support other pv_formula
|
429
|
+
enum = self.before_generate_enum(enum, self.schemaview)
|
378
430
|
|
379
431
|
def extract_permissible_text(pv):
|
380
432
|
if isinstance(pv, str):
|
@@ -398,6 +450,10 @@ class JsonSchemaGenerator(Generator):
|
|
398
450
|
|
399
451
|
if permissible_values_texts:
|
400
452
|
enum_schema["enum"] = permissible_values_texts
|
453
|
+
|
454
|
+
enum_schema = self.after_generate_enum(
|
455
|
+
EnumResult.model_construct(schema_=enum_schema, source=enum), self.schemaview
|
456
|
+
).schema_
|
401
457
|
self.top_level_schema.add_def(enum.name, enum_schema)
|
402
458
|
|
403
459
|
def get_type_info_for_slot_subschema(
|
@@ -490,7 +546,7 @@ class JsonSchemaGenerator(Generator):
|
|
490
546
|
range_id_slot,
|
491
547
|
range_simple_dict_value_slot,
|
492
548
|
range_required_slots,
|
493
|
-
) = self.
|
549
|
+
) = get_range_associated_slots(self.schemaview, slot.range)
|
494
550
|
# if the range class has an ID and the slot is not inlined as a list, then we need to consider
|
495
551
|
# various inlined as dict formats
|
496
552
|
if range_id_slot is not None and not slot.inlined_as_list:
|
@@ -596,6 +652,7 @@ class JsonSchemaGenerator(Generator):
|
|
596
652
|
return prop
|
597
653
|
|
598
654
|
def handle_class_slot(self, subschema: JsonSchema, cls: ClassDefinition, slot: SlotDefinition) -> None:
|
655
|
+
slot = self.before_generate_class_slot(slot, cls, self.schemaview)
|
599
656
|
class_id_slot = self.schemaview.get_identifier_slot(cls.name, use_key=True)
|
600
657
|
value_required = (
|
601
658
|
slot.required or slot == class_id_slot or slot.value_presence == PresenceEnum(PresenceEnum.PRESENT)
|
@@ -604,6 +661,9 @@ class JsonSchemaGenerator(Generator):
|
|
604
661
|
|
605
662
|
aliased_slot_name = self.aliased_slot_name(slot)
|
606
663
|
prop = self.get_subschema_for_slot(slot, include_null=self.include_null)
|
664
|
+
prop = self.after_generate_class_slot(
|
665
|
+
SlotResult.model_construct(schema_=prop, source=slot), cls, self.schemaview
|
666
|
+
).schema_
|
607
667
|
subschema.add_property(
|
608
668
|
aliased_slot_name, prop, value_required=value_required, value_disallowed=value_disallowed
|
609
669
|
)
|
@@ -613,65 +673,25 @@ class JsonSchemaGenerator(Generator):
|
|
613
673
|
prop["enum"] = [type_value]
|
614
674
|
|
615
675
|
def generate(self) -> JsonSchema:
|
676
|
+
self.schema = self.before_generate_schema(self.schema, self.schemaview)
|
616
677
|
self.start_schema()
|
617
|
-
|
678
|
+
|
679
|
+
all_enums = self.before_generate_enums(self.schemaview.all_enums().values(), self.schemaview)
|
680
|
+
for enum_definition in all_enums:
|
618
681
|
self.handle_enum(enum_definition)
|
619
682
|
|
620
|
-
|
683
|
+
all_classes = self.before_generate_classes(self.schemaview.all_classes().values(), self.schemaview)
|
684
|
+
for class_definition in all_classes:
|
621
685
|
self.handle_class(class_definition)
|
622
686
|
|
687
|
+
self.top_level_schema = self.after_generate_schema(
|
688
|
+
SchemaResult.model_construct(schema_=self.top_level_schema, source=self.schema), self.schemaview
|
689
|
+
).schema_
|
623
690
|
return self.top_level_schema
|
624
691
|
|
625
692
|
def serialize(self, **kwargs) -> str:
|
626
693
|
return self.generate().to_json(sort_keys=True, indent=self.indent if self.indent > 0 else None)
|
627
694
|
|
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
|
674
|
-
|
675
695
|
|
676
696
|
@shared_arguments(JsonSchemaGenerator)
|
677
697
|
@click.command(name="json-schema")
|
@@ -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,
|
@@ -170,6 +170,9 @@ class OwlSchemaGenerator(Generator):
|
|
170
170
|
default_factory=lambda: package_schemaview("linkml_runtime.linkml_model.meta")
|
171
171
|
)
|
172
172
|
|
173
|
+
enum_iri_separator: str = "#"
|
174
|
+
"""Separator for enum IRI. Can be overridden for example if your namespace IRI already contains a #"""
|
175
|
+
|
173
176
|
def as_graph(self) -> Graph:
|
174
177
|
"""
|
175
178
|
Generate an rdflib Graph from the LinkML schema.
|
@@ -1254,7 +1257,7 @@ class OwlSchemaGenerator(Generator):
|
|
1254
1257
|
if pv.meaning:
|
1255
1258
|
return URIRef(self.schemaview.expand_curie(pv.meaning))
|
1256
1259
|
else:
|
1257
|
-
return URIRef(enum_uri +
|
1260
|
+
return URIRef(enum_uri + self.enum_iri_separator + pv.text.replace(" ", "+"))
|
1258
1261
|
|
1259
1262
|
def slot_owl_type(self, slot: SlotDefinition) -> URIRef:
|
1260
1263
|
sv = self.schemaview
|
@@ -1353,6 +1356,13 @@ class OwlSchemaGenerator(Generator):
|
|
1353
1356
|
show_default=True,
|
1354
1357
|
help="Default OWL type for permissible values",
|
1355
1358
|
)
|
1359
|
+
@click.option(
|
1360
|
+
"--enum-iri-separator",
|
1361
|
+
default="#",
|
1362
|
+
is_flag=False,
|
1363
|
+
show_default=True,
|
1364
|
+
help="IRI separator for enums.",
|
1365
|
+
)
|
1356
1366
|
@click.version_option(__version__, "-V", "--version")
|
1357
1367
|
def cli(yamlfile, metadata_profile: str, **kwargs):
|
1358
1368
|
"""Generate an OWL representation of a LinkML model
|
@@ -49,14 +49,13 @@ class PlantumlGenerator(Generator):
|
|
49
49
|
classes: Set[ClassDefinitionName] = None
|
50
50
|
directory: Optional[str] = None
|
51
51
|
kroki_server: Optional[str] = "https://kroki.io"
|
52
|
-
load_image: bool = True
|
53
52
|
tooltips_flag: bool = False
|
53
|
+
dry_run: bool = False
|
54
54
|
|
55
55
|
def visit_schema(
|
56
56
|
self,
|
57
57
|
classes: Set[ClassDefinitionName] = None,
|
58
58
|
directory: Optional[str] = None,
|
59
|
-
load_image: bool = True,
|
60
59
|
**_,
|
61
60
|
) -> Optional[str]:
|
62
61
|
if directory:
|
@@ -96,20 +95,21 @@ class PlantumlGenerator(Generator):
|
|
96
95
|
b64_diagram = base64.urlsafe_b64encode(zlib.compress(plantuml_code.encode(), 9))
|
97
96
|
|
98
97
|
plantuml_url = self.kroki_server + "/plantuml/svg/" + b64_diagram.decode()
|
98
|
+
if self.dry_run:
|
99
|
+
return plantuml_url
|
99
100
|
if directory:
|
100
101
|
file_suffix = ".svg" if self.format == "puml" or self.format == "puml" else "." + self.format
|
101
102
|
self.output_file_name = os.path.join(
|
102
103
|
directory,
|
103
104
|
camelcase(sorted(classes)[0] if classes else self.schema.name) + file_suffix,
|
104
105
|
)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
self.logger.error(f"{resp.reason} accessing {plantuml_url}")
|
106
|
+
resp = requests.get(plantuml_url, stream=True, timeout=REQUESTS_TIMEOUT)
|
107
|
+
if resp.ok:
|
108
|
+
with open(self.output_file_name, "wb") as f:
|
109
|
+
for chunk in resp.iter_content(chunk_size=2048):
|
110
|
+
f.write(chunk)
|
111
|
+
else:
|
112
|
+
self.logger.error(f"{resp.reason} accessing {plantuml_url}")
|
113
113
|
else:
|
114
114
|
out = (
|
115
115
|
"@startuml\n"
|
@@ -352,6 +352,13 @@ class PlantumlGenerator(Generator):
|
|
352
352
|
help="URL of the Kroki server to use for diagram drawing",
|
353
353
|
default="https://kroki.io",
|
354
354
|
)
|
355
|
+
@click.option(
|
356
|
+
"--dry-run",
|
357
|
+
is_flag=True,
|
358
|
+
default=False,
|
359
|
+
show_default=True,
|
360
|
+
help="Print out Kroki URL calls instead of sending the real requests",
|
361
|
+
)
|
355
362
|
@click.version_option(__version__, "-V", "--version")
|
356
363
|
def cli(yamlfile, **args):
|
357
364
|
"""Generate a UML representation of a LinkML model"""
|