• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Support Eiffel-style preconditions and postconditions."""
2
3from types import FunctionType as function
4
5class EiffelBaseMetaClass(type):
6
7    def __new__(meta, name, bases, dict):
8        meta.convert_methods(dict)
9        return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases,
10                                                        dict)
11
12    @classmethod
13    def convert_methods(cls, dict):
14        """Replace functions in dict with EiffelMethod wrappers.
15
16        The dict is modified in place.
17
18        If a method ends in _pre or _post, it is removed from the dict
19        regardless of whether there is a corresponding method.
20        """
21        # find methods with pre or post conditions
22        methods = []
23        for k, v in dict.iteritems():
24            if k.endswith('_pre') or k.endswith('_post'):
25                assert isinstance(v, function)
26            elif isinstance(v, function):
27                methods.append(k)
28        for m in methods:
29            pre = dict.get("%s_pre" % m)
30            post = dict.get("%s_post" % m)
31            if pre or post:
32                dict[m] = cls.make_eiffel_method(dict[m], pre, post)
33
34class EiffelMetaClass1(EiffelBaseMetaClass):
35    # an implementation of the "eiffel" meta class that uses nested functions
36
37    @staticmethod
38    def make_eiffel_method(func, pre, post):
39        def method(self, *args, **kwargs):
40            if pre:
41                pre(self, *args, **kwargs)
42            x = func(self, *args, **kwargs)
43            if post:
44                post(self, x, *args, **kwargs)
45            return x
46
47        if func.__doc__:
48            method.__doc__ = func.__doc__
49
50        return method
51
52class EiffelMethodWrapper:
53
54    def __init__(self, inst, descr):
55        self._inst = inst
56        self._descr = descr
57
58    def __call__(self, *args, **kwargs):
59        return self._descr.callmethod(self._inst, args, kwargs)
60
61class EiffelDescriptor(object):
62
63    def __init__(self, func, pre, post):
64        self._func = func
65        self._pre = pre
66        self._post = post
67
68        self.__name__ = func.__name__
69        self.__doc__ = func.__doc__
70
71    def __get__(self, obj, cls):
72        return EiffelMethodWrapper(obj, self)
73
74    def callmethod(self, inst, args, kwargs):
75        if self._pre:
76            self._pre(inst, *args, **kwargs)
77        x = self._func(inst, *args, **kwargs)
78        if self._post:
79            self._post(inst, x, *args, **kwargs)
80        return x
81
82class EiffelMetaClass2(EiffelBaseMetaClass):
83    # an implementation of the "eiffel" meta class that uses descriptors
84
85    make_eiffel_method = EiffelDescriptor
86
87def _test(metaclass):
88    class Eiffel:
89        __metaclass__ = metaclass
90
91    class Test(Eiffel):
92
93        def m(self, arg):
94            """Make it a little larger"""
95            return arg + 1
96
97        def m2(self, arg):
98            """Make it a little larger"""
99            return arg + 1
100
101        def m2_pre(self, arg):
102            assert arg > 0
103
104        def m2_post(self, result, arg):
105            assert result > arg
106
107    class Sub(Test):
108        def m2(self, arg):
109            return arg**2
110        def m2_post(self, Result, arg):
111            super(Sub, self).m2_post(Result, arg)
112            assert Result < 100
113
114    t = Test()
115    t.m(1)
116    t.m2(1)
117    try:
118        t.m2(0)
119    except AssertionError:
120        pass
121    else:
122        assert False
123
124    s = Sub()
125    try:
126        s.m2(1)
127    except AssertionError:
128        pass # result == arg
129    else:
130        assert False
131    try:
132        s.m2(10)
133    except AssertionError:
134        pass # result ==  100
135    else:
136        assert False
137    s.m2(5)
138
139if __name__ == "__main__":
140    _test(EiffelMetaClass1)
141    _test(EiffelMetaClass2)
142