import pkg_resources import sys from pyramid.exceptions import ConfigurationError from pyramid.path import package_of class DottedNameResolver(object): """ This class resolves dotted name references to 'global' Python objects (objects which can be imported) to those objects. Two dotted name styles are supported during deserialization: - ``pkg_resources``-style dotted names where non-module attributes of a package are separated from the rest of the path using a ':' e.g. ``package.module:attr``. - ``zope.dottedname``-style dotted names where non-module attributes of a package are separated from the rest of the path using a '.' e.g. ``package.module.attr``. These styles can be used interchangeably. If the serialization contains a ``:`` (colon), the ``pkg_resources`` resolution mechanism will be chosen, otherwise the ``zope.dottedname`` resolution mechanism will be chosen. The constructor accepts a single argument named ``package`` which should be a one of: - a Python module or package object - A fully qualified (not relative) dotted name to a module or package - The value ``None`` The ``package`` is used when relative dotted names are supplied to the resolver's ``resolve`` and ``maybe_resolve`` methods. A dotted name which has a ``.`` (dot) or ``:`` (colon) as its first character is treated as relative. If the value ``None`` is supplied as the package name, the resolver will only be able to resolve fully qualified (not relative) names. Any attempt to resolve a relative name when the ``package`` is ``None`` will result in an :exc:`pyramid.config.ConfigurationError` exception. If a *module* or *module name* (as opposed to a package or package name) is supplied as ``package``, its containing package is computed and this package used to derive the package name (all names are resolved relative to packages, never to modules). For example, if the ``package`` argument to this type was passed the string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to the ``resolve`` method, the resulting import would be for ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module object, not a package object. If a *package* or *package name* (as opposed to a module or module name) is supplied as ``package``, this package will be used to relative compute dotted names. For example, if the ``package`` argument to this type was passed the string ``xml.dom``, and ``.minidom`` is supplied to the ``resolve`` method, the resulting import would be for ``xml.minidom``. When a dotted name cannot be resolved, a :class:`pyramid.exceptions.ConfigurationError` error is raised. """ def __init__(self, package): if package is None: self.package_name = None self.package = None else: if isinstance(package, basestring): try: __import__(package) except ImportError: raise ConfigurationError( 'The dotted name %r cannot be imported' % (package,)) package = sys.modules[package] self.package = package_of(package) self.package_name = self.package.__name__ def _pkg_resources_style(self, value): """ package.module:attr style """ if value.startswith('.') or value.startswith(':'): if not self.package_name: raise ConfigurationError( 'relative name %r irresolveable without ' 'package_name' % (value,)) if value in ['.', ':']: value = self.package_name else: value = self.package_name + value return pkg_resources.EntryPoint.parse( 'x=%s' % value).load(False) def _zope_dottedname_style(self, value): """ package.module.attr style """ module = self.package_name if not module: module = None if value == '.': if module is None: raise ConfigurationError( 'relative name %r irresolveable without package' % (value,) ) name = module.split('.') else: name = value.split('.') if not name[0]: if module is None: raise ConfigurationError( 'relative name %r irresolveable without ' 'package' % (value,) ) module = module.split('.') name.pop(0) while not name[0]: module.pop() name.pop(0) name = module + name used = name.pop(0) found = __import__(used) for n in name: used += '.' + n try: found = getattr(found, n) except AttributeError: __import__(used) found = getattr(found, n) # pragma: no cover return found def resolve(self, dotted): if not isinstance(dotted, basestring): raise ConfigurationError('%r is not a string' % (dotted,)) return self.maybe_resolve(dotted) def maybe_resolve(self, dotted): if isinstance(dotted, basestring): if ':' in dotted: return self._pkg_resources_style(dotted) else: return self._zope_dottedname_style(dotted) return dotted