vtjson 2.1.5__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.5
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
@@ -168,11 +168,11 @@ A schema can be, in order of precedence:
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.
@@ -148,11 +148,11 @@ A schema can be, in order of precedence:
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.
@@ -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, Mapping, 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
@@ -121,7 +121,7 @@ class SchemaError(Exception):
121
121
  pass
122
122
 
123
123
 
124
- __version__ = "2.1.5"
124
+ __version__ = "2.1.6"
125
125
 
126
126
 
127
127
  @dataclass
@@ -153,6 +153,16 @@ skip_first = Apply(skip_first=True)
153
153
  _dns_resolver: dns.resolver.Resolver | None = None
154
154
 
155
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
+
156
166
  def _get_type_hints(schema: object) -> dict[str, object]:
157
167
  if not supports_structural:
158
168
  raise SchemaError(
@@ -1141,14 +1151,14 @@ def _compile(
1141
1151
  )
1142
1152
  elif origin == tuple:
1143
1153
  ret = _Tuple(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],
1154
+ elif isinstance(origin, type) and issubclass(origin, Mapping):
1155
+ ret = _Mapping(
1156
+ typing.get_args(schema),
1147
1157
  type_schema=origin,
1148
1158
  _deferred_compiles=_deferred_compiles,
1149
1159
  )
1150
- elif isinstance(origin, type) and issubclass(origin, Mapping):
1151
- ret = _Dict(
1160
+ elif isinstance(origin, type) and issubclass(origin, Container):
1161
+ ret = _Container(
1152
1162
  typing.get_args(schema),
1153
1163
  type_schema=origin,
1154
1164
  _deferred_compiles=_deferred_compiles,
@@ -1169,7 +1179,7 @@ def _compile(
1169
1179
  ret = _sequence(schema, _deferred_compiles=_deferred_compiles)
1170
1180
  elif isinstance(schema, Mapping):
1171
1181
  ret = _dict(schema, _deferred_compiles=_deferred_compiles)
1172
- elif isinstance(schema, set):
1182
+ elif isinstance(schema, Set):
1173
1183
  ret = _set(schema, _deferred_compiles=_deferred_compiles)
1174
1184
  else:
1175
1185
  ret = _const(schema)
@@ -1841,13 +1851,10 @@ class _sequence(compiled_schema):
1841
1851
  def __init__(
1842
1852
  self,
1843
1853
  schema: Sequence[object],
1844
- type_schema: type | None = None,
1854
+ # type_schema: type | None = None,
1845
1855
  _deferred_compiles: _mapping | None = None,
1846
1856
  ) -> None:
1847
- if type_schema is None:
1848
- self.type_schema = type(schema)
1849
- else:
1850
- self.type_schema = type_schema
1857
+ self.type_schema = type(schema)
1851
1858
  self.schema = [
1852
1859
  _compile(o, _deferred_compiles=_deferred_compiles)
1853
1860
  for o in schema
@@ -1980,13 +1987,9 @@ class _dict(compiled_schema):
1980
1987
  def __init__(
1981
1988
  self,
1982
1989
  schema: Mapping[object, object],
1983
- type_schema: type | None = None,
1984
1990
  _deferred_compiles: _mapping | None = None,
1985
1991
  ) -> None:
1986
- if type_schema is None:
1987
- self.type_schema = type(schema)
1988
- else:
1989
- self.type_schema = type_schema
1992
+ self.type_schema = type(schema)
1990
1993
  self.min_keys = set()
1991
1994
  self.const_keys = set()
1992
1995
  self.other_keys = set()
@@ -2059,15 +2062,18 @@ class _dict(compiled_schema):
2059
2062
 
2060
2063
 
2061
2064
  class _set(compiled_schema):
2065
+ type_schema: Type[Set[object]]
2062
2066
  schema: compiled_schema
2063
- schema_: set[object]
2067
+ schema_: Set[object]
2064
2068
 
2065
2069
  def __init__(
2066
- self, schema: set[object], _deferred_compiles: _mapping | None = None
2070
+ self,
2071
+ schema: Set[object],
2072
+ _deferred_compiles: _mapping | None = None,
2067
2073
  ) -> None:
2074
+ self.type_schema = type(schema)
2068
2075
  self.schema_ = schema
2069
2076
  if len(schema) == 0:
2070
- self.schema = _const(set())
2071
2077
  setattr(self, "__validate__", self.__validate_empty_set__)
2072
2078
  elif len(schema) == 1:
2073
2079
  self.schema = _compile(
@@ -2084,7 +2090,11 @@ class _set(compiled_schema):
2084
2090
  strict: bool = True,
2085
2091
  subs: Mapping[str, object] = {},
2086
2092
  ) -> str:
2087
- 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 ""
2088
2098
 
2089
2099
  def __validate_singleton__(
2090
2100
  self,
@@ -2094,7 +2104,7 @@ class _set(compiled_schema):
2094
2104
  subs: Mapping[str, object] = {},
2095
2105
  ) -> str:
2096
2106
  if not isinstance(object_, set):
2097
- return _wrong_type_message(object_, name, "set")
2107
+ return _wrong_type_message(object_, name, self.type_schema.__name__)
2098
2108
  for i, o in enumerate(object_):
2099
2109
  name_ = f"{name}{{{i}}}"
2100
2110
  v = self.schema.__validate__(o, name=name_, strict=True, subs=subs)
@@ -2109,8 +2119,8 @@ class _set(compiled_schema):
2109
2119
  strict: bool = True,
2110
2120
  subs: Mapping[str, object] = {},
2111
2121
  ) -> str:
2112
- if not isinstance(object_, set):
2113
- 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__)
2114
2124
  for i, o in enumerate(object_):
2115
2125
  name_ = f"{name}{{{i}}}"
2116
2126
  v = self.schema.__validate__(o, name=name_, strict=True, subs=subs)
@@ -2182,24 +2192,6 @@ class _Union(compiled_schema):
2182
2192
  )
2183
2193
 
2184
2194
 
2185
- class _List(compiled_schema):
2186
- def __init__(
2187
- self,
2188
- schema: object,
2189
- type_schema: type | None = None,
2190
- _deferred_compiles: _mapping | None = None,
2191
- ) -> None:
2192
- setattr(
2193
- self,
2194
- "__validate__",
2195
- _sequence(
2196
- [schema, ...],
2197
- type_schema=type_schema,
2198
- _deferred_compiles=_deferred_compiles,
2199
- ).__validate__,
2200
- )
2201
-
2202
-
2203
2195
  class _Tuple(compiled_schema):
2204
2196
  def __init__(
2205
2197
  self, schema: tuple[object, ...], _deferred_compiles: _mapping | None = None
@@ -2211,21 +2203,89 @@ class _Tuple(compiled_schema):
2211
2203
  )
2212
2204
 
2213
2205
 
2214
- 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
+
2215
2212
  def __init__(
2216
2213
  self,
2217
2214
  schema: tuple[object, ...],
2218
- type_schema: type | None = None,
2215
+ type_schema: type,
2219
2216
  _deferred_compiles: _mapping | None = None,
2220
2217
  ) -> None:
2218
+ if len(schema) != 2:
2219
+ raise SchemaError("Number of arguments of mapping is not two")
2221
2220
  k, v = schema
2222
- setattr(
2223
- self,
2224
- "__validate__",
2225
- _dict(
2226
- {k: v}, type_schema=type_schema, _deferred_compiles=_deferred_compiles
2227
- ).__validate__,
2228
- )
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 ""
2229
2289
 
2230
2290
 
2231
2291
  class _NewType(compiled_schema):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vtjson
3
- Version: 2.1.5
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
@@ -168,11 +168,11 @@ A schema can be, in order of precedence:
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.
@@ -8,6 +8,7 @@ from collections.abc import Mapping, Sequence
8
8
  from datetime import datetime, timezone
9
9
  from typing import (
10
10
  Any,
11
+ Container,
11
12
  Dict,
12
13
  Generator,
13
14
  List,
@@ -1082,10 +1083,30 @@ class TestValidation(unittest.TestCase):
1082
1083
  vtjson.supports_Generic_ABC,
1083
1084
  "Generic base classes were introduced in Pythin 3.9",
1084
1085
  )
1085
- def test_Sequence(self) -> None:
1086
+ def test_Container(self) -> None:
1086
1087
 
1087
1088
  T = TypeVar("T")
1088
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
+
1089
1110
  class dummy(Sequence[T]):
1090
1111
  L: Sequence[T]
1091
1112
 
@@ -1974,6 +1995,11 @@ class TestValidation(unittest.TestCase):
1974
1995
  "Parametrized types were introduced in Python 3.9",
1975
1996
  )
1976
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_)
1977
2003
  schema = list[str]
1978
2004
  validate(schema, ["a", "b"])
1979
2005
  with self.assertRaises(ValidationError) as mc:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes