Advertisement
Guest User

Ryan

a guest
Nov 18th, 2008
266
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.46 KB | None | 0 0
  1. import types
  2. from collections import defaultdict
  3. import inspect
  4.  
  5. class Contract(object):
  6.     """Class to contain preconditions and postconditions, as well as expose
  7.    methods as decorators."""
  8.     def __init__(self, func=None):
  9.         self.do(func)
  10.         self.invariants = []
  11.         self._invariant_called = False
  12.         self.preconditions = []
  13.         self.postconditions = []
  14.  
  15.     def do(self, func):
  16.         """Decorator to associate a method with a contract."""
  17.         self.__doc__ = func.__doc__
  18.         self.method = func
  19.         return self
  20.  
  21.     def ensure(self, func):
  22.         """Decorator to set post conditions."""
  23.         self.postconditions.append(func)
  24.         return self
  25.  
  26.     def require(self, func):
  27.         """Decorator to set pre conditions."""
  28.         self.preconditions.append(func)
  29.         return self
  30.  
  31.     def before(self, *args, **kwargs):
  32.         """Precondition and invariants checked before method invocation."""
  33.         precondition_errors = []
  34.         for precondition in  self.preconditions:
  35.             # 'OR' the preconditions together. This is allowed to pass as
  36.             # long as one of them passes.
  37.             try:
  38.                 precondition(*args, **kwargs)
  39.             except AssertionError, err:
  40.                 precondition_errors.append(err)
  41.             if len(precondition_errors) == len(self.preconditions):
  42.                 raise precondition_errors[0]
  43.  
  44.     def after(self, returned, *args, **kwargs):
  45.         """Postcondition and invariants checked after method call."""
  46.         # This is equivalent to 'AND'ing the postconditions together. If one
  47.         # fails, the entire postcondition check fails. (This is
  48.         # short-circuited).
  49.         for postcondition in self.postconditions:
  50.             postcondition(returned, *args, **kwargs)
  51.  
  52.  
  53.     def __get__(self, obj, type=None):
  54.         if obj is None:
  55.             return self
  56.         return types.MethodType(self, obj)
  57.  
  58.     def __call__(self, *args, **kwargs):
  59.         """Wrap method in precondition, postcondition and invariant calls."""
  60.         if self.preconditions:
  61.             self.before(*args, **kwargs)
  62.         if self.method:
  63.             returned = self.method(*args, **kwargs)
  64.         else:
  65.             raise NotImplementedError("Method not created for contract")
  66.         self.after(returned, *args, **kwargs)
  67.  
  68.         if self.invariants and not self._invariant_called:
  69.             self._invariant_called = True
  70.             for invariant in self.invariants:
  71.                 invariant(args[0])
  72.             self._invariant_called = False
  73.  
  74.         return returned
  75.  
  76.  
  77. def contract(func=None):
  78.     """Decorator to mark a function as having pre and post conditions."""
  79.     return Contract(func)
  80.  
  81. class DbcMeta(type):
  82.  
  83.     def __init__(cls, name, bases, attributes):
  84.         super(DbcMeta, cls).__init__(name, bases, attributes)
  85.  
  86.         super_attrs = defaultdict(list)
  87.         for base in bases:
  88.             for k, v in inspect.getmembers(base):
  89.                 if isinstance(v, Contract):
  90.                     super_attrs[k].append(v)
  91.                 if k == "invariant":
  92.                     invariants.append(v)
  93.  
  94.         cls.convert_methods(attributes, super_attrs)
  95.  
  96.     def convert_methods(cls, attributes, super_attrs):
  97.         """Replace functions in attributes with wrappers.
  98.  
  99.        The attributes is modified in place.
  100.        """
  101.  
  102.         # find methods with contracts.
  103.         contracted_methods = []
  104.         invariants = []
  105.         if hasattr(cls, 'invariant'):
  106.             invariants.append(getattr(cls, "invariant"))
  107.         for k, v in attributes.iteritems():
  108.             if isinstance(v, Contract):
  109.                 contracted_methods.append(k)
  110.  
  111.         # Add inherited pre and post conditions
  112.         # TODO: handle inherited invariants, multiple inheritance, etc.
  113.         for m in contracted_methods:
  114.             contract = getattr(cls,m)
  115.             preconditions = [precond for cont in super_attrs[m]
  116.                                          for precond in cont.preconditions]
  117.             postconditions = [postcond for cont in super_attrs[m]
  118.                                            for postcond in cont.postconditions]
  119.             if invariants:
  120.                 contract.invariants = invariants
  121.             for precond in preconditions:
  122.                 contract.require(precond)
  123.             for postcond in postconditions:
  124.                 contract.ensure(postcond)
  125.  
  126.             setattr(cls, m, contract)
  127.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement