vtjson 2.1.4__tar.gz → 2.1.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vtjson
3
- Version: 2.1.4
3
+ Version: 2.1.6
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
@@ -163,16 +163,16 @@ 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
 
171
- ## Validating dictionaries
171
+ ## Validating Mapping
172
172
 
173
- For a dictionary schema containing only `const keys` (i.e. keys corresponding to a `const schema`) the interpretation is obvious (see the introductory example above). Below we discuss the validation of an object against a dictionary schema in the general case.
173
+ For a Mapping schema containing only `const keys` (i.e. keys corresponding to a `const schema`) the interpretation is obvious (see the introductory example above). Below we discuss the validation of an object against a Mapping schema in the general case.
174
174
 
175
- - First we verify that the object is also a dictionary. If not then validation fails.
175
+ - First we verify that the type of the object is a subtype of the type of the schema. If not then validation fails.
176
176
  - We verify that all non-optional const keys of the schema are also keys of the object. If this is not the case then validation fails.
177
177
  - Now we make a list of all the keys of the schema (both optional and non-optional). The result will be called the `key list` below.
178
178
  - The object will pass validation if all its keys pass validation. We next discuss how to validate a particular key of the object.
@@ -143,16 +143,16 @@ A schema can be, in order of precedence:
143
143
  - A Python type hint such as `list[str]`. This is discussed further below.
144
144
  - 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.
145
145
  - 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.
146
- - 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.
147
- - 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.
146
+ - 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.
147
+ - 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.
148
148
  - 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.
149
149
  - 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`.
150
150
 
151
- ## Validating dictionaries
151
+ ## Validating Mapping
152
152
 
153
- For a dictionary schema containing only `const keys` (i.e. keys corresponding to a `const schema`) the interpretation is obvious (see the introductory example above). Below we discuss the validation of an object against a dictionary schema in the general case.
153
+ For a Mapping schema containing only `const keys` (i.e. keys corresponding to a `const schema`) the interpretation is obvious (see the introductory example above). Below we discuss the validation of an object against a Mapping schema in the general case.
154
154
 
155
- - First we verify that the object is also a dictionary. If not then validation fails.
155
+ - First we verify that the type of the object is a subtype of the type of the schema. If not then validation fails.
156
156
  - We verify that all non-optional const keys of the schema are also keys of the object. If this is not the case then validation fails.
157
157
  - Now we make a list of all the keys of the schema (both optional and non-optional). The result will be called the `key list` below.
158
158
  - The object will pass validation if all its keys pass validation. We next discuss how to validate a particular key of the object.
File without changes
@@ -10,9 +10,9 @@ import types
10
10
  import typing
11
11
  import urllib.parse
12
12
  import warnings
13
- from collections.abc import Sequence, Sized
13
+ from collections.abc import Sequence, Set, Sized
14
14
  from dataclasses import dataclass
15
- from typing import Any, Callable, Type, TypeVar, Union, cast
15
+ from typing import Any, Callable, Container, 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.4"
124
+ __version__ = "2.1.6"
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:
@@ -147,6 +153,16 @@ skip_first = Apply(skip_first=True)
147
153
  _dns_resolver: dns.resolver.Resolver | None = None
148
154
 
149
155
 
156
+ def _generic_name(origin: type, args: tuple[object, ...]) -> str:
157
+ def to_name(c: object) -> str:
158
+ if hasattr(c, "__name__"):
159
+ return str(c.__name__)
160
+ else:
161
+ return str(c)
162
+
163
+ return to_name(origin) + "[" + ",".join([to_name(arg) for arg in args]) + "]"
164
+
165
+
150
166
  def _get_type_hints(schema: object) -> dict[str, object]:
151
167
  if not supports_structural:
152
168
  raise SchemaError(
@@ -159,7 +175,9 @@ def _get_type_hints(schema: object) -> dict[str, object]:
159
175
  return type_hints
160
176
 
161
177
 
162
- def _to_dict(type_hints: dict[str, object], total: bool = True) -> dict[object, object]:
178
+ def _to_dict(
179
+ type_hints: Mapping[str, object], total: bool = True
180
+ ) -> dict[object, object]:
163
181
  d: dict[object, object] = {}
164
182
  if not supports_Generics:
165
183
  raise SchemaError("Generic types are not supported")
@@ -225,7 +243,7 @@ def _wrong_type_message(
225
243
  class _validate_meta(type):
226
244
  __schema__: object
227
245
  __strict__: bool
228
- __subs__: dict[str, object]
246
+ __subs__: Mapping[str, object]
229
247
  __dbg__: bool
230
248
 
231
249
  def __instancecheck__(cls, object_: object) -> bool:
@@ -242,7 +260,7 @@ def make_type(
242
260
  name: str | None = None,
243
261
  strict: bool = True,
244
262
  debug: bool = False,
245
- subs: dict[str, object] = {},
263
+ subs: Mapping[str, object] = {},
246
264
  ) -> _validate_meta:
247
265
  if name is None:
248
266
  if hasattr(schema, "__name__"):
@@ -293,7 +311,7 @@ class _union(compiled_schema):
293
311
  object_: object,
294
312
  name: str = "object",
295
313
  strict: bool = True,
296
- subs: dict[str, object] = {},
314
+ subs: Mapping[str, object] = {},
297
315
  ) -> str:
298
316
  messages = []
299
317
  for schema in self.schemas:
@@ -332,7 +350,7 @@ class _intersect(compiled_schema):
332
350
  object_: object,
333
351
  name: str = "object",
334
352
  strict: bool = True,
335
- subs: dict[str, object] = {},
353
+ subs: Mapping[str, object] = {},
336
354
  ) -> str:
337
355
  for schema in self.schemas:
338
356
  message = schema.__validate__(object_, name=name, strict=strict, subs=subs)
@@ -364,7 +382,7 @@ class _complement(compiled_schema):
364
382
  object_: object,
365
383
  name: str = "object",
366
384
  strict: bool = True,
367
- subs: dict[str, object] = {},
385
+ subs: Mapping[str, object] = {},
368
386
  ) -> str:
369
387
  message = self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
370
388
  if message != "":
@@ -396,7 +414,7 @@ class _lax(compiled_schema):
396
414
  object_: object,
397
415
  name: str = "object",
398
416
  strict: bool = True,
399
- subs: dict[str, object] = {},
417
+ subs: Mapping[str, object] = {},
400
418
  ) -> str:
401
419
  return self.schema.__validate__(object_, name=name, strict=False, subs=subs)
402
420
 
@@ -424,7 +442,7 @@ class _strict(compiled_schema):
424
442
  object_: object,
425
443
  name: str = "object",
426
444
  strict: bool = True,
427
- subs: dict[str, object] = {},
445
+ subs: Mapping[str, object] = {},
428
446
  ) -> str:
429
447
  return self.schema.__validate__(object_, name=name, strict=True, subs=subs)
430
448
 
@@ -458,7 +476,7 @@ class _set_label(compiled_schema):
458
476
  object_: object,
459
477
  name: str = "object",
460
478
  strict: bool = True,
461
- subs: dict[str, object] = {},
479
+ subs: Mapping[str, object] = {},
462
480
  ) -> str:
463
481
  common_labels = tuple(set(subs.keys()).intersection(self.labels))
464
482
  if len(common_labels) >= 2:
@@ -511,7 +529,7 @@ class quote(compiled_schema):
511
529
  object_: object,
512
530
  name: str = "object",
513
531
  strict: bool = True,
514
- subs: dict[str, object] = {},
532
+ subs: Mapping[str, object] = {},
515
533
  ) -> str:
516
534
  return self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
517
535
 
@@ -537,7 +555,7 @@ class _set_name(compiled_schema):
537
555
  object_: object,
538
556
  name: str = "object",
539
557
  strict: bool = True,
540
- subs: dict[str, object] = {},
558
+ subs: Mapping[str, object] = {},
541
559
  ) -> str:
542
560
  message = self.schema.__validate__(object_, name=name, strict=strict, subs=subs)
543
561
  if message != "":
@@ -608,7 +626,7 @@ class regex(compiled_schema):
608
626
  object_: object,
609
627
  name: str = "object",
610
628
  strict: bool = True,
611
- subs: dict[str, object] = {},
629
+ subs: Mapping[str, object] = {},
612
630
  ) -> str:
613
631
  if not isinstance(object_, str):
614
632
  return _wrong_type_message(object_, name, self.__name__)
@@ -647,7 +665,7 @@ class glob(compiled_schema):
647
665
  object_: object,
648
666
  name: str = "object",
649
667
  strict: bool = True,
650
- subs: dict[str, object] = {},
668
+ subs: Mapping[str, object] = {},
651
669
  ) -> str:
652
670
  if not isinstance(object_, str):
653
671
  return _wrong_type_message(object_, name, self.__name__)
@@ -683,7 +701,7 @@ class magic(compiled_schema):
683
701
  object_: object,
684
702
  name: str = "object",
685
703
  strict: bool = True,
686
- subs: dict[str, object] = {},
704
+ subs: Mapping[str, object] = {},
687
705
  ) -> str:
688
706
  if not isinstance(object_, (str, bytes)):
689
707
  return _wrong_type_message(object_, name, self.__name__)
@@ -732,7 +750,7 @@ class div(compiled_schema):
732
750
  object_: object,
733
751
  name: str = "object",
734
752
  strict: bool = True,
735
- subs: dict[str, object] = {},
753
+ subs: Mapping[str, object] = {},
736
754
  ) -> str:
737
755
  if not isinstance(object_, int):
738
756
  return _wrong_type_message(object_, name, "int")
@@ -779,7 +797,7 @@ class close_to(compiled_schema):
779
797
  object_: object,
780
798
  name: str = "object",
781
799
  strict: bool = True,
782
- subs: dict[str, object] = {},
800
+ subs: Mapping[str, object] = {},
783
801
  ) -> str:
784
802
  if not isinstance(object_, (float, int)):
785
803
  return _wrong_type_message(object_, name, "number")
@@ -809,7 +827,7 @@ class gt(compiled_schema):
809
827
  object_: object,
810
828
  name: str = "object",
811
829
  strict: bool = True,
812
- subs: dict[str, object] = {},
830
+ subs: Mapping[str, object] = {},
813
831
  ) -> str:
814
832
  try:
815
833
  if self.lb < object_:
@@ -840,7 +858,7 @@ class ge(compiled_schema):
840
858
  object_: object,
841
859
  name: str = "object",
842
860
  strict: bool = True,
843
- subs: dict[str, object] = {},
861
+ subs: Mapping[str, object] = {},
844
862
  ) -> str:
845
863
  try:
846
864
  if self.lb <= object_:
@@ -871,7 +889,7 @@ class lt(compiled_schema):
871
889
  object_: object,
872
890
  name: str = "object",
873
891
  strict: bool = True,
874
- subs: dict[str, object] = {},
892
+ subs: Mapping[str, object] = {},
875
893
  ) -> str:
876
894
  try:
877
895
  if self.ub > object_:
@@ -902,7 +920,7 @@ class le(compiled_schema):
902
920
  object_: object,
903
921
  name: str = "object",
904
922
  strict: bool = True,
905
- subs: dict[str, object] = {},
923
+ subs: Mapping[str, object] = {},
906
924
  ) -> str:
907
925
  try:
908
926
  if self.ub >= object_:
@@ -1006,7 +1024,7 @@ class size(compiled_schema):
1006
1024
  object_: object,
1007
1025
  name: str = "object",
1008
1026
  strict: bool = True,
1009
- subs: dict[str, object] = {},
1027
+ subs: Mapping[str, object] = {},
1010
1028
  ) -> str:
1011
1029
  if not isinstance(object_, Sized):
1012
1030
  return f"{name} (value:{_c(object_)}) has no len()"
@@ -1029,7 +1047,7 @@ class _deferred(compiled_schema):
1029
1047
  object_: object,
1030
1048
  name: str = "object",
1031
1049
  strict: bool = True,
1032
- subs: dict[str, object] = {},
1050
+ subs: Mapping[str, object] = {},
1033
1051
  ) -> str:
1034
1052
  if self.key not in self.collection:
1035
1053
  raise ValidationError(f"{name}: key {self.key} is unknown")
@@ -1131,12 +1149,20 @@ def _compile(
1131
1149
  schema,
1132
1150
  _deferred_compiles=_deferred_compiles,
1133
1151
  )
1134
- elif origin == list:
1135
- ret = _List(typing.get_args(schema)[0], _deferred_compiles=_deferred_compiles)
1136
1152
  elif origin == tuple:
1137
1153
  ret = _Tuple(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
1138
- elif origin == dict:
1139
- ret = _Dict(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
1154
+ elif isinstance(origin, type) and issubclass(origin, Mapping):
1155
+ ret = _Mapping(
1156
+ typing.get_args(schema),
1157
+ type_schema=origin,
1158
+ _deferred_compiles=_deferred_compiles,
1159
+ )
1160
+ elif isinstance(origin, type) and issubclass(origin, Container):
1161
+ ret = _Container(
1162
+ typing.get_args(schema),
1163
+ type_schema=origin,
1164
+ _deferred_compiles=_deferred_compiles,
1165
+ )
1140
1166
  elif origin == Union:
1141
1167
  ret = _Union(typing.get_args(schema), _deferred_compiles=_deferred_compiles)
1142
1168
  elif supports_Literal and origin == Literal:
@@ -1149,11 +1175,11 @@ def _compile(
1149
1175
  ret = _type(schema)
1150
1176
  elif callable(schema):
1151
1177
  ret = _callable(schema)
1152
- elif isinstance(schema, tuple) or isinstance(schema, list):
1178
+ elif isinstance(schema, Sequence) and not isinstance(schema, str):
1153
1179
  ret = _sequence(schema, _deferred_compiles=_deferred_compiles)
1154
- elif isinstance(schema, dict):
1180
+ elif isinstance(schema, Mapping):
1155
1181
  ret = _dict(schema, _deferred_compiles=_deferred_compiles)
1156
- elif isinstance(schema, set):
1182
+ elif isinstance(schema, Set):
1157
1183
  ret = _set(schema, _deferred_compiles=_deferred_compiles)
1158
1184
  else:
1159
1185
  ret = _const(schema)
@@ -1171,7 +1197,7 @@ def _validate(
1171
1197
  object_: object,
1172
1198
  name: str = "object",
1173
1199
  strict: bool = True,
1174
- subs: dict[str, object] = {},
1200
+ subs: Mapping[str, object] = {},
1175
1201
  ) -> str:
1176
1202
  return compile(schema).__validate__(object_, name=name, strict=strict, subs=subs)
1177
1203
 
@@ -1181,7 +1207,7 @@ def validate(
1181
1207
  object_: object,
1182
1208
  name: str = "object",
1183
1209
  strict: bool = True,
1184
- subs: dict[str, object] = {},
1210
+ subs: Mapping[str, object] = {},
1185
1211
  ) -> None:
1186
1212
  message = _validate(
1187
1213
  schema,
@@ -1210,7 +1236,7 @@ class number(compiled_schema):
1210
1236
  object_: object,
1211
1237
  name: str = "object",
1212
1238
  strict: bool = True,
1213
- subs: dict[str, object] = {},
1239
+ subs: Mapping[str, object] = {},
1214
1240
  ) -> str:
1215
1241
  if isinstance(object_, (int, float)):
1216
1242
  return ""
@@ -1233,7 +1259,7 @@ class email(compiled_schema):
1233
1259
  object_: object,
1234
1260
  name: str = "object",
1235
1261
  strict: bool = True,
1236
- subs: dict[str, object] = {},
1262
+ subs: Mapping[str, object] = {},
1237
1263
  ) -> str:
1238
1264
  if not isinstance(object_, str):
1239
1265
  return _wrong_type_message(
@@ -1270,7 +1296,7 @@ class ip_address(compiled_schema):
1270
1296
  object_: object,
1271
1297
  name: str = "object",
1272
1298
  strict: bool = True,
1273
- subs: dict[str, object] = {},
1299
+ subs: Mapping[str, object] = {},
1274
1300
  ) -> str:
1275
1301
  if not isinstance(object_, (int, str, bytes)):
1276
1302
  return _wrong_type_message(object_, name, self.__name__)
@@ -1287,7 +1313,7 @@ class url(compiled_schema):
1287
1313
  object_: object,
1288
1314
  name: str = "object",
1289
1315
  strict: bool = True,
1290
- subs: dict[str, object] = {},
1316
+ subs: Mapping[str, object] = {},
1291
1317
  ) -> str:
1292
1318
  if not isinstance(object_, str):
1293
1319
  return _wrong_type_message(object_, name, "url")
@@ -1313,7 +1339,7 @@ class date_time(compiled_schema):
1313
1339
  object_: object,
1314
1340
  name: str = "object",
1315
1341
  strict: bool = True,
1316
- subs: dict[str, object] = {},
1342
+ subs: Mapping[str, object] = {},
1317
1343
  ) -> str:
1318
1344
  if not isinstance(object_, str):
1319
1345
  return _wrong_type_message(object_, name, self.__name__)
@@ -1336,7 +1362,7 @@ class date(compiled_schema):
1336
1362
  object_: object,
1337
1363
  name: str = "object",
1338
1364
  strict: bool = True,
1339
- subs: dict[str, object] = {},
1365
+ subs: Mapping[str, object] = {},
1340
1366
  ) -> str:
1341
1367
  if not isinstance(object_, str):
1342
1368
  return _wrong_type_message(object_, name, "date")
@@ -1353,7 +1379,7 @@ class time(compiled_schema):
1353
1379
  object_: object,
1354
1380
  name: str = "object",
1355
1381
  strict: bool = True,
1356
- subs: dict[str, object] = {},
1382
+ subs: Mapping[str, object] = {},
1357
1383
  ) -> str:
1358
1384
  if not isinstance(object_, str):
1359
1385
  return _wrong_type_message(object_, name, "date")
@@ -1370,7 +1396,7 @@ class nothing(compiled_schema):
1370
1396
  object_: object,
1371
1397
  name: str = "object",
1372
1398
  strict: bool = True,
1373
- subs: dict[str, object] = {},
1399
+ subs: Mapping[str, object] = {},
1374
1400
  ) -> str:
1375
1401
  return _wrong_type_message(object_, name, "nothing")
1376
1402
 
@@ -1381,7 +1407,7 @@ class anything(compiled_schema):
1381
1407
  object_: object,
1382
1408
  name: str = "object",
1383
1409
  strict: bool = True,
1384
- subs: dict[str, object] = {},
1410
+ subs: Mapping[str, object] = {},
1385
1411
  ) -> str:
1386
1412
  return ""
1387
1413
 
@@ -1412,7 +1438,7 @@ class domain_name(compiled_schema):
1412
1438
  object_: object,
1413
1439
  name: str = "object",
1414
1440
  strict: bool = True,
1415
- subs: dict[str, object] = {},
1441
+ subs: Mapping[str, object] = {},
1416
1442
  ) -> str:
1417
1443
  if not isinstance(object_, str):
1418
1444
  return _wrong_type_message(object_, name, self.__name__)
@@ -1448,9 +1474,9 @@ class at_least_one_of(compiled_schema):
1448
1474
  object_: object,
1449
1475
  name: str = "object",
1450
1476
  strict: bool = True,
1451
- subs: dict[str, object] = {},
1477
+ subs: Mapping[str, object] = {},
1452
1478
  ) -> str:
1453
- if not isinstance(object_, dict):
1479
+ if not isinstance(object_, Mapping):
1454
1480
  return _wrong_type_message(object_, name, self.__name__)
1455
1481
  try:
1456
1482
  if any([a in object_ for a in self.args]):
@@ -1475,9 +1501,9 @@ class at_most_one_of(compiled_schema):
1475
1501
  object_: object,
1476
1502
  name: str = "object",
1477
1503
  strict: bool = True,
1478
- subs: dict[str, object] = {},
1504
+ subs: Mapping[str, object] = {},
1479
1505
  ) -> str:
1480
- if not isinstance(object_, dict):
1506
+ if not isinstance(object_, Mapping):
1481
1507
  return _wrong_type_message(object_, name, self.__name__)
1482
1508
  try:
1483
1509
  if sum([a in object_ for a in self.args]) <= 1:
@@ -1502,9 +1528,9 @@ class one_of(compiled_schema):
1502
1528
  object_: object,
1503
1529
  name: str = "object",
1504
1530
  strict: bool = True,
1505
- subs: dict[str, object] = {},
1531
+ subs: Mapping[str, object] = {},
1506
1532
  ) -> str:
1507
- if not isinstance(object_, dict):
1533
+ if not isinstance(object_, Mapping):
1508
1534
  return _wrong_type_message(object_, name, self.__name__)
1509
1535
  try:
1510
1536
  if sum([a in object_ for a in self.args]) == 1:
@@ -1526,10 +1552,10 @@ class keys(compiled_schema):
1526
1552
  object_: object,
1527
1553
  name: str = "object",
1528
1554
  strict: bool = True,
1529
- subs: dict[str, object] = {},
1555
+ subs: Mapping[str, object] = {},
1530
1556
  ) -> str:
1531
- if not isinstance(object_, dict):
1532
- return _wrong_type_message(object_, name, "dict") # TODO: __name__
1557
+ if not isinstance(object_, Mapping):
1558
+ return _wrong_type_message(object_, name, "Mapping") # TODO: __name__
1533
1559
  for k in self.args:
1534
1560
  if k not in object_:
1535
1561
  return f"{name}[{repr(k)}] is missing"
@@ -1562,7 +1588,7 @@ class _ifthen(compiled_schema):
1562
1588
  object_: object,
1563
1589
  name: str = "object",
1564
1590
  strict: bool = True,
1565
- subs: dict[str, object] = {},
1591
+ subs: Mapping[str, object] = {},
1566
1592
  ) -> str:
1567
1593
  if (
1568
1594
  self.if_schema.__validate__(object_, name=name, strict=strict, subs=subs)
@@ -1624,7 +1650,7 @@ class _cond(compiled_schema):
1624
1650
  object_: object,
1625
1651
  name: str = "object",
1626
1652
  strict: bool = True,
1627
- subs: dict[str, object] = {},
1653
+ subs: Mapping[str, object] = {},
1628
1654
  ) -> str:
1629
1655
  for c in self.conditions:
1630
1656
  if c[0].__validate__(object_, name=name, strict=strict, subs=subs) == "":
@@ -1649,7 +1675,7 @@ class _fields(compiled_schema):
1649
1675
  d: dict[str, compiled_schema]
1650
1676
 
1651
1677
  def __init__(
1652
- self, d: dict[str, object], _deferred_compiles: _mapping | None = None
1678
+ self, d: Mapping[str, object], _deferred_compiles: _mapping | None = None
1653
1679
  ) -> None:
1654
1680
  self.d = {}
1655
1681
  for k, v in d.items():
@@ -1660,7 +1686,7 @@ class _fields(compiled_schema):
1660
1686
  object_: object,
1661
1687
  name: str = "object",
1662
1688
  strict: bool = True,
1663
- subs: dict[str, object] = {},
1689
+ subs: Mapping[str, object] = {},
1664
1690
  ) -> str:
1665
1691
  for k, v in self.d.items():
1666
1692
  name_ = f"{name}.{k}"
@@ -1676,8 +1702,8 @@ class _fields(compiled_schema):
1676
1702
 
1677
1703
  class fields:
1678
1704
  def __init__(self, d: object) -> None:
1679
- if not isinstance(d, dict):
1680
- raise SchemaError(f"{repr(d)} is not a dictionary")
1705
+ if not isinstance(d, Mapping):
1706
+ raise SchemaError(f"{repr(d)} is not a Mapping")
1681
1707
  for k in d:
1682
1708
  if not isinstance(k, str):
1683
1709
  raise SchemaError(f"key {repr(k)} in {repr(d)} is not a string")
@@ -1716,7 +1742,7 @@ class _filter(compiled_schema):
1716
1742
  object_: object,
1717
1743
  name: str = "object",
1718
1744
  strict: bool = True,
1719
- subs: dict[str, object] = {},
1745
+ subs: Mapping[str, object] = {},
1720
1746
  ) -> str:
1721
1747
  try:
1722
1748
  object_ = self.filter(object_)
@@ -1775,7 +1801,7 @@ class _type(compiled_schema):
1775
1801
  object_: object,
1776
1802
  name: str = "object",
1777
1803
  strict: bool = True,
1778
- subs: dict[str, object] = {},
1804
+ subs: Mapping[str, object] = {},
1779
1805
  ) -> str:
1780
1806
  try:
1781
1807
  if self.schema == float and isinstance(object_, int):
@@ -1792,7 +1818,7 @@ class _type(compiled_schema):
1792
1818
  object_: object,
1793
1819
  name: str = "object",
1794
1820
  strict: bool = True,
1795
- subs: dict[str, object] = {},
1821
+ subs: Mapping[str, object] = {},
1796
1822
  ) -> str:
1797
1823
  # consider int as a subtype of float
1798
1824
  if isinstance(object_, (int, float)):
@@ -1805,7 +1831,7 @@ class _type(compiled_schema):
1805
1831
  object_: object,
1806
1832
  name: str = "object",
1807
1833
  strict: bool = True,
1808
- subs: dict[str, object] = {},
1834
+ subs: Mapping[str, object] = {},
1809
1835
  ) -> str:
1810
1836
  # consider int, float as subtypes of complex
1811
1837
  if isinstance(object_, (int, float, complex)):
@@ -1824,7 +1850,8 @@ class _sequence(compiled_schema):
1824
1850
 
1825
1851
  def __init__(
1826
1852
  self,
1827
- schema: list[object] | tuple[object, ...],
1853
+ schema: Sequence[object],
1854
+ # type_schema: type | None = None,
1828
1855
  _deferred_compiles: _mapping | None = None,
1829
1856
  ) -> None:
1830
1857
  self.type_schema = type(schema)
@@ -1847,10 +1874,10 @@ class _sequence(compiled_schema):
1847
1874
  object_: object,
1848
1875
  name: str = "object",
1849
1876
  strict: bool = True,
1850
- subs: dict[str, object] = {},
1877
+ subs: Mapping[str, object] = {},
1851
1878
  ) -> str:
1852
1879
  if not isinstance(object_, self.type_schema):
1853
- return _wrong_type_message(object_, name, type(self.schema).__name__)
1880
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
1854
1881
  ls = len(self.schema)
1855
1882
  lo = len(object_)
1856
1883
  if strict:
@@ -1870,10 +1897,10 @@ class _sequence(compiled_schema):
1870
1897
  object_: object,
1871
1898
  name: str = "object",
1872
1899
  strict: bool = True,
1873
- subs: dict[str, object] = {},
1900
+ subs: Mapping[str, object] = {},
1874
1901
  ) -> str:
1875
1902
  if not isinstance(object_, self.type_schema):
1876
- return _wrong_type_message(object_, name, type(self.schema).__name__)
1903
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
1877
1904
  ls = len(self.schema)
1878
1905
  lo = len(object_)
1879
1906
  if ls > lo:
@@ -1910,7 +1937,7 @@ class _const(compiled_schema):
1910
1937
  object_: object,
1911
1938
  name: str = "object",
1912
1939
  strict: bool = True,
1913
- subs: dict[str, object] = {},
1940
+ subs: Mapping[str, object] = {},
1914
1941
  ) -> str:
1915
1942
  if object_ != self.schema:
1916
1943
  return self.message(name, object_)
@@ -1936,7 +1963,7 @@ class _callable(compiled_schema):
1936
1963
  object_: object,
1937
1964
  name: str = "object",
1938
1965
  strict: bool = True,
1939
- subs: dict[str, object] = {},
1966
+ subs: Mapping[str, object] = {},
1940
1967
  ) -> str:
1941
1968
  try:
1942
1969
  if self.schema(object_):
@@ -1955,12 +1982,14 @@ class _dict(compiled_schema):
1955
1982
  const_keys: set[object]
1956
1983
  other_keys: set[compiled_schema]
1957
1984
  schema: dict[object, compiled_schema]
1985
+ type_schema: Type[Mapping[object, object]]
1958
1986
 
1959
1987
  def __init__(
1960
1988
  self,
1961
- schema: dict[object, object],
1989
+ schema: Mapping[object, object],
1962
1990
  _deferred_compiles: _mapping | None = None,
1963
1991
  ) -> None:
1992
+ self.type_schema = type(schema)
1964
1993
  self.min_keys = set()
1965
1994
  self.const_keys = set()
1966
1995
  self.other_keys = set()
@@ -1990,10 +2019,10 @@ class _dict(compiled_schema):
1990
2019
  object_: object,
1991
2020
  name: str = "object",
1992
2021
  strict: bool = True,
1993
- subs: dict[str, object] = {},
2022
+ subs: Mapping[str, object] = {},
1994
2023
  ) -> str:
1995
- if not isinstance(object_, dict):
1996
- return _wrong_type_message(object_, name, "dict")
2024
+ if not isinstance(object_, self.type_schema):
2025
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
1997
2026
 
1998
2027
  for k in self.min_keys:
1999
2028
  if k not in object_:
@@ -2033,15 +2062,18 @@ class _dict(compiled_schema):
2033
2062
 
2034
2063
 
2035
2064
  class _set(compiled_schema):
2065
+ type_schema: Type[Set[object]]
2036
2066
  schema: compiled_schema
2037
- schema_: set[object]
2067
+ schema_: Set[object]
2038
2068
 
2039
2069
  def __init__(
2040
- self, schema: set[object], _deferred_compiles: _mapping | None = None
2070
+ self,
2071
+ schema: Set[object],
2072
+ _deferred_compiles: _mapping | None = None,
2041
2073
  ) -> None:
2074
+ self.type_schema = type(schema)
2042
2075
  self.schema_ = schema
2043
2076
  if len(schema) == 0:
2044
- self.schema = _const(set())
2045
2077
  setattr(self, "__validate__", self.__validate_empty_set__)
2046
2078
  elif len(schema) == 1:
2047
2079
  self.schema = _compile(
@@ -2056,19 +2088,23 @@ class _set(compiled_schema):
2056
2088
  object_: object,
2057
2089
  name: str = "object",
2058
2090
  strict: bool = True,
2059
- subs: dict[str, object] = {},
2091
+ subs: Mapping[str, object] = {},
2060
2092
  ) -> str:
2061
- return self.schema.__validate__(object_, name=name, strict=True, subs=subs)
2093
+ if not isinstance(object_, self.type_schema):
2094
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
2095
+ if len(object_) != 0:
2096
+ return f"{name} (value:{_c(object_)}) is not empty"
2097
+ return ""
2062
2098
 
2063
2099
  def __validate_singleton__(
2064
2100
  self,
2065
2101
  object_: object,
2066
2102
  name: str = "object",
2067
2103
  strict: bool = True,
2068
- subs: dict[str, object] = {},
2104
+ subs: Mapping[str, object] = {},
2069
2105
  ) -> str:
2070
2106
  if not isinstance(object_, set):
2071
- return _wrong_type_message(object_, name, "set")
2107
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
2072
2108
  for i, o in enumerate(object_):
2073
2109
  name_ = f"{name}{{{i}}}"
2074
2110
  v = self.schema.__validate__(o, name=name_, strict=True, subs=subs)
@@ -2081,10 +2117,10 @@ class _set(compiled_schema):
2081
2117
  object_: object,
2082
2118
  name: str = "object",
2083
2119
  strict: bool = True,
2084
- subs: dict[str, object] = {},
2120
+ subs: Mapping[str, object] = {},
2085
2121
  ) -> str:
2086
- if not isinstance(object_, set):
2087
- return _wrong_type_message(object_, name, "set")
2122
+ if not isinstance(object_, self.type_schema):
2123
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
2088
2124
  for i, o in enumerate(object_):
2089
2125
  name_ = f"{name}{{{i}}}"
2090
2126
  v = self.schema.__validate__(o, name=name_, strict=True, subs=subs)
@@ -2119,9 +2155,8 @@ class protocol:
2119
2155
  self, _deferred_compiles: _mapping | None = None
2120
2156
  ) -> compiled_schema:
2121
2157
  if not self.dict:
2122
- type_dict_ = cast(dict[str, object], self.type_dict)
2123
2158
  return _set_name(
2124
- fields(type_dict_),
2159
+ fields(self.type_dict),
2125
2160
  self.__name__,
2126
2161
  reason=True,
2127
2162
  _deferred_compiles=_deferred_compiles,
@@ -2157,19 +2192,6 @@ class _Union(compiled_schema):
2157
2192
  )
2158
2193
 
2159
2194
 
2160
- class _List(compiled_schema):
2161
- def __init__(
2162
- self, schema: object, _deferred_compiles: _mapping | None = None
2163
- ) -> None:
2164
- setattr(
2165
- self,
2166
- "__validate__",
2167
- _sequence(
2168
- [schema, ...], _deferred_compiles=_deferred_compiles
2169
- ).__validate__,
2170
- )
2171
-
2172
-
2173
2195
  class _Tuple(compiled_schema):
2174
2196
  def __init__(
2175
2197
  self, schema: tuple[object, ...], _deferred_compiles: _mapping | None = None
@@ -2181,16 +2203,89 @@ class _Tuple(compiled_schema):
2181
2203
  )
2182
2204
 
2183
2205
 
2184
- class _Dict(compiled_schema):
2206
+ class _Mapping(compiled_schema):
2207
+ type_schema: Type[Mapping[object, object]]
2208
+ key: compiled_schema
2209
+ value: compiled_schema
2210
+ __name__: str
2211
+
2185
2212
  def __init__(
2186
- self, schema: tuple[object, ...], _deferred_compiles: _mapping | None = None
2213
+ self,
2214
+ schema: tuple[object, ...],
2215
+ type_schema: type,
2216
+ _deferred_compiles: _mapping | None = None,
2187
2217
  ) -> None:
2218
+ if len(schema) != 2:
2219
+ raise SchemaError("Number of arguments of mapping is not two")
2188
2220
  k, v = schema
2189
- setattr(
2190
- self,
2191
- "__validate__",
2192
- _dict({k: v}, _deferred_compiles=_deferred_compiles).__validate__,
2193
- )
2221
+ self.key = _compile(k, _deferred_compiles=_deferred_compiles)
2222
+ self.value = _compile(v, _deferred_compiles=_deferred_compiles)
2223
+ self.type_schema = type_schema
2224
+ self.__name__ = _generic_name(type_schema, schema)
2225
+
2226
+ def __validate__(
2227
+ self,
2228
+ object_: object,
2229
+ name: str = "object",
2230
+ strict: bool = True,
2231
+ subs: Mapping[str, object] = {},
2232
+ ) -> str:
2233
+
2234
+ if not isinstance(object_, self.type_schema):
2235
+ return _wrong_type_message(object_, name, self.__name__)
2236
+
2237
+ for k, v in object_.items():
2238
+ _name = f"{name}[{repr(k)}]"
2239
+ message = self.key.__validate__(k, name=str(k), strict=strict, subs=subs)
2240
+ if message != "":
2241
+ return f"{_name} is not in the schema"
2242
+ message = self.value.__validate__(v, name=_name, strict=strict, subs=subs)
2243
+ if message != "":
2244
+ return message
2245
+
2246
+ return ""
2247
+
2248
+
2249
+ class _Container(compiled_schema):
2250
+ type_schema: Type[Mapping[object, object]]
2251
+ schema: compiled_schema
2252
+ __name__: str
2253
+
2254
+ def __init__(
2255
+ self,
2256
+ schema: tuple[object, ...],
2257
+ type_schema: type,
2258
+ _deferred_compiles: _mapping | None = None,
2259
+ ) -> None:
2260
+ if len(schema) != 1:
2261
+ raise SchemaError("Number of arguments of Generic type is not one")
2262
+ self.schema = _compile(schema[0], _deferred_compiles=_deferred_compiles)
2263
+ self.type_schema = type_schema
2264
+ self.__name__ = _generic_name(type_schema, schema)
2265
+
2266
+ def __validate__(
2267
+ self,
2268
+ object_: object,
2269
+ name: str = "object",
2270
+ strict: bool = True,
2271
+ subs: Mapping[str, object] = {},
2272
+ ) -> str:
2273
+
2274
+ if not isinstance(object_, self.type_schema):
2275
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
2276
+
2277
+ try:
2278
+ for i, o in enumerate(object_):
2279
+ _name = f"{name}[{i}]"
2280
+ message = self.schema.__validate__(
2281
+ o, name=_name, strict=strict, subs=subs
2282
+ )
2283
+ if message != "":
2284
+ return message
2285
+ except Exception as e:
2286
+ return _wrong_type_message(object_, name, self.__name__, explanation=str(e))
2287
+
2288
+ return ""
2194
2289
 
2195
2290
 
2196
2291
  class _NewType(compiled_schema):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vtjson
3
- Version: 2.1.4
3
+ Version: 2.1.6
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
@@ -163,16 +163,16 @@ 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
 
171
- ## Validating dictionaries
171
+ ## Validating Mapping
172
172
 
173
- For a dictionary schema containing only `const keys` (i.e. keys corresponding to a `const schema`) the interpretation is obvious (see the introductory example above). Below we discuss the validation of an object against a dictionary schema in the general case.
173
+ For a Mapping schema containing only `const keys` (i.e. keys corresponding to a `const schema`) the interpretation is obvious (see the introductory example above). Below we discuss the validation of an object against a Mapping schema in the general case.
174
174
 
175
- - First we verify that the object is also a dictionary. If not then validation fails.
175
+ - First we verify that the type of the object is a subtype of the type of the schema. If not then validation fails.
176
176
  - We verify that all non-optional const keys of the schema are also keys of the object. If this is not the case then validation fails.
177
177
  - Now we make a list of all the keys of the schema (both optional and non-optional). The result will be called the `key list` below.
178
178
  - The object will pass validation if all its keys pass validation. We next discuss how to validate a particular key of the object.
@@ -3,6 +3,7 @@ LICENSE
3
3
  README.md
4
4
  pyproject.toml
5
5
  src/vtjson/__init__.py
6
+ src/vtjson/py.typed
6
7
  src/vtjson/vtjson.py
7
8
  src/vtjson.egg-info/PKG-INFO
8
9
  src/vtjson.egg-info/SOURCES.txt
@@ -4,8 +4,21 @@ import json
4
4
  import re
5
5
  import sys
6
6
  import unittest
7
+ from collections.abc import Mapping, Sequence
7
8
  from datetime import datetime, timezone
8
- from typing import Any, Dict, List, NamedTuple, NewType, Tuple, Union
9
+ from typing import (
10
+ Any,
11
+ Container,
12
+ Dict,
13
+ Generator,
14
+ List,
15
+ NamedTuple,
16
+ NewType,
17
+ Tuple,
18
+ TypeVar,
19
+ Union,
20
+ overload,
21
+ )
9
22
  from urllib.parse import urlparse
10
23
 
11
24
  import vtjson
@@ -1066,6 +1079,200 @@ class TestValidation(unittest.TestCase):
1066
1079
  validate(schema, object_)
1067
1080
  show(mc)
1068
1081
 
1082
+ @unittest.skipUnless(
1083
+ vtjson.supports_Generic_ABC,
1084
+ "Generic base classes were introduced in Pythin 3.9",
1085
+ )
1086
+ def test_Container(self) -> None:
1087
+
1088
+ T = TypeVar("T")
1089
+
1090
+ class dumb(Container): # type: ignore
1091
+ def __contains__(a): # type: ignore
1092
+ return True
1093
+
1094
+ def __str__(b): # type: ignore
1095
+ return "dumb Container"
1096
+
1097
+ with self.assertRaises(ValidationError) as mc:
1098
+ validate(Container[str], dumb())
1099
+ show(mc)
1100
+
1101
+ if vtjson.supports_UnionType:
1102
+ with self.assertRaises(ValidationError) as mc:
1103
+ validate(Container[int | str], dumb())
1104
+ show(mc)
1105
+
1106
+ with self.assertRaises(ValidationError) as mc:
1107
+ validate(Container[Union[int, str]], dumb())
1108
+ show(mc)
1109
+
1110
+ class dummy(Sequence[T]):
1111
+ L: Sequence[T]
1112
+
1113
+ def __init__(self, L: Sequence[T] = ()) -> None:
1114
+ self.L = L
1115
+
1116
+ @overload
1117
+ def __getitem__(self, index: int) -> T: ...
1118
+
1119
+ @overload
1120
+ def __getitem__(self, index: slice) -> dummy[T]: ...
1121
+
1122
+ def __getitem__(self, index: int | slice) -> T | dummy[T]:
1123
+ if isinstance(index, int):
1124
+ return self.L[index]
1125
+ return dummy(self.L[index])
1126
+
1127
+ def __len__(self) -> int:
1128
+ return len(self.L)
1129
+
1130
+ class dummy_ex(dummy[T]):
1131
+ pass
1132
+
1133
+ schema: object
1134
+ object_: object
1135
+
1136
+ object_ = dummy((1, 2))
1137
+ schema = dummy((1, 2))
1138
+ validate(schema, object_)
1139
+
1140
+ schema = dummy([1, 2])
1141
+ validate(schema, object_)
1142
+
1143
+ with self.assertRaises(ValidationError) as mc:
1144
+ object_ = [1, 2]
1145
+ validate(schema, object_)
1146
+ show(mc)
1147
+
1148
+ validate(schema, dummy_ex([1, 2]))
1149
+
1150
+ with self.assertRaises(ValidationError) as mc:
1151
+ object_ = dummy(["a", "b"])
1152
+ validate(schema, object_)
1153
+ show(mc)
1154
+
1155
+ with self.assertRaises(ValidationError) as mc:
1156
+ object_ = dummy(["a", "b", None, "c"])
1157
+ validate(schema, object_)
1158
+ show(mc)
1159
+
1160
+ with self.assertRaises(ValidationError) as mc:
1161
+ schema = dummy(["a", int, ...])
1162
+ object_ = dummy(["a", "c", 1])
1163
+ validate(schema, object_)
1164
+ show(mc)
1165
+
1166
+ with self.assertRaises(ValidationError) as mc:
1167
+ schema = dummy_ex((1, 2))
1168
+ object_ = dummy((1, 2))
1169
+ validate(schema, object_)
1170
+ show(mc)
1171
+ self.assertTrue("dummy_ex" in str(mc.exception))
1172
+
1173
+ schema = dummy[str]
1174
+ object_ = dummy(("a", "b"))
1175
+ validate(schema, object_)
1176
+
1177
+ object_ = dummy_ex(("a", "b"))
1178
+ validate(schema, object_)
1179
+
1180
+ with self.assertRaises(ValidationError) as mc:
1181
+ object_ = dummy((1, 2))
1182
+ validate(schema, object_)
1183
+ show(mc)
1184
+
1185
+ with self.assertRaises(ValidationError) as mc:
1186
+ schema = dummy_ex[str]
1187
+ object_ = dummy(("a", "b"))
1188
+ validate(schema, object_)
1189
+ show(mc)
1190
+ self.assertTrue("dummy_ex" in str(mc.exception))
1191
+
1192
+ @unittest.skipUnless(
1193
+ vtjson.supports_Generic_ABC,
1194
+ "Generic base classes were introduced in Pythin 3.9",
1195
+ )
1196
+ def test_Mapping(self) -> None:
1197
+
1198
+ S = TypeVar("S")
1199
+ T = TypeVar("T")
1200
+
1201
+ class dummy(Mapping[S, T]):
1202
+ L: Mapping[S, T]
1203
+
1204
+ def __init__(self, L: Mapping[S, T] = {}) -> None:
1205
+ self.L = L
1206
+
1207
+ def __getitem__(self, key: S) -> T:
1208
+ return self.L[key]
1209
+
1210
+ def __iter__(self) -> Generator[S]:
1211
+ for key in self.L:
1212
+ yield key
1213
+
1214
+ def __len__(self) -> int:
1215
+ return len(self.L)
1216
+
1217
+ class dummy_ex(dummy[S, T]):
1218
+ pass
1219
+
1220
+ schema: object
1221
+ object_: object
1222
+
1223
+ schema = dummy({1: 2})
1224
+ object_ = dummy({1: 2})
1225
+ validate(schema, object_)
1226
+
1227
+ with self.assertRaises(ValidationError) as mc:
1228
+ object_ = [1, 2]
1229
+ validate(schema, object_)
1230
+ show(mc)
1231
+
1232
+ validate(schema, dummy_ex({1: 2}))
1233
+
1234
+ with self.assertRaises(ValidationError) as mc:
1235
+ object_ = dummy(["a", "b"]) # type: ignore
1236
+ validate(schema, object_)
1237
+ show(mc)
1238
+
1239
+ with self.assertRaises(ValidationError) as mc:
1240
+ object_ = dummy({1: "b"})
1241
+ validate(schema, object_)
1242
+ show(mc)
1243
+
1244
+ with self.assertRaises(ValidationError) as mc:
1245
+ schema = dummy({"a": int})
1246
+ object_ = dummy({"a": "c"})
1247
+ validate(schema, object_)
1248
+ show(mc)
1249
+
1250
+ with self.assertRaises(ValidationError) as mc:
1251
+ schema = dummy_ex({1: 2})
1252
+ object_ = dummy({1: 2})
1253
+ validate(schema, object_)
1254
+ show(mc)
1255
+ self.assertTrue("dummy_ex" in str(mc.exception))
1256
+
1257
+ schema = dummy[str, str]
1258
+ object_ = dummy({"a": "b"})
1259
+ validate(schema, object_)
1260
+
1261
+ object_ = dummy_ex({"a": "b"})
1262
+ validate(schema, object_)
1263
+
1264
+ with self.assertRaises(ValidationError) as mc:
1265
+ object_ = dummy({1: 2})
1266
+ validate(schema, object_)
1267
+ show(mc)
1268
+
1269
+ with self.assertRaises(ValidationError) as mc:
1270
+ schema = dummy_ex[str, str]
1271
+ object_ = dummy({"a": "b"})
1272
+ validate(schema, object_)
1273
+ show(mc)
1274
+ self.assertTrue("dummy_ex" in str(mc.exception))
1275
+
1069
1276
  def test_validate(self) -> None:
1070
1277
  schema: object
1071
1278
  object_: object
@@ -1114,7 +1321,7 @@ class TestValidation(unittest.TestCase):
1114
1321
  object_: object,
1115
1322
  name: str,
1116
1323
  strict: bool,
1117
- subs: Dict[str, object],
1324
+ subs: Mapping[str, object],
1118
1325
  ) -> str:
1119
1326
  if not isinstance(object_, str):
1120
1327
  return f"{name} (value:{object_}) is not of type str"
@@ -1788,6 +1995,11 @@ class TestValidation(unittest.TestCase):
1788
1995
  "Parametrized types were introduced in Python 3.9",
1789
1996
  )
1790
1997
  def test_generic_list(self) -> None:
1998
+ schema: object
1999
+ with self.assertRaises(SchemaError) as mc_:
2000
+ schema = list[str, str] # type: ignore
2001
+ validate(schema, "")
2002
+ show(mc_)
1791
2003
  schema = list[str]
1792
2004
  validate(schema, ["a", "b"])
1793
2005
  with self.assertRaises(ValidationError) as mc:
File without changes
File without changes
File without changes
File without changes
File without changes