diff options
| author | Chris McDonough <chrism@plope.com> | 2012-11-13 14:01:18 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-11-13 14:01:18 -0500 |
| commit | c224c6ea4a50b89869a7108e775e6ee0adc9e63d (patch) | |
| tree | f9c27de63b5113c864ceacb7f065e791eb3523e9 | |
| parent | 8c30a3d9c2437e661eac6f23315837fccb4741ea (diff) | |
| parent | fd77b5c78677109c7756a690d0e4be43895ae12a (diff) | |
| download | pyramid-c224c6ea4a50b89869a7108e775e6ee0adc9e63d.tar.gz pyramid-c224c6ea4a50b89869a7108e775e6ee0adc9e63d.tar.bz2 pyramid-c224c6ea4a50b89869a7108e775e6ee0adc9e63d.zip | |
Merge branch 'master' of github.com:Pylons/pyramid
| -rw-r--r-- | CHANGES.txt | 4 | ||||
| -rw-r--r-- | CONTRIBUTORS.txt | 2 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 13 | ||||
| -rw-r--r-- | pyramid/config/__init__.py | 11 | ||||
| -rw-r--r-- | pyramid/config/assets.py | 2 | ||||
| -rw-r--r-- | pyramid/config/factories.py | 8 | ||||
| -rw-r--r-- | pyramid/config/i18n.py | 3 | ||||
| -rw-r--r-- | pyramid/config/predicates.py | 9 | ||||
| -rw-r--r-- | pyramid/config/rendering.py | 2 | ||||
| -rw-r--r-- | pyramid/config/security.py | 2 | ||||
| -rw-r--r-- | pyramid/config/testing.py | 2 | ||||
| -rw-r--r-- | pyramid/config/util.py | 61 | ||||
| -rw-r--r-- | pyramid/config/views.py | 19 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_init.py | 2 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_predicates.py | 12 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_util.py | 30 | ||||
| -rw-r--r-- | pyramid/tests/test_util.py | 31 | ||||
| -rw-r--r-- | pyramid/util.py | 67 |
18 files changed, 160 insertions, 120 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index d57444ad0..e40312c34 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -57,6 +57,10 @@ Bug Fixes attribute of the request. It no longer fails in this case. See https://github.com/Pylons/pyramid/issues/700 +- Be more tolerant of potential error conditions in ``match_param`` and + ``physical_path`` predicate implementations; instead of raising an exception, + return False. + Deprecations ------------ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index d03da3e62..34d904d0f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -188,3 +188,5 @@ Contributors - Domen Kozar, 2012/09/11 - David Gay, 2012/09/16 + +- Robert Jackiewicz, 2012/11/12 diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 96fa77a07..ea75e5fe4 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -180,11 +180,14 @@ as a forbidden view: config.scan() Like any other view, the forbidden view must accept at least a ``request`` -parameter, or both ``context`` and ``request``. The ``context`` (available -as ``request.context`` if you're using the request-only view argument -pattern) is the context found by the router when the view invocation was -denied. The ``request`` is the current :term:`request` representing the -denied action. +parameter, or both ``context`` and ``request``. If a forbidden view +callable accepts both ``context`` and ``request``, the HTTP Exception is passed +as context. The ``context`` as found by the router when view was +denied (that you normally would expect) is available as +``request.context``. The ``request`` is the current :term:`request` +representing the denied action. + + Here's some sample code that implements a minimal forbidden view: diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 1dc438597..40edaa324 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -70,16 +70,17 @@ from pyramid.config.security import SecurityConfiguratorMixin from pyramid.config.settings import SettingsConfiguratorMixin from pyramid.config.testing import TestingConfiguratorMixin from pyramid.config.tweens import TweensConfiguratorMixin -from pyramid.config.util import ( - action_method, - ActionInfo, - PredicateList, - ) +from pyramid.config.util import PredicateList from pyramid.config.views import ViewsConfiguratorMixin from pyramid.config.zca import ZCAConfiguratorMixin from pyramid.path import DottedNameResolver +from pyramid.util import ( + action_method, + ActionInfo, + ) + empty = text_('') _marker = object() diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py index c93431987..5d4682349 100644 --- a/pyramid/config/assets.py +++ b/pyramid/config/assets.py @@ -8,7 +8,7 @@ from pyramid.interfaces import IPackageOverrides from pyramid.exceptions import ConfigurationError from pyramid.threadlocal import get_current_registry -from pyramid.config.util import action_method +from pyramid.util import action_method class OverrideProvider(pkg_resources.DefaultProvider): def __init__(self, module): diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 01b1fb22e..ef7975d92 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -1,7 +1,5 @@ from zope.interface import implementer -from pyramid.config.util import action_method - from pyramid.interfaces import ( IDefaultRootFactory, IRequestFactory, @@ -11,7 +9,11 @@ from pyramid.interfaces import ( ) from pyramid.traversal import DefaultRootFactory -from pyramid.util import InstancePropertyMixin + +from pyramid.util import ( + action_method, + InstancePropertyMixin, + ) class FactoriesConfiguratorMixin(object): @action_method diff --git a/pyramid/config/i18n.py b/pyramid/config/i18n.py index 67a7e2018..9eb59e1c7 100644 --- a/pyramid/config/i18n.py +++ b/pyramid/config/i18n.py @@ -13,8 +13,7 @@ from pyramid.exceptions import ConfigurationError from pyramid.i18n import get_localizer from pyramid.path import package_path from pyramid.threadlocal import get_current_request - -from pyramid.config.util import action_method +from pyramid.util import action_method class I18NConfiguratorMixin(object): @action_method diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py index e31425899..ded8fbfbf 100644 --- a/pyramid/config/predicates.py +++ b/pyramid/config/predicates.py @@ -17,6 +17,8 @@ from pyramid.security import effective_principals from .util import as_sorted_tuple +_marker = object() + class XHRPredicate(object): def __init__(self, val, config): self.val = bool(val) @@ -174,6 +176,9 @@ class MatchParamPredicate(object): phash = text def __call__(self, context, request): + if not request.matchdict: + # might be None + return False for k, v in self.reqs: if request.matchdict.get(k) != v: return False @@ -266,7 +271,9 @@ class PhysicalPathPredicate(object): phash = text def __call__(self, context, request): - return resource_path_tuple(context) == self.val + if getattr(context, '__name__', _marker) is not _marker: + return resource_path_tuple(context) == self.val + return False class EffectivePrincipalsPredicate(object): def __init__(self, val, config): diff --git a/pyramid/config/rendering.py b/pyramid/config/rendering.py index 926511b7b..4f33b23d9 100644 --- a/pyramid/config/rendering.py +++ b/pyramid/config/rendering.py @@ -6,7 +6,7 @@ from pyramid.interfaces import ( PHASE1_CONFIG, ) -from pyramid.config.util import action_method +from pyramid.util import action_method from pyramid import ( renderers, diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 567999cc4..6a1257b6a 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -7,7 +7,7 @@ from pyramid.interfaces import ( ) from pyramid.exceptions import ConfigurationError -from pyramid.config.util import action_method +from pyramid.util import action_method class SecurityConfiguratorMixin(object): @action_method diff --git a/pyramid/config/testing.py b/pyramid/config/testing.py index abbbffc10..7141a5049 100644 --- a/pyramid/config/testing.py +++ b/pyramid/config/testing.py @@ -14,7 +14,7 @@ from pyramid.traversal import ( split_path_info, ) -from pyramid.config.util import action_method +from pyramid.util import action_method class TestingConfiguratorMixin(object): # testing API diff --git a/pyramid/config/util.py b/pyramid/config/util.py index 1c6e1ca15..c16755a75 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -1,10 +1,4 @@ -import traceback - -from functools import update_wrapper - -from zope.interface import implementer - -from pyramid.interfaces import IActionInfo +from hashlib import md5 from pyramid.compat import ( bytes_, @@ -13,56 +7,19 @@ from pyramid.compat import ( from pyramid.exceptions import ConfigurationError from pyramid.registry import predvalseq -from pyramid.util import TopologicalSorter -from hashlib import md5 +from pyramid.util import ( + TopologicalSorter, + action_method, + ActionInfo, + ) + +action_method = action_method # support bw compat imports +ActionInfo = ActionInfo # support bw compat imports MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() -@implementer(IActionInfo) -class ActionInfo(object): - def __init__(self, file, line, function, src): - self.file = file - self.line = line - self.function = function - self.src = src - - def __str__(self): - srclines = self.src.split('\n') - src = '\n'.join(' %s' % x for x in srclines) - return 'Line %s of file %s:\n%s' % (self.line, self.file, src) - -def action_method(wrapped): - """ Wrapper to provide the right conflict info report data when a method - that calls Configurator.action calls another that does the same""" - def wrapper(self, *arg, **kw): - if self._ainfo is None: - self._ainfo = [] - info = kw.pop('_info', None) - # backframes for outer decorators to actionmethods - backframes = kw.pop('_backframes', 2) - if is_nonstr_iter(info) and len(info) == 4: - # _info permitted as extract_stack tuple - info = ActionInfo(*info) - if info is None: - try: - f = traceback.extract_stack(limit=3) - info = ActionInfo(*f[-backframes]) - except: # pragma: no cover - info = ActionInfo(None, 0, '', '') - self._ainfo.append(info) - try: - result = wrapped(self, *arg, **kw) - finally: - self._ainfo.pop() - return result - - if hasattr(wrapped, '__name__'): - update_wrapper(wrapper, wrapped) - wrapper.__docobj__ = wrapped - return wrapper - def as_sorted_tuple(val): if not is_nonstr_iter(val): val = (val,) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 8a4db149e..745b6f810 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1,7 +1,6 @@ import inspect import operator import os -from functools import wraps from zope.interface import ( Interface, @@ -71,6 +70,8 @@ from pyramid.view import ( from pyramid.util import ( object_description, + viewdefaults, + action_method, ) import pyramid.config.predicates @@ -78,7 +79,6 @@ import pyramid.config.predicates from pyramid.config.util import ( DEFAULT_PHASH, MAX_ORDER, - action_method, ) urljoin = urlparse.urljoin @@ -621,21 +621,6 @@ class MultiView(object): continue raise PredicateMismatch(self.name) -def viewdefaults(wrapped): - def wrapper(self, *arg, **kw): - defaults = {} - if arg: - view = arg[0] - else: - view = kw.get('view') - view = self.maybe_dotted(view) - if inspect.isclass(view): - defaults = getattr(view, '__view_defaults__', {}).copy() - defaults.update(kw) - defaults['_backframes'] = 3 # for action_method - return wrapped(self, *arg, **defaults) - return wraps(wrapped)(wrapper) - class ViewsConfiguratorMixin(object): @viewdefaults @action_method diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 2cf9a269a..7c2880a18 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -772,7 +772,7 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual(config.action('discrim', kw={'a':1}), None) def test_action_autocommit_with_introspectables(self): - from pyramid.config.util import ActionInfo + from pyramid.util import ActionInfo config = self._makeOne(autocommit=True) intr = DummyIntrospectable() config.action('discrim', introspectables=(intr,)) diff --git a/pyramid/tests/test_config/test_predicates.py b/pyramid/tests/test_config/test_predicates.py index 91dfb0fb6..1cd6050bf 100644 --- a/pyramid/tests/test_config/test_predicates.py +++ b/pyramid/tests/test_config/test_predicates.py @@ -187,6 +187,13 @@ class TestMatchParamPredicate(unittest.TestCase): result = inst(None, request) self.assertFalse(result) + def test___call___matchdict_is_None(self): + inst = self._makeOne('abc=1') + request = Dummy() + request.matchdict = None + result = inst(None, request) + self.assertFalse(result) + def test_text(self): inst = self._makeOne(('def= 1', 'abc =2')) self.assertEqual(inst.text(), 'match_param abc=2,def=1') @@ -436,6 +443,11 @@ class Test_PhysicalPathPredicate(unittest.TestCase): context.__parent__ = root self.assertFalse(inst(context, None)) + def test_it_call_context_has_no_name(self): + inst = self._makeOne('/', None) + context = Dummy() + self.assertFalse(inst(context, None)) + class Test_EffectivePrincipalsPredicate(unittest.TestCase): def setUp(self): self.config = testing.setUp() diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index 8c3cd7455..b32f9c6ef 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -366,36 +366,6 @@ class TestPredicateList(unittest.TestCase): self.assertRaises(ConfigurationError, self._callFUT, unknown=1) -class TestActionInfo(unittest.TestCase): - def _getTargetClass(self): - from pyramid.config.util import ActionInfo - return ActionInfo - - def _makeOne(self, filename, lineno, function, linerepr): - return self._getTargetClass()(filename, lineno, function, linerepr) - - def test_class_conforms(self): - from zope.interface.verify import verifyClass - from pyramid.interfaces import IActionInfo - verifyClass(IActionInfo, self._getTargetClass()) - - def test_instance_conforms(self): - from zope.interface.verify import verifyObject - from pyramid.interfaces import IActionInfo - verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f')) - - def test_ctor(self): - inst = self._makeOne('filename', 10, 'function', 'src') - self.assertEqual(inst.file, 'filename') - self.assertEqual(inst.line, 10) - self.assertEqual(inst.function, 'function') - self.assertEqual(inst.src, 'src') - - def test___str__(self): - inst = self._makeOne('filename', 0, 'function', ' linerepr ') - self.assertEqual(str(inst), - "Line 0 of file filename:\n linerepr ") - class DummyCustomPredicate(object): def __init__(self): self.__text__ = 'custom predicate' diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 785950230..2ca4c4a66 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -545,6 +545,37 @@ class TestSentinel(unittest.TestCase): r = repr(Sentinel('ABC')) self.assertEqual(r, 'ABC') +class TestActionInfo(unittest.TestCase): + def _getTargetClass(self): + from pyramid.util import ActionInfo + return ActionInfo + + def _makeOne(self, filename, lineno, function, linerepr): + return self._getTargetClass()(filename, lineno, function, linerepr) + + def test_class_conforms(self): + from zope.interface.verify import verifyClass + from pyramid.interfaces import IActionInfo + verifyClass(IActionInfo, self._getTargetClass()) + + def test_instance_conforms(self): + from zope.interface.verify import verifyObject + from pyramid.interfaces import IActionInfo + verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f')) + + def test_ctor(self): + inst = self._makeOne('filename', 10, 'function', 'src') + self.assertEqual(inst.file, 'filename') + self.assertEqual(inst.line, 10) + self.assertEqual(inst.function, 'function') + self.assertEqual(inst.src, 'src') + + def test___str__(self): + inst = self._makeOne('filename', 0, 'function', ' linerepr ') + self.assertEqual(str(inst), + "Line 0 of file filename:\n linerepr ") + + def dummyfunc(): pass class Dummy(object): diff --git a/pyramid/util.py b/pyramid/util.py index d83837322..ca7f5951c 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -1,6 +1,10 @@ +import functools import inspect +import traceback import weakref +from zope.interface import implementer + from pyramid.exceptions import ( ConfigurationError, CyclicDependencyError, @@ -15,6 +19,7 @@ from pyramid.compat import ( PY3, ) +from pyramid.interfaces import IActionInfo from pyramid.path import DottedNameResolver as _DottedNameResolver class DottedNameResolver(_DottedNameResolver): @@ -453,3 +458,65 @@ class TopologicalSorter(object): return result +def viewdefaults(wrapped): + """ Decorator for add_view-like methods which takes into account + __view_defaults__ attached to view it is passed. Not a documented API but + used by some external systems.""" + def wrapper(self, *arg, **kw): + defaults = {} + if arg: + view = arg[0] + else: + view = kw.get('view') + view = self.maybe_dotted(view) + if inspect.isclass(view): + defaults = getattr(view, '__view_defaults__', {}).copy() + defaults.update(kw) + defaults['_backframes'] = 3 # for action_method + return wrapped(self, *arg, **defaults) + return functools.wraps(wrapped)(wrapper) + +@implementer(IActionInfo) +class ActionInfo(object): + def __init__(self, file, line, function, src): + self.file = file + self.line = line + self.function = function + self.src = src + + def __str__(self): + srclines = self.src.split('\n') + src = '\n'.join(' %s' % x for x in srclines) + return 'Line %s of file %s:\n%s' % (self.line, self.file, src) + +def action_method(wrapped): + """ Wrapper to provide the right conflict info report data when a method + that calls Configurator.action calls another that does the same. Not a + documented API but used by some external systems.""" + def wrapper(self, *arg, **kw): + if self._ainfo is None: + self._ainfo = [] + info = kw.pop('_info', None) + # backframes for outer decorators to actionmethods + backframes = kw.pop('_backframes', 2) + if is_nonstr_iter(info) and len(info) == 4: + # _info permitted as extract_stack tuple + info = ActionInfo(*info) + if info is None: + try: + f = traceback.extract_stack(limit=3) + info = ActionInfo(*f[-backframes]) + except: # pragma: no cover + info = ActionInfo(None, 0, '', '') + self._ainfo.append(info) + try: + result = wrapped(self, *arg, **kw) + finally: + self._ainfo.pop() + return result + + if hasattr(wrapped, '__name__'): + functools.update_wrapper(wrapper, wrapped) + wrapper.__docobj__ = wrapped + return wrapper + |
