vtjson 2.1.8__py3-none-any.whl → 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.1
2
+ Name: vtjson
3
+ Version: 2.2.0
4
+ Summary: A lightweight package for validating JSON like Python objects
5
+ Author-email: Michel Van den Bergh <michel.vandenbergh@uhasselt.be>
6
+ Project-URL: Homepage, https://github.com/vdbergh/vtjson
7
+ Project-URL: Bug Tracker, https://github.com/vdbergh/vtjson/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ License-File: AUTHORS
15
+ Requires-Dist: dnspython
16
+ Requires-Dist: email_validator
17
+ Requires-Dist: idna
18
+ Requires-Dist: python-magic
19
+ Requires-Dist: typing_extensions
20
+
21
+ # vtjson
22
+
23
+ `vtjson` is an easy to use validation library compatible with Python type annotations.
24
+
25
+ ## Introduction
26
+
27
+ Here is a simple schema:
28
+
29
+ ```python
30
+ book_schema = {
31
+ "title": str,
32
+ "authors": [str, ...],
33
+ "editor?": str,
34
+ "year": int,
35
+ }
36
+ ```
37
+
38
+ The following conventions were used:
39
+
40
+ - 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 with `optional_key`.
41
+ - 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.
42
+
43
+ Let's try to validate some book objects:
44
+
45
+ ```python
46
+ good_book = {
47
+ "title": "Gone with the Wind",
48
+ "authors": ["Margaret Mitchell"],
49
+ "year": 1936,
50
+ }
51
+
52
+ bad_book = {
53
+ "title": "Gone with the Wind",
54
+ "authors": ["Margaret Mitchell"],
55
+ "year": "1936",
56
+ }
57
+
58
+ validate(book_schema, good_book, name="good_book")
59
+ validate(book_schema, bad_book, name="bad_book")
60
+ ```
61
+
62
+ As expected `vtjson` throws an exception for the second object:
63
+
64
+ ```text
65
+ Traceback (most recent call last):
66
+ ...
67
+ raise ValidationError(message)
68
+ vtjson.vtjson.ValidationError: bad_book['year'] (value:'1936') is not of type 'int'
69
+ ```
70
+
71
+ We may also rewrite the `book_schema` as a valid Python type annotation.
72
+
73
+ ```python
74
+ class book_schema(TypedDict):
75
+ title: str
76
+ authors: list[str]
77
+ editor: NotRequired[str]
78
+ year: int
79
+ ```
80
+
81
+ Attempting to validate the bad book would raise the same exception as before.
82
+
83
+ Schemas can of course be more complicated and in particular they can be nested.
84
+ Here is an example that shows more of the features of `vtjson`.
85
+
86
+ ```python
87
+ person_schema = {
88
+ "name": regex("[a-zA-Z. ]*"),
89
+ "email?": email,
90
+ "website?": url,
91
+ }
92
+
93
+ book_schema = {
94
+ "title": str,
95
+ "authors": [person_schema, ...],
96
+ "editor?": person_schema,
97
+ "year": intersect(int, ge(1900)),
98
+ }
99
+ ```
100
+
101
+ Let's try to validate an object not fitting the schema.
102
+
103
+ ```python
104
+ bad_book = {
105
+ "title": "Gone with the Wind",
106
+ "authors": [{"name": "Margaret Mitchell", "email": "margaret@gmailcom"}],
107
+ "year": "1936",
108
+ }
109
+ ```
110
+
111
+ ```text
112
+ Traceback (most recent call last):
113
+ ...
114
+ raise ValidationError(message)
115
+ vtjson.vtjson.ValidationError: bad_book['authors'][0]['email'] (value:'margaret@gmailcom') is not of type 'email': The part after the @-sign is not valid. It should have a period.
116
+ ```
117
+
118
+ As before we can rewrite the new `book_schema` as a valid type annotation.
119
+
120
+ ```python
121
+ class person_schema(TypedDict):
122
+ name: Annotated[str, regex("[a-zA-Z. ]*")]
123
+ email: NotRequired[Annotated[str, email]]
124
+ website: NotRequired[Annotated[str, url]]
125
+
126
+ class book_schema(TypedDict):
127
+ title: str
128
+ authors: list[person_schema]
129
+ editor: NotRequired[list[person_schema]]
130
+ year: Annotated[int, ge(1900)]
131
+ ```
132
+
133
+ For comprehensive documentation about `vtjson` see [https://www.cantate.be/vtjson](https://www.cantate.be/vtjson) (canonical reference) or [https://vtjson.readthedocs.io](https://vtjson.readthedocs.io).
@@ -0,0 +1,9 @@
1
+ vtjson/__init__.py,sha256=oLX4JH6_R7dYtTiGfBG3pQGR21IArspifdmZilbuGOw,68
2
+ vtjson/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ vtjson/vtjson.py,sha256=AT9Zr7Ypj6jdA0i60j6VAmYdSqrCTMs2V8lcODHJqFE,86276
4
+ vtjson-2.2.0.dist-info/AUTHORS,sha256=qmxaXxaIO-YPNHJAZ0dcCrnPCs1x9ocbtMksiy4i80M,21
5
+ vtjson-2.2.0.dist-info/LICENSE,sha256=n7xW-zX8xBLHzCdqWIMRuMzBD_ACLcNCwio0LEkKt1o,1077
6
+ vtjson-2.2.0.dist-info/METADATA,sha256=wlW8teJ7Jhv42iMx0yfOmyHlTip1_DtR5dC_KSaKEHo,3951
7
+ vtjson-2.2.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
8
+ vtjson-2.2.0.dist-info/top_level.txt,sha256=9DlSF3l63igcvnYPcj117F2hzOW4Nx0N-JBoW3jjBZM,7
9
+ vtjson-2.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,422 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: vtjson
3
- Version: 2.1.8
4
- Summary: A lightweight package for validating JSON like Python objects
5
- Author-email: Michel Van den Bergh <michel.vandenbergh@uhasselt.be>
6
- Project-URL: Homepage, https://github.com/vdbergh/vtjson
7
- Project-URL: Bug Tracker, https://github.com/vdbergh/vtjson/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- License-File: AUTHORS
15
- Requires-Dist: dnspython
16
- Requires-Dist: email-validator
17
- Requires-Dist: idna
18
- Requires-Dist: python-magic
19
- Requires-Dist: typing-extensions
20
-
21
- # vtjson
22
-
23
- A lightweight package for validating JSON like Python objects.
24
-
25
- ## Schemas
26
-
27
- Validation of JSON like Python objects is done according to a `schema` which is somewhat inspired by a typescript type. The format of a schema is more or less self explanatory. As an example one may consult the [schema of the run object](https://raw.githubusercontent.com/vdbergh/vtjson/refs/heads/main/docs/example1.md) in the mongodb database underlying the Fishtest web application <https://tests.stockfishchess.org/tests>.
28
-
29
- The following conventions are used:
30
-
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
- - 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
-
34
- As of version 2.1, a suitable adapted `vtjson` schema can be used as a Python type annotation. Here is the above example rewritten in a way that is [compatible with type annotations](https://raw.githubusercontent.com/vdbergh/vtjson/refs/heads/main/docs/example2.md). E.g. if one wants to ensure that a run object obtained via an api has the correct type one can do
35
-
36
- ```python
37
- from typing import assert_type
38
-
39
- def f(run_from_api: object, ...) -> ...:
40
- run = safe_cast(runs_schema, run_from_api)
41
- assert_type(run, runs_schema) # Confirm that run has indeed the correct type now
42
- ```
43
-
44
- If the cast succeeds then it means that the `run_from_api` object has been validated against the `runs_schema` and its type has been changed accordingly.
45
-
46
- ## Usage
47
-
48
- To validate an object against a schema one can simply do
49
-
50
- ```python
51
- validate(schema, object)
52
- ```
53
-
54
- If the validation fails this will throw a `ValidationError` and the exception contains an explanation about what went wrong. The full signature of `validate` is
55
-
56
- ```python
57
- validate(schema, object, name="object", strict=True, subs={})
58
- ```
59
-
60
- - The optional argument `name` is used to refer to the object being validated in the returned message.
61
- - The optional argument `strict` indicates whether or not the object being validated is allowed to have keys/entries which are not in the schema.
62
- - The optional argument `subs` is a dictionary whose keys are labels (see below) and whose values are substitution schemas for schemas with those labels.
63
-
64
- ## Wrappers
65
-
66
- A wrapper takes one or more schemas as arguments and produces a new schema.
67
-
68
- - An object matches the schema `union(schema1, ..., schemaN)` if it matches one of the schemas `schema1, ..., schemaN`.
69
- - An object matches the schema `intersect(schema1, ..., schemaN)` if it matches all the schemas `schema1, ..., schemaN`.
70
- - An object matches the schema `complement(schema)` if it does not match `schema`.
71
- - An object matches the schema `lax(schema)` if it matches `schema` when validated with `strict=False`.
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, 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
- - 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
-
77
- ## Built-ins
78
-
79
- Some built-ins take arguments. If no arguments are given then the parentheses can be omitted. So `email` is equivalent to `email()`. Some built-ins have an optional `name` argument. This is used in non-validation messages.
80
-
81
- - `regex(pattern, name=None, fullmatch=True, flags=0)`. This matches the strings which match the given pattern. By default the entire string is matched, but this can be overruled via the `fullmatch` argument. The `flags` argument has the usual meaning.
82
- - `glob(pattern, name=None)`. Unix style filename matching. This is implemented using `pathlib.PurePath().match()`.
83
- - `div(divisor, remainder=0, name=None)`. This matches the integers `x` such that `(x - remainder) % divisor` == 0.
84
- - `close_to(x, abs_tol=None, rel_tol=None)`. This matches the floats that are close to `x` in the sense of `math.isclose`.
85
- - `email`. Checks if the object is a valid email address. This uses the package `email_validator`. The `email` schema accepts the same options as `validate_email` in loc. cit.
86
- - `ip_address(version=None)`. Matches ip addresses of the specified version which can be 4, 6 or None.
87
- - `url`. Matches valid urls.
88
- - `domain_name(ascii_only=True, resolve=False)`. Checks if the object is a valid domain name. If `ascii_only=False` then allow IDNA domain names. If `resolve=True` check if the domain name resolves.
89
- - `date_time(format=None)`. Without argument this represents an ISO 8601 date-time. The `format` argument represents a format string for `strftime`.
90
- - `date` and `time`. These represent an ISO 8601 date and an ISO 8601 time.
91
- - `anything`. Matches anything. This is functionally the same as just `object`.
92
- - `nothing`. Matches nothing.
93
-
94
- ## Mixins
95
-
96
- Mixins are built-ins that are usually combined with other schemas using `intersect`.
97
-
98
- - `one_of(key1, ..., keyN)`. This represents a dictionary with exactly one key among `key1, ..., keyN`.
99
- - `at_least_one_of(key1, ..., keyN)`. This represents a dictionary with a least one key among `key1, ..., keyN`.
100
- - `at_most_one_of(key1, ..., keyN)`. This represents an dictionary with at most one key among `key1, ..., keyN`.
101
- - `keys(key1, ..., keyN)`. This represents a dictionary containing all the keys in `key1, ..., keyN`.
102
- - `interval(lb, ub, strict_lb=False, strict_ub=False)`. This checks if `lb <= object <= ub`, provided the comparisons make sense. An upper/lowerbound `...` (ellipsis) means that the corresponding inequality is not checked. The optional arguments `strict_lb`, `strict_ub` indicate whether the corresponding inequalities should be strict.
103
- - `gt(lb)`. This checks if `object > lb`.
104
- - `ge(lb)`. This checks if `object >= lb`.
105
- - `lt(ub)`. This checks if `object < ub`.
106
- - `le(ub)`. This checks if `object <= ub`.
107
- - `size(lb, ub=None)`. Matches the objects (which support `len()` such as strings or lists) whose length is in the interval `[lb, ub]`. The value of `ub` can be `...` (ellipsis). If `ub=None` then `ub` is set to `lb`.
108
- - `fields({field1: schema1, field2: schema2, ..., fieldN: schemaN})`. Matches Python objects with attributes `field1, field2, ..., fieldN` whose corresponding values should validate against `schema1, schema2, ..., schemaN` respectively.
109
- - `magic(mime_type, name=None)`. Checks if a buffer (for example a string or a byte array) has the given mime type. This is implemented using the `python-magic` package.
110
- - `filter(callable, schema, filter_name=None)`. Applies `callable` to the object and validates the result with `schema`. If the callable throws an exception then validation fails. The optional argument `filter_name` is used in non-validation messages.
111
-
112
- ## Conditional schemas
113
-
114
- - `ifthen(if_schema, then_schema, else_schema=None)`. If the object matches the `if_schema` then it should also match the `then_schema`. If the object does not match the `if_schema` then it should match the `else_schema`, if present.
115
- - `cond((if_schema1, then_schema1), ... , (if_schemaN, then_schemaN))`. An object is successively validated against `if_schema1`, `if_schema2`, ... until a validation succeeds. When this happens the object should match the corresponding `then_schema`. If no `if_schema` succeeds then the object is considered to have been validated. If one sets `if_schemaN` equal to `anything` then this serves as a catch all.
116
-
117
- ## Pre-compiling a schema
118
-
119
- An object matches the schema `compile(schema)` if it matches `schema`. `vtjson` compiles a schema before using it for validation, so pre-compiling is not necessary. However for large schemas it may gain some of performance as it needs to be done only once. Compiling is an idempotent operation. It does nothing for an already compiled schema.
120
-
121
- The full signature of `compile()` is
122
-
123
- ```python
124
- compile(schema)
125
- ```
126
-
127
- ## Schema format
128
-
129
- A schema can be, in order of precedence:
130
-
131
- - An instance of the class `compiled_schema`.
132
-
133
- The class `compiled_schema` defines a single method with signature
134
-
135
- ```python
136
- __validate__(self, object, name, strict, subs)
137
- ```
138
-
139
- The parameters of `__validate__()` have the same semantics as those of `validate()`. The return value of `__validate__()` should be the empty string if validation succeeds, and otherwise it should be an explanation about what went wrong.
140
-
141
- - A subclass of `compiled_schema` with a no-argument constructor.
142
-
143
- - An object having a `__validate__` attribute with signature
144
-
145
- ```python
146
- __validate__(object, name, strict, subs)
147
- ```
148
-
149
- as above.
150
- - An object having a `__compile__` attribute with signature
151
-
152
- ```python
153
- __compile__(_deferred_compiles=None)
154
- ```
155
-
156
- This is an advanced feature which is used for the implementation of wrapper schemas. The function `compile`, which was discussed above, internally invokes
157
-
158
- ```python
159
- _compile(schema, _deferred_compiles=None)
160
- ```
161
-
162
- where the optional argument `_deferred_compiles` is an opaque data structure used for handling recursive schemas. If appropriate, the function `_compile` internally invokes the method `schema.__compile__` and this should produce an instance of the class `compiled_schema`. The method `__compile__` may invoke the function `_compile` again. If this happens then the optional argument `_deferred_compiles` should be passed unmodified. Please consult the source code of `vtjson` for more details.
163
- - A Python type annotation such as `list[str]`. This is discussed further below.
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
- - 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
- - 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
- - 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
- - 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
-
171
- ## Validating against Mapping schemas
172
-
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
-
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
- - 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
- - 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
- - The object will pass validation if all its keys pass validation. We next discuss how to validate a particular key of the object.
179
- - If none of the entries of the key list validate the given key and `strict==True` (the default) then the key fails validation. If on the other hand `strict==False` then the key passes.
180
- - Assuming the fate of the given key hasn't been decided yet, we now match it against all entries of the key list. If it matches an entry and the corresponding value also validates then the key is validated. Otherwise we keep going through the key list.
181
- - If the entire key list is consumed then the key fails validation.
182
-
183
- A consequence of this algorithm is that non-const keys are automatically optional. So applying the wrapper `optional_key` to them is meaningless and has no effect.
184
-
185
- ## Type annotations integration
186
-
187
- ### Type annotations as schemas
188
-
189
- `vtjson` recognizes the following type annotations as schemas.
190
-
191
- ```python
192
- Annotated, Mapping[...,...] and subtypes, Container[...] and subtypes,
193
- tuple[...], Tuple[...], Protocol, NamedTuple, Literal, NewType, TypedDict,
194
- Union (or the equivalent operator |), Any.
195
- ```
196
-
197
- For example `dict[str, str]` is translated internally into the schema `{str: str}`. See below for more information.
198
-
199
- ### Annotated
200
-
201
- - More general vtjson schemas can work along Python type annotations by using the `typing.Annotated` contruct. The most naive way to do this is via
202
-
203
- ```python
204
- Annotated[type_annotation, vtjson_schema, skip_first]
205
- ```
206
-
207
- For example
208
-
209
- ```python
210
- Annotated[list[object], [int, str, float], skip_first]
211
- ```
212
-
213
- A type checker such as `mypy` will only see the type annotation (`list[object]` in the example), whereas vtjson will only see the vtjson schema (`[int, str, float]` in the example). `skip_first` is a built-in short hand for `Apply(skip_first=True)` (see below) which directs vtjson to ignore the first argument of an `Annotated` schema.
214
- - In some use cases a vtjon_schema will meaningfully refine a Python type or type annotation. In that case one should not use `skip_first`. For example:
215
-
216
- ```python
217
- Annotated[datetime, fields({"tzinfo": timezone.utc})]
218
- ```
219
-
220
- defines a `datetime` object whose time zone is `utc`.
221
-
222
- The built-in schemas already check that an object has the correct type. So for those one should use `skip_first`. For example:
223
-
224
- ```python
225
- Annotated[int, div(2), skip_first]
226
- ```
227
-
228
- matches even integers.
229
- - If one wants to pre-compile a schema and still use it as a type annotation (assuming it is valid as such) then one can do:
230
-
231
- ```python
232
- schema = <schema definition>
233
- Schema = Annotated[schema, compile(schema), skip_first]
234
- ```
235
-
236
- ### Supported type annotations
237
-
238
- Note that Python imposes strong restrictions on what constitutes a valid type annotation but `vtjson` is much more lax about this. Enforcing the restrictions is left to the type checkers or the Python interpreter.
239
-
240
- - `TypedDict`. A TypedDict type annotation is translated into a `dict` schema. E.g.
241
-
242
- ```python
243
- class Movie(TypedDict):
244
- title: str
245
- price: float
246
- ```
247
-
248
- internally becomes `{"title": str, "price": float}`. `vtjson` supports the `total` option to `TypedDict` as well as the `Required` and `NotRequired` annotations of fields, if they are compatible with the Python version being used.
249
-
250
- - `Protocol`. A class implementing a protocol is translated into a `fields` schema. E.g.
251
-
252
- ```python
253
- class Movie(Protocol):
254
- title: str
255
- price: float
256
- ```
257
-
258
- internally becomes `fields({"title": str, "price": float})`.
259
-
260
- - `NamedTuple`. A `NamedTuple` class is translated as the intersection of a `tuple` schema and a fields schema. E.g.
261
-
262
- ```python
263
- class Movie(NamedTuple):
264
- title: str
265
- price: float
266
- ```
267
-
268
- internally becomes `intersect(tuple, fields({"title": str, "price": float}))`.
269
-
270
- - `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).
271
-
272
- - `NewType` is translated into a `set_name` schema. E.g. `NewType('Movie', str)` becomes `set_name(str, 'Movie')`
273
-
274
- - `tuple[...]` and `Tuple[...]` are translated into the equivalent `tuple` schemas.
275
-
276
- - `Mapping[S, T]` and subtypes validate those objects that are members of the origin type (a subclass of `Mapping`) and whose (key, value) pairs match `(S, T)`.
277
-
278
- - `Container[T]` and subtypes validate those objects that are members of the origin type (a subclass of `Container`) and whose elements match `T`.
279
-
280
- - `Union` and the `|` operator are translated into `union`.
281
-
282
- - `Literal` is also translated into `union`.
283
-
284
- - `Any` is translated into `anything`.
285
-
286
- ### Apply objects
287
-
288
- - If the list of arguments of an Annotated schema includes Apply objects then those modify the treatement of the arguments that come before them. We already encountered `skip_first` which is a built-in alias for `Apply(skip_first=True)`. The full signature of `Apply` is
289
-
290
- ```python
291
- Apply(skip_first=False, name=None, labels=None)
292
- ```
293
-
294
- The optional `name` argument indicates that the corresponding `set_name` command should be applied to the previous arguments. The optional `labels` argument (a list if present) indicates that the corresponding `set_label` command should be applied to the previous arguments.
295
-
296
- - Multiple `Apply` objects are allowed. E.g. the following contrived schema
297
-
298
- ```python
299
- Annotated[int, str, skip_first, float, skip_first]
300
- ```
301
-
302
- is equivalent to `float`.
303
-
304
- ### Safe cast
305
-
306
- Vtjson includes the command
307
-
308
- ```python
309
- safe_cast(schema, object)
310
- ```
311
-
312
- (where `schema` should be a valid type annotation) that functions exactly like `cast` except that it also verifies at run time that the given object matches the given schema.
313
-
314
- ## Creating types
315
-
316
- A cool feature of `vtjson` is that one can transform a schema into a genuine Python type via
317
-
318
- ```python
319
- t = make_type(schema)
320
- ```
321
-
322
- so that validation can be done via
323
-
324
- ```python
325
- isinstance(object, t)
326
- ```
327
-
328
- The drawback, compared to using `validate` directly, is that there is no feedback when validation fails. You can get it back as a console debug message via the optional `debug` argument to `make_type`.
329
- The full signature of `make_type` is
330
-
331
- ```python
332
- make_type(schema, name=None, strict=True, debug=False, subs={})
333
- ```
334
-
335
- The optional `name` argument is used to set the `__name__` attribute of the type. If it is not supplied then `vtjson` tries to make an educated guess.
336
-
337
- ## Examples
338
-
339
- ```python
340
- >>> from vtjson import set_name, union, validate
341
- >>> schema = {"fruit" : union("apple", "pear", "strawberry"), "price" : float}
342
- >>> object = {"fruit" : "dog", "price": 1.0 }
343
- >>> validate(schema, object)
344
- ...
345
- vtjson.ValidationError: object['fruit'] (value:'dog') is not equal to 'pear' and object['fruit'] (value:'dog') is not equal to 'strawberry' and object['fruit'] (value:'dog') is not equal to 'apple'
346
- >>> fruit = set_name(union("apple", "pear", "strawberry"), "fruit")
347
- >>> schema = {"fruit" : fruit, "price" : float}
348
- >>> validate(schema, object)
349
- ...
350
- vtjson.ValidationError: object['fruit'] (value:'dog') is not of type 'fruit'
351
- >>> object = {"fruit" : "apple"}
352
- >>> validate(schema, object)
353
-
354
- ...
355
- vtjson.ValidationError: object['price'] is missing
356
- ```
357
-
358
- A good source of more advanced examples is the file [`schemas.py`](https://raw.githubusercontent.com/official-stockfish/fishtest/master/server/fishtest/schemas.py) in the source distribution of Fishtest. Another source of examples is the file [`test_validate.py`](https://raw.githubusercontent.com/vdbergh/vtjson/main/test_validate.py) in the source distribution of `vtjson`.
359
-
360
- ## FAQ
361
-
362
- Q: Why not just use the Python implementation of `JSON schema` (see <https://pypi.org/project/jsonschema/>)?
363
-
364
- A: Various reasons.
365
-
366
- - A `vtjson` schema is much more concise than a `JSON` schema!
367
- - `vtjson` can validate objects which are more general than strictly `JSON`. See the introductory example above.
368
- - More fundamentally, the design philosophy of `vtsjon` is different. A `JSON` schema is language independent and fully declarative. These are very nice properties but, this being said, declarative languages have a tendency to suffer from feature creep as they try to deal with more and more exotic use cases (e.g. `css`). A `vtjson` schema on the other hand leverages the versatility of the Python language. It is generally declarative, with a limited, but easily extendable set of primitives. But if more functionality is needed then it can be extended by using appropriate bits of Python code (as the `ordered_pair` example below illustrates). In practice this is what you will need in any case since a purely declarative language will never be able to deal with every possible validation scenario.
369
-
370
- Q: Why yet another Python validation framework?
371
-
372
- A: Good question! Initially `vtjson` consisted of home grown code for validating api calls and database accesses in the Fishtest framework. However the clear and concise schema format seemed to be of independent interest and so the code was refactored into the current self-contained package.
373
-
374
- Q: Why are there no variables in `vtjson` (see <https://opis.io/json-schema/2.x/variables.html>)?
375
-
376
- A: They did not seem to be essential yet. In our use cases conditional schemas were sufficient to achieve the required functionality. See for example the `action_schema` in [`schemas.py`](https://raw.githubusercontent.com/official-stockfish/fishtest/master/server/fishtest/schemas.py). More importantly `vtjson` has a strict separation between the definition of a schema and its subsequent use for validation. By allowing a schema to refer directly to the object being validated this separation would become blurred. This being said, I am still thinking about a good way to introduce variables.
377
-
378
- Q: Does `vtjson` support recursive schemas?
379
-
380
- A: Yes. But it requires a bit of Python gymnastics to create them. Here is an example
381
-
382
- ```python
383
- person={}
384
- person["mother"]=union(person, None)
385
- person["father"]=union(person, None)
386
- ```
387
-
388
- which matches e.g.
389
-
390
- ```python
391
- {"father": {"father": None, "mother": None}, "mother": {"father": None, "mother": None}}
392
- ```
393
-
394
- Note that you can create an infinite recursion by validating a recursive object against a recursive schema.
395
-
396
- Q: How to combine validations?
397
-
398
- A: Use `intersect` (or `Annotated` if applicable). For example the following schema validates positive integers but reject positive floats.
399
-
400
- ```python
401
- schema = intersect(int, interval(0, ...))
402
- ```
403
-
404
- More generally one may use the pattern `intersect(schema, more_validations)` where the first argument makes sure that the object to be validated has the required layout to be an acceptable input for the later arguments. For example an ordered pair of integers can be validated using the schema
405
-
406
- ```python
407
- def ordered_pair(o):
408
- return o[0] <= o[1]
409
- schema = intersect((int, int), ordered_pair)
410
- ```
411
-
412
- Or in a one liner
413
-
414
- ```python
415
- schema = intersect((int, int), set_name(lambda o: o[0] <= o[1], "ordered_pair"))
416
- ```
417
-
418
- The following also works if you are content with less nice output on validation failure (try it)
419
-
420
- ```python
421
- schema = intersect((int, int), lambda o: o[0] <= o[1])
422
- ```
@@ -1,9 +0,0 @@
1
- vtjson/__init__.py,sha256=oLX4JH6_R7dYtTiGfBG3pQGR21IArspifdmZilbuGOw,68
2
- vtjson/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- vtjson/vtjson.py,sha256=rpgDncJmLcr6yoU2Ibh2pyubJJIyWXgxxvAWuMNfy_s,69567
4
- vtjson-2.1.8.dist-info/AUTHORS,sha256=qmxaXxaIO-YPNHJAZ0dcCrnPCs1x9ocbtMksiy4i80M,21
5
- vtjson-2.1.8.dist-info/LICENSE,sha256=n7xW-zX8xBLHzCdqWIMRuMzBD_ACLcNCwio0LEkKt1o,1077
6
- vtjson-2.1.8.dist-info/METADATA,sha256=ayfdkXUD6NbZ-7iOj9rbyI2WBfH7OW_URMug__SeleY,24323
7
- vtjson-2.1.8.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
8
- vtjson-2.1.8.dist-info/top_level.txt,sha256=9DlSF3l63igcvnYPcj117F2hzOW4Nx0N-JBoW3jjBZM,7
9
- vtjson-2.1.8.dist-info/RECORD,,