linkml 1.7.5__py3-none-any.whl → 1.7.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/generators/__init__.py +2 -0
- linkml/generators/docgen/class.md.jinja2 +15 -2
- linkml/generators/docgen/slot.md.jinja2 +18 -4
- linkml/generators/docgen.py +17 -3
- linkml/generators/jsonldcontextgen.py +40 -17
- linkml/generators/jsonldgen.py +3 -1
- linkml/generators/owlgen.py +16 -0
- linkml/generators/prefixmapgen.py +5 -4
- linkml/generators/projectgen.py +14 -2
- linkml/generators/pydanticgen/__init__.py +29 -0
- linkml/generators/pydanticgen/array.py +457 -0
- linkml/generators/pydanticgen/black.py +29 -0
- linkml/generators/pydanticgen/build.py +79 -0
- linkml/generators/{pydanticgen.py → pydanticgen/pydanticgen.py} +252 -304
- linkml/generators/pydanticgen/template.py +577 -0
- linkml/generators/pydanticgen/templates/attribute.py.jinja +10 -0
- linkml/generators/pydanticgen/templates/base_model.py.jinja +29 -0
- linkml/generators/pydanticgen/templates/class.py.jinja +21 -0
- linkml/generators/pydanticgen/templates/conditional_import.py.jinja +9 -0
- linkml/generators/pydanticgen/templates/enum.py.jinja +16 -0
- linkml/generators/pydanticgen/templates/footer.py.jinja +13 -0
- linkml/generators/pydanticgen/templates/imports.py.jinja +31 -0
- linkml/generators/pydanticgen/templates/module.py.jinja +27 -0
- linkml/generators/pydanticgen/templates/validator.py.jinja +15 -0
- linkml/generators/pythongen.py +13 -7
- linkml/generators/shacl/__init__.py +3 -0
- linkml/generators/shacl/ifabsent_processor.py +59 -0
- linkml/generators/shacl/shacl_data_type.py +40 -0
- linkml/generators/shaclgen.py +105 -82
- linkml/generators/shexgen.py +1 -1
- linkml/generators/sqlalchemygen.py +1 -1
- linkml/generators/sqltablegen.py +32 -22
- linkml/generators/terminusdbgen.py +7 -1
- linkml/linter/config/datamodel/config.py +8 -0
- linkml/linter/rules.py +11 -2
- linkml/utils/generator.py +7 -6
- linkml/utils/ifabsent_functions.py +7 -9
- linkml/utils/schemaloader.py +1 -9
- linkml/utils/sqlutils.py +39 -25
- {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/METADATA +9 -4
- {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/RECORD +44 -27
- {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/LICENSE +0 -0
- {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/WHEEL +0 -0
- {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
{% macro import_(module, alias=None, objects = None) %}
|
2
|
+
{%- if objects is none and alias is none %}
|
3
|
+
import {{ module }}
|
4
|
+
{%- elif objects is none and alias is string %}
|
5
|
+
import {{ module }} as {{ alias }}
|
6
|
+
{%- else %}
|
7
|
+
{% if objects | length == 1 %}
|
8
|
+
from {{ module }} import {{ objects[0]['name'] }} {% if objects[0]['alias'] is not none %} as {{ objects[0]['alias'] }} {% endif %}
|
9
|
+
{%- else %}
|
10
|
+
from {{ module }} import (
|
11
|
+
{% for object in objects %}
|
12
|
+
{% if object['alias'] is string %}
|
13
|
+
{{ object['name'] }} as {{ object['alias'] }}
|
14
|
+
{%- else %}
|
15
|
+
{{ object['name'] }}
|
16
|
+
{%- endif %}
|
17
|
+
{% if not loop.last %},{{ '\n' }}{% else %}{{ '\n' }}{% endif %}
|
18
|
+
{% endfor %}
|
19
|
+
)
|
20
|
+
{%- endif %}
|
21
|
+
{%- endif %}
|
22
|
+
{% endmacro %}
|
23
|
+
{%- if module %}
|
24
|
+
{{ import_(module, alias, objects) }}
|
25
|
+
{% endif -%}
|
26
|
+
{#- For when used with Imports container -#}
|
27
|
+
{%- if imports -%}
|
28
|
+
{%- for i in imports -%}
|
29
|
+
{{ i }}
|
30
|
+
{%- endfor -%}
|
31
|
+
{% endif -%}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
{% for import in imports %}
|
2
|
+
{{ import }}
|
3
|
+
{%- endfor -%}
|
4
|
+
|
5
|
+
metamodel_version = "{{metamodel_version}}"
|
6
|
+
version = "{{version if version else None}}"
|
7
|
+
|
8
|
+
|
9
|
+
{{ base_model }}
|
10
|
+
{% if enums %}
|
11
|
+
{% for e in enums.values() %}
|
12
|
+
|
13
|
+
{{ e }}
|
14
|
+
{% endfor %}
|
15
|
+
{% endif %}
|
16
|
+
{% if injected_classes %}
|
17
|
+
{% for c in injected_classes%}
|
18
|
+
|
19
|
+
{{ c }}
|
20
|
+
{% endfor %}
|
21
|
+
{% endif %}
|
22
|
+
{% for c in classes.values() %}
|
23
|
+
|
24
|
+
{{ c }}
|
25
|
+
{% endfor %}
|
26
|
+
|
27
|
+
{% include 'footer.py.jinja' %}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{% if pydantic_ver == 1 %}
|
2
|
+
@validator('{{name}}', allow_reuse=True)
|
3
|
+
{% else %}
|
4
|
+
@field_validator('{{name}}')
|
5
|
+
{% endif %}
|
6
|
+
def pattern_{{name}}(cls, v):
|
7
|
+
pattern=re.compile(r"{{pattern}}")
|
8
|
+
if isinstance(v,list):
|
9
|
+
for element in v:
|
10
|
+
if not pattern.match(element):
|
11
|
+
raise ValueError(f"Invalid {{name}} format: {element}")
|
12
|
+
elif isinstance(v,str):
|
13
|
+
if not pattern.match(v):
|
14
|
+
raise ValueError(f"Invalid {{name}} format: {v}")
|
15
|
+
return v
|
linkml/generators/pythongen.py
CHANGED
@@ -36,11 +36,7 @@ from rdflib import URIRef
|
|
36
36
|
import linkml
|
37
37
|
from linkml._version import __version__
|
38
38
|
from linkml.utils.generator import Generator, shared_arguments
|
39
|
-
from linkml.utils.ifabsent_functions import
|
40
|
-
default_curie_or_uri,
|
41
|
-
ifabsent_postinit_declaration,
|
42
|
-
ifabsent_value_declaration,
|
43
|
-
)
|
39
|
+
from linkml.utils.ifabsent_functions import ifabsent_postinit_declaration, ifabsent_value_declaration
|
44
40
|
|
45
41
|
|
46
42
|
@dataclass
|
@@ -67,7 +63,7 @@ class PythonGenerator(Generator):
|
|
67
63
|
|
68
64
|
def __post_init__(self) -> None:
|
69
65
|
self.sourcefile = self.schema
|
70
|
-
self.schemaview = SchemaView(self.schema)
|
66
|
+
self.schemaview = SchemaView(self.schema, base_dir=self.base_dir)
|
71
67
|
super().__post_init__()
|
72
68
|
if self.format is None:
|
73
69
|
self.format = self.valid_formats[0]
|
@@ -151,6 +147,7 @@ import re
|
|
151
147
|
from jsonasobj2 import JsonObj, as_dict
|
152
148
|
from typing import Optional, List, Union, Dict, ClassVar, Any
|
153
149
|
from dataclasses import dataclass
|
150
|
+
from datetime import date, datetime
|
154
151
|
{enumimports}
|
155
152
|
from linkml_runtime.utils.slot import Slot
|
156
153
|
from linkml_runtime.utils.metamodelcore import empty_list, empty_dict, bnode
|
@@ -294,7 +291,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
294
291
|
return rval.values()
|
295
292
|
|
296
293
|
def gen_namespaces(self) -> str:
|
297
|
-
dflt_prefix =
|
294
|
+
dflt_prefix = self._default_curie_or_uri()
|
298
295
|
dflt = f"CurieNamespace('', '{sfx(dflt_prefix)}')" if ":/" in dflt_prefix else dflt_prefix.upper()
|
299
296
|
curienamespace_defs = [
|
300
297
|
{
|
@@ -641,6 +638,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
|
|
641
638
|
for slot in self.domain_slots(cls):
|
642
639
|
if slot.ifabsent:
|
643
640
|
dflt = ifabsent_postinit_declaration(slot.ifabsent, self, cls, slot)
|
641
|
+
|
644
642
|
if dflt and dflt != "None":
|
645
643
|
post_inits_pre_super.append(f"if self.{self.slot_name(slot.name)} is None:")
|
646
644
|
post_inits_pre_super.append(f"\tself.{self.slot_name(slot.name)} = {dflt}")
|
@@ -1170,6 +1168,14 @@ class {enum_name}(EnumDefinitionImpl):
|
|
1170
1168
|
|
1171
1169
|
return f'{prefix_string}"""{string}"""'
|
1172
1170
|
|
1171
|
+
def _default_curie_or_uri(self) -> str:
|
1172
|
+
dflt = self.schema.default_prefix if self.schema.default_prefix else sfx(self.schema.id)
|
1173
|
+
if ":/" in dflt:
|
1174
|
+
prefix = self.namespaces.prefix_for(self.schema.default_prefix)
|
1175
|
+
if prefix:
|
1176
|
+
dflt = prefix
|
1177
|
+
return dflt
|
1178
|
+
|
1173
1179
|
|
1174
1180
|
@shared_arguments(PythonGenerator)
|
1175
1181
|
@click.command()
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Any, Callable, Optional
|
3
|
+
|
4
|
+
from linkml_runtime import SchemaView
|
5
|
+
from linkml_runtime.linkml_model import SlotDefinition
|
6
|
+
from rdflib import SH, Literal, URIRef
|
7
|
+
from rdflib.term import Identifier
|
8
|
+
|
9
|
+
from linkml.generators.shacl.shacl_data_type import ShaclDataType
|
10
|
+
|
11
|
+
|
12
|
+
class IfAbsentProcessor:
|
13
|
+
"""
|
14
|
+
Processes value of ifabsent slot.
|
15
|
+
|
16
|
+
See `<https://w3id.org/linkml/ifabsent>`_.
|
17
|
+
|
18
|
+
TODO: unify this with ifabsent_functions
|
19
|
+
"""
|
20
|
+
|
21
|
+
ifabsent_regex = re.compile("""(?:(?P<type>\w+)\()?[\"\']?(?P<default_value>[^\(\)\"\')]*)[\"\']?\)?""")
|
22
|
+
|
23
|
+
def __init__(self, schema_view: SchemaView):
|
24
|
+
self.schema_view = schema_view
|
25
|
+
|
26
|
+
def process_slot(
|
27
|
+
self, add_prop: Callable[[URIRef, Identifier], None], slot: SlotDefinition, class_uri: Optional[URIRef] = None
|
28
|
+
) -> None:
|
29
|
+
if slot.ifabsent:
|
30
|
+
ifabsent_match = self.ifabsent_regex.search(slot.ifabsent)
|
31
|
+
ifabsent_default_value = ifabsent_match.group("default_value")
|
32
|
+
|
33
|
+
self._map_to_default_value(slot, add_prop, ifabsent_default_value, class_uri)
|
34
|
+
|
35
|
+
def _map_to_default_value(
|
36
|
+
self,
|
37
|
+
slot: SlotDefinition,
|
38
|
+
add_prop: Callable[[URIRef, Identifier], None],
|
39
|
+
ifabsent_default_value: Any,
|
40
|
+
class_uri: Optional[URIRef] = None,
|
41
|
+
) -> None:
|
42
|
+
for datatype in list(ShaclDataType):
|
43
|
+
if datatype.linkml_type == slot.range:
|
44
|
+
add_prop(SH.defaultValue, Literal(ifabsent_default_value, datatype=datatype.uri_ref))
|
45
|
+
return
|
46
|
+
|
47
|
+
for enum_name, enum in self.schema_view.all_enums().items():
|
48
|
+
if enum_name == slot.range:
|
49
|
+
for permissible_value_name, permissible_value in enum.permissible_values.items():
|
50
|
+
if permissible_value_name == ifabsent_default_value:
|
51
|
+
add_prop(SH.defaultValue, Literal(ifabsent_default_value))
|
52
|
+
return
|
53
|
+
|
54
|
+
if ifabsent_default_value == "class_curie":
|
55
|
+
if class_uri:
|
56
|
+
add_prop(SH.defaultValue, class_uri)
|
57
|
+
return
|
58
|
+
|
59
|
+
raise ValueError(f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot could not be processed")
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
from rdflib import URIRef
|
4
|
+
|
5
|
+
|
6
|
+
class DataType:
|
7
|
+
linkml_type: str
|
8
|
+
uri_ref: URIRef
|
9
|
+
|
10
|
+
|
11
|
+
class ShaclDataType(DataType, Enum):
|
12
|
+
STRING = ("string", URIRef("http://www.w3.org/2001/XMLSchema#string"))
|
13
|
+
BOOLEAN = ("boolean", URIRef("http://www.w3.org/2001/XMLSchema#boolean"))
|
14
|
+
DURATION = ("duration", URIRef("http://www.w3.org/2001/XMLSchema#duration"))
|
15
|
+
DATETIME = ("datetime", URIRef("http://www.w3.org/2001/XMLSchema#dateTime"))
|
16
|
+
DATE = ("date", URIRef("http://www.w3.org/2001/XMLSchema#date"))
|
17
|
+
TIME = ("time", URIRef("http://www.w3.org/2001/XMLSchema#time"))
|
18
|
+
DECIMAL = ("decimal", URIRef("http://www.w3.org/2001/XMLSchema#decimal"))
|
19
|
+
INTEGER = ("integer", URIRef("http://www.w3.org/2001/XMLSchema#integer"))
|
20
|
+
FLOAT = ("float", URIRef("http://www.w3.org/2001/XMLSchema#float"))
|
21
|
+
DOUBLE = ("double", URIRef("http://www.w3.org/2001/XMLSchema#double"))
|
22
|
+
URI = ("uri", URIRef("http://www.w3.org/2001/XMLSchema#anyURI"))
|
23
|
+
CURI = ("curi", URIRef("http://www.w3.org/2001/XMLSchema#string"))
|
24
|
+
NCNAME = ("ncname", URIRef("http://www.w3.org/2001/XMLSchema#string"))
|
25
|
+
OBJECT_IDENTIFIER = ("objectidentifier", URIRef("http://www.w3.org/ns/shex#iri"))
|
26
|
+
NODE_IDENTIFIER = ("nodeidentifier", URIRef("http://www.w3.org/ns/shex#nonLiteral"))
|
27
|
+
JSON_POINTER = ("jsonpointer", URIRef("http://www.w3.org/2001/XMLSchema#string"))
|
28
|
+
JSON_PATH = ("jsonpath", URIRef("http://www.w3.org/2001/XMLSchema#string"))
|
29
|
+
SPARQL_PATH = ("sparqlpath", URIRef("http://www.w3.org/2001/XMLSchema#string"))
|
30
|
+
|
31
|
+
def __new__(cls, linkml_type, uri_ref):
|
32
|
+
obj = object.__new__(cls)
|
33
|
+
obj.linkml_type = linkml_type
|
34
|
+
obj.uri_ref = uri_ref
|
35
|
+
|
36
|
+
return obj
|
37
|
+
|
38
|
+
def __init__(self, linkml_type, uri_ref):
|
39
|
+
self.linkml_type = linkml_type
|
40
|
+
self.uri_ref = uri_ref
|
linkml/generators/shaclgen.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
from dataclasses import dataclass
|
4
|
+
from typing import Callable
|
4
5
|
|
5
6
|
import click
|
7
|
+
from linkml_runtime.linkml_model.meta import ElementName
|
6
8
|
from linkml_runtime.utils.formatutils import underscore
|
7
9
|
from linkml_runtime.utils.schemaview import SchemaView
|
8
10
|
from rdflib import BNode, Graph, Literal, URIRef
|
@@ -10,23 +12,10 @@ from rdflib.collection import Collection
|
|
10
12
|
from rdflib.namespace import RDF, SH
|
11
13
|
|
12
14
|
from linkml._version import __version__
|
15
|
+
from linkml.generators.shacl.ifabsent_processor import IfAbsentProcessor
|
16
|
+
from linkml.generators.shacl.shacl_data_type import ShaclDataType
|
13
17
|
from linkml.utils.generator import Generator, shared_arguments
|
14
18
|
|
15
|
-
LINK_ML_TYPES_STRING = URIRef("http://www.w3.org/2001/XMLSchema#string")
|
16
|
-
LINK_ML_TYPES_BOOL = URIRef("http://www.w3.org/2001/XMLSchema#boolean")
|
17
|
-
LINK_ML_TYPES_DECIMAL = URIRef("http://www.w3.org/2001/XMLSchema#decimal")
|
18
|
-
LINK_ML_TYPES_INTEGER = URIRef("http://www.w3.org/2001/XMLSchema#integer")
|
19
|
-
LINK_ML_TYPES_FLOAT = URIRef("http://www.w3.org/2001/XMLSchema#float")
|
20
|
-
LINK_ML_TYPES_DOUBLE = URIRef("http://www.w3.org/2001/XMLSchema#double")
|
21
|
-
LINK_ML_TYPES_DURATION = URIRef("http://www.w3.org/2001/XMLSchema#duration")
|
22
|
-
LINK_ML_TYPES_DATETIME = URIRef("http://www.w3.org/2001/XMLSchema#dateTime")
|
23
|
-
LINK_ML_TYPES_DATE = URIRef("http://www.w3.org/2001/XMLSchema#date")
|
24
|
-
LINK_ML_TYPES_TIME = URIRef("http://www.w3.org/2001/XMLSchema#time")
|
25
|
-
LINK_ML_TYPES_DATE_TIME = URIRef("http://www.w3.org/2001/XMLSchema#datetime")
|
26
|
-
LINK_ML_TYPES_URI = URIRef("http://www.w3.org/2001/XMLSchema#anyURI")
|
27
|
-
LINK_ML_TYPES_OBJECT_ID = URIRef("http://www.w3.org/ns/shex#iri")
|
28
|
-
LINK_ML_TYPES_NODE_ID = URIRef("http://www.w3.org/ns/shex#nonLiteral")
|
29
|
-
|
30
19
|
|
31
20
|
@dataclass
|
32
21
|
class ShaclGenerator(Generator):
|
@@ -60,6 +49,9 @@ class ShaclGenerator(Generator):
|
|
60
49
|
sv = self.schemaview
|
61
50
|
g = Graph()
|
62
51
|
g.bind("sh", SH)
|
52
|
+
|
53
|
+
ifabsent_processor = IfAbsentProcessor(sv)
|
54
|
+
|
63
55
|
for pfx in self.schema.prefixes.values():
|
64
56
|
g.bind(str(pfx.prefix_prefix), pfx.prefix_reference)
|
65
57
|
|
@@ -116,76 +108,107 @@ class ShaclGenerator(Generator):
|
|
116
108
|
prop_pv_literal(SH.minCount, 1)
|
117
109
|
prop_pv_literal(SH.minInclusive, s.minimum_value)
|
118
110
|
prop_pv_literal(SH.maxInclusive, s.maximum_value)
|
119
|
-
|
120
|
-
|
121
|
-
if
|
122
|
-
|
123
|
-
prop_pv(SH["
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
111
|
+
|
112
|
+
all_classes = sv.all_classes()
|
113
|
+
if s.any_of:
|
114
|
+
or_node = BNode()
|
115
|
+
prop_pv(SH["or"], or_node)
|
116
|
+
range_list = []
|
117
|
+
for any in s.any_of:
|
118
|
+
r = any.range
|
119
|
+
if r in all_classes:
|
120
|
+
class_node = BNode()
|
121
|
+
|
122
|
+
def cl_node_pv(p, v):
|
123
|
+
if v is not None:
|
124
|
+
g.add((class_node, p, v))
|
125
|
+
|
126
|
+
self._add_class(cl_node_pv, r)
|
127
|
+
range_list.append(class_node)
|
128
|
+
elif r in sv.all_types().values():
|
129
|
+
t_node = BNode()
|
130
|
+
|
131
|
+
def t_node_pv(p, v):
|
132
|
+
if v is not None:
|
133
|
+
g.add((t_node, p, v))
|
134
|
+
|
135
|
+
self._add_type(t_node_pv, r)
|
136
|
+
range_list.append(t_node)
|
137
|
+
elif r in sv.all_enums():
|
138
|
+
en_node = BNode()
|
139
|
+
|
140
|
+
def en_node_pv(p, v):
|
141
|
+
if v is not None:
|
142
|
+
g.add((en_node, p, v))
|
143
|
+
|
144
|
+
self._add_enum(g, en_node_pv, r)
|
145
|
+
range_list.append(en_node)
|
146
|
+
else:
|
147
|
+
st_node = BNode()
|
148
|
+
|
149
|
+
def st_node_pv(p, v):
|
150
|
+
if v is not None:
|
151
|
+
g.add((st_node, p, v))
|
152
|
+
|
153
|
+
add_simple_data_type(st_node_pv, r)
|
154
|
+
range_list.append(st_node)
|
155
|
+
Collection(g, or_node, range_list)
|
156
|
+
|
146
157
|
else:
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
prop_pv
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
elif r
|
156
|
-
prop_pv
|
157
|
-
elif r
|
158
|
-
|
159
|
-
|
160
|
-
prop_pv
|
161
|
-
|
162
|
-
prop_pv(SH.
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_FLOAT)
|
167
|
-
elif r == "double":
|
168
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_DOUBLE)
|
169
|
-
elif r == "uri":
|
170
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_URI)
|
171
|
-
elif r == "curi":
|
172
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_STRING)
|
173
|
-
elif r == "ncname":
|
174
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_STRING)
|
175
|
-
elif r == "objectidentifier":
|
176
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_OBJECT_ID)
|
177
|
-
elif r == "nodeidentifier":
|
178
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_NODE_ID)
|
179
|
-
elif r == "jsonpointer":
|
180
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_STRING)
|
181
|
-
elif r == "jsonpath":
|
182
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_STRING)
|
183
|
-
elif r == "sparqlpath":
|
184
|
-
prop_pv(SH.datatype, LINK_ML_TYPES_STRING)
|
185
|
-
if s.pattern:
|
186
|
-
prop_pv(SH.pattern, Literal(s.pattern))
|
158
|
+
prop_pv_literal(SH.hasValue, s.equals_number)
|
159
|
+
r = s.range
|
160
|
+
if r in all_classes:
|
161
|
+
self._add_class(prop_pv, r)
|
162
|
+
if sv.get_identifier_slot(r) is not None:
|
163
|
+
prop_pv(SH.nodeKind, SH.IRI)
|
164
|
+
else:
|
165
|
+
prop_pv(SH.nodeKind, SH.BlankNodeOrIRI)
|
166
|
+
elif r in sv.all_types().values():
|
167
|
+
self._add_type(prop_pv, r)
|
168
|
+
elif r in sv.all_enums():
|
169
|
+
self._add_enum(g, prop_pv, r)
|
170
|
+
else:
|
171
|
+
add_simple_data_type(prop_pv, r)
|
172
|
+
if s.pattern:
|
173
|
+
prop_pv(SH.pattern, Literal(s.pattern))
|
174
|
+
|
175
|
+
ifabsent_processor.process_slot(prop_pv, s, class_uri)
|
176
|
+
|
187
177
|
return g
|
188
178
|
|
179
|
+
def _add_class(self, func: Callable, r: ElementName) -> None:
|
180
|
+
sv = self.schemaview
|
181
|
+
range_ref = sv.get_uri(r, expand=True)
|
182
|
+
func(SH["class"], URIRef(range_ref))
|
183
|
+
|
184
|
+
def _add_enum(self, g: Graph, func: Callable, r: ElementName) -> None:
|
185
|
+
sv = self.schemaview
|
186
|
+
enum = sv.get_enum(r)
|
187
|
+
pv_node = BNode()
|
188
|
+
Collection(
|
189
|
+
g,
|
190
|
+
pv_node,
|
191
|
+
[
|
192
|
+
URIRef(sv.expand_curie(pv.meaning)) if pv.meaning else Literal(pv_name)
|
193
|
+
for pv_name, pv in enum.permissible_values.items()
|
194
|
+
],
|
195
|
+
)
|
196
|
+
func(SH["in"], pv_node)
|
197
|
+
|
198
|
+
def _add_type(self, func: Callable, r: ElementName) -> None:
|
199
|
+
sv = self.schemaview
|
200
|
+
rt = sv.get_type(r)
|
201
|
+
if rt.uri:
|
202
|
+
func(SH.datatype, rt.uri)
|
203
|
+
else:
|
204
|
+
logging.error(f"No URI for type {rt.name}")
|
205
|
+
|
206
|
+
|
207
|
+
def add_simple_data_type(func: Callable, r: ElementName) -> None:
|
208
|
+
for datatype in list(ShaclDataType):
|
209
|
+
if datatype.linkml_type == r:
|
210
|
+
func(SH.datatype, datatype.uri_ref)
|
211
|
+
|
189
212
|
|
190
213
|
@shared_arguments(ShaclGenerator)
|
191
214
|
@click.option(
|
linkml/generators/shexgen.py
CHANGED
@@ -165,7 +165,7 @@ class ShExGenerator(Generator):
|
|
165
165
|
shex = as_json_1(self.shex)
|
166
166
|
if self.format == "rdf":
|
167
167
|
g = Graph()
|
168
|
-
g.parse(data=shex, format="json-ld")
|
168
|
+
g.parse(data=shex, format="json-ld", version="1.1")
|
169
169
|
g.bind("owl", OWL)
|
170
170
|
shex = g.serialize(format="turtle")
|
171
171
|
elif self.format == "shex":
|
linkml/generators/sqltablegen.py
CHANGED
@@ -61,19 +61,19 @@ RANGEMAP = {
|
|
61
61
|
@dataclass
|
62
62
|
class SQLTableGenerator(Generator):
|
63
63
|
"""
|
64
|
-
A :
|
64
|
+
A :class:`~linkml.utils.generator.Generator` for creating SQL DDL
|
65
65
|
|
66
66
|
The basic algorithm for mapping a linkml schema S is as follows:
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
- Each schema S corresponds to one database schema D (see SQLSchema)
|
69
|
+
- Each Class C in S is mapped to a table T (see SQLTable)
|
70
|
+
- Each slot S in each C is mapped to a column Col (see SQLColumn)
|
71
71
|
|
72
72
|
if the direct_mapping attribute is set to true, then no further transformations
|
73
73
|
are applied. Note that this means:
|
74
74
|
|
75
|
-
|
76
|
-
|
75
|
+
- inline objects are modeled as Text strings
|
76
|
+
- multivalued fields are modeled as single Text strings
|
77
77
|
|
78
78
|
this direct mapping is useful for simple spreadsheet/denormalized representations of complex data.
|
79
79
|
however, for other applications, additional transformations should occur. these are:
|
@@ -95,22 +95,30 @@ class SQLTableGenerator(Generator):
|
|
95
95
|
E.g. if a class User has a multivalues slot alias whose range is a string,
|
96
96
|
then create a table user_aliases, with two columns (1) alias [a string] and (2) a backref to user
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
98
|
+
Each mapped slot C.S has a range R
|
99
|
+
|
100
|
+
ranges that are types (literals):
|
101
|
+
|
102
|
+
* If R is a type, and the slot is NOT multivalued, do a direct type conversion
|
103
|
+
* If R is a type, and the slot is multivalued:
|
104
|
+
|
105
|
+
* do not include the mapped column
|
106
|
+
* create a new table T_S, with 2 columns: S, and a backref to T
|
107
|
+
|
108
|
+
ranges that are classes:
|
109
|
+
|
110
|
+
* Ref = map_class_to_table(R)
|
111
|
+
* if R is a class, and the slot is NOT multivalued, and Ref has a singular primary key:
|
112
|
+
|
113
|
+
* Col.type = ForeignKey(Ref.PK)
|
114
|
+
|
115
|
+
* if R is a class, and the slot is NOT multivalued, and Ref has NO singular primary key:
|
116
|
+
|
117
|
+
* add a foreign key C.pk to Ref
|
118
|
+
* add a backref C.S => Ref, C.pk
|
119
|
+
* remove Col from T
|
120
|
+
|
121
|
+
* If R is a class, and the slot IS multivalued
|
114
122
|
|
115
123
|
"""
|
116
124
|
|
@@ -236,6 +244,8 @@ class SQLTableGenerator(Generator):
|
|
236
244
|
range_base = METAMODEL_TYPE_TO_BASE[range]
|
237
245
|
elif range in schema.types:
|
238
246
|
range_base = schema.types[range].base
|
247
|
+
elif range is None:
|
248
|
+
return Text()
|
239
249
|
else:
|
240
250
|
logging.error(f"Unknown range: {range} for {slot.name} = {slot.range}")
|
241
251
|
return Text()
|
@@ -6,7 +6,13 @@ from typing import List
|
|
6
6
|
import click
|
7
7
|
from linkml_runtime.linkml_model.meta import ClassDefinition, SlotDefinition
|
8
8
|
from linkml_runtime.utils.formatutils import be, camelcase, underscore
|
9
|
-
|
9
|
+
|
10
|
+
try:
|
11
|
+
from terminusdb_client.woqlquery import WOQLQuery as WQ
|
12
|
+
except ImportError:
|
13
|
+
import warnings
|
14
|
+
|
15
|
+
warnings.warn("terminusdb_client is not a requirement of this package, please install it separately")
|
10
16
|
|
11
17
|
from linkml._version import __version__
|
12
18
|
from linkml.utils.generator import Generator, shared_arguments
|
@@ -232,11 +232,19 @@ class StandardNamingConfig(RuleConfig):
|
|
232
232
|
|
233
233
|
level: Union[str, "RuleLevel"] = None
|
234
234
|
permissible_values_upper_case: Optional[Union[bool, Bool]] = None
|
235
|
+
slot_pattern: Optional[str] = None
|
236
|
+
class_pattern: Optional[str] = None
|
235
237
|
|
236
238
|
def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
|
237
239
|
if self.permissible_values_upper_case is not None and not isinstance(self.permissible_values_upper_case, Bool):
|
238
240
|
self.permissible_values_upper_case = Bool(self.permissible_values_upper_case)
|
239
241
|
|
242
|
+
if self.class_pattern is not None and not isinstance(self.class_pattern, str):
|
243
|
+
self.class_pattern = str(self.class_pattern)
|
244
|
+
|
245
|
+
if self.slot_pattern is not None and not isinstance(self.slot_pattern, str):
|
246
|
+
self.slot_pattern = str(self.slot_pattern)
|
247
|
+
|
240
248
|
super().__post_init__(**kwargs)
|
241
249
|
|
242
250
|
|
linkml/linter/rules.py
CHANGED
@@ -219,8 +219,17 @@ class StandardNamingRule(LinterRule):
|
|
219
219
|
self.config = config
|
220
220
|
|
221
221
|
def check(self, schema_view: SchemaView, fix: bool = False) -> Iterable[LinterProblem]:
|
222
|
-
class_pattern =
|
223
|
-
|
222
|
+
class_pattern = (
|
223
|
+
self.PATTERNS["uppercamel"]
|
224
|
+
if not self.config.class_pattern
|
225
|
+
else self.PATTERNS.get(self.config.class_pattern, re.compile(self.config.class_pattern))
|
226
|
+
)
|
227
|
+
slot_pattern = (
|
228
|
+
self.PATTERNS["snake"]
|
229
|
+
if not self.config.slot_pattern
|
230
|
+
else self.PATTERNS.get(self.config.slot_pattern, re.compile(self.config.slot_pattern))
|
231
|
+
)
|
232
|
+
|
224
233
|
enum_pattern = self.PATTERNS["uppercamel"]
|
225
234
|
permissible_value_pattern = (
|
226
235
|
self.PATTERNS["uppersnake"] if self.config.permissible_values_upper_case else self.PATTERNS["snake"]
|