linkml-map 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. linkml_map-0.1.0/PKG-INFO +56 -0
  2. linkml_map-0.1.0/README.md +36 -0
  3. linkml_map-0.1.0/pyproject.toml +63 -0
  4. linkml_map-0.1.0/src/linkml_map/__init__.py +6 -0
  5. linkml_map-0.1.0/src/linkml_map/cli/__init__.py +0 -0
  6. linkml_map-0.1.0/src/linkml_map/cli/cli.py +205 -0
  7. linkml_map-0.1.0/src/linkml_map/compiler/__init__.py +1 -0
  8. linkml_map-0.1.0/src/linkml_map/compiler/awk_compiler.py +13 -0
  9. linkml_map-0.1.0/src/linkml_map/compiler/compiler.py +88 -0
  10. linkml_map-0.1.0/src/linkml_map/compiler/graphviz_compiler.py +109 -0
  11. linkml_map-0.1.0/src/linkml_map/compiler/j2_based_compiler.py +36 -0
  12. linkml_map-0.1.0/src/linkml_map/compiler/markdown_compiler.py +12 -0
  13. linkml_map-0.1.0/src/linkml_map/compiler/python_compiler.py +113 -0
  14. linkml_map-0.1.0/src/linkml_map/compiler/r2rml_compiler.py +12 -0
  15. linkml_map-0.1.0/src/linkml_map/compiler/sparql_compiler.py +12 -0
  16. linkml_map-0.1.0/src/linkml_map/compiler/sql_compiler.py +138 -0
  17. linkml_map-0.1.0/src/linkml_map/compiler/sssom_compiler.py +13 -0
  18. linkml_map-0.1.0/src/linkml_map/compiler/templates/__init__.py +3 -0
  19. linkml_map-0.1.0/src/linkml_map/compiler/templates/markdown.j2 +24 -0
  20. linkml_map-0.1.0/src/linkml_map/compiler/tr/__init__.py +4 -0
  21. linkml_map-0.1.0/src/linkml_map/compiler/tr/transformer_to_mapping_tables.tr.yaml +29 -0
  22. linkml_map-0.1.0/src/linkml_map/datamodel/__init__.py +4 -0
  23. linkml_map-0.1.0/src/linkml_map/datamodel/sssom.map.yaml +8 -0
  24. linkml_map-0.1.0/src/linkml_map/datamodel/transformer_model.py +425 -0
  25. linkml_map-0.1.0/src/linkml_map/datamodel/transformer_model.yaml +369 -0
  26. linkml_map-0.1.0/src/linkml_map/functions/__init__.py +0 -0
  27. linkml_map-0.1.0/src/linkml_map/functions/unit_conversion.py +157 -0
  28. linkml_map-0.1.0/src/linkml_map/importer/__init__.py +0 -0
  29. linkml_map-0.1.0/src/linkml_map/importer/importer.py +35 -0
  30. linkml_map-0.1.0/src/linkml_map/inference/__init__.py +0 -0
  31. linkml_map-0.1.0/src/linkml_map/inference/inference.py +38 -0
  32. linkml_map-0.1.0/src/linkml_map/inference/inverter.py +181 -0
  33. linkml_map-0.1.0/src/linkml_map/inference/schema_mapper.py +249 -0
  34. linkml_map-0.1.0/src/linkml_map/session.py +167 -0
  35. linkml_map-0.1.0/src/linkml_map/transformer/__init__.py +0 -0
  36. linkml_map-0.1.0/src/linkml_map/transformer/duckdb_transformer.py +70 -0
  37. linkml_map-0.1.0/src/linkml_map/transformer/object_transformer.py +368 -0
  38. linkml_map-0.1.0/src/linkml_map/transformer/transformer.py +269 -0
  39. linkml_map-0.1.0/src/linkml_map/utils/__init__.py +0 -0
  40. linkml_map-0.1.0/src/linkml_map/utils/dynamic_object.py +49 -0
  41. linkml_map-0.1.0/src/linkml_map/utils/eval_utils.py +207 -0
  42. linkml_map-0.1.0/src/linkml_map/utils/loaders.py +22 -0
  43. linkml_map-0.1.0/src/linkml_map/utils/multi_file_transformer.py +212 -0
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.1
2
+ Name: linkml-map
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: cmungall
6
+ Author-email: cjm@berkeleybop.org
7
+ Requires-Python: >=3.9,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: asteval (>=0.9.29,<0.10.0)
14
+ Requires-Dist: deepdiff (>=6.7.1,<7.0.0)
15
+ Requires-Dist: linkml-runtime (>=1.7.2)
16
+ Requires-Dist: pydantic (>=2.5.3,<3.0.0)
17
+ Requires-Dist: ucumvert (>=0.1.1,<0.2.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ # linkml-map
21
+
22
+ [![Pyversions](https://img.shields.io/pypi/pyversions/linkml-map.svg)](https://pypi.python.org/pypi/linkml-map)
23
+ ![](https://github.com/linkml/linkml-map/workflows/Build/badge.svg)
24
+ [![PyPi](https://img.shields.io/pypi/v/linkml-map.svg)](https://pypi.python.org/pypi/linkml-map)
25
+ [![codecov](https://codecov.io/gh/linkml/linkml-map/branch/main/graph/badge.svg?token=WNQNG986UN)](https://codecov.io/gh/linkml/linkml-map)
26
+
27
+ LinkML Map is a framework for specifying and executing mappings between data models.
28
+
29
+ Features:
30
+
31
+ - YAML-based lightweight syntax
32
+ - Python library for executing mappings on data files
33
+ - Ability to compile to other frameworks (forthcoming)
34
+ - Derivation of target (implicit) schemas, allowing easy customization of data models (*profiling*)
35
+ - Simple YAML dictionaries for simple mappings
36
+ - Automatic unit conversion
37
+ - Use of subset of Python to specify complex mappings
38
+ - Visualizations of mappings
39
+ - Mappings are reversible (provided all expressions used are reversible)
40
+
41
+ For full documentation see:
42
+
43
+ - [linkml.io/linkml-map/](https://linkml.io/linkml-map/)
44
+
45
+ Status:
46
+
47
+ The transformation data model is not yet fully stable, and may be subject to change.
48
+ Not all parts of the model are implemented in the reference Python framework.
49
+
50
+ ## Quickstart
51
+
52
+ * [Tutorial Notebook](src/docs/examples/Tutorial.ipynb)
53
+ * [Generated Docs](https://linkml.github.io/linkml-map/)
54
+ * [Compliance Suite](https://linkml.github.io/linkml-map/specification/compliance)
55
+
56
+
@@ -0,0 +1,36 @@
1
+ # linkml-map
2
+
3
+ [![Pyversions](https://img.shields.io/pypi/pyversions/linkml-map.svg)](https://pypi.python.org/pypi/linkml-map)
4
+ ![](https://github.com/linkml/linkml-map/workflows/Build/badge.svg)
5
+ [![PyPi](https://img.shields.io/pypi/v/linkml-map.svg)](https://pypi.python.org/pypi/linkml-map)
6
+ [![codecov](https://codecov.io/gh/linkml/linkml-map/branch/main/graph/badge.svg?token=WNQNG986UN)](https://codecov.io/gh/linkml/linkml-map)
7
+
8
+ LinkML Map is a framework for specifying and executing mappings between data models.
9
+
10
+ Features:
11
+
12
+ - YAML-based lightweight syntax
13
+ - Python library for executing mappings on data files
14
+ - Ability to compile to other frameworks (forthcoming)
15
+ - Derivation of target (implicit) schemas, allowing easy customization of data models (*profiling*)
16
+ - Simple YAML dictionaries for simple mappings
17
+ - Automatic unit conversion
18
+ - Use of subset of Python to specify complex mappings
19
+ - Visualizations of mappings
20
+ - Mappings are reversible (provided all expressions used are reversible)
21
+
22
+ For full documentation see:
23
+
24
+ - [linkml.io/linkml-map/](https://linkml.io/linkml-map/)
25
+
26
+ Status:
27
+
28
+ The transformation data model is not yet fully stable, and may be subject to change.
29
+ Not all parts of the model are implemented in the reference Python framework.
30
+
31
+ ## Quickstart
32
+
33
+ * [Tutorial Notebook](src/docs/examples/Tutorial.ipynb)
34
+ * [Generated Docs](https://linkml.github.io/linkml-map/)
35
+ * [Compliance Suite](https://linkml.github.io/linkml-map/specification/compliance)
36
+
@@ -0,0 +1,63 @@
1
+ [tool.poetry]
2
+ name = "linkml-map"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = ["cmungall <cjm@berkeleybop.org>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.9"
10
+ linkml-runtime = ">=1.7.2"
11
+ asteval = "^0.9.29"
12
+ deepdiff = "^6.7.1"
13
+ pydantic = "^2.5.3"
14
+ ucumvert = "^0.1.1"
15
+
16
+ [tool.poetry.dev-dependencies]
17
+ pytest = "^7.3.1"
18
+ pytest-cov = "^4.0.0"
19
+ linkml = ">=1.7.0"
20
+ mkdocs-mermaid2-plugin = "^0.6.0"
21
+ mknotebooks = "^0.8.0"
22
+ mkdocs-windmill = "*"
23
+ tox = "*"
24
+
25
+ [tool.poetry.scripts]
26
+ linkml-map = "linkml_map.cli.cli:main"
27
+
28
+ [tool.poetry.group.dev.dependencies]
29
+ jupyter = "^1.0.0"
30
+ linkml = "^1.6.7"
31
+
32
+
33
+ [tool.poetry.group.units.dependencies]
34
+ pint = "^0.23"
35
+
36
+
37
+ [tool.poetry.group.graphviz.dependencies]
38
+ graphviz = "^0.20.1"
39
+
40
+
41
+ [tool.poetry.group.duckdb.dependencies]
42
+ duckdb = "^0.10.1"
43
+
44
+ [build-system]
45
+ requires = ["poetry-core>=1.0.0"]
46
+ build-backend = "poetry.core.masonry.api"
47
+
48
+ [tool.black]
49
+ line-length = 100
50
+ target-version = ["py39", "py310", "py311"]
51
+
52
+ [tool.isort]
53
+ profile = "black"
54
+ multi_line_output = 3
55
+ include_trailing_comma = true
56
+ reverse_relative = true
57
+
58
+ # Ref: https://github.com/codespell-project/codespell#using-a-config-file
59
+ [tool.codespell]
60
+ skip = '.git,*.pdf,*.svg,go.sum,*.lock'
61
+ check-hidden = true
62
+ ignore-regex = '(^\s*"image/\S+": ".*|\b(KEGG.BRITE|mor.nlm.nih.gov)\b)'
63
+ ignore-words-list = 'infarction,amination'
@@ -0,0 +1,6 @@
1
+ from linkml_map.transformer.object_transformer import ObjectTransformer
2
+
3
+ __all__ = [
4
+ "Session",
5
+ "ObjectTransformer",
6
+ ]
File without changes
@@ -0,0 +1,205 @@
1
+ """Command line interface for linkml-transformer."""
2
+
3
+ import logging
4
+ import sys
5
+
6
+ import click
7
+
8
+ __all__ = [
9
+ "main",
10
+ ]
11
+
12
+ import yaml
13
+ from linkml_runtime import SchemaView
14
+ from linkml_runtime.dumpers import yaml_dumper
15
+
16
+ from linkml_map.compiler.markdown_compiler import MarkdownCompiler
17
+ from linkml_map.compiler.python_compiler import PythonCompiler
18
+ from linkml_map.inference.inverter import TransformationSpecificationInverter
19
+ from linkml_map.inference.schema_mapper import SchemaMapper
20
+ from linkml_map.transformer.object_transformer import ObjectTransformer
21
+
22
+ schema_option = click.option("-s", "--schema", help="Path to source schema.")
23
+ transformer_specification_option = click.option(
24
+ "-T", "--transformer-specification", help="Path to transformer specification."
25
+ )
26
+ output_option_wb = click.option(
27
+ "-o",
28
+ "--output",
29
+ type=click.File(mode="wb"),
30
+ default=sys.stdout,
31
+ help="Output file.",
32
+ )
33
+ output_option = click.option("-o", "--output", help="Output file.")
34
+ output_format_options = click.option(
35
+ "-O",
36
+ "--output-format",
37
+ type=click.Choice(["json", "yaml", "pickle", "md"]),
38
+ default="yaml",
39
+ help="Output format.",
40
+ )
41
+
42
+
43
+ @click.group()
44
+ @click.option("-v", "--verbose", count=True)
45
+ @click.option("-q", "--quiet")
46
+ # @click.version_option(__version__)
47
+ def main(verbose: int, quiet: bool):
48
+ """CLI for linkml-transformer."""
49
+ logger = logging.getLogger()
50
+ if verbose >= 2:
51
+ logger.setLevel(level=logging.DEBUG)
52
+ elif verbose == 1:
53
+ logger.setLevel(level=logging.INFO)
54
+ else:
55
+ logger.setLevel(level=logging.WARNING)
56
+ if quiet:
57
+ logger.setLevel(level=logging.ERROR)
58
+ logger.info(f"Logger {logger.name} set to level {logger.level}")
59
+
60
+
61
+ @main.command()
62
+ @output_option
63
+ @transformer_specification_option
64
+ @schema_option
65
+ @output_format_options
66
+ @click.option("--source-type")
67
+ @click.option(
68
+ "--unrestricted-eval/--no-unrestricted-eval",
69
+ default=False,
70
+ show_default=True,
71
+ help="Allow unrestricted eval of python expressions.",
72
+ )
73
+ @click.argument("input")
74
+ def map_data(
75
+ input,
76
+ schema,
77
+ source_type,
78
+ transformer_specification,
79
+ output,
80
+ output_format,
81
+ **kwargs,
82
+ ):
83
+ """
84
+ Map data from a source schema to a target schema using a transformation specification.
85
+
86
+ Example:
87
+
88
+ linkml-tr map-data -T X-to-Y-tr.yaml -s X.yaml X-data.yaml
89
+ """
90
+ logging.info(f"Transforming {input} conforming to {schema} using {transformer_specification}")
91
+ tr = ObjectTransformer(**kwargs)
92
+ tr.source_schemaview = SchemaView(schema)
93
+ tr.load_transformer_specification(transformer_specification)
94
+ with open(input) as file:
95
+ input_obj = yaml.safe_load(file)
96
+ tr.index(input_obj, source_type)
97
+ tr_obj = tr.map_object(input_obj, source_type)
98
+ if output:
99
+ outfile = open(output, "w", encoding="utf-8")
100
+ else:
101
+ outfile = sys.stdout
102
+ outfile.write(yaml_dumper.dumps(tr_obj))
103
+
104
+
105
+ @main.command()
106
+ @output_option
107
+ @transformer_specification_option
108
+ @schema_option
109
+ @click.option("--source-type")
110
+ @click.option("--target", default="python", show_default=True, help="Target representation.")
111
+ def compile(
112
+ schema,
113
+ source_type,
114
+ transformer_specification,
115
+ target,
116
+ output,
117
+ **kwargs,
118
+ ):
119
+ """
120
+ Compiles a schema to another representation.
121
+
122
+ Example:
123
+
124
+ linkml-tr compile -T X-to-Y-tr.yaml -s X.yaml
125
+ """
126
+ logging.info(f"Compiling {transformer_specification} with {schema}")
127
+ compiler_args = {}
128
+ if schema:
129
+ compiler_args["source_schemaview"] = SchemaView(schema)
130
+ if target == "python":
131
+ compiler = PythonCompiler(**compiler_args)
132
+ elif target == "markdown":
133
+ compiler = MarkdownCompiler(**compiler_args)
134
+ else:
135
+ raise NotImplementedError(f"Compiler {target} not implemented")
136
+ tr = ObjectTransformer()
137
+ tr.source_schemaview = SchemaView(schema)
138
+ tr.load_transformer_specification(transformer_specification)
139
+ result = compiler.compile(tr.specification)
140
+ print(result.serialization)
141
+
142
+
143
+ @main.command()
144
+ @output_option
145
+ @transformer_specification_option
146
+ @output_format_options
147
+ @click.argument("schema")
148
+ def derive_schema(schema, transformer_specification, output, output_format, **kwargs):
149
+ """Derive a schema from a source schema and a transformation specification.
150
+
151
+ This can be thought of as "copying" the source to a target, using the transformation
152
+ specification as a "patch"
153
+
154
+ Notes:
155
+
156
+ the implementation is currently incomplete; the derived schema may not be valid
157
+ linkml, e.g. there may be "dangling" references.
158
+
159
+ Example:
160
+
161
+ linkml-tr derive-schema -T transform/personinfo-to-agent.transform.yaml source/personinfo.yaml
162
+ """
163
+ logging.info(f"Transforming {schema} using {transformer_specification}")
164
+ tr = ObjectTransformer()
165
+ tr.load_transformer_specification(transformer_specification)
166
+ mapper = SchemaMapper(transformer=tr)
167
+ mapper.source_schemaview = SchemaView(schema)
168
+ target_schema = mapper.derive_schema()
169
+ if output:
170
+ outfile = open(output, "w", encoding="utf-8")
171
+ else:
172
+ outfile = sys.stdout
173
+ outfile.write(yaml_dumper.dumps(target_schema))
174
+
175
+
176
+ @main.command()
177
+ @output_option
178
+ @transformer_specification_option
179
+ @output_format_options
180
+ @click.option("--strict/--no-strict", default=True, show_default=True, help="Strict mode.")
181
+ @click.argument("schema")
182
+ def invert(schema, transformer_specification, output, output_format, **kwargs):
183
+ """Invert a transformation specification.
184
+
185
+ Example:
186
+
187
+ linkml-tr invert -T transform/personinfo-to-agent.transform.yaml source/personinfo.yaml
188
+ """
189
+ logging.info(f"Inverting {transformer_specification} using {schema} as source")
190
+ tr = ObjectTransformer()
191
+ tr.load_transformer_specification(transformer_specification)
192
+ inverter = TransformationSpecificationInverter(
193
+ source_schemaview=SchemaView(schema),
194
+ **kwargs,
195
+ )
196
+ inverted_spec = inverter.invert(tr.specification)
197
+ if output:
198
+ outfile = open(output, "w", encoding="utf-8")
199
+ else:
200
+ outfile = sys.stdout
201
+ outfile.write(yaml_dumper.dumps(inverted_spec))
202
+
203
+
204
+ if __name__ == "__main__":
205
+ main()
@@ -0,0 +1 @@
1
+ """Compiles schemas into other transformation frameworks."""
@@ -0,0 +1,13 @@
1
+ from linkml_map.compiler.compiler import CompiledSpecification, Compiler
2
+ from linkml_map.datamodel.transformer_model import TransformationSpecification
3
+
4
+
5
+ class AWKCompiler(Compiler):
6
+ """
7
+ Compiles a Transformation Specification to an Awk script.
8
+
9
+ Note: this is only expected to work for flat schemas.
10
+ """
11
+
12
+ def compile(self, specification: TransformationSpecification) -> CompiledSpecification:
13
+ raise NotImplementedError
@@ -0,0 +1,88 @@
1
+ """
2
+ Compilers are responsible for compiling a transformation specification into an alternative representation.
3
+
4
+ This is the opposite of an importer.
5
+
6
+ For example:
7
+
8
+ - LinkML-Transformer Specifications to R2RML
9
+ - LinkML-Transformer Specifications to awk scripts
10
+ - LinkML-Transformer Specifications to SQL
11
+ - LinkML-Transformer Specifications to Python (OO)
12
+ - LinkML-Transformer Specifications to Pandas
13
+ - LinkML-Transformer Specifications to Hamilton
14
+ """
15
+
16
+ from abc import ABC
17
+ from dataclasses import dataclass, field
18
+ from types import ModuleType
19
+ from typing import Iterator, Optional
20
+
21
+ from linkml_runtime import SchemaView
22
+ from linkml_runtime.dumpers import yaml_dumper
23
+ from linkml_runtime.utils.compile_python import compile_python
24
+
25
+ from linkml_map.datamodel.transformer_model import TransformationSpecification
26
+ from linkml_map.inference.schema_mapper import SchemaMapper
27
+
28
+
29
+ @dataclass
30
+ class CompiledSpecification:
31
+ serialization: str = field(default="")
32
+
33
+ _module: Optional[ModuleType] = None
34
+
35
+ @property
36
+ def module(self) -> ModuleType:
37
+ if not self._module:
38
+ self._module = compile_python(self.serialization)
39
+ return self._module
40
+
41
+
42
+ @dataclass
43
+ class Compiler(ABC):
44
+ """
45
+ Base class for all compilers.
46
+
47
+ A compiler will compile a transformation specification into
48
+ an alternative representation.
49
+
50
+ An example compiler would be a R2RML compiler.
51
+
52
+ Note: Compilers and Importers will in general be implemented by providing
53
+ mapping specifications
54
+ """
55
+
56
+ source_schemaview: SchemaView = None
57
+ """A view over the schema describing the source."""
58
+
59
+ source_python_module: str = None
60
+ """The python module containing the source classes."""
61
+
62
+ target_python_module: str = None
63
+ """The python module containing the target classes."""
64
+
65
+ def compile(self, specification: TransformationSpecification) -> CompiledSpecification:
66
+ """
67
+ Transform source object into an instance of the target class.
68
+
69
+ :param specification:
70
+ :return:
71
+ """
72
+ s = self._compile_header(specification)
73
+ for chunk in self._compile_iterator(specification):
74
+ s += chunk
75
+ return CompiledSpecification(serialization=s)
76
+
77
+ def _compile_header(self, specification: TransformationSpecification) -> str:
78
+ return ""
79
+
80
+ def _compile_iterator(self, specification: TransformationSpecification) -> Iterator[str]:
81
+ raise NotImplementedError
82
+
83
+ def derived_target_schemaview(self, specification: TransformationSpecification):
84
+ """
85
+ Returns a view over the target schema, including any derived classes.
86
+ """
87
+ mapper = SchemaMapper(source_schemaview=self.source_schemaview)
88
+ return SchemaView(yaml_dumper.dumps(mapper.derive_schema(specification)))
@@ -0,0 +1,109 @@
1
+ import re
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional, Tuple
4
+
5
+ from graphviz import Digraph
6
+ from linkml_runtime import SchemaView
7
+ from pydantic import BaseModel
8
+
9
+ from linkml_map.compiler.compiler import CompiledSpecification, Compiler
10
+ from linkml_map.datamodel.transformer_model import TransformationSpecification
11
+
12
+
13
+ class Record(BaseModel):
14
+ """
15
+ A simplified representation of a class, UML-style.
16
+ """
17
+
18
+ name: str
19
+ source: str
20
+ fields: List[Tuple[str, str]] = []
21
+
22
+ @property
23
+ def id(self):
24
+ return f"{self.source}{self.name}"
25
+
26
+ def __str__(self):
27
+ return (
28
+ f"""<
29
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
30
+ <TR><TD><B>{self.name}</B></TD></TR>
31
+ """
32
+ + "".join(
33
+ [
34
+ f'<TR><TD SIDES="LRB" PORT="{f[0]}">{f[0]} : {f[1]}</TD></TR>'
35
+ for f in self.fields
36
+ ]
37
+ )
38
+ + """
39
+ </TABLE>>"""
40
+ )
41
+
42
+
43
+ @dataclass
44
+ class GraphvizObject(CompiledSpecification):
45
+ digraph: Digraph = None
46
+
47
+ def render(self, file_path: str, format="png", view=False) -> None:
48
+ """
49
+ Renders a graphviz graph to a file.
50
+ :param file_path:
51
+ :return:
52
+ """
53
+ self.digraph.render(file_path, format=format, view=view)
54
+
55
+
56
+ class GraphvizCompiler(Compiler):
57
+ """
58
+ Compiles a Transformation Specification to GraphViz.
59
+ """
60
+
61
+ def compile(
62
+ self, specification: TransformationSpecification, elements: Optional[List[str]] = None
63
+ ) -> GraphvizObject:
64
+ dg = Digraph(comment="UML Class Diagram", format="png")
65
+ dg.attr(rankdir="LR") # Set graph direction from left to right
66
+ target_schemaview = self.derived_target_schemaview(specification)
67
+ source_schemaview = self.source_schemaview
68
+
69
+ records = []
70
+ records += self.add_records(source_schemaview, "source")
71
+ records += self.add_records(target_schemaview, "target")
72
+
73
+ for record in records:
74
+ dg.node(record.id, str(record), shape="plaintext")
75
+
76
+ # Define the class nodes with fields in UML format using HTML-like labels
77
+ # for precise control over the stacking of the fields
78
+ for target_cn, cd in specification.class_derivations.items():
79
+ source_cn = cd.populated_from
80
+ if source_cn is None:
81
+ source_cn = cd.name
82
+ if elements is not None and target_cn not in elements:
83
+ continue
84
+ source_record = Record(name=source_cn, source="source")
85
+ target_record = Record(name=target_cn, source="target")
86
+ for sd in cd.slot_derivations.values():
87
+ target_slot = sd.name
88
+ target_id = f"{target_record.id}:{target_slot}"
89
+ source_slot = sd.populated_from
90
+ if source_slot:
91
+ source_id = f"{source_record.id}:{source_slot}"
92
+ dg.edge(source_id, target_id)
93
+ elif sd.expr:
94
+ # TODO: do this in a less hacky way
95
+ tokens = re.findall(r"\w+", sd.expr)
96
+ for token in tokens:
97
+ if token not in source_schemaview.all_slots():
98
+ continue
99
+ dg.edge(f"{source_record.id}:{token}", target_id, style="dashed")
100
+ return GraphvizObject(digraph=dg, serialization=dg.source)
101
+
102
+ def add_records(self, schemaview: SchemaView, source: str) -> List[Record]:
103
+ records = []
104
+ for cn in schemaview.all_classes():
105
+ record = Record(name=cn, source=source)
106
+ for induced_slot in schemaview.class_induced_slots(cn):
107
+ record.fields.append((induced_slot.name, induced_slot.range))
108
+ records.append(record)
109
+ return records
@@ -0,0 +1,36 @@
1
+ from dataclasses import dataclass
2
+
3
+ from jinja2 import Environment, FileSystemLoader
4
+
5
+ from linkml_map.compiler.compiler import CompiledSpecification, Compiler
6
+ from linkml_map.compiler.templates import TEMPLATE_DIR
7
+ from linkml_map.datamodel.transformer_model import TransformationSpecification
8
+
9
+
10
+ @dataclass
11
+ class J2BasedCompiler(Compiler):
12
+ """
13
+ Compiles a Transformation Specification using a Jinja2 template.
14
+ """
15
+
16
+ template_dir: str = None
17
+ """The directory containing the Jinja2 template."""
18
+
19
+ template_name: str = None
20
+ """The name of the Jinja2 template."""
21
+
22
+ def compile(self, specification: TransformationSpecification) -> CompiledSpecification:
23
+ template_dir = self.template_dir
24
+ if not template_dir:
25
+ template_dir = TEMPLATE_DIR
26
+ if not template_dir:
27
+ raise ValueError("template_dir must be set")
28
+ loader = FileSystemLoader(template_dir)
29
+ env = Environment(loader=loader, autoescape=True)
30
+ if not self.template_name:
31
+ raise ValueError("template_name must be set")
32
+ template = env.get_template(self.template_name)
33
+ rendered = template.render(
34
+ spec=specification,
35
+ )
36
+ return CompiledSpecification(serialization=rendered)
@@ -0,0 +1,12 @@
1
+ from dataclasses import dataclass
2
+
3
+ from linkml_map.compiler.j2_based_compiler import J2BasedCompiler
4
+
5
+
6
+ @dataclass
7
+ class MarkdownCompiler(J2BasedCompiler):
8
+ """
9
+ Compiles a Transformation Specification to Markdown.
10
+ """
11
+
12
+ template_name: str = "markdown.j2"