@@ -4,391 +4,9 @@ pyvalid
44.. image :: https://img.shields.io/codecov/c/github/uzumaxy/pyvalid.svg?style=plastic
55.. image :: https://img.shields.io/github/workflow/status/uzumaxy/pyvalid/Python%20package?style=plastic
66
7- The pyvalid is the Python validation tool for checking a function's input
8- parameters and return values.
9-
10- Purposes of the pyvalid package:
11-
12- #. Provide an ability to validate a user input (such as usernames,
13- phone numbers, emails, dates and times, etc) and minimize the amount of
14- code required for the implementation of the comprehensive validation
15- systems;
16- #. Add an additional layer of dynamic code analysis for the development and
17- testing stages — pyvalid will raise the exception if a function accepts or
18- returns unexpected values and it's always possible to disable pyvalid in
19- production if needed.
20- #. Help to catch runtime issues.
21-
22-
23- How to install
24- ++++++++++++++
25-
26- * With PyPI: ``pip install -U pyvalid ``
27- * Manually: ``python setup.py install ``
28-
29-
30- How to use
31- ++++++++++
32-
33- The schema below reveals the general structure of the ``pyvalid `` package:
34-
35- .. image :: https://raw.githubusercontent.com/uzumaxy/pyvalid/master/docs/assets/pyvalid-map.png
36- :width: 600
37-
38- The package consists of two decorators: ``accepts `` and ``returns ``, which
39- validates the function’s input and output values accordingly. To know how to
40- validate the data, ``accepts `` and ``returns `` decorators should receive the
41- information about excepted values/types/validators.
42-
43- The very basic example below shows how to use ``accepts `` and ``returns ``
44- decorators.
45-
46- .. code-block :: python
47-
48- from pyvalid import accepts, returns
49-
50-
51- @accepts (int , int )
52- @returns (float )
53- def divide (num_1 , num_2 ):
54- return num_1 / num_2
55-
56- divide(8 , 42 )
57- # Returns float value
58-
59- divide(' Python' , 42 )
60- # Raises the ArgumentValidationError exception, since the 1st argument is
61- # the str value, when we're expecting int values only.
62-
63- If just specifying an expected type or value is not enough, then it's worth to
64- use the custom validator. All the built-in validators are located in the
65- ``pyvalid.validators `` module and it's also possible to create a new one using
66- the ``is_validator `` decorator or through extending the ``AbstractValidator ``
67- class.
68-
69- We can flexibly control the state of the ``pyvalid `` validation using the
70- ``pyvalid.switch `` module. This module provides an ability to switch the
71- ``pyvalid `` on/off.
72-
73- In most cases, it's worth to use the ``pyvalid `` features to validate
74- incoming/outcoming data, such as: user input, the data sent to the API, etc.
75-
76- But it's also possible to use the ``pyvalid `` package as a part of the CI/CD
77- processes only:
78-
79- #. Apply the ``accepts `` and ``returns `` decorators to all needed functions
80- and methods.
81- #. Perform unit testing, integration testing, etc.
82- #. The ``accepts `` and ``returns `` decorators will raise exceptions in case if
83- the input/output data is not valid.
84- #. Collect information about raised exceptions and fix the code, which causes
85- them.
86- #. Turn off the ``pyvalid `` before going live in order to avoid unnecessary
87- exceptions in production.
88-
89-
90- ``pyvalid.accepts(*allowed_arg_values, **allowed_kwargs_values) ``
91- -----------------------------------------------------------------
92-
93- The decorator which validates input parameters of the wrapped function.
94-
95- To use it, we need to specify the list of allowed types or values. If the
96- function’s input doesn’t match the allowed types/values, one of the following
97- errors will be thrown:
98-
99- * ``pyvalid.ArgumentValidationError `` — when the actual type/value of the
100- function’s argument is different from the expected one;
101- * ``pyvalid.InvalidArgumentNumberError `` — when the number/position of
102- function’s arguments is incorrect.
103-
104- Examples of usage:
105-
106- Let's define the ``multiply ``, which accepts only ``int `` values, and see how
107- does it work with other types.
108-
109- .. code-block :: python
110-
111- from pyvalid import accepts
112-
113-
114- @accepts (int , int )
115- def multiply (num_1 , num_2 ):
116- return num_1 * num_2
117-
118-
119- multiply(4 , 2 )
120- # Returns 8.
121-
122- multiply(3.14 , 8 )
123- # Raises the ArgumentValidationError exception, since the 1st argument is
124- # the float value, when we're expecting int values only.
125-
126- multiply(3 , ' pyvalid' )
127- # Raises the ArgumentValidationError exception, since the 2nd argument is
128- # the str value, when we're expecting int values only.
129-
130- multiply(128 )
131- # Raises the InvalidArgumentNumberError exception, since the second
132- # argument is missing.
133-
134-
135- ``pyvalid.returns(*allowed_return_values) ``
136- -------------------------------------------
137-
138- The decorator which validates the value returned by the wrapped function.
139-
140- To use it, we need to specify the list of expected return types or values.
141- If the function’s return value doesn’t match the allowed types/values, the
142- ``pyvalid.InvalidReturnTypeError `` error will be thrown.
143-
144- Examples of usage:
145-
146- Let's define the ``multiply ``, which returns only ``int `` values, and see how
147- does it work with other types.
148-
149- .. code-block :: python
150-
151- from pyvalid import returns
152-
153-
154- @returns (int )
155- def multiply (num_1 , num_2 ):
156- return num_1 * num_2
157-
158-
159- multiply(4 , 2 )
160- # Returns 8.
161-
162- multiply(3.14 , 8 )
163- # Raises the InvalidReturnTypeError exception, since the function returns
164- # the float value, when we're expecting int values only.
165-
166- multiply(3 , ' pyvalid' )
167- # Raises the InvalidReturnTypeError exception, since the function returns
168- # the str value, when we're expecting int values only.
169-
170-
171- Advanced examples
172- +++++++++++++++++
173-
174- Function ``calculate `` in the example below has the following limitations:
175-
176- * Function should return ``int `` or ``float `` values only;
177- * First parameter must be ``str `` value;
178- * Second parameter must be ``int `` value or be equal to the ``2.0 ``;
179- * Third parameter must be ``int `` or ``float `` value.
180-
181- .. code-block :: python
182-
183- from pyvalid import accepts, returns
184-
185-
186- @returns (int , float )
187- @accepts (str , (int , 2.0 ), (int , float ))
188- def calculate (operator , val1 , val2 , val3 ):
189- expression = ' {v1} {op} {v2} {op} {v3} ' .format(
190- op = operator,
191- v1 = val1, v2 = val2, v3 = val3
192- )
193- return eval (expression)
194-
195-
196- calculate(' *' , 2 , 3 , 4 )
197- # Returns 24.
198-
199- calculate(operator = ' *' , val1 = 2 , val2 = 3.0 , val3 = 4 )
200- # Returns 24.0.
201-
202- calculate(' *' , 2.0 , 3 , 4 )
203- # Still returns 24.0.
204-
205- calculate(' *' , 3.14 , 3 , 4 )
206- # Raises the ArgumentValidationError exception, because the second
207- # argument is not valid.
208-
209- calculate(' *' , 2 , 3 , ' "4"' )
210- # Raises the InvalidReturnTypeError exception, because of invalid return
211- # value: function returns the str value, when only int and float values
212- # are allowed.
213-
214-
215- The example below demonstrates how to use the ``accepts `` and ``returns ``
216- decorators in the classes. Please pay attention to the method ``connect `` of
217- the class ``SqlDriver ``. In these classes we're using the ``accepts ``
218- decorator to validate keyword arguments.
219-
220- .. code-block :: python
221-
222- from pyvalid import accepts, returns
223- from collections.abc import Iterable
224-
225-
226- class SqlDriver (object ):
227-
228- @returns (bool )
229- @accepts (object , host = str , port = int , usr = str , pwd = str , db = [str , None ])
230- def connect (self , ** kwargs ):
231- conn_req = ' tsql -S {host} -p {port} -U {usr} -P {pwd} -D {db} '
232- conn_req = conn_req.format(** kwargs)
233- try :
234- print (' Establishing connection: "{} "' .format(conn_req))
235- # Some code, which may cause the ConnectionError
236- return True
237- except ConnectionError :
238- return False
239-
240- @returns (bool )
241- def close (self ):
242- try :
243- print (' Closing connection' )
244- # Some code, which may cause the ConnectionError
245- return True
246- except ConnectionError :
247- return False
248-
249- @returns (None , dict )
250- @accepts (object , str , Iterable)
251- def query (self , sql , params = None ):
252- try :
253- if params is not None :
254- sql = sql.format(* params)
255- query_info = ' Processing request "{} "' .format(sql)
256- print (query_info)
257- return dict ()
258- # Some code, which may cause the ConnectionError
259- except ConnectionError :
260- return None
261-
262-
263- sql_driver = SqlDriver()
264-
265- conn_params = {
266- ' host' : ' 8.8.8.8' ,
267- ' port' : 1433 ,
268- ' usr' : ' admin' ,
269- ' pwd' : ' password' ,
270- ' db' : ' wiki'
271- }
272- sql_driver.connect(** conn_params)
273-
274- sql = ' SELECT * FROM ProgrammingLang'
275- pl = sql_driver.query(sql)
276-
277- sql = ' SELECT * FROM ProgrammingLang WHERE name={} '
278- python_pl = sql_driver.query(sql, (' Python' ,))
279-
280- sql_driver.close()
281-
282-
283- When we need a bit more complex validators, we may use built-in ``pyvalid`
284- validators available in the ``pyvalid.validators `` module.
285- For example, here we're using the ``StringValidator `` validator based on the
286- regular expression and the ``NumberValidator `` based on the min/max allowed
287- values:
288-
289- .. code-block :: python
290-
291- from pyvalid import accepts, returns
292- from pyvalid.validators import NumberValidator, StringValidator
293-
294- @accepts (StringValidator(re_pattern = r ' ^ [A-Za-z ]+ \s ? [A-Za-z ]+ \s ? [A-Za-z ]+ $ ' ))
295- @returns (NumberValidator(min_val = 0 , max_val = 10 ))
296- def get_review (name ):
297- message = ' Hello, {} ! Please review our application from 0 to 10.'
298- print (message.format(name))
299- return float (input ())
300-
301- review = get_review(' Elon Musk' )
302- print (review)
303- # Will raise the InvalidReturnTypeError exception only if user enter
304- # the value, which is not in the [0, 10] range.
305-
306- another_review = get_review(' Elon Musk 2' )
307- # Raises the ArgumentValidationError exception, since the "Elon Musk 2"
308- # value doesn't match the pattern.
309-
310-
311- The example below explains how to use the custom validator. It's pretty
312- easy actually, we just need to apply the ``pyvalid.validators.is_validator ``
313- decorator to the validation function.
314-
315- .. code-block :: python
316-
317- from pyvalid import accepts
318- from pyvalid.validators import is_validator
319-
320-
321- class User (object ):
322-
323- registered_users = list ()
324-
325- class Validator (object ):
326-
327- unsafe_passwords = [
328- ' 111111' , ' 000000' , ' 123123' ,
329- ' 123456' , ' 12345678' , ' 1234567890' ,
330- ' qwerty' , ' sunshine' , ' password' ,
331- ]
332-
333- @ classmethod
334- @is_validator
335- def login_checker (cls , login ):
336- if isinstance (login, str ) and 1 <= len (login) <= 16 :
337- for reg_user in User.registered_users:
338- if login == reg_user.login:
339- return False
340- return True
341-
342- @ classmethod
343- @is_validator
344- def password_checker (cls , password ):
345- return (
346- isinstance (password, str )
347- and
348- 6 <= len (password) <= 32
349- and
350- password not in cls .unsafe_passwords
351- )
352-
353- def __init__ (self , login , password ):
354- self .__login = None
355- self .login = login
356- self .__password = None
357- self .password = password
358- User.registered_users.append(self )
359-
360- @ property
361- def login (self ):
362- return self .__login
363-
364- @login.setter
365- @accepts (object , Validator.login_checker)
366- def login (self , value ):
367- self .__login = value
368-
369- @ property
370- def password (self ):
371- return self .__password
372-
373- @password.setter
374- @accepts (object , Validator.password_checker)
375- def password (self , value ):
376- self .__password = value
377-
378-
379- user = User(' admin' , ' Str0ng_P@ssw0rd!' )
380-
381- print (user.login, user.password)
382- # Outputs: "admin Str0ng_P@ssw0rd!"
383-
384- user.password = ' qwerty'
385- # Raises the ArgumentValidationError exception, because the
386- # User.Validator.password_checker method returns False.
387-
388- user = User(' admin' , ' An0ther_Str0ng_P@ssw0rd!' )
389- # Raises the ArgumentValidationError exception, because the
390- # User.Validator.login_checker method returns False.
7+ pyvalid is a Python data validation tool.
3918
9+ Documentation: `uzumaxy.github.io/pyvalid <https://uzumaxy.github.io/pyvalid/ >`_.
39210
39311License
39412+++++++
0 commit comments