summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-12-08 17:10:18 -0500
committerChris McDonough <chrism@plope.com>2011-12-08 17:10:18 -0500
commit078859d058ac8c1617349a12ea29b9d1d0187485 (patch)
tree830355d295d9d8203bdabbd71f0254614865f253
parent674636494b7e546598ac3adb094c3dca6f6b8c9e (diff)
downloadpyramid-078859d058ac8c1617349a12ea29b9d1d0187485.tar.gz
pyramid-078859d058ac8c1617349a12ea29b9d1d0187485.tar.bz2
pyramid-078859d058ac8c1617349a12ea29b9d1d0187485.zip
provide caller_path support for both asset resolver and dotted name resolver, make it the default
-rw-r--r--docs/api/path.rst7
-rw-r--r--pyramid/config/__init__.py11
-rw-r--r--pyramid/path.py128
-rw-r--r--pyramid/tests/test_path.py110
-rw-r--r--pyramid/util.py6
5 files changed, 184 insertions, 78 deletions
diff --git a/docs/api/path.rst b/docs/api/path.rst
index 045d77da2..d46c35d8e 100644
--- a/docs/api/path.rst
+++ b/docs/api/path.rst
@@ -5,6 +5,13 @@
.. automodule:: pyramid.path
+ .. attribute:: CALLER_PACKAGE
+
+ A constant used by the constructor of
+ :class:`pyramid.path.DottedNameResolver` and
+ :class:`pyramid.path.AssetResolver` (see their docstrings for more
+ info).
+
.. autoclass:: DottedNameResolver
:members:
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 11f1758fc..04f9b6fb5 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -278,8 +278,8 @@ class Configurator(
package = caller_package()
name_resolver = DottedNameResolver(package)
self.name_resolver = name_resolver
- self.package_name = name_resolver.package_name
- self.package = name_resolver.package
+ self.package_name = name_resolver.get_package_name()
+ self.package = name_resolver.get_package()
self.registry = registry
self.autocommit = autocommit
self.route_prefix = route_prefix
@@ -339,6 +339,7 @@ class Configurator(
self._fix_registry()
if introspector is not None:
+ # use nondefault introspector
self.introspector = introspector
self._set_settings(settings)
@@ -484,9 +485,9 @@ class Configurator(
def _del_introspector(self):
del self.registry.introspector
- introspector = property(_get_introspector,
- _set_introspector,
- _del_introspector)
+ introspector = property(
+ _get_introspector, _set_introspector, _del_introspector
+ )
@property
def action_info(self):
diff --git a/pyramid/path.py b/pyramid/path.py
index 05a54fff7..540b1c5df 100644
--- a/pyramid/path.py
+++ b/pyramid/path.py
@@ -74,11 +74,16 @@ def package_path(package):
pass
return prefix
+class _CALLER_PACKAGE(object):
+ def __repr__(self): # for docs
+ return 'pyramid.path.CALLER_PACKAGE'
+
+CALLER_PACKAGE = _CALLER_PACKAGE()
+
class Resolver(object):
- def __init__(self, package=None):
- if package is None:
- self.package_name = None
- self.package = None
+ def __init__(self, package=CALLER_PACKAGE):
+ if package in (None, CALLER_PACKAGE):
+ self.package = package
else:
if isinstance(package, string_types):
try:
@@ -89,7 +94,21 @@ class Resolver(object):
)
package = sys.modules[package]
self.package = package_of(package)
- self.package_name = self.package.__name__
+
+ def get_package_name(self):
+ if self.package is CALLER_PACKAGE:
+ package_name = caller_package().__name__
+ else:
+ package_name = self.package.__name__
+ return package_name
+
+ def get_package(self):
+ if self.package is CALLER_PACKAGE:
+ package = caller_package()
+ else:
+ package = self.package
+ return package
+
class AssetResolver(Resolver):
""" A class used to resolve an :term:`asset specification` to an
@@ -106,16 +125,25 @@ class AssetResolver(Resolver):
- The value ``None``
+ - The constant value :attr:`pyramid.path.CALLER_PACKAGE`.
+
+ The default value is :attr:`pyramid.path.CALLER_PACKAGE`.
+
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
+ If the value ``None`` is supplied as the ``package``, 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 the value :attr:`pyramid.path.CALLER_PACKAGE` is supplied as the
+ ``package``, the resolver will treat relative asset specifications as
+ relative to the caller of the :meth:`~pyramid.path.AssetResolver.resolve`
+ method.
+
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
@@ -157,22 +185,25 @@ class AssetResolver(Resolver):
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 the AssetResolver is constructed without a ``package`` argument of
+ ``None``, and a relative asset specification is passed to
+ ``resolve``, an :exc:`ValueError` exception is raised.
"""
if os.path.isabs(spec):
return FSAssetDescriptor(spec)
path = spec
if ':' in path:
- pkg_name, path = spec.split(':', 1)
+ package_name, path = spec.split(':', 1)
else:
- pkg_name = self.package_name
- if pkg_name is None:
+ if self.package is CALLER_PACKAGE:
+ package_name = caller_package().__name__
+ else:
+ package_name = getattr(self.package, '__name__', None)
+ if package_name is None:
raise ValueError(
'relative spec %r irresolveable without package' % (spec,)
)
- return PkgResourcesAssetDescriptor(pkg_name, path)
+ return PkgResourcesAssetDescriptor(package_name, path)
class DottedNameResolver(Resolver):
""" A class used to resolve a :term:`dotted Python name` to a package or
@@ -189,16 +220,25 @@ class DottedNameResolver(Resolver):
- The value ``None``
+ - The constant value :attr:`pyramid.path.CALLER_PACKAGE`.
+
+ The default value is :attr:`pyramid.path.CALLER_PACKAGE`.
+
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
+ If the value ``None`` is supplied as the ``package``, 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 the value :attr:`pyramid.path.CALLER_PACKAGE` is supplied as the
+ ``package``, the resolver will treat relative dotted names as relative to
+ the caller of the :meth:`~pyramid.path.DottedNameResolver.resolve`
+ method.
+
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
@@ -215,11 +255,8 @@ class DottedNameResolver(Resolver):
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):
+ def resolve(self, dotted):
"""
This method resolves a dotted name reference to a global Python
object (an object which can be imported) to the object itself.
@@ -239,52 +276,71 @@ class DottedNameResolver(Resolver):
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
+ If the ``dotted`` argument passed to this method is not a string, a
:exc:`ValueError` will be raised.
+
+ When a dotted name cannot be resolved, a :exc:`ValueError` error is
+ raised.
+
+ Example:
+
+ .. code-block:: python
+
+ r = DottedNameResolver()
+ v = r.resolve('xml') # v is the xml module
+
"""
- if not isinstance(name, string_types):
- raise ValueError('%r is not a string' % (name,))
- return self.maybe_resolve(name)
+ if not isinstance(dotted, string_types):
+ raise ValueError('%r is not a string' % (dotted,))
+ package = self.package
+ if package is CALLER_PACKAGE:
+ package = caller_package()
+ return self._resolve(dotted, package)
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
+ ``dotted`` value passed is not a string, it is simply returned. For
example:
.. code-block:: python
import xml
r = DottedNameResolver()
- v = r.resolve(xml)
+ v = r.maybe_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)
+ package = self.package
+ if package is CALLER_PACKAGE:
+ package = caller_package()
+ return self._resolve(dotted, package)
return dotted
+ def _resolve(self, dotted, package):
+ if ':' in dotted:
+ return self._pkg_resources_style(dotted, package)
+ else:
+ return self._zope_dottedname_style(dotted, package)
- def _pkg_resources_style(self, value):
+ def _pkg_resources_style(self, value, package):
""" package.module:attr style """
if value.startswith('.') or value.startswith(':'):
- if not self.package_name:
+ if not package:
raise ValueError(
- 'relative name %r irresolveable without '
- 'package_name' % (value,))
+ 'relative name %r irresolveable without package' % (value,)
+ )
if value in ['.', ':']:
- value = self.package_name
+ value = package.__name__
else:
- value = self.package_name + value
+ value = package.__name__ + value
return pkg_resources.EntryPoint.parse(
'x=%s' % value).load(False)
- def _zope_dottedname_style(self, value):
+ def _zope_dottedname_style(self, value, package):
""" package.module.attr style """
- module = self.package_name
+ module = getattr(package, '__name__', None) # package may be None
if not module:
module = None
if value == '.':
diff --git a/pyramid/tests/test_path.py b/pyramid/tests/test_path.py
index f99436eb8..ac51c92d7 100644
--- a/pyramid/tests/test_path.py
+++ b/pyramid/tests/test_path.py
@@ -170,6 +170,34 @@ class TestPackageName(unittest.TestCase):
result = self._callFUT(__main__)
self.assertEqual(result, '__main__')
+class TestResolver(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.path import Resolver
+ return Resolver
+
+ def _makeOne(self, package):
+ return self._getTargetClass()(package)
+
+ def test_get_package_caller_package(self):
+ import pyramid.tests
+ from pyramid.path import CALLER_PACKAGE
+ self.assertEqual(self._makeOne(CALLER_PACKAGE).get_package(),
+ pyramid.tests)
+
+ def test_get_package_name_caller_package(self):
+ from pyramid.path import CALLER_PACKAGE
+ self.assertEqual(self._makeOne(CALLER_PACKAGE).get_package_name(),
+ 'pyramid.tests')
+
+ def test_get_package_string(self):
+ import pyramid.tests
+ self.assertEqual(self._makeOne('pyramid.tests').get_package(),
+ pyramid.tests)
+
+ def test_get_package_name_string(self):
+ self.assertEqual(self._makeOne('pyramid.tests').get_package_name(),
+ 'pyramid.tests')
+
class TestAssetResolver(unittest.TestCase):
def _getTargetClass(self):
from pyramid.path import AssetResolver
@@ -182,14 +210,12 @@ class TestAssetResolver(unittest.TestCase):
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):
@@ -216,6 +242,14 @@ class TestAssetResolver(unittest.TestCase):
def test_resolve_relspec_no_package(self):
inst = self._makeOne(None)
self.assertRaises(ValueError, inst.resolve, 'test_asset.py')
+
+ def test_resolve_relspec_caller_package(self):
+ from pyramid.path import PkgResourcesAssetDescriptor
+ from pyramid.path import CALLER_PACKAGE
+ inst = self._makeOne(CALLER_PACKAGE)
+ r = inst.resolve('test_asset.py')
+ self.assertEqual(r.__class__, PkgResourcesAssetDescriptor)
+ self.failUnless(r.exists())
class TestPkgResourcesAssetDescriptor(unittest.TestCase):
def _getTargetClass(self):
@@ -338,66 +372,66 @@ class TestDottedNameResolver(unittest.TestCase):
def test_zope_dottedname_style_resolve_builtin(self):
typ = self._makeOne()
if PY3: # pragma: no cover
- result = typ._zope_dottedname_style('builtins.str')
+ result = typ._zope_dottedname_style('builtins.str', None)
else:
- result = typ._zope_dottedname_style('__builtin__.str')
+ result = typ._zope_dottedname_style('__builtin__.str', None)
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')
+ 'pyramid.tests.test_path.TestDottedNameResolver', None)
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')
+ 'pyramid.test_path.nonexisting_name', None)
def test__zope_dottedname_style_resolve_relative(self):
import pyramid.tests
- typ = self._makeOne(package=pyramid.tests)
+ typ = self._makeOne()
result = typ._zope_dottedname_style(
- '.test_path.TestDottedNameResolver')
+ '.test_path.TestDottedNameResolver', pyramid.tests)
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)
+ typ = self._makeOne()
result = typ._zope_dottedname_style(
- '..tests.test_path.TestDottedNameResolver')
+ '..tests.test_path.TestDottedNameResolver', pyramid.tests)
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('.')
+ typ = self._makeOne()
+ result = typ._zope_dottedname_style('.', pyramid.tests)
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, '.')
+ e = self.config_exc(typ._zope_dottedname_style, '.', None)
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')
+ e = self.config_exc(typ._zope_dottedname_style, '.whatever', None)
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)
+ typ = self._makeOne()
self.assertRaises(ImportError, typ._zope_dottedname_style,
- '.notexisting')
+ '.notexisting', pyramid.tests)
def test__zope_dottedname_style_resolveable_relative(self):
import pyramid
- typ = self._makeOne(package=pyramid)
- result = typ._zope_dottedname_style('.tests')
+ typ = self._makeOne()
+ result = typ._zope_dottedname_style('.tests', pyramid)
from pyramid import tests
self.assertEqual(result, tests)
@@ -405,48 +439,48 @@ class TestDottedNameResolver(unittest.TestCase):
typ = self._makeOne()
self.assertRaises(
ImportError,
- typ._zope_dottedname_style, 'pyramid.fudge.bar')
+ typ._zope_dottedname_style, 'pyramid.fudge.bar', None)
def test__zope_dottedname_style_resolveable_absolute(self):
typ = self._makeOne()
result = typ._zope_dottedname_style(
- 'pyramid.tests.test_path.TestDottedNameResolver')
+ 'pyramid.tests.test_path.TestDottedNameResolver', None)
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')
+ 'pyramid.tests.test_path:TestDottedNameResolver', None)
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')
+ 'pyramid.tests:nonexisting', None)
def test__pkg_resources_style_resolve_relative(self):
import pyramid.tests
- typ = self._makeOne(package=pyramid.tests)
+ typ = self._makeOne()
result = typ._pkg_resources_style(
- '.test_path:TestDottedNameResolver')
+ '.test_path:TestDottedNameResolver', pyramid.tests)
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('.')
+ typ = self._makeOne()
+ result = typ._pkg_resources_style('.', pyramid.tests)
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')
+ '.whatever', None)
def test__pkg_resources_style_irrresolveable_relative(self):
import pyramid
- typ = self._makeOne(package=pyramid)
+ typ = self._makeOne()
self.assertRaises(ImportError, typ._pkg_resources_style,
- ':notexisting')
+ ':notexisting', pyramid)
def test_resolve_not_a_string(self):
typ = self._makeOne()
@@ -469,17 +503,27 @@ class TestDottedNameResolver(unittest.TestCase):
typ = self._makeOne()
self.assertRaises(ImportError, typ.resolve, 'cant.be.found')
+ def test_resolve_caller_package(self):
+ from pyramid.path import CALLER_PACKAGE
+ typ = self._makeOne(CALLER_PACKAGE)
+ self.assertEqual(typ.resolve('.test_path.TestDottedNameResolver'),
+ self.__class__)
+
+ def test_maybe_resolve_caller_package(self):
+ from pyramid.path import CALLER_PACKAGE
+ typ = self._makeOne(CALLER_PACKAGE)
+ self.assertEqual(typ.maybe_resolve('.test_path.TestDottedNameResolver'),
+ self.__class__)
+
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')
@@ -489,19 +533,15 @@ class TestDottedNameResolver(unittest.TestCase):
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
diff --git a/pyramid/util.py b/pyramid/util.py
index 2018fb73e..76968bbbd 100644
--- a/pyramid/util.py
+++ b/pyramid/util.py
@@ -8,9 +8,11 @@ from pyramid.compat import (
PY3,
)
-from pyramid.path import DottedNameResolver # bw compat
+from pyramid.path import DottedNameResolver as _DottedNameResolver
-DottedNameResolver = DottedNameResolver # for pyflakes
+class DottedNameResolver(_DottedNameResolver):
+ def __init__(self, package=None): # default to package = None for bw compat
+ return _DottedNameResolver.__init__(self, package)
class WeakOrderedSet(object):
""" Maintain a set of items.