Chris McDonough
2013-07-31 5fc0d36724a6197c8c0106e846d8e78e1219b1fe
first cut at indicating that only some predicates are negatable
5 files modified
72 ■■■■ changed files
docs/narr/viewconfig.rst 18 ●●●● patch | view | raw | blame | history
pyramid/config/__init__.py 12 ●●●●● patch | view | raw | blame | history
pyramid/config/predicates.py 8 ●●●●● patch | view | raw | blame | history
pyramid/config/util.py 8 ●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_util.py 26 ●●●● patch | view | raw | blame | history
docs/narr/viewconfig.rst
@@ -560,8 +560,8 @@
Inverting Predicate Values
~~~~~~~~~~~~~~~~~~~~~~~~~~
You can invert the meaning of any predicate value by wrapping it in a call to
:class:`pyramid.config.not_`.
You can invert the meaning of a supported predicate value by wrapping it in a
call to :class:`pyramid.config.not_`.
.. code-block:: python
   :linenos:
@@ -571,11 +571,21 @@
   config.add_view(
       'mypackage.views.my_view',
       route_name='ok',
       request_method=not_('POST')
       header=not_('X-Foo')
       )
The above example will ensure that the view is called if the request method
is *not* ``POST``, at least if no other view is more specific.
does not have the ``X-Foo`` header, at least if no other view is more specific.
A predicate must be *negatable* to use ``not_`` against its value.  The current
set of built-in negatable predicates are: ``path_info``, ``request_param``,
``header``, ``containment``, ``request_type``, ``match_param``,
``physical_path``, and ``effective_principals``.  If you try to use ``not_``
against a non-negatable predicate, an error will be raised at startup time.
You can make sure a custom view predicate added via
:meth:`pyramid.config.Configurator.add_view_predicate` is negatable by adding a
``negatable`` attribute to it that is True.
This technique of wrapping a predicate value in ``not_`` can be used anywhere
predicate values are accepted:
pyramid/config/__init__.py
@@ -1152,7 +1152,17 @@
            # and "i" exist for sorting purposes after conflict resolution.
            ainfo = (order, i, action)
            discriminator = undefer(action['discriminator'])
            try:
                discriminator = undefer(action['discriminator'])
            except:
                t, v, tb = sys.exc_info()
                try:
                    reraise(ConfigurationExecutionError,
                            ConfigurationExecutionError(t, v, action['info']),
                            tb)
                finally:
                   del t, v, tb
            action['discriminator'] = discriminator
            if discriminator is None:
pyramid/config/predicates.py
@@ -48,6 +48,7 @@
        return request.method in self.val
class PathInfoPredicate(object):
    negatable = True
    def __init__(self, val, config):
        self.orig = val
        try:
@@ -65,6 +66,7 @@
        return self.val.match(request.upath_info) is not None
    
class RequestParamPredicate(object):
    negatable = True
    def __init__(self, val, config):
        val = as_sorted_tuple(val)
        reqs = []
@@ -95,6 +97,7 @@
        return True
class HeaderPredicate(object):
    negatable = True
    def __init__(self, val, config):
        name = val
        v = None
@@ -137,6 +140,7 @@
        return self.val in request.accept
class ContainmentPredicate(object):
    negatable = True
    def __init__(self, val, config):
        self.val = config.maybe_dotted(val)
@@ -150,6 +154,7 @@
        return find_interface(ctx, self.val) is not None
    
class RequestTypePredicate(object):
    negatable = True
    def __init__(self, val, config):
        self.val = val
@@ -162,6 +167,7 @@
        return self.val.providedBy(request)
    
class MatchParamPredicate(object):
    negatable = True
    def __init__(self, val, config):
        val = as_sorted_tuple(val)
        self.val = val
@@ -258,6 +264,7 @@
        return True
class PhysicalPathPredicate(object):
    negatable = True
    def __init__(self, val, config):
        if is_nonstr_iter(val):
            self.val = tuple(val)
@@ -276,6 +283,7 @@
        return False
class EffectivePrincipalsPredicate(object):
    negatable = True
    def __init__(self, val, config):
        if is_nonstr_iter(val):
            self.val = set(val)
pyramid/config/util.py
@@ -110,7 +110,13 @@
                    notted = True
                pred = predicate_factory(realval, config)
                if notted:
                    pred = Notted(pred)
                    if getattr(pred, 'negatable', False):
                        pred = Notted(pred)
                    else:
                        raise ConfigurationError(
                            '%s is not a negatable predicate' % (pred.text(),),
                            )
                hashes = pred.phash()
                if not is_nonstr_iter(hashes):
                    hashes = [hashes]
pyramid/tests/test_config/test_util.py
@@ -365,19 +365,31 @@
        from pyramid.exceptions import ConfigurationError
        self.assertRaises(ConfigurationError, self._callFUT, unknown=1)
    def test_notted(self):
    def test_notted_configerror(self):
        from pyramid.exceptions import ConfigurationError
        from pyramid.config import not_
        from pyramid.testing import DummyRequest
        request = DummyRequest()
        _, predicates, _ = self._callFUT(
        self.assertRaises(ConfigurationError, self._callFUT,
            xhr='xhr',
            request_method=not_('POST'),
            header=not_('header'),
            )
    def test_notted_ok(self):
        from pyramid.config import not_
        _, predicates, _ = self._callFUT(
            xhr='xhr',
            path_info=not_('/path_info'),
            header=not_('header'),
            )
        from pyramid.testing import DummyRequest
        request = DummyRequest()
        request.upath_info = request.path_info
        request.is_xhr = False
        self.assertEqual(predicates[0].text(), 'xhr = True')
        self.assertEqual(predicates[1].text(),
                         "!request_method = POST")
        self.assertEqual(predicates[2].text(), '!header header')
        self.assertEqual(predicates[1].text(), '!path_info = /path_info')
        self.assertEqual(predicates[2].text(),
                         "!header header")
        self.assertEqual(predicates[0](None, request), False)
        self.assertEqual(predicates[1](None, request), True)
        self.assertEqual(predicates[2](None, request), True)