summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2010-11-24 18:42:35 -0500
committerChris McDonough <chrism@plope.com>2010-11-24 18:42:35 -0500
commita9f17c8b3a7fc935b70e87f2a1fb8728deb16f85 (patch)
treec903cd88e217b09b8faa58d0044feac9deb01bab
parent36715bcdef36737671eef74dfee22d466d690a04 (diff)
downloadpyramid-a9f17c8b3a7fc935b70e87f2a1fb8728deb16f85.tar.gz
pyramid-a9f17c8b3a7fc935b70e87f2a1fb8728deb16f85.tar.bz2
pyramid-a9f17c8b3a7fc935b70e87f2a1fb8728deb16f85.zip
- The Mako renderer did not properly turn the ``mako.imports``,
``mako.default_filters``, and ``mako.imports`` settings into lists. - The Mako renderer did not properly convert the ``mako.error_handler`` setting from a dotted name to a callable. - The Mako renderer now accepts a resource specification for ``mako.module_directory``.
-rw-r--r--CHANGES.txt9
-rw-r--r--pyramid/configuration.py141
-rw-r--r--pyramid/mako_templating.py19
-rw-r--r--pyramid/tests/test_configuration.py175
-rw-r--r--pyramid/tests/test_mako_templating.py92
-rw-r--r--pyramid/tests/test_util.py177
-rw-r--r--pyramid/util.py144
7 files changed, 437 insertions, 320 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index b8c3a847f..547a7254a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -16,6 +16,9 @@ Features
- Add support for json on GAE by catching NotImplementedError and importing
simplejson from django.utils.
+- The Mako renderer now accepts a resource specification for
+ ``mako.module_directory``.
+
Bug Fixes
---------
@@ -25,6 +28,12 @@ Bug Fixes
- Make default renderer work (renderer factory registered with no name, which
is active for every view unless the view names a specific renderer).
+- The Mako renderer did not properly turn the ``mako.imports``,
+ ``mako.default_filters``, and ``mako.imports`` settings into lists.
+
+- The Mako renderer did not properly convert the ``mako.error_handler``
+ setting from a dotted name to a callable.
+
Documentation
-------------
diff --git a/pyramid/configuration.py b/pyramid/configuration.py
index 6ebb56ec3..29a322c7f 100644
--- a/pyramid/configuration.py
+++ b/pyramid/configuration.py
@@ -3,7 +3,6 @@ import re
import sys
import threading
import inspect
-import pkg_resources
import venusian
@@ -67,7 +66,6 @@ from pyramid.i18n import get_localizer
from pyramid.log import make_stream_logger
from pyramid.mako_templating import renderer_factory as mako_renderer_factory
from pyramid.path import caller_package
-from pyramid.path import package_of
from pyramid.path import package_path
from pyramid.registry import Registry
from pyramid.renderers import RendererHelper
@@ -83,6 +81,7 @@ from pyramid.traversal import DefaultRootFactory
from pyramid.traversal import find_interface
from pyramid.traversal import traversal_path
from pyramid.urldispatch import RoutesMapper
+from pyramid.util import DottedNameResolver
from pyramid.view import default_exceptionresponse_view
from pyramid.view import render_view_to_response
@@ -2727,144 +2726,6 @@ def make_app(root_factory, package=None, filename='configure.zcml',
config.end()
return config.make_wsgi_app()
-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.configuration.ConfigurationError` exception.
-
- If a *module* or *module name* (as opposed to a package or package
- name) is supplied as ``package``, its containing package is
- computed and this package used to derive the package name (all
- names are resolved relative to packages, never to modules). For
- example, if the ``package`` argument to this type was passed the
- string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to
- the ``resolve`` method, the resulting import would be for
- ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module
- object, not a package object.
-
- If a *package* or *package name* (as opposed to a module or module
- name) is supplied as ``package``, this package will be used to
- relative compute dotted names. For example, if the ``package``
- argument to this type was passed the string ``xml.dom``, and
- ``.minidom`` is supplied to the ``resolve`` method, the resulting
- import would be for ``xml.minidom``.
-
- When a dotted name cannot be resolved, a
- :class:`pyramid.exceptions.ConfigurationError` error is raised.
- """
- def __init__(self, package):
- if package is None:
- self.package_name = None
- self.package = None
- else:
- if isinstance(package, basestring):
- try:
- __import__(package)
- except ImportError:
- raise ConfigurationError(
- 'The dotted name %r cannot be imported' % (package,))
- package = sys.modules[package]
- self.package = package_of(package)
- self.package_name = self.package.__name__
-
- def _pkg_resources_style(self, value):
- """ package.module:attr style """
- if value.startswith('.') or value.startswith(':'):
- if not self.package_name:
- raise ConfigurationError(
- 'relative name %r irresolveable without '
- 'package_name' % (value,))
- if value in ['.', ':']:
- value = self.package_name
- else:
- value = self.package_name + value
- return pkg_resources.EntryPoint.parse(
- 'x=%s' % value).load(False)
-
- def _zope_dottedname_style(self, value):
- """ package.module.attr style """
- module = self.package_name and self.package_name or None
- if value == '.':
- if self.package_name is None:
- raise ConfigurationError(
- 'relative name %r irresolveable without package' % (value,)
- )
- name = module.split('.')
- else:
- name = value.split('.')
- if not name[0]:
- if module is None:
- raise ConfigurationError(
- 'relative name %r irresolveable without '
- 'package' % (value,)
- )
- module = module.split('.')
- name.pop(0)
- while not name[0]:
- module.pop()
- name.pop(0)
- name = module + name
-
- used = name.pop(0)
- found = __import__(used)
- for n in name:
- used += '.' + n
- try:
- found = getattr(found, n)
- except AttributeError:
- __import__(used)
- found = getattr(found, n) # pragma: no cover
-
- return found
-
- def resolve(self, dotted):
- if not isinstance(dotted, basestring):
- raise ConfigurationError('%r is not a string' % (dotted,))
- return self.maybe_resolve(dotted)
-
- def maybe_resolve(self, dotted):
- if isinstance(dotted, basestring):
- if ':' in dotted:
- return self._pkg_resources_style(dotted)
- else:
- return self._zope_dottedname_style(dotted)
- return dotted
-
-
class ActionPredicate(object):
action_name = 'action'
def __init__(self, action):
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
index 6356e0db4..c9c041edb 100644
--- a/pyramid/mako_templating.py
+++ b/pyramid/mako_templating.py
@@ -8,6 +8,7 @@ from pyramid.interfaces import ITemplateRenderer
from pyramid.exceptions import ConfigurationError
from pyramid.resource import resolve_resource_spec
from pyramid.resource import abspath_from_resource_spec
+from pyramid.util import DottedNameResolver
from mako.lookup import TemplateLookup
from mako import exceptions
@@ -61,17 +62,27 @@ def renderer_factory(info):
lookup = registry.queryUtility(IMakoLookup)
if lookup is None:
reload_templates = settings.get('reload_templates', False)
- directories = settings.get('mako.directories')
- module_directory = settings.get('mako.module_directory')
+ directories = settings.get('mako.directories', None)
+ module_directory = settings.get('mako.module_directory', None)
input_encoding = settings.get('mako.input_encoding', 'utf-8')
error_handler = settings.get('mako.error_handler', None)
default_filters = settings.get('mako.default_filters', None)
- imports = settings.get('mako.imports', [])
+ imports = settings.get('mako.imports', None)
if directories is None:
raise ConfigurationError(
'Mako template used without a ``mako.directories`` setting')
- directories = directories.splitlines()
+ directories = filter(None, directories.splitlines())
directories = [ abspath_from_resource_spec(d) for d in directories ]
+ if module_directory is not None:
+ module_directory = abspath_from_resource_spec(module_directory)
+ if error_handler is not None:
+ dotted = DottedNameResolver(info.package)
+ error_handler = dotted.maybe_resolve(error_handler)
+ if default_filters is not None:
+ default_filters = filter(None, default_filters.splitlines())
+ if imports:
+ imports = filter(None, imports.splitlines())
+
lookup = PkgResourceTemplateLookup(directories=directories,
module_directory=module_directory,
input_encoding=input_encoding,
diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py
index 5113d1f4e..938db3c31 100644
--- a/pyramid/tests/test_configuration.py
+++ b/pyramid/tests/test_configuration.py
@@ -4286,181 +4286,6 @@ class TestMakeApp(unittest.TestCase):
Configurator=DummyConfigurator)
self.assertEqual(app.zcml_file, '2.zcml')
-class TestDottedNameResolver(unittest.TestCase):
- def _makeOne(self, package=None):
- from pyramid.configuration import DottedNameResolver
- return DottedNameResolver(package)
-
- def config_exc(self, func, *arg, **kw):
- from pyramid.exceptions import ConfigurationError
- try:
- func(*arg, **kw)
- except ConfigurationError, e:
- return e
- else:
- raise AssertionError('Invalid not raised') # pragma: no cover
-
- def test_zope_dottedname_style_resolve_absolute(self):
- typ = self._makeOne()
- result = typ._zope_dottedname_style(
- 'pyramid.tests.test_configuration.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_configuration.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_configuration.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_configuration.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_configuration.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_configuration: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_configuration: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_configuration:TestDottedNameResolver')
- self.assertEqual(result, self.__class__)
-
- def test_resolve_using_zope_dottedname_style(self):
- typ = self._makeOne()
- result = typ.resolve(
- 'pyramid.tests.test_configuration: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_configuration')
- 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.configuration import ConfigurationError
- self.assertRaises(ConfigurationError, self._makeOne, 'cant.be.found')
-
- def test_ctor_module(self):
- import pyramid.tests
- import pyramid.tests.test_configuration
- typ = self._makeOne(pyramid.tests.test_configuration)
- 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_isexception(unittest.TestCase):
def _callFUT(self, ob):
from pyramid.configuration import isexception
diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py
index e0c02550b..cfb54b902 100644
--- a/pyramid/tests/test_mako_templating.py
+++ b/pyramid/tests/test_mako_templating.py
@@ -47,7 +47,7 @@ class Test_renderer_factory(Base, unittest.TestCase):
def test_composite_directories_path(self):
from pyramid.mako_templating import IMakoLookup
- twice = self.templates_dir + '\n' + self.templates_dir
+ twice = '\n' + self.templates_dir + '\n' + self.templates_dir + '\n'
settings = {'mako.directories':twice}
info = DummyRendererInfo({
'name':'helloworld.mak',
@@ -59,6 +59,96 @@ class Test_renderer_factory(Base, unittest.TestCase):
lookup = self.config.registry.getUtility(IMakoLookup)
self.assertEqual(lookup.directories, [self.templates_dir]*2)
+ def test_with_module_directory_resource_spec(self):
+ import os
+ from pyramid.mako_templating import IMakoLookup
+ module_directory = 'pyramid.tests:fixtures'
+ settings = {'mako.directories':self.templates_dir,
+ 'mako.module_directory':module_directory}
+ info = DummyRendererInfo({
+ 'name':'helloworld.mak',
+ 'package':None,
+ 'registry':self.config.registry,
+ 'settings':settings,
+ })
+ self._callFUT(info)
+ lookup = self.config.registry.getUtility(IMakoLookup)
+ fixtures = os.path.join(os.path.dirname(__file__), 'fixtures')
+ self.assertEqual(lookup.module_directory, fixtures)
+
+ def test_with_module_directory_resource_abspath(self):
+ import os
+ from pyramid.mako_templating import IMakoLookup
+ fixtures = os.path.join(os.path.dirname(__file__), 'fixtures')
+ settings = {'mako.directories':self.templates_dir,
+ 'mako.module_directory':fixtures}
+ info = DummyRendererInfo({
+ 'name':'helloworld.mak',
+ 'package':None,
+ 'registry':self.config.registry,
+ 'settings':settings,
+ })
+ self._callFUT(info)
+ lookup = self.config.registry.getUtility(IMakoLookup)
+ self.assertEqual(lookup.module_directory, fixtures)
+
+ def test_with_input_encoding(self):
+ from pyramid.mako_templating import IMakoLookup
+ settings = {'mako.directories':self.templates_dir,
+ 'mako.input_encoding':'utf-16'}
+ info = DummyRendererInfo({
+ 'name':'helloworld.mak',
+ 'package':None,
+ 'registry':self.config.registry,
+ 'settings':settings,
+ })
+ self._callFUT(info)
+ lookup = self.config.registry.getUtility(IMakoLookup)
+ self.assertEqual(lookup.template_args['input_encoding'], 'utf-16')
+
+ def test_with_error_handler(self):
+ from pyramid.mako_templating import IMakoLookup
+ settings = {'mako.directories':self.templates_dir,
+ 'mako.error_handler':'pyramid.tests'}
+ import pyramid.tests
+ info = DummyRendererInfo({
+ 'name':'helloworld.mak',
+ 'package':None,
+ 'registry':self.config.registry,
+ 'settings':settings,
+ })
+ self._callFUT(info)
+ lookup = self.config.registry.getUtility(IMakoLookup)
+ self.assertEqual(lookup.template_args['error_handler'], pyramid.tests)
+
+ def test_with_default_filters(self):
+ from pyramid.mako_templating import IMakoLookup
+ settings = {'mako.directories':self.templates_dir,
+ 'mako.default_filters':'\nh\ng\n\n'}
+ info = DummyRendererInfo({
+ 'name':'helloworld.mak',
+ 'package':None,
+ 'registry':self.config.registry,
+ 'settings':settings,
+ })
+ self._callFUT(info)
+ lookup = self.config.registry.getUtility(IMakoLookup)
+ self.assertEqual(lookup.template_args['default_filters'], ['h', 'g'])
+
+ def test_with_imports(self):
+ from pyramid.mako_templating import IMakoLookup
+ settings = {'mako.directories':self.templates_dir,
+ 'mako.imports':'\none\ntwo\n\n'}
+ info = DummyRendererInfo({
+ 'name':'helloworld.mak',
+ 'package':None,
+ 'registry':self.config.registry,
+ 'settings':settings,
+ })
+ self._callFUT(info)
+ lookup = self.config.registry.getUtility(IMakoLookup)
+ self.assertEqual(lookup.template_args['imports'], ['one', 'two'])
+
def test_with_lookup(self):
from pyramid.mako_templating import IMakoLookup
lookup = dict()
diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py
new file mode 100644
index 000000000..2929f888f
--- /dev/null
+++ b/pyramid/tests/test_util.py
@@ -0,0 +1,177 @@
+import unittest
+
+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, e:
+ return e
+ else:
+ raise AssertionError('Invalid not raised') # pragma: no cover
+
+ 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.configuration 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)
+
diff --git a/pyramid/util.py b/pyramid/util.py
new file mode 100644
index 000000000..84a23bb23
--- /dev/null
+++ b/pyramid/util.py
@@ -0,0 +1,144 @@
+import pkg_resources
+import sys
+
+from pyramid.exceptions import ConfigurationError
+from pyramid.path import package_of
+
+class DottedNameResolver(object):
+ """ This class resolves dotted name references to 'global' Python
+ objects (objects which can be imported) to those objects.
+
+ Two dotted name styles are supported during deserialization:
+
+ - ``pkg_resources``-style dotted names where non-module attributes
+ of a package are separated from the rest of the path using a ':'
+ e.g. ``package.module:attr``.
+
+ - ``zope.dottedname``-style dotted names where non-module
+ attributes of a package are separated from the rest of the path
+ using a '.' e.g. ``package.module.attr``.
+
+ These styles can be used interchangeably. If the serialization
+ contains a ``:`` (colon), the ``pkg_resources`` resolution
+ mechanism will be chosen, otherwise the ``zope.dottedname``
+ resolution mechanism will be chosen.
+
+ The constructor accepts a single argument named ``package`` which
+ should be a one of:
+
+ - a Python module or package object
+
+ - A fully qualified (not relative) dotted name to a module or package
+
+ - The value ``None``
+
+ The ``package`` is used when relative dotted names are supplied to
+ the resolver's ``resolve`` and ``maybe_resolve`` methods. A
+ dotted name which has a ``.`` (dot) or ``:`` (colon) as its first
+ character is treated as relative.
+
+ If the value ``None`` is supplied as the package name, the
+ resolver will only be able to resolve fully qualified (not
+ relative) names. Any attempt to resolve a relative name when the
+ ``package`` is ``None`` will result in an
+ :exc:`pyramid.configuration.ConfigurationError` exception.
+
+ If a *module* or *module name* (as opposed to a package or package
+ name) is supplied as ``package``, its containing package is
+ computed and this package used to derive the package name (all
+ names are resolved relative to packages, never to modules). For
+ example, if the ``package`` argument to this type was passed the
+ string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to
+ the ``resolve`` method, the resulting import would be for
+ ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module
+ object, not a package object.
+
+ If a *package* or *package name* (as opposed to a module or module
+ name) is supplied as ``package``, this package will be used to
+ relative compute dotted names. For example, if the ``package``
+ argument to this type was passed the string ``xml.dom``, and
+ ``.minidom`` is supplied to the ``resolve`` method, the resulting
+ import would be for ``xml.minidom``.
+
+ When a dotted name cannot be resolved, a
+ :class:`pyramid.exceptions.ConfigurationError` error is raised.
+ """
+ def __init__(self, package):
+ if package is None:
+ self.package_name = None
+ self.package = None
+ else:
+ if isinstance(package, basestring):
+ try:
+ __import__(package)
+ except ImportError:
+ raise ConfigurationError(
+ 'The dotted name %r cannot be imported' % (package,))
+ package = sys.modules[package]
+ self.package = package_of(package)
+ self.package_name = self.package.__name__
+
+ def _pkg_resources_style(self, value):
+ """ package.module:attr style """
+ if value.startswith('.') or value.startswith(':'):
+ if not self.package_name:
+ raise ConfigurationError(
+ 'relative name %r irresolveable without '
+ 'package_name' % (value,))
+ if value in ['.', ':']:
+ value = self.package_name
+ else:
+ value = self.package_name + value
+ return pkg_resources.EntryPoint.parse(
+ 'x=%s' % value).load(False)
+
+ def _zope_dottedname_style(self, value):
+ """ package.module.attr style """
+ module = self.package_name and self.package_name or None
+ if value == '.':
+ if self.package_name is None:
+ raise ConfigurationError(
+ 'relative name %r irresolveable without package' % (value,)
+ )
+ name = module.split('.')
+ else:
+ name = value.split('.')
+ if not name[0]:
+ if module is None:
+ raise ConfigurationError(
+ 'relative name %r irresolveable without '
+ 'package' % (value,)
+ )
+ module = module.split('.')
+ name.pop(0)
+ while not name[0]:
+ module.pop()
+ name.pop(0)
+ name = module + name
+
+ used = name.pop(0)
+ found = __import__(used)
+ for n in name:
+ used += '.' + n
+ try:
+ found = getattr(found, n)
+ except AttributeError:
+ __import__(used)
+ found = getattr(found, n) # pragma: no cover
+
+ return found
+
+ def resolve(self, dotted):
+ if not isinstance(dotted, basestring):
+ raise ConfigurationError('%r is not a string' % (dotted,))
+ return self.maybe_resolve(dotted)
+
+ def maybe_resolve(self, dotted):
+ if isinstance(dotted, basestring):
+ if ':' in dotted:
+ return self._pkg_resources_style(dotted)
+ else:
+ return self._zope_dottedname_style(dotted)
+ return dotted
+
+