Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

add not_ predicate feature

  • Loading branch information...
commit 32333e4d84fe0e71ce097a5dca57025353956dbe 1 parent d79c033
@mcdonc mcdonc authored
View
24 CHANGES.txt
@@ -4,6 +4,30 @@ Next Release
Features
--------
+- Add the ability to invert the result of any view, route, or subscriber
+ predicate using the ``not_`` class. For example::
+
+ from pyramid.config import not_
+
+ @view_config(route_name='myroute', request_method=not_('POST'))
+ def myview(request): ...
+
+ 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).
+
+ The :class:`pyramid.config.not_` class can be used against any value that is
+ a predicate value passed in any of these contexts:
+
+ - ``pyramid.config.Configurator.add_view``
+
+ - ``pyramid.config.Configurator.add_route``
+
+ - ``pyramid.config.Configurator.add_subscriber``
+
+ - ``pyramid.view.view_config``
+
+ - ``pyramid.events.subscriber``
+
- ``scripts/prequest.py``: add support for submitting ``PUT`` and ``PATCH``
requests. See https://github.com/Pylons/pyramid/pull/1033. add support for
submitting ``OPTIONS`` and ``PROPFIND`` requests, and allow users to specify
View
1  docs/api/config.rst
@@ -135,3 +135,4 @@
will only exist for the lifetime of the actual applications for which they
are being used.
+.. autoclass:: not_
View
27 docs/narr/viewconfig.rst
@@ -557,6 +557,33 @@ form of :term:`declarative configuration`, while
:meth:`pyramid.config.Configurator.add_view` is a form of :term:`imperative
configuration`. However, they both do the same thing.
+Inverting Predicate Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can invert the meaning of any predicate value by wrapping it in a call to
+:class:`pyramid.config.not_`.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.config import not_
+
+ config.add_view(
+ 'mypackage.views.my_view',
+ route_name='ok',
+ request_method=not_('POST')
+ )
+
+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.
+
+This technique of wrapping a predicate value in ``not_`` can be used anywhere
+predicate values are accepted:
+
+- :meth:`pyramid.config.Configurator.add_view`
+
+- :meth:`pyramid.view.view_config`
+
.. index::
single: view_config placement
View
26 docs/whatsnew-1.5.rst
@@ -12,6 +12,32 @@ Feature Additions
The feature additions in Pyramid 1.5 follow.
+- Add the ability to invert the result of any view, route, or subscriber
+ predicate value using the ``not_`` class. For example:
+
+ .. code-block:: python
+
+ from pyramid.config import not_
+
+ @view_config(route_name='myroute', request_method=not_('POST'))
+ def myview(request): ...
+
+ 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.
+
+ The :class:`pyramid.config.not_` class can be used against any value that is
+ a predicate value passed in any of these contexts:
+
+ - :meth:`pyramid.config.Configurator.add_view`
+
+ - :meth:`pyramid.config.Configurator.add_route`
+
+ - :meth:`pyramid.config.Configurator.add_subscriber`
+
+ - :meth:`pyramid.view.view_config`
+
+ - :meth:`pyramid.events.subscriber`
+
- View lookup will now search for valid views based on the inheritance
hierarchy of the context. It tries to find views based on the most specific
context first, and upon predicate failure, will move up the inheritance chain
View
5 pyramid/config/__init__.py
@@ -70,7 +70,7 @@
from pyramid.config.settings import SettingsConfiguratorMixin
from pyramid.config.testing import TestingConfiguratorMixin
from pyramid.config.tweens import TweensConfiguratorMixin
-from pyramid.config.util import PredicateList
+from pyramid.config.util import PredicateList, not_
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
@@ -86,6 +86,9 @@
ConfigurationError = ConfigurationError # pyflakes
+not_ = not_ # pyflakes, this is an API
+
+
class Configurator(
TestingConfiguratorMixin,
TweensConfiguratorMixin,
View
1  pyramid/config/predicates.py
@@ -294,3 +294,4 @@ def __call__(self, context, request):
if self.val.issubset(rpset):
return True
return False
+
View
38 pyramid/config/util.py
@@ -28,6 +28,35 @@ def as_sorted_tuple(val):
val = tuple(sorted(val))
return val
+class not_(object):
+ def __init__(self, value):
+ self.value = value
+
+class Notted(object):
+ def __init__(self, predicate):
+ self.predicate = predicate
+
+ def _notted_text(self, val):
+ # if the underlying predicate doesnt return a value, it's not really
+ # a predicate, it's just something pretending to be a predicate,
+ # so dont update the hash
+ if val:
+ val = '!' + val
+ return val
+
+ def text(self):
+ return self._notted_text(self.predicate.text())
+
+ def phash(self):
+ return self._notted_text(self.predicate.phash())
+
+ def __call__(self, context, request):
+ result = self.predicate(context, request)
+ phash = self.phash()
+ if phash:
+ result = not result
+ return result
+
# under = after
# over = before
@@ -74,7 +103,14 @@ def make(self, config, **kw):
if not isinstance(vals, predvalseq):
vals = (vals,)
for val in vals:
- pred = predicate_factory(val, config)
+ realval = val
+ notted = False
+ if isinstance(val, not_):
+ realval = val.value
+ notted = True
+ pred = predicate_factory(realval, config)
+ if notted:
+ pred = Notted(pred)
hashes = pred.phash()
if not is_nonstr_iter(hashes):
hashes = [hashes]
View
47 pyramid/tests/test_config/test_util.py
@@ -364,6 +364,23 @@ def test_request_method_ordering_hashes_same(self):
def test_unknown_predicate(self):
from pyramid.exceptions import ConfigurationError
self.assertRaises(ConfigurationError, self._callFUT, unknown=1)
+
+ def test_notted(self):
+ from pyramid.config import not_
+ from pyramid.testing import DummyRequest
+ request = DummyRequest()
+ _, predicates, _ = self._callFUT(
+ xhr='xhr',
+ request_method=not_('POST'),
+ header=not_('header'),
+ )
+ 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](None, request), True)
+ self.assertEqual(predicates[2](None, request), True)
+
class Test_takes_one_arg(unittest.TestCase):
def _callFUT(self, view, attr=None, argname=None):
@@ -551,7 +568,37 @@ def method(self, request):
foo = Foo()
self.assertTrue(self._callFUT(foo.method))
+class TestNotted(unittest.TestCase):
+ def _makeOne(self, predicate):
+ from pyramid.config.util import Notted
+ return Notted(predicate)
+
+ def test_it_with_phash_val(self):
+ pred = DummyPredicate('val')
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.text(), '!val')
+ self.assertEqual(inst.phash(), '!val')
+ self.assertEqual(inst(None, None), False)
+
+ def test_it_without_phash_val(self):
+ pred = DummyPredicate('')
+ inst = self._makeOne(pred)
+ self.assertEqual(inst.text(), '')
+ self.assertEqual(inst.phash(), '')
+ self.assertEqual(inst(None, None), True)
+
+class DummyPredicate(object):
+ def __init__(self, result):
+ self.result = result
+
+ def text(self):
+ return self.result
+
+ phash = text
+ def __call__(self, context, request):
+ return True
+
class DummyCustomPredicate(object):
def __init__(self):
self.__text__ = 'custom predicate'

2 comments on commit 32333e4

@tshepang

I find not_ uglier than is_not. The latter is also clearer, even if a bit. Too late to change?

@mcdonc
Owner

TBH I'd rather leave it the way it is. You'll barely notice after a while. ;-)

Please sign in to comment.
Something went wrong with that request. Please try again.