diff options
| -rw-r--r-- | CHANGES.txt | 24 | ||||
| -rw-r--r-- | docs/api/config.rst | 1 | ||||
| -rw-r--r-- | docs/narr/viewconfig.rst | 27 | ||||
| -rw-r--r-- | docs/whatsnew-1.5.rst | 26 | ||||
| -rw-r--r-- | pyramid/config/__init__.py | 5 | ||||
| -rw-r--r-- | pyramid/config/predicates.py | 1 | ||||
| -rw-r--r-- | pyramid/config/util.py | 38 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_util.py | 47 |
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' |
