diff options
| author | Chris McDonough <chrism@plope.com> | 2010-11-24 18:42:35 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2010-11-24 18:42:35 -0500 |
| commit | a9f17c8b3a7fc935b70e87f2a1fb8728deb16f85 (patch) | |
| tree | c903cd88e217b09b8faa58d0044feac9deb01bab | |
| parent | 36715bcdef36737671eef74dfee22d466d690a04 (diff) | |
| download | pyramid-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.txt | 9 | ||||
| -rw-r--r-- | pyramid/configuration.py | 141 | ||||
| -rw-r--r-- | pyramid/mako_templating.py | 19 | ||||
| -rw-r--r-- | pyramid/tests/test_configuration.py | 175 | ||||
| -rw-r--r-- | pyramid/tests/test_mako_templating.py | 92 | ||||
| -rw-r--r-- | pyramid/tests/test_util.py | 177 | ||||
| -rw-r--r-- | pyramid/util.py | 144 |
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 + + |
