##############################################################################
#
# Copyright (c) 2004 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.
#
##############################################################################
"""Component Inspection Utilities
"""
__docformat__ = 'restructuredtext'
import six
import types
import zope.interface.declarations
from zope.component import getGlobalSiteManager
from zope.component.interfaces import IFactory
from zope.i18nmessageid import ZopeMessageFactory as _
from zope.interface import Interface
from zope.interface.interface import InterfaceClass
from zope.publisher.interfaces import IRequest
from zope.app.apidoc.classregistry import classRegistry
from zope.app.apidoc.utilities import relativizePath, truncateSysPath
from zope.app.apidoc.utilities import getPythonPath, isReferencable, renderText
from zope.app.apidoc.utilitymodule import utilitymodule
SPECIFIC_INTERFACE_LEVEL = 1
EXTENDED_INTERFACE_LEVEL = 2
GENERIC_INTERFACE_LEVEL = 4
def _adapterishRegistrations(registry):
for registrations in (registry.registeredAdapters,
registry.registeredSubscriptionAdapters,
registry.registeredHandlers):
for r in registrations():
yield r
def _ignore_adapter(reg, withViews=False):
return (
# Ignore adapters that have no required interfaces
not reg.required
# Ignore views
or (not withViews and reg.required[-1].isOrExtends(IRequest)))
[docs]def getRequiredAdapters(iface, withViews=False):
"""Get global adapter registrations where the specified interface is required."""
gsm = getGlobalSiteManager()
for reg in _adapterishRegistrations(gsm):
if _ignore_adapter(reg, withViews):
continue
# Only get the adapters for which this interface is required
for required_iface in reg.required:
if iface.isOrExtends(required_iface):
yield reg
[docs]def getProvidedAdapters(iface, withViews=False):
"""Get global adapter registrations where this interface is provided."""
gsm = getGlobalSiteManager()
for reg in _adapterishRegistrations(gsm):
if _ignore_adapter(reg, withViews):
continue
# Only get adapters for which this interface is provided
if reg.provided is None or not reg.provided.isOrExtends(iface):
continue
yield reg
[docs]def filterAdapterRegistrations(regs, iface, level=SPECIFIC_INTERFACE_LEVEL):
"""Return only those registrations that match the specifed level"""
for reg in regs:
if level & GENERIC_INTERFACE_LEVEL:
for required_iface in reg.required:
if required_iface in (Interface, None):
yield reg
continue
if level & EXTENDED_INTERFACE_LEVEL:
for required_iface in reg.required:
if required_iface is not Interface and \
iface.extends(required_iface):
yield reg
continue
if level & SPECIFIC_INTERFACE_LEVEL:
for required_iface in reg.required:
if required_iface is iface:
yield reg
continue
[docs]def getClasses(iface):
"""Get the classes that implement this interface."""
return classRegistry.getClassesThatImplement(iface)
[docs]def getFactories(iface):
"""Return the global factory registrations, who will return objects providing this
interface."""
gsm = getGlobalSiteManager()
for reg in gsm.registeredUtilities():
if reg.provided is not IFactory:
continue
interfaces = reg.component.getInterfaces()
if hasattr(interfaces, 'isOrExtends'): # Single interface
interfaces = (interfaces,)
for interface in interfaces:
if interface.isOrExtends(iface):
yield reg
break
[docs]def getUtilities(iface):
"""Return all global utility registrations that provide the interface."""
gsm = getGlobalSiteManager()
for reg in gsm.registeredUtilities():
if reg.provided.isOrExtends(iface):
yield reg
[docs]def getRealFactory(factory):
"""Get the real factory.
Sometimes the original factory is masked by functions. If the function
keeps track of the original factory, use it.
"""
# Remove all wrappers until none are found anymore.
while hasattr(factory, 'factory'):
factory = factory.factory
# If we have an instance, return its class
if not hasattr(factory, '__name__'):
return factory.__class__
return factory
[docs]def getParserInfoInfoDictionary(info):
"""Return a PT-friendly info dictionary for a parser info object."""
return {'file': relativizePath(info.file),
'url': truncateSysPath(info.file).replace('\\', '/'),
'line': info.line,
'eline': info.eline,
'column': info.column,
'ecolumn': info.ecolumn}
[docs]def getInterfaceInfoDictionary(iface):
"""Return a PT-friendly info dictionary for an interface."""
if isinstance(iface, zope.interface.declarations.Implements):
iface = iface.inherit
if iface is None:
return None
return {'module': getattr(iface, '__module__', _('<unknown>')),
'name': getattr(iface, '__name__', _('<unknown>'))}
[docs]def getTypeInfoDictionary(type):
"""Return a PT-friendly info dictionary for a type."""
path = getPythonPath(type)
return {'name': type.__name__,
'module': type.__module__,
'url': isReferencable(path) and path.replace('.', '/') or None}
[docs]def getSpecificationInfoDictionary(spec):
"""Return an info dictionary for one specification."""
info = {'isInterface': False, 'isType': False}
if zope.interface.interfaces.IInterface.providedBy(spec):
info.update(getInterfaceInfoDictionary(spec))
info['isInterface'] = True
else:
info.update(getTypeInfoDictionary(spec.inherit))
info['isType'] = True
return info
[docs]def getAdapterInfoDictionary(reg):
"""Return a PT-friendly info dictionary for an adapter registration."""
factory = getRealFactory(reg.factory)
path = getPythonPath(factory)
url = None
if isReferencable(path):
url = path.replace('.', '/')
if isinstance(reg.info, six.string_types):
doc = reg.info
zcml = None
else:
doc = None
zcml = getParserInfoInfoDictionary(reg.info)
name = getattr(reg, 'name', u'')
name = name.decode('utf-8') if isinstance(name, bytes) else name
return {
'provided': getInterfaceInfoDictionary(reg.provided),
'required': [getSpecificationInfoDictionary(iface)
for iface in reg.required
if iface is not None],
'name': name,
'factory': path,
'factory_url': url,
'doc': doc,
'zcml': zcml
}
[docs]def getFactoryInfoDictionary(reg):
"""Return a PT-friendly info dictionary for a factory."""
factory = reg.component
callable = factory
# Usually only zope.component.factory.Factory instances have this attribute
if IFactory.providedBy(factory) and hasattr(factory, '_callable'):
callable = factory._callable
elif hasattr(callable, '__class__'):
callable = callable.__class__
path = getPythonPath(callable)
return {'name': six.text_type(reg.name) or _('<i>no name</i>'),
'title': getattr(factory, 'title', u''),
'description': renderText(getattr(factory, 'description', u''),
module=callable.__module__),
'url': isReferencable(path) and path.replace('.', '/') or None}
[docs]def getUtilityInfoDictionary(reg):
"""Return a PT-friendly info dictionary for a factory."""
component = reg.component
# Check whether we have an instance of some custom type or not
# Unfortunately, a lot of utilities have a `__name__` attribute, so we
# cannot simply check for its absence
# TODO: Once we support passive display of instances, this insanity can go
# away.
if not isinstance(component, (types.MethodType, types.FunctionType,
six.class_types,
InterfaceClass)):
component = getattr(component, '__class__', component)
path = getPythonPath(component)
# provided interface id
iface_id = '%s.%s' % (reg.provided.__module__, reg.provided.getName())
# Determine the URL
if isinstance(component, InterfaceClass):
url = 'Interface/%s' % path
else:
url = None
if isReferencable(path):
url = 'Code/%s' % path.replace('.', '/')
return {'name': six.text_type(reg.name) or _('<i>no name</i>'),
'url_name': utilitymodule.encodeName(reg.name or '__noname__'),
'iface_id': iface_id,
'path': path,
'url': url}