summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst4
-rw-r--r--docs/narr/viewconfig.rst6
-rw-r--r--src/pyramid/config/routes.py3
-rw-r--r--src/pyramid/config/views.py3
-rw-r--r--src/pyramid/predicates.py52
-rw-r--r--tests/test_config/test_predicates.py69
6 files changed, 107 insertions, 30 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 67256db8d..3bd14705d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,10 @@ unreleased
Features
--------
+- It is now possible to pass multiple values to the ``header`` predicate
+ for route and view configuration.
+ See https://github.com/Pylons/pyramid/pull/3576
+
- Add support for Python 3.8.
See https://github.com/Pylons/pyramid/pull/3547
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 891d294d7..c40f1181a 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -448,10 +448,10 @@ configured view.
associated view callable.
``header``
- This value represents an HTTP header name or a header name/value pair.
+ This value matches one or more HTTP header names or header name/value pairs.
- If ``header`` is specified, it must be a header name or a
- ``headername:headervalue`` pair.
+ If ``header`` is specified, it must be a string or a sequence of strings,
+ each being a header name or a ``headername:headervalue`` pair.
If ``header`` is specified without a value (a bare header name only, e.g.,
``If-Modified-Since``), the view will only be invoked if the HTTP header
diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py
index 7c78fbfa7..5daa1dc00 100644
--- a/src/pyramid/config/routes.py
+++ b/src/pyramid/config/routes.py
@@ -220,7 +220,8 @@ class RoutesConfiguratorMixin:
header
This argument represents an HTTP header name or a header
- name/value pair. If the argument contains a ``:`` (colon),
+ name/value pair, or a sequence of them.
+ If the argument contains a ``:`` (colon),
it will be considered a name/value pair
(e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). If
the value contains a colon, the value portion should be a
diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py
index 466c31f94..9053160fa 100644
--- a/src/pyramid/config/views.py
+++ b/src/pyramid/config/views.py
@@ -671,7 +671,8 @@ class ViewsConfiguratorMixin:
header
This value represents an HTTP header name or a header
- name/value pair. If the value contains a ``:`` (colon), it
+ name/value pair, or a sequence of them.
+ If the value contains a ``:`` (colon), it
will be considered a name/value pair
(e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). The
value portion should be a regular expression. If the value
diff --git a/src/pyramid/predicates.py b/src/pyramid/predicates.py
index f51ea3b21..0c74ed6d5 100644
--- a/src/pyramid/predicates.py
+++ b/src/pyramid/predicates.py
@@ -98,33 +98,43 @@ class RequestParamPredicate:
class HeaderPredicate:
def __init__(self, val, config):
- name = val
- v = None
- if ':' in name:
- name, val_str = name.split(':', 1)
- try:
- v = re.compile(val_str)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- if v is None:
- self._text = 'header %s' % (name,)
- else:
- self._text = 'header %s=%s' % (name, val_str)
- self.name = name
- self.val = v
+ values = []
+
+ val = as_sorted_tuple(val)
+ for name in val:
+ v, val_str = None, None
+ if ':' in name:
+ name, val_str = name.split(':', 1)
+ try:
+ v = re.compile(val_str)
+ except re.error as why:
+ raise ConfigurationError(why.args[0])
+
+ values.append((name, v, val_str))
+
+ self.val = values
def text(self):
- return self._text
+ return 'header %s' % ', '.join(
+ '%s=%s' % (name, val_str) if val_str else name
+ for name, _, val_str in self.val
+ )
phash = text
def __call__(self, context, request):
- if self.val is None:
- return self.name in request.headers
- val = request.headers.get(self.name)
- if val is None:
- return False
- return self.val.match(val) is not None
+ for name, val, _ in self.val:
+ if val is None:
+ if name not in request.headers:
+ return False
+ else:
+ value = request.headers.get(name)
+ if value is None:
+ return False
+ if val.match(value) is None:
+ return False
+
+ return True
class AcceptPredicate:
diff --git a/tests/test_config/test_predicates.py b/tests/test_config/test_predicates.py
index 31e7a38e7..c0185340f 100644
--- a/tests/test_config/test_predicates.py
+++ b/tests/test_config/test_predicates.py
@@ -315,14 +315,14 @@ class TestPredicateList(unittest.TestCase):
def test_predicate_text_is_correct_when_multiple(self):
_, predicates, _ = self._callFUT(
request_method=('one', 'two'),
- request_param=('param1', 'param2=on'),
- header='header:text/*',
+ request_param=('par2=on', 'par1'),
+ header=('header2', 'header1:val.*'),
accept=('accept1', 'accept2'),
match_param=('foo=bar', 'baz=bim'),
)
self.assertEqual(predicates[0].text(), "request_method = one,two")
- self.assertEqual(predicates[1].text(), 'request_param param1,param2=on')
- self.assertEqual(predicates[2].text(), 'header header=text/*')
+ self.assertEqual(predicates[1].text(), 'request_param par1,par2=on')
+ self.assertEqual(predicates[2].text(), 'header header1=val.*, header2')
self.assertEqual(predicates[3].text(), 'accept = accept1, accept2')
self.assertEqual(predicates[4].text(), "match_param baz=bim,foo=bar")
@@ -368,6 +368,66 @@ class TestPredicateList(unittest.TestCase):
hash2, _, __ = self._callFUT(request_method='GET')
self.assertEqual(hash1, hash2)
+ def test_header_simple(self):
+ _, predicates, _ = self._callFUT(header='foo')
+ request = DummyRequest()
+ request.headers = {'foo': 'bars', 'baz': 'foo'}
+ self.assertTrue(predicates[0](Dummy(), request))
+
+ def test_header_simple_fails(self):
+ _, predicates, _ = self._callFUT(header='content-length')
+ request = DummyRequest()
+ request.headers = {'foo': 'bars', 'baz': 'foo'}
+ self.assertFalse(predicates[0](Dummy(), request))
+
+ def test_header_with_value(self):
+ _, predicates, _ = self._callFUT(header='foo:bar')
+ request = DummyRequest()
+ request.headers = {'foo': 'bars', 'baz': 'foo'}
+ self.assertTrue(predicates[0](Dummy(), request))
+
+ def test_header_with_value_fails(self):
+ _, predicates, _ = self._callFUT(header='foo:bar')
+ request = DummyRequest()
+ request.headers = {'foo': 'nobar', 'baz': 'foo'}
+ self.assertFalse(predicates[0](Dummy(), request))
+
+ def test_header_multiple(self):
+ _, predicates, _ = self._callFUT(header=('foo', 'content-length'))
+ request = DummyRequest()
+ request.headers = {'foo': 'bars', 'content-length': '42'}
+ self.assertTrue(predicates[0](Dummy(), request))
+
+ def test_header_multiple_fails(self):
+ _, predicates, _ = self._callFUT(header=('foo', 'content-encoding'))
+ request = DummyRequest()
+ request.headers = {'foo': 'bars', 'content-length': '42'}
+ self.assertFalse(predicates[0](Dummy(), request))
+
+ def test_header_multiple_with_values(self):
+ _, predicates, _ = self._callFUT(header=('foo:bar', 'spam:egg'))
+ request = DummyRequest()
+ request.headers = {'foo': 'bars', 'spam': 'eggs'}
+ self.assertTrue(predicates[0](Dummy(), request))
+
+ def test_header_multiple_with_values_fails(self):
+ _, predicates, _ = self._callFUT(header=('foo:bar', 'spam:egg$'))
+ request = DummyRequest()
+ request.headers = {'foo': 'bars', 'spam': 'eggs'}
+ self.assertFalse(predicates[0](Dummy(), request))
+
+ def test_header_multiple_mixed(self):
+ _, predicates, _ = self._callFUT(header=('foo:bar', 'spam'))
+ request = DummyRequest()
+ request.headers = {'foo': 'bars', 'spam': 'ham'}
+ self.assertTrue(predicates[0](Dummy(), request))
+
+ def test_header_multiple_mixed_fails(self):
+ _, predicates, _ = self._callFUT(header=('foo:bar', 'spam'))
+ request = DummyRequest()
+ request.headers = {'foo': 'nobar', 'spamme': 'ham'}
+ self.assertFalse(predicates[0](Dummy(), request))
+
def test_unknown_predicate(self):
from pyramid.exceptions import ConfigurationError
@@ -486,6 +546,7 @@ class DummyRequest:
environ = {}
self.environ = environ
self.params = {}
+ self.headers = {}
self.cookies = {}