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