vtjson 2.1.7__py3-none-any.whl → 2.1.9__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.
- vtjson/vtjson.py +802 -273
- vtjson-2.1.9.dist-info/METADATA +133 -0
- vtjson-2.1.9.dist-info/RECORD +9 -0
- {vtjson-2.1.7.dist-info → vtjson-2.1.9.dist-info}/WHEEL +1 -1
- vtjson-2.1.7.dist-info/METADATA +0 -422
- vtjson-2.1.7.dist-info/RECORD +0 -9
- {vtjson-2.1.7.dist-info → vtjson-2.1.9.dist-info}/AUTHORS +0 -0
- {vtjson-2.1.7.dist-info → vtjson-2.1.9.dist-info}/LICENSE +0 -0
- {vtjson-2.1.7.dist-info → vtjson-2.1.9.dist-info}/top_level.txt +0 -0
vtjson/vtjson.py
CHANGED
|
@@ -12,7 +12,17 @@ import urllib.parse
|
|
|
12
12
|
import warnings
|
|
13
13
|
from collections.abc import Sequence, Set, Sized
|
|
14
14
|
from dataclasses import dataclass
|
|
15
|
-
from typing import
|
|
15
|
+
from typing import (
|
|
16
|
+
Any,
|
|
17
|
+
Callable,
|
|
18
|
+
Container,
|
|
19
|
+
Generic,
|
|
20
|
+
Mapping,
|
|
21
|
+
Type,
|
|
22
|
+
TypeVar,
|
|
23
|
+
Union,
|
|
24
|
+
cast,
|
|
25
|
+
)
|
|
16
26
|
|
|
17
27
|
try:
|
|
18
28
|
from typing import Literal
|
|
@@ -78,23 +88,88 @@ import idna
|
|
|
78
88
|
T = TypeVar("T")
|
|
79
89
|
|
|
80
90
|
|
|
81
|
-
def safe_cast(
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
def safe_cast(schema: Type[T], obj: Any) -> T:
|
|
92
|
+
"""
|
|
93
|
+
Validates the given object against the given schema and changes its type
|
|
94
|
+
accordingly.
|
|
95
|
+
|
|
96
|
+
:param schema: the given schema
|
|
97
|
+
:param obj: the object to be validated
|
|
98
|
+
:return: the validated object with its type changed
|
|
99
|
+
|
|
100
|
+
:raises ValidationError: exception thrown when the object does not
|
|
101
|
+
validate; the exception message contains an explanation about what went
|
|
102
|
+
wrong
|
|
103
|
+
|
|
104
|
+
:raises SchemaError: exception thrown when the schema definition is found
|
|
105
|
+
to contain an error
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
validate(schema, obj)
|
|
109
|
+
return cast(T, obj)
|
|
84
110
|
|
|
85
111
|
|
|
86
112
|
class compiled_schema:
|
|
113
|
+
"""
|
|
114
|
+
The result of compiling a schema. A :py:class:`compiled_schema` is
|
|
115
|
+
produced by the factory function :py:func:`vtjson.compile`.
|
|
116
|
+
"""
|
|
117
|
+
|
|
87
118
|
def __validate__(
|
|
88
119
|
self,
|
|
89
|
-
|
|
120
|
+
obj: object,
|
|
90
121
|
name: str,
|
|
91
122
|
strict: bool,
|
|
92
123
|
subs: Mapping[str, object],
|
|
93
124
|
) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Validates the given object against the given schema.
|
|
127
|
+
|
|
128
|
+
:param schema: the given schema
|
|
129
|
+
:param obj: the object to be validated
|
|
130
|
+
:param name: common name for the object to be validated; used in
|
|
131
|
+
non-validation messages
|
|
132
|
+
:param strict: indicates whether or not the object being validated is
|
|
133
|
+
allowed to have keys/entries which are not in the schema
|
|
134
|
+
:param subs: a dictionary whose keys are labels and whose values are
|
|
135
|
+
substitution schemas for schemas with those labels
|
|
136
|
+
:return: an empty string if validation succeeds; otherwise an
|
|
137
|
+
explanation about what went wrong
|
|
138
|
+
"""
|
|
139
|
+
|
|
94
140
|
return ""
|
|
95
141
|
|
|
96
142
|
|
|
143
|
+
class wrapper:
|
|
144
|
+
"""
|
|
145
|
+
Base class for schemas that refer to other schemas.
|
|
146
|
+
|
|
147
|
+
Handling such schemas is somewhat delicate since `vtjson` allows them to
|
|
148
|
+
be recursive.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __compile__(
|
|
152
|
+
self, _deferred_compiles: _mapping | None = None
|
|
153
|
+
) -> compiled_schema:
|
|
154
|
+
"""
|
|
155
|
+
Compiles a schema.
|
|
156
|
+
|
|
157
|
+
:param _deferred_compiles: an opaque data structure used for handling
|
|
158
|
+
recursive schemas; it should be passed unmodifed to any internal
|
|
159
|
+
invocations of :py:func:`vtjson._compile` or
|
|
160
|
+
:py:meth:`vtjson.wrapper.__compile__`
|
|
161
|
+
|
|
162
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
163
|
+
found to contain an error
|
|
164
|
+
"""
|
|
165
|
+
return anything()
|
|
166
|
+
|
|
167
|
+
|
|
97
168
|
class comparable(Protocol):
|
|
169
|
+
"""
|
|
170
|
+
Base class for objects that are comparable with each other.
|
|
171
|
+
"""
|
|
172
|
+
|
|
98
173
|
def __eq__(self, x: Any) -> bool: ...
|
|
99
174
|
|
|
100
175
|
def __lt__(self, x: Any) -> bool: ...
|
|
@@ -114,18 +189,39 @@ except Exception:
|
|
|
114
189
|
|
|
115
190
|
|
|
116
191
|
class ValidationError(Exception):
|
|
192
|
+
"""
|
|
193
|
+
Raised if validation fails. The associated message explains what went
|
|
194
|
+
wrong.
|
|
195
|
+
"""
|
|
196
|
+
|
|
117
197
|
pass
|
|
118
198
|
|
|
119
199
|
|
|
120
200
|
class SchemaError(Exception):
|
|
201
|
+
"""
|
|
202
|
+
Raised if a schema contains an error.
|
|
203
|
+
"""
|
|
204
|
+
|
|
121
205
|
pass
|
|
122
206
|
|
|
123
207
|
|
|
124
|
-
__version__ = "2.1.
|
|
208
|
+
__version__ = "2.1.9"
|
|
125
209
|
|
|
126
210
|
|
|
127
211
|
@dataclass
|
|
128
212
|
class Apply:
|
|
213
|
+
"""
|
|
214
|
+
Modifies the treatement of the previous arguments in an `Annotated` schema.
|
|
215
|
+
|
|
216
|
+
:param skip_first: if `True` do not use the first argument (the Python
|
|
217
|
+
type annotation) in an `Annotated` construct for validation because this
|
|
218
|
+
is already covered by the other arguments
|
|
219
|
+
:param name: apply the corresponding :py:class:`vtjson.set_name` command
|
|
220
|
+
to the previous arguments
|
|
221
|
+
:param labels: apply the corresponding :py:class:`vtjson.set_label`
|
|
222
|
+
command to the previous arguments
|
|
223
|
+
"""
|
|
224
|
+
|
|
129
225
|
skip_first: bool | None = None
|
|
130
226
|
name: str | None = None
|
|
131
227
|
labels: Sequence[str] | None = None
|
|
@@ -177,13 +273,13 @@ def _get_type_hints(schema: object) -> dict[str, object]:
|
|
|
177
273
|
|
|
178
274
|
def _to_dict(
|
|
179
275
|
type_hints: Mapping[str, object], total: bool = True
|
|
180
|
-
) -> dict[
|
|
181
|
-
d: dict[
|
|
276
|
+
) -> dict[str | optional_key[str], object]:
|
|
277
|
+
d: dict[str | optional_key[str], object] = {}
|
|
182
278
|
if not supports_Generics:
|
|
183
279
|
raise SchemaError("Generic types are not supported")
|
|
184
280
|
for k, v in type_hints.items():
|
|
185
281
|
v_ = v
|
|
186
|
-
k_: str | optional_key = k
|
|
282
|
+
k_: str | optional_key[str] = k
|
|
187
283
|
value_type = typing.get_origin(v)
|
|
188
284
|
if supports_NotRequired and value_type in (Required, NotRequired):
|
|
189
285
|
v_ = typing.get_args(v)[0]
|
|
@@ -225,14 +321,14 @@ def _c(s: object) -> str:
|
|
|
225
321
|
|
|
226
322
|
|
|
227
323
|
def _wrong_type_message(
|
|
228
|
-
|
|
324
|
+
obj: object,
|
|
229
325
|
name: str,
|
|
230
326
|
type_name: str,
|
|
231
327
|
explanation: str | None = None,
|
|
232
328
|
skip_value: bool = False,
|
|
233
329
|
) -> str:
|
|
234
330
|
if not skip_value:
|
|
235
|
-
message = f"{name} (value:{_c(
|
|
331
|
+
message = f"{name} (value:{_c(obj)}) is not of type '{type_name}'"
|
|
236
332
|
else:
|
|
237
333
|
message = f"{name} is not of type '{type_name}'"
|
|
238
334
|
if explanation is not None:
|
|
@@ -246,9 +342,9 @@ class _validate_meta(type):
|
|
|
246
342
|
__subs__: Mapping[str, object]
|
|
247
343
|
__dbg__: bool
|
|
248
344
|
|
|
249
|
-
def __instancecheck__(cls,
|
|
345
|
+
def __instancecheck__(cls, obj: object) -> bool:
|
|
250
346
|
valid = _validate(
|
|
251
|
-
cls.__schema__,
|
|
347
|
+
cls.__schema__, obj, "object", strict=cls.__strict__, subs=cls.__subs__
|
|
252
348
|
)
|
|
253
349
|
if cls.__dbg__ and valid != "":
|
|
254
350
|
print(f"DEBUG: {valid}")
|
|
@@ -262,6 +358,20 @@ def make_type(
|
|
|
262
358
|
debug: bool = False,
|
|
263
359
|
subs: Mapping[str, object] = {},
|
|
264
360
|
) -> _validate_meta:
|
|
361
|
+
"""
|
|
362
|
+
Transforms a schema into a genuine Python type.
|
|
363
|
+
|
|
364
|
+
:param schema: the given schema
|
|
365
|
+
:param name: sets the `__name__` attribute of the type; if it is not
|
|
366
|
+
supplied then `vtjson` makes an educated guess
|
|
367
|
+
:param strict: indicates whether or not the object being validated is
|
|
368
|
+
allowed to have keys/entries which are not in the schema
|
|
369
|
+
:param debug: print feedback on the console if validation fails
|
|
370
|
+
:param subs: a dictionary whose keys are labels and whose values are
|
|
371
|
+
substitution schemas for schemas with those labels
|
|
372
|
+
:raises SchemaError: exception thrown when the schema definition is found
|
|
373
|
+
to contain an error
|
|
374
|
+
"""
|
|
265
375
|
if name is None:
|
|
266
376
|
if hasattr(schema, "__name__"):
|
|
267
377
|
name = schema.__name__
|
|
@@ -279,16 +389,28 @@ def make_type(
|
|
|
279
389
|
)
|
|
280
390
|
|
|
281
391
|
|
|
282
|
-
|
|
283
|
-
|
|
392
|
+
K = TypeVar("K")
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class optional_key(Generic[K]):
|
|
396
|
+
"""
|
|
397
|
+
Make a key in a Mapping schema optional. In the common case that the key
|
|
398
|
+
is a string, the same effect can be achieved by appending `?`.
|
|
399
|
+
"""
|
|
284
400
|
|
|
285
|
-
|
|
401
|
+
key: K
|
|
402
|
+
|
|
403
|
+
def __init__(self, key: K) -> None:
|
|
404
|
+
"""
|
|
405
|
+
:param key: the key to be made optional
|
|
406
|
+
"""
|
|
286
407
|
self.key = key
|
|
287
408
|
|
|
288
409
|
def __eq__(self, key: object) -> bool:
|
|
289
410
|
if not isinstance(key, optional_key):
|
|
290
411
|
return False
|
|
291
|
-
|
|
412
|
+
k: object = key.key
|
|
413
|
+
return self.key == k
|
|
292
414
|
|
|
293
415
|
def __hash__(self) -> int:
|
|
294
416
|
return hash(self.key)
|
|
@@ -308,14 +430,14 @@ class _union(compiled_schema):
|
|
|
308
430
|
|
|
309
431
|
def __validate__(
|
|
310
432
|
self,
|
|
311
|
-
|
|
433
|
+
obj: object,
|
|
312
434
|
name: str = "object",
|
|
313
435
|
strict: bool = True,
|
|
314
436
|
subs: Mapping[str, object] = {},
|
|
315
437
|
) -> str:
|
|
316
438
|
messages = []
|
|
317
439
|
for schema in self.schemas:
|
|
318
|
-
message = schema.__validate__(
|
|
440
|
+
message = schema.__validate__(obj, name=name, strict=strict, subs=subs)
|
|
319
441
|
if message == "":
|
|
320
442
|
return ""
|
|
321
443
|
else:
|
|
@@ -323,10 +445,18 @@ class _union(compiled_schema):
|
|
|
323
445
|
return " and ".join(messages)
|
|
324
446
|
|
|
325
447
|
|
|
326
|
-
class union:
|
|
448
|
+
class union(wrapper):
|
|
449
|
+
"""
|
|
450
|
+
An object matches the schema `union(schema1, ..., schemaN)` if it matches
|
|
451
|
+
one of the schemas `schema1, ..., schemaN`.
|
|
452
|
+
"""
|
|
453
|
+
|
|
327
454
|
schemas: tuple[object, ...]
|
|
328
455
|
|
|
329
456
|
def __init__(self, *schemas: object) -> None:
|
|
457
|
+
"""
|
|
458
|
+
:param schemas: collection of schemas, one of which should match
|
|
459
|
+
"""
|
|
330
460
|
self.schemas = schemas
|
|
331
461
|
|
|
332
462
|
def __compile__(self, _deferred_compiles: _mapping | None = None) -> _union:
|
|
@@ -347,22 +477,30 @@ class _intersect(compiled_schema):
|
|
|
347
477
|
|
|
348
478
|
def __validate__(
|
|
349
479
|
self,
|
|
350
|
-
|
|
480
|
+
obj: object,
|
|
351
481
|
name: str = "object",
|
|
352
482
|
strict: bool = True,
|
|
353
483
|
subs: Mapping[str, object] = {},
|
|
354
484
|
) -> str:
|
|
355
485
|
for schema in self.schemas:
|
|
356
|
-
message = schema.__validate__(
|
|
486
|
+
message = schema.__validate__(obj, name=name, strict=strict, subs=subs)
|
|
357
487
|
if message != "":
|
|
358
488
|
return message
|
|
359
489
|
return ""
|
|
360
490
|
|
|
361
491
|
|
|
362
|
-
class intersect:
|
|
492
|
+
class intersect(wrapper):
|
|
493
|
+
"""
|
|
494
|
+
An object matches the schema `intersect(schema1, ..., schemaN)` if it
|
|
495
|
+
matches all the schemas `schema1, ..., schemaN`.
|
|
496
|
+
"""
|
|
497
|
+
|
|
363
498
|
schemas: tuple[object, ...]
|
|
364
499
|
|
|
365
500
|
def __init__(self, *schemas: object) -> None:
|
|
501
|
+
"""
|
|
502
|
+
:param schemas: collection of schemas, all of which should match
|
|
503
|
+
"""
|
|
366
504
|
self.schemas = schemas
|
|
367
505
|
|
|
368
506
|
def __compile__(self, _deferred_compiles: _mapping | None = None) -> _intersect:
|
|
@@ -379,22 +517,30 @@ class _complement(compiled_schema):
|
|
|
379
517
|
|
|
380
518
|
def __validate__(
|
|
381
519
|
self,
|
|
382
|
-
|
|
520
|
+
obj: object,
|
|
383
521
|
name: str = "object",
|
|
384
522
|
strict: bool = True,
|
|
385
523
|
subs: Mapping[str, object] = {},
|
|
386
524
|
) -> str:
|
|
387
|
-
message = self.schema.__validate__(
|
|
525
|
+
message = self.schema.__validate__(obj, name=name, strict=strict, subs=subs)
|
|
388
526
|
if message != "":
|
|
389
527
|
return ""
|
|
390
528
|
else:
|
|
391
529
|
return f"{name} does not match the complemented schema"
|
|
392
530
|
|
|
393
531
|
|
|
394
|
-
class complement:
|
|
532
|
+
class complement(wrapper):
|
|
533
|
+
"""
|
|
534
|
+
An object matches the schema `complement(schema)` if it does not match
|
|
535
|
+
`schema`.
|
|
536
|
+
"""
|
|
537
|
+
|
|
395
538
|
schema: object
|
|
396
539
|
|
|
397
540
|
def __init__(self, schema: object) -> None:
|
|
541
|
+
"""
|
|
542
|
+
:param schema: the schema that should not be matched
|
|
543
|
+
"""
|
|
398
544
|
self.schema = schema
|
|
399
545
|
|
|
400
546
|
def __compile__(self, _deferred_compiles: _mapping | None = None) -> _complement:
|
|
@@ -411,18 +557,27 @@ class _lax(compiled_schema):
|
|
|
411
557
|
|
|
412
558
|
def __validate__(
|
|
413
559
|
self,
|
|
414
|
-
|
|
560
|
+
obj: object,
|
|
415
561
|
name: str = "object",
|
|
416
562
|
strict: bool = True,
|
|
417
563
|
subs: Mapping[str, object] = {},
|
|
418
564
|
) -> str:
|
|
419
|
-
return self.schema.__validate__(
|
|
565
|
+
return self.schema.__validate__(obj, name=name, strict=False, subs=subs)
|
|
566
|
+
|
|
420
567
|
|
|
568
|
+
class lax(wrapper):
|
|
569
|
+
"""
|
|
570
|
+
An object matches the schema `lax(schema)` if it matches `schema` when
|
|
571
|
+
validated with `strict=False`.
|
|
572
|
+
"""
|
|
421
573
|
|
|
422
|
-
class lax:
|
|
423
574
|
schema: object
|
|
424
575
|
|
|
425
576
|
def __init__(self, schema: object) -> None:
|
|
577
|
+
"""
|
|
578
|
+
:param schema: schema that should be validated against with
|
|
579
|
+
`strict=False`
|
|
580
|
+
"""
|
|
426
581
|
self.schema = schema
|
|
427
582
|
|
|
428
583
|
def __compile__(self, _deferred_compiles: _mapping | None = None) -> _lax:
|
|
@@ -439,16 +594,25 @@ class _strict(compiled_schema):
|
|
|
439
594
|
|
|
440
595
|
def __validate__(
|
|
441
596
|
self,
|
|
442
|
-
|
|
597
|
+
obj: object,
|
|
443
598
|
name: str = "object",
|
|
444
599
|
strict: bool = True,
|
|
445
600
|
subs: Mapping[str, object] = {},
|
|
446
601
|
) -> str:
|
|
447
|
-
return self.schema.__validate__(
|
|
602
|
+
return self.schema.__validate__(obj, name=name, strict=True, subs=subs)
|
|
603
|
+
|
|
448
604
|
|
|
605
|
+
class strict(wrapper):
|
|
606
|
+
"""
|
|
607
|
+
An object matches the schema `strict(schema)` if it matches `schema` when
|
|
608
|
+
validated with `strict=True`.
|
|
609
|
+
"""
|
|
449
610
|
|
|
450
|
-
class strict:
|
|
451
611
|
def __init__(self, schema: object) -> None:
|
|
612
|
+
"""
|
|
613
|
+
:param schema: schema that should be validated against with
|
|
614
|
+
`strict=True`
|
|
615
|
+
"""
|
|
452
616
|
self.schema = schema
|
|
453
617
|
|
|
454
618
|
def __compile__(self, _deferred_compiles: _mapping | None = None) -> _strict:
|
|
@@ -473,7 +637,7 @@ class _set_label(compiled_schema):
|
|
|
473
637
|
|
|
474
638
|
def __validate__(
|
|
475
639
|
self,
|
|
476
|
-
|
|
640
|
+
obj: object,
|
|
477
641
|
name: str = "object",
|
|
478
642
|
strict: bool = True,
|
|
479
643
|
subs: Mapping[str, object] = {},
|
|
@@ -492,17 +656,31 @@ class _set_label(compiled_schema):
|
|
|
492
656
|
# known at schema creation time.
|
|
493
657
|
#
|
|
494
658
|
# But the user can always pre-compile subs[key].
|
|
495
|
-
return _validate(subs[key],
|
|
659
|
+
return _validate(subs[key], obj, name=name, strict=True, subs=subs)
|
|
496
660
|
else:
|
|
497
|
-
return self.schema.__validate__(
|
|
661
|
+
return self.schema.__validate__(obj, name=name, strict=True, subs=subs)
|
|
498
662
|
|
|
499
663
|
|
|
500
|
-
class set_label:
|
|
664
|
+
class set_label(wrapper):
|
|
665
|
+
"""
|
|
666
|
+
An object matches the schema `set_label(schema, label1, ..., labelN,
|
|
667
|
+
debug=False)` if it matches `schema`, unless the schema is replaced by a
|
|
668
|
+
different one via the `subs` argument to `validate`.
|
|
669
|
+
"""
|
|
670
|
+
|
|
501
671
|
schema: object
|
|
502
672
|
labels: set[str]
|
|
503
673
|
debug: bool
|
|
504
674
|
|
|
505
675
|
def __init__(self, schema: object, *labels: str, debug: bool = False) -> None:
|
|
676
|
+
"""
|
|
677
|
+
:param schema: the schema that will be labeled
|
|
678
|
+
:param labels: labels that will be attached to the schema
|
|
679
|
+
:param debug: it `True` print a message on the console if the schema
|
|
680
|
+
was changed via substitution
|
|
681
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
682
|
+
found to contain an error
|
|
683
|
+
"""
|
|
506
684
|
self.schema = schema
|
|
507
685
|
for L in labels:
|
|
508
686
|
if not isinstance(L, str):
|
|
@@ -519,19 +697,28 @@ class set_label:
|
|
|
519
697
|
|
|
520
698
|
|
|
521
699
|
class quote(compiled_schema):
|
|
700
|
+
"""
|
|
701
|
+
An object matches the schema `quote(schema)` if it is equal to `schema`.
|
|
702
|
+
For example the schema `str` matches strings but the schema `quote(str)`
|
|
703
|
+
matches the object `str`.
|
|
704
|
+
"""
|
|
705
|
+
|
|
522
706
|
schema: _const
|
|
523
707
|
|
|
524
708
|
def __init__(self, schema: object) -> None:
|
|
709
|
+
"""
|
|
710
|
+
:param schema: the schema to be quoted
|
|
711
|
+
"""
|
|
525
712
|
self.schema = _const(schema, strict_eq=True)
|
|
526
713
|
|
|
527
714
|
def __validate__(
|
|
528
715
|
self,
|
|
529
|
-
|
|
716
|
+
obj: object,
|
|
530
717
|
name: str = "object",
|
|
531
718
|
strict: bool = True,
|
|
532
719
|
subs: Mapping[str, object] = {},
|
|
533
720
|
) -> str:
|
|
534
|
-
return self.schema.__validate__(
|
|
721
|
+
return self.schema.__validate__(obj, name=name, strict=strict, subs=subs)
|
|
535
722
|
|
|
536
723
|
|
|
537
724
|
class _set_name(compiled_schema):
|
|
@@ -552,28 +739,40 @@ class _set_name(compiled_schema):
|
|
|
552
739
|
|
|
553
740
|
def __validate__(
|
|
554
741
|
self,
|
|
555
|
-
|
|
742
|
+
obj: object,
|
|
556
743
|
name: str = "object",
|
|
557
744
|
strict: bool = True,
|
|
558
745
|
subs: Mapping[str, object] = {},
|
|
559
746
|
) -> str:
|
|
560
|
-
message = self.schema.__validate__(
|
|
747
|
+
message = self.schema.__validate__(obj, name=name, strict=strict, subs=subs)
|
|
561
748
|
if message != "":
|
|
562
749
|
if not self.reason:
|
|
563
|
-
return _wrong_type_message(
|
|
750
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
564
751
|
else:
|
|
565
752
|
return _wrong_type_message(
|
|
566
|
-
|
|
753
|
+
obj, name, self.__name__, explanation=message, skip_value=True
|
|
567
754
|
)
|
|
568
755
|
return ""
|
|
569
756
|
|
|
570
757
|
|
|
571
|
-
class set_name:
|
|
758
|
+
class set_name(wrapper):
|
|
759
|
+
"""
|
|
760
|
+
An object matches the schema `set_name(schema, name, reason=False)` if it
|
|
761
|
+
matches `schema`, but the `name` argument will be used in non-validation
|
|
762
|
+
messages.
|
|
763
|
+
"""
|
|
764
|
+
|
|
572
765
|
reason: bool
|
|
573
766
|
schema: object
|
|
574
767
|
name: str
|
|
575
768
|
|
|
576
769
|
def __init__(self, schema: object, name: str, reason: bool = False) -> None:
|
|
770
|
+
"""
|
|
771
|
+
:param schema: the original schema
|
|
772
|
+
:param name: name for use in non-validation messages
|
|
773
|
+
:param reason: indicates whether the original non-validation message
|
|
774
|
+
should be suppressed
|
|
775
|
+
"""
|
|
577
776
|
if not isinstance(name, str):
|
|
578
777
|
raise SchemaError(f"The name {_c(name)} is not a string")
|
|
579
778
|
self.schema = schema
|
|
@@ -590,6 +789,10 @@ class set_name:
|
|
|
590
789
|
|
|
591
790
|
|
|
592
791
|
class regex(compiled_schema):
|
|
792
|
+
"""
|
|
793
|
+
This matches the strings which match the given pattern.
|
|
794
|
+
"""
|
|
795
|
+
|
|
593
796
|
regex: str
|
|
594
797
|
fullmatch: bool
|
|
595
798
|
__name__: str
|
|
@@ -602,6 +805,17 @@ class regex(compiled_schema):
|
|
|
602
805
|
fullmatch: bool = True,
|
|
603
806
|
flags: int = 0,
|
|
604
807
|
) -> None:
|
|
808
|
+
"""
|
|
809
|
+
:param regex: the regular expression pattern
|
|
810
|
+
:param name: common name for the pattern that will be used in
|
|
811
|
+
non-validation messages
|
|
812
|
+
:param fullmatch: indicates whether or not the full string should be
|
|
813
|
+
matched
|
|
814
|
+
:param flags: the flags argument used when invoking `re.compile`
|
|
815
|
+
|
|
816
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
817
|
+
found to contain an error
|
|
818
|
+
"""
|
|
605
819
|
self.regex = regex
|
|
606
820
|
self.fullmatch = fullmatch
|
|
607
821
|
if name is not None:
|
|
@@ -623,28 +837,42 @@ class regex(compiled_schema):
|
|
|
623
837
|
|
|
624
838
|
def __validate__(
|
|
625
839
|
self,
|
|
626
|
-
|
|
840
|
+
obj: object,
|
|
627
841
|
name: str = "object",
|
|
628
842
|
strict: bool = True,
|
|
629
843
|
subs: Mapping[str, object] = {},
|
|
630
844
|
) -> str:
|
|
631
|
-
if not isinstance(
|
|
632
|
-
return _wrong_type_message(
|
|
845
|
+
if not isinstance(obj, str):
|
|
846
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
633
847
|
try:
|
|
634
|
-
if self.fullmatch and self.pattern.fullmatch(
|
|
848
|
+
if self.fullmatch and self.pattern.fullmatch(obj):
|
|
635
849
|
return ""
|
|
636
|
-
elif not self.fullmatch and self.pattern.match(
|
|
850
|
+
elif not self.fullmatch and self.pattern.match(obj):
|
|
637
851
|
return ""
|
|
638
852
|
except Exception:
|
|
639
853
|
pass
|
|
640
|
-
return _wrong_type_message(
|
|
854
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
641
855
|
|
|
642
856
|
|
|
643
857
|
class glob(compiled_schema):
|
|
858
|
+
"""
|
|
859
|
+
Unix style filename matching. This is implemented using
|
|
860
|
+
`pathlib.PurePath().match()`.
|
|
861
|
+
"""
|
|
862
|
+
|
|
644
863
|
pattern: str
|
|
645
864
|
__name__: str
|
|
646
865
|
|
|
647
866
|
def __init__(self, pattern: str, name: str | None = None) -> None:
|
|
867
|
+
"""
|
|
868
|
+
:param pattern: the wild card pattern to match
|
|
869
|
+
:param name: common name for the pattern that will be used in
|
|
870
|
+
non-validation messages
|
|
871
|
+
|
|
872
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
873
|
+
found to contain an error
|
|
874
|
+
"""
|
|
875
|
+
|
|
648
876
|
self.pattern = pattern
|
|
649
877
|
|
|
650
878
|
if name is None:
|
|
@@ -662,27 +890,39 @@ class glob(compiled_schema):
|
|
|
662
890
|
|
|
663
891
|
def __validate__(
|
|
664
892
|
self,
|
|
665
|
-
|
|
893
|
+
obj: object,
|
|
666
894
|
name: str = "object",
|
|
667
895
|
strict: bool = True,
|
|
668
896
|
subs: Mapping[str, object] = {},
|
|
669
897
|
) -> str:
|
|
670
|
-
if not isinstance(
|
|
671
|
-
return _wrong_type_message(
|
|
898
|
+
if not isinstance(obj, str):
|
|
899
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
672
900
|
try:
|
|
673
|
-
if pathlib.PurePath(
|
|
901
|
+
if pathlib.PurePath(obj).match(self.pattern):
|
|
674
902
|
return ""
|
|
675
903
|
else:
|
|
676
|
-
return _wrong_type_message(
|
|
904
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
677
905
|
except Exception as e:
|
|
678
|
-
return _wrong_type_message(
|
|
906
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
679
907
|
|
|
680
908
|
|
|
681
909
|
class magic(compiled_schema):
|
|
910
|
+
"""
|
|
911
|
+
Checks if a buffer (for example a string or a byte array) has the given
|
|
912
|
+
mime type. This is implemented using the `python-magic` package.
|
|
913
|
+
"""
|
|
914
|
+
|
|
682
915
|
mime_type: str
|
|
683
916
|
__name__: str
|
|
684
917
|
|
|
685
918
|
def __init__(self, mime_type: str, name: str | None = None) -> None:
|
|
919
|
+
"""
|
|
920
|
+
:param mime_type: the mime type
|
|
921
|
+
:param name: common name to refer to this mime type
|
|
922
|
+
|
|
923
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
924
|
+
found to contain an error
|
|
925
|
+
"""
|
|
686
926
|
if not HAS_MAGIC:
|
|
687
927
|
raise SchemaError("Failed to load python-magic")
|
|
688
928
|
|
|
@@ -698,28 +938,32 @@ class magic(compiled_schema):
|
|
|
698
938
|
|
|
699
939
|
def __validate__(
|
|
700
940
|
self,
|
|
701
|
-
|
|
941
|
+
obj: object,
|
|
702
942
|
name: str = "object",
|
|
703
943
|
strict: bool = True,
|
|
704
944
|
subs: Mapping[str, object] = {},
|
|
705
945
|
) -> str:
|
|
706
|
-
if not isinstance(
|
|
707
|
-
return _wrong_type_message(
|
|
946
|
+
if not isinstance(obj, (str, bytes)):
|
|
947
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
708
948
|
try:
|
|
709
|
-
|
|
949
|
+
objmime_type = magic_.from_buffer(obj, mime=True)
|
|
710
950
|
except Exception as e:
|
|
711
|
-
return _wrong_type_message(
|
|
712
|
-
if
|
|
951
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
952
|
+
if objmime_type != self.mime_type:
|
|
713
953
|
return _wrong_type_message(
|
|
714
|
-
|
|
954
|
+
obj,
|
|
715
955
|
name,
|
|
716
956
|
self.__name__,
|
|
717
|
-
f"{repr(
|
|
957
|
+
f"{repr(objmime_type)} is different from {repr(self.mime_type)}",
|
|
718
958
|
)
|
|
719
959
|
return ""
|
|
720
960
|
|
|
721
961
|
|
|
722
962
|
class div(compiled_schema):
|
|
963
|
+
"""
|
|
964
|
+
This matches the integers `x` such that `(x - remainder) % divisor` == 0.
|
|
965
|
+
"""
|
|
966
|
+
|
|
723
967
|
divisor: int
|
|
724
968
|
remainder: int
|
|
725
969
|
__name__: str
|
|
@@ -727,6 +971,15 @@ class div(compiled_schema):
|
|
|
727
971
|
def __init__(
|
|
728
972
|
self, divisor: int, remainder: int = 0, name: str | None = None
|
|
729
973
|
) -> None:
|
|
974
|
+
"""
|
|
975
|
+
:param divisor: the divisor
|
|
976
|
+
:param remainder: the remainer
|
|
977
|
+
:param name: common name (e.g. `even` or `odd`) that will be used in
|
|
978
|
+
non-validation messages
|
|
979
|
+
|
|
980
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
981
|
+
found to contain an error
|
|
982
|
+
"""
|
|
730
983
|
if not isinstance(divisor, int):
|
|
731
984
|
raise SchemaError(f"The divisor {repr(divisor)} is not an integer")
|
|
732
985
|
if divisor == 0:
|
|
@@ -747,20 +1000,25 @@ class div(compiled_schema):
|
|
|
747
1000
|
|
|
748
1001
|
def __validate__(
|
|
749
1002
|
self,
|
|
750
|
-
|
|
1003
|
+
obj: object,
|
|
751
1004
|
name: str = "object",
|
|
752
1005
|
strict: bool = True,
|
|
753
1006
|
subs: Mapping[str, object] = {},
|
|
754
1007
|
) -> str:
|
|
755
|
-
if not isinstance(
|
|
756
|
-
return _wrong_type_message(
|
|
757
|
-
elif (
|
|
1008
|
+
if not isinstance(obj, int):
|
|
1009
|
+
return _wrong_type_message(obj, name, "int")
|
|
1010
|
+
elif (obj - self.remainder) % self.divisor == 0:
|
|
758
1011
|
return ""
|
|
759
1012
|
else:
|
|
760
|
-
return _wrong_type_message(
|
|
1013
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
761
1014
|
|
|
762
1015
|
|
|
763
1016
|
class close_to(compiled_schema):
|
|
1017
|
+
"""
|
|
1018
|
+
This matches the real numbers that are close to `x` in the sense of
|
|
1019
|
+
`math.isclose`.
|
|
1020
|
+
"""
|
|
1021
|
+
|
|
764
1022
|
kw: dict[str, float]
|
|
765
1023
|
x: int | float
|
|
766
1024
|
__name__: str
|
|
@@ -771,6 +1029,13 @@ class close_to(compiled_schema):
|
|
|
771
1029
|
rel_tol: int | float | None = None,
|
|
772
1030
|
abs_tol: int | float | None = None,
|
|
773
1031
|
) -> None:
|
|
1032
|
+
"""
|
|
1033
|
+
:param rel_tol: the maximal allowed relative deviation
|
|
1034
|
+
:param abs_tol: the maximal allowed absolute deviation
|
|
1035
|
+
|
|
1036
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
1037
|
+
found to contain an error
|
|
1038
|
+
"""
|
|
774
1039
|
self.kw = {}
|
|
775
1040
|
if not isinstance(x, (int, float)):
|
|
776
1041
|
raise SchemaError(f"{repr(x)} is not a number")
|
|
@@ -794,23 +1059,33 @@ class close_to(compiled_schema):
|
|
|
794
1059
|
|
|
795
1060
|
def __validate__(
|
|
796
1061
|
self,
|
|
797
|
-
|
|
1062
|
+
obj: object,
|
|
798
1063
|
name: str = "object",
|
|
799
1064
|
strict: bool = True,
|
|
800
1065
|
subs: Mapping[str, object] = {},
|
|
801
1066
|
) -> str:
|
|
802
|
-
if not isinstance(
|
|
803
|
-
return _wrong_type_message(
|
|
804
|
-
elif math.isclose(
|
|
1067
|
+
if not isinstance(obj, (float, int)):
|
|
1068
|
+
return _wrong_type_message(obj, name, "number")
|
|
1069
|
+
elif math.isclose(obj, self.x, **self.kw):
|
|
805
1070
|
return ""
|
|
806
1071
|
else:
|
|
807
|
-
return _wrong_type_message(
|
|
1072
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
808
1073
|
|
|
809
1074
|
|
|
810
1075
|
class gt(compiled_schema):
|
|
1076
|
+
"""
|
|
1077
|
+
This checks if `object > lb`.
|
|
1078
|
+
"""
|
|
1079
|
+
|
|
811
1080
|
lb: comparable
|
|
812
1081
|
|
|
813
1082
|
def __init__(self, lb: comparable) -> None:
|
|
1083
|
+
"""
|
|
1084
|
+
:param lb: the strict lower bound
|
|
1085
|
+
|
|
1086
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
1087
|
+
found to contain an error
|
|
1088
|
+
"""
|
|
814
1089
|
try:
|
|
815
1090
|
lb <= lb
|
|
816
1091
|
except Exception:
|
|
@@ -819,29 +1094,39 @@ class gt(compiled_schema):
|
|
|
819
1094
|
) from None
|
|
820
1095
|
self.lb = lb
|
|
821
1096
|
|
|
822
|
-
def message(self, name: str,
|
|
823
|
-
return f"{name} (value:{_c(
|
|
1097
|
+
def message(self, name: str, obj: object) -> str:
|
|
1098
|
+
return f"{name} (value:{_c(obj)}) is not strictly greater than {self.lb}"
|
|
824
1099
|
|
|
825
1100
|
def __validate__(
|
|
826
1101
|
self,
|
|
827
|
-
|
|
1102
|
+
obj: object,
|
|
828
1103
|
name: str = "object",
|
|
829
1104
|
strict: bool = True,
|
|
830
1105
|
subs: Mapping[str, object] = {},
|
|
831
1106
|
) -> str:
|
|
832
1107
|
try:
|
|
833
|
-
if self.lb <
|
|
1108
|
+
if self.lb < obj:
|
|
834
1109
|
return ""
|
|
835
1110
|
else:
|
|
836
|
-
return self.message(name,
|
|
1111
|
+
return self.message(name, obj)
|
|
837
1112
|
except Exception as e:
|
|
838
|
-
return f"{self.message(name,
|
|
1113
|
+
return f"{self.message(name, obj)}: {str(e)}"
|
|
839
1114
|
|
|
840
1115
|
|
|
841
1116
|
class ge(compiled_schema):
|
|
1117
|
+
"""
|
|
1118
|
+
This checks if `object >= lb`.
|
|
1119
|
+
"""
|
|
1120
|
+
|
|
842
1121
|
lb: comparable
|
|
843
1122
|
|
|
844
1123
|
def __init__(self, lb: comparable) -> None:
|
|
1124
|
+
"""
|
|
1125
|
+
:param lb: the lower bound
|
|
1126
|
+
|
|
1127
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
1128
|
+
found to contain an error
|
|
1129
|
+
"""
|
|
845
1130
|
try:
|
|
846
1131
|
lb <= lb
|
|
847
1132
|
except Exception:
|
|
@@ -850,29 +1135,39 @@ class ge(compiled_schema):
|
|
|
850
1135
|
) from None
|
|
851
1136
|
self.lb = lb
|
|
852
1137
|
|
|
853
|
-
def message(self, name: str,
|
|
854
|
-
return f"{name} (value:{_c(
|
|
1138
|
+
def message(self, name: str, obj: object) -> str:
|
|
1139
|
+
return f"{name} (value:{_c(obj)}) is not greater than or equal to {self.lb}"
|
|
855
1140
|
|
|
856
1141
|
def __validate__(
|
|
857
1142
|
self,
|
|
858
|
-
|
|
1143
|
+
obj: object,
|
|
859
1144
|
name: str = "object",
|
|
860
1145
|
strict: bool = True,
|
|
861
1146
|
subs: Mapping[str, object] = {},
|
|
862
1147
|
) -> str:
|
|
863
1148
|
try:
|
|
864
|
-
if self.lb <=
|
|
1149
|
+
if self.lb <= obj:
|
|
865
1150
|
return ""
|
|
866
1151
|
else:
|
|
867
|
-
return self.message(name,
|
|
1152
|
+
return self.message(name, obj)
|
|
868
1153
|
except Exception as e:
|
|
869
|
-
return f"{self.message(name,
|
|
1154
|
+
return f"{self.message(name, obj)}: {str(e)}"
|
|
870
1155
|
|
|
871
1156
|
|
|
872
1157
|
class lt(compiled_schema):
|
|
1158
|
+
"""
|
|
1159
|
+
This checks if `object < ub`.
|
|
1160
|
+
"""
|
|
1161
|
+
|
|
873
1162
|
ub: comparable
|
|
874
1163
|
|
|
875
1164
|
def __init__(self, ub: comparable) -> None:
|
|
1165
|
+
"""
|
|
1166
|
+
:param ub: the strict upper bound
|
|
1167
|
+
|
|
1168
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
1169
|
+
found to contain an error
|
|
1170
|
+
"""
|
|
876
1171
|
try:
|
|
877
1172
|
ub <= ub
|
|
878
1173
|
except Exception:
|
|
@@ -881,29 +1176,39 @@ class lt(compiled_schema):
|
|
|
881
1176
|
) from None
|
|
882
1177
|
self.ub = ub
|
|
883
1178
|
|
|
884
|
-
def message(self, name: str,
|
|
885
|
-
return f"{name} (value:{_c(
|
|
1179
|
+
def message(self, name: str, obj: object) -> str:
|
|
1180
|
+
return f"{name} (value:{_c(obj)}) is not strictly less than {self.ub}"
|
|
886
1181
|
|
|
887
1182
|
def __validate__(
|
|
888
1183
|
self,
|
|
889
|
-
|
|
1184
|
+
obj: object,
|
|
890
1185
|
name: str = "object",
|
|
891
1186
|
strict: bool = True,
|
|
892
1187
|
subs: Mapping[str, object] = {},
|
|
893
1188
|
) -> str:
|
|
894
1189
|
try:
|
|
895
|
-
if self.ub >
|
|
1190
|
+
if self.ub > obj:
|
|
896
1191
|
return ""
|
|
897
1192
|
else:
|
|
898
|
-
return self.message(name,
|
|
1193
|
+
return self.message(name, obj)
|
|
899
1194
|
except Exception as e:
|
|
900
|
-
return f"{self.message(name,
|
|
1195
|
+
return f"{self.message(name, obj)}: {str(e)}"
|
|
901
1196
|
|
|
902
1197
|
|
|
903
1198
|
class le(compiled_schema):
|
|
1199
|
+
"""
|
|
1200
|
+
This checks if `object <= ub`.
|
|
1201
|
+
"""
|
|
1202
|
+
|
|
904
1203
|
ub: comparable
|
|
905
1204
|
|
|
906
1205
|
def __init__(self, ub: comparable) -> None:
|
|
1206
|
+
"""
|
|
1207
|
+
:param ub: the upper bound
|
|
1208
|
+
|
|
1209
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
1210
|
+
found to contain an error
|
|
1211
|
+
"""
|
|
907
1212
|
try:
|
|
908
1213
|
ub <= ub
|
|
909
1214
|
except Exception:
|
|
@@ -912,26 +1217,30 @@ class le(compiled_schema):
|
|
|
912
1217
|
) from None
|
|
913
1218
|
self.ub = ub
|
|
914
1219
|
|
|
915
|
-
def message(self, name: str,
|
|
916
|
-
return f"{name} (value:{_c(
|
|
1220
|
+
def message(self, name: str, obj: object) -> str:
|
|
1221
|
+
return f"{name} (value:{_c(obj)}) is not less than or equal to {self.ub}"
|
|
917
1222
|
|
|
918
1223
|
def __validate__(
|
|
919
1224
|
self,
|
|
920
|
-
|
|
1225
|
+
obj: object,
|
|
921
1226
|
name: str = "object",
|
|
922
1227
|
strict: bool = True,
|
|
923
1228
|
subs: Mapping[str, object] = {},
|
|
924
1229
|
) -> str:
|
|
925
1230
|
try:
|
|
926
|
-
if self.ub >=
|
|
1231
|
+
if self.ub >= obj:
|
|
927
1232
|
return ""
|
|
928
1233
|
else:
|
|
929
|
-
return self.message(name,
|
|
1234
|
+
return self.message(name, obj)
|
|
930
1235
|
except Exception as e:
|
|
931
|
-
return f"{self.message(name,
|
|
1236
|
+
return f"{self.message(name, obj)}: {str(e)}"
|
|
932
1237
|
|
|
933
1238
|
|
|
934
1239
|
class interval(compiled_schema):
|
|
1240
|
+
"""
|
|
1241
|
+
This checks if `lb <= object <= ub`, provided the comparisons make sense.
|
|
1242
|
+
"""
|
|
1243
|
+
|
|
935
1244
|
lb_s: str
|
|
936
1245
|
ub_s: str
|
|
937
1246
|
|
|
@@ -942,7 +1251,15 @@ class interval(compiled_schema):
|
|
|
942
1251
|
strict_lb: bool = False,
|
|
943
1252
|
strict_ub: bool = False,
|
|
944
1253
|
) -> None:
|
|
945
|
-
|
|
1254
|
+
"""
|
|
1255
|
+
:param lb: lower bound; ... (ellipsis) means no lower bound
|
|
1256
|
+
:param ub: upper bound; ... (ellipsis) means no upper bound
|
|
1257
|
+
:param strict_lb: if True use a strict lower bound
|
|
1258
|
+
:param strict_ub: if True use a strict upper bound
|
|
1259
|
+
|
|
1260
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
1261
|
+
found to contain an error
|
|
1262
|
+
"""
|
|
946
1263
|
self.lb_s = "..." if lb == ... else repr(lb)
|
|
947
1264
|
self.ub_s = "..." if ub == ... else repr(ub)
|
|
948
1265
|
|
|
@@ -995,9 +1312,22 @@ class interval(compiled_schema):
|
|
|
995
1312
|
|
|
996
1313
|
|
|
997
1314
|
class size(compiled_schema):
|
|
1315
|
+
"""
|
|
1316
|
+
Matches the objects (which support `len()` such as strings or lists) whose
|
|
1317
|
+
length is in the interval `[lb, ub]`.
|
|
1318
|
+
"""
|
|
1319
|
+
|
|
998
1320
|
interval_: interval
|
|
999
1321
|
|
|
1000
1322
|
def __init__(self, lb: int, ub: int | types.EllipsisType | None = None) -> None:
|
|
1323
|
+
"""
|
|
1324
|
+
:param lb: the lower bound for the length
|
|
1325
|
+
:param ub: the upper bound for the length; ... (ellipsis) means that
|
|
1326
|
+
there is no upper bound; `None` means `lb==ub`
|
|
1327
|
+
|
|
1328
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
1329
|
+
found to contain an error
|
|
1330
|
+
"""
|
|
1001
1331
|
if ub is None:
|
|
1002
1332
|
ub = lb
|
|
1003
1333
|
if not isinstance(lb, int):
|
|
@@ -1021,15 +1351,15 @@ class size(compiled_schema):
|
|
|
1021
1351
|
|
|
1022
1352
|
def __validate__(
|
|
1023
1353
|
self,
|
|
1024
|
-
|
|
1354
|
+
obj: object,
|
|
1025
1355
|
name: str = "object",
|
|
1026
1356
|
strict: bool = True,
|
|
1027
1357
|
subs: Mapping[str, object] = {},
|
|
1028
1358
|
) -> str:
|
|
1029
|
-
if not isinstance(
|
|
1030
|
-
return f"{name} (value:{_c(
|
|
1359
|
+
if not isinstance(obj, Sized):
|
|
1360
|
+
return f"{name} (value:{_c(obj)}) has no len()"
|
|
1031
1361
|
|
|
1032
|
-
L = len(
|
|
1362
|
+
L = len(obj)
|
|
1033
1363
|
|
|
1034
1364
|
return self.interval_.__validate__(L, f"len({name})", strict, subs)
|
|
1035
1365
|
|
|
@@ -1044,7 +1374,7 @@ class _deferred(compiled_schema):
|
|
|
1044
1374
|
|
|
1045
1375
|
def __validate__(
|
|
1046
1376
|
self,
|
|
1047
|
-
|
|
1377
|
+
obj: object,
|
|
1048
1378
|
name: str = "object",
|
|
1049
1379
|
strict: bool = True,
|
|
1050
1380
|
subs: Mapping[str, object] = {},
|
|
@@ -1052,7 +1382,7 @@ class _deferred(compiled_schema):
|
|
|
1052
1382
|
if self.key not in self.collection:
|
|
1053
1383
|
raise ValidationError(f"{name}: key {self.key} is unknown")
|
|
1054
1384
|
return self.collection[self.key].__validate__(
|
|
1055
|
-
|
|
1385
|
+
obj, name=name, strict=strict, subs=subs
|
|
1056
1386
|
)
|
|
1057
1387
|
|
|
1058
1388
|
|
|
@@ -1092,12 +1422,32 @@ class _validate_schema(compiled_schema):
|
|
|
1092
1422
|
|
|
1093
1423
|
|
|
1094
1424
|
def compile(schema: object) -> compiled_schema:
|
|
1425
|
+
"""
|
|
1426
|
+
Compiles a schema. Internally invokes :py:func:`vtjson._compile`.
|
|
1427
|
+
|
|
1428
|
+
:param schema: the schema that should be compiled
|
|
1429
|
+
|
|
1430
|
+
:raises SchemaError: exception thrown when the schema definition is found
|
|
1431
|
+
to contain an error
|
|
1432
|
+
"""
|
|
1095
1433
|
return _compile(schema, _deferred_compiles=None)
|
|
1096
1434
|
|
|
1097
1435
|
|
|
1098
1436
|
def _compile(
|
|
1099
1437
|
schema: object, _deferred_compiles: _mapping | None = None
|
|
1100
1438
|
) -> compiled_schema:
|
|
1439
|
+
"""
|
|
1440
|
+
Compiles a schema.
|
|
1441
|
+
|
|
1442
|
+
:param schema: the schema that should be compiled
|
|
1443
|
+
:param _deferred_compiles: an opaque data structure used for handling
|
|
1444
|
+
recursive schemas; it should be passed unmodifed to any internal
|
|
1445
|
+
invocations of :py:func:`vtjson._compile` or
|
|
1446
|
+
:py:meth:`vtjson.wrapper.__compile__`
|
|
1447
|
+
|
|
1448
|
+
:raises SchemaError: exception thrown when the schema definition is found
|
|
1449
|
+
to contain an error
|
|
1450
|
+
"""
|
|
1101
1451
|
if _deferred_compiles is None:
|
|
1102
1452
|
_deferred_compiles = _mapping()
|
|
1103
1453
|
# avoid infinite loop in case of a recursive schema
|
|
@@ -1123,7 +1473,7 @@ def _compile(
|
|
|
1123
1473
|
) from None
|
|
1124
1474
|
elif hasattr(schema, "__validate__"):
|
|
1125
1475
|
ret = _validate_schema(schema)
|
|
1126
|
-
elif
|
|
1476
|
+
elif isinstance(schema, wrapper):
|
|
1127
1477
|
ret = schema.__compile__(_deferred_compiles=_deferred_compiles)
|
|
1128
1478
|
elif isinstance(schema, compiled_schema):
|
|
1129
1479
|
ret = schema
|
|
@@ -1194,24 +1544,41 @@ def _compile(
|
|
|
1194
1544
|
|
|
1195
1545
|
def _validate(
|
|
1196
1546
|
schema: object,
|
|
1197
|
-
|
|
1547
|
+
obj: object,
|
|
1198
1548
|
name: str = "object",
|
|
1199
1549
|
strict: bool = True,
|
|
1200
1550
|
subs: Mapping[str, object] = {},
|
|
1201
1551
|
) -> str:
|
|
1202
|
-
return compile(schema).__validate__(
|
|
1552
|
+
return compile(schema).__validate__(obj, name=name, strict=strict, subs=subs)
|
|
1203
1553
|
|
|
1204
1554
|
|
|
1205
1555
|
def validate(
|
|
1206
1556
|
schema: object,
|
|
1207
|
-
|
|
1557
|
+
obj: object,
|
|
1208
1558
|
name: str = "object",
|
|
1209
1559
|
strict: bool = True,
|
|
1210
1560
|
subs: Mapping[str, object] = {},
|
|
1211
1561
|
) -> None:
|
|
1562
|
+
"""
|
|
1563
|
+
Validates the given object against the given schema.
|
|
1564
|
+
|
|
1565
|
+
:param schema: the given schema
|
|
1566
|
+
:param obj: the object to be validated
|
|
1567
|
+
:param name: common name for the object to be validated; used in
|
|
1568
|
+
non-validation messages
|
|
1569
|
+
:param strict: indicates whether or not the object being validated is
|
|
1570
|
+
allowed to have keys/entries which are not in the schema
|
|
1571
|
+
:param subs: a dictionary whose keys are labels and whose values are
|
|
1572
|
+
substitution schemas for schemas with those labels
|
|
1573
|
+
:raises ValidationError: exception thrown when the object does not
|
|
1574
|
+
validate; the exception message contains an explanation about what went
|
|
1575
|
+
wrong
|
|
1576
|
+
:raises SchemaError: exception thrown when the schema definition is found
|
|
1577
|
+
to contain an error
|
|
1578
|
+
"""
|
|
1212
1579
|
message = _validate(
|
|
1213
1580
|
schema,
|
|
1214
|
-
|
|
1581
|
+
obj,
|
|
1215
1582
|
name=name,
|
|
1216
1583
|
strict=strict,
|
|
1217
1584
|
subs=subs,
|
|
@@ -1224,7 +1591,10 @@ def validate(
|
|
|
1224
1591
|
|
|
1225
1592
|
|
|
1226
1593
|
class number(compiled_schema):
|
|
1227
|
-
|
|
1594
|
+
"""
|
|
1595
|
+
A deprecated alias for `float`.
|
|
1596
|
+
"""
|
|
1597
|
+
|
|
1228
1598
|
def __init__(self) -> None:
|
|
1229
1599
|
warnings.warn(
|
|
1230
1600
|
"The schema 'number' is deprecated. Use 'float' instead.",
|
|
@@ -1233,21 +1603,31 @@ class number(compiled_schema):
|
|
|
1233
1603
|
|
|
1234
1604
|
def __validate__(
|
|
1235
1605
|
self,
|
|
1236
|
-
|
|
1606
|
+
obj: object,
|
|
1237
1607
|
name: str = "object",
|
|
1238
1608
|
strict: bool = True,
|
|
1239
1609
|
subs: Mapping[str, object] = {},
|
|
1240
1610
|
) -> str:
|
|
1241
|
-
if isinstance(
|
|
1611
|
+
if isinstance(obj, (int, float)):
|
|
1242
1612
|
return ""
|
|
1243
1613
|
else:
|
|
1244
|
-
return _wrong_type_message(
|
|
1614
|
+
return _wrong_type_message(obj, name, "number")
|
|
1245
1615
|
|
|
1246
1616
|
|
|
1247
1617
|
class email(compiled_schema):
|
|
1618
|
+
"""
|
|
1619
|
+
Checks if the object is a valid email address. This uses the package
|
|
1620
|
+
`email_validator`. The `email` schema accepts the same options as
|
|
1621
|
+
`validate_email` in loc. cit.
|
|
1622
|
+
"""
|
|
1623
|
+
|
|
1248
1624
|
kw: dict[str, Any]
|
|
1249
1625
|
|
|
1250
1626
|
def __init__(self, **kw: Any) -> None:
|
|
1627
|
+
"""
|
|
1628
|
+
:param kw: optional keyword arguments to be forwarded to
|
|
1629
|
+
`email_validator.validate_email`
|
|
1630
|
+
"""
|
|
1251
1631
|
self.kw = kw
|
|
1252
1632
|
if "dns_resolver" not in kw:
|
|
1253
1633
|
self.kw["dns_resolver"] = _get_dns_resolver()
|
|
@@ -1256,28 +1636,34 @@ class email(compiled_schema):
|
|
|
1256
1636
|
|
|
1257
1637
|
def __validate__(
|
|
1258
1638
|
self,
|
|
1259
|
-
|
|
1639
|
+
obj: object,
|
|
1260
1640
|
name: str = "object",
|
|
1261
1641
|
strict: bool = True,
|
|
1262
1642
|
subs: Mapping[str, object] = {},
|
|
1263
1643
|
) -> str:
|
|
1264
|
-
if not isinstance(
|
|
1265
|
-
return _wrong_type_message(
|
|
1266
|
-
object_, name, "email", f"{_c(object_)} is not a string"
|
|
1267
|
-
)
|
|
1644
|
+
if not isinstance(obj, str):
|
|
1645
|
+
return _wrong_type_message(obj, name, "email", f"{_c(obj)} is not a string")
|
|
1268
1646
|
try:
|
|
1269
|
-
email_validator.validate_email(
|
|
1647
|
+
email_validator.validate_email(obj, **self.kw)
|
|
1270
1648
|
return ""
|
|
1271
1649
|
except Exception as e:
|
|
1272
|
-
return _wrong_type_message(
|
|
1650
|
+
return _wrong_type_message(obj, name, "email", str(e))
|
|
1273
1651
|
|
|
1274
1652
|
|
|
1275
1653
|
class ip_address(compiled_schema):
|
|
1654
|
+
"""
|
|
1655
|
+
Matches ip addresses of the specified version which can be 4, 6 or None.
|
|
1656
|
+
"""
|
|
1276
1657
|
|
|
1277
1658
|
__name__: str
|
|
1278
1659
|
method: Callable[[Any], Any]
|
|
1279
1660
|
|
|
1280
|
-
def __init__(self, version: Literal[4, 6
|
|
1661
|
+
def __init__(self, version: Literal[4, 6, None] = None) -> None:
|
|
1662
|
+
"""
|
|
1663
|
+
:param version: the version of the ip protocol
|
|
1664
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
1665
|
+
found to contain an error
|
|
1666
|
+
"""
|
|
1281
1667
|
if version is not None and version not in (4, 6):
|
|
1282
1668
|
raise SchemaError("version is not 4 or 6")
|
|
1283
1669
|
if version is None:
|
|
@@ -1293,41 +1679,53 @@ class ip_address(compiled_schema):
|
|
|
1293
1679
|
|
|
1294
1680
|
def __validate__(
|
|
1295
1681
|
self,
|
|
1296
|
-
|
|
1682
|
+
obj: object,
|
|
1297
1683
|
name: str = "object",
|
|
1298
1684
|
strict: bool = True,
|
|
1299
1685
|
subs: Mapping[str, object] = {},
|
|
1300
1686
|
) -> str:
|
|
1301
|
-
if not isinstance(
|
|
1302
|
-
return _wrong_type_message(
|
|
1687
|
+
if not isinstance(obj, (int, str, bytes)):
|
|
1688
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1303
1689
|
try:
|
|
1304
|
-
self.method(
|
|
1690
|
+
self.method(obj)
|
|
1305
1691
|
except ValueError as e:
|
|
1306
|
-
return _wrong_type_message(
|
|
1692
|
+
return _wrong_type_message(obj, name, self.__name__, explanation=str(e))
|
|
1307
1693
|
return ""
|
|
1308
1694
|
|
|
1309
1695
|
|
|
1310
1696
|
class url(compiled_schema):
|
|
1697
|
+
"""
|
|
1698
|
+
Matches valid urls.
|
|
1699
|
+
"""
|
|
1700
|
+
|
|
1311
1701
|
def __validate__(
|
|
1312
1702
|
self,
|
|
1313
|
-
|
|
1703
|
+
obj: object,
|
|
1314
1704
|
name: str = "object",
|
|
1315
1705
|
strict: bool = True,
|
|
1316
1706
|
subs: Mapping[str, object] = {},
|
|
1317
1707
|
) -> str:
|
|
1318
|
-
if not isinstance(
|
|
1319
|
-
return _wrong_type_message(
|
|
1320
|
-
result = urllib.parse.urlparse(
|
|
1708
|
+
if not isinstance(obj, str):
|
|
1709
|
+
return _wrong_type_message(obj, name, "url")
|
|
1710
|
+
result = urllib.parse.urlparse(obj)
|
|
1321
1711
|
if all([result.scheme, result.netloc]):
|
|
1322
1712
|
return ""
|
|
1323
|
-
return _wrong_type_message(
|
|
1713
|
+
return _wrong_type_message(obj, name, "url")
|
|
1324
1714
|
|
|
1325
1715
|
|
|
1326
1716
|
class date_time(compiled_schema):
|
|
1717
|
+
"""
|
|
1718
|
+
Without argument this represents an ISO 8601 date-time. The `format`
|
|
1719
|
+
argument represents a format string for `strftime`.
|
|
1720
|
+
"""
|
|
1721
|
+
|
|
1327
1722
|
format: str | None
|
|
1328
1723
|
__name__: str
|
|
1329
1724
|
|
|
1330
1725
|
def __init__(self, format: str | None = None) -> None:
|
|
1726
|
+
"""
|
|
1727
|
+
:param format: format string for `strftime`
|
|
1728
|
+
"""
|
|
1331
1729
|
self.format = format
|
|
1332
1730
|
if format is not None:
|
|
1333
1731
|
self.__name__ = f"date_time({repr(format)})"
|
|
@@ -1336,75 +1734,91 @@ class date_time(compiled_schema):
|
|
|
1336
1734
|
|
|
1337
1735
|
def __validate__(
|
|
1338
1736
|
self,
|
|
1339
|
-
|
|
1737
|
+
obj: object,
|
|
1340
1738
|
name: str = "object",
|
|
1341
1739
|
strict: bool = True,
|
|
1342
1740
|
subs: Mapping[str, object] = {},
|
|
1343
1741
|
) -> str:
|
|
1344
|
-
if not isinstance(
|
|
1345
|
-
return _wrong_type_message(
|
|
1742
|
+
if not isinstance(obj, str):
|
|
1743
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1346
1744
|
if self.format is not None:
|
|
1347
1745
|
try:
|
|
1348
|
-
datetime.datetime.strptime(
|
|
1746
|
+
datetime.datetime.strptime(obj, self.format)
|
|
1349
1747
|
except Exception as e:
|
|
1350
|
-
return _wrong_type_message(
|
|
1748
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
1351
1749
|
else:
|
|
1352
1750
|
try:
|
|
1353
|
-
datetime.datetime.fromisoformat(
|
|
1751
|
+
datetime.datetime.fromisoformat(obj)
|
|
1354
1752
|
except Exception as e:
|
|
1355
|
-
return _wrong_type_message(
|
|
1753
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
1356
1754
|
return ""
|
|
1357
1755
|
|
|
1358
1756
|
|
|
1359
1757
|
class date(compiled_schema):
|
|
1758
|
+
"""
|
|
1759
|
+
Matches an ISO 8601 date.
|
|
1760
|
+
"""
|
|
1761
|
+
|
|
1360
1762
|
def __validate__(
|
|
1361
1763
|
self,
|
|
1362
|
-
|
|
1764
|
+
obj: object,
|
|
1363
1765
|
name: str = "object",
|
|
1364
1766
|
strict: bool = True,
|
|
1365
1767
|
subs: Mapping[str, object] = {},
|
|
1366
1768
|
) -> str:
|
|
1367
|
-
if not isinstance(
|
|
1368
|
-
return _wrong_type_message(
|
|
1769
|
+
if not isinstance(obj, str):
|
|
1770
|
+
return _wrong_type_message(obj, name, "date")
|
|
1369
1771
|
try:
|
|
1370
|
-
datetime.date.fromisoformat(
|
|
1772
|
+
datetime.date.fromisoformat(obj)
|
|
1371
1773
|
except Exception as e:
|
|
1372
|
-
return _wrong_type_message(
|
|
1774
|
+
return _wrong_type_message(obj, name, "date", str(e))
|
|
1373
1775
|
return ""
|
|
1374
1776
|
|
|
1375
1777
|
|
|
1376
1778
|
class time(compiled_schema):
|
|
1779
|
+
"""
|
|
1780
|
+
Matches an ISO 8601 time.
|
|
1781
|
+
"""
|
|
1782
|
+
|
|
1377
1783
|
def __validate__(
|
|
1378
1784
|
self,
|
|
1379
|
-
|
|
1785
|
+
obj: object,
|
|
1380
1786
|
name: str = "object",
|
|
1381
1787
|
strict: bool = True,
|
|
1382
1788
|
subs: Mapping[str, object] = {},
|
|
1383
1789
|
) -> str:
|
|
1384
|
-
if not isinstance(
|
|
1385
|
-
return _wrong_type_message(
|
|
1790
|
+
if not isinstance(obj, str):
|
|
1791
|
+
return _wrong_type_message(obj, name, "date")
|
|
1386
1792
|
try:
|
|
1387
|
-
datetime.time.fromisoformat(
|
|
1793
|
+
datetime.time.fromisoformat(obj)
|
|
1388
1794
|
except Exception as e:
|
|
1389
|
-
return _wrong_type_message(
|
|
1795
|
+
return _wrong_type_message(obj, name, "time", str(e))
|
|
1390
1796
|
return ""
|
|
1391
1797
|
|
|
1392
1798
|
|
|
1393
1799
|
class nothing(compiled_schema):
|
|
1800
|
+
"""
|
|
1801
|
+
Matches nothing.
|
|
1802
|
+
"""
|
|
1803
|
+
|
|
1394
1804
|
def __validate__(
|
|
1395
1805
|
self,
|
|
1396
|
-
|
|
1806
|
+
obj: object,
|
|
1397
1807
|
name: str = "object",
|
|
1398
1808
|
strict: bool = True,
|
|
1399
1809
|
subs: Mapping[str, object] = {},
|
|
1400
1810
|
) -> str:
|
|
1401
|
-
return _wrong_type_message(
|
|
1811
|
+
return _wrong_type_message(obj, name, "nothing")
|
|
1402
1812
|
|
|
1403
1813
|
|
|
1404
1814
|
class anything(compiled_schema):
|
|
1815
|
+
"""
|
|
1816
|
+
Matchess anything.
|
|
1817
|
+
"""
|
|
1818
|
+
|
|
1405
1819
|
def __validate__(
|
|
1406
1820
|
self,
|
|
1407
|
-
|
|
1821
|
+
obj: object,
|
|
1408
1822
|
name: str = "object",
|
|
1409
1823
|
strict: bool = True,
|
|
1410
1824
|
subs: Mapping[str, object] = {},
|
|
@@ -1413,12 +1827,20 @@ class anything(compiled_schema):
|
|
|
1413
1827
|
|
|
1414
1828
|
|
|
1415
1829
|
class domain_name(compiled_schema):
|
|
1830
|
+
"""
|
|
1831
|
+
Checks if the object is a valid domain name.
|
|
1832
|
+
"""
|
|
1833
|
+
|
|
1416
1834
|
re_asci: re.Pattern[str]
|
|
1417
1835
|
ascii_only: bool
|
|
1418
1836
|
resolve: bool
|
|
1419
1837
|
__name__: str
|
|
1420
1838
|
|
|
1421
1839
|
def __init__(self, ascii_only: bool = True, resolve: bool = False) -> None:
|
|
1840
|
+
"""
|
|
1841
|
+
:param ascii_only: if `False` then allow IDNA domain names
|
|
1842
|
+
:param resolve: if `True` check if the domain names resolves
|
|
1843
|
+
"""
|
|
1422
1844
|
self.re_ascii = re.compile(r"[\x00-\x7F]*")
|
|
1423
1845
|
self.ascii_only = ascii_only
|
|
1424
1846
|
self.resolve = resolve
|
|
@@ -1435,129 +1857,161 @@ class domain_name(compiled_schema):
|
|
|
1435
1857
|
|
|
1436
1858
|
def __validate__(
|
|
1437
1859
|
self,
|
|
1438
|
-
|
|
1860
|
+
obj: object,
|
|
1439
1861
|
name: str = "object",
|
|
1440
1862
|
strict: bool = True,
|
|
1441
1863
|
subs: Mapping[str, object] = {},
|
|
1442
1864
|
) -> str:
|
|
1443
|
-
if not isinstance(
|
|
1444
|
-
return _wrong_type_message(
|
|
1865
|
+
if not isinstance(obj, str):
|
|
1866
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1445
1867
|
if self.ascii_only:
|
|
1446
|
-
if not self.re_ascii.fullmatch(
|
|
1868
|
+
if not self.re_ascii.fullmatch(obj):
|
|
1447
1869
|
return _wrong_type_message(
|
|
1448
|
-
|
|
1870
|
+
obj, name, self.__name__, "Non-ascii characters"
|
|
1449
1871
|
)
|
|
1450
1872
|
try:
|
|
1451
|
-
idna.encode(
|
|
1873
|
+
idna.encode(obj, uts46=False)
|
|
1452
1874
|
except idna.core.IDNAError as e:
|
|
1453
|
-
return _wrong_type_message(
|
|
1875
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
1454
1876
|
|
|
1455
1877
|
if self.resolve:
|
|
1456
1878
|
try:
|
|
1457
|
-
_get_dns_resolver().resolve(
|
|
1879
|
+
_get_dns_resolver().resolve(obj)
|
|
1458
1880
|
except Exception as e:
|
|
1459
|
-
return _wrong_type_message(
|
|
1881
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
1460
1882
|
return ""
|
|
1461
1883
|
|
|
1462
1884
|
|
|
1463
1885
|
class at_least_one_of(compiled_schema):
|
|
1886
|
+
"""
|
|
1887
|
+
This represents a dictionary with a least one key among a collection of
|
|
1888
|
+
keys.
|
|
1889
|
+
"""
|
|
1890
|
+
|
|
1464
1891
|
args: tuple[object, ...]
|
|
1465
1892
|
__name__: str
|
|
1466
1893
|
|
|
1467
1894
|
def __init__(self, *args: object) -> None:
|
|
1895
|
+
"""
|
|
1896
|
+
:param args: a collection of keys
|
|
1897
|
+
"""
|
|
1468
1898
|
self.args = args
|
|
1469
1899
|
args_s = [repr(a) for a in args]
|
|
1470
1900
|
self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
|
|
1471
1901
|
|
|
1472
1902
|
def __validate__(
|
|
1473
1903
|
self,
|
|
1474
|
-
|
|
1904
|
+
obj: object,
|
|
1475
1905
|
name: str = "object",
|
|
1476
1906
|
strict: bool = True,
|
|
1477
1907
|
subs: Mapping[str, object] = {},
|
|
1478
1908
|
) -> str:
|
|
1479
|
-
if not isinstance(
|
|
1480
|
-
return _wrong_type_message(
|
|
1909
|
+
if not isinstance(obj, Mapping):
|
|
1910
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1481
1911
|
try:
|
|
1482
|
-
if any([a in
|
|
1912
|
+
if any([a in obj for a in self.args]):
|
|
1483
1913
|
return ""
|
|
1484
1914
|
else:
|
|
1485
|
-
return _wrong_type_message(
|
|
1915
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1486
1916
|
except Exception as e:
|
|
1487
|
-
return _wrong_type_message(
|
|
1917
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
1488
1918
|
|
|
1489
1919
|
|
|
1490
1920
|
class at_most_one_of(compiled_schema):
|
|
1921
|
+
"""
|
|
1922
|
+
This represents an dictionary with at most one key among a collection of
|
|
1923
|
+
keys.
|
|
1924
|
+
"""
|
|
1925
|
+
|
|
1491
1926
|
args: tuple[object, ...]
|
|
1492
1927
|
__name__: str
|
|
1493
1928
|
|
|
1494
1929
|
def __init__(self, *args: object) -> None:
|
|
1930
|
+
"""
|
|
1931
|
+
:param args: a collection of keys
|
|
1932
|
+
"""
|
|
1495
1933
|
self.args = args
|
|
1496
1934
|
args_s = [repr(a) for a in args]
|
|
1497
1935
|
self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
|
|
1498
1936
|
|
|
1499
1937
|
def __validate__(
|
|
1500
1938
|
self,
|
|
1501
|
-
|
|
1939
|
+
obj: object,
|
|
1502
1940
|
name: str = "object",
|
|
1503
1941
|
strict: bool = True,
|
|
1504
1942
|
subs: Mapping[str, object] = {},
|
|
1505
1943
|
) -> str:
|
|
1506
|
-
if not isinstance(
|
|
1507
|
-
return _wrong_type_message(
|
|
1944
|
+
if not isinstance(obj, Mapping):
|
|
1945
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1508
1946
|
try:
|
|
1509
|
-
if sum([a in
|
|
1947
|
+
if sum([a in obj for a in self.args]) <= 1:
|
|
1510
1948
|
return ""
|
|
1511
1949
|
else:
|
|
1512
|
-
return _wrong_type_message(
|
|
1950
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1513
1951
|
except Exception as e:
|
|
1514
|
-
return _wrong_type_message(
|
|
1952
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
1515
1953
|
|
|
1516
1954
|
|
|
1517
1955
|
class one_of(compiled_schema):
|
|
1956
|
+
"""
|
|
1957
|
+
This represents a dictionary with exactly one key among a collection of
|
|
1958
|
+
keys.
|
|
1959
|
+
"""
|
|
1960
|
+
|
|
1518
1961
|
args: tuple[object, ...]
|
|
1519
1962
|
__name__: str
|
|
1520
1963
|
|
|
1521
1964
|
def __init__(self, *args: object) -> None:
|
|
1965
|
+
"""
|
|
1966
|
+
:param args: a collection of keys
|
|
1967
|
+
"""
|
|
1522
1968
|
self.args = args
|
|
1523
1969
|
args_s = [repr(a) for a in args]
|
|
1524
1970
|
self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
|
|
1525
1971
|
|
|
1526
1972
|
def __validate__(
|
|
1527
1973
|
self,
|
|
1528
|
-
|
|
1974
|
+
obj: object,
|
|
1529
1975
|
name: str = "object",
|
|
1530
1976
|
strict: bool = True,
|
|
1531
1977
|
subs: Mapping[str, object] = {},
|
|
1532
1978
|
) -> str:
|
|
1533
|
-
if not isinstance(
|
|
1534
|
-
return _wrong_type_message(
|
|
1979
|
+
if not isinstance(obj, Mapping):
|
|
1980
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1535
1981
|
try:
|
|
1536
|
-
if sum([a in
|
|
1982
|
+
if sum([a in obj for a in self.args]) == 1:
|
|
1537
1983
|
return ""
|
|
1538
1984
|
else:
|
|
1539
|
-
return _wrong_type_message(
|
|
1985
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1540
1986
|
except Exception as e:
|
|
1541
|
-
return _wrong_type_message(
|
|
1987
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
1542
1988
|
|
|
1543
1989
|
|
|
1544
1990
|
class keys(compiled_schema):
|
|
1991
|
+
"""
|
|
1992
|
+
This represents a dictionary containing all the keys in a collection of
|
|
1993
|
+
keys
|
|
1994
|
+
"""
|
|
1995
|
+
|
|
1545
1996
|
args: tuple[object, ...]
|
|
1546
1997
|
|
|
1547
1998
|
def __init__(self, *args: object) -> None:
|
|
1999
|
+
"""
|
|
2000
|
+
:param args: a collection of keys
|
|
2001
|
+
"""
|
|
1548
2002
|
self.args = args
|
|
1549
2003
|
|
|
1550
2004
|
def __validate__(
|
|
1551
2005
|
self,
|
|
1552
|
-
|
|
2006
|
+
obj: object,
|
|
1553
2007
|
name: str = "object",
|
|
1554
2008
|
strict: bool = True,
|
|
1555
2009
|
subs: Mapping[str, object] = {},
|
|
1556
2010
|
) -> str:
|
|
1557
|
-
if not isinstance(
|
|
1558
|
-
return _wrong_type_message(
|
|
2011
|
+
if not isinstance(obj, Mapping):
|
|
2012
|
+
return _wrong_type_message(obj, name, "Mapping") # TODO: __name__
|
|
1559
2013
|
for k in self.args:
|
|
1560
|
-
if k not in
|
|
2014
|
+
if k not in obj:
|
|
1561
2015
|
return f"{name}[{repr(k)}] is missing"
|
|
1562
2016
|
return ""
|
|
1563
2017
|
|
|
@@ -1585,26 +2039,29 @@ class _ifthen(compiled_schema):
|
|
|
1585
2039
|
|
|
1586
2040
|
def __validate__(
|
|
1587
2041
|
self,
|
|
1588
|
-
|
|
2042
|
+
obj: object,
|
|
1589
2043
|
name: str = "object",
|
|
1590
2044
|
strict: bool = True,
|
|
1591
2045
|
subs: Mapping[str, object] = {},
|
|
1592
2046
|
) -> str:
|
|
1593
|
-
if (
|
|
1594
|
-
self.if_schema.__validate__(object_, name=name, strict=strict, subs=subs)
|
|
1595
|
-
== ""
|
|
1596
|
-
):
|
|
2047
|
+
if self.if_schema.__validate__(obj, name=name, strict=strict, subs=subs) == "":
|
|
1597
2048
|
return self.then_schema.__validate__(
|
|
1598
|
-
|
|
2049
|
+
obj, name=name, strict=strict, subs=subs
|
|
1599
2050
|
)
|
|
1600
2051
|
elif self.else_schema is not None:
|
|
1601
2052
|
return self.else_schema.__validate__(
|
|
1602
|
-
|
|
2053
|
+
obj, name=name, strict=strict, subs=subs
|
|
1603
2054
|
)
|
|
1604
2055
|
return ""
|
|
1605
2056
|
|
|
1606
2057
|
|
|
1607
|
-
class ifthen:
|
|
2058
|
+
class ifthen(wrapper):
|
|
2059
|
+
"""
|
|
2060
|
+
If the object matches the `if_schema` then it should also match the
|
|
2061
|
+
`then_schema`. If the object does not match the `if_schema` then it should
|
|
2062
|
+
match the `else_schema`, if present.
|
|
2063
|
+
"""
|
|
2064
|
+
|
|
1608
2065
|
if_schema: object
|
|
1609
2066
|
then_schema: object
|
|
1610
2067
|
else_schema: object | None
|
|
@@ -1615,6 +2072,11 @@ class ifthen:
|
|
|
1615
2072
|
then_schema: object,
|
|
1616
2073
|
else_schema: object | None = None,
|
|
1617
2074
|
) -> None:
|
|
2075
|
+
"""
|
|
2076
|
+
:param if_schema: the `if_schema`
|
|
2077
|
+
:param then_schema: the `then_schema`
|
|
2078
|
+
:param else_schema: the `else_schema`
|
|
2079
|
+
"""
|
|
1618
2080
|
self.if_schema = if_schema
|
|
1619
2081
|
self.then_schema = then_schema
|
|
1620
2082
|
self.else_schema = else_schema
|
|
@@ -1647,21 +2109,35 @@ class _cond(compiled_schema):
|
|
|
1647
2109
|
|
|
1648
2110
|
def __validate__(
|
|
1649
2111
|
self,
|
|
1650
|
-
|
|
2112
|
+
obj: object,
|
|
1651
2113
|
name: str = "object",
|
|
1652
2114
|
strict: bool = True,
|
|
1653
2115
|
subs: Mapping[str, object] = {},
|
|
1654
2116
|
) -> str:
|
|
1655
2117
|
for c in self.conditions:
|
|
1656
|
-
if c[0].__validate__(
|
|
1657
|
-
return c[1].__validate__(
|
|
2118
|
+
if c[0].__validate__(obj, name=name, strict=strict, subs=subs) == "":
|
|
2119
|
+
return c[1].__validate__(obj, name=name, strict=strict, subs=subs)
|
|
1658
2120
|
return ""
|
|
1659
2121
|
|
|
1660
2122
|
|
|
1661
|
-
class cond:
|
|
2123
|
+
class cond(wrapper):
|
|
2124
|
+
"""
|
|
2125
|
+
An object is successively validated against `if_schema1`, `if_schema2`,
|
|
2126
|
+
... until a validation succeeds. When this happens the object should match
|
|
2127
|
+
the corresponding `then_schema`. If no `if_schema` succeeds then the
|
|
2128
|
+
object is considered to have been validated. If one sets `if_schemaN`
|
|
2129
|
+
equal to `anything` then this serves as a catch all.
|
|
2130
|
+
"""
|
|
2131
|
+
|
|
1662
2132
|
args: tuple[tuple[object, object], ...]
|
|
1663
2133
|
|
|
1664
2134
|
def __init__(self, *args: tuple[object, object]) -> None:
|
|
2135
|
+
"""
|
|
2136
|
+
:param args: list of tuples pairing `if_schemas` with `then_schemas`
|
|
2137
|
+
|
|
2138
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
2139
|
+
found to contain an error
|
|
2140
|
+
"""
|
|
1665
2141
|
for c in args:
|
|
1666
2142
|
if not isinstance(c, tuple) or len(c) != 2:
|
|
1667
2143
|
raise SchemaError(f"{repr(c)} is not a tuple of length two")
|
|
@@ -1672,10 +2148,12 @@ class cond:
|
|
|
1672
2148
|
|
|
1673
2149
|
|
|
1674
2150
|
class _fields(compiled_schema):
|
|
1675
|
-
d: dict[str, compiled_schema]
|
|
2151
|
+
d: dict[str | optional_key[str], compiled_schema]
|
|
1676
2152
|
|
|
1677
2153
|
def __init__(
|
|
1678
|
-
self,
|
|
2154
|
+
self,
|
|
2155
|
+
d: Mapping[str | optional_key[str], object],
|
|
2156
|
+
_deferred_compiles: _mapping | None = None,
|
|
1679
2157
|
) -> None:
|
|
1680
2158
|
self.d = {}
|
|
1681
2159
|
for k, v in d.items():
|
|
@@ -1683,31 +2161,56 @@ class _fields(compiled_schema):
|
|
|
1683
2161
|
|
|
1684
2162
|
def __validate__(
|
|
1685
2163
|
self,
|
|
1686
|
-
|
|
2164
|
+
obj: object,
|
|
1687
2165
|
name: str = "object",
|
|
1688
2166
|
strict: bool = True,
|
|
1689
2167
|
subs: Mapping[str, object] = {},
|
|
1690
2168
|
) -> str:
|
|
1691
2169
|
for k, v in self.d.items():
|
|
1692
2170
|
name_ = f"{name}.{k}"
|
|
1693
|
-
if not hasattr(
|
|
2171
|
+
if not isinstance(k, optional_key) and not hasattr(obj, k):
|
|
1694
2172
|
return f"{name_} is missing"
|
|
2173
|
+
if isinstance(k, optional_key):
|
|
2174
|
+
k_ = k.key
|
|
2175
|
+
else:
|
|
2176
|
+
k_ = k
|
|
1695
2177
|
ret = self.d[k].__validate__(
|
|
1696
|
-
getattr(
|
|
2178
|
+
getattr(obj, k_), name=name_, strict=strict, subs=subs
|
|
1697
2179
|
)
|
|
1698
2180
|
if ret != "":
|
|
1699
2181
|
return ret
|
|
1700
2182
|
return ""
|
|
1701
2183
|
|
|
1702
2184
|
|
|
1703
|
-
class fields:
|
|
1704
|
-
|
|
2185
|
+
class fields(wrapper):
|
|
2186
|
+
"""
|
|
2187
|
+
Matches Python objects with attributes `field1, field2, ..., fieldN` whose
|
|
2188
|
+
corresponding values should validate against `schema1, schema2, ...,
|
|
2189
|
+
schemaN` respectively
|
|
2190
|
+
"""
|
|
2191
|
+
|
|
2192
|
+
d: dict[str | optional_key[str], object]
|
|
2193
|
+
|
|
2194
|
+
def __init__(self, d: Mapping[str | optional_key[str], object]) -> None:
|
|
2195
|
+
"""
|
|
2196
|
+
:param d: a dictionary associating fields with schemas
|
|
2197
|
+
|
|
2198
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
2199
|
+
found to contain an error
|
|
2200
|
+
"""
|
|
2201
|
+
self.d = {}
|
|
1705
2202
|
if not isinstance(d, Mapping):
|
|
1706
2203
|
raise SchemaError(f"{repr(d)} is not a Mapping")
|
|
1707
|
-
for k in d:
|
|
1708
|
-
if not isinstance(k, str):
|
|
1709
|
-
raise SchemaError(
|
|
1710
|
-
|
|
2204
|
+
for k, v in d.items():
|
|
2205
|
+
if not isinstance(k, str) and not isinstance(k, optional_key):
|
|
2206
|
+
raise SchemaError(
|
|
2207
|
+
f"key {repr(k)} in {repr(d)} is not an instance of"
|
|
2208
|
+
" optional_key and not a string"
|
|
2209
|
+
)
|
|
2210
|
+
if isinstance(k, str) and k[-1] == "?":
|
|
2211
|
+
self.d[optional_key(k[:-1])] = v
|
|
2212
|
+
else:
|
|
2213
|
+
self.d[k] = v
|
|
1711
2214
|
|
|
1712
2215
|
def __compile__(self, _deferred_compiles: _mapping | None = None) -> _fields:
|
|
1713
2216
|
return _fields(self.d, _deferred_compiles=_deferred_compiles)
|
|
@@ -1739,25 +2242,28 @@ class _filter(compiled_schema):
|
|
|
1739
2242
|
|
|
1740
2243
|
def __validate__(
|
|
1741
2244
|
self,
|
|
1742
|
-
|
|
2245
|
+
obj: object,
|
|
1743
2246
|
name: str = "object",
|
|
1744
2247
|
strict: bool = True,
|
|
1745
2248
|
subs: Mapping[str, object] = {},
|
|
1746
2249
|
) -> str:
|
|
1747
2250
|
try:
|
|
1748
|
-
|
|
2251
|
+
obj = self.filter(obj)
|
|
1749
2252
|
except Exception as e:
|
|
1750
2253
|
return (
|
|
1751
2254
|
f"Applying {self.filter_name} to {name} "
|
|
1752
|
-
f"(value: {_c(
|
|
2255
|
+
f"(value: {_c(obj)}) failed: {str(e)}"
|
|
1753
2256
|
)
|
|
1754
2257
|
name = f"{self.filter_name}({name})"
|
|
1755
|
-
return self.schema.__validate__(
|
|
1756
|
-
object_, name="object", strict=strict, subs=subs
|
|
1757
|
-
)
|
|
2258
|
+
return self.schema.__validate__(obj, name="object", strict=strict, subs=subs)
|
|
1758
2259
|
|
|
1759
2260
|
|
|
1760
|
-
class filter:
|
|
2261
|
+
class filter(wrapper):
|
|
2262
|
+
"""
|
|
2263
|
+
Applies `callable` to the object and validates the result with `schema`.
|
|
2264
|
+
If the callable throws an exception then validation fails.
|
|
2265
|
+
"""
|
|
2266
|
+
|
|
1761
2267
|
filter: Callable[[Any], object]
|
|
1762
2268
|
schema: object
|
|
1763
2269
|
filter_name: str | None
|
|
@@ -1768,6 +2274,15 @@ class filter:
|
|
|
1768
2274
|
schema: object,
|
|
1769
2275
|
filter_name: str | None = None,
|
|
1770
2276
|
) -> None:
|
|
2277
|
+
"""
|
|
2278
|
+
:param filter: the filter to apply to the object
|
|
2279
|
+
:param schema: the schema used for validation once the filter has been
|
|
2280
|
+
applied
|
|
2281
|
+
:param filter_name: common name to refer to the filter
|
|
2282
|
+
|
|
2283
|
+
:raises SchemaError: exception thrown when the schema definition is
|
|
2284
|
+
found to contain an error
|
|
2285
|
+
"""
|
|
1771
2286
|
if filter_name is not None and not isinstance(filter_name, str):
|
|
1772
2287
|
raise SchemaError("The filter name is not a string")
|
|
1773
2288
|
if not callable(filter):
|
|
@@ -1798,16 +2313,16 @@ class _type(compiled_schema):
|
|
|
1798
2313
|
|
|
1799
2314
|
def __validate__(
|
|
1800
2315
|
self,
|
|
1801
|
-
|
|
2316
|
+
obj: object,
|
|
1802
2317
|
name: str = "object",
|
|
1803
2318
|
strict: bool = True,
|
|
1804
2319
|
subs: Mapping[str, object] = {},
|
|
1805
2320
|
) -> str:
|
|
1806
2321
|
try:
|
|
1807
|
-
if self.schema == float and isinstance(
|
|
2322
|
+
if self.schema == float and isinstance(obj, int):
|
|
1808
2323
|
return ""
|
|
1809
|
-
if not isinstance(
|
|
1810
|
-
return _wrong_type_message(
|
|
2324
|
+
if not isinstance(obj, self.schema):
|
|
2325
|
+
return _wrong_type_message(obj, name, self.schema.__name__)
|
|
1811
2326
|
else:
|
|
1812
2327
|
return ""
|
|
1813
2328
|
except Exception as e:
|
|
@@ -1815,29 +2330,29 @@ class _type(compiled_schema):
|
|
|
1815
2330
|
|
|
1816
2331
|
def __validate_float__(
|
|
1817
2332
|
self,
|
|
1818
|
-
|
|
2333
|
+
obj: object,
|
|
1819
2334
|
name: str = "object",
|
|
1820
2335
|
strict: bool = True,
|
|
1821
2336
|
subs: Mapping[str, object] = {},
|
|
1822
2337
|
) -> str:
|
|
1823
2338
|
# consider int as a subtype of float
|
|
1824
|
-
if isinstance(
|
|
2339
|
+
if isinstance(obj, (int, float)):
|
|
1825
2340
|
return ""
|
|
1826
2341
|
else:
|
|
1827
|
-
return _wrong_type_message(
|
|
2342
|
+
return _wrong_type_message(obj, name, "float")
|
|
1828
2343
|
|
|
1829
2344
|
def __validate_complex__(
|
|
1830
2345
|
self,
|
|
1831
|
-
|
|
2346
|
+
obj: object,
|
|
1832
2347
|
name: str = "object",
|
|
1833
2348
|
strict: bool = True,
|
|
1834
2349
|
subs: Mapping[str, object] = {},
|
|
1835
2350
|
) -> str:
|
|
1836
2351
|
# consider int, float as subtypes of complex
|
|
1837
|
-
if isinstance(
|
|
2352
|
+
if isinstance(obj, (int, float, complex)):
|
|
1838
2353
|
return ""
|
|
1839
2354
|
else:
|
|
1840
|
-
return _wrong_type_message(
|
|
2355
|
+
return _wrong_type_message(obj, name, "complex")
|
|
1841
2356
|
|
|
1842
2357
|
def __str__(self) -> str:
|
|
1843
2358
|
return self.schema.__name__
|
|
@@ -1871,15 +2386,15 @@ class _sequence(compiled_schema):
|
|
|
1871
2386
|
|
|
1872
2387
|
def __validate__(
|
|
1873
2388
|
self,
|
|
1874
|
-
|
|
2389
|
+
obj: object,
|
|
1875
2390
|
name: str = "object",
|
|
1876
2391
|
strict: bool = True,
|
|
1877
2392
|
subs: Mapping[str, object] = {},
|
|
1878
2393
|
) -> str:
|
|
1879
|
-
if not isinstance(
|
|
1880
|
-
return _wrong_type_message(
|
|
2394
|
+
if not isinstance(obj, self.type_schema):
|
|
2395
|
+
return _wrong_type_message(obj, name, self.type_schema.__name__)
|
|
1881
2396
|
ls = len(self.schema)
|
|
1882
|
-
lo = len(
|
|
2397
|
+
lo = len(obj)
|
|
1883
2398
|
if strict:
|
|
1884
2399
|
if lo > ls:
|
|
1885
2400
|
return f"{name}[{ls}] is not in the schema"
|
|
@@ -1887,32 +2402,32 @@ class _sequence(compiled_schema):
|
|
|
1887
2402
|
return f"{name}[{lo}] is missing"
|
|
1888
2403
|
for i in range(ls):
|
|
1889
2404
|
name_ = f"{name}[{i}]"
|
|
1890
|
-
ret = self.schema[i].__validate__(
|
|
2405
|
+
ret = self.schema[i].__validate__(obj[i], name_, strict, subs)
|
|
1891
2406
|
if ret != "":
|
|
1892
2407
|
return ret
|
|
1893
2408
|
return ""
|
|
1894
2409
|
|
|
1895
2410
|
def __validate_ellipsis__(
|
|
1896
2411
|
self,
|
|
1897
|
-
|
|
2412
|
+
obj: object,
|
|
1898
2413
|
name: str = "object",
|
|
1899
2414
|
strict: bool = True,
|
|
1900
2415
|
subs: Mapping[str, object] = {},
|
|
1901
2416
|
) -> str:
|
|
1902
|
-
if not isinstance(
|
|
1903
|
-
return _wrong_type_message(
|
|
2417
|
+
if not isinstance(obj, self.type_schema):
|
|
2418
|
+
return _wrong_type_message(obj, name, self.type_schema.__name__)
|
|
1904
2419
|
ls = len(self.schema)
|
|
1905
|
-
lo = len(
|
|
2420
|
+
lo = len(obj)
|
|
1906
2421
|
if ls > lo:
|
|
1907
2422
|
return f"{name}[{lo}] is missing"
|
|
1908
2423
|
for i in range(ls):
|
|
1909
2424
|
name_ = f"{name}[{i}]"
|
|
1910
|
-
ret = self.schema[i].__validate__(
|
|
2425
|
+
ret = self.schema[i].__validate__(obj[i], name_, strict, subs)
|
|
1911
2426
|
if ret != "":
|
|
1912
2427
|
return ret
|
|
1913
2428
|
for i in range(ls, lo):
|
|
1914
2429
|
name_ = f"{name}[{i}]"
|
|
1915
|
-
ret = self.fill.__validate__(
|
|
2430
|
+
ret = self.fill.__validate__(obj[i], name_, strict, subs)
|
|
1916
2431
|
if ret != "":
|
|
1917
2432
|
return ret
|
|
1918
2433
|
return ""
|
|
@@ -1929,18 +2444,18 @@ class _const(compiled_schema):
|
|
|
1929
2444
|
if isinstance(schema, float) and not strict_eq:
|
|
1930
2445
|
setattr(self, "__validate__", close_to(schema).__validate__)
|
|
1931
2446
|
|
|
1932
|
-
def message(self, name: str,
|
|
1933
|
-
return f"{name} (value:{_c(
|
|
2447
|
+
def message(self, name: str, obj: object) -> str:
|
|
2448
|
+
return f"{name} (value:{_c(obj)}) is not equal to {repr(self.schema)}"
|
|
1934
2449
|
|
|
1935
2450
|
def __validate__(
|
|
1936
2451
|
self,
|
|
1937
|
-
|
|
2452
|
+
obj: object,
|
|
1938
2453
|
name: str = "object",
|
|
1939
2454
|
strict: bool = True,
|
|
1940
2455
|
subs: Mapping[str, object] = {},
|
|
1941
2456
|
) -> str:
|
|
1942
|
-
if
|
|
1943
|
-
return self.message(name,
|
|
2457
|
+
if obj != self.schema:
|
|
2458
|
+
return self.message(name, obj)
|
|
1944
2459
|
return ""
|
|
1945
2460
|
|
|
1946
2461
|
def __str__(self) -> str:
|
|
@@ -1960,18 +2475,18 @@ class _callable(compiled_schema):
|
|
|
1960
2475
|
|
|
1961
2476
|
def __validate__(
|
|
1962
2477
|
self,
|
|
1963
|
-
|
|
2478
|
+
obj: object,
|
|
1964
2479
|
name: str = "object",
|
|
1965
2480
|
strict: bool = True,
|
|
1966
2481
|
subs: Mapping[str, object] = {},
|
|
1967
2482
|
) -> str:
|
|
1968
2483
|
try:
|
|
1969
|
-
if self.schema(
|
|
2484
|
+
if self.schema(obj):
|
|
1970
2485
|
return ""
|
|
1971
2486
|
else:
|
|
1972
|
-
return _wrong_type_message(
|
|
2487
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
1973
2488
|
except Exception as e:
|
|
1974
|
-
return _wrong_type_message(
|
|
2489
|
+
return _wrong_type_message(obj, name, self.__name__, str(e))
|
|
1975
2490
|
|
|
1976
2491
|
def __str__(self) -> str:
|
|
1977
2492
|
return str(self.schema)
|
|
@@ -2016,25 +2531,25 @@ class _dict(compiled_schema):
|
|
|
2016
2531
|
|
|
2017
2532
|
def __validate__(
|
|
2018
2533
|
self,
|
|
2019
|
-
|
|
2534
|
+
obj: object,
|
|
2020
2535
|
name: str = "object",
|
|
2021
2536
|
strict: bool = True,
|
|
2022
2537
|
subs: Mapping[str, object] = {},
|
|
2023
2538
|
) -> str:
|
|
2024
|
-
if not isinstance(
|
|
2025
|
-
return _wrong_type_message(
|
|
2539
|
+
if not isinstance(obj, self.type_schema):
|
|
2540
|
+
return _wrong_type_message(obj, name, self.type_schema.__name__)
|
|
2026
2541
|
|
|
2027
2542
|
for k in self.min_keys:
|
|
2028
|
-
if k not in
|
|
2543
|
+
if k not in obj:
|
|
2029
2544
|
name_ = f"{name}[{repr(k)}]"
|
|
2030
2545
|
return f"{name_} is missing"
|
|
2031
2546
|
|
|
2032
|
-
for k in
|
|
2547
|
+
for k in obj:
|
|
2033
2548
|
vals = []
|
|
2034
2549
|
name_ = f"{name}[{repr(k)}]"
|
|
2035
2550
|
if k in self.const_keys:
|
|
2036
2551
|
val = self.schema[k].__validate__(
|
|
2037
|
-
|
|
2552
|
+
obj[k], name=name_, strict=strict, subs=subs
|
|
2038
2553
|
)
|
|
2039
2554
|
if val == "":
|
|
2040
2555
|
continue
|
|
@@ -2044,7 +2559,7 @@ class _dict(compiled_schema):
|
|
|
2044
2559
|
for kk in self.other_keys:
|
|
2045
2560
|
if kk.__validate__(k, name="key", strict=strict, subs=subs) == "":
|
|
2046
2561
|
val = self.schema[kk].__validate__(
|
|
2047
|
-
|
|
2562
|
+
obj[k], name=name_, strict=strict, subs=subs
|
|
2048
2563
|
)
|
|
2049
2564
|
if val == "":
|
|
2050
2565
|
break
|
|
@@ -2085,27 +2600,27 @@ class _set(compiled_schema):
|
|
|
2085
2600
|
|
|
2086
2601
|
def __validate_empty_set__(
|
|
2087
2602
|
self,
|
|
2088
|
-
|
|
2603
|
+
obj: object,
|
|
2089
2604
|
name: str = "object",
|
|
2090
2605
|
strict: bool = True,
|
|
2091
2606
|
subs: Mapping[str, object] = {},
|
|
2092
2607
|
) -> str:
|
|
2093
|
-
if not isinstance(
|
|
2094
|
-
return _wrong_type_message(
|
|
2095
|
-
if len(
|
|
2096
|
-
return f"{name} (value:{_c(
|
|
2608
|
+
if not isinstance(obj, self.type_schema):
|
|
2609
|
+
return _wrong_type_message(obj, name, self.type_schema.__name__)
|
|
2610
|
+
if len(obj) != 0:
|
|
2611
|
+
return f"{name} (value:{_c(obj)}) is not empty"
|
|
2097
2612
|
return ""
|
|
2098
2613
|
|
|
2099
2614
|
def __validate_singleton__(
|
|
2100
2615
|
self,
|
|
2101
|
-
|
|
2616
|
+
obj: object,
|
|
2102
2617
|
name: str = "object",
|
|
2103
2618
|
strict: bool = True,
|
|
2104
2619
|
subs: Mapping[str, object] = {},
|
|
2105
2620
|
) -> str:
|
|
2106
|
-
if not isinstance(
|
|
2107
|
-
return _wrong_type_message(
|
|
2108
|
-
for i, o in enumerate(
|
|
2621
|
+
if not isinstance(obj, set):
|
|
2622
|
+
return _wrong_type_message(obj, name, self.type_schema.__name__)
|
|
2623
|
+
for i, o in enumerate(obj):
|
|
2109
2624
|
name_ = f"{name}{{{i}}}"
|
|
2110
2625
|
v = self.schema.__validate__(o, name=name_, strict=True, subs=subs)
|
|
2111
2626
|
if v != "":
|
|
@@ -2114,14 +2629,14 @@ class _set(compiled_schema):
|
|
|
2114
2629
|
|
|
2115
2630
|
def __validate__(
|
|
2116
2631
|
self,
|
|
2117
|
-
|
|
2632
|
+
obj: object,
|
|
2118
2633
|
name: str = "object",
|
|
2119
2634
|
strict: bool = True,
|
|
2120
2635
|
subs: Mapping[str, object] = {},
|
|
2121
2636
|
) -> str:
|
|
2122
|
-
if not isinstance(
|
|
2123
|
-
return _wrong_type_message(
|
|
2124
|
-
for i, o in enumerate(
|
|
2637
|
+
if not isinstance(obj, self.type_schema):
|
|
2638
|
+
return _wrong_type_message(obj, name, self.type_schema.__name__)
|
|
2639
|
+
for i, o in enumerate(obj):
|
|
2125
2640
|
name_ = f"{name}{{{i}}}"
|
|
2126
2641
|
v = self.schema.__validate__(o, name=name_, strict=True, subs=subs)
|
|
2127
2642
|
if v != "":
|
|
@@ -2132,12 +2647,26 @@ class _set(compiled_schema):
|
|
|
2132
2647
|
return str(self.schema_)
|
|
2133
2648
|
|
|
2134
2649
|
|
|
2135
|
-
class protocol:
|
|
2136
|
-
|
|
2650
|
+
class protocol(wrapper):
|
|
2651
|
+
"""
|
|
2652
|
+
An object matches the schema `protocol(schema, dict=False)` if `schema` is
|
|
2653
|
+
a class (or class like object) and its fields are annotated with schemas
|
|
2654
|
+
which validate the corresponding fields in the object.
|
|
2655
|
+
"""
|
|
2656
|
+
|
|
2657
|
+
type_dict: dict[str | optional_key[str], object]
|
|
2137
2658
|
dict: bool
|
|
2138
2659
|
__name__: str
|
|
2139
2660
|
|
|
2140
2661
|
def __init__(self, schema: object, dict: bool = False):
|
|
2662
|
+
"""
|
|
2663
|
+
:param schema: a type annotated class (or class like object such as
|
|
2664
|
+
Protocol or TypedDict) serving as prototype
|
|
2665
|
+
:param dict: if `True` parse the object as a `dict`
|
|
2666
|
+
|
|
2667
|
+
:raises SchemaError: exception thrown when the schema does not support
|
|
2668
|
+
type_hints
|
|
2669
|
+
"""
|
|
2141
2670
|
if not isinstance(dict, bool):
|
|
2142
2671
|
raise SchemaError("bool flag is not a bool")
|
|
2143
2672
|
type_hints = _get_type_hints(schema)
|
|
@@ -2225,16 +2754,16 @@ class _Mapping(compiled_schema):
|
|
|
2225
2754
|
|
|
2226
2755
|
def __validate__(
|
|
2227
2756
|
self,
|
|
2228
|
-
|
|
2757
|
+
obj: object,
|
|
2229
2758
|
name: str = "object",
|
|
2230
2759
|
strict: bool = True,
|
|
2231
2760
|
subs: Mapping[str, object] = {},
|
|
2232
2761
|
) -> str:
|
|
2233
2762
|
|
|
2234
|
-
if not isinstance(
|
|
2235
|
-
return _wrong_type_message(
|
|
2763
|
+
if not isinstance(obj, self.type_schema):
|
|
2764
|
+
return _wrong_type_message(obj, name, self.__name__)
|
|
2236
2765
|
|
|
2237
|
-
for k, v in
|
|
2766
|
+
for k, v in obj.items():
|
|
2238
2767
|
_name = f"{name}[{repr(k)}]"
|
|
2239
2768
|
message = self.key.__validate__(k, name=str(k), strict=strict, subs=subs)
|
|
2240
2769
|
if message != "":
|
|
@@ -2265,17 +2794,17 @@ class _Container(compiled_schema):
|
|
|
2265
2794
|
|
|
2266
2795
|
def __validate__(
|
|
2267
2796
|
self,
|
|
2268
|
-
|
|
2797
|
+
obj: object,
|
|
2269
2798
|
name: str = "object",
|
|
2270
2799
|
strict: bool = True,
|
|
2271
2800
|
subs: Mapping[str, object] = {},
|
|
2272
2801
|
) -> str:
|
|
2273
2802
|
|
|
2274
|
-
if not isinstance(
|
|
2275
|
-
return _wrong_type_message(
|
|
2803
|
+
if not isinstance(obj, self.type_schema):
|
|
2804
|
+
return _wrong_type_message(obj, name, self.type_schema.__name__)
|
|
2276
2805
|
|
|
2277
2806
|
try:
|
|
2278
|
-
for i, o in enumerate(
|
|
2807
|
+
for i, o in enumerate(obj):
|
|
2279
2808
|
_name = f"{name}[{i}]"
|
|
2280
2809
|
message = self.schema.__validate__(
|
|
2281
2810
|
o, name=_name, strict=strict, subs=subs
|
|
@@ -2283,7 +2812,7 @@ class _Container(compiled_schema):
|
|
|
2283
2812
|
if message != "":
|
|
2284
2813
|
return message
|
|
2285
2814
|
except Exception as e:
|
|
2286
|
-
return _wrong_type_message(
|
|
2815
|
+
return _wrong_type_message(obj, name, self.__name__, explanation=str(e))
|
|
2287
2816
|
|
|
2288
2817
|
return ""
|
|
2289
2818
|
|