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: #pragma NO COVER Python > 3.x 32 __python3 = True 33else: #pragma NO COVER Python < 3.x 34 __python3 = False 35 36import sys 37 38def getFrameInfo(frame): 39 """Return (kind,module,locals,globals) for a frame 40 41 'kind' is one of "exec", "module", "class", "function call", or "unknown". 42 """ 43 44 f_locals = frame.f_locals 45 f_globals = frame.f_globals 46 47 sameNamespace = f_locals is f_globals 48 hasModule = '__module__' in f_locals 49 hasName = '__name__' in f_globals 50 51 sameName = hasModule and hasName 52 sameName = sameName and f_globals['__name__']==f_locals['__module__'] 53 54 module = hasName and sys.modules.get(f_globals['__name__']) or None 55 56 namespaceIsModule = module and module.__dict__ is f_globals 57 58 if not namespaceIsModule: 59 # some kind of funky exec 60 kind = "exec" 61 elif sameNamespace and not hasModule: 62 kind = "module" 63 elif sameName and not sameNamespace: 64 kind = "class" 65 elif not sameNamespace: 66 kind = "function call" 67 else: # pragma NO COVER 68 # How can you have f_locals is f_globals, and have '__module__' set? 69 # This is probably module-level code, but with a '__module__' variable. 70 kind = "unknown" 71 return kind, module, f_locals, f_globals 72 73 74def addClassAdvisor(callback, depth=2): 75 """Set up 'callback' to be passed the containing class upon creation 76 77 This function is designed to be called by an "advising" function executed 78 in a class suite. The "advising" function supplies a callback that it 79 wishes to have executed when the containing class is created. The 80 callback will be given one argument: the newly created containing class. 81 The return value of the callback will be used in place of the class, so 82 the callback should return the input if it does not wish to replace the 83 class. 84 85 The optional 'depth' argument to this function determines the number of 86 frames between this function and the targeted class suite. 'depth' 87 defaults to 2, since this skips this function's frame and one calling 88 function frame. If you use this function from a function called directly 89 in the class suite, the default will be correct, otherwise you will need 90 to determine the correct depth yourself. 91 92 This function works by installing a special class factory function in 93 place of the '__metaclass__' of the containing class. Therefore, only 94 callbacks *after* the last '__metaclass__' assignment in the containing 95 class will be executed. Be sure that classes using "advising" functions 96 declare any '__metaclass__' *first*, to ensure all callbacks are run.""" 97 # This entire approach is invalid under Py3K. Don't even try to fix 98 # the coverage for this block there. :( 99 if __python3: #pragma NO COVER 100 raise TypeError('Class advice impossible in Python3') 101 102 frame = sys._getframe(depth) 103 kind, module, caller_locals, caller_globals = getFrameInfo(frame) 104 105 # This causes a problem when zope interfaces are used from doctest. 106 # In these cases, kind == "exec". 107 # 108 #if kind != "class": 109 # raise SyntaxError( 110 # "Advice must be in the body of a class statement" 111 # ) 112 113 previousMetaclass = caller_locals.get('__metaclass__') 114 if __python3: # pragma NO COVER 115 defaultMetaclass = caller_globals.get('__metaclass__', type) 116 else: 117 defaultMetaclass = caller_globals.get('__metaclass__', ClassType) 118 119 120 def advise(name, bases, cdict): 121 122 if '__metaclass__' in cdict: 123 del cdict['__metaclass__'] 124 125 if previousMetaclass is None: 126 if bases: 127 # find best metaclass or use global __metaclass__ if no bases 128 meta = determineMetaclass(bases) 129 else: 130 meta = defaultMetaclass 131 132 elif isClassAdvisor(previousMetaclass): 133 # special case: we can't compute the "true" metaclass here, 134 # so we need to invoke the previous metaclass and let it 135 # figure it out for us (and apply its own advice in the process) 136 meta = previousMetaclass 137 138 else: 139 meta = determineMetaclass(bases, previousMetaclass) 140 141 newClass = meta(name,bases,cdict) 142 143 # this lets the callback replace the class completely, if it wants to 144 return callback(newClass) 145 146 # introspection data only, not used by inner function 147 advise.previousMetaclass = previousMetaclass 148 advise.callback = callback 149 150 # install the advisor 151 caller_locals['__metaclass__'] = advise 152 153 154def isClassAdvisor(ob): 155 """True if 'ob' is a class advisor function""" 156 return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass') 157 158 159def determineMetaclass(bases, explicit_mc=None): 160 """Determine metaclass from 1+ bases and optional explicit __metaclass__""" 161 162 meta = [getattr(b,'__class__',type(b)) for b in bases] 163 164 if explicit_mc is not None: 165 # The explicit metaclass needs to be verified for compatibility 166 # as well, and allowed to resolve the incompatible bases, if any 167 meta.append(explicit_mc) 168 169 if len(meta)==1: 170 # easy case 171 return meta[0] 172 173 candidates = minimalBases(meta) # minimal set of metaclasses 174 175 if not candidates: #pragma NO COVER 176 # they're all "classic" classes 177 assert(not __python3) # This should not happen under Python 3 178 return ClassType 179 180 elif len(candidates)>1: 181 # We could auto-combine, but for now we won't... 182 raise TypeError("Incompatible metatypes",bases) 183 184 # Just one, return it 185 return candidates[0] 186 187 188def minimalBases(classes): 189 """Reduce a list of base classes to its ordered minimum equivalent""" 190 191 if not __python3: #pragma NO COVER 192 classes = [c for c in classes if c is not ClassType] 193 candidates = [] 194 195 for m in classes: 196 for n in classes: 197 if issubclass(n,m) and m is not n: 198 break 199 else: 200 # m has no subclasses in 'classes' 201 if m in candidates: 202 candidates.remove(m) # ensure that we're later in the list 203 candidates.append(m) 204 205 return candidates 206 207