summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-11-13 14:01:18 -0500
committerChris McDonough <chrism@plope.com>2012-11-13 14:01:18 -0500
commitc224c6ea4a50b89869a7108e775e6ee0adc9e63d (patch)
treef9c27de63b5113c864ceacb7f065e791eb3523e9
parent8c30a3d9c2437e661eac6f23315837fccb4741ea (diff)
parentfd77b5c78677109c7756a690d0e4be43895ae12a (diff)
downloadpyramid-c224c6ea4a50b89869a7108e775e6ee0adc9e63d.tar.gz
pyramid-c224c6ea4a50b89869a7108e775e6ee0adc9e63d.tar.bz2
pyramid-c224c6ea4a50b89869a7108e775e6ee0adc9e63d.zip
Merge branch 'master' of github.com:Pylons/pyramid
-rw-r--r--CHANGES.txt4
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--docs/narr/hooks.rst13
-rw-r--r--pyramid/config/__init__.py11
-rw-r--r--pyramid/config/assets.py2
-rw-r--r--pyramid/config/factories.py8
-rw-r--r--pyramid/config/i18n.py3
-rw-r--r--pyramid/config/predicates.py9
-rw-r--r--pyramid/config/rendering.py2
-rw-r--r--pyramid/config/security.py2
-rw-r--r--pyramid/config/testing.py2
-rw-r--r--pyramid/config/util.py61
-rw-r--r--pyramid/config/views.py19
-rw-r--r--pyramid/tests/test_config/test_init.py2
-rw-r--r--pyramid/tests/test_config/test_predicates.py12
-rw-r--r--pyramid/tests/test_config/test_util.py30
-rw-r--r--pyramid/tests/test_util.py31
-rw-r--r--pyramid/util.py67
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
+