119 lines
3.8 KiB
Python
119 lines
3.8 KiB
Python
##############################################################################
|
|
#
|
|
# 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
|
|
|
|
__all__ = [
|
|
'determineMetaclass',
|
|
'getFrameInfo',
|
|
'isClassAdvisor',
|
|
'minimalBases',
|
|
]
|
|
|
|
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 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 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"""
|
|
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
|