vtjson 2.2.5__tar.gz → 2.2.7__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.4
2
2
  Name: vtjson
3
- Version: 2.2.5
3
+ Version: 2.2.7
4
4
  Summary: An easy to use validation library compatible with Python type annotations
5
5
  Author-email: Michel Van den Bergh <michel.vandenbergh@uhasselt.be>
6
6
  Project-URL: Homepage, https://github.com/vdbergh/vtjson
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime
4
+ import functools
5
+ import inspect
4
6
  import ipaddress
5
7
  import math
6
8
  import pathlib
@@ -10,7 +12,7 @@ import types
10
12
  import typing
11
13
  import urllib.parse
12
14
  import warnings
13
- from collections.abc import Sequence, Set, Sized
15
+ from collections.abc import Iterable, Sequence, Set, Sized
14
16
  from dataclasses import dataclass
15
17
  from typing import (
16
18
  Any,
@@ -204,7 +206,7 @@ class SchemaError(Exception):
204
206
  pass
205
207
 
206
208
 
207
- __version__ = "2.2.5"
209
+ __version__ = "2.2.7"
208
210
 
209
211
 
210
212
  @dataclass
@@ -248,14 +250,79 @@ skip_first = Apply(skip_first=True)
248
250
  _dns_resolver: dns.resolver.Resolver | None = None
249
251
 
250
252
 
251
- def _generic_name(origin: type, args: tuple[object, ...]) -> str:
252
- def to_name(c: object) -> str:
253
- if hasattr(c, "__name__"):
254
- return str(c.__name__)
253
+ def _to_name(s: object) -> str:
254
+ if hasattr(s, "__name__"):
255
+ return str(s.__name__)
256
+ else:
257
+ if isinstance(s, str):
258
+ return repr(s)
259
+ elif s == Ellipsis:
260
+ return "..."
255
261
  else:
256
- return str(c)
262
+ return str(s)
263
+
264
+
265
+ def _generic_name(origin: type, args: tuple[object, ...]) -> str:
266
+ return _to_name(origin) + "[" + ",".join([_to_name(arg) for arg in args]) + "]"
267
+
268
+
269
+ def _make_name(
270
+ type: type,
271
+ args: tuple[object, ...],
272
+ kw: dict[str, object],
273
+ defaults: dict[str, object] = {},
274
+ ) -> str:
275
+ arg_list = []
276
+ for a in args:
277
+ arg_list.append(_to_name(a))
278
+ for k, v in kw.items():
279
+ if k not in defaults or v != defaults[k]:
280
+ arg_list.append(f"{k}={_to_name(v)}")
281
+ if len(arg_list) == 0:
282
+ return _to_name(type)
283
+ else:
284
+ return f"{_to_name(type)}({','.join(arg_list)})"
285
+
286
+
287
+ C = TypeVar("C")
257
288
 
258
- return to_name(origin) + "[" + ",".join([to_name(arg) for arg in args]) + "]"
289
+
290
+ def _set__name__(c: type[C]) -> type[C]:
291
+ defaults = {}
292
+ a = inspect.getfullargspec(c.__init__)
293
+ a_defaults: tuple[object, ...] = ()
294
+ if a.defaults is not None:
295
+ a_defaults = a.defaults
296
+
297
+ if a is None or a.args is None:
298
+ raise Exception(f"Could not get signature of {c.__name__}")
299
+ start = len(a.args) - len(a_defaults)
300
+ for i in range(start, len(a.args)):
301
+ defaults[a.args[i]] = a_defaults[i - start]
302
+ __init__org = c.__init__
303
+
304
+ @functools.wraps(c.__init__)
305
+ def __init__wrapper(self: C, *args: object, **kw: object) -> None:
306
+ setattr(
307
+ self, "__name__", _make_name(self.__class__, args, kw, defaults=defaults)
308
+ )
309
+ return __init__org(self, *args, **kw)
310
+
311
+ @functools.wraps(c.__str__)
312
+ def __str__(self: C) -> str:
313
+ assert hasattr(self, "__name__")
314
+ return str(self.__name__)
315
+
316
+ @functools.wraps(c.__repr__)
317
+ def __repr__(self: C) -> str:
318
+ assert hasattr(self, "__name__")
319
+ return str(self.__name__)
320
+
321
+ setattr(c, "__init__", __init__wrapper)
322
+ setattr(c, "__str__", __str__)
323
+ setattr(c, "__repr__", __repr__)
324
+
325
+ return c
259
326
 
260
327
 
261
328
  def _get_type_hints(schema: object) -> dict[str, object]:
@@ -414,6 +481,7 @@ def make_type(
414
481
  K = TypeVar("K")
415
482
 
416
483
 
484
+ @_set__name__
417
485
  class optional_key(Generic[K]):
418
486
  """
419
487
  Make a key in a Mapping schema optional. In the common case that the key
@@ -424,6 +492,7 @@ class optional_key(Generic[K]):
424
492
 
425
493
  key: K
426
494
  optional: bool
495
+ __name__: str
427
496
 
428
497
  def __init__(self, key: K, _optional: bool = True) -> None:
429
498
  """
@@ -445,6 +514,13 @@ class optional_key(Generic[K]):
445
514
  return hash(self.key)
446
515
 
447
516
 
517
+ # def __str__(self) -> str:
518
+ # return self.__name__
519
+ #
520
+ # def __repr__(self) -> str:
521
+ # return self.__name__
522
+
523
+
448
524
  StringKeyType = TypeVar("StringKeyType", bound=Union[str, optional_key[str]])
449
525
 
450
526
 
@@ -823,6 +899,7 @@ class set_name(wrapper):
823
899
  )
824
900
 
825
901
 
902
+ @_set__name__
826
903
  class regex(compiled_schema):
827
904
  """
828
905
  This matches the strings which match the given pattern.
@@ -857,10 +934,6 @@ class regex(compiled_schema):
857
934
  if not isinstance(name, str):
858
935
  raise SchemaError(f"The regex name {_c(name)} is not a string")
859
936
  self.__name__ = name
860
- else:
861
- _flags = "" if flags == 0 else f", flags={flags}"
862
- _fullmatch = "" if fullmatch else ", fullmatch=False"
863
- self.__name__ = f"regex({repr(regex)}{_fullmatch}{_flags})"
864
937
 
865
938
  try:
866
939
  self.pattern = re.compile(regex, flags)
@@ -878,7 +951,9 @@ class regex(compiled_schema):
878
951
  subs: Mapping[str, object] = {},
879
952
  ) -> str:
880
953
  if not isinstance(obj, str):
881
- return _wrong_type_message(obj, name, self.__name__)
954
+ return _wrong_type_message(
955
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a string"
956
+ )
882
957
  try:
883
958
  if self.fullmatch and self.pattern.fullmatch(obj):
884
959
  return ""
@@ -889,6 +964,7 @@ class regex(compiled_schema):
889
964
  return _wrong_type_message(obj, name, self.__name__)
890
965
 
891
966
 
967
+ @_set__name__
892
968
  class glob(compiled_schema):
893
969
  """
894
970
  Unix style filename matching. This is implemented using
@@ -910,9 +986,7 @@ class glob(compiled_schema):
910
986
 
911
987
  self.pattern = pattern
912
988
 
913
- if name is None:
914
- self.__name__ = f"glob({repr(pattern)})"
915
- else:
989
+ if name is not None:
916
990
  self.__name__ = name
917
991
 
918
992
  try:
@@ -931,7 +1005,9 @@ class glob(compiled_schema):
931
1005
  subs: Mapping[str, object] = {},
932
1006
  ) -> str:
933
1007
  if not isinstance(obj, str):
934
- return _wrong_type_message(obj, name, self.__name__)
1008
+ return _wrong_type_message(
1009
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a string"
1010
+ )
935
1011
  try:
936
1012
  if pathlib.PurePath(obj).match(self.pattern):
937
1013
  return ""
@@ -941,6 +1017,7 @@ class glob(compiled_schema):
941
1017
  return _wrong_type_message(obj, name, self.__name__, str(e))
942
1018
 
943
1019
 
1020
+ @_set__name__
944
1021
  class magic(compiled_schema):
945
1022
  """
946
1023
  Checks if a buffer (for example a string or a byte array) has the given
@@ -966,9 +1043,7 @@ class magic(compiled_schema):
966
1043
 
967
1044
  self.mime_type = mime_type
968
1045
 
969
- if name is None:
970
- self.__name__ = f"magic({repr(mime_type)})"
971
- else:
1046
+ if name is not None:
972
1047
  self.__name__ = name
973
1048
 
974
1049
  def __validate__(
@@ -979,7 +1054,12 @@ class magic(compiled_schema):
979
1054
  subs: Mapping[str, object] = {},
980
1055
  ) -> str:
981
1056
  if not isinstance(obj, (str, bytes)):
982
- return _wrong_type_message(obj, name, self.__name__)
1057
+ return _wrong_type_message(
1058
+ obj,
1059
+ name,
1060
+ self.__name__,
1061
+ explanation=f"{_c(obj)} is not a string nor bytes",
1062
+ )
983
1063
  try:
984
1064
  objmime_type = magic_.from_buffer(obj, mime=True)
985
1065
  except Exception as e:
@@ -994,6 +1074,7 @@ class magic(compiled_schema):
994
1074
  return ""
995
1075
 
996
1076
 
1077
+ @_set__name__
997
1078
  class div(compiled_schema):
998
1079
  """
999
1080
  This matches the integers `x` such that `(x - remainder) % divisor` == 0.
@@ -1024,13 +1105,7 @@ class div(compiled_schema):
1024
1105
  self.divisor = divisor
1025
1106
  self.remainder = remainder
1026
1107
 
1027
- if name is None:
1028
- _divisor = str(divisor)
1029
- _remainder = ""
1030
- if remainder != 0:
1031
- _remainder = "," + str(remainder)
1032
- self.__name__ = f"div({_divisor+_remainder})"
1033
- else:
1108
+ if name is not None:
1034
1109
  self.__name__ = name
1035
1110
 
1036
1111
  def __validate__(
@@ -1041,13 +1116,16 @@ class div(compiled_schema):
1041
1116
  subs: Mapping[str, object] = {},
1042
1117
  ) -> str:
1043
1118
  if not isinstance(obj, int):
1044
- return _wrong_type_message(obj, name, "int")
1119
+ return _wrong_type_message(
1120
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not an integer"
1121
+ )
1045
1122
  elif (obj - self.remainder) % self.divisor == 0:
1046
1123
  return ""
1047
1124
  else:
1048
1125
  return _wrong_type_message(obj, name, self.__name__)
1049
1126
 
1050
1127
 
1128
+ @_set__name__
1051
1129
  class close_to(compiled_schema):
1052
1130
  """
1053
1131
  This matches the real numbers that are close to `x` in the sense of
@@ -1087,9 +1165,6 @@ class close_to(compiled_schema):
1087
1165
  )
1088
1166
  self.kw["abs_tol"] = abs_tol
1089
1167
 
1090
- kwl = [str(x)] + [f"{k}={v}" for (k, v) in self.kw.items()]
1091
- kwl_ = ",".join(kwl)
1092
- self.__name__ = f"close_to({kwl_})"
1093
1168
  self.x = x
1094
1169
 
1095
1170
  def __validate__(
@@ -1100,19 +1175,23 @@ class close_to(compiled_schema):
1100
1175
  subs: Mapping[str, object] = {},
1101
1176
  ) -> str:
1102
1177
  if not isinstance(obj, (float, int)):
1103
- return _wrong_type_message(obj, name, "number")
1178
+ return _wrong_type_message(
1179
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not an integer"
1180
+ )
1104
1181
  elif math.isclose(obj, self.x, **self.kw):
1105
1182
  return ""
1106
1183
  else:
1107
1184
  return _wrong_type_message(obj, name, self.__name__)
1108
1185
 
1109
1186
 
1187
+ @_set__name__
1110
1188
  class gt(compiled_schema):
1111
1189
  """
1112
1190
  This checks if `object > lb`.
1113
1191
  """
1114
1192
 
1115
1193
  lb: comparable
1194
+ __name__: str
1116
1195
 
1117
1196
  def __init__(self, lb: comparable) -> None:
1118
1197
  """
@@ -1148,12 +1227,14 @@ class gt(compiled_schema):
1148
1227
  return f"{self.message(name, obj)}: {str(e)}"
1149
1228
 
1150
1229
 
1230
+ @_set__name__
1151
1231
  class ge(compiled_schema):
1152
1232
  """
1153
1233
  This checks if `object >= lb`.
1154
1234
  """
1155
1235
 
1156
1236
  lb: comparable
1237
+ __name__: str
1157
1238
 
1158
1239
  def __init__(self, lb: comparable) -> None:
1159
1240
  """
@@ -1189,12 +1270,14 @@ class ge(compiled_schema):
1189
1270
  return f"{self.message(name, obj)}: {str(e)}"
1190
1271
 
1191
1272
 
1273
+ @_set__name__
1192
1274
  class lt(compiled_schema):
1193
1275
  """
1194
1276
  This checks if `object < ub`.
1195
1277
  """
1196
1278
 
1197
1279
  ub: comparable
1280
+ __name__: str
1198
1281
 
1199
1282
  def __init__(self, ub: comparable) -> None:
1200
1283
  """
@@ -1230,12 +1313,14 @@ class lt(compiled_schema):
1230
1313
  return f"{self.message(name, obj)}: {str(e)}"
1231
1314
 
1232
1315
 
1316
+ @_set__name__
1233
1317
  class le(compiled_schema):
1234
1318
  """
1235
1319
  This checks if `object <= ub`.
1236
1320
  """
1237
1321
 
1238
1322
  ub: comparable
1323
+ __name__: str
1239
1324
 
1240
1325
  def __init__(self, ub: comparable) -> None:
1241
1326
  """
@@ -1271,6 +1356,7 @@ class le(compiled_schema):
1271
1356
  return f"{self.message(name, obj)}: {str(e)}"
1272
1357
 
1273
1358
 
1359
+ @_set__name__
1274
1360
  class interval(compiled_schema):
1275
1361
  """
1276
1362
  This checks if `lb <= object <= ub`, provided the comparisons make sense.
@@ -1278,6 +1364,7 @@ class interval(compiled_schema):
1278
1364
 
1279
1365
  lb_s: str
1280
1366
  ub_s: str
1367
+ __name__: str
1281
1368
 
1282
1369
  def __init__(
1283
1370
  self,
@@ -1346,6 +1433,7 @@ class interval(compiled_schema):
1346
1433
  setattr(self, "__validate__", anything().__validate__)
1347
1434
 
1348
1435
 
1436
+ @_set__name__
1349
1437
  class size(compiled_schema):
1350
1438
  """
1351
1439
  Matches the objects (which support `len()` such as strings or lists) whose
@@ -1353,6 +1441,7 @@ class size(compiled_schema):
1353
1441
  """
1354
1442
 
1355
1443
  interval_: interval
1444
+ __name__: str
1356
1445
 
1357
1446
  def __init__(self, lb: int, ub: int | types.EllipsisType | None = None) -> None:
1358
1447
  """
@@ -1625,11 +1714,14 @@ def validate(
1625
1714
  # Some predefined schemas
1626
1715
 
1627
1716
 
1717
+ @_set__name__
1628
1718
  class number(compiled_schema):
1629
1719
  """
1630
1720
  A deprecated alias for `float`.
1631
1721
  """
1632
1722
 
1723
+ __name__: str
1724
+
1633
1725
  def __init__(self) -> None:
1634
1726
  warnings.warn(
1635
1727
  "The schema 'number' is deprecated. Use 'float' instead.",
@@ -1646,14 +1738,20 @@ class number(compiled_schema):
1646
1738
  if isinstance(obj, (int, float)):
1647
1739
  return ""
1648
1740
  else:
1649
- return _wrong_type_message(obj, name, "number")
1741
+ return _wrong_type_message(obj, name, self.__name__)
1650
1742
 
1651
1743
 
1744
+ @_set__name__
1652
1745
  class float_(compiled_schema):
1653
1746
  """
1654
1747
  Schema that only matches floats. Not ints.
1655
1748
  """
1656
1749
 
1750
+ __name__: str
1751
+
1752
+ def __init__(self) -> None:
1753
+ pass
1754
+
1657
1755
  def __validate__(
1658
1756
  self,
1659
1757
  obj: object,
@@ -1664,9 +1762,10 @@ class float_(compiled_schema):
1664
1762
  if isinstance(obj, float):
1665
1763
  return ""
1666
1764
  else:
1667
- return _wrong_type_message(obj, name, "float_")
1765
+ return _wrong_type_message(obj, name, self.__name__)
1668
1766
 
1669
1767
 
1768
+ @_set__name__
1670
1769
  class email(compiled_schema):
1671
1770
  """
1672
1771
  Checks if the object is a valid email address. This uses the package
@@ -1675,6 +1774,7 @@ class email(compiled_schema):
1675
1774
  """
1676
1775
 
1677
1776
  kw: dict[str, Any]
1777
+ __name__: str
1678
1778
 
1679
1779
  def __init__(self, **kw: Any) -> None:
1680
1780
  """
@@ -1695,14 +1795,17 @@ class email(compiled_schema):
1695
1795
  subs: Mapping[str, object] = {},
1696
1796
  ) -> str:
1697
1797
  if not isinstance(obj, str):
1698
- return _wrong_type_message(obj, name, "email", f"{_c(obj)} is not a string")
1798
+ return _wrong_type_message(
1799
+ obj, name, self.__name__, f"{_c(obj)} is not a string"
1800
+ )
1699
1801
  try:
1700
1802
  email_validator.validate_email(obj, **self.kw)
1701
1803
  return ""
1702
1804
  except Exception as e:
1703
- return _wrong_type_message(obj, name, "email", str(e))
1805
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1704
1806
 
1705
1807
 
1808
+ @_set__name__
1706
1809
  class ip_address(compiled_schema):
1707
1810
  """
1708
1811
  Matches ip addresses of the specified version which can be 4, 6 or None.
@@ -1719,10 +1822,7 @@ class ip_address(compiled_schema):
1719
1822
  """
1720
1823
  if version is not None and version not in (4, 6):
1721
1824
  raise SchemaError("version is not 4 or 6")
1722
- if version is None:
1723
- self.__name__ = "ip_address"
1724
- else:
1725
- self.__name__ = f"ip_address(version={version})"
1825
+
1726
1826
  if version == 4:
1727
1827
  self.method = ipaddress.IPv4Address
1728
1828
  elif version == 6:
@@ -1738,7 +1838,12 @@ class ip_address(compiled_schema):
1738
1838
  subs: Mapping[str, object] = {},
1739
1839
  ) -> str:
1740
1840
  if not isinstance(obj, (int, str, bytes)):
1741
- return _wrong_type_message(obj, name, self.__name__)
1841
+ return _wrong_type_message(
1842
+ obj,
1843
+ name,
1844
+ self.__name__,
1845
+ explanation=f"{_c(obj)} is not a string, an int or bytes",
1846
+ )
1742
1847
  try:
1743
1848
  self.method(obj)
1744
1849
  except ValueError as e:
@@ -1746,11 +1851,89 @@ class ip_address(compiled_schema):
1746
1851
  return ""
1747
1852
 
1748
1853
 
1854
+ @_set__name__
1855
+ class regex_pattern(compiled_schema):
1856
+ """
1857
+ Matches valid regular expression patterns
1858
+ """
1859
+
1860
+ __name__: str
1861
+
1862
+ def __init__(self) -> None:
1863
+ pass
1864
+
1865
+ def __validate__(
1866
+ self,
1867
+ obj: object,
1868
+ name: str = "object",
1869
+ strict: bool = True,
1870
+ subs: Mapping[str, object] = {},
1871
+ ) -> str:
1872
+ if not isinstance(obj, str):
1873
+ return _wrong_type_message(
1874
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a string"
1875
+ )
1876
+ try:
1877
+ re.compile(obj)
1878
+ except re.error as e:
1879
+ return _wrong_type_message(obj, name, self.__name__, explanation=str(e))
1880
+ return ""
1881
+
1882
+
1883
+ @_set__name__
1884
+ class unique(compiled_schema):
1885
+ """
1886
+ Matches containers whose entries do not repeat. We first attempt to convert the
1887
+ object to a set and check its size. If this does not work then we check the
1888
+ entries of the object one by one (this is a quadratic algorithm).
1889
+ """
1890
+
1891
+ __name__: str
1892
+
1893
+ def __init__(self) -> None:
1894
+ pass
1895
+
1896
+ def __validate__(
1897
+ self,
1898
+ obj: object,
1899
+ name: str = "object",
1900
+ strict: bool = True,
1901
+ subs: Mapping[str, object] = {},
1902
+ ) -> str:
1903
+ if not isinstance(obj, Iterable):
1904
+ return _wrong_type_message(
1905
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not iterable"
1906
+ )
1907
+ try:
1908
+ if isinstance(obj, Sized):
1909
+ if len(set(obj)) == len(obj):
1910
+ return ""
1911
+ except Exception:
1912
+ pass
1913
+ try:
1914
+ object_list = []
1915
+ for o in obj:
1916
+ if o in object_list:
1917
+ return _wrong_type_message(
1918
+ obj, name, self.__name__, explanation=f"{_c(o)} is repeated"
1919
+ )
1920
+ object_list.append(o)
1921
+ except Exception as e:
1922
+ return _wrong_type_message(obj, name, self.__name__, explanation=str(e))
1923
+ return ""
1924
+
1925
+
1926
+ @_set__name__
1749
1927
  class url(compiled_schema):
1750
1928
  """
1751
1929
  Matches valid urls.
1752
1930
  """
1753
1931
 
1932
+ __name__: str
1933
+
1934
+ def __init__(self) -> None:
1935
+ pass
1936
+
1754
1937
  def __validate__(
1755
1938
  self,
1756
1939
  obj: object,
@@ -1759,13 +1942,16 @@ class url(compiled_schema):
1759
1942
  subs: Mapping[str, object] = {},
1760
1943
  ) -> str:
1761
1944
  if not isinstance(obj, str):
1762
- return _wrong_type_message(obj, name, "url")
1945
+ return _wrong_type_message(
1946
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a string"
1947
+ )
1763
1948
  result = urllib.parse.urlparse(obj)
1764
1949
  if all([result.scheme, result.netloc]):
1765
1950
  return ""
1766
- return _wrong_type_message(obj, name, "url")
1951
+ return _wrong_type_message(obj, name, self.__name__)
1767
1952
 
1768
1953
 
1954
+ @_set__name__
1769
1955
  class date_time(compiled_schema):
1770
1956
  """
1771
1957
  Without argument this represents an ISO 8601 date-time. The `format`
@@ -1780,10 +1966,6 @@ class date_time(compiled_schema):
1780
1966
  :param format: format string for `strftime`
1781
1967
  """
1782
1968
  self.format = format
1783
- if format is not None:
1784
- self.__name__ = f"date_time({repr(format)})"
1785
- else:
1786
- self.__name__ = "date_time"
1787
1969
 
1788
1970
  def __validate__(
1789
1971
  self,
@@ -1793,7 +1975,9 @@ class date_time(compiled_schema):
1793
1975
  subs: Mapping[str, object] = {},
1794
1976
  ) -> str:
1795
1977
  if not isinstance(obj, str):
1796
- return _wrong_type_message(obj, name, self.__name__)
1978
+ return _wrong_type_message(
1979
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a string"
1980
+ )
1797
1981
  if self.format is not None:
1798
1982
  try:
1799
1983
  datetime.datetime.strptime(obj, self.format)
@@ -1807,11 +1991,17 @@ class date_time(compiled_schema):
1807
1991
  return ""
1808
1992
 
1809
1993
 
1994
+ @_set__name__
1810
1995
  class date(compiled_schema):
1811
1996
  """
1812
1997
  Matches an ISO 8601 date.
1813
1998
  """
1814
1999
 
2000
+ __name__: str
2001
+
2002
+ def __init__(self) -> None:
2003
+ pass
2004
+
1815
2005
  def __validate__(
1816
2006
  self,
1817
2007
  obj: object,
@@ -1820,19 +2010,27 @@ class date(compiled_schema):
1820
2010
  subs: Mapping[str, object] = {},
1821
2011
  ) -> str:
1822
2012
  if not isinstance(obj, str):
1823
- return _wrong_type_message(obj, name, "date")
2013
+ return _wrong_type_message(
2014
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a string"
2015
+ )
1824
2016
  try:
1825
2017
  datetime.date.fromisoformat(obj)
1826
2018
  except Exception as e:
1827
- return _wrong_type_message(obj, name, "date", str(e))
2019
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1828
2020
  return ""
1829
2021
 
1830
2022
 
2023
+ @_set__name__
1831
2024
  class time(compiled_schema):
1832
2025
  """
1833
2026
  Matches an ISO 8601 time.
1834
2027
  """
1835
2028
 
2029
+ __name__: str
2030
+
2031
+ def __init__(self) -> None:
2032
+ pass
2033
+
1836
2034
  def __validate__(
1837
2035
  self,
1838
2036
  obj: object,
@@ -1841,19 +2039,27 @@ class time(compiled_schema):
1841
2039
  subs: Mapping[str, object] = {},
1842
2040
  ) -> str:
1843
2041
  if not isinstance(obj, str):
1844
- return _wrong_type_message(obj, name, "date")
2042
+ return _wrong_type_message(
2043
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a string"
2044
+ )
1845
2045
  try:
1846
2046
  datetime.time.fromisoformat(obj)
1847
2047
  except Exception as e:
1848
- return _wrong_type_message(obj, name, "time", str(e))
2048
+ return _wrong_type_message(obj, name, self.__name__, str(e))
1849
2049
  return ""
1850
2050
 
1851
2051
 
2052
+ @_set__name__
1852
2053
  class nothing(compiled_schema):
1853
2054
  """
1854
2055
  Matches nothing.
1855
2056
  """
1856
2057
 
2058
+ __name__: str
2059
+
2060
+ def __init__(self) -> None:
2061
+ pass
2062
+
1857
2063
  def __validate__(
1858
2064
  self,
1859
2065
  obj: object,
@@ -1861,14 +2067,20 @@ class nothing(compiled_schema):
1861
2067
  strict: bool = True,
1862
2068
  subs: Mapping[str, object] = {},
1863
2069
  ) -> str:
1864
- return _wrong_type_message(obj, name, "nothing")
2070
+ return _wrong_type_message(obj, name, self.__name__)
1865
2071
 
1866
2072
 
2073
+ @_set__name__
1867
2074
  class anything(compiled_schema):
1868
2075
  """
1869
2076
  Matchess anything.
1870
2077
  """
1871
2078
 
2079
+ __name__: str
2080
+
2081
+ def __init__(self) -> None:
2082
+ pass
2083
+
1872
2084
  def __validate__(
1873
2085
  self,
1874
2086
  obj: object,
@@ -1879,6 +2091,7 @@ class anything(compiled_schema):
1879
2091
  return ""
1880
2092
 
1881
2093
 
2094
+ @_set__name__
1882
2095
  class domain_name(compiled_schema):
1883
2096
  """
1884
2097
  Checks if the object is a valid domain name.
@@ -1897,16 +2110,6 @@ class domain_name(compiled_schema):
1897
2110
  self.re_ascii = re.compile(r"[\x00-\x7F]*")
1898
2111
  self.ascii_only = ascii_only
1899
2112
  self.resolve = resolve
1900
- arg_string = ""
1901
- if not ascii_only:
1902
- arg_string += ", ascii_only=False"
1903
- if resolve:
1904
- arg_string += ", resolve=True"
1905
- if arg_string != "":
1906
- arg_string = arg_string[2:]
1907
- self.__name__ = (
1908
- "domain_name" if not arg_string else f"domain_name({arg_string})"
1909
- )
1910
2113
 
1911
2114
  def __validate__(
1912
2115
  self,
@@ -1916,7 +2119,9 @@ class domain_name(compiled_schema):
1916
2119
  subs: Mapping[str, object] = {},
1917
2120
  ) -> str:
1918
2121
  if not isinstance(obj, str):
1919
- return _wrong_type_message(obj, name, self.__name__)
2122
+ return _wrong_type_message(
2123
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a string"
2124
+ )
1920
2125
  if self.ascii_only:
1921
2126
  if not self.re_ascii.fullmatch(obj):
1922
2127
  return _wrong_type_message(
@@ -1935,6 +2140,7 @@ class domain_name(compiled_schema):
1935
2140
  return ""
1936
2141
 
1937
2142
 
2143
+ @_set__name__
1938
2144
  class at_least_one_of(compiled_schema):
1939
2145
  """
1940
2146
  This represents a dictionary with a least one key among a collection of
@@ -1949,8 +2155,6 @@ class at_least_one_of(compiled_schema):
1949
2155
  :param args: a collection of keys
1950
2156
  """
1951
2157
  self.args = args
1952
- args_s = [repr(a) for a in args]
1953
- self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
1954
2158
 
1955
2159
  def __validate__(
1956
2160
  self,
@@ -1960,7 +2164,9 @@ class at_least_one_of(compiled_schema):
1960
2164
  subs: Mapping[str, object] = {},
1961
2165
  ) -> str:
1962
2166
  if not isinstance(obj, Mapping):
1963
- return _wrong_type_message(obj, name, self.__name__)
2167
+ return _wrong_type_message(
2168
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a Mapping"
2169
+ )
1964
2170
  try:
1965
2171
  if any([a in obj for a in self.args]):
1966
2172
  return ""
@@ -1970,6 +2176,7 @@ class at_least_one_of(compiled_schema):
1970
2176
  return _wrong_type_message(obj, name, self.__name__, str(e))
1971
2177
 
1972
2178
 
2179
+ @_set__name__
1973
2180
  class at_most_one_of(compiled_schema):
1974
2181
  """
1975
2182
  This represents an dictionary with at most one key among a collection of
@@ -1984,8 +2191,6 @@ class at_most_one_of(compiled_schema):
1984
2191
  :param args: a collection of keys
1985
2192
  """
1986
2193
  self.args = args
1987
- args_s = [repr(a) for a in args]
1988
- self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
1989
2194
 
1990
2195
  def __validate__(
1991
2196
  self,
@@ -1995,7 +2200,9 @@ class at_most_one_of(compiled_schema):
1995
2200
  subs: Mapping[str, object] = {},
1996
2201
  ) -> str:
1997
2202
  if not isinstance(obj, Mapping):
1998
- return _wrong_type_message(obj, name, self.__name__)
2203
+ return _wrong_type_message(
2204
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a Mapping"
2205
+ )
1999
2206
  try:
2000
2207
  if sum([a in obj for a in self.args]) <= 1:
2001
2208
  return ""
@@ -2005,6 +2212,7 @@ class at_most_one_of(compiled_schema):
2005
2212
  return _wrong_type_message(obj, name, self.__name__, str(e))
2006
2213
 
2007
2214
 
2215
+ @_set__name__
2008
2216
  class one_of(compiled_schema):
2009
2217
  """
2010
2218
  This represents a dictionary with exactly one key among a collection of
@@ -2019,8 +2227,6 @@ class one_of(compiled_schema):
2019
2227
  :param args: a collection of keys
2020
2228
  """
2021
2229
  self.args = args
2022
- args_s = [repr(a) for a in args]
2023
- self.__name__ = f"{self.__class__.__name__}({','.join(args_s)})"
2024
2230
 
2025
2231
  def __validate__(
2026
2232
  self,
@@ -2030,7 +2236,9 @@ class one_of(compiled_schema):
2030
2236
  subs: Mapping[str, object] = {},
2031
2237
  ) -> str:
2032
2238
  if not isinstance(obj, Mapping):
2033
- return _wrong_type_message(obj, name, self.__name__)
2239
+ return _wrong_type_message(
2240
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a Mapping"
2241
+ )
2034
2242
  try:
2035
2243
  if sum([a in obj for a in self.args]) == 1:
2036
2244
  return ""
@@ -2040,6 +2248,7 @@ class one_of(compiled_schema):
2040
2248
  return _wrong_type_message(obj, name, self.__name__, str(e))
2041
2249
 
2042
2250
 
2251
+ @_set__name__
2043
2252
  class keys(compiled_schema):
2044
2253
  """
2045
2254
  This represents a dictionary containing all the keys in a collection of
@@ -2047,6 +2256,7 @@ class keys(compiled_schema):
2047
2256
  """
2048
2257
 
2049
2258
  args: tuple[object, ...]
2259
+ __name__: str
2050
2260
 
2051
2261
  def __init__(self, *args: object) -> None:
2052
2262
  """
@@ -2062,7 +2272,9 @@ class keys(compiled_schema):
2062
2272
  subs: Mapping[str, object] = {},
2063
2273
  ) -> str:
2064
2274
  if not isinstance(obj, Mapping):
2065
- return _wrong_type_message(obj, name, "Mapping") # TODO: __name__
2275
+ return _wrong_type_message(
2276
+ obj, name, self.__name__, explanation=f"{_c(obj)} is not a Mapping"
2277
+ )
2066
2278
  for k in self.args:
2067
2279
  if k not in obj:
2068
2280
  return f"{name}[{repr(k)}] is missing"
@@ -2242,6 +2454,7 @@ class _fields(compiled_schema):
2242
2454
  return ""
2243
2455
 
2244
2456
 
2457
+ @_set__name__
2245
2458
  class fields(wrapper, Generic[StringKeyType]):
2246
2459
  """
2247
2460
  `d` is a dictionary `{"field1": schema1, ...}`.
@@ -2251,6 +2464,7 @@ class fields(wrapper, Generic[StringKeyType]):
2251
2464
  """
2252
2465
 
2253
2466
  d: Mapping[StringKeyType, object]
2467
+ __name__: str
2254
2468
 
2255
2469
  def __init__(self, d: Mapping[StringKeyType, object]) -> None:
2256
2470
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vtjson
3
- Version: 2.2.5
3
+ Version: 2.2.7
4
4
  Summary: An easy to use validation library compatible with Python type annotations
5
5
  Author-email: Michel Van den Bergh <michel.vandenbergh@uhasselt.be>
6
6
  Project-URL: Homepage, https://github.com/vdbergh/vtjson
@@ -96,6 +96,7 @@ from vtjson import (
96
96
  protocol,
97
97
  quote,
98
98
  regex,
99
+ regex_pattern,
99
100
  safe_cast,
100
101
  set_label,
101
102
  set_name,
@@ -103,6 +104,7 @@ from vtjson import (
103
104
  strict,
104
105
  time,
105
106
  union,
107
+ unique,
106
108
  url,
107
109
  validate,
108
110
  )
@@ -110,7 +112,7 @@ from vtjson import (
110
112
 
111
113
  def show(mc: Any) -> None:
112
114
  exception = mc.exception
113
- print(f"{exception.__class__.__name__}: {str(mc.exception)}")
115
+ print(f"{exception.__class__.__name__}: {str(mc.exception)}", flush=True)
114
116
 
115
117
 
116
118
  class TestValidation(unittest.TestCase):
@@ -211,6 +213,10 @@ class TestValidation(unittest.TestCase):
211
213
  object_ = "hello.doc"
212
214
  validate(schema, object_)
213
215
  show(mc)
216
+ with self.assertRaises(ValidationError) as mc:
217
+ object_ = 123
218
+ validate(schema, object_)
219
+ show(mc)
214
220
  with self.assertRaises(SchemaError) as mc_:
215
221
  schema = glob({}) # type: ignore
216
222
  show(mc_)
@@ -403,6 +409,10 @@ class TestValidation(unittest.TestCase):
403
409
  schema = close_to(1.0)
404
410
  validate(schema, 1.0)
405
411
 
412
+ with self.assertRaises(ValidationError) as mc:
413
+ validate(schema, "1.0")
414
+ show(mc)
415
+
406
416
  with self.assertRaises(ValidationError) as mc:
407
417
  validate(schema, 1.1)
408
418
  show(mc)
@@ -422,6 +432,10 @@ class TestValidation(unittest.TestCase):
422
432
  validate(schema, object_)
423
433
  object_ = {"cat": None}
424
434
  validate(schema, object_)
435
+ with self.assertRaises(ValidationError) as mc:
436
+ object_ = 123
437
+ validate(schema, object_)
438
+ show(mc)
425
439
  with self.assertRaises(ValidationError) as mc:
426
440
  object_ = {"cat": None, "dog": None}
427
441
  validate(schema, object_)
@@ -473,6 +487,10 @@ class TestValidation(unittest.TestCase):
473
487
  schema = keys("a", "b")
474
488
  object_ = {"a": 1, "b": 2}
475
489
  validate(schema, object_)
490
+ with self.assertRaises(ValidationError) as mc:
491
+ object_ = 123
492
+ validate(schema, object_)
493
+ show(mc)
476
494
  with self.assertRaises(ValidationError) as mc:
477
495
  object_ = {"a": 1}
478
496
  validate(schema, object_)
@@ -760,6 +778,11 @@ class TestValidation(unittest.TestCase):
760
778
  def test_date_time(self) -> None:
761
779
  schema: object
762
780
  object_: object
781
+ with self.assertRaises(ValidationError) as mc:
782
+ schema = date_time
783
+ object_ = 123
784
+ validate(schema, object_)
785
+ show(mc)
763
786
  with self.assertRaises(ValidationError) as mc:
764
787
  schema = date_time
765
788
  object_ = "2000-30-30"
@@ -795,6 +818,10 @@ class TestValidation(unittest.TestCase):
795
818
  object_ = "2023-10-10"
796
819
  validate(schema, object_)
797
820
 
821
+ with self.assertRaises(ValidationError) as mc:
822
+ object_ = 123
823
+ validate(schema, object_)
824
+ show(mc)
798
825
  with self.assertRaises(ValidationError) as mc:
799
826
  object_ = "2023-10-10T01:01:01"
800
827
  validate(schema, object_)
@@ -807,6 +834,10 @@ class TestValidation(unittest.TestCase):
807
834
  object_ = "01:01:01"
808
835
  validate(schema, object_)
809
836
 
837
+ with self.assertRaises(ValidationError) as mc:
838
+ object_ = 123
839
+ validate(schema, object_)
840
+ show(mc)
810
841
  with self.assertRaises(ValidationError) as mc:
811
842
  object_ = "2023-10-10T01:01:01"
812
843
  validate(schema, object_)
@@ -1385,6 +1416,12 @@ class TestValidation(unittest.TestCase):
1385
1416
  compile(schema)
1386
1417
  show(cm_)
1387
1418
 
1419
+ ip_address = regex(r"(?:[\d]+\.){3}(?:[\d]+)")
1420
+ with self.assertRaises(ValidationError) as mc:
1421
+ object_ = 123
1422
+ validate(ip_address, object_)
1423
+ show(mc)
1424
+
1388
1425
  ip_address = regex(r"(?:[\d]+\.){3}(?:[\d]+)", name="ip_address")
1389
1426
  with self.assertRaises(ValidationError) as mc:
1390
1427
  object_ = 123
@@ -1787,6 +1824,11 @@ class TestValidation(unittest.TestCase):
1787
1824
  object_ = {"url": "https://user:pass@google.com?search=chatgpt"}
1788
1825
  validate(schema, object_)
1789
1826
 
1827
+ with self.assertRaises(ValidationError) as mc:
1828
+ object_ = {"url": 123}
1829
+ validate(schema, object_)
1830
+ show(mc)
1831
+
1790
1832
  with self.assertRaises(ValidationError) as mc:
1791
1833
  object_ = {"url": "google.com"}
1792
1834
  validate(schema, object_)
@@ -1799,6 +1841,10 @@ class TestValidation(unittest.TestCase):
1799
1841
  object_ = "www.example.com"
1800
1842
  validate(schema, object_)
1801
1843
 
1844
+ with self.assertRaises(ValidationError) as mc:
1845
+ object_ = 123
1846
+ validate(schema, object_)
1847
+ show(mc)
1802
1848
  with self.assertRaises(ValidationError) as mc:
1803
1849
  object_ = "www.éxample.com"
1804
1850
  validate(schema, object_)
@@ -1844,6 +1890,41 @@ class TestValidation(unittest.TestCase):
1844
1890
  validate(schema, object_)
1845
1891
  show(mc)
1846
1892
 
1893
+ def test_regex_pattern(self) -> None:
1894
+ schema: object
1895
+ object_: object
1896
+ schema = regex_pattern
1897
+ with self.assertRaises(ValidationError) as mc:
1898
+ object_ = 123
1899
+ validate(schema, object_)
1900
+ show(mc)
1901
+ with self.assertRaises(ValidationError) as mc:
1902
+ object_ = "(("
1903
+ validate(schema, object_)
1904
+ show(mc)
1905
+ object_ = ".*"
1906
+ validate(schema, object_)
1907
+
1908
+ def test_unique(self) -> None:
1909
+ schema = unique
1910
+ object_: object
1911
+ with self.assertRaises(ValidationError) as mc:
1912
+ object_ = 123
1913
+ validate(schema, object_)
1914
+ show(mc)
1915
+ with self.assertRaises(ValidationError) as mc:
1916
+ object_ = [1, 2, 3, 2]
1917
+ validate(schema, object_)
1918
+ show(mc)
1919
+ with self.assertRaises(ValidationError) as mc:
1920
+ object_ = [[1], [2], [3], [2]]
1921
+ validate(schema, object_)
1922
+ show(mc)
1923
+ object_ = [1, 2, 3]
1924
+ validate(schema, object_)
1925
+ object_ = [[1], [2], [3]]
1926
+ validate(schema, object_)
1927
+
1847
1928
  def test_truncation(self) -> None:
1848
1929
  schema: object
1849
1930
  object_: object
@@ -2368,6 +2449,131 @@ class TestValidation(unittest.TestCase):
2368
2449
  validate(protocol(dummy2), u(""))
2369
2450
  show(mc)
2370
2451
 
2452
+ def test_name(self) -> None:
2453
+ schema: object
2454
+
2455
+ schema = regex("abc", name="cba")
2456
+ self.assertEqual(schema.__name__, "cba")
2457
+ schema = regex("abc")
2458
+ self.assertEqual(schema.__name__, "regex('abc')")
2459
+ schema = regex("abc", fullmatch=False)
2460
+ self.assertEqual(schema.__name__, "regex('abc',fullmatch=False)")
2461
+ schema = regex("abc", fullmatch=True)
2462
+ self.assertEqual(schema.__name__, "regex('abc')")
2463
+ schema = regex("abc", fullmatch=False, flags=re.ASCII | re.MULTILINE)
2464
+ self.assertEqual(
2465
+ schema.__name__, "regex('abc',fullmatch=False,flags=re.ASCII|re.MULTILINE)"
2466
+ )
2467
+ schema = regex("abc", fullmatch=False, flags=0)
2468
+ self.assertEqual(schema.__name__, "regex('abc',fullmatch=False)")
2469
+
2470
+ schema = glob("*.txt", "text_file")
2471
+ self.assertEqual(schema.__name__, "text_file")
2472
+ schema = glob("*.txt")
2473
+ self.assertEqual(schema.__name__, "glob('*.txt')")
2474
+
2475
+ schema = div(2, name="even")
2476
+ self.assertEqual(schema.__name__, "even")
2477
+ schema = div(2)
2478
+ self.assertEqual(schema.__name__, "div(2)")
2479
+ schema = div(2, 1)
2480
+ self.assertEqual(schema.__name__, "div(2,1)")
2481
+
2482
+ schema = close_to(0.75)
2483
+ self.assertEqual(schema.__name__, "close_to(0.75)")
2484
+
2485
+ schema = email
2486
+ self.assertEqual(schema.__name__, "email")
2487
+
2488
+ schema = ip_address
2489
+ self.assertEqual(schema.__name__, "ip_address")
2490
+ schema = ip_address(version=4)
2491
+ self.assertEqual(schema.__name__, "ip_address(version=4)")
2492
+
2493
+ schema = regex_pattern
2494
+ self.assertEqual(schema.__name__, "regex_pattern")
2495
+
2496
+ schema = url
2497
+ self.assertEqual(schema.__name__, "url")
2498
+
2499
+ schema = domain_name
2500
+ self.assertEqual(schema.__name__, "domain_name")
2501
+ schema = domain_name(ascii_only=False)
2502
+ self.assertEqual(schema.__name__, "domain_name(ascii_only=False)")
2503
+ schema = domain_name(ascii_only=False, resolve=True)
2504
+ self.assertEqual(schema.__name__, "domain_name(ascii_only=False,resolve=True)")
2505
+
2506
+ schema = date_time
2507
+ self.assertEqual(schema.__name__, "date_time")
2508
+ schema = date_time("%Y^%m^%d")
2509
+ self.assertEqual(schema.__name__, "date_time('%Y^%m^%d')")
2510
+
2511
+ schema = date
2512
+ self.assertEqual(schema.__name__, "date")
2513
+
2514
+ schema = time
2515
+ self.assertEqual(schema.__name__, "time")
2516
+
2517
+ schema = nothing
2518
+ self.assertEqual(schema.__name__, "nothing")
2519
+
2520
+ schema = anything
2521
+ self.assertEqual(schema.__name__, "anything")
2522
+
2523
+ schema = float_
2524
+ self.assertEqual(schema.__name__, "float_")
2525
+
2526
+ schema = one_of("a", "b", 1)
2527
+ self.assertEqual(schema.__name__, "one_of('a','b',1)")
2528
+
2529
+ schema = at_least_one_of("a", "b", 1)
2530
+ self.assertEqual(schema.__name__, "at_least_one_of('a','b',1)")
2531
+
2532
+ schema = at_most_one_of("a", "b", 1)
2533
+ self.assertEqual(schema.__name__, "at_most_one_of('a','b',1)")
2534
+
2535
+ schema = keys("a", "b", 1)
2536
+ self.assertEqual(schema.__name__, "keys('a','b',1)")
2537
+
2538
+ schema = interval(1, 10)
2539
+ self.assertEqual(schema.__name__, "interval(1,10)")
2540
+ schema = interval(1, ...)
2541
+ self.assertEqual(schema.__name__, "interval(1,...)")
2542
+ schema = interval(..., 10)
2543
+ self.assertEqual(schema.__name__, "interval(...,10)")
2544
+ schema = interval(..., 10, strict_ub=True)
2545
+ self.assertEqual(schema.__name__, "interval(...,10,strict_ub=True)")
2546
+
2547
+ schema = gt(10)
2548
+ self.assertEqual(schema.__name__, "gt(10)")
2549
+
2550
+ schema = ge(10)
2551
+ self.assertEqual(schema.__name__, "ge(10)")
2552
+
2553
+ schema = lt(10)
2554
+ self.assertEqual(schema.__name__, "lt(10)")
2555
+
2556
+ schema = le(10)
2557
+ self.assertEqual(schema.__name__, "le(10)")
2558
+
2559
+ schema = size(1)
2560
+ self.assertEqual(schema.__name__, "size(1)")
2561
+ schema = size(1, 10)
2562
+ self.assertEqual(schema.__name__, "size(1,10)")
2563
+
2564
+ schema = fields({"a": "b"})
2565
+ self.assertEqual(schema.__name__, "fields({'a': 'b'})")
2566
+ schema = fields({optional_key("a"): "b"})
2567
+ self.assertEqual(schema.__name__, "fields({optional_key('a'): 'b'})")
2568
+
2569
+ schema = magic("application/html", name="html")
2570
+ self.assertEqual(schema.__name__, "html")
2571
+ schema = magic("application/html")
2572
+ self.assertEqual(schema.__name__, "magic('application/html')")
2573
+
2574
+ schema = unique
2575
+ self.assertEqual(schema.__name__, "unique")
2576
+
2371
2577
 
2372
2578
  if __name__ == "__main__":
2373
2579
  unittest.main(verbosity=2)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes