vtjson 2.1.3__py3-none-any.whl → 2.1.5__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/py.typed ADDED
File without changes
vtjson/vtjson.py CHANGED
@@ -12,7 +12,7 @@ import urllib.parse
12
12
  import warnings
13
13
  from collections.abc import Sequence, Sized
14
14
  from dataclasses import dataclass
15
- from typing import Any, Callable, Type, TypeVar, Union, cast
15
+ from typing import Any, Callable, Mapping, Type, TypeVar, Union, cast
16
16
 
17
17
  try:
18
18
  from typing import Literal
@@ -45,6 +45,12 @@ if hasattr(typing, "get_origin"):
45
45
  else:
46
46
  supports_Generics = False
47
47
 
48
+ try:
49
+ Sequence[str]
50
+ supports_Generic_ABC = True
51
+ except Exception:
52
+ supports_Generic_ABC = False
53
+
48
54
  try:
49
55
  typing.get_type_hints(int, include_extras=True)
50
56
  supports_structural = True
@@ -83,7 +89,7 @@ class compiled_schema:
83
89
  object_: object,
84
90
  name: str,
85
91
  strict: bool,
86
- subs: dict[str, object],
92
+ subs: Mapping[str, object],
87
93
  ) -> str:
88
94
  return ""
89
95
 
@@ -115,14 +121,14 @@ class SchemaError(Exception):
115
121
  pass
116
122
 
117
123
 
118
- __version__ = "2.1.3"
124
+ __version__ = "2.1.5"
119
125
 
120
126
 
121
127
  @dataclass
122
128
  class Apply:
123
129
  skip_first: bool | None = None
124
130
  name: str | None = None
125
- labels: list[str] | None = None
131
+ labels: Sequence[str] | None = None
126
132
 
127
133
  def __call__(self, schemas: tuple[object, ...]) -> object:
128
134
  if len(schemas) == 0:
@@ -152,13 +158,16 @@ def _get_type_hints(schema: object) -> dict[str, object]:
152
158
  raise SchemaError(
153
159
  "Structural subtyping in not supported in this " "Python version"
154
160
  )
155
- type_hints = {}
156
161
  if isinstance(schema, type) and hasattr(schema, "__annotations__"):
157
162
  type_hints = typing.get_type_hints(schema, include_extras=True)
163
+ else:
164
+ raise SchemaError("The schema does not have type hints")
158
165
  return type_hints
159
166
 
160
167
 
161
- def _to_dict(type_hints: dict[str, object], total: bool = True) -> dict[object, object]:
168
+ def _to_dict(
169
+ type_hints: Mapping[str, object], total: bool = True
170
+ ) -> dict[object, object]:
162
171
  d: dict[object, object] = {}
163
172
  if not supports_Generics:
164
173
  raise SchemaError("Generic types are not supported")
@@ -224,7 +233,7 @@ def _wrong_type_message(
224
233
  class _validate_meta(type):
225
234
  __schema__: object
226
235
  __strict__: bool
227
- __subs__: dict[str, object]
236
+ __subs__: Mapping[str, object]
228
237
  __dbg__: bool
229
238
 
230
239
  def __instancecheck__(cls, object_: object) -> bool:
@@ -241,7 +250,7 @@ def make_type(
241
250
  name: str | None = None,
242
251
  strict: bool = True,
243
252
  debug: bool = False,
244
- subs: dict[str, object] = {},
253
+ subs: Mapping[str, object] = {},
245
254
  ) -> _validate_meta:
246
255
  if name is None:
247
256
  if hasattr(schema, "__name__"):
@@ -292,7 +301,7 @@ class _union(compiled_schema):
292
301
  object_: object,
293
302
  name: str = "object",
294
303
  strict: bool = True,
295
- subs: dict[str, object] = {},
304
+ subs: Mapping[str, object] = {},
296
305
  ) -> str:
297
306
  messages = []
298
307
  for schema in self.schemas:
@@ -331,7 +340,7 @@ class _intersect(compiled_schema):
331
340
  object_: object,
332
341
  name: str = "object",
333
342
  strict: bool = True,
334
- subs: dict[str, object] = {},
343
+ subs: Mapping[str, object] = {},
335
344
  ) -> str:
336
345
  for schema in self.schemas:
337
346
  message = schema.__validate__(object_, name=name, strict=strict, subs=subs)
@@ -363,7 +372,7 @@ class _complement(compiled_schema):
363
372
  object_: object,
364
373
  name: str = "object",
365
374
  strict: bool = True,
366
- subs: dict[str, object] = {},
375
+ subs: Mapping[str, object] = {},
367
376
  ) -> str:
368
377
  message = self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
369
378
  if message != "":
@@ -395,7 +404,7 @@ class _lax(compiled_schema):
395
404
  object_: object,
396
405
  name: str = "object",
397
406
  strict: bool = True,
398
- subs: dict[str, object] = {},
407
+ subs: Mapping[str, object] = {},
399
408
  ) -> str:
400
409
  return self.schema.__validate__(object_, name=name, strict=False, subs=subs)
401
410
 
@@ -423,7 +432,7 @@ class _strict(compiled_schema):
423
432
  object_: object,
424
433
  name: str = "object",
425
434
  strict: bool = True,
426
- subs: dict[str, object] = {},
435
+ subs: Mapping[str, object] = {},
427
436
  ) -> str:
428
437
  return self.schema.__validate__(object_, name=name, strict=True, subs=subs)
429
438
 
@@ -457,7 +466,7 @@ class _set_label(compiled_schema):
457
466
  object_: object,
458
467
  name: str = "object",
459
468
  strict: bool = True,
460
- subs: dict[str, object] = {},
469
+ subs: Mapping[str, object] = {},
461
470
  ) -> str:
462
471
  common_labels = tuple(set(subs.keys()).intersection(self.labels))
463
472
  if len(common_labels) >= 2:
@@ -510,7 +519,7 @@ class quote(compiled_schema):
510
519
  object_: object,
511
520
  name: str = "object",
512
521
  strict: bool = True,
513
- subs: dict[str, object] = {},
522
+ subs: Mapping[str, object] = {},
514
523
  ) -> str:
515
524
  return self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
516
525
 
@@ -536,7 +545,7 @@ class _set_name(compiled_schema):
536
545
  object_: object,
537
546
  name: str = "object",
538
547
  strict: bool = True,
539
- subs: dict[str, object] = {},
548
+ subs: Mapping[str, object] = {},
540
549
  ) -> str:
541
550
  message = self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
542
551
  if message != "":
@@ -607,7 +616,7 @@ class regex(compiled_schema):
607
616
  object_: object,
608
617
  name: str = "object",
609
618
  strict: bool = True,
610
- subs: dict[str, object] = {},
619
+ subs: Mapping[str, object] = {},
611
620
  ) -> str:
612
621
  if not isinstance(object_, str):
613
622
  return _wrong_type_message(object_, name, self.__name__)
@@ -646,7 +655,7 @@ class glob(compiled_schema):
646
655
  object_: object,
647
656
  name: str = "object",
648
657
  strict: bool = True,
649
- subs: dict[str, object] = {},
658
+ subs: Mapping[str, object] = {},
650
659
  ) -> str:
651
660
  if not isinstance(object_, str):
652
661
  return _wrong_type_message(object_, name, self.__name__)
@@ -682,7 +691,7 @@ class magic(compiled_schema):
682
691
  object_: object,
683
692
  name: str = "object",
684
693
  strict: bool = True,
685
- subs: dict[str, object] = {},
694
+ subs: Mapping[str, object] = {},
686
695
  ) -> str:
687
696
  if not isinstance(object_, (str, bytes)):
688
697
  return _wrong_type_message(object_, name, self.__name__)
@@ -731,7 +740,7 @@ class div(compiled_schema):
731
740
  object_: object,
732
741
  name: str = "object",
733
742
  strict: bool = True,
734
- subs: dict[str, object] = {},
743
+ subs: Mapping[str, object] = {},
735
744
  ) -> str:
736
745
  if not isinstance(object_, int):
737
746
  return _wrong_type_message(object_, name, "int")
@@ -778,7 +787,7 @@ class close_to(compiled_schema):
778
787
  object_: object,
779
788
  name: str = "object",
780
789
  strict: bool = True,
781
- subs: dict[str, object] = {},
790
+ subs: Mapping[str, object] = {},
782
791
  ) -> str:
783
792
  if not isinstance(object_, (float, int)):
784
793
  return _wrong_type_message(object_, name, "number")
@@ -808,7 +817,7 @@ class gt(compiled_schema):
808
817
  object_: object,
809
818
  name: str = "object",
810
819
  strict: bool = True,
811
- subs: dict[str, object] = {},
820
+ subs: Mapping[str, object] = {},
812
821
  ) -> str:
813
822
  try:
814
823
  if self.lb < object_:
@@ -839,7 +848,7 @@ class ge(compiled_schema):
839
848
  object_: object,
840
849
  name: str = "object",
841
850
  strict: bool = True,
842
- subs: dict[str, object] = {},
851
+ subs: Mapping[str, object] = {},
843
852
  ) -> str:
844
853
  try:
845
854
  if self.lb <= object_:
@@ -870,7 +879,7 @@ class lt(compiled_schema):
870
879
  object_: object,
871
880
  name: str = "object",
872
881
  strict: bool = True,
873
- subs: dict[str, object] = {},
882
+ subs: Mapping[str, object] = {},
874
883
  ) -> str:
875
884
  try:
876
885
  if self.ub > object_:
@@ -901,7 +910,7 @@ class le(compiled_schema):
901
910
  object_: object,
902
911
  name: str = "object",
903
912
  strict: bool = True,
904
- subs: dict[str, object] = {},
913
+ subs: Mapping[str, object] = {},
905
914
  ) -> str:
906
915
  try:
907
916
  if self.ub >= object_:
@@ -1005,7 +1014,7 @@ class size(compiled_schema):
1005
1014
  object_: object,
1006
1015
  name: str = "object",
1007
1016
  strict: bool = True,
1008
- subs: dict[str, object] = {},
1017
+ subs: Mapping[str, object] = {},
1009
1018
  ) -> str:
1010
1019
  if not isinstance(object_, Sized):
1011
1020
  return f"{name} (value:{_c(object_)}) has no len()"
@@ -1028,7 +1037,7 @@ class _deferred(compiled_schema):
1028
1037
  object_: object,
1029
1038
  name: str = "object",
1030
1039
  strict: bool = True,
1031
- subs: dict[str, object] = {},
1040
+ subs: Mapping[str, object] = {},
1032
1041
  ) -> str:
1033
1042
  if self.key not in self.collection:
1034
1043
  raise ValidationError(f"{name}: key {self.key} is unknown")
@@ -1110,11 +1119,19 @@ def _compile(
1110
1119
  ret = schema
1111
1120
  elif supports_TypedDict and typing.is_typeddict(schema):
1112
1121
  ret = _compile(
1113
- structural(schema, dict=True), _deferred_compiles=_deferred_compiles
1122
+ protocol(schema, dict=True), _deferred_compiles=_deferred_compiles
1114
1123
  )
1115
1124
  elif isinstance(schema, type) and hasattr(schema, "_is_protocol"):
1116
1125
  assert hasattr(schema, "__name__") and isinstance(schema.__name__, str)
1117
- ret = _compile(structural(schema), _deferred_compiles=_deferred_compiles)
1126
+ ret = _compile(protocol(schema), _deferred_compiles=_deferred_compiles)
1127
+ elif (
1128
+ isinstance(schema, type)
1129
+ and issubclass(schema, tuple)
1130
+ and hasattr(schema, "_fields")
1131
+ ):
1132
+ ret = _compile(
1133
+ intersect(tuple, protocol(schema)), _deferred_compiles=_deferred_compiles
1134
+ )
1118
1135
  elif schema == Any:
1119
1136
  ret = anything()
1120
1137
  elif hasattr(schema, "__name__") and hasattr(schema, "__supertype__"):
@@ -1122,12 +1139,20 @@ def _compile(
1122
1139
  schema,
1123
1140
  _deferred_compiles=_deferred_compiles,
1124
1141
  )
1125
- elif origin == list:
1126
- ret = _List(typing.get_args(schema)[0], _deferred_compiles=_deferred_compiles)
1127
1142
  elif origin == tuple:
1128
1143
  ret = _Tuple(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
1129
- elif origin == dict:
1130
- ret = _Dict(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
1144
+ elif isinstance(origin, type) and issubclass(origin, Sequence):
1145
+ ret = _List(
1146
+ typing.get_args(schema)[0],
1147
+ type_schema=origin,
1148
+ _deferred_compiles=_deferred_compiles,
1149
+ )
1150
+ elif isinstance(origin, type) and issubclass(origin, Mapping):
1151
+ ret = _Dict(
1152
+ typing.get_args(schema),
1153
+ type_schema=origin,
1154
+ _deferred_compiles=_deferred_compiles,
1155
+ )
1131
1156
  elif origin == Union:
1132
1157
  ret = _Union(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
1133
1158
  elif supports_Literal and origin == Literal:
@@ -1140,9 +1165,9 @@ def _compile(
1140
1165
  ret = _type(schema)
1141
1166
  elif callable(schema):
1142
1167
  ret = _callable(schema)
1143
- elif isinstance(schema, tuple) or isinstance(schema, list):
1168
+ elif isinstance(schema, Sequence) and not isinstance(schema, str):
1144
1169
  ret = _sequence(schema, _deferred_compiles=_deferred_compiles)
1145
- elif isinstance(schema, dict):
1170
+ elif isinstance(schema, Mapping):
1146
1171
  ret = _dict(schema, _deferred_compiles=_deferred_compiles)
1147
1172
  elif isinstance(schema, set):
1148
1173
  ret = _set(schema, _deferred_compiles=_deferred_compiles)
@@ -1162,7 +1187,7 @@ def _validate(
1162
1187
  object_: object,
1163
1188
  name: str = "object",
1164
1189
  strict: bool = True,
1165
- subs: dict[str, object] = {},
1190
+ subs: Mapping[str, object] = {},
1166
1191
  ) -> str:
1167
1192
  return compile(schema).__validate__(object_, name=name, strict=strict, subs=subs)
1168
1193
 
@@ -1172,7 +1197,7 @@ def validate(
1172
1197
  object_: object,
1173
1198
  name: str = "object",
1174
1199
  strict: bool = True,
1175
- subs: dict[str, object] = {},
1200
+ subs: Mapping[str, object] = {},
1176
1201
  ) -> None:
1177
1202
  message = _validate(
1178
1203
  schema,
@@ -1201,7 +1226,7 @@ class number(compiled_schema):
1201
1226
  object_: object,
1202
1227
  name: str = "object",
1203
1228
  strict: bool = True,
1204
- subs: dict[str, object] = {},
1229
+ subs: Mapping[str, object] = {},
1205
1230
  ) -> str:
1206
1231
  if isinstance(object_, (int, float)):
1207
1232
  return ""
@@ -1224,7 +1249,7 @@ class email(compiled_schema):
1224
1249
  object_: object,
1225
1250
  name: str = "object",
1226
1251
  strict: bool = True,
1227
- subs: dict[str, object] = {},
1252
+ subs: Mapping[str, object] = {},
1228
1253
  ) -> str:
1229
1254
  if not isinstance(object_, str):
1230
1255
  return _wrong_type_message(
@@ -1261,7 +1286,7 @@ class ip_address(compiled_schema):
1261
1286
  object_: object,
1262
1287
  name: str = "object",
1263
1288
  strict: bool = True,
1264
- subs: dict[str, object] = {},
1289
+ subs: Mapping[str, object] = {},
1265
1290
  ) -> str:
1266
1291
  if not isinstance(object_, (int, str, bytes)):
1267
1292
  return _wrong_type_message(object_, name, self.__name__)
@@ -1278,7 +1303,7 @@ class url(compiled_schema):
1278
1303
  object_: object,
1279
1304
  name: str = "object",
1280
1305
  strict: bool = True,
1281
- subs: dict[str, object] = {},
1306
+ subs: Mapping[str, object] = {},
1282
1307
  ) -> str:
1283
1308
  if not isinstance(object_, str):
1284
1309
  return _wrong_type_message(object_, name, "url")
@@ -1304,7 +1329,7 @@ class date_time(compiled_schema):
1304
1329
  object_: object,
1305
1330
  name: str = "object",
1306
1331
  strict: bool = True,
1307
- subs: dict[str, object] = {},
1332
+ subs: Mapping[str, object] = {},
1308
1333
  ) -> str:
1309
1334
  if not isinstance(object_, str):
1310
1335
  return _wrong_type_message(object_, name, self.__name__)
@@ -1327,7 +1352,7 @@ class date(compiled_schema):
1327
1352
  object_: object,
1328
1353
  name: str = "object",
1329
1354
  strict: bool = True,
1330
- subs: dict[str, object] = {},
1355
+ subs: Mapping[str, object] = {},
1331
1356
  ) -> str:
1332
1357
  if not isinstance(object_, str):
1333
1358
  return _wrong_type_message(object_, name, "date")
@@ -1344,7 +1369,7 @@ class time(compiled_schema):
1344
1369
  object_: object,
1345
1370
  name: str = "object",
1346
1371
  strict: bool = True,
1347
- subs: dict[str, object] = {},
1372
+ subs: Mapping[str, object] = {},
1348
1373
  ) -> str:
1349
1374
  if not isinstance(object_, str):
1350
1375
  return _wrong_type_message(object_, name, "date")
@@ -1361,7 +1386,7 @@ class nothing(compiled_schema):
1361
1386
  object_: object,
1362
1387
  name: str = "object",
1363
1388
  strict: bool = True,
1364
- subs: dict[str, object] = {},
1389
+ subs: Mapping[str, object] = {},
1365
1390
  ) -> str:
1366
1391
  return _wrong_type_message(object_, name, "nothing")
1367
1392
 
@@ -1372,7 +1397,7 @@ class anything(compiled_schema):
1372
1397
  object_: object,
1373
1398
  name: str = "object",
1374
1399
  strict: bool = True,
1375
- subs: dict[str, object] = {},
1400
+ subs: Mapping[str, object] = {},
1376
1401
  ) -> str:
1377
1402
  return ""
1378
1403
 
@@ -1403,7 +1428,7 @@ class domain_name(compiled_schema):
1403
1428
  object_: object,
1404
1429
  name: str = "object",
1405
1430
  strict: bool = True,
1406
- subs: dict[str, object] = {},
1431
+ subs: Mapping[str, object] = {},
1407
1432
  ) -> str:
1408
1433
  if not isinstance(object_, str):
1409
1434
  return _wrong_type_message(object_, name, self.__name__)
@@ -1439,9 +1464,9 @@ class at_least_one_of(compiled_schema):
1439
1464
  object_: object,
1440
1465
  name: str = "object",
1441
1466
  strict: bool = True,
1442
- subs: dict[str, object] = {},
1467
+ subs: Mapping[str, object] = {},
1443
1468
  ) -> str:
1444
- if not isinstance(object_, dict):
1469
+ if not isinstance(object_, Mapping):
1445
1470
  return _wrong_type_message(object_, name, self.__name__)
1446
1471
  try:
1447
1472
  if any([a in object_ for a in self.args]):
@@ -1466,9 +1491,9 @@ class at_most_one_of(compiled_schema):
1466
1491
  object_: object,
1467
1492
  name: str = "object",
1468
1493
  strict: bool = True,
1469
- subs: dict[str, object] = {},
1494
+ subs: Mapping[str, object] = {},
1470
1495
  ) -> str:
1471
- if not isinstance(object_, dict):
1496
+ if not isinstance(object_, Mapping):
1472
1497
  return _wrong_type_message(object_, name, self.__name__)
1473
1498
  try:
1474
1499
  if sum([a in object_ for a in self.args]) <= 1:
@@ -1493,9 +1518,9 @@ class one_of(compiled_schema):
1493
1518
  object_: object,
1494
1519
  name: str = "object",
1495
1520
  strict: bool = True,
1496
- subs: dict[str, object] = {},
1521
+ subs: Mapping[str, object] = {},
1497
1522
  ) -> str:
1498
- if not isinstance(object_, dict):
1523
+ if not isinstance(object_, Mapping):
1499
1524
  return _wrong_type_message(object_, name, self.__name__)
1500
1525
  try:
1501
1526
  if sum([a in object_ for a in self.args]) == 1:
@@ -1517,10 +1542,10 @@ class keys(compiled_schema):
1517
1542
  object_: object,
1518
1543
  name: str = "object",
1519
1544
  strict: bool = True,
1520
- subs: dict[str, object] = {},
1545
+ subs: Mapping[str, object] = {},
1521
1546
  ) -> str:
1522
- if not isinstance(object_, dict):
1523
- return _wrong_type_message(object_, name, "dict") # TODO: __name__
1547
+ if not isinstance(object_, Mapping):
1548
+ return _wrong_type_message(object_, name, "Mapping") # TODO: __name__
1524
1549
  for k in self.args:
1525
1550
  if k not in object_:
1526
1551
  return f"{name}[{repr(k)}] is missing"
@@ -1553,7 +1578,7 @@ class _ifthen(compiled_schema):
1553
1578
  object_: object,
1554
1579
  name: str = "object",
1555
1580
  strict: bool = True,
1556
- subs: dict[str, object] = {},
1581
+ subs: Mapping[str, object] = {},
1557
1582
  ) -> str:
1558
1583
  if (
1559
1584
  self.if_schema.__validate__(object_, name=name, strict=strict, subs=subs)
@@ -1615,7 +1640,7 @@ class _cond(compiled_schema):
1615
1640
  object_: object,
1616
1641
  name: str = "object",
1617
1642
  strict: bool = True,
1618
- subs: dict[str, object] = {},
1643
+ subs: Mapping[str, object] = {},
1619
1644
  ) -> str:
1620
1645
  for c in self.conditions:
1621
1646
  if c[0].__validate__(object_, name=name, strict=strict, subs=subs) == "":
@@ -1640,7 +1665,7 @@ class _fields(compiled_schema):
1640
1665
  d: dict[str, compiled_schema]
1641
1666
 
1642
1667
  def __init__(
1643
- self, d: dict[str, object], _deferred_compiles: _mapping | None = None
1668
+ self, d: Mapping[str, object], _deferred_compiles: _mapping | None = None
1644
1669
  ) -> None:
1645
1670
  self.d = {}
1646
1671
  for k, v in d.items():
@@ -1651,7 +1676,7 @@ class _fields(compiled_schema):
1651
1676
  object_: object,
1652
1677
  name: str = "object",
1653
1678
  strict: bool = True,
1654
- subs: dict[str, object] = {},
1679
+ subs: Mapping[str, object] = {},
1655
1680
  ) -> str:
1656
1681
  for k, v in self.d.items():
1657
1682
  name_ = f"{name}.{k}"
@@ -1667,8 +1692,8 @@ class _fields(compiled_schema):
1667
1692
 
1668
1693
  class fields:
1669
1694
  def __init__(self, d: object) -> None:
1670
- if not isinstance(d, dict):
1671
- raise SchemaError(f"{repr(d)} is not a dictionary")
1695
+ if not isinstance(d, Mapping):
1696
+ raise SchemaError(f"{repr(d)} is not a Mapping")
1672
1697
  for k in d:
1673
1698
  if not isinstance(k, str):
1674
1699
  raise SchemaError(f"key {repr(k)} in {repr(d)} is not a string")
@@ -1707,7 +1732,7 @@ class _filter(compiled_schema):
1707
1732
  object_: object,
1708
1733
  name: str = "object",
1709
1734
  strict: bool = True,
1710
- subs: dict[str, object] = {},
1735
+ subs: Mapping[str, object] = {},
1711
1736
  ) -> str:
1712
1737
  try:
1713
1738
  object_ = self.filter(object_)
@@ -1766,7 +1791,7 @@ class _type(compiled_schema):
1766
1791
  object_: object,
1767
1792
  name: str = "object",
1768
1793
  strict: bool = True,
1769
- subs: dict[str, object] = {},
1794
+ subs: Mapping[str, object] = {},
1770
1795
  ) -> str:
1771
1796
  try:
1772
1797
  if self.schema == float and isinstance(object_, int):
@@ -1783,7 +1808,7 @@ class _type(compiled_schema):
1783
1808
  object_: object,
1784
1809
  name: str = "object",
1785
1810
  strict: bool = True,
1786
- subs: dict[str, object] = {},
1811
+ subs: Mapping[str, object] = {},
1787
1812
  ) -> str:
1788
1813
  # consider int as a subtype of float
1789
1814
  if isinstance(object_, (int, float)):
@@ -1796,7 +1821,7 @@ class _type(compiled_schema):
1796
1821
  object_: object,
1797
1822
  name: str = "object",
1798
1823
  strict: bool = True,
1799
- subs: dict[str, object] = {},
1824
+ subs: Mapping[str, object] = {},
1800
1825
  ) -> str:
1801
1826
  # consider int, float as subtypes of complex
1802
1827
  if isinstance(object_, (int, float, complex)):
@@ -1815,10 +1840,14 @@ class _sequence(compiled_schema):
1815
1840
 
1816
1841
  def __init__(
1817
1842
  self,
1818
- schema: list[object] | tuple[object, ...],
1843
+ schema: Sequence[object],
1844
+ type_schema: type | None = None,
1819
1845
  _deferred_compiles: _mapping | None = None,
1820
1846
  ) -> None:
1821
- self.type_schema = type(schema)
1847
+ if type_schema is None:
1848
+ self.type_schema = type(schema)
1849
+ else:
1850
+ self.type_schema = type_schema
1822
1851
  self.schema = [
1823
1852
  _compile(o, _deferred_compiles=_deferred_compiles)
1824
1853
  for o in schema
@@ -1838,10 +1867,10 @@ class _sequence(compiled_schema):
1838
1867
  object_: object,
1839
1868
  name: str = "object",
1840
1869
  strict: bool = True,
1841
- subs: dict[str, object] = {},
1870
+ subs: Mapping[str, object] = {},
1842
1871
  ) -> str:
1843
1872
  if not isinstance(object_, self.type_schema):
1844
- return _wrong_type_message(object_, name, type(self.schema).__name__)
1873
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
1845
1874
  ls = len(self.schema)
1846
1875
  lo = len(object_)
1847
1876
  if strict:
@@ -1861,10 +1890,10 @@ class _sequence(compiled_schema):
1861
1890
  object_: object,
1862
1891
  name: str = "object",
1863
1892
  strict: bool = True,
1864
- subs: dict[str, object] = {},
1893
+ subs: Mapping[str, object] = {},
1865
1894
  ) -> str:
1866
1895
  if not isinstance(object_, self.type_schema):
1867
- return _wrong_type_message(object_, name, type(self.schema).__name__)
1896
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
1868
1897
  ls = len(self.schema)
1869
1898
  lo = len(object_)
1870
1899
  if ls > lo:
@@ -1901,7 +1930,7 @@ class _const(compiled_schema):
1901
1930
  object_: object,
1902
1931
  name: str = "object",
1903
1932
  strict: bool = True,
1904
- subs: dict[str, object] = {},
1933
+ subs: Mapping[str, object] = {},
1905
1934
  ) -> str:
1906
1935
  if object_ != self.schema:
1907
1936
  return self.message(name, object_)
@@ -1927,7 +1956,7 @@ class _callable(compiled_schema):
1927
1956
  object_: object,
1928
1957
  name: str = "object",
1929
1958
  strict: bool = True,
1930
- subs: dict[str, object] = {},
1959
+ subs: Mapping[str, object] = {},
1931
1960
  ) -> str:
1932
1961
  try:
1933
1962
  if self.schema(object_):
@@ -1946,12 +1975,18 @@ class _dict(compiled_schema):
1946
1975
  const_keys: set[object]
1947
1976
  other_keys: set[compiled_schema]
1948
1977
  schema: dict[object, compiled_schema]
1978
+ type_schema: Type[Mapping[object, object]]
1949
1979
 
1950
1980
  def __init__(
1951
1981
  self,
1952
- schema: dict[object, object],
1982
+ schema: Mapping[object, object],
1983
+ type_schema: type | None = None,
1953
1984
  _deferred_compiles: _mapping | None = None,
1954
1985
  ) -> None:
1986
+ if type_schema is None:
1987
+ self.type_schema = type(schema)
1988
+ else:
1989
+ self.type_schema = type_schema
1955
1990
  self.min_keys = set()
1956
1991
  self.const_keys = set()
1957
1992
  self.other_keys = set()
@@ -1981,10 +2016,10 @@ class _dict(compiled_schema):
1981
2016
  object_: object,
1982
2017
  name: str = "object",
1983
2018
  strict: bool = True,
1984
- subs: dict[str, object] = {},
2019
+ subs: Mapping[str, object] = {},
1985
2020
  ) -> str:
1986
- if not isinstance(object_, dict):
1987
- return _wrong_type_message(object_, name, "dict")
2021
+ if not isinstance(object_, self.type_schema):
2022
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
1988
2023
 
1989
2024
  for k in self.min_keys:
1990
2025
  if k not in object_:
@@ -2047,7 +2082,7 @@ class _set(compiled_schema):
2047
2082
  object_: object,
2048
2083
  name: str = "object",
2049
2084
  strict: bool = True,
2050
- subs: dict[str, object] = {},
2085
+ subs: Mapping[str, object] = {},
2051
2086
  ) -> str:
2052
2087
  return self.schema.__validate__(object_, name=name, strict=True, subs=subs)
2053
2088
 
@@ -2056,7 +2091,7 @@ class _set(compiled_schema):
2056
2091
  object_: object,
2057
2092
  name: str = "object",
2058
2093
  strict: bool = True,
2059
- subs: dict[str, object] = {},
2094
+ subs: Mapping[str, object] = {},
2060
2095
  ) -> str:
2061
2096
  if not isinstance(object_, set):
2062
2097
  return _wrong_type_message(object_, name, "set")
@@ -2072,7 +2107,7 @@ class _set(compiled_schema):
2072
2107
  object_: object,
2073
2108
  name: str = "object",
2074
2109
  strict: bool = True,
2075
- subs: dict[str, object] = {},
2110
+ subs: Mapping[str, object] = {},
2076
2111
  ) -> str:
2077
2112
  if not isinstance(object_, set):
2078
2113
  return _wrong_type_message(object_, name, "set")
@@ -2087,7 +2122,7 @@ class _set(compiled_schema):
2087
2122
  return str(self.schema_)
2088
2123
 
2089
2124
 
2090
- class structural:
2125
+ class protocol:
2091
2126
  type_dict: dict[object, object]
2092
2127
  dict: bool
2093
2128
  __name__: str
@@ -2101,16 +2136,17 @@ class structural:
2101
2136
  if hasattr(schema, "__total__") and isinstance(schema.__total__, bool):
2102
2137
  total = schema.__total__
2103
2138
  self.type_dict = _to_dict(type_hints, total=total)
2104
- assert hasattr(schema, "__name__") and isinstance(schema.__name__, str)
2105
- self.__name__ = schema.__name__
2139
+ if hasattr(schema, "__name__") and isinstance(schema.__name__, str):
2140
+ self.__name__ = schema.__name__
2141
+ else:
2142
+ self.__name__ = "schema"
2106
2143
 
2107
2144
  def __compile__(
2108
2145
  self, _deferred_compiles: _mapping | None = None
2109
2146
  ) -> compiled_schema:
2110
2147
  if not self.dict:
2111
- type_dict_ = cast(dict[str, object], self.type_dict)
2112
2148
  return _set_name(
2113
- fields(type_dict_),
2149
+ fields(self.type_dict),
2114
2150
  self.__name__,
2115
2151
  reason=True,
2116
2152
  _deferred_compiles=_deferred_compiles,
@@ -2148,13 +2184,18 @@ class _Union(compiled_schema):
2148
2184
 
2149
2185
  class _List(compiled_schema):
2150
2186
  def __init__(
2151
- self, schema: object, _deferred_compiles: _mapping | None = None
2187
+ self,
2188
+ schema: object,
2189
+ type_schema: type | None = None,
2190
+ _deferred_compiles: _mapping | None = None,
2152
2191
  ) -> None:
2153
2192
  setattr(
2154
2193
  self,
2155
2194
  "__validate__",
2156
2195
  _sequence(
2157
- [schema, ...], _deferred_compiles=_deferred_compiles
2196
+ [schema, ...],
2197
+ type_schema=type_schema,
2198
+ _deferred_compiles=_deferred_compiles,
2158
2199
  ).__validate__,
2159
2200
  )
2160
2201
 
@@ -2172,13 +2213,18 @@ class _Tuple(compiled_schema):
2172
2213
 
2173
2214
  class _Dict(compiled_schema):
2174
2215
  def __init__(
2175
- self, schema: tuple[object, ...], _deferred_compiles: _mapping | None = None
2216
+ self,
2217
+ schema: tuple[object, ...],
2218
+ type_schema: type | None = None,
2219
+ _deferred_compiles: _mapping | None = None,
2176
2220
  ) -> None:
2177
2221
  k, v = schema
2178
2222
  setattr(
2179
2223
  self,
2180
2224
  "__validate__",
2181
- _dict({k: v}, _deferred_compiles=_deferred_compiles).__validate__,
2225
+ _dict(
2226
+ {k: v}, type_schema=type_schema, _deferred_compiles=_deferred_compiles
2227
+ ).__validate__,
2182
2228
  )
2183
2229
 
2184
2230
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vtjson
3
- Version: 2.1.3
3
+ Version: 2.1.5
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
@@ -70,8 +70,8 @@ A wrapper takes one or more schemas as arguments and produces a new schema.
70
70
  - An object matches the schema `complement(schema)` if it does not match `schema`.
71
71
  - An object matches the schema `lax(schema)` if it matches `schema` when validated with `strict=False`.
72
72
  - An object matches the schema `strict(schema)` if it matches `schema` when validated with `strict=True`.
73
- - An object matches the schema `set_name(schema, name)` if it matches `schema`. But the `name` argument will be used in non-validation messages.
74
- - An object matches the schema `quote(schema)` if it is equal to `schema`. For example the schema `str` matches strings but the schema `quote(str)` matches the object `str`.
73
+ - An object matches the schema `set_name(schema, name, reason=False)` if it matches `schema`, but the `name` argument will be used in non-validation messages. Unless `reason` is `True` the original non-validation message will be suppressed.
74
+ - An object matches the schema `protocol(schema, dict=False)` if `schema` is a class and its fields are annotated with schemas which validate the corresponding fields in the object. If `dict` is `True` then the object is validated as a `dict`.
75
75
  - An object matches the schema `set_label(schema, label1, ..., labelN, debug=False)` if it matches `schema`, unless the schema is replaced by a different one via the `subs` argument to `validate`. If the optional argument `debug` is `True` then a message will be printed on the console if the schema was changed.
76
76
 
77
77
  ## Built-ins
@@ -163,8 +163,8 @@ A schema can be, in order of precedence:
163
163
  - A Python type hint such as `list[str]`. This is discussed further below.
164
164
  - A Python type. In that case validation is done by checking membership. By convention the schema `float` matches both ints and floats. Similarly the schema `complex` matches ints and floats besides of course complex numbers.
165
165
  - A callable. Validation is done by applying the callable to the object. If applying the callable throws an exception then the corresponding message will be part of the non-validation message.
166
- - A `list` or a `tuple`. Validation is done by first checking membership of the corresponding types, and then performing validation for each of the entries of the object being validated against the corresponding entries of the schema.
167
- - A dictionary. Validation is done by first checking membership of the `dict` type, and then performing validation for each of the values of the object being validated against the corresponding values of the schema. Keys are themselves considered as schemas. E.g. `{str: str}` represents a dictionary whose keys and values are both strings. A more elaborate discussion of validation of dictionaries is given below.
166
+ - An instance of `Sequence` that is not an instance of `str` (e.g a `list` or a `tuple`). Validation is done by first checking membership of the schema type, and then performing validation for each of the entries of the object being validated against the corresponding entries of the schema.
167
+ - An instance of `Mapping`. Validation is done by first checking membership of the schema type, and then performing validation for each of the values of the object being validated against the corresponding values of the schema. Keys are themselves considered as schemas. E.g. `{str: str}` represents a dictionary whose keys and values are both strings. A more elaborate discussion of validation of dictionaries is given below.
168
168
  - A `set`. A set validates an object if the object is a set and the elements of the object are validated by an element of the schema.
169
169
  - An arbitrary Python object. Validation is done by checking equality of the schema and the object, except when the schema is `float`, in which case `math.isclose` is used. Below we call such an object a `const schema`.
170
170
 
@@ -190,7 +190,7 @@ A consequence of this algorithm is that non-const keys are automatically optiona
190
190
 
191
191
  ```python
192
192
  Annotated, dict[...], Dict[...], list[...], List[...], tuple[...], Tuple[...],
193
- Protocol, Literal, NewType, TypedDict, Union (or the equivalent operator |).
193
+ Protocol, NamedTuple, Literal, NewType, TypedDict, Union (or the equivalent operator |).
194
194
  ```
195
195
 
196
196
  For example `dict[str, str]` is translated internally into the schema `{str: str}`. See below for more information.
@@ -256,6 +256,16 @@ Note that Python imposes strong restrictions on what constitutes a valid type hi
256
256
 
257
257
  internally becomes `fields({"title": str, "price": float})`.
258
258
 
259
+ - `NamedTuple`. A `NamedTuple` class is translated as the intersection of a `tuple` schema and a fields schema. E.g.
260
+
261
+ ```python
262
+ class Movie(NamedTuple):
263
+ title: str
264
+ price: float
265
+ ```
266
+
267
+ internally becomes `intersect(tuple, fields({"title": str, "price": float}))`.
268
+
259
269
  - `Annotated` has already been discussed. It is translated into a suitable `intersect` schema. The handling of `Annotated` schemas can be influenced by `Apply` objects (see below).
260
270
 
261
271
  - `NewType` is translated into a `set_name` schema. E.g. `NewType('Movie', str)` becomes `set_name(str, 'Movie')`
@@ -0,0 +1,9 @@
1
+ vtjson/__init__.py,sha256=oLX4JH6_R7dYtTiGfBG3pQGR21IArspifdmZilbuGOw,68
2
+ vtjson/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ vtjson/vtjson.py,sha256=xq5w9FrmPf6ctjLml2To0nEfd5Okm3ijND44GLWarr0,67357
4
+ vtjson-2.1.5.dist-info/AUTHORS,sha256=qmxaXxaIO-YPNHJAZ0dcCrnPCs1x9ocbtMksiy4i80M,21
5
+ vtjson-2.1.5.dist-info/LICENSE,sha256=n7xW-zX8xBLHzCdqWIMRuMzBD_ACLcNCwio0LEkKt1o,1077
6
+ vtjson-2.1.5.dist-info/METADATA,sha256=eALkzFzpWaZECpnduYIsaT1KSCREWw-fTXjLqCbyrjg,24035
7
+ vtjson-2.1.5.dist-info/WHEEL,sha256=a7TGlA-5DaHMRrarXjVbQagU3Man_dCnGIWMJr5kRWo,91
8
+ vtjson-2.1.5.dist-info/top_level.txt,sha256=9DlSF3l63igcvnYPcj117F2hzOW4Nx0N-JBoW3jjBZM,7
9
+ vtjson-2.1.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.0)
2
+ Generator: setuptools (75.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +0,0 @@
1
- vtjson/__init__.py,sha256=oLX4JH6_R7dYtTiGfBG3pQGR21IArspifdmZilbuGOw,68
2
- vtjson/vtjson.py,sha256=bCndIajECAc65M4aeNcjDJRZ8cLv4Jz4H7ryt7XHwio,65935
3
- vtjson-2.1.3.dist-info/AUTHORS,sha256=qmxaXxaIO-YPNHJAZ0dcCrnPCs1x9ocbtMksiy4i80M,21
4
- vtjson-2.1.3.dist-info/LICENSE,sha256=n7xW-zX8xBLHzCdqWIMRuMzBD_ACLcNCwio0LEkKt1o,1077
5
- vtjson-2.1.3.dist-info/METADATA,sha256=hoLkMLs5Kh6GVA-Xw8QX1TlCJDB7gSrzRSYrjzj9OqM,23506
6
- vtjson-2.1.3.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
7
- vtjson-2.1.3.dist-info/top_level.txt,sha256=9DlSF3l63igcvnYPcj117F2hzOW4Nx0N-JBoW3jjBZM,7
8
- vtjson-2.1.3.dist-info/RECORD,,