

Contract based programming (Python) - irahul
https://github.com/thoughtnirvana/augment

======
tdavis
The huge number of lambdas had a tendency to muddle the first example;
somebody should tell this gentleman about the `operator` module. Other than
that, looks interesting! The decorator syntax may be a more apparent way to
handle assertions about arguments in situations where numerous (or repetitive
ones) are required. Hooks can be similarly handy.

~~~
irahul
> The huge number of lambdas had a tendency to muddle the first example;
> somebody should tell this gentleman about the `operator` module.

The lambdas are how you specify the constraints. How does operator module help
here?

    
    
        @ensure_args(a=lambda x: x > 10 and x < 100)
        def foo(a):
            pass
    

Here, the constraint is `a` should be greater than 10 and less than 100. Where
does operator module comes in here?

And the first example is using multiple lambdas to show the multiple use
cases:

1\. `a=lambda x: x > 10` is for the positional argument `a`.

2\. `b=r'^?-\d+(\\.\d+)$'` is to show if you need the arg to match a regex,
you can directly pass it.

3\. c=lambda x: x < 5) # `c` will be picked from `kwargs`.

And here, the comment clarifies that c isn't there in the positional args. But
since arguments are looked for in both kwargs and positional args, this
constraint will work.

~~~
MostAwesomeDude
And presumably these can be flicked off in production, for speed?

~~~
irahul
Won't that defeat the whole point? Contract based programming means your
function assumes certain properties and acts accordingly. If you turn off the
contract enforcers, your function will still be acting under the assumptions
that the input meets the contract and will err.

~~~
MostAwesomeDude
I think the key word here is "assumption." zope.interface doesn't enforce its
contracts by default; one must explicitly invoke verifyObject() to verify the
invariants and interfaces of a given object. But, for speed, verifyObject() is
usually omitted in interface-heavy applications. (I admit that my personal
projects always verifyObject(), but only for rigorous plugin validation.)

~~~
irahul
The waters are murkier here. Apart from checks, you also have

    
    
        @transform_args(a=lambda x: x*x)
        def foo(a):
            pass
    

This isn't validation and can't be turned off. The idea behind the project was
the function should concentrate on logic and validations should happen
declaratively. If the `verifyObject` sort of call comes inside the function,
it impacts the declarative goal.

The current validators throw Exception by default. They can be optionally take
an `error_handler` to which they pass the errors. Bringing in inside the
function will mean manually accumulating the errors from various validators.

Also, these validators are mostly for conditions which must be met. Say you
are writing a sqrt function, then the pre-condition is the number shouldn't be
negative and that isn't something that can be turned off.

I don't know how zope.interface works but this isn't supposed to be optional
validation. It is basically about taking the manual checks from inside the
function to above the function with convenient declarative syntax.

~~~
MostAwesomeDude
Ah, I see. You should definitely check out zope.interface; it's a different
approach to a similar problem. This is quite cool, though. Good work!

------
irahul
I liked contracts in Racket, so ported some of it to Python. These checks can
very well be done inside the called function but decorators enable a more
declarative style.

~~~
grncdr
Very nice. I did something very similar for an RPC server I wrote, but it's
less general and relies on the ORM (SQLAlchemy). Essentially, it turns
database id's into the mapped objects, checks the objects against the
constraints declared as arguments to the decorator (much like this module),
and then passes the mapped objects in to the original function in place of the
id. It's proven to be a very useful pattern, but I'll probably use this
library instead for future projects.

------
targeted
In Python 3 a similar thing looks better with the use of "function
annotations": <http://code.activestate.com/recipes/572161/>

There also are a few modules out there that implement design-by-contract's
pre-/post-conditions for class instances, for example
<http://code.activestate.com/recipes/436834/>

------
St-Clock
One thing I may have missed from the documentation: is it possible to check
post-conditions (side effects of the function and return value)?

~~~
irahul
Side effects can be checked in `@leave(check_side_effects)`. I plan to add
something to check results.

