1 """This module tells you if an object's signature matches a class.
2 It lets you know if one class can substitute for another,
3 even if they are unrelated in an OO hierarchy.
4
5 This allows you to begin to check that a foreign object will have the
6 necessary properties to be used in your code. It is more of a
7 functional check than C{isimplements}().
8
9 For instance, if your code needs a write() method, you
10 can check for it like this::
11
12 class some_template(object):
13 def write(self, somearg):
14 pass
15 #
16 g_implements.check(x, some_template)
17 #... at this point, we can be assured that a
18 # call to x.write(3) is possible.
19 # (Note that we don't know what the call will *do*,
20 # merely that the proper method of x exists and that
21 # it can take an argument.
22
23 Semi-kluge: if you have an instance, C{i} that needs to convince
24 C{g_implements} that it can actually behave as class C{C},
25 then set C{i.__g_implements__ = ['C']}.
26 Generally, this can be a list or set of class names.
27 This trick is useful for C-implementations of python classes
28 (where introspection cannot get all the necessary information) or
29 for fancy python classes that use C{__getattribute__} and similar.
30 """
31
32 import inspect
33
34 _cache = {}
35
36
37
38
39
40
41
42 MAXCACHE = 100
43
44 Optional = 'optional'
45 Varargs = 'varargs'
50
51 Ignore = set( ['__class__', '__delattr__', '__module__', '__new__',
52 '__doc__', '__dict__', '__getattribute__',
53 '__hash__',
54 '__repr__', '__str__',
55 '__reduce__', '__reduce_ex__', '__setattr__',
56 '__weakref__', '__slots__'
57 ]
58 )
59
60
61 -def why(instance, classobj, ignore=None):
62 """Explains why instance doesn't implement the classobj.
63 @param instance: an instance of a class
64 @param classobj: a class object
65 @return: None (if it does) or a string explanation of why it doesn't
66 @rtype None or str
67 """
68 try:
69 c = instance.__class__
70 except AttributeError:
71 return "Instance (arg 1) has no __class__ attribute."
72 key = (c, classobj)
73 try:
74 return _cache[key]
75 except KeyError:
76 pass
77 assert MAXCACHE >= 0
78 while len(_cache) > MAXCACHE:
79 _cache.popitem()
80
81 try:
82
83
84 if classobj.__name__ in instance.__g_implements__:
85 _cache[key] = None
86 return None
87 except AttributeError:
88 pass
89
90 for mem in dir(classobj):
91 if mem in Ignore or (ignore is not None and mem in ignore):
92 continue
93 v = getattr(classobj, mem)
94 gis = getattr(v, 'g_implements', Strict)
95 if gis == Optional:
96 continue
97 if not hasattr(c, mem):
98 _cache[key] = 'Missing member: %s; %s does not implement %s' % (mem, str(type(instance)), str(classobj))
99 return _cache[key]
100 com = getattr(c, mem)
101 if callable(gis) and gis(mem, com):
102 continue
103 if inspect.ismethod(com):
104 if inspect.ismethod(v):
105 vargs, vv, vk, vdef = inspect.getargspec(v.im_func)
106 margs, mv, mk, mdef = inspect.getargspec(com.im_func)
107
108 if mdef is None:
109 matchlen = len(margs)
110 else:
111 matchlen = len(margs)-len(mdef)
112 if vargs[:matchlen] != margs[:matchlen]:
113 if gis == Varargs:
114 continue
115 _cache[key] = "Method argument list does not match for %s: %s instead of %s; type=%s does not implement %s" \
116 % (mem, margs[:matchlen], vargs[:matchlen],
117 str(type(instance)), str(classobj))
118 return _cache[key]
119 elif inspect.ismethoddescriptor(v):
120 continue
121 elif type(com) != type(v):
122 _cache[key] = 'Wrong type for %s: %s instead of %s; type=%s does not implement %s' \
123 % (mem, type(com), type(v),
124 str(type(instance)), str(classobj)
125 )
126 return _cache[key]
127 _cache[key] = None
128 return _cache[key]
129
130
131 -def impl(instance, classobj):
132 """Tells you if an instance of an object implements a class.
133 By implements, I mean that the instance supplies every member
134 that the class supplies, and every member has the same type.
135 The instance may have *more* members, of course.
136 Functions require that the argument names must match, too
137 at least as far as the required arguments in the classobj's function.
138
139 The match may be made looser by adding a g_implements attribute
140 to various class members. Possibilities for the value
141 are Optional, Strict, Vartype, Varargs, or you can give
142 a two-argument function, and that function will be called
143 to decide whether the match is acceptable or not.
144 """
145 return why(instance, classobj) is None
146
147
149 """This is a decorator for a function.
150 make_optional implies make_varargs.
151 """
152 x.g_implements = Optional
153 return x
154
159
164
169
170
172 """Exception raised by L{check} to indicate failure.
173 """
176
177
178 -def check(instance, classobj, ignore=None):
179 """Require that C{instance} has the attributes defined by C{classobj}.
180 This function either quietly succeeds or it raises a GITypeError exception.
181 @type instance: any instance of a class (i.e. anything with a C{__class__} attribute).
182 @type classobj: any class object (i.e. not an instance, typically).
183 @param instance: the thing to check
184 @param classobj: a class that provides a pattern of attributes.
185 @raise GITypeError: C{instance} doesn't provide all the features of C{classobj}.
186 """
187 w = why(instance, classobj, ignore=ignore)
188 if w is not None:
189 raise GITypeError, w
190
191
197
201
207
213
215 x = 0
216 y = 'x'
219 w = []
220 - def foo(self, a, b):
222
223
226
231
232 -class _tc1c(_tc0a, _tc0b):
234
240
242 assert not impl(_tc1(), _tc2)
243 check(_tc1(), _tc1a)
244 assert not impl(_tc1(), _tc3)
245 assert not impl(_tc1(), _tc1b)
246 check(_tc1b(), _tc1)
247 check(_tc1c(), _tc1)
248 check(_tc1(), _tc1c)
249 assert not impl(_tc1(), _tc2a)
250
251 if __name__ == '__main__':
252 test()
253