blob: 1dd7e6c17f04e078b672d35ab0d746deda00dd73 [file] [log] [blame]
Jeremy Hyltona0033162002-07-11 21:08:06 +00001"""Support Eiffel-style preconditions and postconditions."""
2
3from new import function
4
5class EiffelBaseMetaClass(type):
6
Jeremy Hylton98013be2002-07-12 15:42:10 +00007 def __new__(meta, name, bases, dict):
8 meta.convert_methods(dict)
9 return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases,
10 dict)
11
Jeremy Hyltona0033162002-07-11 21:08:06 +000012 def convert_methods(cls, dict):
13 """Replace functions in dict with EiffelMethod wrappers.
14
15 The dict is modified in place.
16
17 If a method ends in _pre or _post, it is removed from the dict
18 regardless of whether there is a corresponding method.
19 """
20 # find methods with pre or post conditions
21 methods = []
Jeremy Hyltona0033162002-07-11 21:08:06 +000022 for k, v in dict.iteritems():
23 if k.endswith('_pre') or k.endswith('_post'):
24 assert isinstance(v, function)
Jeremy Hyltona0033162002-07-11 21:08:06 +000025 elif isinstance(v, function):
26 methods.append(k)
27 for m in methods:
28 pre = dict.get("%s_pre" % m)
29 post = dict.get("%s_post" % m)
30 if pre or post:
31 dict[k] = cls.make_eiffel_method(dict[m], pre, post)
32
33 convert_methods = classmethod(convert_methods)
34
Jeremy Hylton1808fbc2002-07-11 21:17:26 +000035class EiffelMetaClass1(EiffelBaseMetaClass):
36 # an implementation of the "eiffel" meta class that uses nested functions
37
Jeremy Hyltona0033162002-07-11 21:08:06 +000038 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
52 make_eiffel_method = staticmethod(make_eiffel_method)
53
Jeremy Hyltona0033162002-07-11 21:08:06 +000054class EiffelMethodWrapper:
55
56 def __init__(self, inst, descr):
57 self._inst = inst
58 self._descr = descr
59
60 def __call__(self, *args, **kwargs):
61 return self._descr.callmethod(self._inst, args, kwargs)
Jeremy Hyltona0033162002-07-11 21:08:06 +000062
63class EiffelDescriptor(object):
64
65 def __init__(self, func, pre, post):
66 self._func = func
67 self._pre = pre
68 self._post = post
69
70 self.__name__ = func.__name__
71 self.__doc__ = func.__doc__
72
73 def __get__(self, obj, cls):
74 return EiffelMethodWrapper(obj, self)
75
76 def callmethod(self, inst, args, kwargs):
77 if self._pre:
78 self._pre(inst, *args, **kwargs)
79 x = self._func(inst, *args, **kwargs)
80 if self._post:
81 self._post(inst, x, *args, **kwargs)
82 return x
83
Jeremy Hylton98013be2002-07-12 15:42:10 +000084class EiffelMetaClass2(EiffelBaseMetaClass):
Jeremy Hyltona0033162002-07-11 21:08:06 +000085 # an implementation of the "eiffel" meta class that uses descriptors
86
87 make_eiffel_method = EiffelDescriptor
88
89def _test(metaclass):
90 class Eiffel:
91 __metaclass__ = metaclass
92
93 class Test(Eiffel):
94
95 def m(self, arg):
96 """Make it a little larger"""
97 return arg + 1
98
99 def m2(self, arg):
100 """Make it a little larger"""
101 return arg + 1
102
103 def m2_pre(self, arg):
104 assert arg > 0
105
106 def m2_post(self, result, arg):
107 assert result > arg
108
109 class Sub(Test):
110 def m2(self, arg):
111 return arg**2
112 def m2_post(self, Result, arg):
113 super(Sub, self).m2_post(Result, arg)
114 assert Result < 100
115
116 t = Test()
117 t.m(1)
118 t.m2(1)
119 try:
120 t.m2(0)
121 except AssertionError:
122 pass
123 else:
124 assert False
125
126 s = Sub()
127 try:
128 s.m2(1)
129 except AssertionError:
130 pass # result == arg
131 else:
132 assert False
133 try:
134 s.m2(10)
135 except AssertionError:
136 pass # result == 100
137 else:
138 assert False
Jeremy Hyltonc9329ca2002-07-11 21:09:34 +0000139 s.m2(5)
Jeremy Hyltona0033162002-07-11 21:08:06 +0000140
141if __name__ == "__main__":
142 _test(EiffelMetaClass1)
143 _test(EiffelMetaClass2)
144