Metadata-Version: 2.0
Name: required
Version: 0.3.3
Summary: A easy dependency validator
Home-page: https://github.com/shezadkhan137/required
Author: Shezad Khan
Author-email: shezadkhan137@gmail.com
License: MIT
Keywords: dependency,validator
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Dist: six

required: Easy multi-field validation
=====================================

|PyPI| |Build Status| |Coverage Status|

Required is a simple libaray 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 declariative 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-resuable.

The aim of Required is to do the following:

-  To have a declaritave way to encode validation logic
-  Allow you to maintain extreamly complex multi field valiation 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
paramaters (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:

.. code:: python

    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:

.. code:: python

    # 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.

.. code:: python

    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:

.. code:: python

    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.

.. code:: python

    # 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 <https://choosealicense.com/licenses/mit/>`__
licence. It is tested using `tox <https://pypi.python.org/pypi/tox>`__
against Python 2.7 and 3.4+

.. |PyPI| image:: https://img.shields.io/pypi/v/required.svg
   :target: 
.. |Build Status| image:: https://travis-ci.org/shezadkhan137/required.svg?branch=master
   :target: https://travis-ci.org/shezadkhan137/required
.. |Coverage Status| image:: https://coveralls.io/repos/github/shezadkhan137/required/badge.svg?branch=master
   :target: https://coveralls.io/github/shezadkhan137/required?branch=master


