Source code for constraints.constraints

"""
constraints provides several facilities:

* The abstract class generator :class:`Constraints`
* 3 Design by contract style condition classes:
    * :class:`Precondition`
    * :class:`Postcondition`
    * :class:`Invariant`
    
Constraint classes generated by :class:`Constraints` can validate instance
attribute values when used as descriptors.  The condition classes provide
context managers, and both :class:`Precondition` and :class:`Postcondition`
can be used as function decorators.
"""

from sys import _getframe
from abc import ABCMeta
from proxy import Symbol
from decorator import decorator
from inspect import getcallargs

def _frame_value(name):
    # We have to break out of all the decorators in order to get the outer scope
    frame = _getframe(3)
    return frame.f_locals[name]

[docs]class Constraints(ABCMeta): """ Metaclass which provides constraint verification for objects. Constraints are specified as arguments to the __new__ method. Constraints can be either no argument callables or Symbol expressions. isinstance(obj, ConstraintsInstance) will return True iff obj satisfies all constraints. """ def __init__(self, *args): pass def __new__(self, *args): return super(Constraints, self).__new__( self, "Constraint", (ConstraintBase,), {"args":args} ) def __instancecheck__(self, other): # We need to replace the placeholder object with other return all( not isinstance(arg, Symbol) and arg(other) or \ arg.__evaluate__(other) for arg in self.args )
[docs]class ConstraintBase(object): """Constraint base class. Constraints are usable as descriptors.""" def __init__(self, callable_=None, name=None): if name is None and isinstance(callable, basestring): # If someone wants to pass name where callable should be, we are ok with that. (name, callable_) = (callable_, name) self.name = name self.callable = callable_ def __get__(self, obj, type_=None): return getattr(self, "value", None) def __set__(self, obj, value): if isinstance(value, type(self)): self.value = value else: raise AssertionError("Specified value (%s) does not satisfy this" " constraint" % value) def __delete__(self, obj): try: del self.value except AttributeError: pass @classmethod def precondition(cls, callable_=None, name=None): """ Returns a Precondition instance. :param callable_: A no argument callable. :param name: The name of the variable to verify. :type name: string .. note:: `callable`, if specified, takes precedence over `name` in situations where either could be used. If `name` is not specified and `callable` is a string, `callable` will be used as `name`. """ return Precondition(cls, callable_, name) @classmethod def postcondition(cls, callable_=None, name=None): """ Returns a Postcondition instance. :param callable_: A no argument callable. :param name: The name of the variable to verify. :type name: string .. note:: `callable`, if specified, takes precedence over `name` in situations where either could be used. If `name` is not specified and `callable` is a string, `callable` will be used as `name`. """ return Postcondition(cls, callable_, name) @classmethod def invariant(cls, callable_=None, name=None): """ Returns an Invariant instance. :param callable_: A no argument callable. :param name: The name of the variable to verify. :type name: string .. note:: `callable`, if specified, takes precedence over `name` in situations where either could be used. If `name` is not specified and `callable` is a string, `callable` will be used as `name`. """ return Invariant(cls, callable_, name)
[docs]class ConditionBase(object): """Base class for design by contract style conditions.""" def __init__(self, constraint, target=None, name=None): self.constraint = constraint if name is None and isinstance(target, basestring): # If someone wants to pass name where callable should be, we are ok with that. (name, target) = (target, name) self.target = target self.name = name def __call__(self, f): return decorator(self.decorator, f) def __enter__(self): pass def __exit__(self, exc_type, exc_val, exc_tb): pass
[docs]class Precondition(ConditionBase): """ Constraint container that verifies a precondition. Usable as a decorator or context manager. .. note:: You must specify a string argument name for the precondition or the decorator will not function properly. """ def __enter__(self): value = (getattr(self, "target", None) or (lambda: _frame_value(self.name)))() if not isinstance(value, self.constraint): raise AssertionError("The value (%s) did not meet the specified" " pre-condition" % value)
[docs] def decorator(self, f, *args, **kwargs): """ Precondition decorator, looks at the bound value of the arg with the key equal to self.name. """ arg_values = getcallargs(f, *args, **kwargs) arg = arg_values[self.name] if not isinstance(arg, self.constraint): raise AssertionError("The value (%s) did not meet the specified" " pre-condition" % arg) return f(*args, **kwargs)
[docs]class Postcondition(ConditionBase): """ Constraint container that verifies a postcondition. Usable as a decorator or context manager. .. note:: The decorator constrains the return value of the decorated function. It ignores the callable and name attributes of the condition if they are present. """ def __exit__(self, exc_type, exc_val, exc_tb): value = (getattr(self, "target", None) or (lambda: _frame_value(self.name)))() if not isinstance(value, self.constraint): raise AssertionError("The value (%s) did not meet the specified" " post-condition" % value)
[docs] def decorator(self, f, *args, **kwargs): """ Postcondition decorator, looks at the result of the function call and verify that it is satisfies the constraint condition. """ result = f(*args, **kwargs) if not isinstance(result, self.constraint): raise AssertionError("The value (%s) did not meet the specified" " post-condition" % result) return result
[docs]class Invariant(ConditionBase): """ Constraint container that verifies an invariant condition. Usable as a context manager. """ def __enter__(self): value = (getattr(self, "target", None) or (lambda: _frame_value(self.name)))() if not isinstance(value, self.constraint): raise AssertionError("The value (%s) did not meet the specified" " invariant condition" % value) def __exit__(self, exc_type, exc_val, exc_tb): value = (getattr(self, "target", None) or (lambda: _frame_value(self.name)))() if not isinstance(value, self.constraint): raise AssertionError("The value (%s) did not meet the specified" " invariant condition" % value) def __call__(self, f): raise NotImplementedError("Invariant objects do not provide decorator functionality")