Skip to content

Commit f4598e4

Browse files
author
Sylvain MARIE
committed
Updated doc and created corresponding tests
1 parent e9ccaef commit f4598e4

2 files changed

Lines changed: 152 additions & 12 deletions

File tree

docs/index.md

Lines changed: 103 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
*Validating types for python - use `isinstance()` to validate both type and value.*
88

9-
`vtypes` is a small library to define "validating types". These types can be used to add value validation on top of type checking anywhere where you usually rely on `isinstance()`. This can in particular be used to make validation schemas simpler and more readable, for example used in [`pyfields`](https://smarie.github.io/python-pyfields/#3-autofields).
9+
`vtypes` is a small library to define "validating types". These types can be used to **add value validation on top of type checking** anywhere where you usually rely on `isinstance()`. This can in particular be used to make validation schemas simpler and more readable, for example used in [`pyfields`](https://smarie.github.io/python-pyfields/#3-autofields).
1010

1111

1212
## Installing
@@ -19,7 +19,11 @@
1919

2020
### a - basics
2121

22-
You create a `VType` by combining one or several base types with optional value validators following the [`valid8` simple syntax](https://smarie.github.io/python-valid8/validation_funcs/c_simple_syntax/).
22+
A `VType` is a combination of :
23+
24+
- a type **name**, for example `'PositiveInt'`
25+
- one or several **base types**: for example `int`.
26+
- one or several **validators**: for example `lambda x: x >= 0`
2327

2428
For example we can create a positive int:
2529

@@ -29,37 +33,124 @@ from vtypes import vtype
2933
PositiveInt = vtype('PositiveInt', int, {'should be positive': lambda x: x >= 0})
3034
```
3135

32-
`isinstance` works as expected:
36+
A `VType`'s main purpose is to behave like a type (therefore to be compliant with `isinstance`) and to validate both type and values at the same time when `isinstance` is called:
3337

3438
```python
3539
assert isinstance(1, PositiveInt)
3640
assert not isinstance(-1, PositiveInt)
3741
```
3842

39-
You can also get a more detailed error if you wish:
43+
### b - goodies
44+
45+
In addition to this primary feature, a `VType` provides a few handy methods:
46+
47+
- detailed error messages with `validate` (note: a variable name should be provided, for example `'size'`).
48+
49+
```python
50+
>>> PositiveInt.validate('size', -1)
51+
52+
ValidationError[ValueError]: Error validating [size=-1].
53+
InvalidValue: should be positive.
54+
Function [<lambda>] returned [False] for value -1.
55+
```
56+
57+
- partial checkers: `has_valid_type` for type-only, and `has_valid_value` for value-only:
58+
59+
```python
60+
assert PositiveInt.has_valid_type(-1) # -1 is an int
61+
assert not PositiveInt.has_valid_value(-1) # -1 < 0
62+
```
63+
64+
Finally, you may wish to use `is_vtype` to check if anything is a `VType`:
4065

4166
```python
42-
>>> PositiveInt.assert_valid('x', -1)
43-
ValidationError[ValueError]: Error validating [x=-1].
44-
InvalidValue: should be positive. Function [<lambda>] returned [False] for value -1.
67+
from vtypes import is_vtype
68+
69+
assert is_vtype(PositiveInt)
70+
assert not is_vtype(int)
71+
assert not is_vtype(1)
4572
```
4673

47-
### b - composition
74+
### c - validators syntax
75+
76+
There are many ways to declare validators:
77+
78+
- a single callable
79+
80+
- a single tuple `(<callable>, <error_msg>)`, `(<callable>, <failure_type>)` or `(<callable>, <error_msg>, <failure_type>)`
81+
82+
- a list of such callables and tuples
83+
84+
- a **dictionary** where keys are `<callable>`, `<error_msg>`, or `<failure_type>` and values are one or two (tuple) of such elements. This is at least for the author, the most intuitive and readable style:
85+
86+
```python
87+
ConstrainedInt = vtype('ConstrainedInt', int,
88+
{'should be positive': lambda x: x >= 0,
89+
'should be a multiple of 3': lambda x: x % 3})
90+
```
91+
92+
Note that this syntax is [`valid8` simple syntax](https://smarie.github.io/python-valid8/validation_funcs/c_simple_syntax/).
93+
94+
If you wish to create even more compact callables, you may wish to look at [`mini_lambda`](https://smarie.github.io/python-mini-lambda/).
95+
96+
### d - composition
97+
98+
You can combine types, for example a nonempty string can be obtained by mixing `NonEmpty` and `str`:
99+
100+
```python
101+
NonEmpty = vtype('NonEmpty', (), {'should be non empty': lambda x: len(x) > 0})
102+
"""A VType describing non-empty containers, with strictly positive length."""
103+
104+
NonEmptyStr = vtype('NonEmptyStr', (NonEmpty, str), ())
105+
"""A VType for non-empty strings"""
106+
```
107+
108+
109+
### e - alternate coding style
110+
111+
An alternate way to define `VType`s is to define a python class inheriting from `VType`.
112+
113+
- the validators can be provided as a class member named `__validators__`
114+
115+
- the base type(s) can be either provided as superclass(es), or as a class member named `__type__`.
116+
117+
This provides an alternate style that developers might find handy in particular for entering docstrings and for making `VTypes` composition appear "just like normal python inheritance".
118+
119+
```python
120+
from vtypes import VType
121+
122+
class NonEmpty(VType):
123+
"""A VType describing non-empty containers, with strictly positive length."""
124+
__validators__ = {'should be non empty': lambda x: len(x) > 0}
125+
126+
class NonEmptyStr(NonEmpty, str):
127+
"""A VType for non-empty strings"""
128+
129+
class AlternateNonEmptyStr(VType):
130+
"""A VType for non-empty strings - alternate style"""
131+
__type__ = NonEmpty, str
132+
```
48133

49-
You can combine types, for example a positive int can be obtained by mixing `Positive` and `int`:
134+
The vtypes work as expected:
50135

51136
```python
52-
TODO
137+
assert isinstance('hoho', NonEmptyStr)
138+
assert not isinstance('', NonEmptyStr)
139+
assert not isinstance(1, NonEmptyStr)
53140
```
54141

55142

56143
## Main features
57144

58-
* **TODO**
145+
* Validate both type and value with `isinstance`, thanks to easy-to-write "validating types"
146+
* `has_valid_type` and `has_valid_value` methods provided for easy auditing, as well as `is_vtype`
147+
* Validation syntax fully compliant with `valid8`. Compliant error message available through a `validate()` method
148+
* v-types are composable so that creating a library of reusable elements is straightforward (note: should we provide one in this library based on `valid8` [library](https://smarie.github.io/python-valid8/validation_funcs/b_base_validation_lib/) ?)
149+
* Alternate class-style available to perform composition using inheritance, and write docstrings more easily.
59150

60151
## See Also
61152

62-
* [`checktypes`](https://gitlab.com/yahya-abou-imran/checktypes), that was a great source of inspiration. The only reason for recreating something new was the capability to use the `valid8` syntax for validators (as well as its standardized exceptions).
153+
* [`checktypes`](https://gitlab.com/yahya-abou-imran/checktypes), that was a great source of inspiration. The only reason I ended up recreating something new a couple years after discovering it, was that I really wanted to leverage the `valid8` syntax for validators (as well as its standardized exceptions).
63154

64155
*Do you like this library ? You might also like [my other python libraries](https://github.com/smarie/OVERVIEW#python)*
65156

vtypes/tests/test_doc.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Authors: Sylvain Marie <sylvain.marie@se.com>
2+
#
3+
# Copyright (c) Schneider Electric Industries, 2020. All right reserved.
4+
import pytest
5+
from valid8 import ValidationError
6+
7+
from vtypes import vtype
8+
9+
10+
def test_doc_first():
11+
"""The first example in the doc"""
12+
13+
PositiveInt = vtype('PositiveInt', int, {'should be positive': lambda x: x >= 0})
14+
15+
assert isinstance(1, PositiveInt)
16+
assert not isinstance(-1, PositiveInt)
17+
18+
with pytest.raises(ValidationError):
19+
PositiveInt.validate('x', -1)
20+
21+
assert PositiveInt.has_valid_type(-1)
22+
assert not PositiveInt.has_valid_value(-1)
23+
24+
25+
def test_doc_second():
26+
from vtypes import VType
27+
28+
class NonEmpty(VType):
29+
"""A VType describing non-empty containers, with strictly positive length."""
30+
__validators__ = {'should be non empty': lambda x: len(x) > 0}
31+
32+
class NonEmptyStr(NonEmpty, str):
33+
"""A VType for non-empty strings"""
34+
35+
class AlternateNonEmptyStr(VType):
36+
"""A VType for non-empty strings - alternate style"""
37+
__type__ = NonEmpty, str
38+
39+
assert isinstance('hoho', NonEmpty)
40+
assert not isinstance('', NonEmpty)
41+
assert not isinstance([], NonEmpty)
42+
assert isinstance([1], NonEmpty)
43+
assert not isinstance(1, NonEmpty)
44+
assert isinstance('hoho', NonEmptyStr)
45+
assert isinstance('hoho', AlternateNonEmptyStr)
46+
assert not isinstance('', NonEmptyStr)
47+
assert not isinstance('', AlternateNonEmptyStr)
48+
assert not isinstance(1, NonEmptyStr)
49+
assert not isinstance(1, AlternateNonEmptyStr)

0 commit comments

Comments
 (0)