Installation
============

To install constrict, run::

	python setup.py install

You will probably need to use root privilages.

Note that `python` needs to be Python 3 so on some systems you will need to run::

	python3 setup.py install

instead.

Running Tests
=============

To run all tests, run::

	python tests/run.py

or::

	python3 tests/run.py



constrict
=========
Constrict is a framework for adding type checking and constraints to functions.
Constraints are declared as function annotations (following the `typespec` 
format) and type checking is added using function decorators.

Constrict also provides a set of assertions for ad hoc type checking an a mix
in TestCase class for extending your unit tests.

Constrict depends on the ``typespec`` <https://github.com/galini/typespec> 
module and you should see the documentation of that module for details of how 
to format function arguments.

The Decorators
--------------

Import the decorators like so::

    >>> from constrict import checkargs, checkreturn, checkyield, check

Adding type checking for function arguments::

    >>> @checkargs
    ... def my_func_of_int(i : int) -> int:
    ...     return i + 1
    >>> my_func_of_int("hello")
    Traceback (most recent call last):
        ...
    constrict.ArgumentAssertionError: Invalid value 'hello' for argument i - i must be int.
    >>> my_func_of_int(1)
    2

Adding type checking for function return values::

    >>> @checkreturn
    ... def my_func_of_int(i : int) -> int:
    ...     return str(i+1)
    >>> my_func_of_int("hello")
    Traceback (most recent call last):
        ...
    TypeError: Can't convert 'int' object to str implicitly
    >>> my_func_of_int(1)
    Traceback (most recent call last):
        ...
    constrict.ReturnAssertionError: Invalid return value '2' - value must be int.

Adding type checking for yielded values::

    >>> @checkyield
    ... def my_generator() -> int:
    ...     for val in (1, 2, 3, "4", 5, 6):
    ...         yield val
    >>> for val in my_generator():
    ...     pass
    Traceback (most recent call last):
        ...
    constrict.YieldAssertionError: Invalid yielded value '4' - value must be int.

The ``check`` decorator is the same as using both ``checkargs`` and 
``checkreturn`` for a function or ``checkargs`` and ``checkyield`` for a 
generator.

The Assertions
--------------

Importing all assertions::

    >>> from constrict import (assert_isa, assert_valid, assert_valid_args,
    ...     assert_valid_return, assert_valid_yield, assert_call_ok, 
    ...     assert_iter_ok)

The assertions are functions that work like the ``assert`` statement. They do 
dynamic type checking and raise errors for invalid types. See the documentation
of each function for further details.

Using With unittest
-------------------

You can write test cases that check argument, return and yielded values.
Simply mix in the constrict.TestCase class into your test cases::

    import unittest
    import constrict
    
    class MyTestCase(unittest.TestCase, constrict.TestCase):
        def testSomething(self):
            ...

    if __name__ == '__main__':
        unittest.main()

This makes available extra test assertions that you can use.
See the documentation of the ``constrict.TestCase`` class for more details.

