linkml 1.5.5__py3-none-any.whl → 1.5.7__py3-none-any.whl
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/__init__.py +2 -6
- linkml/_version.py +1 -1
- linkml/generators/PythonGenNotes.md +4 -4
- linkml/generators/__init__.py +26 -5
- linkml/generators/common/type_designators.py +27 -22
- linkml/generators/csvgen.py +4 -10
- linkml/generators/docgen/class.md.jinja2 +7 -0
- linkml/generators/docgen/class_diagram.md.jinja2 +0 -6
- linkml/generators/docgen/subset.md.jinja2 +54 -13
- linkml/generators/docgen.py +94 -92
- linkml/generators/dotgen.py +5 -9
- linkml/generators/erdiagramgen.py +58 -53
- linkml/generators/excelgen.py +10 -16
- linkml/generators/golanggen.py +11 -21
- linkml/generators/golrgen.py +4 -13
- linkml/generators/graphqlgen.py +3 -11
- linkml/generators/javagen.py +8 -15
- linkml/generators/jsonldcontextgen.py +7 -36
- linkml/generators/jsonldgen.py +14 -12
- linkml/generators/jsonschemagen.py +183 -136
- linkml/generators/linkmlgen.py +1 -1
- linkml/generators/markdowngen.py +40 -89
- linkml/generators/namespacegen.py +1 -2
- linkml/generators/oocodegen.py +22 -25
- linkml/generators/owlgen.py +48 -49
- linkml/generators/prefixmapgen.py +6 -14
- linkml/generators/projectgen.py +7 -14
- linkml/generators/protogen.py +3 -5
- linkml/generators/pydanticgen.py +85 -73
- linkml/generators/pythongen.py +89 -157
- linkml/generators/rdfgen.py +5 -11
- linkml/generators/shaclgen.py +32 -18
- linkml/generators/shexgen.py +19 -24
- linkml/generators/sparqlgen.py +5 -13
- linkml/generators/sqlalchemy/__init__.py +3 -2
- linkml/generators/sqlalchemy/sqlalchemy_declarative_template.py +7 -7
- linkml/generators/sqlalchemy/sqlalchemy_imperative_template.py +3 -3
- linkml/generators/sqlalchemygen.py +29 -27
- linkml/generators/sqlddlgen.py +34 -26
- linkml/generators/sqltablegen.py +21 -21
- linkml/generators/sssomgen.py +11 -13
- linkml/generators/summarygen.py +2 -4
- linkml/generators/terminusdbgen.py +7 -19
- linkml/generators/typescriptgen.py +10 -18
- linkml/generators/yamlgen.py +0 -2
- linkml/generators/yumlgen.py +23 -71
- linkml/linter/cli.py +4 -11
- linkml/linter/config/datamodel/config.py +17 -47
- linkml/linter/linter.py +2 -4
- linkml/linter/rules.py +34 -48
- linkml/reporting/__init__.py +2 -0
- linkml/reporting/model.py +9 -24
- linkml/transformers/relmodel_transformer.py +20 -33
- linkml/transformers/schema_renamer.py +14 -10
- linkml/utils/converter.py +15 -15
- linkml/utils/datautils.py +9 -24
- linkml/utils/datavalidator.py +2 -2
- linkml/utils/execute_tutorial.py +10 -12
- linkml/utils/generator.py +74 -92
- linkml/utils/helpers.py +4 -2
- linkml/utils/ifabsent_functions.py +23 -15
- linkml/utils/mergeutils.py +19 -35
- linkml/utils/rawloader.py +2 -6
- linkml/utils/schema_builder.py +31 -19
- linkml/utils/schema_fixer.py +28 -18
- linkml/utils/schemaloader.py +44 -89
- linkml/utils/schemasynopsis.py +50 -73
- linkml/utils/sqlutils.py +40 -30
- linkml/utils/typereferences.py +9 -6
- linkml/utils/validation.py +4 -5
- linkml/validators/__init__.py +2 -0
- linkml/validators/jsonschemavalidator.py +104 -53
- linkml/validators/sparqlvalidator.py +5 -15
- linkml/workspaces/datamodel/workspaces.py +13 -30
- linkml/workspaces/example_runner.py +75 -68
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/METADATA +2 -2
- linkml-1.5.7.dist-info/RECORD +109 -0
- linkml-1.5.5.dist-info/RECORD +0 -109
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/LICENSE +0 -0
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/WHEEL +0 -0
- {linkml-1.5.5.dist-info → linkml-1.5.7.dist-info}/entry_points.txt +0 -0
linkml/generators/shexgen.py
CHANGED
@@ -3,23 +3,25 @@
|
|
3
3
|
"""
|
4
4
|
import os
|
5
5
|
from dataclasses import dataclass, field
|
6
|
-
from typing import List, Optional,
|
6
|
+
from typing import List, Optional, Union
|
7
7
|
|
8
8
|
import click
|
9
9
|
from jsonasobj import as_json as as_json_1
|
10
|
-
from linkml_runtime.linkml_model.meta import (
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
from linkml_runtime.linkml_model.meta import (
|
11
|
+
ClassDefinition,
|
12
|
+
ElementName,
|
13
|
+
EnumDefinition,
|
14
|
+
SlotDefinition,
|
15
|
+
SlotDefinitionName,
|
16
|
+
TypeDefinition,
|
17
|
+
)
|
15
18
|
from linkml_runtime.linkml_model.types import SHEX
|
16
19
|
from linkml_runtime.utils.formatutils import camelcase, sfx
|
17
20
|
from linkml_runtime.utils.metamodelcore import URIorCURIE
|
18
21
|
from rdflib import OWL, RDF, XSD, Graph, Namespace
|
19
22
|
from ShExJSG import ShExC
|
20
23
|
from ShExJSG.SchemaWithContext import Schema
|
21
|
-
from ShExJSG.ShExJ import
|
22
|
-
TripleConstraint)
|
24
|
+
from ShExJSG.ShExJ import IRIREF, EachOf, NodeConstraint, Shape, ShapeOr, TripleConstraint
|
23
25
|
|
24
26
|
from linkml import METAMODEL_NAMESPACE, METAMODEL_NAMESPACE_NAME
|
25
27
|
from linkml._version import __version__
|
@@ -32,6 +34,7 @@ class ShExGenerator(Generator):
|
|
32
34
|
generatorname = os.path.basename(__file__)
|
33
35
|
generatorversion = "0.0.2"
|
34
36
|
valid_formats = ["shex", "json", "rdf"]
|
37
|
+
file_extension = "shex.rdf"
|
35
38
|
visit_all_class_slots = False
|
36
39
|
uses_schemaloader = True
|
37
40
|
|
@@ -39,7 +42,9 @@ class ShExGenerator(Generator):
|
|
39
42
|
shex: Schema = field(default_factory=lambda: Schema()) # ShEx Schema being generated
|
40
43
|
shapes: List = field(default_factory=lambda: [])
|
41
44
|
shape: Optional[Shape] = None # Current shape being defined
|
42
|
-
list_shapes: List[IRIREF] = field(
|
45
|
+
list_shapes: List[IRIREF] = field(
|
46
|
+
default_factory=lambda: []
|
47
|
+
) # Shapes that have been defined as lists
|
43
48
|
|
44
49
|
def __post_init__(self):
|
45
50
|
super().__post_init__()
|
@@ -71,14 +76,10 @@ class ShExGenerator(Generator):
|
|
71
76
|
if typ_type_uri in (XSD.anyURI, SHEX.iri):
|
72
77
|
self.shapes.append(NodeConstraint(id=model_uri, nodeKind="iri"))
|
73
78
|
elif typ_type_uri == SHEX.nonLiteral:
|
74
|
-
self.shapes.append(
|
75
|
-
NodeConstraint(id=model_uri, nodeKind="nonliteral")
|
76
|
-
)
|
79
|
+
self.shapes.append(NodeConstraint(id=model_uri, nodeKind="nonliteral"))
|
77
80
|
else:
|
78
81
|
self.shapes.append(
|
79
|
-
NodeConstraint(
|
80
|
-
id=model_uri, datatype=self.namespaces.uri_for(typ.uri)
|
81
|
-
)
|
82
|
+
NodeConstraint(id=model_uri, datatype=self.namespaces.uri_for(typ.uri))
|
82
83
|
)
|
83
84
|
else:
|
84
85
|
typeof_uri = self._class_or_type_uri(typ.typeof)
|
@@ -95,9 +96,7 @@ class ShExGenerator(Generator):
|
|
95
96
|
struct_ref_list.append(applier)
|
96
97
|
for sr in struct_ref_list:
|
97
98
|
self._add_constraint(self._class_or_type_uri(sr, "_tes"))
|
98
|
-
self._add_constraint(
|
99
|
-
self._type_arc(self.schema.classes[sr].class_uri, opt=True)
|
100
|
-
)
|
99
|
+
self._add_constraint(self._type_arc(self.schema.classes[sr].class_uri, opt=True))
|
101
100
|
return True
|
102
101
|
|
103
102
|
def end_class(self, cls: ClassDefinition) -> None:
|
@@ -121,9 +120,7 @@ class ShExGenerator(Generator):
|
|
121
120
|
# If this class has subtypes, define the class as the union of its subtypes and itself (if not abstract)
|
122
121
|
if cls.name in self.synopsis.isarefs:
|
123
122
|
childrenExprs = []
|
124
|
-
for child_classname in sorted(
|
125
|
-
list(self.synopsis.isarefs[cls.name].classrefs)
|
126
|
-
):
|
123
|
+
for child_classname in sorted(list(self.synopsis.isarefs[cls.name].classrefs)):
|
127
124
|
childrenExprs.append(self._class_or_type_uri(child_classname))
|
128
125
|
if not (cls.mixin or cls.abstract) or len(childrenExprs) == 1:
|
129
126
|
childrenExprs.insert(0, self.shape)
|
@@ -203,9 +200,7 @@ class ShExGenerator(Generator):
|
|
203
200
|
self.shape.expression = constraint
|
204
201
|
# One constraint
|
205
202
|
elif not isinstance(self.shape.expression, EachOf):
|
206
|
-
self.shape.expression = EachOf(
|
207
|
-
expressions=[self.shape.expression, constraint]
|
208
|
-
)
|
203
|
+
self.shape.expression = EachOf(expressions=[self.shape.expression, constraint])
|
209
204
|
# Two or more constraints
|
210
205
|
else:
|
211
206
|
self.shape.expression.expressions.append(constraint)
|
linkml/generators/sparqlgen.py
CHANGED
@@ -3,18 +3,12 @@ import os
|
|
3
3
|
from collections import defaultdict
|
4
4
|
from dataclasses import dataclass
|
5
5
|
from pathlib import Path
|
6
|
-
from typing import Dict, List, Optional
|
6
|
+
from typing import Dict, List, Optional
|
7
7
|
|
8
8
|
import click
|
9
9
|
from jinja2 import Template
|
10
|
-
from linkml_runtime.linkml_model.meta import
|
11
|
-
|
12
|
-
EnumDefinitionName, Prefix,
|
13
|
-
SchemaDefinition, SlotDefinition,
|
14
|
-
SlotDefinitionName,
|
15
|
-
TypeDefinition,
|
16
|
-
TypeDefinitionName)
|
17
|
-
from linkml_runtime.utils.formatutils import camelcase, underscore
|
10
|
+
from linkml_runtime.linkml_model.meta import Prefix
|
11
|
+
from linkml_runtime.utils.formatutils import underscore
|
18
12
|
from linkml_runtime.utils.schemaview import SchemaView
|
19
13
|
|
20
14
|
from linkml._version import __version__
|
@@ -109,9 +103,7 @@ x = """
|
|
109
103
|
def materialize_schema(schemaview: SchemaView):
|
110
104
|
schema = schemaview.schema
|
111
105
|
if "rdf" not in schema.prefixes:
|
112
|
-
schema.prefixes["rdf"] = Prefix(
|
113
|
-
"rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
114
|
-
)
|
106
|
+
schema.prefixes["rdf"] = Prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
|
115
107
|
for scn in schemaview.imports_closure():
|
116
108
|
for pfxn, pfx in schemaview.schema_map[scn].prefixes.items():
|
117
109
|
if pfxn not in schema:
|
@@ -203,7 +195,7 @@ class SparqlGenerator(Generator):
|
|
203
195
|
def cli(yamlfile, dir, **kwargs):
|
204
196
|
"""Generate SPARQL queries for validation
|
205
197
|
|
206
|
-
This will generate a directory of queries that
|
198
|
+
This will generate a directory of queries that can be used for QC over a triplestore that
|
207
199
|
is conformant to the same LinkML schema.
|
208
200
|
|
209
201
|
Each query in the directory will be of the form
|
@@ -1,3 +1,4 @@
|
|
1
|
-
from .sqlalchemy_declarative_template import
|
2
|
-
sqlalchemy_declarative_template_str
|
1
|
+
from .sqlalchemy_declarative_template import sqlalchemy_declarative_template_str
|
3
2
|
from .sqlalchemy_imperative_template import sqlalchemy_imperative_template_str
|
3
|
+
|
4
|
+
__all__ = ["sqlalchemy_declarative_template_str", "sqlalchemy_imperative_template_str"]
|
@@ -14,7 +14,7 @@ class {{classname(c.name)}}({% if c.is_a %}{{ classname(c.is_a) }}{% else %}Base
|
|
14
14
|
{% if c.description %}{{c.description}}{% else %}{{c.alias}}{% endif %}
|
15
15
|
\"\"\"
|
16
16
|
__tablename__ = '{{c.name}}'
|
17
|
-
|
17
|
+
|
18
18
|
{% for s in c.attributes.values() -%}
|
19
19
|
{{s.alias}} = Column({{s.annotations['sql_type'].value}}
|
20
20
|
{%- if 'foreign_key' in s.annotations -%}, ForeignKey('{{ s.annotations['foreign_key'].value }}') {%- endif -%}
|
@@ -26,7 +26,7 @@ class {{classname(c.name)}}({% if c.is_a %}{{ classname(c.is_a) }}{% else %}Base
|
|
26
26
|
{{s.annotations['original_slot'].value}} = relationship("{{classname(s.range)}}", uselist=False, foreign_keys=[{{s.alias}}])
|
27
27
|
{% endif -%}
|
28
28
|
{% endfor %}
|
29
|
-
|
29
|
+
|
30
30
|
{%- for mapping in backrefs[c.name] %}
|
31
31
|
{% if mapping.mapping_type == "ManyToMany" %}
|
32
32
|
# ManyToMany
|
@@ -40,15 +40,15 @@ class {{classname(c.name)}}({% if c.is_a %}{{ classname(c.is_a) }}{% else %}Base
|
|
40
40
|
{{mapping.source_slot}} = relationship( "{{ classname(mapping.target_class) }}", foreign_keys="[{{ mapping.target_class }}.{{mapping.target_slot}}]")
|
41
41
|
{% endif -%}
|
42
42
|
{%- endfor %}
|
43
|
-
|
43
|
+
|
44
44
|
def __repr__(self):
|
45
45
|
return f"{{c.name}}(
|
46
46
|
{%- for s in c.attributes.values() -%}
|
47
|
-
{{s.alias}}={self.{{s.alias}}},
|
47
|
+
{{s.alias}}={self.{{s.alias}}},
|
48
48
|
{%- endfor %})"
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
|
50
|
+
|
51
|
+
|
52
52
|
{% if c.is_a or c.mixins %}
|
53
53
|
# Using concrete inheritance: see https://docs.sqlalchemy.org/en/14/orm/inheritance.html
|
54
54
|
__mapper_args__ = {
|
@@ -27,10 +27,10 @@ tbl_{{classname(c.name)}} = Table('{{c.name}}', metadata,
|
|
27
27
|
Column('{{s.name}}',
|
28
28
|
Text,
|
29
29
|
{% if 'foreign_key' in s.annotations -%}
|
30
|
-
ForeignKey('{{ s.annotations['foreign_key'].value }}'),
|
30
|
+
ForeignKey('{{ s.annotations['foreign_key'].value }}'),
|
31
31
|
{% endif -%}
|
32
|
-
{% if 'primary_key' in s.annotations -%}
|
33
|
-
primary_key=True
|
32
|
+
{% if 'primary_key' in s.annotations -%}
|
33
|
+
primary_key=True
|
34
34
|
{%- endif -%}
|
35
35
|
),
|
36
36
|
{%- endfor %}
|
@@ -3,26 +3,30 @@ import os
|
|
3
3
|
from collections import defaultdict
|
4
4
|
from dataclasses import dataclass
|
5
5
|
from types import ModuleType
|
6
|
-
from typing import
|
6
|
+
from typing import List, Union
|
7
7
|
|
8
8
|
import click
|
9
9
|
from jinja2 import Template
|
10
|
-
from linkml_runtime.linkml_model import (
|
11
|
-
|
12
|
-
|
10
|
+
from linkml_runtime.linkml_model import (
|
11
|
+
Annotation,
|
12
|
+
ClassDefinition,
|
13
|
+
ClassDefinitionName,
|
14
|
+
SchemaDefinition,
|
15
|
+
)
|
13
16
|
from linkml_runtime.utils.compile_python import compile_python
|
14
17
|
from linkml_runtime.utils.formatutils import camelcase, underscore
|
15
18
|
from linkml_runtime.utils.schemaview import SchemaView
|
16
|
-
from sqlalchemy import
|
19
|
+
from sqlalchemy import Enum
|
17
20
|
|
18
21
|
from linkml._version import __version__
|
19
22
|
from linkml.generators.pydanticgen import PydanticGenerator
|
20
23
|
from linkml.generators.pythongen import PythonGenerator
|
21
|
-
from linkml.generators.sqlalchemy import (
|
22
|
-
|
24
|
+
from linkml.generators.sqlalchemy import (
|
25
|
+
sqlalchemy_declarative_template_str,
|
26
|
+
sqlalchemy_imperative_template_str,
|
27
|
+
)
|
23
28
|
from linkml.generators.sqltablegen import SQLTableGenerator
|
24
|
-
from linkml.transformers.relmodel_transformer import
|
25
|
-
ForeignKeyPolicy, RelationalModelTransformer)
|
29
|
+
from linkml.transformers.relmodel_transformer import ForeignKeyPolicy, RelationalModelTransformer
|
26
30
|
from linkml.utils.generator import Generator, shared_arguments
|
27
31
|
|
28
32
|
|
@@ -43,7 +47,9 @@ class SQLAlchemyGenerator(Generator):
|
|
43
47
|
generatorname = os.path.basename(__file__)
|
44
48
|
generatorversion = "0.1.1"
|
45
49
|
valid_formats = ["sqla"]
|
50
|
+
file_extension = "py"
|
46
51
|
uses_schemaloader = False
|
52
|
+
template: TemplateEnum = None
|
47
53
|
|
48
54
|
# ObjectVars
|
49
55
|
original_schema: Union[SchemaDefinition, str] = None
|
@@ -53,17 +59,20 @@ class SQLAlchemyGenerator(Generator):
|
|
53
59
|
self.schemaview = SchemaView(self.schema)
|
54
60
|
super().__post_init__()
|
55
61
|
|
56
|
-
|
57
62
|
def generate_sqla(
|
58
63
|
self,
|
59
64
|
model_path: str = None,
|
60
65
|
no_model_import=False,
|
61
|
-
template: TemplateEnum =
|
66
|
+
template: TemplateEnum = None,
|
62
67
|
foreign_key_policy: ForeignKeyPolicy = None,
|
63
68
|
**kwargs,
|
64
69
|
) -> str:
|
65
70
|
# src_sv = SchemaView(self.schema)
|
66
71
|
# self.schema = src_sv.schema
|
72
|
+
if template is None:
|
73
|
+
template = self.template
|
74
|
+
if template is None:
|
75
|
+
template = TemplateEnum.IMPERATIVE
|
67
76
|
sqltr = RelationalModelTransformer(self.schemaview)
|
68
77
|
if foreign_key_policy:
|
69
78
|
sqltr.foreign_key_policy = foreign_key_policy
|
@@ -90,7 +99,6 @@ class SQLAlchemyGenerator(Generator):
|
|
90
99
|
backrefs = defaultdict(list)
|
91
100
|
for m in tr_result.mappings:
|
92
101
|
backrefs[m.source_class].append(m)
|
93
|
-
skip = {}
|
94
102
|
# for c in tr_schema.classes.values():
|
95
103
|
# if len(c.attributes) == 0:
|
96
104
|
# skip[c.name] = True
|
@@ -98,18 +106,13 @@ class SQLAlchemyGenerator(Generator):
|
|
98
106
|
self.add_safe_aliases(tr_schema)
|
99
107
|
tr_sv = SchemaView(tr_schema)
|
100
108
|
rel_schema_classes_ordered = [
|
101
|
-
tr_sv.get_class(cn, strict=True)
|
102
|
-
for cn in self.order_classes_by_hierarchy(tr_sv)
|
103
|
-
]
|
104
|
-
rel_schema_classes_ordered = [
|
105
|
-
c for c in rel_schema_classes_ordered if not self.skip(c)
|
109
|
+
tr_sv.get_class(cn, strict=True) for cn in self.order_classes_by_hierarchy(tr_sv)
|
106
110
|
]
|
111
|
+
rel_schema_classes_ordered = [c for c in rel_schema_classes_ordered if not self.skip(c)]
|
107
112
|
for c in rel_schema_classes_ordered:
|
108
113
|
# For SQLA there needs to be a primary key for each class;
|
109
114
|
# autogenerate this as a compound key if none declared
|
110
|
-
has_pk = any(
|
111
|
-
a for a in c.attributes.values() if "primary_key" in a.annotations
|
112
|
-
)
|
115
|
+
has_pk = any(a for a in c.attributes.values() if "primary_key" in a.annotations)
|
113
116
|
if not has_pk:
|
114
117
|
for a in c.attributes.values():
|
115
118
|
ann = Annotation("primary_key", "true")
|
@@ -127,6 +130,9 @@ class SQLAlchemyGenerator(Generator):
|
|
127
130
|
)
|
128
131
|
return code
|
129
132
|
|
133
|
+
def serialize(self, **kwargs) -> str:
|
134
|
+
return self.generate_sqla(**kwargs)
|
135
|
+
|
130
136
|
def compile_sqla(
|
131
137
|
self,
|
132
138
|
compile_python_dataclasses=False,
|
@@ -167,9 +173,7 @@ class SQLAlchemyGenerator(Generator):
|
|
167
173
|
else:
|
168
174
|
pygen = PythonGenerator(self.original_schema)
|
169
175
|
dc_code = pygen.serialize()
|
170
|
-
sqla_code = self.generate_sqla(
|
171
|
-
model_path=None, no_model_import=True, **kwargs
|
172
|
-
)
|
176
|
+
sqla_code = self.generate_sqla(model_path=None, no_model_import=True, **kwargs)
|
173
177
|
return compile_python(f"{dc_code}\n{sqla_code}", package_path=model_path)
|
174
178
|
else:
|
175
179
|
code = self.generate_sqla(model_path=model_path, **kwargs)
|
@@ -231,9 +235,7 @@ class SQLAlchemyGenerator(Generator):
|
|
231
235
|
)
|
232
236
|
@click.version_option(__version__, "-V", "--version")
|
233
237
|
@click.command()
|
234
|
-
def cli(
|
235
|
-
yamlfile, declarative, generate_classes, pydantic, use_foreign_keys=True, **args
|
236
|
-
):
|
238
|
+
def cli(yamlfile, declarative, generate_classes, pydantic, use_foreign_keys=True, **args):
|
237
239
|
"""Generate SQL DDL representation"""
|
238
240
|
if pydantic:
|
239
241
|
pygen = PydanticGenerator(yamlfile)
|
@@ -249,7 +251,7 @@ def cli(
|
|
249
251
|
foreign_key_policy = ForeignKeyPolicy.NO_FOREIGN_KEYS
|
250
252
|
print(gen.generate_sqla(template=t, foreign_key_policy=foreign_key_policy))
|
251
253
|
if generate_classes:
|
252
|
-
raise NotImplementedError(
|
254
|
+
raise NotImplementedError("generate classes not implemented")
|
253
255
|
|
254
256
|
|
255
257
|
if __name__ == "__main__":
|
linkml/generators/sqlddlgen.py
CHANGED
@@ -1,17 +1,34 @@
|
|
1
|
+
"""DEPRECATED: Use SQLTableGenerator instead"""
|
1
2
|
import logging
|
2
3
|
import os
|
3
4
|
from contextlib import redirect_stdout
|
4
5
|
from dataclasses import dataclass, field
|
5
|
-
from typing import Dict, List, Optional, TextIO,
|
6
|
+
from typing import Dict, List, Optional, TextIO, Union
|
6
7
|
|
7
8
|
import click
|
8
9
|
from deprecated.classic import deprecated
|
9
|
-
from linkml_runtime.linkml_model.meta import (
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
from linkml_runtime.linkml_model.meta import (
|
11
|
+
ClassDefinition,
|
12
|
+
ClassDefinitionName,
|
13
|
+
SchemaDefinition,
|
14
|
+
SlotDefinition,
|
15
|
+
)
|
13
16
|
from linkml_runtime.utils.formatutils import camelcase, underscore
|
14
|
-
from sqlalchemy import
|
17
|
+
from sqlalchemy import (
|
18
|
+
Boolean,
|
19
|
+
Column,
|
20
|
+
Date,
|
21
|
+
DateTime,
|
22
|
+
Enum,
|
23
|
+
Float,
|
24
|
+
ForeignKey,
|
25
|
+
Integer,
|
26
|
+
MetaData,
|
27
|
+
Table,
|
28
|
+
Text,
|
29
|
+
Time,
|
30
|
+
create_mock_engine,
|
31
|
+
)
|
15
32
|
|
16
33
|
from linkml._version import __version__
|
17
34
|
from linkml.utils.generator import Generator, shared_arguments
|
@@ -202,7 +219,7 @@ class SQLDDLGenerator(Generator):
|
|
202
219
|
generatorversion = "0.1.1"
|
203
220
|
valid_formats = ["sql"]
|
204
221
|
visit_all_class_slots: bool = True
|
205
|
-
use_inherits: bool = False
|
222
|
+
use_inherits: bool = False # postgresql supports inheritance
|
206
223
|
dialect: str
|
207
224
|
inject_primary_keys: bool = True
|
208
225
|
sqlschema: SQLSchema = SQLSchema()
|
@@ -248,7 +265,7 @@ class SQLDDLGenerator(Generator):
|
|
248
265
|
if self._is_hidden(cls):
|
249
266
|
return False
|
250
267
|
if cls.description:
|
251
|
-
None
|
268
|
+
None # TODO
|
252
269
|
tname = self._class_name_to_table(cls.name)
|
253
270
|
# add table
|
254
271
|
self.sqlschema.tables[tname] = SQLTable(name=tname, mapped_to=cls)
|
@@ -261,7 +278,7 @@ class SQLDDLGenerator(Generator):
|
|
261
278
|
# postgresql supports inheritance
|
262
279
|
# if you want to use plain SQL DDL then use sqlutils to unfold hierarchy
|
263
280
|
# TODO: raise error if the target is standard SQL
|
264
|
-
raise Exception(
|
281
|
+
raise Exception("PostgreSQL Inheritance not yet supported")
|
265
282
|
|
266
283
|
def visit_class_slot(
|
267
284
|
self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition
|
@@ -320,17 +337,11 @@ class SQLDDLGenerator(Generator):
|
|
320
337
|
linktable_name = f"{table.name}_{sqlcol.name}"
|
321
338
|
backref_col_name = "backref_id"
|
322
339
|
linktable = SQLTable(name=linktable_name)
|
323
|
-
linktable.add_column(
|
324
|
-
SQLColumn(name=backref_col_name, foreign_key=table_pk)
|
325
|
-
)
|
340
|
+
linktable.add_column(SQLColumn(name=backref_col_name, foreign_key=table_pk))
|
326
341
|
linktable.add_column(sqlcol)
|
327
342
|
sqlschema.add_table(linktable)
|
328
343
|
table.remove_column(sqlcol)
|
329
|
-
if (
|
330
|
-
not is_primitive
|
331
|
-
and table_pk is not None
|
332
|
-
and len(ref.referenced_by) == 1
|
333
|
-
):
|
344
|
+
if not is_primitive and table_pk is not None and len(ref.referenced_by) == 1:
|
334
345
|
# e.g. user->addresses
|
335
346
|
backref_col_name = f"{table.name}_{table_pk.name}"
|
336
347
|
backref_col = SQLColumn(name=backref_col_name, foreign_key=table_pk)
|
@@ -392,12 +403,9 @@ class SQLDDLGenerator(Generator):
|
|
392
403
|
def dump(sql, *multiparams, **params):
|
393
404
|
print(f"{str(sql.compile(dialect=engine.dialect)).rstrip()};")
|
394
405
|
|
395
|
-
engine = create_mock_engine(
|
396
|
-
f"{self.dialect}://./MyDb", strategy="mock", executor=dump
|
397
|
-
)
|
406
|
+
engine = create_mock_engine(f"{self.dialect}://./MyDb", strategy="mock", executor=dump)
|
398
407
|
schema_metadata = MetaData()
|
399
408
|
for t in self.sqlschema.tables.values():
|
400
|
-
cls = t.mapped_to
|
401
409
|
sqlcols = t.columns.values()
|
402
410
|
if len(sqlcols) > 0:
|
403
411
|
cols = []
|
@@ -415,14 +423,14 @@ class SQLDDLGenerator(Generator):
|
|
415
423
|
)
|
416
424
|
|
417
425
|
cols.append(col)
|
418
|
-
|
426
|
+
Table(t.name, schema_metadata, *cols)
|
419
427
|
print()
|
420
428
|
schema_metadata.create_all(engine)
|
421
429
|
|
422
430
|
def write_sqla_python_imperative(self, model_path: str) -> str:
|
423
431
|
"""
|
424
432
|
imperative mapping:
|
425
|
-
https://docs.sqlalchemy.org/en/14/orm/
|
433
|
+
https://docs.sqlalchemy.org/en/14/orm/dataclasses.html#mapping-dataclasses-using-imperative-mapping
|
426
434
|
|
427
435
|
maps to the python classes generated using PythonGenerator
|
428
436
|
|
@@ -491,8 +499,8 @@ from {model_path} import *
|
|
491
499
|
backref_slot_range = camelcase(backref_slot.range)
|
492
500
|
print(
|
493
501
|
f"""
|
494
|
-
'{underscore(original_col.mapped_to_alias)}':
|
495
|
-
relationship({backref_slot_range},
|
502
|
+
'{underscore(original_col.mapped_to_alias)}':
|
503
|
+
relationship({backref_slot_range},
|
496
504
|
foreign_keys={backref.backref_column.table.as_var()}.columns["{backref.backref_column.name}"],
|
497
505
|
backref='{cn}'),
|
498
506
|
"""
|
@@ -527,7 +535,7 @@ Python import header for generated sql-alchemy code
|
|
527
535
|
default=False,
|
528
536
|
show_default=True,
|
529
537
|
help="""
|
530
|
-
Map classes directly to
|
538
|
+
Map classes directly to
|
531
539
|
""",
|
532
540
|
)
|
533
541
|
@click.option(
|
linkml/generators/sqltablegen.py
CHANGED
@@ -1,22 +1,18 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
from dataclasses import dataclass, field
|
4
|
-
from typing import
|
4
|
+
from typing import Optional
|
5
5
|
|
6
6
|
import click
|
7
7
|
from linkml_runtime.dumpers import yaml_dumper
|
8
|
-
from linkml_runtime.linkml_model import
|
9
|
-
SlotDefinition)
|
8
|
+
from linkml_runtime.linkml_model import SchemaDefinition, SlotDefinition
|
10
9
|
from linkml_runtime.utils.formatutils import camelcase, underscore
|
11
10
|
from linkml_runtime.utils.schemaview import SchemaView
|
12
|
-
from sqlalchemy import Column, ForeignKey, MetaData, Table, create_mock_engine
|
13
|
-
from sqlalchemy.types import
|
14
|
-
Text, Time)
|
11
|
+
from sqlalchemy import Column, ForeignKey, MetaData, Table, UniqueConstraint, create_mock_engine
|
12
|
+
from sqlalchemy.types import Boolean, Date, DateTime, Enum, Float, Integer, Text, Time
|
15
13
|
|
16
14
|
from linkml._version import __version__
|
17
|
-
from linkml.
|
18
|
-
from linkml.transformers.relmodel_transformer import (
|
19
|
-
ForeignKeyPolicy, RelationalModelTransformer)
|
15
|
+
from linkml.transformers.relmodel_transformer import ForeignKeyPolicy, RelationalModelTransformer
|
20
16
|
from linkml.utils.generator import Generator, shared_arguments
|
21
17
|
from linkml.utils.schemaloader import SchemaLoader
|
22
18
|
|
@@ -122,10 +118,11 @@ class SQLTableGenerator(Generator):
|
|
122
118
|
generatorname = os.path.basename(__file__)
|
123
119
|
generatorversion = "0.1.1"
|
124
120
|
valid_formats = ["sql"]
|
121
|
+
file_extension = "sql"
|
125
122
|
uses_schemaloader = False
|
126
123
|
|
127
124
|
# ObjectVars
|
128
|
-
use_inherits: bool = False
|
125
|
+
use_inherits: bool = False # postgresql supports inheritance
|
129
126
|
dialect: str = field(default_factory=lambda: "sqlite")
|
130
127
|
inject_primary_keys: bool = field(default_factory=lambda: True)
|
131
128
|
use_foreign_keys: bool = field(default_factory=lambda: True)
|
@@ -133,6 +130,9 @@ class SQLTableGenerator(Generator):
|
|
133
130
|
direct_mapping: bool = field(default_factory=lambda: False)
|
134
131
|
relative_slot_num: bool = field(default_factory=lambda: 0)
|
135
132
|
|
133
|
+
def serialize(self, **kwargs) -> str:
|
134
|
+
return self.generate_ddl(**kwargs)
|
135
|
+
|
136
136
|
def generate_ddl(self, naming_policy: SqlNamingPolicy = None, **kwargs) -> str:
|
137
137
|
ddl_str = ""
|
138
138
|
|
@@ -140,9 +140,7 @@ class SQLTableGenerator(Generator):
|
|
140
140
|
nonlocal ddl_str
|
141
141
|
ddl_str += f"{str(sql.compile(dialect=engine.dialect)).rstrip()};"
|
142
142
|
|
143
|
-
engine = create_mock_engine(
|
144
|
-
f"{self.dialect}://./MyDb", strategy="mock", executor=dump
|
145
|
-
)
|
143
|
+
engine = create_mock_engine(f"{self.dialect}://./MyDb", strategy="mock", executor=dump)
|
146
144
|
schema_metadata = MetaData()
|
147
145
|
sqltr = RelationalModelTransformer(SchemaView(self.schema))
|
148
146
|
if not self.use_foreign_keys:
|
@@ -168,7 +166,8 @@ class SQLTableGenerator(Generator):
|
|
168
166
|
return ""
|
169
167
|
return txt.replace("\n", "")
|
170
168
|
|
171
|
-
# Currently SQLite dialect in SQLA does not
|
169
|
+
# Currently SQLite dialect in SQLA does not generate comments; see
|
170
|
+
# https://github.com/sqlalchemy/sqlalchemy/issues/1546#issuecomment-1067389172
|
172
171
|
# As a workaround we add these as "--" comments via direct string manipulation
|
173
172
|
include_comments = self.dialect == "sqlite"
|
174
173
|
sv = SchemaView(schema)
|
@@ -197,10 +196,15 @@ class SQLTableGenerator(Generator):
|
|
197
196
|
nullable=not s.required,
|
198
197
|
)
|
199
198
|
if include_comments:
|
200
|
-
ddl_str +=
|
199
|
+
ddl_str += (
|
200
|
+
f"-- * Slot: {sn} Description: {strip_newlines(s.description)}\n"
|
201
|
+
)
|
201
202
|
if s.description:
|
202
203
|
col.comment = s.description
|
203
204
|
cols.append(col)
|
205
|
+
for uc_name, uc in c.unique_keys.items():
|
206
|
+
sql_uc = UniqueConstraint(*[sql_name(sn) for sn in uc.unique_key_slots])
|
207
|
+
cols.append(sql_uc)
|
204
208
|
Table(sql_name(cn), schema_metadata, *cols, comment=str(c.description))
|
205
209
|
schema_metadata.create_all(engine)
|
206
210
|
return ddl_str
|
@@ -235,9 +239,7 @@ class SQLTableGenerator(Generator):
|
|
235
239
|
if range_base in RANGEMAP:
|
236
240
|
return RANGEMAP[range_base]
|
237
241
|
else:
|
238
|
-
logging.error(
|
239
|
-
f"UNKNOWN range base: {range_base} for {slot.name} = {slot.range}"
|
240
|
-
)
|
242
|
+
logging.error(f"UNKNOWN range base: {range_base} for {slot.name} = {slot.range}")
|
241
243
|
return Text()
|
242
244
|
|
243
245
|
def get_foreign_key(self, cn: str, sv: SchemaView) -> str:
|
@@ -271,9 +273,7 @@ class SQLTableGenerator(Generator):
|
|
271
273
|
"--relmodel-output",
|
272
274
|
help="Path to intermediate LinkML YAML of transformed relational model",
|
273
275
|
)
|
274
|
-
@click.option(
|
275
|
-
"--python-import", help="Python import header for generated sql-alchemy code"
|
276
|
-
)
|
276
|
+
@click.option("--python-import", help="Python import header for generated sql-alchemy code")
|
277
277
|
@click.option(
|
278
278
|
"--use-foreign-keys/--no-use-foreign-keys",
|
279
279
|
default=True,
|
linkml/generators/sssomgen.py
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
import os
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from datetime import date
|
4
|
-
from typing import Optional, TextIO, Union
|
5
4
|
|
6
5
|
import click
|
7
|
-
from linkml_runtime.linkml_model.meta import (
|
8
|
-
|
9
|
-
|
6
|
+
from linkml_runtime.linkml_model.meta import (
|
7
|
+
LINKML,
|
8
|
+
ClassDefinition,
|
9
|
+
Definition,
|
10
|
+
EnumDefinition,
|
11
|
+
SchemaDefinition,
|
12
|
+
SlotDefinition,
|
13
|
+
)
|
10
14
|
|
11
15
|
from linkml._version import __version__
|
12
16
|
from linkml.utils.generator import Generator, shared_arguments
|
@@ -19,6 +23,7 @@ class SSSOMGenerator(Generator):
|
|
19
23
|
"""
|
20
24
|
Generates Simple Standard for Sharing Ontology Mappings (SSSOM) TSVs
|
21
25
|
"""
|
26
|
+
|
22
27
|
# ClassVats
|
23
28
|
generatorname = os.path.basename(__file__)
|
24
29
|
generatorversion = "0.0.1"
|
@@ -62,7 +67,6 @@ class SSSOMGenerator(Generator):
|
|
62
67
|
else:
|
63
68
|
self.output_file = DEFAULT_OUTPUT_FILENAME
|
64
69
|
|
65
|
-
|
66
70
|
def make_msdf_list(self, row_as_dict: dict) -> None:
|
67
71
|
list_of_row = []
|
68
72
|
for col_name in self.msdf_columns:
|
@@ -118,11 +122,7 @@ class SSSOMGenerator(Generator):
|
|
118
122
|
for k, v in obj.permissible_values.items():
|
119
123
|
if v["meaning"]:
|
120
124
|
subject_id = (
|
121
|
-
default_prefix
|
122
|
-
+ ":"
|
123
|
-
+ subject_category
|
124
|
-
+ "#"
|
125
|
-
+ k.replace(" ", "")
|
125
|
+
default_prefix + ":" + subject_category + "#" + k.replace(" ", "")
|
126
126
|
)
|
127
127
|
subject_label = k.replace(" ", "")
|
128
128
|
object_id = obj.permissible_values[k]["meaning"]
|
@@ -164,9 +164,7 @@ class SSSOMGenerator(Generator):
|
|
164
164
|
metadata["mapping_tool"] = LINKML
|
165
165
|
metadata["creator_id"] = "linkml_user"
|
166
166
|
metadata["mapping_date"] = date.today().strftime("%Y-%m-%d")
|
167
|
-
metadata["curie_map"] = {
|
168
|
-
k: v.prefix_reference for k, v in schema.prefixes.items()
|
169
|
-
}
|
167
|
+
metadata["curie_map"] = {k: v.prefix_reference for k, v in schema.prefixes.items()}
|
170
168
|
with open(self.output_file, "w", encoding="UTF-8") as sssom_tsv:
|
171
169
|
for k, v in metadata.items():
|
172
170
|
if k != "curie_map":
|