Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1############################################################################## 

2# 

3# Copyright (c) 2003 Zope Foundation and Contributors. 

4# All Rights Reserved. 

5# 

6# This software is subject to the provisions of the Zope Public License, 

7# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 

8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 

9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 

11# FOR A PARTICULAR PURPOSE. 

12# 

13############################################################################## 

14"""Class advice. 

15 

16This module was adapted from 'protocols.advice', part of the Python 

17Enterprise Application Kit (PEAK). Please notify the PEAK authors 

18(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or 

19Zope-specific changes are required, so that the PEAK version of this module 

20can be kept in sync. 

21 

22PEAK is a Python application framework that interoperates with (but does 

23not require) Zope 3 and Twisted. It provides tools for manipulating UML 

24models, object-relational persistence, aspect-oriented programming, and more. 

25Visit the PEAK home page at http://peak.telecommunity.com for more information. 

26""" 

27 

28from types import FunctionType 

29try: 

30 from types import ClassType 

31except ImportError: 

32 __python3 = True 

33else: 

34 __python3 = False 

35 

36__all__ = [ 

37 'addClassAdvisor', 

38 'determineMetaclass', 

39 'getFrameInfo', 

40 'isClassAdvisor', 

41 'minimalBases', 

42] 

43 

44import sys 

45 

46def getFrameInfo(frame): 

47 """Return (kind,module,locals,globals) for a frame 

48 

49 'kind' is one of "exec", "module", "class", "function call", or "unknown". 

50 """ 

51 

52 f_locals = frame.f_locals 

53 f_globals = frame.f_globals 

54 

55 sameNamespace = f_locals is f_globals 

56 hasModule = '__module__' in f_locals 

57 hasName = '__name__' in f_globals 

58 

59 sameName = hasModule and hasName 

60 sameName = sameName and f_globals['__name__']==f_locals['__module__'] 

61 

62 module = hasName and sys.modules.get(f_globals['__name__']) or None 

63 

64 namespaceIsModule = module and module.__dict__ is f_globals 

65 

66 if not namespaceIsModule: 

67 # some kind of funky exec 

68 kind = "exec" 

69 elif sameNamespace and not hasModule: 

70 kind = "module" 

71 elif sameName and not sameNamespace: 

72 kind = "class" 

73 elif not sameNamespace: 

74 kind = "function call" 

75 else: # pragma: no cover 

76 # How can you have f_locals is f_globals, and have '__module__' set? 

77 # This is probably module-level code, but with a '__module__' variable. 

78 kind = "unknown" 

79 return kind, module, f_locals, f_globals 

80 

81 

82def addClassAdvisor(callback, depth=2): 

83 """Set up 'callback' to be passed the containing class upon creation 

84 

85 This function is designed to be called by an "advising" function executed 

86 in a class suite. The "advising" function supplies a callback that it 

87 wishes to have executed when the containing class is created. The 

88 callback will be given one argument: the newly created containing class. 

89 The return value of the callback will be used in place of the class, so 

90 the callback should return the input if it does not wish to replace the 

91 class. 

92 

93 The optional 'depth' argument to this function determines the number of 

94 frames between this function and the targeted class suite. 'depth' 

95 defaults to 2, since this skips this function's frame and one calling 

96 function frame. If you use this function from a function called directly 

97 in the class suite, the default will be correct, otherwise you will need 

98 to determine the correct depth yourself. 

99 

100 This function works by installing a special class factory function in 

101 place of the '__metaclass__' of the containing class. Therefore, only 

102 callbacks *after* the last '__metaclass__' assignment in the containing 

103 class will be executed. Be sure that classes using "advising" functions 

104 declare any '__metaclass__' *first*, to ensure all callbacks are run.""" 

105 # This entire approach is invalid under Py3K. Don't even try to fix 

106 # the coverage for this block there. :( 

107 if __python3: # pragma: no cover 

108 raise TypeError('Class advice impossible in Python3') 

109 

110 frame = sys._getframe(depth) 

111 kind, module, caller_locals, caller_globals = getFrameInfo(frame) 

112 

113 # This causes a problem when zope interfaces are used from doctest. 

114 # In these cases, kind == "exec". 

115 # 

116 #if kind != "class": 

117 # raise SyntaxError( 

118 # "Advice must be in the body of a class statement" 

119 # ) 

120 

121 previousMetaclass = caller_locals.get('__metaclass__') 

122 if __python3: # pragma: no cover 

123 defaultMetaclass = caller_globals.get('__metaclass__', type) 

124 else: 

125 defaultMetaclass = caller_globals.get('__metaclass__', ClassType) 

126 

127 

128 def advise(name, bases, cdict): 

129 

130 if '__metaclass__' in cdict: 

131 del cdict['__metaclass__'] 

132 

133 if previousMetaclass is None: 

134 if bases: 

135 # find best metaclass or use global __metaclass__ if no bases 

136 meta = determineMetaclass(bases) 

137 else: 

138 meta = defaultMetaclass 

139 

140 elif isClassAdvisor(previousMetaclass): 

141 # special case: we can't compute the "true" metaclass here, 

142 # so we need to invoke the previous metaclass and let it 

143 # figure it out for us (and apply its own advice in the process) 

144 meta = previousMetaclass 

145 

146 else: 

147 meta = determineMetaclass(bases, previousMetaclass) 

148 

149 newClass = meta(name,bases,cdict) 

150 

151 # this lets the callback replace the class completely, if it wants to 

152 return callback(newClass) 

153 

154 # introspection data only, not used by inner function 

155 advise.previousMetaclass = previousMetaclass 

156 advise.callback = callback 

157 

158 # install the advisor 

159 caller_locals['__metaclass__'] = advise 

160 

161 

162def isClassAdvisor(ob): 

163 """True if 'ob' is a class advisor function""" 

164 return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass') 

165 

166 

167def determineMetaclass(bases, explicit_mc=None): 

168 """Determine metaclass from 1+ bases and optional explicit __metaclass__""" 

169 

170 meta = [getattr(b,'__class__',type(b)) for b in bases] 

171 

172 if explicit_mc is not None: 

173 # The explicit metaclass needs to be verified for compatibility 

174 # as well, and allowed to resolve the incompatible bases, if any 

175 meta.append(explicit_mc) 

176 

177 if len(meta)==1: 

178 # easy case 

179 return meta[0] 

180 

181 candidates = minimalBases(meta) # minimal set of metaclasses 

182 

183 if not candidates: # pragma: no cover 

184 # they're all "classic" classes 

185 assert(not __python3) # This should not happen under Python 3 

186 return ClassType 

187 

188 elif len(candidates)>1: 

189 # We could auto-combine, but for now we won't... 

190 raise TypeError("Incompatible metatypes",bases) 

191 

192 # Just one, return it 

193 return candidates[0] 

194 

195 

196def minimalBases(classes): 

197 """Reduce a list of base classes to its ordered minimum equivalent""" 

198 

199 if not __python3: # pragma: no cover 

200 classes = [c for c in classes if c is not ClassType] 

201 candidates = [] 

202 

203 for m in classes: 

204 for n in classes: 

205 if issubclass(n,m) and m is not n: 

206 break 

207 else: 

208 # m has no subclasses in 'classes' 

209 if m in candidates: 

210 candidates.remove(m) # ensure that we're later in the list 

211 candidates.append(m) 

212 

213 return candidates