diff options
| author | Chris McDonough <chrism@agendaless.com> | 2010-08-02 20:12:11 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2010-08-02 20:12:11 +0000 |
| commit | b05de75dd707a77c4ca3da5780de79efe4ad1092 (patch) | |
| tree | 6f3209cf40fc729784fef682cffdeb55bd64a941 | |
| parent | 672bbe5a4c3141e2a995c4bf9998f9b73a99b752 (diff) | |
| download | pyramid-b05de75dd707a77c4ca3da5780de79efe4ad1092.tar.gz pyramid-b05de75dd707a77c4ca3da5780de79efe4ad1092.tar.bz2 pyramid-b05de75dd707a77c4ca3da5780de79efe4ad1092.zip | |
add infrastructure for resolving dotted names
| -rw-r--r-- | repoze/bfg/configuration.py | 98 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 149 |
2 files changed, 247 insertions, 0 deletions
diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py index d11839613..f694d7737 100644 --- a/repoze/bfg/configuration.py +++ b/repoze/bfg/configuration.py @@ -2372,3 +2372,101 @@ 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 module 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 module 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 Python module or package object; it is used when + *relative* dotted names are supplied to the ``__call__`` method. + A dotted name which has a ``.`` (dot) or ``:`` (colon) as its + first character is treated as relative. E.g. if ``.minidom`` is + supplied to ``deserialize``, and the ``package`` argument to this + type was passed the ``xml`` module object, the resulting import + would be for ``xml.minidom``. If a relative package name is + supplied to ``deserialize``, and no ``package`` was supplied to + the constructor, an :exc:`repoze.bfg.ConfigurationError` error + will be raised. + + When a dotted name cannot be resolved, a + :class:`repoze.bfg.exceptions.ConfigurationError` error is raised. + """ + def __init__(self, package): + self.package = package + + def _pkg_resources_style(self, value): + """ package.module:attr style """ + import pkg_resources + if value.startswith('.') or value.startswith(':'): + if not self.package: + raise ConfigurationError( + 'relative name %r irresolveable without package' % 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 and self.package.__name__ or None + if value == '.': + if self.package 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 __call__(self, dotted): + if not isinstance(dotted, basestring): + raise ConfigurationError('%r is not a string' % dotted) + try: + if ':' in dotted: + return self._pkg_resources_style(dotted) + else: + return self._zope_dottedname_style(dotted) + except ImportError: + raise ConfigurationError( + 'The dotted name %r cannot be imported' % dotted) diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py index b34f6c270..2f001ba8d 100644 --- a/repoze/bfg/tests/test_configuration.py +++ b/repoze/bfg/tests/test_configuration.py @@ -3553,6 +3553,155 @@ class TestMakeApp(unittest.TestCase): Configurator=DummyConfigurator) self.assertEqual(app.zcml_file, '2.zcml') +class TestDottedNameResolver(unittest.TestCase): + def _makeOne(self, package=None): + from repoze.bfg.configuration import DottedNameResolver + return DottedNameResolver(package) + + def config_exc(self, func, *arg, **kw): + from repoze.bfg.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( + 'repoze.bfg.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, + 'repoze.bfg.test_configuration.nonexisting_name') + + def test__zope_dottedname_style_resolve_relative(self): + import repoze.bfg.tests + typ = self._makeOne(package=repoze.bfg.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 repoze.bfg.tests.test_configuration + typ = self._makeOne(package=repoze.bfg.tests.test_configuration) + result = typ._zope_dottedname_style( + '..test_configuration.TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test__zope_dottedname_style_resolve_relative_is_dot(self): + import repoze.bfg.tests + typ = self._makeOne(package=repoze.bfg.tests) + result = typ._zope_dottedname_style('.') + self.assertEqual(result, repoze.bfg.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 repoze.bfg.tests + typ = self._makeOne(package=repoze.bfg.tests) + self.assertRaises(ImportError, typ._zope_dottedname_style, + '.notexisting') + + def test__zope_dottedname_style_resolveable_relative(self): + import repoze.bfg + typ = self._makeOne(package=repoze.bfg) + result = typ._zope_dottedname_style('.tests') + from repoze.bfg import tests + self.assertEqual(result, tests) + + def test__zope_dottedname_style_irresolveable_absolute(self): + typ = self._makeOne() + self.assertRaises( + ImportError, + typ._zope_dottedname_style, 'repoze.bfg.fudge.bar') + + def test__zope_dottedname_style_resolveable_absolute(self): + typ = self._makeOne() + result = typ._zope_dottedname_style( + 'repoze.bfg.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( + 'repoze.bfg.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, + 'repoze.bfg.tests:nonexisting') + + def test__pkg_resources_style_resolve_relative_startswith_colon(self): + import repoze.bfg.tests.test_configuration + typ = self._makeOne(package=repoze.bfg.tests.test_configuration) + result = typ._pkg_resources_style(':TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test__pkg_resources_style_resolve_relative_startswith_dot(self): + import repoze.bfg.tests + typ = self._makeOne(package=repoze.bfg.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 repoze.bfg.tests + typ = self._makeOne(package=repoze.bfg.tests) + result = typ._pkg_resources_style('.') + self.assertEqual(result, repoze.bfg.tests) + + def test__pkg_resources_style_resolve_relative_nocurrentpackage(self): + typ = self._makeOne() + from repoze.bfg.exceptions import ConfigurationError + self.assertRaises(ConfigurationError, typ._pkg_resources_style, + '.whatever') + + def test__pkg_resources_style_irrresolveable_relative(self): + import repoze.bfg + typ = self._makeOne(package=repoze.bfg) + self.assertRaises(ImportError, typ._pkg_resources_style, + ':notexisting') + + def test_deserialize_not_a_string(self): + typ = self._makeOne() + e = self.config_exc(typ, None) + self.assertEqual(e.args[0], 'None is not a string') + + def test_deserialize_using_pkgresources_style(self): + typ = self._makeOne() + result = typ( + 'repoze.bfg.tests.test_configuration:TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test_deserialize_using_zope_dottedname_style(self): + typ = self._makeOne() + result = typ( + 'repoze.bfg.tests.test_configuration:TestDottedNameResolver') + self.assertEqual(result, self.__class__) + + def test_deserialize_style_raises(self): + typ = self._makeOne() + e = self.config_exc(typ, 'cant.be.found') + self.assertEqual(e.args[0], + "The dotted name 'cant.be.found' cannot be imported") + class DummyRequest: subpath = () def __init__(self): |
