vtjson 2.1.9__tar.gz → 2.2.1__tar.gz
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-2.1.9/src/vtjson.egg-info → vtjson-2.2.1}/PKG-INFO +2 -2
- {vtjson-2.1.9 → vtjson-2.2.1}/README.md +1 -1
- {vtjson-2.1.9 → vtjson-2.2.1}/src/vtjson/vtjson.py +76 -41
- {vtjson-2.1.9 → vtjson-2.2.1/src/vtjson.egg-info}/PKG-INFO +2 -2
- {vtjson-2.1.9 → vtjson-2.2.1}/tests/test_vtjson.py +17 -4
- {vtjson-2.1.9 → vtjson-2.2.1}/AUTHORS +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/LICENSE +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/pyproject.toml +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/setup.cfg +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/src/vtjson/__init__.py +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/src/vtjson/py.typed +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/src/vtjson.egg-info/SOURCES.txt +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/src/vtjson.egg-info/dependency_links.txt +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/src/vtjson.egg-info/requires.txt +0 -0
- {vtjson-2.1.9 → vtjson-2.2.1}/src/vtjson.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vtjson
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: A lightweight package for validating JSON like Python objects
|
|
5
5
|
Author-email: Michel Van den Bergh <michel.vandenbergh@uhasselt.be>
|
|
6
6
|
Project-URL: Homepage, https://github.com/vdbergh/vtjson
|
|
@@ -130,4 +130,4 @@ class book_schema(TypedDict):
|
|
|
130
130
|
year: Annotated[int, ge(1900)]
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
For comprehensive documentation about `vtjson` see [https://www.cantate.be/vtjson](https://www.cantate.be/vtjson).
|
|
133
|
+
For comprehensive documentation about `vtjson` see [https://www.cantate.be/vtjson](https://www.cantate.be/vtjson) (canonical reference) or [https://vtjson.readthedocs.io](https://vtjson.readthedocs.io).
|
|
@@ -110,4 +110,4 @@ class book_schema(TypedDict):
|
|
|
110
110
|
year: Annotated[int, ge(1900)]
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
For comprehensive documentation about `vtjson` see [https://www.cantate.be/vtjson](https://www.cantate.be/vtjson).
|
|
113
|
+
For comprehensive documentation about `vtjson` see [https://www.cantate.be/vtjson](https://www.cantate.be/vtjson) (canonical reference) or [https://vtjson.readthedocs.io](https://vtjson.readthedocs.io).
|
|
@@ -22,6 +22,7 @@ from typing import (
|
|
|
22
22
|
TypeVar,
|
|
23
23
|
Union,
|
|
24
24
|
cast,
|
|
25
|
+
overload,
|
|
25
26
|
)
|
|
26
27
|
|
|
27
28
|
try:
|
|
@@ -85,8 +86,6 @@ import dns.resolver
|
|
|
85
86
|
import email_validator
|
|
86
87
|
import idna
|
|
87
88
|
|
|
88
|
-
T = TypeVar("T")
|
|
89
|
-
|
|
90
89
|
|
|
91
90
|
def safe_cast(schema: Type[T], obj: Any) -> T:
|
|
92
91
|
"""
|
|
@@ -205,7 +204,7 @@ class SchemaError(Exception):
|
|
|
205
204
|
pass
|
|
206
205
|
|
|
207
206
|
|
|
208
|
-
__version__ = "2.1
|
|
207
|
+
__version__ = "2.2.1"
|
|
209
208
|
|
|
210
209
|
|
|
211
210
|
@dataclass
|
|
@@ -273,13 +272,13 @@ def _get_type_hints(schema: object) -> dict[str, object]:
|
|
|
273
272
|
|
|
274
273
|
def _to_dict(
|
|
275
274
|
type_hints: Mapping[str, object], total: bool = True
|
|
276
|
-
) -> dict[
|
|
277
|
-
d: dict[
|
|
275
|
+
) -> dict[optional_key[str], object]:
|
|
276
|
+
d: dict[optional_key[str], object] = {}
|
|
278
277
|
if not supports_Generics:
|
|
279
278
|
raise SchemaError("Generic types are not supported")
|
|
280
279
|
for k, v in type_hints.items():
|
|
281
280
|
v_ = v
|
|
282
|
-
k_
|
|
281
|
+
k_ = optional_key(k, _optional=False)
|
|
283
282
|
value_type = typing.get_origin(v)
|
|
284
283
|
if supports_NotRequired and value_type in (Required, NotRequired):
|
|
285
284
|
v_ = typing.get_args(v)[0]
|
|
@@ -320,6 +319,29 @@ def _c(s: object) -> str:
|
|
|
320
319
|
return ret
|
|
321
320
|
|
|
322
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
|
+
|
|
323
345
|
def _wrong_type_message(
|
|
324
346
|
obj: object,
|
|
325
347
|
name: str,
|
|
@@ -395,22 +417,29 @@ K = TypeVar("K")
|
|
|
395
417
|
class optional_key(Generic[K]):
|
|
396
418
|
"""
|
|
397
419
|
Make a key in a Mapping schema optional. In the common case that the key
|
|
398
|
-
|
|
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.
|
|
399
423
|
"""
|
|
400
424
|
|
|
401
425
|
key: K
|
|
426
|
+
optional: bool
|
|
402
427
|
|
|
403
|
-
def __init__(self, key: K) -> None:
|
|
428
|
+
def __init__(self, key: K, _optional: bool = True) -> None:
|
|
404
429
|
"""
|
|
405
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
|
|
406
433
|
"""
|
|
407
434
|
self.key = key
|
|
435
|
+
self.optional = _optional
|
|
408
436
|
|
|
409
437
|
def __eq__(self, key: object) -> bool:
|
|
410
438
|
if not isinstance(key, optional_key):
|
|
411
439
|
return False
|
|
412
440
|
k: object = key.key
|
|
413
|
-
|
|
441
|
+
o: object = key.optional
|
|
442
|
+
return (self.key, key.optional) == (k, o)
|
|
414
443
|
|
|
415
444
|
def __hash__(self) -> int:
|
|
416
445
|
return hash(self.key)
|
|
@@ -1614,6 +1643,24 @@ class number(compiled_schema):
|
|
|
1614
1643
|
return _wrong_type_message(obj, name, "number")
|
|
1615
1644
|
|
|
1616
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,
|
|
1654
|
+
name: str = "object",
|
|
1655
|
+
strict: bool = True,
|
|
1656
|
+
subs: Mapping[str, object] = {},
|
|
1657
|
+
) -> str:
|
|
1658
|
+
if isinstance(obj, float):
|
|
1659
|
+
return ""
|
|
1660
|
+
else:
|
|
1661
|
+
return _wrong_type_message(obj, name, "float_")
|
|
1662
|
+
|
|
1663
|
+
|
|
1617
1664
|
class email(compiled_schema):
|
|
1618
1665
|
"""
|
|
1619
1666
|
Checks if the object is a valid email address. This uses the package
|
|
@@ -2148,11 +2195,11 @@ class cond(wrapper):
|
|
|
2148
2195
|
|
|
2149
2196
|
|
|
2150
2197
|
class _fields(compiled_schema):
|
|
2151
|
-
d: dict[
|
|
2198
|
+
d: dict[optional_key[str], compiled_schema]
|
|
2152
2199
|
|
|
2153
2200
|
def __init__(
|
|
2154
2201
|
self,
|
|
2155
|
-
d: Mapping[
|
|
2202
|
+
d: Mapping[optional_key[str], object],
|
|
2156
2203
|
_deferred_compiles: _mapping | None = None,
|
|
2157
2204
|
) -> None:
|
|
2158
2205
|
self.d = {}
|
|
@@ -2167,9 +2214,14 @@ class _fields(compiled_schema):
|
|
|
2167
2214
|
subs: Mapping[str, object] = {},
|
|
2168
2215
|
) -> str:
|
|
2169
2216
|
for k, v in self.d.items():
|
|
2170
|
-
name_ = f"{name}.{k}"
|
|
2171
|
-
if not isinstance(k, optional_key)
|
|
2172
|
-
|
|
2217
|
+
name_ = f"{name}.{k.key}"
|
|
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
|
+
|
|
2173
2225
|
if isinstance(k, optional_key):
|
|
2174
2226
|
k_ = k.key
|
|
2175
2227
|
else:
|
|
@@ -2189,7 +2241,7 @@ class fields(wrapper):
|
|
|
2189
2241
|
schemaN` respectively
|
|
2190
2242
|
"""
|
|
2191
2243
|
|
|
2192
|
-
d: dict[
|
|
2244
|
+
d: dict[optional_key[str], object]
|
|
2193
2245
|
|
|
2194
2246
|
def __init__(self, d: Mapping[str | optional_key[str], object]) -> None:
|
|
2195
2247
|
"""
|
|
@@ -2207,10 +2259,8 @@ class fields(wrapper):
|
|
|
2207
2259
|
f"key {repr(k)} in {repr(d)} is not an instance of"
|
|
2208
2260
|
" optional_key and not a string"
|
|
2209
2261
|
)
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
else:
|
|
2213
|
-
self.d[k] = v
|
|
2262
|
+
key_ = _canonize_key(k)
|
|
2263
|
+
self.d[key_] = v
|
|
2214
2264
|
|
|
2215
2265
|
def __compile__(self, _deferred_compiles: _mapping | None = None) -> _fields:
|
|
2216
2266
|
return _fields(self.d, _deferred_compiles=_deferred_compiles)
|
|
@@ -2307,8 +2357,6 @@ class _type(compiled_schema):
|
|
|
2307
2357
|
if math_numbers:
|
|
2308
2358
|
if schema == float:
|
|
2309
2359
|
setattr(self, "__validate__", self.__validate_float__)
|
|
2310
|
-
if schema == complex:
|
|
2311
|
-
setattr(self, "__validate__", self.__validate_complex__)
|
|
2312
2360
|
self.schema = schema
|
|
2313
2361
|
|
|
2314
2362
|
def __validate__(
|
|
@@ -2341,19 +2389,6 @@ class _type(compiled_schema):
|
|
|
2341
2389
|
else:
|
|
2342
2390
|
return _wrong_type_message(obj, name, "float")
|
|
2343
2391
|
|
|
2344
|
-
def __validate_complex__(
|
|
2345
|
-
self,
|
|
2346
|
-
obj: object,
|
|
2347
|
-
name: str = "object",
|
|
2348
|
-
strict: bool = True,
|
|
2349
|
-
subs: Mapping[str, object] = {},
|
|
2350
|
-
) -> str:
|
|
2351
|
-
# consider int, float as subtypes of complex
|
|
2352
|
-
if isinstance(obj, (int, float, complex)):
|
|
2353
|
-
return ""
|
|
2354
|
-
else:
|
|
2355
|
-
return _wrong_type_message(obj, name, "complex")
|
|
2356
|
-
|
|
2357
2392
|
def __str__(self) -> str:
|
|
2358
2393
|
return self.schema.__name__
|
|
2359
2394
|
|
|
@@ -2512,13 +2547,13 @@ class _dict(compiled_schema):
|
|
|
2512
2547
|
for k in schema:
|
|
2513
2548
|
compiled_schema = _compile(schema[k], _deferred_compiles=_deferred_compiles)
|
|
2514
2549
|
optional = True
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2550
|
+
key_ = _canonize_key(k)
|
|
2551
|
+
if isinstance(key_, optional_key):
|
|
2552
|
+
key = key_.key
|
|
2553
|
+
optional = key_.optional
|
|
2519
2554
|
else:
|
|
2555
|
+
key = key_
|
|
2520
2556
|
optional = False
|
|
2521
|
-
key = k
|
|
2522
2557
|
c = _compile(key, _deferred_compiles=_deferred_compiles)
|
|
2523
2558
|
if isinstance(c, _const):
|
|
2524
2559
|
if not optional:
|
|
@@ -2654,7 +2689,7 @@ class protocol(wrapper):
|
|
|
2654
2689
|
which validate the corresponding fields in the object.
|
|
2655
2690
|
"""
|
|
2656
2691
|
|
|
2657
|
-
type_dict: dict[
|
|
2692
|
+
type_dict: dict[optional_key[str], object]
|
|
2658
2693
|
dict: bool
|
|
2659
2694
|
__name__: str
|
|
2660
2695
|
|
|
@@ -2685,7 +2720,7 @@ class protocol(wrapper):
|
|
|
2685
2720
|
) -> compiled_schema:
|
|
2686
2721
|
if not self.dict:
|
|
2687
2722
|
return _set_name(
|
|
2688
|
-
|
|
2723
|
+
_fields(self.type_dict),
|
|
2689
2724
|
self.__name__,
|
|
2690
2725
|
reason=True,
|
|
2691
2726
|
_deferred_compiles=_deferred_compiles,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vtjson
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: A lightweight package for validating JSON like Python objects
|
|
5
5
|
Author-email: Michel Van den Bergh <michel.vandenbergh@uhasselt.be>
|
|
6
6
|
Project-URL: Homepage, https://github.com/vdbergh/vtjson
|
|
@@ -130,4 +130,4 @@ class book_schema(TypedDict):
|
|
|
130
130
|
year: Annotated[int, ge(1900)]
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
For comprehensive documentation about `vtjson` see [https://www.cantate.be/vtjson](https://www.cantate.be/vtjson).
|
|
133
|
+
For comprehensive documentation about `vtjson` see [https://www.cantate.be/vtjson](https://www.cantate.be/vtjson) (canonical reference) or [https://vtjson.readthedocs.io](https://vtjson.readthedocs.io).
|
|
@@ -75,6 +75,7 @@ from vtjson import (
|
|
|
75
75
|
email,
|
|
76
76
|
fields,
|
|
77
77
|
filter,
|
|
78
|
+
float_,
|
|
78
79
|
ge,
|
|
79
80
|
glob,
|
|
80
81
|
gt,
|
|
@@ -303,6 +304,17 @@ class TestValidation(unittest.TestCase):
|
|
|
303
304
|
object_ = {"a": 3}
|
|
304
305
|
validate(schema, object_)
|
|
305
306
|
show(mc)
|
|
307
|
+
schema = {r"a\?": str, "b": str}
|
|
308
|
+
with self.assertRaises(ValidationError) as mc:
|
|
309
|
+
object_ = {"b": "c"}
|
|
310
|
+
validate(schema, object_)
|
|
311
|
+
show(mc)
|
|
312
|
+
with self.assertRaises(ValidationError) as mc:
|
|
313
|
+
object_ = {"a": "c", "b": "d"}
|
|
314
|
+
validate(schema, object_)
|
|
315
|
+
show(mc)
|
|
316
|
+
object_ = {"a?": "c", "b": "d"}
|
|
317
|
+
validate(schema, object_)
|
|
306
318
|
|
|
307
319
|
class fake_string:
|
|
308
320
|
def __init__(self, s: str) -> None:
|
|
@@ -1872,7 +1884,7 @@ class TestValidation(unittest.TestCase):
|
|
|
1872
1884
|
self.assertTrue("...}" in valid)
|
|
1873
1885
|
self.assertTrue("TRUNCATED" in valid)
|
|
1874
1886
|
|
|
1875
|
-
def
|
|
1887
|
+
def test_int_float(self) -> None:
|
|
1876
1888
|
schema: object
|
|
1877
1889
|
schema = int
|
|
1878
1890
|
validate(schema, 1)
|
|
@@ -1885,10 +1897,11 @@ class TestValidation(unittest.TestCase):
|
|
|
1885
1897
|
with self.assertRaises(ValidationError) as mc:
|
|
1886
1898
|
validate(schema, 1.0 + 1.0j)
|
|
1887
1899
|
show(mc)
|
|
1888
|
-
schema =
|
|
1889
|
-
|
|
1900
|
+
schema = float_
|
|
1901
|
+
with self.assertRaises(ValidationError) as mc:
|
|
1902
|
+
validate(schema, 1)
|
|
1903
|
+
show(mc)
|
|
1890
1904
|
validate(schema, 1.0)
|
|
1891
|
-
validate(schema, 1.0 + 1.0j)
|
|
1892
1905
|
|
|
1893
1906
|
def test_float_equal(self) -> None:
|
|
1894
1907
|
schema: object
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|