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.
Files changed (44) hide show
  1. linkml/generators/__init__.py +2 -0
  2. linkml/generators/docgen/class.md.jinja2 +15 -2
  3. linkml/generators/docgen/slot.md.jinja2 +18 -4
  4. linkml/generators/docgen.py +17 -3
  5. linkml/generators/jsonldcontextgen.py +40 -17
  6. linkml/generators/jsonldgen.py +3 -1
  7. linkml/generators/owlgen.py +16 -0
  8. linkml/generators/prefixmapgen.py +5 -4
  9. linkml/generators/projectgen.py +14 -2
  10. linkml/generators/pydanticgen/__init__.py +29 -0
  11. linkml/generators/pydanticgen/array.py +457 -0
  12. linkml/generators/pydanticgen/black.py +29 -0
  13. linkml/generators/pydanticgen/build.py +79 -0
  14. linkml/generators/{pydanticgen.py → pydanticgen/pydanticgen.py} +252 -304
  15. linkml/generators/pydanticgen/template.py +577 -0
  16. linkml/generators/pydanticgen/templates/attribute.py.jinja +10 -0
  17. linkml/generators/pydanticgen/templates/base_model.py.jinja +29 -0
  18. linkml/generators/pydanticgen/templates/class.py.jinja +21 -0
  19. linkml/generators/pydanticgen/templates/conditional_import.py.jinja +9 -0
  20. linkml/generators/pydanticgen/templates/enum.py.jinja +16 -0
  21. linkml/generators/pydanticgen/templates/footer.py.jinja +13 -0
  22. linkml/generators/pydanticgen/templates/imports.py.jinja +31 -0
  23. linkml/generators/pydanticgen/templates/module.py.jinja +27 -0
  24. linkml/generators/pydanticgen/templates/validator.py.jinja +15 -0
  25. linkml/generators/pythongen.py +13 -7
  26. linkml/generators/shacl/__init__.py +3 -0
  27. linkml/generators/shacl/ifabsent_processor.py +59 -0
  28. linkml/generators/shacl/shacl_data_type.py +40 -0
  29. linkml/generators/shaclgen.py +105 -82
  30. linkml/generators/shexgen.py +1 -1
  31. linkml/generators/sqlalchemygen.py +1 -1
  32. linkml/generators/sqltablegen.py +32 -22
  33. linkml/generators/terminusdbgen.py +7 -1
  34. linkml/linter/config/datamodel/config.py +8 -0
  35. linkml/linter/rules.py +11 -2
  36. linkml/utils/generator.py +7 -6
  37. linkml/utils/ifabsent_functions.py +7 -9
  38. linkml/utils/schemaloader.py +1 -9
  39. linkml/utils/sqlutils.py +39 -25
  40. {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/METADATA +9 -4
  41. {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/RECORD +44 -27
  42. {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/LICENSE +0 -0
  43. {linkml-1.7.5.dist-info → linkml-1.7.7.dist-info}/WHEEL +0 -0
  44. {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
@@ -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 = default_curie_or_uri(self)
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,3 @@
1
+ from linkml.generators.shacl import ifabsent_processor
2
+
3
+ __all__ = ["shacl_data_type", "ifabsent_processor"]
@@ -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
@@ -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
- prop_pv_literal(SH.hasValue, s.equals_number)
120
- r = s.range
121
- if r in sv.all_classes():
122
- range_ref = sv.get_uri(r, expand=True)
123
- prop_pv(SH["class"], URIRef(range_ref))
124
- if sv.get_identifier_slot(r) is not None:
125
- prop_pv(SH.nodeKind, SH.IRI)
126
- else:
127
- prop_pv(SH.nodeKind, SH.BlankNode)
128
- elif r in sv.all_types().values():
129
- rt = sv.get_type(r)
130
- if rt.uri:
131
- prop_pv(SH.datatype, rt.uri)
132
- else:
133
- logging.error(f"No URI for type {rt.name}")
134
- elif r in sv.all_enums():
135
- e = sv.get_enum(r)
136
- pv_node = BNode()
137
- Collection(
138
- g,
139
- pv_node,
140
- [
141
- URIRef(sv.expand_curie(pv.meaning)) if pv.meaning else Literal(pv_name)
142
- for pv_name, pv in e.permissible_values.items()
143
- ],
144
- )
145
- prop_pv(SH["in"], pv_node)
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
- if r == "string":
148
- prop_pv(SH.datatype, LINK_ML_TYPES_STRING)
149
- elif r == "boolean":
150
- prop_pv(SH.datatype, LINK_ML_TYPES_BOOL)
151
- elif r == "duration":
152
- prop_pv(SH.datatype, LINK_ML_TYPES_DURATION)
153
- elif r == "datetime":
154
- prop_pv(SH.datatype, LINK_ML_TYPES_DATETIME)
155
- elif r == "date":
156
- prop_pv(SH.datatype, LINK_ML_TYPES_DATE)
157
- elif r == "time":
158
- prop_pv(SH.datatype, LINK_ML_TYPES_TIME)
159
- elif r == "datetime":
160
- prop_pv(SH.datatype, LINK_ML_TYPES_DATE_TIME)
161
- elif r == "decimal":
162
- prop_pv(SH.datatype, LINK_ML_TYPES_DECIMAL)
163
- elif r == "integer":
164
- prop_pv(SH.datatype, LINK_ML_TYPES_INTEGER)
165
- elif r == "float":
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(
@@ -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":
@@ -40,7 +40,7 @@ class SQLAlchemyGenerator(Generator):
40
40
  """
41
41
  Generates SQL Alchemy classes
42
42
 
43
- See also: :ref:`SQLTableGenerator`
43
+ See also: :class:`~linkml.generators.sqltablegen.SQLTableGenerator`
44
44
  """
45
45
 
46
46
  # ClassVars
@@ -61,19 +61,19 @@ RANGEMAP = {
61
61
  @dataclass
62
62
  class SQLTableGenerator(Generator):
63
63
  """
64
- A :ref:`Generator` for creating SQL DDL
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
- - 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)
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
- - inline objects are modeled as Text strings
76
- - multivalued fields are modeled as single Text strings
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
- Each mapped slot C.S has a range R
99
-
100
- ranges that are types (literals):
101
- - If R is a type, and the slot is NOT multivalued, do a direct type conversion
102
- - If R is a type, and the slot is multivalued:
103
- * do not include the mapped column
104
- * create a new table T_S, with 2 columns: S, and a backref to T
105
- ranges that are classes:
106
- Ref = map_class_to_table(R)
107
- - if R is a class, and the slot is NOT multivalued, and Ref has a singular primary key:
108
- * Col.type = ForeignKey(Ref.PK)
109
- - if R is a class, and the slot is NOT multivalued, and Ref has NO singular primary key:
110
- * add a foreign key C.pk to Ref
111
- * add a backref C.S => Ref, C.pk
112
- * remove Col from T
113
- - If R is a class, and the slot IS multivalued
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
- from terminusdb_client.woqlquery import WOQLQuery as WQ
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 = self.PATTERNS["uppercamel"]
223
- slot_pattern = self.PATTERNS["snake"]
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"]