linkml 1.8.2__py3-none-any.whl → 1.8.3__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/cli/main.py CHANGED
@@ -6,6 +6,7 @@ Gathers all the other linkml click entrypoints and puts them under ``linkml`` :)
6
6
 
7
7
  import click
8
8
 
9
+ from linkml._version import __version__
9
10
  from linkml.generators.csvgen import cli as gen_csv
10
11
  from linkml.generators.docgen import cli as gen_doc
11
12
  from linkml.generators.dotgen import cli as gen_graphviz
@@ -54,6 +55,7 @@ from linkml.workspaces.example_runner import cli as linkml_run_examples
54
55
 
55
56
 
56
57
  @click.group()
58
+ @click.version_option(__version__, "-V", "--version")
57
59
  def linkml():
58
60
  """
59
61
  LinkML: A flexible linked data modeling language
@@ -61,6 +63,7 @@ def linkml():
61
63
 
62
64
 
63
65
  @linkml.group()
66
+ @click.version_option(__version__, "-V", "--version")
64
67
  def generate():
65
68
  """
66
69
  Generate formats from a LinkML schema
@@ -68,6 +71,7 @@ def generate():
68
71
 
69
72
 
70
73
  @linkml.group()
74
+ @click.version_option(__version__, "-V", "--version")
71
75
  def dev():
72
76
  """
73
77
  Helper tools for linkml development
@@ -0,0 +1,286 @@
1
+ import abc
2
+ import re
3
+ from abc import ABC
4
+ from typing import Any, Optional
5
+
6
+ from linkml_runtime import SchemaView
7
+ from linkml_runtime.linkml_model import (
8
+ Boolean,
9
+ ClassDefinition,
10
+ Date,
11
+ Datetime,
12
+ Decimal,
13
+ Double,
14
+ EnumDefinitionName,
15
+ Float,
16
+ Integer,
17
+ SlotDefinition,
18
+ String,
19
+ Time,
20
+ Uri,
21
+ )
22
+ from linkml_runtime.linkml_model.types import (
23
+ Curie,
24
+ DateOrDatetime,
25
+ Jsonpath,
26
+ Jsonpointer,
27
+ Ncname,
28
+ Nodeidentifier,
29
+ Objectidentifier,
30
+ Sparqlpath,
31
+ Uriorcurie,
32
+ )
33
+
34
+
35
+ class IfAbsentProcessor(ABC):
36
+ """
37
+ Processes value of ifabsent slot.
38
+
39
+ See `<https://w3id.org/linkml/ifabsent>`_.
40
+ """
41
+
42
+ ifabsent_regex = re.compile("""(?:(?P<type>\w+)\()?[\"\']?(?P<default_value>[^\(\)\"\')]*)[\"\']?\)?""")
43
+
44
+ def __init__(self, schema_view: SchemaView):
45
+ self.schema_view = schema_view
46
+
47
+ def process_slot(self, slot: SlotDefinition, cls: ClassDefinition) -> Optional[str]:
48
+ if slot.ifabsent:
49
+ ifabsent_match = self.ifabsent_regex.search(slot.ifabsent)
50
+ ifabsent_default_value = ifabsent_match.group("default_value")
51
+
52
+ return self._map_to_default_value(slot, ifabsent_default_value, cls)
53
+
54
+ return None
55
+
56
+ def _map_to_default_value(
57
+ self, slot: SlotDefinition, ifabsent_default_value: Any, cls: ClassDefinition
58
+ ) -> Optional[str]:
59
+ # Used to manage specific cases that aren't generic
60
+ mapped, custom_default_value = self.map_custom_default_values(ifabsent_default_value, slot, cls)
61
+ if mapped:
62
+ return custom_default_value
63
+
64
+ if slot.range == String.type_name:
65
+ return self.map_string_default_value(ifabsent_default_value, slot, cls)
66
+
67
+ if slot.range == Boolean.type_name:
68
+ if re.match(r"^[Tt]rue$", ifabsent_default_value):
69
+ return self.map_boolean_true_default_value(slot, cls)
70
+ elif re.match(r"^[Ff]alse$", ifabsent_default_value):
71
+ return self.map_boolean_false_default_value(slot, cls)
72
+ else:
73
+ raise ValueError(
74
+ f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot does not match a valid boolean "
75
+ f"value"
76
+ )
77
+
78
+ if slot.range == Integer.type_name:
79
+ return self.map_integer_default_value(ifabsent_default_value, slot, cls)
80
+
81
+ if slot.range == Float.type_name:
82
+ return self.map_float_default_value(ifabsent_default_value, slot, cls)
83
+
84
+ if slot.range == Double.type_name:
85
+ return self.map_double_default_value(ifabsent_default_value, slot, cls)
86
+
87
+ if slot.range == Decimal.type_name:
88
+ return self.map_decimal_default_value(ifabsent_default_value, slot, cls)
89
+
90
+ if slot.range == Time.type_name:
91
+ match = re.match(r"^(\d{2}):(\d{2}):(\d{2}).*$", ifabsent_default_value)
92
+ if match:
93
+ return self.map_time_default_value(match[1], match[2], match[3], slot, cls)
94
+ else:
95
+ raise ValueError(
96
+ f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot does not match a valid time value"
97
+ )
98
+
99
+ # TODO manage timezones and offsets
100
+ if slot.range == Date.type_name:
101
+ match = re.match(r"^(\d{4})-(\d{2})-(\d{2})$", ifabsent_default_value)
102
+ if match:
103
+ return self.map_date_default_value(match[1], match[2], match[3], slot, cls)
104
+ else:
105
+ raise ValueError(
106
+ f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot does not match a valid date value"
107
+ )
108
+
109
+ # TODO manage timezones and offsets
110
+ if slot.range == Datetime.type_name:
111
+ match = re.match(r"^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).*$", ifabsent_default_value)
112
+ if match:
113
+ return self.map_datetime_default_value(
114
+ match[1], match[2], match[3], match[4], match[5], match[6], slot, cls
115
+ )
116
+ else:
117
+ raise ValueError(
118
+ f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot does not match a valid datetime "
119
+ f"value"
120
+ )
121
+
122
+ # TODO manage timezones and offsets
123
+ if slot.range == DateOrDatetime.type_name:
124
+ match = re.match(r"^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2}))?.*$", ifabsent_default_value)
125
+ if match and (match[4] is None or match[5] is None or match[6] is None):
126
+ return self.map_date_default_value(match[1], match[2], match[3], slot, cls)
127
+ elif match:
128
+ return self.map_datetime_default_value(
129
+ match[1], match[2], match[3], match[4], match[5], match[6], slot, cls
130
+ )
131
+ else:
132
+ raise ValueError(
133
+ f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot does not match a valid date or "
134
+ f"datetime value"
135
+ )
136
+
137
+ if slot.range == Uri.type_name:
138
+ return self.map_uri_default_value(ifabsent_default_value, slot, cls)
139
+
140
+ if slot.range == Curie.type_name:
141
+ return self.map_curie_default_value(ifabsent_default_value, slot, cls)
142
+
143
+ if slot.range == Uriorcurie.type_name:
144
+ return self.map_uri_or_curie_default_value(ifabsent_default_value, slot, cls)
145
+
146
+ if slot.range == Ncname.type_name:
147
+ return self.map_nc_name_default_value(ifabsent_default_value, slot, cls)
148
+
149
+ if slot.range == Objectidentifier.type_name:
150
+ return self.map_object_identifier_default_value(ifabsent_default_value, slot, cls)
151
+
152
+ if slot.range == Nodeidentifier.type_name:
153
+ return self.map_node_identifier_default_value(ifabsent_default_value, slot, cls)
154
+
155
+ if slot.range == Jsonpointer.type_name:
156
+ return self.map_json_pointer_default_value(ifabsent_default_value, slot, cls)
157
+
158
+ if slot.range == Jsonpath.type_name:
159
+ return self.map_json_path_default_value(ifabsent_default_value, slot, cls)
160
+
161
+ if slot.range == Sparqlpath.type_name:
162
+ return self.map_sparql_path_default_value(ifabsent_default_value, slot, cls)
163
+
164
+ # -----------------------
165
+ # Enum slot ranges
166
+ # -----------------------
167
+
168
+ for enum_name, enum in self.schema_view.all_enums().items():
169
+ if enum_name == slot.range:
170
+ for permissible_value_name, permissible_value in enum.permissible_values.items():
171
+ if permissible_value_name == ifabsent_default_value:
172
+ return self.map_enum_default_value(enum_name, permissible_value_name, slot, cls)
173
+
174
+ raise ValueError(f"The ifabsent value `{slot.ifabsent}` of the `{slot.name}` slot could not be processed")
175
+
176
+ @abc.abstractmethod
177
+ def map_custom_default_values(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition) -> (bool, str):
178
+ """
179
+ Maps custom default values that aren't generic behaviours.
180
+
181
+ @param default_value: the default value extracted from the ifabsent attribute
182
+ @param slot: the definition of the slot
183
+ @param cls: the definition of the class
184
+ @return: a boolean that indicates if the value has been mapped followed by the mapped value
185
+ """
186
+ return False, None
187
+
188
+ @abc.abstractmethod
189
+ def map_string_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
190
+ raise NotImplementedError()
191
+
192
+ @abc.abstractmethod
193
+ def map_integer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
194
+ raise NotImplementedError()
195
+
196
+ @abc.abstractmethod
197
+ def map_boolean_true_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
198
+ raise NotImplementedError()
199
+
200
+ @abc.abstractmethod
201
+ def map_boolean_false_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
202
+ raise NotImplementedError()
203
+
204
+ @abc.abstractmethod
205
+ def map_float_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
206
+ raise NotImplementedError()
207
+
208
+ @abc.abstractmethod
209
+ def map_double_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
210
+ raise NotImplementedError()
211
+
212
+ @abc.abstractmethod
213
+ def map_decimal_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
214
+ raise NotImplementedError()
215
+
216
+ @abc.abstractmethod
217
+ def map_time_default_value(self, hour: str, minutes: str, seconds: str, slot: SlotDefinition, cls: ClassDefinition):
218
+ raise NotImplementedError()
219
+
220
+ @abc.abstractmethod
221
+ def map_date_default_value(self, year: str, month: str, day: str, slot: SlotDefinition, cls: ClassDefinition):
222
+ raise NotImplementedError()
223
+
224
+ @abc.abstractmethod
225
+ def map_datetime_default_value(
226
+ self,
227
+ year: str,
228
+ month: str,
229
+ day: str,
230
+ hour: str,
231
+ minutes: str,
232
+ seconds: str,
233
+ slot: SlotDefinition,
234
+ cls: ClassDefinition,
235
+ ):
236
+ raise NotImplementedError()
237
+
238
+ @abc.abstractmethod
239
+ def map_uri_or_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
240
+ raise NotImplementedError()
241
+
242
+ @abc.abstractmethod
243
+ def map_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
244
+ raise NotImplementedError()
245
+
246
+ @abc.abstractmethod
247
+ def map_uri_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
248
+ raise NotImplementedError()
249
+
250
+ @abc.abstractmethod
251
+ def map_nc_name_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
252
+ raise NotImplementedError()
253
+
254
+ @abc.abstractmethod
255
+ def map_object_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
256
+ raise NotImplementedError()
257
+
258
+ @abc.abstractmethod
259
+ def map_node_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
260
+ raise NotImplementedError()
261
+
262
+ @abc.abstractmethod
263
+ def map_json_pointer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
264
+ raise NotImplementedError()
265
+
266
+ @abc.abstractmethod
267
+ def map_json_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
268
+ raise NotImplementedError()
269
+
270
+ @abc.abstractmethod
271
+ def map_sparql_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
272
+ raise NotImplementedError()
273
+
274
+ @abc.abstractmethod
275
+ def map_enum_default_value(
276
+ self, enum_name: EnumDefinitionName, permissible_value_name: str, slot: SlotDefinition, cls: ClassDefinition
277
+ ):
278
+ raise NotImplementedError()
279
+
280
+ def _uri_for(self, s: str) -> str:
281
+ uri = str(self.schema_view.namespaces().uri_for(s))
282
+ return self.schema_view.namespaces().curie_for(uri, True, True) or self._strval(uri)
283
+
284
+ def _strval(self, txt: str) -> str:
285
+ txt = str(txt).replace('"', '\\"')
286
+ return f'"{txt}"'
@@ -21,7 +21,6 @@ from linkml.utils.generator import Generator, shared_arguments
21
21
  URI_RANGES = (SHEX.nonliteral, SHEX.bnode, SHEX.iri)
22
22
 
23
23
  ENUM_CONTEXT = {
24
- "@vocab": "@null",
25
24
  "text": "skos:notation",
26
25
  "description": "skos:prefLabel",
27
26
  "meaning": "@id",
@@ -41,9 +41,9 @@ from linkml.generators.pydanticgen.template import (
41
41
  PydanticModule,
42
42
  PydanticTemplateModel,
43
43
  )
44
+ from linkml.generators.python.python_ifabsent_processor import PythonIfAbsentProcessor
44
45
  from linkml.utils import deprecation_warning
45
46
  from linkml.utils.generator import shared_arguments
46
- from linkml.utils.ifabsent_functions import ifabsent_value_declaration
47
47
 
48
48
  if int(PYDANTIC_VERSION[0]) == 1:
49
49
  deprecation_warning("pydantic-v1")
@@ -67,7 +67,9 @@ def _get_pyrange(t: TypeDefinition, sv: SchemaView) -> str:
67
67
  DEFAULT_IMPORTS = (
68
68
  Imports()
69
69
  + Import(module="__future__", objects=[ObjectImport(name="annotations")])
70
- + Import(module="datetime", objects=[ObjectImport(name="datetime"), ObjectImport(name="date")])
70
+ + Import(
71
+ module="datetime", objects=[ObjectImport(name="datetime"), ObjectImport(name="date"), ObjectImport(name="time")]
72
+ )
71
73
  + Import(module="decimal", objects=[ObjectImport(name="Decimal")])
72
74
  + Import(module="enum", objects=[ObjectImport(name="Enum")])
73
75
  + Import(module="re")
@@ -526,6 +528,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
526
528
  """
527
529
  if self._predefined_slot_values is None:
528
530
  sv = self.schemaview
531
+ ifabsent_processor = PythonIfAbsentProcessor(sv)
529
532
  slot_values = defaultdict(dict)
530
533
  for class_def in sv.all_classes().values():
531
534
  for slot_name in sv.class_slots(class_def.name):
@@ -541,10 +544,10 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
541
544
  slot.name
542
545
  ]
543
546
  elif slot.ifabsent is not None:
544
- value = ifabsent_value_declaration(slot.ifabsent, sv, class_def, slot)
547
+ value = ifabsent_processor.process_slot(slot, class_def)
545
548
  slot_values[camelcase(class_def.name)][slot.name] = value
546
549
 
547
- self._predefined_slot_values = slot_values
550
+ self._predefined_slot_values = slot_values
548
551
 
549
552
  return self._predefined_slot_values
550
553
 
@@ -1061,7 +1064,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
1061
1064
  # interpret all imported schema paths as relative to that
1062
1065
  output_path.parent.mkdir(parents=True, exist_ok=True)
1063
1066
  serialized = generator.serialize(rendered_module=rendered)
1064
- with open(output_path, "w") as ofile:
1067
+ with open(output_path, "w", encoding="utf-8") as ofile:
1065
1068
  ofile.write(serialized)
1066
1069
 
1067
1070
  results.append(
@@ -1080,7 +1083,7 @@ class PydanticGenerator(OOCodeGenerator, LifecycleMixin):
1080
1083
  rel_path = _import_to_path(generated_import.module)
1081
1084
  abs_path = (output_path.parent / rel_path).resolve()
1082
1085
  abs_path.parent.mkdir(parents=True, exist_ok=True)
1083
- with open(abs_path, "w") as ofile:
1086
+ with open(abs_path, "w", encoding="utf-8") as ofile:
1084
1087
  ofile.write(serialized)
1085
1088
 
1086
1089
  results.append(
@@ -1123,7 +1126,7 @@ def _ensure_inits(paths: List[Path]):
1123
1126
  common_path = Path(os.path.commonpath(paths))
1124
1127
 
1125
1128
  if not (ipath := (common_path / "__init__.py")).exists():
1126
- with open(ipath, "w") as ifile:
1129
+ with open(ipath, "w", encoding="utf-8") as ifile:
1127
1130
  ifile.write(" \n")
1128
1131
 
1129
1132
  for path in paths:
@@ -1131,7 +1134,7 @@ def _ensure_inits(paths: List[Path]):
1131
1134
  path = path.parent
1132
1135
  while path != common_path:
1133
1136
  if not (ipath := (path / "__init__.py")).exists():
1134
- with open(ipath, "w") as ifile:
1137
+ with open(ipath, "w", encoding="utf-8") as ifile:
1135
1138
  ifile.write(" \n")
1136
1139
  path = path.parent
1137
1140
 
@@ -0,0 +1 @@
1
+ __all__ = ["python_ifabsent_processor"]
@@ -0,0 +1,92 @@
1
+ from linkml_runtime.linkml_model import (
2
+ ClassDefinition,
3
+ EnumDefinitionName,
4
+ SlotDefinition,
5
+ )
6
+
7
+ from linkml.generators.common.ifabsent_processor import IfAbsentProcessor
8
+
9
+
10
+ class PythonIfAbsentProcessor(IfAbsentProcessor):
11
+ UNIMPLEMENTED_DEFAULT_VALUES = ["class_curie", "class_uri", "slot_uri", "slot_curie", "default_range", "default_ns"]
12
+
13
+ def map_custom_default_values(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition) -> (bool, str):
14
+ if default_value in self.UNIMPLEMENTED_DEFAULT_VALUES:
15
+ return True, None
16
+
17
+ if default_value == "bnode":
18
+ return True, "bnode()"
19
+
20
+ return False, None
21
+
22
+ def map_string_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
23
+ return self._strval(default_value)
24
+
25
+ def map_boolean_true_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
26
+ return "True"
27
+
28
+ def map_boolean_false_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
29
+ return "False"
30
+
31
+ def map_integer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
32
+ return default_value
33
+
34
+ def map_float_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
35
+ return default_value
36
+
37
+ def map_double_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
38
+ return default_value
39
+
40
+ def map_decimal_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
41
+ return default_value
42
+
43
+ def map_time_default_value(self, hour: str, minutes: str, seconds: str, slot: SlotDefinition, cls: ClassDefinition):
44
+ return f"time({int(hour)}, {int(minutes)}, {int(seconds)})"
45
+
46
+ def map_date_default_value(self, year: str, month: str, day: str, slot: SlotDefinition, cls: ClassDefinition):
47
+ return f"date({int(year)}, {int(month)}, {int(day)})"
48
+
49
+ def map_datetime_default_value(
50
+ self,
51
+ year: str,
52
+ month: str,
53
+ day: str,
54
+ hour: str,
55
+ minutes: str,
56
+ seconds: str,
57
+ slot: SlotDefinition,
58
+ cls: ClassDefinition,
59
+ ):
60
+ return f"datetime({int(year)}, {int(month)}, {int(day)}, " f"{int(hour)}, {int(minutes)}, {int(seconds)})"
61
+
62
+ def map_uri_or_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
63
+ return self._uri_for(default_value)
64
+
65
+ def map_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
66
+ return self._uri_for(default_value)
67
+
68
+ def map_uri_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
69
+ return self._uri_for(default_value)
70
+
71
+ def map_enum_default_value(
72
+ self, enum_name: EnumDefinitionName, permissible_value_name: str, slot: SlotDefinition, cls: ClassDefinition
73
+ ):
74
+ return f"{enum_name}.{permissible_value_name}"
75
+
76
+ def map_nc_name_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
77
+ raise NotImplementedError()
78
+
79
+ def map_object_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
80
+ raise NotImplementedError()
81
+
82
+ def map_node_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
83
+ raise NotImplementedError()
84
+
85
+ def map_json_pointer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
86
+ raise NotImplementedError()
87
+
88
+ def map_json_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
89
+ raise NotImplementedError()
90
+
91
+ def map_sparql_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
92
+ raise NotImplementedError()
@@ -29,8 +29,8 @@ from rdflib import URIRef
29
29
 
30
30
  import linkml
31
31
  from linkml._version import __version__
32
+ from linkml.generators.python.python_ifabsent_processor import PythonIfAbsentProcessor
32
33
  from linkml.utils.generator import Generator, shared_arguments
33
- from linkml.utils.ifabsent_functions import ifabsent_postinit_declaration, ifabsent_value_declaration
34
34
 
35
35
 
36
36
  @dataclass
@@ -70,6 +70,7 @@ class PythonGenerator(Generator):
70
70
  self.schema = str(self.schema)
71
71
  self.sourcefile = self.schema
72
72
  self.schemaview = SchemaView(self.schema, base_dir=self.base_dir)
73
+ self.ifabsent_processor = PythonIfAbsentProcessor(self.schemaview)
73
74
  super().__post_init__()
74
75
  if self.format is None:
75
76
  self.format = self.valid_formats[0]
@@ -153,7 +154,7 @@ import re
153
154
  from jsonasobj2 import JsonObj, as_dict
154
155
  from typing import Optional, List, Union, Dict, ClassVar, Any
155
156
  from dataclasses import dataclass
156
- from datetime import date, datetime
157
+ from datetime import date, datetime, time
157
158
  {enumimports}
158
159
  from linkml_runtime.utils.slot import Slot
159
160
  from linkml_runtime.utils.metamodelcore import empty_list, empty_dict, bnode
@@ -515,7 +516,7 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
515
516
 
516
517
  return "\n\t".join(initializers)
517
518
 
518
- def gen_class_variable(self, cls: ClassDefinition, slot: SlotDefinition, can_be_positional: bool) -> str:
519
+ def gen_class_variable(self, cls: ClassDefinition, slot: SlotDefinition, can_be_positional: bool = False) -> str:
519
520
  """
520
521
  Generate a class variable declaration for the supplied slot. Note: the can_be_positional attribute works,
521
522
  but it makes tag/value lists unduly complex, as you can't load them with tag=..., value=... -- you HAVE
@@ -527,12 +528,9 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
527
528
  :param can_be_positional: True means that positional parameters are allowed.
528
529
  :return: Initializer string
529
530
  """
530
- can_be_positional = False # Force everything to be tag values
531
531
  slotname = self.slot_name(slot.name)
532
532
  slot_range, default_val = self.range_cardinality(slot, cls, can_be_positional)
533
- ifabsent_text = (
534
- ifabsent_value_declaration(slot.ifabsent, self, cls, slot) if slot.ifabsent is not None else None
535
- )
533
+ ifabsent_text = self.ifabsent_processor.process_slot(slot, cls) if slot.ifabsent is not None else None
536
534
  if ifabsent_text is not None:
537
535
  default = f"= {ifabsent_text}"
538
536
  else:
@@ -637,15 +635,6 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
637
635
 
638
636
  def gen_postinits(self, cls: ClassDefinition) -> str:
639
637
  """Generate all the typing and existence checks post initialize"""
640
- post_inits_pre_super = []
641
- for slot in self.domain_slots(cls):
642
- if slot.ifabsent:
643
- dflt = ifabsent_postinit_declaration(slot.ifabsent, self, cls, slot)
644
-
645
- if dflt and dflt != "None":
646
- post_inits_pre_super.append(f"if self.{self.slot_name(slot.name)} is None:")
647
- post_inits_pre_super.append(f"\tself.{self.slot_name(slot.name)} = {dflt}")
648
-
649
638
  post_inits = []
650
639
  if not (cls.mixin or cls.abstract):
651
640
  pkeys = self.primary_keys_for(cls)
@@ -676,20 +665,17 @@ dataclasses._init_fn = dataclasses_init_fn_with_kwargs
676
665
  if slot.name not in domain_slot_names and slot.designates_type:
677
666
  post_inits_designators.append(self.gen_postinit(cls, slot))
678
667
 
679
- post_inits_pre_super_line = "\n\t\t".join([p for p in post_inits_pre_super if p]) + (
680
- "\n\t\t" if post_inits_pre_super else ""
681
- )
682
668
  post_inits_post_super_line = "\n\t\t".join(post_inits_designators)
683
669
  post_inits_line = "\n\t\t".join([p for p in post_inits if p])
684
670
  return (
685
671
  (
686
672
  f"""
687
673
  def __post_init__(self, *_: List[str], **kwargs: Dict[str, Any]):
688
- {post_inits_pre_super_line}{post_inits_line}
674
+ {post_inits_line}
689
675
  super().__post_init__(**kwargs)
690
676
  {post_inits_post_super_line}"""
691
677
  )
692
- if post_inits_line or post_inits_pre_super_line or post_inits_post_super_line
678
+ if post_inits_line or post_inits_post_super_line
693
679
  else ""
694
680
  )
695
681
 
@@ -1,3 +1 @@
1
- from linkml.generators.shacl import ifabsent_processor
2
-
3
- __all__ = ["shacl_data_type", "ifabsent_processor"]
1
+ __all__ = ["shacl_data_type", "shacl_ifabsent_processor"]
@@ -20,7 +20,7 @@ class ShaclDataType(DataType, Enum):
20
20
  FLOAT = ("float", URIRef("http://www.w3.org/2001/XMLSchema#float"))
21
21
  DOUBLE = ("double", URIRef("http://www.w3.org/2001/XMLSchema#double"))
22
22
  URI = ("uri", URIRef("http://www.w3.org/2001/XMLSchema#anyURI"))
23
- CURI = ("curi", URIRef("http://www.w3.org/2001/XMLSchema#string"))
23
+ CURIE = ("curi", URIRef("http://www.w3.org/2001/XMLSchema#string"))
24
24
  NCNAME = ("ncname", URIRef("http://www.w3.org/2001/XMLSchema#string"))
25
25
  OBJECT_IDENTIFIER = ("objectidentifier", URIRef("http://www.w3.org/ns/shex#iri"))
26
26
  NODE_IDENTIFIER = ("nodeidentifier", URIRef("http://www.w3.org/ns/shex#nonLiteral"))
@@ -0,0 +1,89 @@
1
+ from linkml_runtime.linkml_model import ClassDefinition, EnumDefinitionName, SlotDefinition
2
+ from rdflib import Literal, URIRef
3
+
4
+ from linkml.generators.common.ifabsent_processor import IfAbsentProcessor
5
+ from linkml.generators.shacl.shacl_data_type import ShaclDataType
6
+
7
+
8
+ class ShaclIfAbsentProcessor(IfAbsentProcessor):
9
+
10
+ def map_custom_default_values(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition) -> (bool, str):
11
+ if default_value == "class_curie":
12
+ class_uri = self.schema_view.get_uri(cls, expand=True)
13
+ if class_uri:
14
+ return True, URIRef(class_uri)
15
+ return True, ""
16
+
17
+ return False, None
18
+
19
+ def map_enum_default_value(
20
+ self, enum_name: EnumDefinitionName, permissible_value_name: str, slot: SlotDefinition, cls: ClassDefinition
21
+ ):
22
+ return Literal(permissible_value_name)
23
+
24
+ def map_string_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
25
+ return Literal(default_value, datatype=ShaclDataType.STRING.uri_ref)
26
+
27
+ def map_integer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
28
+ return Literal(default_value, datatype=ShaclDataType.INTEGER.uri_ref)
29
+
30
+ def map_boolean_true_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
31
+ return Literal(True, datatype=ShaclDataType.BOOLEAN.uri_ref)
32
+
33
+ def map_boolean_false_default_value(self, slot: SlotDefinition, cls: ClassDefinition):
34
+ return Literal(False, datatype=ShaclDataType.BOOLEAN.uri_ref)
35
+
36
+ def map_float_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
37
+ return Literal(default_value, datatype=ShaclDataType.FLOAT.uri_ref)
38
+
39
+ def map_double_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
40
+ return Literal(default_value, datatype=ShaclDataType.DOUBLE.uri_ref)
41
+
42
+ def map_decimal_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
43
+ return Literal(default_value, datatype=ShaclDataType.DECIMAL.uri_ref)
44
+
45
+ def map_time_default_value(self, hour: str, minutes: str, seconds: str, slot: SlotDefinition, cls: ClassDefinition):
46
+ return Literal(f"{hour}:{minutes}:{seconds}", datatype=ShaclDataType.TIME.uri_ref)
47
+
48
+ def map_date_default_value(self, year: str, month: str, day: str, slot: SlotDefinition, cls: ClassDefinition):
49
+ return Literal(f"{year}-{month}-{day}", datatype=ShaclDataType.DATE.uri_ref)
50
+
51
+ def map_datetime_default_value(
52
+ self,
53
+ year: str,
54
+ month: str,
55
+ day: str,
56
+ hour: str,
57
+ minutes: str,
58
+ seconds: str,
59
+ slot: SlotDefinition,
60
+ cls: ClassDefinition,
61
+ ):
62
+ return Literal(f"{year}-{month}-{day}T{hour}:{minutes}:{seconds}", datatype=ShaclDataType.DATETIME.uri_ref)
63
+
64
+ def map_uri_or_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
65
+ raise NotImplementedError()
66
+
67
+ def map_curie_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
68
+ return Literal(default_value, datatype=ShaclDataType.CURIE.uri_ref)
69
+
70
+ def map_uri_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
71
+ return Literal(default_value, datatype=ShaclDataType.URI.uri_ref)
72
+
73
+ def map_nc_name_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
74
+ raise NotImplementedError()
75
+
76
+ def map_object_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
77
+ raise NotImplementedError()
78
+
79
+ def map_node_identifier_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
80
+ raise NotImplementedError()
81
+
82
+ def map_json_pointer_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
83
+ raise NotImplementedError()
84
+
85
+ def map_json_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
86
+ raise NotImplementedError()
87
+
88
+ def map_sparql_path_default_value(self, default_value: str, slot: SlotDefinition, cls: ClassDefinition):
89
+ raise NotImplementedError()
@@ -14,8 +14,8 @@ from rdflib.collection import Collection
14
14
  from rdflib.namespace import RDF, RDFS, SH, XSD
15
15
 
16
16
  from linkml._version import __version__
17
- from linkml.generators.shacl.ifabsent_processor import IfAbsentProcessor
18
17
  from linkml.generators.shacl.shacl_data_type import ShaclDataType
18
+ from linkml.generators.shacl.shacl_ifabsent_processor import ShaclIfAbsentProcessor
19
19
  from linkml.utils.generator import Generator, shared_arguments
20
20
 
21
21
 
@@ -56,7 +56,7 @@ class ShaclGenerator(Generator):
56
56
  g = Graph()
57
57
  g.bind("sh", SH)
58
58
 
59
- ifabsent_processor = IfAbsentProcessor(sv)
59
+ ifabsent_processor = ShaclIfAbsentProcessor(sv)
60
60
 
61
61
  for pfx in self.schema.prefixes.values():
62
62
  g.bind(str(pfx.prefix_prefix), pfx.prefix_reference)
@@ -208,7 +208,9 @@ class ShaclGenerator(Generator):
208
208
  # Map equal_string and equal_string_in to sh:in
209
209
  self._and_equals_string(g, prop_pv, s.equals_string_in)
210
210
 
211
- ifabsent_processor.process_slot(prop_pv, s, class_uri)
211
+ default_value = ifabsent_processor.process_slot(s, c)
212
+ if default_value:
213
+ prop_pv(SH.defaultValue, default_value)
212
214
 
213
215
  return g
214
216
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: linkml
3
- Version: 1.8.2
3
+ Version: 1.8.3
4
4
  Summary: Linked Open Data Modeling Language
5
5
  Home-page: https://linkml.io/linkml/
6
6
  Keywords: schema,linked data,data modeling,rdf,owl,biolink
@@ -2,12 +2,13 @@ linkml/__init__.py,sha256=0REu65rXiOk3F1CYLzq60HiSZ2DfFySfGS6QFljZ01s,3663
2
2
  linkml/_version.py,sha256=udxv6OEqcE89DTVMYPtetXYg8IA8Sgc6yW26-6f8C3M,310
3
3
  linkml/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  linkml/cli/__main__.py,sha256=oPa7Fl2DQP40UivpIWNRr1CgwT2SQM0rhE478155zBY,80
5
- linkml/cli/main.py,sha256=-VaV8Z6rhadMOIHS9kdeV30NdXsE6jG3kVpvGbkF2wY,5184
5
+ linkml/cli/main.py,sha256=83lyx36tcy95StDRtN7BmwNFCPt4As1wb0wbC3YwHKg,5386
6
6
  linkml/generators/PythonGenNotes.md,sha256=wb9BPLLhF6378OKLbwSBAwmniLpwrcT5_bbfCHfLme8,51006
7
7
  linkml/generators/README.md,sha256=RMzT8EblC_GEdPy5WyfXHDBXlFI6k6mz3Cx2sdpcyWI,4438
8
8
  linkml/generators/__init__.py,sha256=dQ2EUKy_MctEVEzbC3XzEJL5YmYwjYfsa44mG2zKn7g,1517
9
9
  linkml/generators/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  linkml/generators/common/build.py,sha256=hXf2gf1ox2hiR-D6N-qXt5w5QkISLlo1A-_GurY3yHo,2897
11
+ linkml/generators/common/ifabsent_processor.py,sha256=D8vKZLa15mc_SUyvFsE3NZfWi-c4kawYW6WdGozJLRQ,11275
11
12
  linkml/generators/common/lifecycle.py,sha256=UOJXlIyWQkvsn0Dao9_ZDzlamJtiINyVJQ6SRj66L9s,4910
12
13
  linkml/generators/common/template.py,sha256=bjcSNiC85MYl9oGhk0ZpqwZpRxxy_NqxG8XBV-TXXDY,3338
13
14
  linkml/generators/common/type_designators.py,sha256=lgVcIRJJ-yCvVGeP9U_gQZpm77UmJBQo9Bh3lGJITno,1956
@@ -32,7 +33,7 @@ linkml/generators/graphqlgen.py,sha256=-BS-UHPyHqauUgtgcFCO4xSxxogk7BPAQFSXHIrKJ
32
33
  linkml/generators/javagen/example_template.java.jinja2,sha256=ec4CVTv_0zS7V5Y-1E6H4lRraya10gfX7BEMBlu38X4,444
33
34
  linkml/generators/javagen/java_record_template.jinja2,sha256=OQZffLSy_xR3FIhQMltvrYyVeut7l2Q-tzK7AOiVmWs,1729
34
35
  linkml/generators/javagen.py,sha256=BTAXgvOOvRa3AOfaaiVFyuyaqg4XFR_JbO6_7tT_AnQ,5335
35
- linkml/generators/jsonldcontextgen.py,sha256=fZ22yjYGLMf_rQYKQSRh3IvMdGVAFhJ0PdQF8d514NI,8689
36
+ linkml/generators/jsonldcontextgen.py,sha256=J-9QF7doco3-hYReiik67PjiaF_UudrFEiVsbnJFCJg,8666
36
37
  linkml/generators/jsonldgen.py,sha256=9ru1rb4_u7t_Rp8ILQSmDIsk_evrDL_OeilnQUsu338,7770
37
38
  linkml/generators/jsonschemagen.py,sha256=XoA0HJ1XlItogmhr3F8XNnAi1UG6fXeBAKkjme48n7o,30388
38
39
  linkml/generators/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -50,7 +51,7 @@ linkml/generators/pydanticgen/array.py,sha256=mF_vhmlPfd9yVlVyANvT9GgLP6iQLSheqa
50
51
  linkml/generators/pydanticgen/black.py,sha256=BaJ7b-kMUbIN_cKRT3yCaMEbFwxzj1_U7w4fKQnkL44,723
51
52
  linkml/generators/pydanticgen/build.py,sha256=_NQzXK_wWJ-EAHBi1nqZCEWSTYy7KWkc5w-mRsEXycI,3936
52
53
  linkml/generators/pydanticgen/includes.py,sha256=kDAwonKbhTFzrFa2TrmTYuOSHXuzDy5WHgBaI5nVY6c,504
53
- linkml/generators/pydanticgen/pydanticgen.py,sha256=TCQG5O0GFT1GDsrMby79jDujWFJ3D_pXW8sUB3vw3L0,49059
54
+ linkml/generators/pydanticgen/pydanticgen.py,sha256=GpGROIR1CX876NIvVoW9dlGe1r_vQSnojC7qnM9RNrM,49239
54
55
  linkml/generators/pydanticgen/template.py,sha256=alt5yXyf2zdh93rLhyuIu9Ctb9C3O1oM62Upr4JH6Cw,21000
55
56
  linkml/generators/pydanticgen/templates/attribute.py.jinja,sha256=uMyVKpO5uQkAFlIvIBwA6FFZl_BSIotfwCorrSwMA3Q,873
56
57
  linkml/generators/pydanticgen/templates/base_model.py.jinja,sha256=6KRQ1ZVHh1uwZnYL3qxBnyit8_DNE7D-4l2-1wsRsG0,406
@@ -61,12 +62,14 @@ linkml/generators/pydanticgen/templates/footer.py.jinja,sha256=t_24p1xmqdbfxGqz_
61
62
  linkml/generators/pydanticgen/templates/imports.py.jinja,sha256=d1XFna2eOpkH8cgJML3vXwqGcocczvOcrbg6jjd4kP0,945
62
63
  linkml/generators/pydanticgen/templates/module.py.jinja,sha256=NLCYn4wgwVBCs8trV2hNq0_BkZgfIIS6zQTmjzFDJCQ,568
63
64
  linkml/generators/pydanticgen/templates/validator.py.jinja,sha256=BhXRGrT7HIweVHEE3Z7ZUtFFV8kP1dmRKJfu53jZBBQ,441
64
- linkml/generators/pythongen.py,sha256=Ti654YxzTwZRXap-vT5T7iP3NYWox0yFqdp4oHV8tNs,53289
65
+ linkml/generators/python/__init__.py,sha256=NS9fPEuAo0punDplD91F9mcViXMJJv04Q5NkFcKPNvY,40
66
+ linkml/generators/python/python_ifabsent_processor.py,sha256=a98JCbMsR_Fsdaw8s299-0jFhE0V6F4zLl326ITA7lQ,3848
67
+ linkml/generators/pythongen.py,sha256=3XOWUMEUlknkdpzGMbJrNGWPWxnhwxLHPr5anOk_m2M,52612
65
68
  linkml/generators/rdfgen.py,sha256=OT8oS8MPUVf5UK3OKvRWgAh9iQiWwdyWJSA69PncOw4,2969
66
- linkml/generators/shacl/__init__.py,sha256=O-M-wndKw8rMW-U8X3QCNHal-ErXP6uXZqxiQSa77l4,108
67
- linkml/generators/shacl/ifabsent_processor.py,sha256=kV9BGA2ZPXLRfaFuW0o4jpkATvGggvrqpAo9c1UqWNE,2193
68
- linkml/generators/shacl/shacl_data_type.py,sha256=BT3C9tdFyBQnuucPN7YQiFAKEa9yuzy-Q26X6dmOXgo,1827
69
- linkml/generators/shaclgen.py,sha256=9fp7qsKhMpplDHNIBIwfFLguYubjOEUn9th_Fvtg8zA,13283
69
+ linkml/generators/shacl/__init__.py,sha256=KgMOyESGTdKk2Vhx9uuUYgEJPuJQ-iT1vDQVIMoiXCM,58
70
+ linkml/generators/shacl/shacl_data_type.py,sha256=gX3Y2KyTVPwMyef0cJMTEpU9h0oH-H0ThVDORdNW170,1828
71
+ linkml/generators/shacl/shacl_ifabsent_processor.py,sha256=hbgt5UtVHo_z_JOLkg6ltGRdVuiv65b2PA3x1Oy4ssQ,4269
72
+ linkml/generators/shaclgen.py,sha256=JGk4X-PQadF_D-ciWKBVHuKU4QdfKewC395ujR5EsBk,13392
70
73
  linkml/generators/shexgen.py,sha256=-GFdapDEAUXAjsXdTI7ZlS39YfuvaxfzPkdqUNM19qI,9941
71
74
  linkml/generators/sparqlgen.py,sha256=ULvWokNGPADq1qDENoFeRYg3FmjdTh4-Vq-eYjsAEK4,6151
72
75
  linkml/generators/sqlalchemy/__init__.py,sha256=mb9AC1rIFkSiNZhhG0TAk45ol9PjS1XvsrvCjgfVUpQ,249
@@ -113,7 +116,6 @@ linkml/utils/deprecation.py,sha256=onhB82qjIkKzyUOTO9WDqN6QF3I2cJoyBrbm66vHmgM,8
113
116
  linkml/utils/execute_tutorial.py,sha256=KGmSnaz6joIAShnrD24n8kD6URe6CBoSUYsDOTg7qCg,7039
114
117
  linkml/utils/generator.py,sha256=nAZTBFDKAZ2Asi6f54-GSOZoP03jQKgbYCW82KA34Z4,39100
115
118
  linkml/utils/helpers.py,sha256=yR8n4zFA5wPcYC7xzRuNF3wO16vG80v6j7DM3qTNmIc,447
116
- linkml/utils/ifabsent_functions.py,sha256=IkcBcmRYu8sllx7_mTCqu4aHfTxX2AnQoccZ1KOODds,5843
117
119
  linkml/utils/logictools.py,sha256=BGtcoqr_Rsu08Ywa6lNiL881uZzQmNWk1p4FEIVKYe0,23641
118
120
  linkml/utils/mergeutils.py,sha256=QVm2iQB4v_L2rSvPBsPe9C865R03BgV3TzlPoTTTwWQ,9044
119
121
  linkml/utils/rawloader.py,sha256=nUB8Gfn8yEVwyo6bx0TtB2mBGkilC-fybi0xvQ73Gks,4417
@@ -149,8 +151,8 @@ linkml/workspaces/datamodel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
149
151
  linkml/workspaces/datamodel/workspaces.py,sha256=4HdkqweGNfMPqnB1_Onc9DcTfkhoagTRcqruh08nRoI,14905
150
152
  linkml/workspaces/datamodel/workspaces.yaml,sha256=EjVrwPpeRZqJRjuGyyDRxxFzuv55SiLIXPBRUG6HStU,4233
151
153
  linkml/workspaces/example_runner.py,sha256=qrn3EALJsHpyzQXo2npg5oTmMNQTrCjaNIAuMV0P5c8,11720
152
- linkml-1.8.2.dist-info/entry_points.txt,sha256=jvnPJ8UTca4_L-Dh8OdUm5td8I3ZFlKRhMPjqRLtaSg,2084
153
- linkml-1.8.2.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
154
- linkml-1.8.2.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
155
- linkml-1.8.2.dist-info/METADATA,sha256=rdL9CvP5g68hZnKnQcInhtIMvcI2Ip5wqBdII0le-nY,3731
156
- linkml-1.8.2.dist-info/RECORD,,
154
+ linkml-1.8.3.dist-info/entry_points.txt,sha256=jvnPJ8UTca4_L-Dh8OdUm5td8I3ZFlKRhMPjqRLtaSg,2084
155
+ linkml-1.8.3.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
156
+ linkml-1.8.3.dist-info/LICENSE,sha256=kORMoywK6j9_iy0UvLR-a80P1Rvc9AOM4gsKlUNZABg,535
157
+ linkml-1.8.3.dist-info/METADATA,sha256=riRcN5vHtkNy2v364JshSNmukypRDIFaw0B7JbQuv_E,3731
158
+ linkml-1.8.3.dist-info/RECORD,,
@@ -1,59 +0,0 @@
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")
@@ -1,138 +0,0 @@
1
- import re
2
- from typing import Callable, List, Match, Optional, Text, Tuple, Union
3
-
4
- from linkml_runtime.linkml_model.meta import ClassDefinition, SlotDefinition
5
- from linkml_runtime.utils.formatutils import sfx
6
-
7
- from linkml.utils.schemaloader import SchemaLoader
8
-
9
-
10
- def strval(txt: str) -> str:
11
- txt = str(txt).replace('"', '\\"')
12
- return f'"{txt}"'
13
-
14
-
15
- def default_uri_for(loader: SchemaLoader) -> str:
16
- dflt = loader.schema.default_prefix if loader.schema.default_prefix else sfx(loader.schema.id)
17
- return sfx(loader.namespaces.uri_for(dflt))
18
-
19
-
20
- def default_curie_or_uri(loader: SchemaLoader) -> str:
21
- dflt = loader.schema.default_prefix if loader.schema.default_prefix else sfx(loader.schema.id)
22
- if ":/" in dflt:
23
- prefix = loader.namespaces.prefix_for(loader.schema.default_prefix)
24
- if prefix:
25
- dflt = prefix
26
- return dflt
27
-
28
-
29
- def curie_for(loader: SchemaLoader, is_class: bool) -> Optional[str]:
30
- """Return the Curie for the schema in loader. Return None if there is no curie form"""
31
- prefix = default_curie_or_uri(loader)
32
- suffix = "camelcase(self.name)" if is_class else "underscore(self.alias if self.alias else self.name)"
33
- if ":/" not in prefix:
34
- return '"' + prefix + ":" + '" + ' + suffix
35
- else:
36
- pn = loader.namespaces.curie_for(prefix, default_ok=False)
37
- return ('"' + pn + '" + ' + suffix) if pn else None
38
-
39
-
40
- def uri_for(s: str, loader: SchemaLoader) -> str:
41
- uri = str(loader.namespaces.uri_for(s))
42
- return loader.namespaces.curie_for(uri, True, True) or strval(uri)
43
-
44
-
45
- def default_ns_for(loader: SchemaLoader, cls: ClassDefinition) -> str:
46
- """Return code to produce the default namespace for the supplied class"""
47
- # TODO: figure out how to mark a slot as a namespace
48
- return "sfx(str(self.id))" if "id" in cls.slots else "None"
49
- # cls_id = None
50
- # for slotname in cls.slots:
51
- # slot = loader.schema.slots[slotname]
52
- # if slot.identifier:
53
- # cls_id = slotname
54
- # return f"sfx(str(self.{cls_id}))" if cls_id else "None"
55
-
56
-
57
- # Library of named default values -- this is here to prevent code injection
58
- # Contents: Match text (as re),
59
- # flag that indicates whether we're generating a default value expression or postinig code
60
- # Function that takes the match string, SchemaLoader, ClassDefinition, and SlotDefinition and returns the
61
- # appropriate string
62
- default_library: List[
63
- Tuple[
64
- Text,
65
- bool,
66
- Callable[[Match[str], SchemaLoader, ClassDefinition, SlotDefinition], str],
67
- ]
68
- ] = [
69
- (r"[Tt]rue", False, lambda _, __, ___, ____: "True"),
70
- (r"[Ff]alse", False, lambda _, __, ___, ____: "False"),
71
- (r"int\(([-+]?[0-9]+)\)", False, lambda m, __, ___, ____: int(m[1])),
72
- (
73
- r"float\(([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\)",
74
- False,
75
- lambda m, __, ___, ____: float(m[1]),
76
- ),
77
- (
78
- r"date\((\d{4})-(\d{2})-(\d{2})\)",
79
- False,
80
- lambda m, __, ___, ____: f"date({int(m[1])}, {int(m[2])}, {int(m[3])})",
81
- ),
82
- (
83
- r"datetime\((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\)",
84
- False,
85
- lambda m, __, ___, ____: f"datetime({int(m[1])}, {int(m[2])}, {int(m[3])}, "
86
- f"{int(m[4])}, {int(m[5])}, {int(m[6])})",
87
- ),
88
- # TODO: We have to make the real URI available before any of these can work
89
- # ("class_uri", True,
90
- # lambda _, loader, ___, ____: f'"{default_uri_for(loader)}" + camelcase(self.name)'),
91
- # ("slot_uri", True,
92
- # lambda _, loader, ___, ____: f'"{default_uri_for(loader)}" +
93
- # underscore(self.alias if self.alias else self.name)'),
94
- # ("class_curie", True, lambda _, loader, ___, ____: curie_for(loader, True)),
95
- # ("slot_curie", True, lambda _, loader, ___, ____: curie_for(loader, False)),
96
- ("class_uri", True, lambda _, loader, ___, ____: "None"),
97
- ("slot_uri", True, lambda _, loader, ___, ____: "None"),
98
- ("class_curie", True, lambda _, loader, ___, ____: "None"),
99
- ("slot_curie", True, lambda _, loader, ___, ____: "None"),
100
- # See: https://github.com/linkml/linkml/issues/1333
101
- # ("class_uri", False, lambda _, __, class_definition, ____: 'class_class_uri'),
102
- # ("class_curie", False, lambda _, __, class_definition, ____: 'class_class_curie'),
103
- # ("slot_uri", True, lambda _, loader, ___, slot_definition: f'slots.{slot_definition.name}.uri'),
104
- # ("slot_curie", True, lambda _, loader, ___, slot_definition: f'slots.{slot_definition.name}.curie'),
105
- # ("default_range", False, lambda _, loader, __, ____: f"{strval(loader.schema.default_range)}"),
106
- ("default_range", False, lambda _, __, ___, ____: "None"),
107
- ("bnode", False, lambda _, __, ___, ____: "bnode()"),
108
- (r"string\((.*)\)", False, lambda m, __, ___, ____: strval(m[1])),
109
- (r"uri\((.*)\)", False, lambda m, loader, _, __: uri_for(m[1], loader)),
110
- ("default_ns", True, lambda _, loader, cls, ____: default_ns_for(loader, cls)),
111
- # ("default_ns", False, lambda _, loader, __, ____: f"{strval(loader.schema.default_prefix)}"),
112
- ]
113
-
114
-
115
- def isabsent_match(
116
- txt: Text,
117
- ) -> Union[
118
- Tuple[Match[str], bool, Callable[[Match[str], SchemaLoader, ClassDefinition, SlotDefinition], str]],
119
- Tuple[None, None, None],
120
- ]:
121
- txt = str(txt)
122
- for pattern, postinit, f in default_library:
123
- m = re.match(pattern + "$", txt)
124
- if m:
125
- return m, postinit, f
126
- raise ValueError(f"Incompatible default value: {txt}")
127
-
128
-
129
- def ifabsent_value_declaration(txt: Text, loader, cls, slot) -> Optional[str]:
130
- m, postinit, f = isabsent_match(txt)
131
- if m and not postinit:
132
- return f(m, loader, cls, slot)
133
-
134
-
135
- def ifabsent_postinit_declaration(txt: Text, loader, cls, slot) -> Optional[str]:
136
- m, postinit, f = isabsent_match(txt)
137
- if m and postinit:
138
- return f(m, loader, cls, slot)
File without changes