Skip to main content

A easy dependency validator

Project description

required: Easy multi-field validation

PyPI Build Status Coverage Status

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
  • end_date - filter events which start before this date

    • Only valid with start_date
    • Must be before 2018
    • Must be after start_date

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

required-0.3.4.post1.tar.gz (10.8 kB view hashes)

Uploaded Source

Built Distribution

required-0.3.4.post1-py2.py3-none-any.whl (8.0 kB view hashes)

Uploaded Python 2 Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page