Source code for pyramid.path

from importlib.machinery import SOURCE_SUFFIXES
import os
import pkg_resources
import sys
from zope.interface import implementer

from pyramid.interfaces import IAssetDescriptor

init_names = ['__init__%s' % x for x in SOURCE_SUFFIXES]


def caller_path(path, level=2):
    if not os.path.isabs(path):
        module = caller_module(level + 1)
        prefix = package_path(module)
        path = os.path.join(prefix, path)
    return path


def caller_module(level=2, sys=sys):
    module_globals = sys._getframe(level).f_globals
    module_name = module_globals.get('__name__') or '__main__'
    module = sys.modules[module_name]
    return module


def package_name(pkg_or_module):
    """If this function is passed a module, return the dotted Python
    package name of the package in which the module lives.  If this
    function is passed a package, return the dotted Python package
    name of the package itself."""
    if pkg_or_module is None or pkg_or_module.__name__ == '__main__':
        return '__main__'
    pkg_name = pkg_or_module.__name__
    pkg_filename = getattr(pkg_or_module, '__file__', None)
    if pkg_filename is None:
        # Namespace packages do not have __init__.py* files,
        # and so have no __file__ attribute
        return pkg_name
    splitted = os.path.split(pkg_filename)
    if splitted[-1] in init_names:
        # it's a package
        return pkg_name
    return pkg_name.rsplit('.', 1)[0]


def package_of(pkg_or_module):
    """Return the package of a module or return the package itself"""
    pkg_name = package_name(pkg_or_module)
    __import__(pkg_name)
    return sys.modules[pkg_name]


def caller_package(level=2, caller_module=caller_module):
    # caller_module in arglist for tests
    module = caller_module(level + 1)
    f = getattr(module, '__file__', '')
    if ('__init__.py' in f) or ('__init__$py' in f):  # empty at >>>
        # Module is a package
        return module
    # Go up one level to get package
    package_name = module.__name__.rsplit('.', 1)[0]
    return sys.modules[package_name]


def package_path(package):
    # computing the abspath is actually kinda expensive so we memoize
    # the result
    prefix = getattr(package, '__abspath__', None)
    if prefix is None:
        prefix = pkg_resources.resource_filename(package.__name__, '')
        # pkg_resources doesn't care whether we feed it a package
        # name or a module name within the package, the result
        # will be the same: a directory name to the package itself
        try:
            package.__abspath__ = prefix
        except Exception:
            # this is only an optimization, ignore any error
            pass
    return prefix


class _CALLER_PACKAGE:
    def __repr__(self):  # pragma: no cover (for docs)
        return 'pyramid.path.CALLER_PACKAGE'


CALLER_PACKAGE = _CALLER_PACKAGE()


class Resolver:
    def __init__(self, package=CALLER_PACKAGE):
        if package in (None, CALLER_PACKAGE):
            self.package = package
        else:
            if isinstance(package, str):
                try:
                    __import__(package)
                except ImportError:
                    raise ValueError(
                        'The dotted name %r cannot be imported' % (package,)
                    )
                package = sys.modules[package]
            self.package = package_of(package)

    def get_package_name(self):
        if self.package is CALLER_PACKAGE:
            package_name = caller_package().__name__
        else:
            package_name = self.package.__name__
        return package_name

    def get_package(self):
        if self.package is CALLER_PACKAGE:
            package = caller_package()
        else:
            package = self.package
        return package


[docs] class AssetResolver(Resolver): """A class used to resolve an :term:`asset specification` to an :term:`asset descriptor`. .. versionadded:: 1.3 The constructor accepts a single argument named ``package`` which may be any of: - A fully qualified (not relative) dotted name to a module or package - a Python module or package object - The value ``None`` - The constant value :attr:`pyramid.path.CALLER_PACKAGE`. The default value is :attr:`pyramid.path.CALLER_PACKAGE`. The ``package`` is used when a relative asset specification is supplied to the :meth:`~pyramid.path.AssetResolver.resolve` method. An asset specification without a colon in it is treated as relative. If ``package`` is ``None``, the resolver will only be able to resolve fully qualified (not relative) asset specifications. Any attempt to resolve a relative asset specification will result in an :exc:`ValueError` exception. If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, the resolver will treat relative asset specifications as relative to the caller of the :meth:`~pyramid.path.AssetResolver.resolve` method. If ``package`` is a *module* or *module name* (as opposed to a package or package name), its containing package is computed and this package is 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 ``template.pt`` is supplied to the :meth:`~pyramid.path.AssetResolver.resolve` method, the resulting absolute asset spec would be ``xml.minidom:template.pt``, because ``xml.dom.expatbuilder`` is a module object, not a package object. If ``package`` is a *package* or *package name* (as opposed to a module or module name), this package will be used to compute relative asset specifications. For example, if the ``package`` argument to this type was passed the string ``xml.dom``, and ``template.pt`` is supplied to the :meth:`~pyramid.path.AssetResolver.resolve` method, the resulting absolute asset spec would be ``xml.minidom:template.pt``. """
[docs] def resolve(self, spec): """ Resolve the asset spec named as ``spec`` to an object that has the attributes and methods described in :class:`pyramid.interfaces.IAssetDescriptor`. If ``spec`` is an absolute filename (e.g. ``/path/to/myproject/templates/foo.pt``) or an absolute asset spec (e.g. ``myproject:templates.foo.pt``), an asset descriptor is returned without taking into account the ``package`` passed to this class' constructor. If ``spec`` is a *relative* asset specification (an asset specification without a ``:`` in it, e.g. ``templates/foo.pt``), the ``package`` argument of the constructor is used as the package portion of the asset spec. For example: .. code-block:: python a = AssetResolver('myproject') resolver = a.resolve('templates/foo.pt') print(resolver.abspath()) # -> /path/to/myproject/templates/foo.pt If the AssetResolver is constructed without a ``package`` argument of ``None``, and a relative asset specification is passed to ``resolve``, an :exc:`ValueError` exception is raised. """ if os.path.isabs(spec): return FSAssetDescriptor(spec) path = spec if ':' in path: package_name, path = spec.split(':', 1) else: if self.package is CALLER_PACKAGE: package_name = caller_package().__name__ else: package_name = getattr(self.package, '__name__', None) if package_name is None: raise ValueError( 'relative spec %r irresolveable without package' % (spec,) ) return PkgResourcesAssetDescriptor(package_name, path)
[docs] class DottedNameResolver(Resolver): """A class used to resolve a :term:`dotted Python name` to a package or module object. .. versionadded:: 1.3 The constructor accepts a single argument named ``package`` which may be any of: - A fully qualified (not relative) dotted name to a module or package - a Python module or package object - The value ``None`` - The constant value :attr:`pyramid.path.CALLER_PACKAGE`. The default value is :attr:`pyramid.path.CALLER_PACKAGE`. The ``package`` is used when a relative dotted name is supplied to the :meth:`~pyramid.path.DottedNameResolver.resolve` method. A dotted name which has a ``.`` (dot) or ``:`` (colon) as its first character is treated as relative. If ``package`` is ``None``, the resolver will only be able to resolve fully qualified (not relative) names. Any attempt to resolve a relative name will result in an :exc:`ValueError` exception. If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, the resolver will treat relative dotted names as relative to the caller of the :meth:`~pyramid.path.DottedNameResolver.resolve` method. If ``package`` is a *module* or *module name* (as opposed to a package or package name), 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 :meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting import would be for ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module object, not a package object. If ``package`` is a *package* or *package name* (as opposed to a module or module name), 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 :meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting import would be for ``xml.minidom``. """
[docs] def resolve(self, dotted): """ This method resolves a dotted name reference to a global Python object (an object which can be imported) to the object itself. Two dotted name styles are supported: - ``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 supplied name contains a ``:`` (colon), the ``pkg_resources`` resolution mechanism will be chosen, otherwise the ``zope.dottedname`` resolution mechanism will be chosen. If the ``dotted`` argument passed to this method is not a string, a :exc:`ValueError` will be raised. When a dotted name cannot be resolved, a :exc:`ValueError` error is raised. Example: .. code-block:: python r = DottedNameResolver() v = r.resolve('xml') # v is the xml module """ if not isinstance(dotted, str): raise ValueError('%r is not a string' % (dotted,)) package = self.package if package is CALLER_PACKAGE: package = caller_package() return self._resolve(dotted, package)
[docs] def maybe_resolve(self, dotted): """ This method behaves just like :meth:`~pyramid.path.DottedNameResolver.resolve`, except if the ``dotted`` value passed is not a string, it is simply returned. For example: .. code-block:: python import xml r = DottedNameResolver() v = r.maybe_resolve(xml) # v is the xml module; no exception raised """ if isinstance(dotted, str): package = self.package if package is CALLER_PACKAGE: package = caller_package() return self._resolve(dotted, package) return dotted
def _resolve(self, dotted, package): if ':' in dotted: return self._pkg_resources_style(dotted, package) else: return self._zope_dottedname_style(dotted, package) def _pkg_resources_style(self, value, package): """package.module:attr style""" if value.startswith(('.', ':')): if not package: raise ValueError( 'relative name %r irresolveable without package' % (value,) ) if value in ['.', ':']: value = package.__name__ else: value = package.__name__ + value # Calling EntryPoint.load with an argument is deprecated. # See https://pythonhosted.org/setuptools/history.html#id8 ep = pkg_resources.EntryPoint.parse('x=%s' % value) if hasattr(ep, 'resolve'): # setuptools>=10.2 return ep.resolve() # pragma: NO COVER else: return ep.load(False) # pragma: NO COVER def _zope_dottedname_style(self, value, package): """package.module.attr style""" module = getattr(package, '__name__', None) # package may be None if not module: module = None if value == '.': if module is None: raise ValueError( 'relative name %r irresolveable without package' % (value,) ) name = module.split('.') else: name = value.split('.') if not name[0]: if module is None: raise ValueError( '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
@implementer(IAssetDescriptor) class PkgResourcesAssetDescriptor: pkg_resources = pkg_resources def __init__(self, pkg_name, path): self.pkg_name = pkg_name self.path = path def absspec(self): return '%s:%s' % (self.pkg_name, self.path) def abspath(self): return os.path.abspath( self.pkg_resources.resource_filename(self.pkg_name, self.path) ) def stream(self): return self.pkg_resources.resource_stream(self.pkg_name, self.path) def isdir(self): return self.pkg_resources.resource_isdir(self.pkg_name, self.path) def listdir(self): return self.pkg_resources.resource_listdir(self.pkg_name, self.path) def exists(self): return self.pkg_resources.resource_exists(self.pkg_name, self.path) @implementer(IAssetDescriptor) class FSAssetDescriptor: def __init__(self, path): self.path = os.path.abspath(path) def absspec(self): raise NotImplementedError def abspath(self): return self.path def stream(self): return open(self.path, 'rb') def isdir(self): return os.path.isdir(self.path) def listdir(self): return os.listdir(self.path) def exists(self): return os.path.exists(self.path)