A easy dependency validator
Project description
required: Easy multi-field validation
Required is a simple library which allows you to validate dependencies across multiple fields. The goal is to make writing things like Forms and Seralizers much easier by providing a declarative way to encode your complex validation logic.
Most Forms and Serializers limit you to doing validation on a single
field, and then have one single clean
method where you can do
muti-field validation logic. The problem with this is that if you have a
large number of optional fields which depend on each other, your
validation code can quickly become unreadable, unmaintainable and
non-reusable.
The aim of Required is to do the following:
- To have a declarative way to encode validation logic
- Allow you to maintain extremely complex multi field validation logic
- Allow you to reuse your validation logic easily
- Be flexible with what you want to validate
If this all sounds good. Read On!
Installation
Install using pip
pip install required
Quickstart
Lets start with a quick example.
You want to validate some business rules on some optional input parameters (for example to a API endpoint or function). They are start_date
and end_date
.
The business rules:
-
start_date
- Only valid with
end_date
- Must be after 2017
- Must be before 2018
- Only valid with
-
end_date
- filter events which start before this date- Only valid with
start_date
- Must be before 2018
- Must be after
start_date
- Only valid with
Theses rules can be written with required
as follows:
import datetime
from required import Requires, R
# start_date requirements
start_requires_end = Requires("start_date", "end_date")
start_after_2017 = Requires("start_date", R("start_date") > datetime.date(2017, 1, 1))
start_before_2018 = Requires("start_date", R("start_date") < datetime.date(2018, 1, 1))
# end_date requirements
end_requires_start = Requires("end_date", "start_date")
end_before_2018 = Requires("end_date", R("end_date") < datetime.date(2018, 1, 1))
end_after_start = Requires("end_date", R("end_date") > R("start_date"))
The above introduces the two important concepts of required; the Requires
and R
objects.
The Requires
object is used to define pair-wise dependencies. It has two non-optional arguments, the first one is the target (key) of the constraint, and the second argument is the constraint itself. Requires("start_date", "end_date")
means "start_date requires end_date to be present".
The R
object acts as a placeholder for a future value. If you require a future value of end_date
to be more than start_date
, you would write it as R("end_date") > R("start_date)
. Any such expression can be used as the constraint for the Requires
object.
The last step is simply summing all the Requires
together in order to combine the rules:
# combine all the rules
all_rules = (
start_requires_end +
start_after_2017 +
start_before_2018 +
end_requires_start +
end_before_2018 +
end_after_start
)
Once you have combined all the rules, you can simply call validate on the all_rules
object with a dict of your data you want to validate.
data = {
"start_date": datetime.date(2017, 10, 10),
"end_date": datetime.date(2017, 10, 9),
}
all_rules.validate(data)
# RequirementError: end_date requires end_date to be greater than start_date
The above not only tells you that the data was invalid, but which rule it broke. The following correct data passes validation:
data = {
"start_date": datetime.date(2017, 10, 10),
"end_date": datetime.date(2017, 10, 11),
}
all_rules.validate(data)
Cookbook
The following shows some recipes for forming validation rules with the R
object.
# Arithmetic on the `R` object follows normal maths rules.
R("x", R("x") + 1 < 1)
R("x", R("x") - R("y") == 1)
# A value `x` needs to be in an array
R("x", R("x").in_(array))
# The length of x must be 10
R("x", R("x").length() == 10)
# The length of x and y must be the same
R("x", R("x").length() == R("y").length())
# when x is present y must not be present
# from required import empty
R("x", R("y") == empty)
# x must be equal to the return value of a function
# this is useful if what you are checking is against
# is non-pure eg. current time
f = lambda x: 1
Requires("x", R("x") == Func(f, R("x")))
# the above can be used to ensure that a value is not in the past
R("start_date", R("start_date") > Func(datetime.now))
# Partial dependencies can be also specified with R objects
# x requires y when x is equal to 1
Requires(R("x") == 1, "y")
Contributing
If you want to contribute you are most welcome! This project is distributed under the MIT licence. It is tested using tox against Python 2.7 and 3.4+
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file required-0.3.4.post1.tar.gz
.
File metadata
- Download URL: required-0.3.4.post1.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.20.1 setuptools/40.6.2 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.6.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | d4a3b51724003664513a3bd20cb960aed9663e832f547489fa2c672ea2f22384 |
|
MD5 | 4aec144348778400bb18360f2d43280e |
|
BLAKE2b-256 | 1f3aa9a30bcf2ebafb9bf059673f040df61270f53f4fa9047b142bed64c2f523 |
File details
Details for the file required-0.3.4.post1-py2.py3-none-any.whl
.
File metadata
- Download URL: required-0.3.4.post1-py2.py3-none-any.whl
- Upload date:
- Size: 8.0 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.20.1 setuptools/40.6.2 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.6.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a7acde3fed2f1fe483a7e8a994ef153d536c5dfe99847a2eed7418878ee2a13b |
|
MD5 | d6c75b716801b0af615626ecab93cbeb |
|
BLAKE2b-256 | bb1b1b96db2dff196ec10c17be5644c840c8f4dcaed709802db9aae4c03dcf30 |