summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt24
-rw-r--r--docs/api/config.rst1
-rw-r--r--docs/narr/viewconfig.rst27
-rw-r--r--docs/whatsnew-1.5.rst26
-rw-r--r--pyramid/config/__init__.py5
-rw-r--r--pyramid/config/predicates.py1
-rw-r--r--pyramid/config/util.py38
-rw-r--r--pyramid/tests/test_config/test_util.py47
8 files changed, 167 insertions, 2 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 0031fc635..c100f7fa6 100644
--- a/CHANGES.txt
+++ b/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
diff --git a/docs/api/config.rst b/docs/api/config.rst
index 39d504348..1f65be9f1 100644
--- a/docs/api/config.rst
+++ b/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_
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 241ce62b5..388371a0d 100644
--- a/docs/narr/viewconfig.rst
+++ b/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
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
index 47b768eb9..ee2250017 100644
--- a/docs/whatsnew-1.5.rst
+++ b/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
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 5bb9987af..d52ee0e7a 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -70,7 +70,7 @@ 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 PredicateList
+from pyramid.config.util import PredicateList, not_
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
@@ -86,6 +86,9 @@ _marker = object()
ConfigurationError = ConfigurationError # pyflakes
+not_ = not_ # pyflakes, this is an API
+
+
class Configurator(
TestingConfiguratorMixin,
TweensConfiguratorMixin,
diff --git a/pyramid/config/predicates.py b/pyramid/config/predicates.py
index ded8fbfbf..c8f66e83d 100644
--- a/pyramid/config/predicates.py
+++ b/pyramid/config/predicates.py
@@ -294,3 +294,4 @@ class EffectivePrincipalsPredicate(object):
if self.val.issubset(rpset):
return True
return False
+
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index af0dd1641..a98e71cf5 100644
--- a/pyramid/config/util.py
+++ b/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 @@ class PredicateList(object):
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]
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index a984acfd0..f6cd414fb 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -364,6 +364,23 @@ class TestPredicateList(unittest.TestCase):
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 @@ class Test_takes_one_arg(unittest.TestCase):
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'