FourCIPP 1.13.0__py3-none-any.whl → 1.15.0__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.
- fourcipp/config/4C_metadata.yaml +1 -16
- fourcipp/config/4C_schema.json +3688 -3706
- fourcipp/fourc_input.py +3 -3
- fourcipp/legacy_io/__init__.py +1 -1
- fourcipp/legacy_io/element.py +1 -1
- fourcipp/legacy_io/inline_dat.py +8 -6
- fourcipp/legacy_io/particle.py +1 -1
- fourcipp/utils/cli.py +1 -1
- fourcipp/utils/configuration.py +1 -1
- fourcipp/utils/metadata.py +994 -9
- fourcipp/utils/not_set.py +21 -8
- fourcipp/utils/{typing.py → type_hinting.py} +5 -0
- fourcipp/utils/yaml_io.py +1 -1
- fourcipp/version.py +2 -2
- {fourcipp-1.13.0.dist-info → fourcipp-1.15.0.dist-info}/METADATA +1 -1
- fourcipp-1.15.0.dist-info/RECORD +28 -0
- fourcipp-1.13.0.dist-info/RECORD +0 -28
- {fourcipp-1.13.0.dist-info → fourcipp-1.15.0.dist-info}/WHEEL +0 -0
- {fourcipp-1.13.0.dist-info → fourcipp-1.15.0.dist-info}/entry_points.txt +0 -0
- {fourcipp-1.13.0.dist-info → fourcipp-1.15.0.dist-info}/licenses/LICENSE +0 -0
- {fourcipp-1.13.0.dist-info → fourcipp-1.15.0.dist-info}/top_level.txt +0 -0
fourcipp/utils/metadata.py
CHANGED
|
@@ -19,14 +19,999 @@
|
|
|
19
19
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
21
|
# THE SOFTWARE.
|
|
22
|
-
"""Metadata
|
|
22
|
+
"""Metadata objects from the 4C input metadata."""
|
|
23
23
|
|
|
24
|
-
import
|
|
24
|
+
from __future__ import annotations
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
import abc
|
|
27
|
+
import re
|
|
28
|
+
from collections.abc import Generator, Sequence
|
|
29
|
+
from typing import Callable, Literal, Protocol, TypeAlias, TypeVar
|
|
30
|
+
|
|
31
|
+
from fourcipp.utils.not_set import NotSet, check_if_set
|
|
32
|
+
from fourcipp.utils.type_hinting import NotSetAlias
|
|
33
|
+
|
|
34
|
+
PRIMITIVES_PYTHON_TYPES = float | bool | int | str
|
|
35
|
+
|
|
36
|
+
ValidatorAlias: TypeAlias = Callable[[object], bool] | None
|
|
37
|
+
|
|
38
|
+
NotSetString: NotSetAlias[str] = NotSet(str)
|
|
39
|
+
|
|
40
|
+
T = TypeVar("T", int, float)
|
|
41
|
+
G = TypeVar("G", contravariant=True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class InputSpec:
|
|
45
|
+
"""Input spec base class."""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
spec_type: str,
|
|
50
|
+
name: NotSetAlias[str] = NotSetString,
|
|
51
|
+
description: NotSetAlias[str] = NotSetString,
|
|
52
|
+
required: bool = False,
|
|
53
|
+
noneable: bool = False,
|
|
54
|
+
validator: ValidatorAlias = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Initialise InputSpec.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
spec_type (str): Type of input spec
|
|
60
|
+
name: Name
|
|
61
|
+
description: Description
|
|
62
|
+
required: True if parameter is required
|
|
63
|
+
noneable: True if parameter can be None
|
|
64
|
+
validator: Validator callable
|
|
65
|
+
"""
|
|
66
|
+
self.spec_type = spec_type
|
|
67
|
+
self.name = name
|
|
68
|
+
self.description = description
|
|
69
|
+
self.required = required
|
|
70
|
+
self.noneable = noneable
|
|
71
|
+
self.validator = validator
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
@abc.abstractmethod
|
|
75
|
+
def from_4C_metadata(cls, data_dict: dict) -> InputSpec:
|
|
76
|
+
"""Create InputSpec from 4C metadata file.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
data_dict: Data from 4C metadata file
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
InputSpec
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class All_Of:
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
specs: Sequence[Spec],
|
|
90
|
+
description: NotSetAlias[str] = NotSetString,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Initialise All_Of.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
specs: Specs of the All_Of
|
|
96
|
+
description: Description
|
|
97
|
+
"""
|
|
98
|
+
self.specs: list[InputSpec | One_Of] = All_Of.condense(specs)
|
|
99
|
+
self.description = description
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def condense(specs: Sequence[Spec]) -> list[InputSpec | One_Of]:
|
|
103
|
+
"""Condense All_Of.
|
|
104
|
+
|
|
105
|
+
All_Ofs of All_Ofs are joined, aka flattened or condensed.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
specs: Specs to condense
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Condensed specs
|
|
112
|
+
"""
|
|
113
|
+
new_specs: list[InputSpec | One_Of] = []
|
|
114
|
+
one_ofs: list[One_Of] = []
|
|
115
|
+
|
|
116
|
+
for spec in specs:
|
|
117
|
+
match spec:
|
|
118
|
+
case InputSpec():
|
|
119
|
+
new_specs.append(spec)
|
|
120
|
+
case All_Of():
|
|
121
|
+
# Check if it is an One_Of
|
|
122
|
+
if spec.is_one_of():
|
|
123
|
+
one_ofs.append(spec.specs[0]) # type: ignore
|
|
124
|
+
else:
|
|
125
|
+
new_specs.extend(All_Of.condense(spec.specs))
|
|
126
|
+
case One_Of():
|
|
127
|
+
one_ofs.append(spec)
|
|
128
|
+
case _:
|
|
129
|
+
raise TypeError(f"Invalid spec {spec}")
|
|
130
|
+
|
|
131
|
+
if len(one_ofs) > 1:
|
|
132
|
+
oo = "\n".join([str(oo) for oo in one_ofs])
|
|
133
|
+
raise ValueError(f"More than one_of is not allowed {oo}")
|
|
134
|
+
|
|
135
|
+
if one_ofs:
|
|
136
|
+
new_specs = [one_ofs[0].add_specs(new_specs)]
|
|
137
|
+
|
|
138
|
+
return new_specs
|
|
139
|
+
|
|
140
|
+
def add_specs(self, input_specs: Sequence[Spec]) -> All_Of:
|
|
141
|
+
"""Add specs to the All_Of.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
All_Of object.
|
|
145
|
+
"""
|
|
146
|
+
self.specs = All_Of.condense(self.specs + list(input_specs))
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def is_one_of(self) -> bool:
|
|
150
|
+
"""Check if All_Of only hold a One_Of.
|
|
151
|
+
|
|
152
|
+
In essence this means that the All_Of is simply a One_Of.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
True if is a One_Of in disguise
|
|
156
|
+
"""
|
|
157
|
+
return len(self.specs) == 1 and isinstance(self.specs[0], One_Of)
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def from_4C_metadata(cls, data_dict: dict) -> All_Of:
|
|
161
|
+
"""Create All_Of from 4C metadata file.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
data_dict: Data from 4C metadata file
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
All_Of
|
|
168
|
+
"""
|
|
169
|
+
data_dict.pop("type", None)
|
|
170
|
+
specs = data_dict.pop("specs", [])
|
|
171
|
+
specs = [metadata_from_dict(spec) for spec in specs]
|
|
172
|
+
return cls(specs=specs, **data_dict)
|
|
173
|
+
|
|
174
|
+
def __str__(self) -> str: # pragma: no cover
|
|
175
|
+
"""String method."""
|
|
176
|
+
return "All_of(" + ", ".join([str(spec) for spec in self.specs]) + ")"
|
|
177
|
+
|
|
178
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
179
|
+
"""Representation method."""
|
|
180
|
+
return "All_of(" + ", ".join([str(spec) for spec in self.specs]) + ")"
|
|
181
|
+
|
|
182
|
+
def __len__(self) -> int:
|
|
183
|
+
"""Len of the All_Of.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
number of specs
|
|
187
|
+
"""
|
|
188
|
+
return len(self.specs)
|
|
189
|
+
|
|
190
|
+
def __iter__(self) -> Generator[One_Of | InputSpec]:
|
|
191
|
+
"""Iterate All_Of.
|
|
192
|
+
|
|
193
|
+
Yields:
|
|
194
|
+
All_Of specs
|
|
195
|
+
"""
|
|
196
|
+
yield from self.specs
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class One_Of:
|
|
200
|
+
def __init__(
|
|
201
|
+
self,
|
|
202
|
+
specs: Sequence[Spec],
|
|
203
|
+
description: NotSetAlias[str] = NotSetString,
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Initialise One_Of.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
specs: Specs of the One_Of, i.e., each distinct branch
|
|
209
|
+
description: Description
|
|
210
|
+
"""
|
|
211
|
+
self.specs: list[All_Of] = One_Of.condense(specs)
|
|
212
|
+
self.description = description
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def condense(specs: Sequence[Spec]) -> list[All_Of]:
|
|
216
|
+
"""Condense One_Of.
|
|
217
|
+
|
|
218
|
+
One_Ofs of One_Ofs are combined to a single One_Of.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
specs: Specs of the One_Of
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
condesed specs
|
|
225
|
+
"""
|
|
226
|
+
new_specs: list[All_Of] = []
|
|
227
|
+
|
|
228
|
+
for spec in specs:
|
|
229
|
+
match spec:
|
|
230
|
+
case InputSpec():
|
|
231
|
+
new_specs.append(All_Of([spec]))
|
|
232
|
+
case All_Of():
|
|
233
|
+
if spec.is_one_of():
|
|
234
|
+
# One_of in One_of
|
|
235
|
+
new_specs.extend(One_Of.condense(spec.specs[0].specs)) # type: ignore
|
|
236
|
+
else:
|
|
237
|
+
new_specs.append(spec)
|
|
238
|
+
case One_Of():
|
|
239
|
+
# One_of in one_of
|
|
240
|
+
new_specs.extend(One_Of.condense(spec.specs))
|
|
241
|
+
case _:
|
|
242
|
+
raise TypeError(f"Not supported type {type(spec)} for {spec}")
|
|
243
|
+
|
|
244
|
+
return new_specs
|
|
245
|
+
|
|
246
|
+
def add_specs(self, input_specs: Sequence[Spec]) -> One_Of:
|
|
247
|
+
"""Add specs to the One_Of.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
One_Of object.
|
|
251
|
+
"""
|
|
252
|
+
for i in range(len(self.specs)):
|
|
253
|
+
self.specs[i].add_specs(input_specs)
|
|
254
|
+
self.specs = One_Of.condense(self.specs)
|
|
255
|
+
return self
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def from_4C_metadata(cls, data_dict: dict) -> One_Of:
|
|
259
|
+
"""Create One_Of from 4C metadata file.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
data_dict: Data from 4C metadata file
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
One_Of
|
|
266
|
+
"""
|
|
267
|
+
data_dict.pop("type", None)
|
|
268
|
+
specs = [metadata_from_dict(spec) for spec in data_dict.pop("specs")]
|
|
269
|
+
return cls(specs=specs, **data_dict)
|
|
270
|
+
|
|
271
|
+
def __str__(self) -> str: # pragma: no cover
|
|
272
|
+
"""String method."""
|
|
273
|
+
return "One_of(" + ", ".join([str(spec) for spec in self.specs]) + ")"
|
|
274
|
+
|
|
275
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
276
|
+
"""Representation method."""
|
|
277
|
+
return "One_of(" + ", ".join([str(spec) for spec in self.specs]) + ")"
|
|
278
|
+
|
|
279
|
+
def __len__(self) -> int:
|
|
280
|
+
"""Len of the One_Of.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
number of One_Of branches
|
|
284
|
+
"""
|
|
285
|
+
return len(self.specs)
|
|
286
|
+
|
|
287
|
+
def __iter__(self) -> Generator[All_Of]:
|
|
288
|
+
"""Iterate One_of.
|
|
289
|
+
|
|
290
|
+
Yields:
|
|
291
|
+
One_Of branches
|
|
292
|
+
"""
|
|
293
|
+
yield from self.specs
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class Primitive(InputSpec):
|
|
297
|
+
PRIMITIVE_TYPES: list[str] = ["double", "bool", "int", "string", "path"]
|
|
298
|
+
PRIMITIVE_TYPES_TO_PYTHON: dict[str, type] = {
|
|
299
|
+
"double": float,
|
|
300
|
+
"bool": bool,
|
|
301
|
+
"int": int,
|
|
302
|
+
"string": str,
|
|
303
|
+
"path": str,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
def __init__(
|
|
307
|
+
self,
|
|
308
|
+
spec_type: Literal["double", "int", "bool", "string", "path"],
|
|
309
|
+
name: NotSetAlias[
|
|
310
|
+
str
|
|
311
|
+
] = NotSetString, # Name is only optional with vectors, maps, tuple
|
|
312
|
+
description: NotSetAlias[str] = NotSetString,
|
|
313
|
+
required: bool = True,
|
|
314
|
+
noneable: bool = False,
|
|
315
|
+
validator: ValidatorAlias = None,
|
|
316
|
+
default: NotSetAlias[PRIMITIVES_PYTHON_TYPES] = NotSet("default"),
|
|
317
|
+
) -> None:
|
|
318
|
+
"""Initialise primitive.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
spec_type: Type of primitive
|
|
322
|
+
name: Name
|
|
323
|
+
description: Description
|
|
324
|
+
required: True if parameter is required
|
|
325
|
+
noneable: True if parameter can be None
|
|
326
|
+
validator: Validator callable
|
|
327
|
+
default: Default value of the spec
|
|
328
|
+
"""
|
|
329
|
+
if spec_type not in Primitive.PRIMITIVE_TYPES:
|
|
330
|
+
raise TypeError(
|
|
331
|
+
f"Spec type {spec_type} is not supported. Supported ones are: {Primitive.PRIMITIVE_TYPES}"
|
|
332
|
+
)
|
|
333
|
+
super().__init__(spec_type, name, description, required, noneable, validator)
|
|
334
|
+
if check_if_set(default) and default is not None and False:
|
|
335
|
+
if not isinstance(default, Primitive.PRIMITIVE_TYPES_TO_PYTHON[spec_type]):
|
|
336
|
+
raise TypeError(
|
|
337
|
+
f"Default {default} for the parameter of type '{spec_type}' is not Python type {Primitive.PRIMITIVE_TYPES_TO_PYTHON[spec_type]}"
|
|
338
|
+
)
|
|
339
|
+
self.default = default
|
|
340
|
+
|
|
341
|
+
@classmethod
|
|
342
|
+
def from_4C_metadata(cls, data_dict: dict) -> Primitive:
|
|
343
|
+
"""Create primitive from 4C metadata file.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
data_dict: Data from 4C metadata file
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
primitive
|
|
350
|
+
"""
|
|
351
|
+
spec_type = data_dict.pop("type")
|
|
352
|
+
if "validator" in data_dict:
|
|
353
|
+
data_dict["validator"] = validator_from_dict(data_dict["validator"])
|
|
354
|
+
return cls(spec_type=spec_type, **data_dict)
|
|
355
|
+
|
|
356
|
+
def __str__(self) -> str: # pragma: no cover
|
|
357
|
+
"""String method."""
|
|
358
|
+
return f"{self.name}"
|
|
359
|
+
|
|
360
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
361
|
+
"""Representation method."""
|
|
362
|
+
return f"{self.name}"
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class Enum(InputSpec):
|
|
366
|
+
def __init__(
|
|
367
|
+
self,
|
|
368
|
+
choices: Sequence[str],
|
|
369
|
+
name: NotSetAlias[str] = NotSetString,
|
|
370
|
+
description: NotSetAlias[str] = NotSetString,
|
|
371
|
+
required: bool = True,
|
|
372
|
+
noneable: bool = False,
|
|
373
|
+
validator: ValidatorAlias = None,
|
|
374
|
+
default: NotSetAlias[str] = NotSet("default"),
|
|
375
|
+
) -> None:
|
|
376
|
+
"""Initialise enum.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
choices: Enum choices
|
|
380
|
+
name: Name
|
|
381
|
+
description: Description
|
|
382
|
+
required: True if parameter is required
|
|
383
|
+
noneable: True if parameter can be None
|
|
384
|
+
validator: Validator callable
|
|
385
|
+
default: Default value
|
|
386
|
+
"""
|
|
387
|
+
super().__init__("enum", name, description, required, noneable, validator)
|
|
388
|
+
if check_if_set(default):
|
|
389
|
+
if default not in choices:
|
|
390
|
+
raise ValueError(
|
|
391
|
+
f"Default choice {default} is not in the valid enum choices {choices}"
|
|
392
|
+
)
|
|
393
|
+
self.choices = choices
|
|
394
|
+
self.default = default
|
|
395
|
+
|
|
396
|
+
@classmethod
|
|
397
|
+
def from_4C_metadata(cls, data_dict: dict) -> Enum:
|
|
398
|
+
"""Create enum from 4C metadata file.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
data_dict: Data from 4C metadata file
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
enum
|
|
405
|
+
"""
|
|
406
|
+
data_dict.pop("type", None)
|
|
407
|
+
choices = [c["name"] for c in data_dict.pop("choices")]
|
|
408
|
+
# TODO add description from choices
|
|
409
|
+
if "validator" in data_dict:
|
|
410
|
+
data_dict["validator"] = validator_from_dict(data_dict["validator"])
|
|
411
|
+
return cls(choices=choices, **data_dict)
|
|
412
|
+
|
|
413
|
+
def __str__(self) -> str: # pragma: no cover
|
|
414
|
+
"""String method."""
|
|
415
|
+
return f"{self.name}"
|
|
416
|
+
|
|
417
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
418
|
+
"""Representation method."""
|
|
419
|
+
return f"{self.name}"
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class Vector(InputSpec):
|
|
423
|
+
def __init__(
|
|
424
|
+
self,
|
|
425
|
+
value_type: NATIVE_CPP_ALIAS,
|
|
426
|
+
size: int | None = None,
|
|
427
|
+
name: NotSetAlias[str] = NotSetString,
|
|
428
|
+
description: NotSetAlias[str] = NotSetString,
|
|
429
|
+
required: bool = True,
|
|
430
|
+
noneable: bool = False,
|
|
431
|
+
validator: ValidatorAlias = None,
|
|
432
|
+
default: NotSetAlias[Sequence[NATIVE_CPP_ALIAS]] = NotSet("default"),
|
|
433
|
+
) -> None:
|
|
434
|
+
"""Initialise vector.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
value_type: Type of vector elements
|
|
438
|
+
size: Vector dimension
|
|
439
|
+
name: Name
|
|
440
|
+
description: Description
|
|
441
|
+
required: True if parameter is required
|
|
442
|
+
noneable: True if parameter can be None
|
|
443
|
+
validator: Validator callable
|
|
444
|
+
default: Default value
|
|
445
|
+
"""
|
|
446
|
+
super().__init__("vector", name, description, required, noneable, validator)
|
|
447
|
+
if not isinstance(value_type, NATIVE_CPP_TYPES):
|
|
448
|
+
raise TypeError(
|
|
449
|
+
f"Value type {value_type} has to be of type: {NATIVE_CPP_TYPES}"
|
|
450
|
+
)
|
|
451
|
+
self.value_type = value_type
|
|
452
|
+
self.size = size
|
|
453
|
+
self.default = default
|
|
454
|
+
|
|
455
|
+
@classmethod
|
|
456
|
+
def from_4C_metadata(cls, data_dict: dict) -> Vector:
|
|
457
|
+
"""Create vector from 4C metadata file.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
data_dict: Data from 4C metadata file
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
vector
|
|
464
|
+
"""
|
|
465
|
+
data_dict.pop("type", None)
|
|
466
|
+
value_type: NATIVE_CPP_ALIAS = metadata_from_dict(data_dict.pop("value_type")) # type: ignore
|
|
467
|
+
if "validator" in data_dict:
|
|
468
|
+
data_dict["validator"] = validator_from_dict(data_dict["validator"])
|
|
469
|
+
return cls(value_type=value_type, **data_dict)
|
|
470
|
+
|
|
471
|
+
def __str__(self) -> str: # pragma: no cover
|
|
472
|
+
"""String method."""
|
|
473
|
+
return f"{self.name}"
|
|
474
|
+
|
|
475
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
476
|
+
"""Representation method."""
|
|
477
|
+
return f"{self.name}"
|
|
478
|
+
|
|
479
|
+
def __iter__(self) -> Generator[NATIVE_CPP_ALIAS]:
|
|
480
|
+
"""Iterate vector.
|
|
481
|
+
|
|
482
|
+
Yields:
|
|
483
|
+
value type of the vector
|
|
484
|
+
"""
|
|
485
|
+
yield self.value_type
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class Map(InputSpec):
|
|
489
|
+
def __init__(
|
|
490
|
+
self,
|
|
491
|
+
value_type: NATIVE_CPP_ALIAS,
|
|
492
|
+
size: int | None = None,
|
|
493
|
+
name: NotSetAlias[str] = NotSetString,
|
|
494
|
+
description: NotSetAlias[str] = NotSetString,
|
|
495
|
+
required: bool = True,
|
|
496
|
+
noneable: bool = False,
|
|
497
|
+
validator: ValidatorAlias = None,
|
|
498
|
+
default: NotSetAlias[dict[str, NATIVE_CPP_ALIAS]] = NotSet("default"),
|
|
499
|
+
) -> None:
|
|
500
|
+
"""Initialise map.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
value_type: Type of map elements
|
|
504
|
+
size: Number of entries in the map
|
|
505
|
+
name: Name
|
|
506
|
+
description: Description
|
|
507
|
+
required: True if parameter is required
|
|
508
|
+
noneable: True if parameter can be None
|
|
509
|
+
validator: Validator callable
|
|
510
|
+
default: Default value
|
|
511
|
+
"""
|
|
512
|
+
if not isinstance(value_type, NATIVE_CPP_TYPES):
|
|
513
|
+
raise TypeError(
|
|
514
|
+
f"Value type {value_type} has to be of type: {NATIVE_CPP_TYPES}"
|
|
515
|
+
)
|
|
516
|
+
super().__init__("map", name, description, required, noneable, validator)
|
|
517
|
+
self.value_type: NATIVE_CPP_ALIAS = value_type
|
|
518
|
+
self.size = size
|
|
519
|
+
self.default = default
|
|
520
|
+
|
|
521
|
+
@classmethod
|
|
522
|
+
def from_4C_metadata(cls, data_dict: dict) -> Map:
|
|
523
|
+
"""Create map from 4C metadata file.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
data_dict: Data from 4C metadata file
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
map
|
|
530
|
+
"""
|
|
531
|
+
data_dict.pop("type", None)
|
|
532
|
+
value_type: NATIVE_CPP_ALIAS = metadata_from_dict(data_dict.pop("value_type")) # type: ignore
|
|
533
|
+
if "validator" in data_dict:
|
|
534
|
+
data_dict["validator"] = validator_from_dict(data_dict["validator"])
|
|
535
|
+
return cls(value_type=value_type, **data_dict)
|
|
536
|
+
|
|
537
|
+
def __str__(self) -> str: # pragma: no cover
|
|
538
|
+
"""String method."""
|
|
539
|
+
return f"{self.name}"
|
|
540
|
+
|
|
541
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
542
|
+
"""Representation method."""
|
|
543
|
+
return f"{self.name}"
|
|
544
|
+
|
|
545
|
+
def __iter__(self) -> Generator[NATIVE_CPP_ALIAS]:
|
|
546
|
+
"""Iterate map.
|
|
547
|
+
|
|
548
|
+
Yields:
|
|
549
|
+
value type of the map
|
|
550
|
+
"""
|
|
551
|
+
yield self.value_type
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
class Tuple(InputSpec):
|
|
555
|
+
def __init__(
|
|
556
|
+
self,
|
|
557
|
+
value_types: Sequence[NATIVE_CPP_ALIAS],
|
|
558
|
+
size: int,
|
|
559
|
+
name: NotSetAlias[str] = NotSetString,
|
|
560
|
+
description: NotSetAlias[str] = NotSetString,
|
|
561
|
+
required: bool = True,
|
|
562
|
+
noneable: bool = False,
|
|
563
|
+
validator: ValidatorAlias = None,
|
|
564
|
+
default: NotSetAlias[Sequence[NATIVE_CPP_ALIAS]] = NotSet("default"),
|
|
565
|
+
) -> None:
|
|
566
|
+
"""Initialise tuple.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
value_types: Types of the tuple element
|
|
570
|
+
size: Number of tuple entries
|
|
571
|
+
name: Name
|
|
572
|
+
description: Description
|
|
573
|
+
required: True if parameter is required
|
|
574
|
+
noneable: True if parameter can be None
|
|
575
|
+
validator: Validator callable
|
|
576
|
+
default: Default value
|
|
577
|
+
"""
|
|
578
|
+
super().__init__("tuple", name, description, required, noneable, validator)
|
|
579
|
+
for i, value_type in enumerate(value_types):
|
|
580
|
+
if not isinstance(value_type, NATIVE_CPP_TYPES):
|
|
581
|
+
raise TypeError(
|
|
582
|
+
f"Value type {value_type} (parameter number {i + 1} in {value_types}) has to be of type: {NATIVE_CPP_TYPES}"
|
|
583
|
+
)
|
|
584
|
+
self.value_types = value_types
|
|
585
|
+
self.size = size
|
|
586
|
+
if self.size != len(self.value_types):
|
|
587
|
+
raise ValueError(
|
|
588
|
+
f"Tuple size {self.size} does not match the number of value types "
|
|
589
|
+
f"{len(self.value_types)}"
|
|
590
|
+
)
|
|
591
|
+
self.default = default
|
|
592
|
+
|
|
593
|
+
@classmethod
|
|
594
|
+
def from_4C_metadata(cls, data_dict: dict) -> Tuple:
|
|
595
|
+
"""Create tuple from 4C metadata file.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
data_dict: Data from 4C metadata file
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
tuple
|
|
602
|
+
"""
|
|
603
|
+
data_dict.pop("type", None)
|
|
604
|
+
value_types = [
|
|
605
|
+
metadata_from_dict(value_type)
|
|
606
|
+
for value_type in data_dict.pop("value_types")
|
|
607
|
+
]
|
|
608
|
+
if "validator" in data_dict:
|
|
609
|
+
data_dict["validator"] = validator_from_dict(data_dict["validator"])
|
|
610
|
+
return cls(value_types=value_types, **data_dict) # type: ignore
|
|
611
|
+
|
|
612
|
+
def __str__(self) -> str: # pragma: no cover
|
|
613
|
+
"""String method."""
|
|
614
|
+
return f"{self.name}"
|
|
615
|
+
|
|
616
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
617
|
+
"""Representation method."""
|
|
618
|
+
return f"{self.name}"
|
|
619
|
+
|
|
620
|
+
def __iter__(self) -> Generator[NATIVE_CPP_ALIAS]:
|
|
621
|
+
"""Iterate tuple.
|
|
622
|
+
|
|
623
|
+
Yields:
|
|
624
|
+
value types of the tuple
|
|
625
|
+
"""
|
|
626
|
+
yield from self.value_types
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
class Selection(InputSpec):
|
|
630
|
+
def __init__(
|
|
631
|
+
self,
|
|
632
|
+
name: NotSetAlias[str],
|
|
633
|
+
choices: dict[str, Spec],
|
|
634
|
+
description: NotSetAlias[str] = NotSetString,
|
|
635
|
+
required: bool = True,
|
|
636
|
+
noneable: bool = False,
|
|
637
|
+
defaultable: bool = False,
|
|
638
|
+
validator: ValidatorAlias = None,
|
|
639
|
+
) -> None:
|
|
640
|
+
"""Initialise selection.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
name: Name
|
|
644
|
+
choices: Choices of the selection
|
|
645
|
+
description: Description
|
|
646
|
+
required: True if parameter is required
|
|
647
|
+
noneable: True if parameter can be None
|
|
648
|
+
defaultable: Can have a default
|
|
649
|
+
validator: Validator callable
|
|
650
|
+
"""
|
|
651
|
+
super().__init__("selection", name, description, required, noneable, validator)
|
|
652
|
+
self.choices: dict[str, All_Of] = {k: All_Of([v]) for k, v in choices.items()}
|
|
653
|
+
self.defautable = defaultable
|
|
654
|
+
|
|
655
|
+
@classmethod
|
|
656
|
+
def from_4C_metadata(cls, data_dict: dict) -> Selection:
|
|
657
|
+
"""Create selection from 4C metadata file.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
data_dict: Data from 4C metadata file
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
selection
|
|
664
|
+
"""
|
|
665
|
+
data_dict.pop("type", None)
|
|
666
|
+
|
|
667
|
+
choices = {}
|
|
668
|
+
for choice in data_dict.pop("choices"):
|
|
669
|
+
spec = choice.pop("spec")
|
|
670
|
+
choices[choice.pop("name")] = metadata_from_dict(spec)
|
|
671
|
+
if "validator" in data_dict:
|
|
672
|
+
data_dict["validator"] = validator_from_dict(data_dict["validator"])
|
|
673
|
+
return cls(choices=choices, **data_dict)
|
|
674
|
+
|
|
675
|
+
def __iter__(self) -> Generator[Spec]:
|
|
676
|
+
"""Iterate selection.
|
|
677
|
+
|
|
678
|
+
Yields:
|
|
679
|
+
choices of the selection
|
|
680
|
+
"""
|
|
681
|
+
yield from self.choices.values()
|
|
682
|
+
|
|
683
|
+
def __len__(self) -> int:
|
|
684
|
+
"""Len of the selection.
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
number of choices
|
|
688
|
+
"""
|
|
689
|
+
return len(self.choices)
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
class Group(InputSpec):
|
|
693
|
+
def __init__(
|
|
694
|
+
self,
|
|
695
|
+
name: NotSetAlias[str],
|
|
696
|
+
spec: Spec,
|
|
697
|
+
description: NotSetAlias[str] = NotSetString,
|
|
698
|
+
required: bool = True,
|
|
699
|
+
noneable: bool = False,
|
|
700
|
+
defaultable: bool = False,
|
|
701
|
+
validator: ValidatorAlias = None,
|
|
702
|
+
) -> None:
|
|
703
|
+
"""Initialise group.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
name: Name
|
|
707
|
+
spec: Spec of the group
|
|
708
|
+
description: Description
|
|
709
|
+
required: True if parameter is required
|
|
710
|
+
noneable: True if parameter can be None
|
|
711
|
+
defaultable: Can have a default
|
|
712
|
+
validator: Validator callable
|
|
713
|
+
"""
|
|
714
|
+
super().__init__("group", name, description, required, noneable, validator)
|
|
715
|
+
self.spec: All_Of = All_Of([spec])
|
|
716
|
+
self.defautable = defaultable
|
|
717
|
+
|
|
718
|
+
@classmethod
|
|
719
|
+
def from_4C_metadata(cls, data_dict: dict) -> Group:
|
|
720
|
+
"""Create group from 4C metadata file.
|
|
721
|
+
|
|
722
|
+
Args:
|
|
723
|
+
data_dict: Data from 4C metadata file
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
group
|
|
727
|
+
"""
|
|
728
|
+
data_dict.pop("type", None)
|
|
729
|
+
specs = [metadata_from_dict(spec) for spec in data_dict.pop("specs", [])]
|
|
730
|
+
if "validator" in data_dict:
|
|
731
|
+
data_dict["validator"] = validator_from_dict(data_dict["validator"])
|
|
732
|
+
return cls(spec=All_Of(specs), **data_dict)
|
|
733
|
+
|
|
734
|
+
def __iter__(self) -> Generator[All_Of]:
|
|
735
|
+
"""Iterate group.
|
|
736
|
+
|
|
737
|
+
Yields:
|
|
738
|
+
group spec
|
|
739
|
+
"""
|
|
740
|
+
yield self.spec
|
|
741
|
+
|
|
742
|
+
def __str__(self) -> str: # pragma: no cover
|
|
743
|
+
"""String method."""
|
|
744
|
+
return f"{self.name} Group({self.spec})"
|
|
745
|
+
|
|
746
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
747
|
+
"""Representation method."""
|
|
748
|
+
return f"{self.name} Group({self.spec})"
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
class List(InputSpec):
|
|
752
|
+
def __init__(
|
|
753
|
+
self,
|
|
754
|
+
name: NotSetAlias[str],
|
|
755
|
+
spec: Spec,
|
|
756
|
+
size: int | None = None,
|
|
757
|
+
description: NotSetAlias[str] = NotSetString,
|
|
758
|
+
required: bool = True,
|
|
759
|
+
noneable: bool = False,
|
|
760
|
+
defaultable: bool = False,
|
|
761
|
+
validator: ValidatorAlias = None,
|
|
762
|
+
) -> None:
|
|
763
|
+
"""Initialise list.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
name: Name
|
|
767
|
+
spec: Spec of the list entries
|
|
768
|
+
size: Number of list entries
|
|
769
|
+
description: Description
|
|
770
|
+
required: True if parameter is required
|
|
771
|
+
noneable: True if parameter can be None
|
|
772
|
+
defaultable: Can have a default
|
|
773
|
+
validator: Validator callable
|
|
774
|
+
"""
|
|
775
|
+
super().__init__("list", name, description, required, noneable, validator)
|
|
776
|
+
self.spec: All_Of = All_Of([spec])
|
|
777
|
+
self.size = size
|
|
778
|
+
self.defautable = defaultable
|
|
779
|
+
|
|
780
|
+
@classmethod
|
|
781
|
+
def from_4C_metadata(cls, data_dict: dict) -> List:
|
|
782
|
+
"""Create list from 4C metadata file.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
data_dict: Data from 4C metadata file
|
|
786
|
+
|
|
787
|
+
Returns:
|
|
788
|
+
list
|
|
789
|
+
"""
|
|
790
|
+
data_dict.pop("type", None)
|
|
791
|
+
spec = metadata_from_dict(data_dict.pop("spec"))
|
|
792
|
+
if "validator" in data_dict:
|
|
793
|
+
data_dict["validator"] = validator_from_dict(data_dict["validator"])
|
|
794
|
+
return cls(spec=All_Of([spec]), **data_dict)
|
|
795
|
+
|
|
796
|
+
def __iter__(self) -> Generator[All_Of]:
|
|
797
|
+
"""Iterate list.
|
|
798
|
+
|
|
799
|
+
Yields:
|
|
800
|
+
spec
|
|
801
|
+
"""
|
|
802
|
+
yield self.spec
|
|
803
|
+
|
|
804
|
+
def __str__(self) -> str: # pragma: no cover
|
|
805
|
+
"""String method."""
|
|
806
|
+
return f"{self.name} List({self.spec})"
|
|
807
|
+
|
|
808
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
809
|
+
"""Representation method."""
|
|
810
|
+
return f"{self.name} List({self.spec})"
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
Spec: TypeAlias = InputSpec | All_Of | One_Of
|
|
814
|
+
NATIVE_CPP_ALIAS: TypeAlias = Primitive | Enum | Vector | Map | Tuple
|
|
815
|
+
NATIVE_CPP_TYPES = (Primitive, Enum, Vector, Map, Tuple)
|
|
816
|
+
|
|
817
|
+
_METADATA_TYPE_TO_CLASS: dict[str, type[Spec]] = {
|
|
818
|
+
i.__name__.lower(): i
|
|
819
|
+
for i in [Enum, Vector, Map, Tuple, Selection, Group, List, All_Of, One_Of]
|
|
820
|
+
} | {i: Primitive for i in Primitive.PRIMITIVE_TYPES}
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def metadata_from_dict(data_dict: dict) -> Spec:
|
|
824
|
+
"""Create metadata object from 4C metadata dict.
|
|
825
|
+
|
|
826
|
+
Args:
|
|
827
|
+
data_dict: Data from 4C metadata file
|
|
828
|
+
|
|
829
|
+
Returns:
|
|
830
|
+
metadata object
|
|
831
|
+
"""
|
|
832
|
+
entry_type: str = data_dict.get("type") # type: ignore
|
|
833
|
+
|
|
834
|
+
metadata_class: type[Spec] | None = _METADATA_TYPE_TO_CLASS.get(entry_type, None)
|
|
835
|
+
|
|
836
|
+
if metadata_class is not None:
|
|
837
|
+
return metadata_class.from_4C_metadata(data_dict)
|
|
838
|
+
|
|
839
|
+
raise TypeError(
|
|
840
|
+
f"Unknown type {entry_type} for {data_dict}, known ones are {list(_METADATA_TYPE_TO_CLASS.keys())}"
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
class Validator(Protocol[G]):
|
|
845
|
+
"""Validator protocol."""
|
|
846
|
+
|
|
847
|
+
def _validate(self, entry: G) -> bool:
|
|
848
|
+
"""Validate object.
|
|
849
|
+
|
|
850
|
+
Args:
|
|
851
|
+
entry: Object to validate
|
|
852
|
+
|
|
853
|
+
Returns:
|
|
854
|
+
True if valid
|
|
855
|
+
"""
|
|
856
|
+
pass
|
|
857
|
+
|
|
858
|
+
def __call__(self, entry: G) -> bool:
|
|
859
|
+
"""Call method."""
|
|
860
|
+
return self._validate(entry)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
class RangeValidator(Validator[T]):
|
|
864
|
+
def __init__(
|
|
865
|
+
self,
|
|
866
|
+
minimum: T,
|
|
867
|
+
maximum: T,
|
|
868
|
+
minimum_exclusive: bool = False,
|
|
869
|
+
maximum_exclusive: bool = False,
|
|
870
|
+
) -> None:
|
|
871
|
+
"""Range validator.
|
|
872
|
+
|
|
873
|
+
Args:
|
|
874
|
+
entry: Entry value
|
|
875
|
+
minimum: Minimum value
|
|
876
|
+
maximum: Maximum value
|
|
877
|
+
minimum_exclusive: Minimum is exclusive
|
|
878
|
+
maximum_exclusive: Maximum is exclusive
|
|
879
|
+
"""
|
|
880
|
+
self.minimum: T = minimum
|
|
881
|
+
self.maximum: T = maximum
|
|
882
|
+
self.minimum_exclusive = minimum_exclusive
|
|
883
|
+
self.maximum_exclusive = maximum_exclusive
|
|
884
|
+
|
|
885
|
+
def _validate(self, entry: T) -> bool:
|
|
886
|
+
"""Validate if float or in is in range.
|
|
887
|
+
|
|
888
|
+
Args:
|
|
889
|
+
entry: Entry value
|
|
890
|
+
minimum: Minimum value
|
|
891
|
+
maximum: Maximum value
|
|
892
|
+
minimum_exclusive: Minimum is exclusive
|
|
893
|
+
maximum_exclusive: Maximum is exclusive
|
|
894
|
+
|
|
895
|
+
Returns:
|
|
896
|
+
True if in range
|
|
897
|
+
"""
|
|
898
|
+
if self.minimum_exclusive:
|
|
899
|
+
if entry <= self.minimum:
|
|
900
|
+
return False
|
|
901
|
+
else:
|
|
902
|
+
if entry < self.minimum:
|
|
903
|
+
return False
|
|
904
|
+
|
|
905
|
+
if self.maximum_exclusive:
|
|
906
|
+
if entry >= self.maximum:
|
|
907
|
+
return False
|
|
908
|
+
else:
|
|
909
|
+
if entry > self.maximum:
|
|
910
|
+
return False
|
|
911
|
+
|
|
912
|
+
return True
|
|
913
|
+
|
|
914
|
+
@classmethod
|
|
915
|
+
def from_4C_metadata_dict(cls, data_dict: dict) -> RangeValidator:
|
|
916
|
+
"""Create validator from 4C metadata dict.
|
|
917
|
+
|
|
918
|
+
Args:
|
|
919
|
+
data_dict: Data from 4C metadata file
|
|
920
|
+
|
|
921
|
+
Returns:
|
|
922
|
+
range validator
|
|
923
|
+
"""
|
|
924
|
+
return cls(**data_dict)
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
class AllEmementsValidator(Validator[Sequence]):
|
|
928
|
+
def __init__(self, element_validator: Callable[[Sequence], bool]) -> None:
|
|
929
|
+
"""All elements validator.
|
|
930
|
+
|
|
931
|
+
Validates each entry of a sequence with the `element_validator`.
|
|
932
|
+
|
|
933
|
+
Args:
|
|
934
|
+
element_validator: Validator for each entry
|
|
935
|
+
"""
|
|
936
|
+
self.element_validator = element_validator
|
|
937
|
+
|
|
938
|
+
def _validate(self, entry: Sequence) -> bool:
|
|
939
|
+
"""Validate sequence.
|
|
940
|
+
|
|
941
|
+
Args:
|
|
942
|
+
entry: Sequence to validate
|
|
943
|
+
|
|
944
|
+
Returns:
|
|
945
|
+
True if all entries are valid
|
|
946
|
+
"""
|
|
947
|
+
return all([self.element_validator(e) for e in entry])
|
|
948
|
+
|
|
949
|
+
@classmethod
|
|
950
|
+
def from_4C_metadata_dict(cls, data_dict: dict) -> AllEmementsValidator:
|
|
951
|
+
"""Create validator from 4C metadata dict.
|
|
952
|
+
|
|
953
|
+
Args:
|
|
954
|
+
data_dict: Data from 4C metadata file
|
|
955
|
+
|
|
956
|
+
Returns:
|
|
957
|
+
all elements validator
|
|
958
|
+
"""
|
|
959
|
+
element_validator = validator_from_dict(data_dict)
|
|
960
|
+
return cls(element_validator)
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
class PatternValidator(Validator):
|
|
964
|
+
def __init__(self, pattern: str):
|
|
965
|
+
"""Regex pattern validator.
|
|
966
|
+
|
|
967
|
+
Args:
|
|
968
|
+
pattern: Pattern to match
|
|
969
|
+
"""
|
|
970
|
+
self.pattern = re.compile(pattern)
|
|
971
|
+
|
|
972
|
+
def _validate(self, entry: str) -> bool:
|
|
973
|
+
"""Validate pattern.
|
|
974
|
+
|
|
975
|
+
Args:
|
|
976
|
+
entry: Entry to validate
|
|
977
|
+
|
|
978
|
+
Returns:
|
|
979
|
+
True if pattern matches
|
|
980
|
+
"""
|
|
981
|
+
return bool(self.pattern.match(entry))
|
|
982
|
+
|
|
983
|
+
@classmethod
|
|
984
|
+
def from_4C_metadata_dict(cls, data_dict: dict) -> PatternValidator:
|
|
985
|
+
"""Create validator from 4C metadata dict.
|
|
986
|
+
|
|
987
|
+
Args:
|
|
988
|
+
data_dict: Data from 4C metadata file
|
|
989
|
+
|
|
990
|
+
Returns:
|
|
991
|
+
validator
|
|
992
|
+
"""
|
|
993
|
+
return cls(**data_dict)
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def validator_from_dict(data_dict: dict) -> Validator:
|
|
997
|
+
"""Create validator from dict.
|
|
998
|
+
|
|
999
|
+
Args:
|
|
1000
|
+
data_dict: Data from 4C metadata file
|
|
1001
|
+
|
|
1002
|
+
Returns:
|
|
1003
|
+
validator
|
|
1004
|
+
"""
|
|
1005
|
+
if len(data_dict) == 0:
|
|
1006
|
+
raise ValueError(f"Can not construct value error from {data_dict}.")
|
|
1007
|
+
validator_name = list(data_dict.keys())[0]
|
|
1008
|
+
validator_data = data_dict[validator_name]
|
|
1009
|
+
match validator_name:
|
|
1010
|
+
case "range":
|
|
1011
|
+
return RangeValidator.from_4C_metadata_dict(validator_data)
|
|
1012
|
+
case "all_elements":
|
|
1013
|
+
return AllEmementsValidator.from_4C_metadata_dict(validator_data)
|
|
1014
|
+
case "pattern":
|
|
1015
|
+
return PatternValidator.from_4C_metadata_dict(validator_data)
|
|
1016
|
+
case _:
|
|
1017
|
+
raise KeyError(f"Unknown validator {data_dict}")
|