Chris McDonough
2011-12-08 56df902d0a5bcd29a2b4c3dfafab9a09d6f0c29d
- New APIs: ``pyramid.path.AssetResolver`` and
``pyramid.path.DottedNameResolver``. The former can be used to resolve
asset specifications, the latter can be used to resolve dotted names to
modules or packages.
1 files added
13 files modified
1078 ■■■■■ changed files
CHANGES.txt 5 ●●●●● patch | view | raw | blame | history
TODO.txt 2 ●●●●● patch | view | raw | blame | history
docs/api.rst 1 ●●●● patch | view | raw | blame | history
docs/api/path.rst 13 ●●●●● patch | view | raw | blame | history
docs/glossary.rst 6 ●●●●● patch | view | raw | blame | history
docs/whatsnew-1.3.rst 6 ●●●●● patch | view | raw | blame | history
pyramid/asset.py 1 ●●●● patch | view | raw | blame | history
pyramid/config/__init__.py 3 ●●●● patch | view | raw | blame | history
pyramid/interfaces.py 38 ●●●●● patch | view | raw | blame | history
pyramid/path.py 301 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_asset.py 25 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_path.py 350 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_util.py 183 ●●●●● patch | view | raw | blame | history
pyramid/util.py 144 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -38,6 +38,11 @@
- Allow extra keyword arguments to be passed to the
  ``pyramid.config.Configurator.action`` method.
- New APIs: ``pyramid.path.AssetResolver`` and
  ``pyramid.path.DottedNameResolver``.  The former can be used to resolve
  asset specifications, the latter can be used to resolve dotted names to
  modules or packages.
Bug Fixes
---------
TODO.txt
@@ -34,6 +34,8 @@
- Implement analogue of "paster request"?
- AssetResolver method to guess relative based on caller?
Nice-to-Have
------------
docs/api.rst
@@ -21,6 +21,7 @@
   api/interfaces
   api/location
   api/paster
   api/path
   api/registry
   api/renderers
   api/request
docs/api/path.rst
New file
@@ -0,0 +1,13 @@
.. _path_module:
:mod:`pyramid.path`
---------------------------
.. automodule:: pyramid.path
    .. autoclass:: DottedNameResolver
       :members:
    .. autoclass:: AssetResolver
       :members:
docs/glossary.rst
@@ -977,3 +977,9 @@
      a running Pyramid application.  An introspectable is associated with a
      :term:`action` by virtue of the
      :meth:`pyramid.config.Configurator.action` method.
   asset descriptor
      An instance representing an :term:`asset specification` provided by the
      :meth:`pyramid.path.AssetResolver.resolve` method.  It supports the
      methods and attributes documented in
      :class:`pyramid.interfaces.IAssetDescriptor`.
docs/whatsnew-1.3.rst
@@ -43,6 +43,12 @@
Minor Feature Additions
-----------------------
- New APIs: :class:`pyramid.path.AssetResolver` and
  :class:`pyramid.path.DottedNameResolver`.  The former can be used to
  resolve an :term:`asset specification` to an API that can be used to read
  the asset's data, the latter can be used to resolve a :term:`dotted Python
  name` to a module or a package.
- A ``mako.directories`` setting is no longer required to use Mako templates
  Rationale: Mako template renderers can be specified using an absolute asset
  spec.  An entire application can be written with such asset specs,
pyramid/asset.py
@@ -40,3 +40,4 @@
    if pname is None:
        return filename
    return pkg_resources.resource_filename(pname, filename)
pyramid/config/__init__.py
@@ -53,7 +53,6 @@
from pyramid.threadlocal import manager
from pyramid.util import (
    DottedNameResolver,
    WeakOrderedSet,
    object_description,
    )
@@ -76,6 +75,8 @@
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
from pyramid.path import DottedNameResolver
empty = text_('')
_marker = object()
pyramid/interfaces.py
@@ -1013,6 +1013,44 @@
        """ Return a representation of the action information (including
        source code from file, if possible) """
class IAssetDescriptor(Interface):
    """
    Describes an :term:`asset`.
    """
    def absspec():
        """
        Returns the absolute asset specification for this asset
        (e.g. ``mypackage:templates/foo.pt``).
        """
    def abspath():
        """
        Returns an absolute path in the filesystem to the asset.
        """
    def stream():
        """
        Returns an input stream for reading asset contents.  Raises an
        exception if the asset is a directory or does not exist.
        """
    def isdir():
        """
        Returns True if the asset is a directory, otherwise returns False.
        """
    def listdir():
        """
        Returns iterable of filenames of directory contents.  Raises an
        exception if asset is not a directory.
        """
    def exists():
        """
        Returns True if asset exists, otherwise returns False.
        """
# configuration phases: a lower phase number means the actions associated
# with this phase will be executed earlier than those with later phase
# numbers.  The default phase number is 0, FTR.
pyramid/path.py
@@ -3,6 +3,12 @@
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 ]
@@ -68,3 +74,298 @@
            pass
    return prefix
class Resolver(object):
    def __init__(self, package=None):
        if package is None:
            self.package_name = None
            self.package = None
        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)
            self.package_name = self.package.__name__
class AssetResolver(Resolver):
    """ A class used to resolve an :term:`asset specification` to an
    :term:`asset descriptor`.
    .. warning:: 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 ``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 name, 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 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``.
    """
    def resolve(self, spec):
        """
        Resolve the asset spec named as ``spec`` to an object that has the
        attributes and methods described in
        `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 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,
        and a relative asset specification is passed to ``resolve``, a
        :exc:`ValueError` exception is raised.
        """
        if os.path.isabs(spec):
            return FSAssetDescriptor(spec)
        path = spec
        if ':' in path:
            pkg_name, path = spec.split(':', 1)
        else:
            pkg_name = self.package_name
            if pkg_name is None:
                raise ValueError(
                    'relative spec %r irresolveable without package' % (spec,)
                )
        return PkgResourcesAssetDescriptor(pkg_name, path)
class DottedNameResolver(Resolver):
    """ A class used to resolve a :term:`dotted Python name` to a package or
    module object.
    .. warning:: 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 ``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 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:`ValueError` 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
    :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``.
    When a dotted name cannot be resolved, a :exc:`ValueError` error is
    raised.
    """
    def resolve(self, name):
        """
        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 ``name`` argument passed to this method is not a string, a
        :exc:`ValueError` will be raised.
        """
        if not isinstance(name, string_types):
            raise ValueError('%r is not a string' % (name,))
        return self.maybe_resolve(name)
    def maybe_resolve(self, dotted):
        """
        This method behaves just like
        :meth:`~pyramid.path.DottedNameResolver.resolve`, except if the
        ``name`` value passed is not a string, it is simply returned.  For
        example:
        .. code-block:: python
           import xml
           r = DottedNameResolver()
           v = r.resolve(xml)
           # v is the xml module; no exception raised
        """
        if isinstance(dotted, string_types):
            if ':' in dotted:
                return self._pkg_resources_style(dotted)
            else:
                return self._zope_dottedname_style(dotted)
        return dotted
    def _pkg_resources_style(self, value):
        """ package.module:attr style """
        if value.startswith('.') or value.startswith(':'):
            if not self.package_name:
                raise ValueError(
                    '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 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)
pyramid/tests/test_asset.py
@@ -1,4 +1,7 @@
import unittest
import os
here = os.path.abspath(os.path.dirname(__file__))
class Test_resolve_asset_spec(unittest.TestCase):
    def _callFUT(self, spec, package_name='__main__'):
@@ -6,11 +9,8 @@
        return resolve_asset_spec(spec, package_name)
    def test_abspath(self):
        import os
        here = os.path.dirname(__file__)
        path = os.path.abspath(here)
        package_name, filename = self._callFUT(path, 'apackage')
        self.assertEqual(filename, path)
        package_name, filename = self._callFUT(here, 'apackage')
        self.assertEqual(filename, here)
        self.assertEqual(package_name, None)
    def test_rel_spec(self):
@@ -57,11 +57,8 @@
        self.assertEqual(result, '/abc')
    def test_pkgrelative(self):
        import os
        here = os.path.dirname(__file__)
        path = os.path.abspath(here)
        result = self._callFUT('abc', 'pyramid.tests')
        self.assertEqual(result, os.path.join(path, 'abc'))
        self.assertEqual(result, os.path.join(here, 'abc'))
class Test_asset_spec_from_abspath(unittest.TestCase):
    def _callFUT(self, abspath, package):
@@ -74,20 +71,16 @@
        self.assertEqual(result, 'abspath')
    def test_abspath_startswith_package_path(self):
        import os
        abspath = os.path.join(os.path.dirname(__file__), 'fixtureapp')
        abspath = os.path.join(here, 'fixtureapp')
        pkg = DummyPackage('pyramid.tests')
        pkg.__file__ = 'file'
        result = self._callFUT(abspath, pkg)
        self.assertEqual(result, 'pyramid:fixtureapp')
    def test_abspath_doesnt_startwith_package_path(self):
        import os
        abspath = os.path.dirname(__file__)
        pkg = DummyPackage('pyramid.tests')
        result = self._callFUT(abspath, pkg)
        self.assertEqual(result, abspath)
        result = self._callFUT(here, pkg)
        self.assertEqual(result, here)
class DummyPackage:
    def __init__(self, name):
pyramid/tests/test_path.py
@@ -1,4 +1,8 @@
import unittest
import os
from pyramid.compat import PY3
here = os.path.abspath(os.path.dirname(__file__))
class TestCallerPath(unittest.TestCase):
    def tearDown(self):
@@ -16,7 +20,6 @@
    def test_pkgrelative(self):
        import os
        here = os.path.abspath(os.path.dirname(__file__))
        result = self._callFUT('a/b/c')
        self.assertEqual(result, os.path.join(here, 'a/b/c'))
@@ -29,7 +32,6 @@
    def test_memoization_success(self):
        import os
        here = os.path.abspath(os.path.dirname(__file__))
        from pyramid.tests import test_path
        result = self._callFUT('a/b/c')
        self.assertEqual(result, os.path.join(here, 'a/b/c'))
@@ -167,7 +169,343 @@
        import __main__
        result = self._callFUT(__main__)
        self.assertEqual(result, '__main__')
class TestAssetResolver(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.path import AssetResolver
        return AssetResolver
    def _makeOne(self, package='pyramid.tests'):
        return self._getTargetClass()(package)
    def test_ctor_as_package(self):
        import sys
        tests = sys.modules['pyramid.tests']
        inst = self._makeOne(tests)
        self.assertEqual(inst.package_name, 'pyramid.tests')
        self.assertEqual(inst.package, tests)
    def test_ctor_as_str(self):
        import sys
        tests = sys.modules['pyramid.tests']
        inst = self._makeOne('pyramid.tests')
        self.assertEqual(inst.package_name, 'pyramid.tests')
        self.assertEqual(inst.package, tests)
    def test_resolve_abspath(self):
        from pyramid.path import FSAssetDescriptor
        inst = self._makeOne(None)
        r = inst.resolve(os.path.join(here, 'test_asset.py'))
        self.assertEqual(r.__class__, FSAssetDescriptor)
        self.failUnless(r.exists())
    def test_resolve_absspec(self):
        from pyramid.path import PkgResourcesAssetDescriptor
        inst = self._makeOne(None)
        r = inst.resolve('pyramid.tests:test_asset.py')
        self.assertEqual(r.__class__, PkgResourcesAssetDescriptor)
        self.failUnless(r.exists())
    def test_resolve_relspec_with_pkg(self):
        from pyramid.path import PkgResourcesAssetDescriptor
        inst = self._makeOne('pyramid.tests')
        r = inst.resolve('test_asset.py')
        self.assertEqual(r.__class__, PkgResourcesAssetDescriptor)
        self.failUnless(r.exists())
    def test_resolve_relspec_no_package(self):
        inst = self._makeOne(None)
        self.assertRaises(ValueError, inst.resolve, 'test_asset.py')
class TestPkgResourcesAssetDescriptor(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.path import PkgResourcesAssetDescriptor
        return PkgResourcesAssetDescriptor
    def _makeOne(self, pkg='pyramid.tests', path='test_asset.py'):
        return self._getTargetClass()(pkg, path)
    def test_class_implements(self):
        from pyramid.interfaces import IAssetDescriptor
        from zope.interface.verify import verifyClass
        klass = self._getTargetClass()
        verifyClass(IAssetDescriptor, klass)
    def test_instance_implements(self):
        from pyramid.interfaces import IAssetDescriptor
        from zope.interface.verify import verifyObject
        inst = self._makeOne()
        verifyObject(IAssetDescriptor, inst)
    def test_absspec(self):
        inst = self._makeOne()
        self.assertEqual(inst.absspec(), 'pyramid.tests:test_asset.py')
    def test_abspath(self):
        inst = self._makeOne()
        self.assertEqual(inst.abspath(), os.path.join(here, 'test_asset.py'))
    def test_stream(self):
        inst = self._makeOne()
        inst.pkg_resources = DummyPkgResource()
        inst.pkg_resources.resource_stream = lambda x, y: '%s:%s' % (x, y)
        self.assertEqual(inst.stream(),
                         '%s:%s' % ('pyramid.tests', 'test_asset.py'))
    def test_isdir(self):
        inst = self._makeOne()
        inst.pkg_resources = DummyPkgResource()
        inst.pkg_resources.resource_isdir = lambda x, y: '%s:%s' % (x, y)
        self.assertEqual(inst.isdir(),
                         '%s:%s' % ('pyramid.tests', 'test_asset.py'))
    def test_listdir(self):
        inst = self._makeOne()
        inst.pkg_resources = DummyPkgResource()
        inst.pkg_resources.resource_listdir = lambda x, y: '%s:%s' % (x, y)
        self.assertEqual(inst.listdir(),
                         '%s:%s' % ('pyramid.tests', 'test_asset.py'))
    def test_exists(self):
        inst = self._makeOne()
        inst.pkg_resources = DummyPkgResource()
        inst.pkg_resources.resource_exists = lambda x, y: '%s:%s' % (x, y)
        self.assertEqual(inst.exists(),
                         '%s:%s' % ('pyramid.tests', 'test_asset.py'))
class TestFSAssetDescriptor(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.path import FSAssetDescriptor
        return FSAssetDescriptor
    def _makeOne(self, path=os.path.join(here, 'test_asset.py')):
        return self._getTargetClass()(path)
    def test_class_implements(self):
        from pyramid.interfaces import IAssetDescriptor
        from zope.interface.verify import verifyClass
        klass = self._getTargetClass()
        verifyClass(IAssetDescriptor, klass)
    def test_instance_implements(self):
        from pyramid.interfaces import IAssetDescriptor
        from zope.interface.verify import verifyObject
        inst = self._makeOne()
        verifyObject(IAssetDescriptor, inst)
    def test_absspec(self):
        inst = self._makeOne()
        self.assertRaises(NotImplementedError, inst.absspec)
    def test_abspath(self):
        inst = self._makeOne()
        self.assertEqual(inst.abspath(), os.path.join(here, 'test_asset.py'))
    def test_stream(self):
        inst = self._makeOne()
        val = inst.stream().read()
        self.assertTrue(b'asset' in val)
    def test_isdir_False(self):
        inst = self._makeOne()
        self.assertFalse(inst.isdir())
    def test_isdir_True(self):
        inst = self._makeOne(here)
        self.assertTrue(inst.isdir())
    def test_listdir(self):
        inst = self._makeOne(here)
        self.assertTrue(inst.listdir())
    def test_exists(self):
        inst = self._makeOne()
        self.assertTrue(inst.exists())
class TestDottedNameResolver(unittest.TestCase):
    def _makeOne(self, package=None):
        from pyramid.path import DottedNameResolver
        return DottedNameResolver(package)
    def config_exc(self, func, *arg, **kw):
        try:
            func(*arg, **kw)
        except ValueError as e:
            return e
        else:
            raise AssertionError('Invalid not raised') # pragma: no cover
    def test_zope_dottedname_style_resolve_builtin(self):
        typ = self._makeOne()
        if PY3: # pragma: no cover
            result = typ._zope_dottedname_style('builtins.str')
        else:
            result = typ._zope_dottedname_style('__builtin__.str')
        self.assertEqual(result, str)
    def test_zope_dottedname_style_resolve_absolute(self):
        typ = self._makeOne()
        result = typ._zope_dottedname_style(
            'pyramid.tests.test_path.TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test_zope_dottedname_style_irrresolveable_absolute(self):
        typ = self._makeOne()
        self.assertRaises(ImportError, typ._zope_dottedname_style,
            'pyramid.test_path.nonexisting_name')
    def test__zope_dottedname_style_resolve_relative(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        result = typ._zope_dottedname_style(
            '.test_path.TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__zope_dottedname_style_resolve_relative_leading_dots(self):
        import pyramid.tests.test_configuration
        typ = self._makeOne(package=pyramid.tests)
        result = typ._zope_dottedname_style(
            '..tests.test_path.TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__zope_dottedname_style_resolve_relative_is_dot(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        result = typ._zope_dottedname_style('.')
        self.assertEqual(result, pyramid.tests)
    def test__zope_dottedname_style_irresolveable_relative_is_dot(self):
        typ = self._makeOne()
        e = self.config_exc(typ._zope_dottedname_style, '.')
        self.assertEqual(
            e.args[0],
            "relative name '.' irresolveable without package")
    def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self):
        typ = self._makeOne()
        e = self.config_exc(typ._zope_dottedname_style, '.whatever')
        self.assertEqual(
            e.args[0],
            "relative name '.whatever' irresolveable without package")
    def test_zope_dottedname_style_irrresolveable_relative(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        self.assertRaises(ImportError, typ._zope_dottedname_style,
                          '.notexisting')
    def test__zope_dottedname_style_resolveable_relative(self):
        import pyramid
        typ = self._makeOne(package=pyramid)
        result = typ._zope_dottedname_style('.tests')
        from pyramid import tests
        self.assertEqual(result, tests)
    def test__zope_dottedname_style_irresolveable_absolute(self):
        typ = self._makeOne()
        self.assertRaises(
            ImportError,
            typ._zope_dottedname_style, 'pyramid.fudge.bar')
    def test__zope_dottedname_style_resolveable_absolute(self):
        typ = self._makeOne()
        result = typ._zope_dottedname_style(
            'pyramid.tests.test_path.TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__pkg_resources_style_resolve_absolute(self):
        typ = self._makeOne()
        result = typ._pkg_resources_style(
            'pyramid.tests.test_path:TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__pkg_resources_style_irrresolveable_absolute(self):
        typ = self._makeOne()
        self.assertRaises(ImportError, typ._pkg_resources_style,
            'pyramid.tests:nonexisting')
    def test__pkg_resources_style_resolve_relative(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        result = typ._pkg_resources_style(
            '.test_path:TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__pkg_resources_style_resolve_relative_is_dot(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        result = typ._pkg_resources_style('.')
        self.assertEqual(result, pyramid.tests)
    def test__pkg_resources_style_resolve_relative_nocurrentpackage(self):
        typ = self._makeOne()
        self.assertRaises(ValueError, typ._pkg_resources_style,
                          '.whatever')
    def test__pkg_resources_style_irrresolveable_relative(self):
        import pyramid
        typ = self._makeOne(package=pyramid)
        self.assertRaises(ImportError, typ._pkg_resources_style,
                          ':notexisting')
    def test_resolve_not_a_string(self):
        typ = self._makeOne()
        e = self.config_exc(typ.resolve, None)
        self.assertEqual(e.args[0], 'None is not a string')
    def test_resolve_using_pkgresources_style(self):
        typ = self._makeOne()
        result = typ.resolve(
            'pyramid.tests.test_path:TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test_resolve_using_zope_dottedname_style(self):
        typ = self._makeOne()
        result = typ.resolve(
            'pyramid.tests.test_path:TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test_resolve_missing_raises(self):
        typ = self._makeOne()
        self.assertRaises(ImportError, typ.resolve, 'cant.be.found')
    def test_ctor_string_module_resolveable(self):
        import pyramid.tests
        typ = self._makeOne('pyramid.tests.test_path')
        self.assertEqual(typ.package, pyramid.tests)
        self.assertEqual(typ.package_name, 'pyramid.tests')
    def test_ctor_string_package_resolveable(self):
        import pyramid.tests
        typ = self._makeOne('pyramid.tests')
        self.assertEqual(typ.package, pyramid.tests)
        self.assertEqual(typ.package_name, 'pyramid.tests')
    def test_ctor_string_irresolveable(self):
        self.assertRaises(ValueError, self._makeOne, 'cant.be.found')
    def test_ctor_module(self):
        import pyramid.tests
        import pyramid.tests.test_path
        typ = self._makeOne(pyramid.tests.test_path)
        self.assertEqual(typ.package, pyramid.tests)
        self.assertEqual(typ.package_name, 'pyramid.tests')
    def test_ctor_package(self):
        import pyramid.tests
        typ = self._makeOne(pyramid.tests)
        self.assertEqual(typ.package, pyramid.tests)
        self.assertEqual(typ.package_name, 'pyramid.tests')
    def test_ctor_None(self):
        typ = self._makeOne(None)
        self.assertEqual(typ.package, None)
        self.assertEqual(typ.package_name, None)
class DummyPkgResource(object):
    pass
class DummyPackageOrModule:
    def __init__(self, real_package_or_module, raise_exc=None):
        self.__dict__['raise_exc'] = raise_exc
@@ -181,9 +519,3 @@
        if self.raise_exc is not None:
            raise self.raise_exc
        self.__dict__[key] = val
pyramid/tests/test_util.py
@@ -1,189 +1,6 @@
import unittest
from pyramid.compat import PY3
class TestDottedNameResolver(unittest.TestCase):
    def _makeOne(self, package=None):
        from pyramid.util import DottedNameResolver
        return DottedNameResolver(package)
    def config_exc(self, func, *arg, **kw):
        from pyramid.exceptions import ConfigurationError
        try:
            func(*arg, **kw)
        except ConfigurationError as e:
            return e
        else:
            raise AssertionError('Invalid not raised') # pragma: no cover
    def test_zope_dottedname_style_resolve_builtin(self):
        typ = self._makeOne()
        if PY3: # pragma: no cover
            result = typ._zope_dottedname_style('builtins.str')
        else:
            result = typ._zope_dottedname_style('__builtin__.str')
        self.assertEqual(result, str)
    def test_zope_dottedname_style_resolve_absolute(self):
        typ = self._makeOne()
        result = typ._zope_dottedname_style(
            'pyramid.tests.test_util.TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test_zope_dottedname_style_irrresolveable_absolute(self):
        typ = self._makeOne()
        self.assertRaises(ImportError, typ._zope_dottedname_style,
            'pyramid.test_util.nonexisting_name')
    def test__zope_dottedname_style_resolve_relative(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        result = typ._zope_dottedname_style(
            '.test_util.TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__zope_dottedname_style_resolve_relative_leading_dots(self):
        import pyramid.tests.test_configuration
        typ = self._makeOne(package=pyramid.tests)
        result = typ._zope_dottedname_style(
            '..tests.test_util.TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__zope_dottedname_style_resolve_relative_is_dot(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        result = typ._zope_dottedname_style('.')
        self.assertEqual(result, pyramid.tests)
    def test__zope_dottedname_style_irresolveable_relative_is_dot(self):
        typ = self._makeOne()
        e = self.config_exc(typ._zope_dottedname_style, '.')
        self.assertEqual(
            e.args[0],
            "relative name '.' irresolveable without package")
    def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self):
        typ = self._makeOne()
        e = self.config_exc(typ._zope_dottedname_style, '.whatever')
        self.assertEqual(
            e.args[0],
            "relative name '.whatever' irresolveable without package")
    def test_zope_dottedname_style_irrresolveable_relative(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        self.assertRaises(ImportError, typ._zope_dottedname_style,
                          '.notexisting')
    def test__zope_dottedname_style_resolveable_relative(self):
        import pyramid
        typ = self._makeOne(package=pyramid)
        result = typ._zope_dottedname_style('.tests')
        from pyramid import tests
        self.assertEqual(result, tests)
    def test__zope_dottedname_style_irresolveable_absolute(self):
        typ = self._makeOne()
        self.assertRaises(
            ImportError,
            typ._zope_dottedname_style, 'pyramid.fudge.bar')
    def test__zope_dottedname_style_resolveable_absolute(self):
        typ = self._makeOne()
        result = typ._zope_dottedname_style(
            'pyramid.tests.test_util.TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__pkg_resources_style_resolve_absolute(self):
        typ = self._makeOne()
        result = typ._pkg_resources_style(
            'pyramid.tests.test_util:TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__pkg_resources_style_irrresolveable_absolute(self):
        typ = self._makeOne()
        self.assertRaises(ImportError, typ._pkg_resources_style,
            'pyramid.tests:nonexisting')
    def test__pkg_resources_style_resolve_relative(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        result = typ._pkg_resources_style(
            '.test_util:TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test__pkg_resources_style_resolve_relative_is_dot(self):
        import pyramid.tests
        typ = self._makeOne(package=pyramid.tests)
        result = typ._pkg_resources_style('.')
        self.assertEqual(result, pyramid.tests)
    def test__pkg_resources_style_resolve_relative_nocurrentpackage(self):
        typ = self._makeOne()
        from pyramid.exceptions import ConfigurationError
        self.assertRaises(ConfigurationError, typ._pkg_resources_style,
                          '.whatever')
    def test__pkg_resources_style_irrresolveable_relative(self):
        import pyramid
        typ = self._makeOne(package=pyramid)
        self.assertRaises(ImportError, typ._pkg_resources_style,
                          ':notexisting')
    def test_resolve_not_a_string(self):
        typ = self._makeOne()
        e = self.config_exc(typ.resolve, None)
        self.assertEqual(e.args[0], 'None is not a string')
    def test_resolve_using_pkgresources_style(self):
        typ = self._makeOne()
        result = typ.resolve(
            'pyramid.tests.test_util:TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test_resolve_using_zope_dottedname_style(self):
        typ = self._makeOne()
        result = typ.resolve(
            'pyramid.tests.test_util:TestDottedNameResolver')
        self.assertEqual(result, self.__class__)
    def test_resolve_missing_raises(self):
        typ = self._makeOne()
        self.assertRaises(ImportError, typ.resolve, 'cant.be.found')
    def test_ctor_string_module_resolveable(self):
        import pyramid.tests
        typ = self._makeOne('pyramid.tests.test_util')
        self.assertEqual(typ.package, pyramid.tests)
        self.assertEqual(typ.package_name, 'pyramid.tests')
    def test_ctor_string_package_resolveable(self):
        import pyramid.tests
        typ = self._makeOne('pyramid.tests')
        self.assertEqual(typ.package, pyramid.tests)
        self.assertEqual(typ.package_name, 'pyramid.tests')
    def test_ctor_string_irresolveable(self):
        from pyramid.config import ConfigurationError
        self.assertRaises(ConfigurationError, self._makeOne, 'cant.be.found')
    def test_ctor_module(self):
        import pyramid.tests
        import pyramid.tests.test_util
        typ = self._makeOne(pyramid.tests.test_util)
        self.assertEqual(typ.package, pyramid.tests)
        self.assertEqual(typ.package_name, 'pyramid.tests')
    def test_ctor_package(self):
        import pyramid.tests
        typ = self._makeOne(pyramid.tests)
        self.assertEqual(typ.package, pyramid.tests)
        self.assertEqual(typ.package_name, 'pyramid.tests')
    def test_ctor_None(self):
        typ = self._makeOne(None)
        self.assertEqual(typ.package, None)
        self.assertEqual(typ.package_name, None)
class Test_WeakOrderedSet(unittest.TestCase):
    def _makeOne(self):
        from pyramid.config import WeakOrderedSet
pyramid/util.py
@@ -1,6 +1,4 @@
import inspect
import pkg_resources
import sys
import weakref
from pyramid.compat import (
@@ -10,147 +8,9 @@
    PY3,
    )
from pyramid.exceptions import ConfigurationError
from pyramid.path import package_of
from pyramid.path import DottedNameResolver # bw compat
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, string_types):
                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, string_types):
            raise ConfigurationError('%r is not a string' % (dotted,))
        return self.maybe_resolve(dotted)
    def maybe_resolve(self, dotted):
        if isinstance(dotted, string_types):
            if ':' in dotted:
                return self._pkg_resources_style(dotted)
            else:
                return self._zope_dottedname_style(dotted)
        return dotted
DottedNameResolver = DottedNameResolver # for pyflakes
class WeakOrderedSet(object):
    """ Maintain a set of items.