Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/zope/interface/advice.py : 19%

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.
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.
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"""
28from types import FunctionType
29try:
30 from types import ClassType
31except ImportError:
32 __python3 = True
33else:
34 __python3 = False
36__all__ = [
37 'addClassAdvisor',
38 'determineMetaclass',
39 'getFrameInfo',
40 'isClassAdvisor',
41 'minimalBases',
42]
44import sys
46def getFrameInfo(frame):
47 """Return (kind,module,locals,globals) for a frame
49 'kind' is one of "exec", "module", "class", "function call", or "unknown".
50 """
52 f_locals = frame.f_locals
53 f_globals = frame.f_globals
55 sameNamespace = f_locals is f_globals
56 hasModule = '__module__' in f_locals
57 hasName = '__name__' in f_globals
59 sameName = hasModule and hasName
60 sameName = sameName and f_globals['__name__']==f_locals['__module__']
62 module = hasName and sys.modules.get(f_globals['__name__']) or None
64 namespaceIsModule = module and module.__dict__ is f_globals
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
82def addClassAdvisor(callback, depth=2):
83 """Set up 'callback' to be passed the containing class upon creation
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.
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.
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')
110 frame = sys._getframe(depth)
111 kind, module, caller_locals, caller_globals = getFrameInfo(frame)
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 # )
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)
128 def advise(name, bases, cdict):
130 if '__metaclass__' in cdict:
131 del cdict['__metaclass__']
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
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
146 else:
147 meta = determineMetaclass(bases, previousMetaclass)
149 newClass = meta(name,bases,cdict)
151 # this lets the callback replace the class completely, if it wants to
152 return callback(newClass)
154 # introspection data only, not used by inner function
155 advise.previousMetaclass = previousMetaclass
156 advise.callback = callback
158 # install the advisor
159 caller_locals['__metaclass__'] = advise
162def isClassAdvisor(ob):
163 """True if 'ob' is a class advisor function"""
164 return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass')
167def determineMetaclass(bases, explicit_mc=None):
168 """Determine metaclass from 1+ bases and optional explicit __metaclass__"""
170 meta = [getattr(b,'__class__',type(b)) for b in bases]
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)
177 if len(meta)==1:
178 # easy case
179 return meta[0]
181 candidates = minimalBases(meta) # minimal set of metaclasses
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
188 elif len(candidates)>1:
189 # We could auto-combine, but for now we won't...
190 raise TypeError("Incompatible metatypes",bases)
192 # Just one, return it
193 return candidates[0]
196def minimalBases(classes):
197 """Reduce a list of base classes to its ordered minimum equivalent"""
199 if not __python3: # pragma: no cover
200 classes = [c for c in classes if c is not ClassType]
201 candidates = []
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)
213 return candidates