vtjson 2.1.2__tar.gz → 2.1.4__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.2
3
+ Version: 2.1.4
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
@@ -31,7 +31,7 @@ The following conventions are used:
31
31
  - As in typescript, a (string) key ending in `?` represents an optional key. The corresponding schema (the item the key points to) will only be used for validation when the key is present in the object that should be validated. A key can also be made optional by wrapping it as `optional_key(key)`.
32
32
  - If in a list/tuple the last entry is `...` (ellipsis) it means that the next to last entry will be repeated zero or more times. In this way generic types can be created. For example the schema `[str, ...]` represents a list of strings.
33
33
 
34
- As of version 2.1, a suitable adapted `vtjson` schema can be used as a Python type hint. Here is the above [example](https://github.com/vdbergh/vtjson/blob/main/docs/example2.md) rewritten in a way that is compatible with type hints. E.g. if one wants to ensure that a run object obtained via an api has the correct type one can do
34
+ As of version 2.1, a suitable adapted `vtjson` schema can be used as a Python type hint. Here is the above [example](https://raw.githubusercontent.com/vdbergh/vtjson/refs/heads/main/docs/example2.md) rewritten in a way that is compatible with type hints. E.g. if one wants to ensure that a run object obtained via an api has the correct type one can do
35
35
 
36
36
  ```python
37
37
  from typing import assert_type
@@ -70,8 +70,8 @@ A wrapper takes one or more schemas as arguments and produces a new schema.
70
70
  - An object matches the schema `complement(schema)` if it does not match `schema`.
71
71
  - An object matches the schema `lax(schema)` if it matches `schema` when validated with `strict=False`.
72
72
  - An object matches the schema `strict(schema)` if it matches `schema` when validated with `strict=True`.
73
- - An object matches the schema `set_name(schema, name)` if it matches `schema`. But the `name` argument will be used in non-validation messages.
74
- - An object matches the schema `quote(schema)` if it is equal to `schema`. For example the schema `str` matches strings but the schema `quote(str)` matches the object `str`.
73
+ - An object matches the schema `set_name(schema, name, reason=False)` if it matches `schema`, but the `name` argument will be used in non-validation messages. Unless `reason` is `True` the original non-validation message will be suppressed.
74
+ - An object matches the schema `protocol(schema, dict=False)` if `schema` is a class and its fields are annotated with schemas which validate the corresponding fields in the object. If `dict` is `True` then the object is validated as a `dict`.
75
75
  - An object matches the schema `set_label(schema, label1, ..., labelN, debug=False)` if it matches `schema`, unless the schema is replaced by a different one via the `subs` argument to `validate`. If the optional argument `debug` is `True` then a message will be printed on the console if the schema was changed.
76
76
 
77
77
  ## Built-ins
@@ -190,7 +190,7 @@ A consequence of this algorithm is that non-const keys are automatically optiona
190
190
 
191
191
  ```python
192
192
  Annotated, dict[...], Dict[...], list[...], List[...], tuple[...], Tuple[...],
193
- Protocol, Literal, NewType, TypedDict, Union (or the equivalent operator |).
193
+ Protocol, NamedTuple, Literal, NewType, TypedDict, Union (or the equivalent operator |).
194
194
  ```
195
195
 
196
196
  For example `dict[str, str]` is translated internally into the schema `{str: str}`. See below for more information.
@@ -256,6 +256,16 @@ Note that Python imposes strong restrictions on what constitutes a valid type hi
256
256
 
257
257
  internally becomes `fields({"title": str, "price": float})`.
258
258
 
259
+ - `NamedTuple`. A `NamedTuple` class is translated as the intersection of a `tuple` schema and a fields schema. E.g.
260
+
261
+ ```python
262
+ class Movie(NamedTuple):
263
+ title: str
264
+ price: float
265
+ ```
266
+
267
+ internally becomes `intersect(tuple, fields({"title": str, "price": float}))`.
268
+
259
269
  - `Annotated` has already been discussed. It is translated into a suitable `intersect` schema. The handling of `Annotated` schemas can be influenced by `Apply` objects (see below).
260
270
 
261
271
  - `NewType` is translated into a `set_name` schema. E.g. `NewType('Movie', str)` becomes `set_name(str, 'Movie')`
@@ -11,7 +11,7 @@ The following conventions are used:
11
11
  - As in typescript, a (string) key ending in `?` represents an optional key. The corresponding schema (the item the key points to) will only be used for validation when the key is present in the object that should be validated. A key can also be made optional by wrapping it as `optional_key(key)`.
12
12
  - If in a list/tuple the last entry is `...` (ellipsis) it means that the next to last entry will be repeated zero or more times. In this way generic types can be created. For example the schema `[str, ...]` represents a list of strings.
13
13
 
14
- As of version 2.1, a suitable adapted `vtjson` schema can be used as a Python type hint. Here is the above [example](https://github.com/vdbergh/vtjson/blob/main/docs/example2.md) rewritten in a way that is compatible with type hints. E.g. if one wants to ensure that a run object obtained via an api has the correct type one can do
14
+ As of version 2.1, a suitable adapted `vtjson` schema can be used as a Python type hint. Here is the above [example](https://raw.githubusercontent.com/vdbergh/vtjson/refs/heads/main/docs/example2.md) rewritten in a way that is compatible with type hints. E.g. if one wants to ensure that a run object obtained via an api has the correct type one can do
15
15
 
16
16
  ```python
17
17
  from typing import assert_type
@@ -50,8 +50,8 @@ A wrapper takes one or more schemas as arguments and produces a new schema.
50
50
  - An object matches the schema `complement(schema)` if it does not match `schema`.
51
51
  - An object matches the schema `lax(schema)` if it matches `schema` when validated with `strict=False`.
52
52
  - An object matches the schema `strict(schema)` if it matches `schema` when validated with `strict=True`.
53
- - An object matches the schema `set_name(schema, name)` if it matches `schema`. But the `name` argument will be used in non-validation messages.
54
- - An object matches the schema `quote(schema)` if it is equal to `schema`. For example the schema `str` matches strings but the schema `quote(str)` matches the object `str`.
53
+ - An object matches the schema `set_name(schema, name, reason=False)` if it matches `schema`, but the `name` argument will be used in non-validation messages. Unless `reason` is `True` the original non-validation message will be suppressed.
54
+ - An object matches the schema `protocol(schema, dict=False)` if `schema` is a class and its fields are annotated with schemas which validate the corresponding fields in the object. If `dict` is `True` then the object is validated as a `dict`.
55
55
  - An object matches the schema `set_label(schema, label1, ..., labelN, debug=False)` if it matches `schema`, unless the schema is replaced by a different one via the `subs` argument to `validate`. If the optional argument `debug` is `True` then a message will be printed on the console if the schema was changed.
56
56
 
57
57
  ## Built-ins
@@ -170,7 +170,7 @@ A consequence of this algorithm is that non-const keys are automatically optiona
170
170
 
171
171
  ```python
172
172
  Annotated, dict[...], Dict[...], list[...], List[...], tuple[...], Tuple[...],
173
- Protocol, Literal, NewType, TypedDict, Union (or the equivalent operator |).
173
+ Protocol, NamedTuple, Literal, NewType, TypedDict, Union (or the equivalent operator |).
174
174
  ```
175
175
 
176
176
  For example `dict[str, str]` is translated internally into the schema `{str: str}`. See below for more information.
@@ -236,6 +236,16 @@ Note that Python imposes strong restrictions on what constitutes a valid type hi
236
236
 
237
237
  internally becomes `fields({"title": str, "price": float})`.
238
238
 
239
+ - `NamedTuple`. A `NamedTuple` class is translated as the intersection of a `tuple` schema and a fields schema. E.g.
240
+
241
+ ```python
242
+ class Movie(NamedTuple):
243
+ title: str
244
+ price: float
245
+ ```
246
+
247
+ internally becomes `intersect(tuple, fields({"title": str, "price": float}))`.
248
+
239
249
  - `Annotated` has already been discussed. It is translated into a suitable `intersect` schema. The handling of `Annotated` schemas can be influenced by `Apply` objects (see below).
240
250
 
241
251
  - `NewType` is translated into a `set_name` schema. E.g. `NewType('Movie', str)` becomes `set_name(str, 'Movie')`
@@ -115,7 +115,7 @@ class SchemaError(Exception):
115
115
  pass
116
116
 
117
117
 
118
- __version__ = "2.1.2"
118
+ __version__ = "2.1.4"
119
119
 
120
120
 
121
121
  @dataclass
@@ -152,9 +152,10 @@ def _get_type_hints(schema: object) -> dict[str, object]:
152
152
  raise SchemaError(
153
153
  "Structural subtyping in not supported in this " "Python version"
154
154
  )
155
- type_hints = {}
156
155
  if isinstance(schema, type) and hasattr(schema, "__annotations__"):
157
156
  type_hints = typing.get_type_hints(schema, include_extras=True)
157
+ else:
158
+ raise SchemaError("The schema does not have type hints")
158
159
  return type_hints
159
160
 
160
161
 
@@ -1110,11 +1111,19 @@ def _compile(
1110
1111
  ret = schema
1111
1112
  elif supports_TypedDict and typing.is_typeddict(schema):
1112
1113
  ret = _compile(
1113
- structural(schema, dict=True), _deferred_compiles=_deferred_compiles
1114
+ protocol(schema, dict=True), _deferred_compiles=_deferred_compiles
1114
1115
  )
1115
1116
  elif isinstance(schema, type) and hasattr(schema, "_is_protocol"):
1116
1117
  assert hasattr(schema, "__name__") and isinstance(schema.__name__, str)
1117
- ret = _compile(structural(schema), _deferred_compiles=_deferred_compiles)
1118
+ ret = _compile(protocol(schema), _deferred_compiles=_deferred_compiles)
1119
+ elif (
1120
+ isinstance(schema, type)
1121
+ and issubclass(schema, tuple)
1122
+ and hasattr(schema, "_fields")
1123
+ ):
1124
+ ret = _compile(
1125
+ intersect(tuple, protocol(schema)), _deferred_compiles=_deferred_compiles
1126
+ )
1118
1127
  elif schema == Any:
1119
1128
  ret = anything()
1120
1129
  elif hasattr(schema, "__name__") and hasattr(schema, "__supertype__"):
@@ -2087,7 +2096,7 @@ class _set(compiled_schema):
2087
2096
  return str(self.schema_)
2088
2097
 
2089
2098
 
2090
- class structural:
2099
+ class protocol:
2091
2100
  type_dict: dict[object, object]
2092
2101
  dict: bool
2093
2102
  __name__: str
@@ -2101,8 +2110,10 @@ class structural:
2101
2110
  if hasattr(schema, "__total__") and isinstance(schema.__total__, bool):
2102
2111
  total = schema.__total__
2103
2112
  self.type_dict = _to_dict(type_hints, total=total)
2104
- assert hasattr(schema, "__name__") and isinstance(schema.__name__, str)
2105
- self.__name__ = schema.__name__
2113
+ if hasattr(schema, "__name__") and isinstance(schema.__name__, str):
2114
+ self.__name__ = schema.__name__
2115
+ else:
2116
+ self.__name__ = "schema"
2106
2117
 
2107
2118
  def __compile__(
2108
2119
  self, _deferred_compiles: _mapping | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vtjson
3
- Version: 2.1.2
3
+ Version: 2.1.4
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
@@ -31,7 +31,7 @@ The following conventions are used:
31
31
  - As in typescript, a (string) key ending in `?` represents an optional key. The corresponding schema (the item the key points to) will only be used for validation when the key is present in the object that should be validated. A key can also be made optional by wrapping it as `optional_key(key)`.
32
32
  - If in a list/tuple the last entry is `...` (ellipsis) it means that the next to last entry will be repeated zero or more times. In this way generic types can be created. For example the schema `[str, ...]` represents a list of strings.
33
33
 
34
- As of version 2.1, a suitable adapted `vtjson` schema can be used as a Python type hint. Here is the above [example](https://github.com/vdbergh/vtjson/blob/main/docs/example2.md) rewritten in a way that is compatible with type hints. E.g. if one wants to ensure that a run object obtained via an api has the correct type one can do
34
+ As of version 2.1, a suitable adapted `vtjson` schema can be used as a Python type hint. Here is the above [example](https://raw.githubusercontent.com/vdbergh/vtjson/refs/heads/main/docs/example2.md) rewritten in a way that is compatible with type hints. E.g. if one wants to ensure that a run object obtained via an api has the correct type one can do
35
35
 
36
36
  ```python
37
37
  from typing import assert_type
@@ -70,8 +70,8 @@ A wrapper takes one or more schemas as arguments and produces a new schema.
70
70
  - An object matches the schema `complement(schema)` if it does not match `schema`.
71
71
  - An object matches the schema `lax(schema)` if it matches `schema` when validated with `strict=False`.
72
72
  - An object matches the schema `strict(schema)` if it matches `schema` when validated with `strict=True`.
73
- - An object matches the schema `set_name(schema, name)` if it matches `schema`. But the `name` argument will be used in non-validation messages.
74
- - An object matches the schema `quote(schema)` if it is equal to `schema`. For example the schema `str` matches strings but the schema `quote(str)` matches the object `str`.
73
+ - An object matches the schema `set_name(schema, name, reason=False)` if it matches `schema`, but the `name` argument will be used in non-validation messages. Unless `reason` is `True` the original non-validation message will be suppressed.
74
+ - An object matches the schema `protocol(schema, dict=False)` if `schema` is a class and its fields are annotated with schemas which validate the corresponding fields in the object. If `dict` is `True` then the object is validated as a `dict`.
75
75
  - An object matches the schema `set_label(schema, label1, ..., labelN, debug=False)` if it matches `schema`, unless the schema is replaced by a different one via the `subs` argument to `validate`. If the optional argument `debug` is `True` then a message will be printed on the console if the schema was changed.
76
76
 
77
77
  ## Built-ins
@@ -190,7 +190,7 @@ A consequence of this algorithm is that non-const keys are automatically optiona
190
190
 
191
191
  ```python
192
192
  Annotated, dict[...], Dict[...], list[...], List[...], tuple[...], Tuple[...],
193
- Protocol, Literal, NewType, TypedDict, Union (or the equivalent operator |).
193
+ Protocol, NamedTuple, Literal, NewType, TypedDict, Union (or the equivalent operator |).
194
194
  ```
195
195
 
196
196
  For example `dict[str, str]` is translated internally into the schema `{str: str}`. See below for more information.
@@ -256,6 +256,16 @@ Note that Python imposes strong restrictions on what constitutes a valid type hi
256
256
 
257
257
  internally becomes `fields({"title": str, "price": float})`.
258
258
 
259
+ - `NamedTuple`. A `NamedTuple` class is translated as the intersection of a `tuple` schema and a fields schema. E.g.
260
+
261
+ ```python
262
+ class Movie(NamedTuple):
263
+ title: str
264
+ price: float
265
+ ```
266
+
267
+ internally becomes `intersect(tuple, fields({"title": str, "price": float}))`.
268
+
259
269
  - `Annotated` has already been discussed. It is translated into a suitable `intersect` schema. The handling of `Annotated` schemas can be influenced by `Apply` objects (see below).
260
270
 
261
271
  - `NewType` is translated into a `set_name` schema. E.g. `NewType('Movie', str)` becomes `set_name(str, 'Movie')`
@@ -5,7 +5,7 @@ import re
5
5
  import sys
6
6
  import unittest
7
7
  from datetime import datetime, timezone
8
- from typing import Any, Dict, List, NewType, Tuple, Union
8
+ from typing import Any, Dict, List, NamedTuple, NewType, Tuple, Union
9
9
  from urllib.parse import urlparse
10
10
 
11
11
  import vtjson
@@ -79,6 +79,7 @@ from vtjson import (
79
79
  number,
80
80
  one_of,
81
81
  optional_key,
82
+ protocol,
82
83
  quote,
83
84
  regex,
84
85
  safe_cast,
@@ -86,7 +87,6 @@ from vtjson import (
86
87
  set_name,
87
88
  size,
88
89
  strict,
89
- structural,
90
90
  time,
91
91
  union,
92
92
  url,
@@ -2045,7 +2045,39 @@ class TestValidation(unittest.TestCase):
2045
2045
 
2046
2046
  validate(dummy, w())
2047
2047
 
2048
- def test_structural(self) -> None:
2048
+ def test_NamedTuple(self) -> None:
2049
+ class dummy(NamedTuple):
2050
+ b: int = 0
2051
+ c: str = ""
2052
+
2053
+ def f(self, i: float) -> bool:
2054
+ return i == i
2055
+
2056
+ class x(NamedTuple):
2057
+ b: str = ""
2058
+ c: str = ""
2059
+
2060
+ if not vtjson.supports_structural:
2061
+ with self.assertRaises(SchemaError) as mc_:
2062
+ compile(dummy)
2063
+ show(mc_)
2064
+ return
2065
+
2066
+ with self.assertRaises(ValidationError) as mc:
2067
+ validate(dummy, x())
2068
+ show(mc)
2069
+ self.assertTrue("dummy" in str(mc.exception))
2070
+
2071
+ class w(NamedTuple):
2072
+ b: int = 1
2073
+ c: str = ""
2074
+
2075
+ def g(self) -> bool:
2076
+ return True
2077
+
2078
+ validate(dummy, w())
2079
+
2080
+ def test_protocol(self) -> None:
2049
2081
  class dummy:
2050
2082
  b: int = 0
2051
2083
  c: str = ""
@@ -2055,11 +2087,19 @@ class TestValidation(unittest.TestCase):
2055
2087
 
2056
2088
  if not vtjson.supports_structural:
2057
2089
  with self.assertRaises(SchemaError) as mc_:
2058
- schema = structural(dummy)
2090
+ schema = protocol(dummy)
2059
2091
  show(mc_)
2060
2092
  return
2061
2093
 
2062
- schema = structural(dummy)
2094
+ with self.assertRaises(SchemaError) as mc_:
2095
+ schema = protocol(dummy, dict=None) # type: ignore
2096
+ show(mc_)
2097
+
2098
+ with self.assertRaises(SchemaError) as mc_:
2099
+ schema = protocol({})
2100
+ show(mc_)
2101
+
2102
+ schema = protocol(dummy)
2063
2103
 
2064
2104
  class x:
2065
2105
  b: str = ""
@@ -2079,13 +2119,29 @@ class TestValidation(unittest.TestCase):
2079
2119
 
2080
2120
  validate(schema, w())
2081
2121
 
2082
- schema = structural(dummy, dict=True)
2122
+ schema = protocol(dummy, dict=True)
2083
2123
  validate(schema, {"b": 1, "c": ""})
2084
2124
 
2085
2125
  with self.assertRaises(ValidationError):
2086
2126
  validate(schema, w())
2087
2127
  show(mc)
2088
2128
 
2129
+ if sys.version_info >= (3, 11):
2130
+
2131
+ class dummy2:
2132
+ # oh, horror
2133
+ a: {"b": int} # type: ignore # noqa: F821
2134
+
2135
+ class u:
2136
+ def __init__(self, v: object) -> None:
2137
+ self.a = {"b": v}
2138
+
2139
+ validate(protocol(dummy2), u(5))
2140
+
2141
+ with self.assertRaises(ValidationError) as mc:
2142
+ validate(protocol(dummy2), u(""))
2143
+ show(mc)
2144
+
2089
2145
 
2090
2146
  if __name__ == "__main__":
2091
2147
  unittest.main(verbosity=2)
File without changes
File without changes
File without changes
File without changes
File without changes