CHANGES.txt | ●●●●● patch | view | raw | blame | history | |
TODO.txt | ●●●●● patch | view | raw | blame | history | |
docs/api.rst | ●●●●● patch | view | raw | blame | history | |
docs/api/path.rst | ●●●●● patch | view | raw | blame | history | |
docs/glossary.rst | ●●●●● patch | view | raw | blame | history | |
docs/whatsnew-1.3.rst | ●●●●● patch | view | raw | blame | history | |
pyramid/asset.py | ●●●●● patch | view | raw | blame | history | |
pyramid/config/__init__.py | ●●●●● patch | view | raw | blame | history | |
pyramid/interfaces.py | ●●●●● patch | view | raw | blame | history | |
pyramid/path.py | ●●●●● patch | view | raw | blame | history | |
pyramid/tests/test_asset.py | ●●●●● patch | view | raw | blame | history | |
pyramid/tests/test_path.py | ●●●●● patch | view | raw | blame | history | |
pyramid/tests/test_util.py | ●●●●● patch | view | raw | blame | history | |
pyramid/util.py | ●●●●● 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.