added forced package imports
This commit is contained in:
parent
0e2ffdbbb1
commit
ef9022c6eb
943 changed files with 125530 additions and 16 deletions
205
lib/zope/interface/advice.py
Normal file
205
lib/zope/interface/advice.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2003 Zope Foundation and Contributors.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# This software is subject to the provisions of the Zope Public License,
|
||||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
|
||||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
|
||||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
##############################################################################
|
||||
"""Class advice.
|
||||
|
||||
This module was adapted from 'protocols.advice', part of the Python
|
||||
Enterprise Application Kit (PEAK). Please notify the PEAK authors
|
||||
(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or
|
||||
Zope-specific changes are required, so that the PEAK version of this module
|
||||
can be kept in sync.
|
||||
|
||||
PEAK is a Python application framework that interoperates with (but does
|
||||
not require) Zope 3 and Twisted. It provides tools for manipulating UML
|
||||
models, object-relational persistence, aspect-oriented programming, and more.
|
||||
Visit the PEAK home page at http://peak.telecommunity.com for more information.
|
||||
"""
|
||||
|
||||
from types import FunctionType
|
||||
try:
|
||||
from types import ClassType
|
||||
except ImportError:
|
||||
__python3 = True
|
||||
else:
|
||||
__python3 = False
|
||||
|
||||
import sys
|
||||
|
||||
def getFrameInfo(frame):
|
||||
"""Return (kind,module,locals,globals) for a frame
|
||||
|
||||
'kind' is one of "exec", "module", "class", "function call", or "unknown".
|
||||
"""
|
||||
|
||||
f_locals = frame.f_locals
|
||||
f_globals = frame.f_globals
|
||||
|
||||
sameNamespace = f_locals is f_globals
|
||||
hasModule = '__module__' in f_locals
|
||||
hasName = '__name__' in f_globals
|
||||
|
||||
sameName = hasModule and hasName
|
||||
sameName = sameName and f_globals['__name__']==f_locals['__module__']
|
||||
|
||||
module = hasName and sys.modules.get(f_globals['__name__']) or None
|
||||
|
||||
namespaceIsModule = module and module.__dict__ is f_globals
|
||||
|
||||
if not namespaceIsModule:
|
||||
# some kind of funky exec
|
||||
kind = "exec"
|
||||
elif sameNamespace and not hasModule:
|
||||
kind = "module"
|
||||
elif sameName and not sameNamespace:
|
||||
kind = "class"
|
||||
elif not sameNamespace:
|
||||
kind = "function call"
|
||||
else: # pragma: no cover
|
||||
# How can you have f_locals is f_globals, and have '__module__' set?
|
||||
# This is probably module-level code, but with a '__module__' variable.
|
||||
kind = "unknown"
|
||||
return kind, module, f_locals, f_globals
|
||||
|
||||
|
||||
def addClassAdvisor(callback, depth=2):
|
||||
"""Set up 'callback' to be passed the containing class upon creation
|
||||
|
||||
This function is designed to be called by an "advising" function executed
|
||||
in a class suite. The "advising" function supplies a callback that it
|
||||
wishes to have executed when the containing class is created. The
|
||||
callback will be given one argument: the newly created containing class.
|
||||
The return value of the callback will be used in place of the class, so
|
||||
the callback should return the input if it does not wish to replace the
|
||||
class.
|
||||
|
||||
The optional 'depth' argument to this function determines the number of
|
||||
frames between this function and the targeted class suite. 'depth'
|
||||
defaults to 2, since this skips this function's frame and one calling
|
||||
function frame. If you use this function from a function called directly
|
||||
in the class suite, the default will be correct, otherwise you will need
|
||||
to determine the correct depth yourself.
|
||||
|
||||
This function works by installing a special class factory function in
|
||||
place of the '__metaclass__' of the containing class. Therefore, only
|
||||
callbacks *after* the last '__metaclass__' assignment in the containing
|
||||
class will be executed. Be sure that classes using "advising" functions
|
||||
declare any '__metaclass__' *first*, to ensure all callbacks are run."""
|
||||
# This entire approach is invalid under Py3K. Don't even try to fix
|
||||
# the coverage for this block there. :(
|
||||
if __python3: # pragma: no cover
|
||||
raise TypeError('Class advice impossible in Python3')
|
||||
|
||||
frame = sys._getframe(depth)
|
||||
kind, module, caller_locals, caller_globals = getFrameInfo(frame)
|
||||
|
||||
# This causes a problem when zope interfaces are used from doctest.
|
||||
# In these cases, kind == "exec".
|
||||
#
|
||||
#if kind != "class":
|
||||
# raise SyntaxError(
|
||||
# "Advice must be in the body of a class statement"
|
||||
# )
|
||||
|
||||
previousMetaclass = caller_locals.get('__metaclass__')
|
||||
if __python3: # pragma: no cover
|
||||
defaultMetaclass = caller_globals.get('__metaclass__', type)
|
||||
else:
|
||||
defaultMetaclass = caller_globals.get('__metaclass__', ClassType)
|
||||
|
||||
|
||||
def advise(name, bases, cdict):
|
||||
|
||||
if '__metaclass__' in cdict:
|
||||
del cdict['__metaclass__']
|
||||
|
||||
if previousMetaclass is None:
|
||||
if bases:
|
||||
# find best metaclass or use global __metaclass__ if no bases
|
||||
meta = determineMetaclass(bases)
|
||||
else:
|
||||
meta = defaultMetaclass
|
||||
|
||||
elif isClassAdvisor(previousMetaclass):
|
||||
# special case: we can't compute the "true" metaclass here,
|
||||
# so we need to invoke the previous metaclass and let it
|
||||
# figure it out for us (and apply its own advice in the process)
|
||||
meta = previousMetaclass
|
||||
|
||||
else:
|
||||
meta = determineMetaclass(bases, previousMetaclass)
|
||||
|
||||
newClass = meta(name,bases,cdict)
|
||||
|
||||
# this lets the callback replace the class completely, if it wants to
|
||||
return callback(newClass)
|
||||
|
||||
# introspection data only, not used by inner function
|
||||
advise.previousMetaclass = previousMetaclass
|
||||
advise.callback = callback
|
||||
|
||||
# install the advisor
|
||||
caller_locals['__metaclass__'] = advise
|
||||
|
||||
|
||||
def isClassAdvisor(ob):
|
||||
"""True if 'ob' is a class advisor function"""
|
||||
return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass')
|
||||
|
||||
|
||||
def determineMetaclass(bases, explicit_mc=None):
|
||||
"""Determine metaclass from 1+ bases and optional explicit __metaclass__"""
|
||||
|
||||
meta = [getattr(b,'__class__',type(b)) for b in bases]
|
||||
|
||||
if explicit_mc is not None:
|
||||
# The explicit metaclass needs to be verified for compatibility
|
||||
# as well, and allowed to resolve the incompatible bases, if any
|
||||
meta.append(explicit_mc)
|
||||
|
||||
if len(meta)==1:
|
||||
# easy case
|
||||
return meta[0]
|
||||
|
||||
candidates = minimalBases(meta) # minimal set of metaclasses
|
||||
|
||||
if not candidates: # pragma: no cover
|
||||
# they're all "classic" classes
|
||||
assert(not __python3) # This should not happen under Python 3
|
||||
return ClassType
|
||||
|
||||
elif len(candidates)>1:
|
||||
# We could auto-combine, but for now we won't...
|
||||
raise TypeError("Incompatible metatypes",bases)
|
||||
|
||||
# Just one, return it
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def minimalBases(classes):
|
||||
"""Reduce a list of base classes to its ordered minimum equivalent"""
|
||||
|
||||
if not __python3: # pragma: no cover
|
||||
classes = [c for c in classes if c is not ClassType]
|
||||
candidates = []
|
||||
|
||||
for m in classes:
|
||||
for n in classes:
|
||||
if issubclass(n,m) and m is not n:
|
||||
break
|
||||
else:
|
||||
# m has no subclasses in 'classes'
|
||||
if m in candidates:
|
||||
candidates.remove(m) # ensure that we're later in the list
|
||||
candidates.append(m)
|
||||
|
||||
return candidates
|
||||
Loading…
Add table
Add a link
Reference in a new issue