blob: ef7f0c6e6095d5999117c2b4fca2377733ba719d [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
Guido van Rossum75b64e62005-01-16 00:16:11 +000012 @classmethod
Jeremy Hyltona0033162002-07-11 21:08:06 +000013 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 = []
Collin Winter6f2df4d2007-07-17 20:59:35 +000023 for k, v in dict.items():
Jeremy Hyltona0033162002-07-11 21:08:06 +000024 if k.endswith('_pre') or k.endswith('_post'):
25 assert isinstance(v, function)
Jeremy Hyltona0033162002-07-11 21:08:06 +000026 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[k] = cls.make_eiffel_method(dict[m], pre, post)
33
Jeremy Hylton1808fbc2002-07-11 21:17:26 +000034class EiffelMetaClass1(EiffelBaseMetaClass):
35 # an implementation of the "eiffel" meta class that uses nested functions
36
Guido van Rossum75b64e62005-01-16 00:16:11 +000037 @staticmethod
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
Tim Peterse6ddc8b2004-07-18 05:56:09 +000051
Jeremy Hyltona0033162002-07-11 21:08:06 +000052class 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)
Jeremy Hyltona0033162002-07-11 21:08:06 +000060
61class EiffelDescriptor(object):
62
63 def __init__(self, func, pre, post):
64 self._func = func
65 self._pre = pre
66 self._post = post
Tim Peterse6ddc8b2004-07-18 05:56:09 +000067
Jeremy Hyltona0033162002-07-11 21:08:06 +000068 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
Jeremy Hylton98013be2002-07-12 15:42:10 +000082class EiffelMetaClass2(EiffelBaseMetaClass):
Jeremy Hyltona0033162002-07-11 21:08:06 +000083 # 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
Jeremy Hyltonc9329ca2002-07-11 21:09:34 +0000137 s.m2(5)
Jeremy Hyltona0033162002-07-11 21:08:06 +0000138
139if __name__ == "__main__":
140 _test(EiffelMetaClass1)
141 _test(EiffelMetaClass2)