import os
import pkg_resources
import sys
import imp
from zope.interface import implementer
from pyramid.interfaces import IAssetDescriptor
from pyramid.compat import string_types
ignore_types = [ imp.C_EXTENSION, imp.C_BUILTIN ]
init_names = [ '__init__%s' % x[0] for x in imp.get_suffixes() if
x[0] and x[2] not in ignore_types ]
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:
# this is only an optimization, ignore any error
pass
return prefix
class _CALLER_PACKAGE(object):
def __repr__(self): # pragma: no cover (for docs)
return 'pyramid.path.CALLER_PACKAGE'
CALLER_PACKAGE = _CALLER_PACKAGE()
class Resolver(object):
def __init__(self, package=CALLER_PACKAGE):
if package in (None, CALLER_PACKAGE):
self.package = package
else:
if isinstance(package, string_types):
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`.
.. note:: This API is new as of Pyramid 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 the value ``None`` is supplied as the ``package``, the resolver will
only be able to resolve fully qualified (not relative) asset
specifications. Any attempt to resolve a relative asset specification
when the ``package`` is ``None`` will result in an :exc:`ValueError`
exception.
If the value :attr:`pyramid.path.CALLER_PACKAGE` is supplied as the
``package``, the resolver will treat relative asset specifications as
relative to the caller of the :meth:`~pyramid.path.AssetResolver.resolve`
method.
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
``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 a *package* or *package name* (as opposed to a module or module name)
is supplied as ``package``, 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.
.. note:: This API is new as of Pyramid 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 the value ``None`` is supplied as the ``package``, 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:`ValueError` exception.
If the value :attr:`pyramid.path.CALLER_PACKAGE` is supplied as the
``package``, the resolver will treat relative dotted names as relative to
the caller of the :meth:`~pyramid.path.DottedNameResolver.resolve`
method.
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
: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 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
: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, string_types):
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, string_types):
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('.') or 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
return pkg_resources.EntryPoint.parse(
'x=%s' % value).load(False)
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(object):
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 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(object):
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)