summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst11
-rw-r--r--docs/narr/viewconfig.rst14
-rw-r--r--pyramid/config/routes.py38
-rw-r--r--pyramid/config/util.py47
-rw-r--r--pyramid/config/views.py61
-rw-r--r--pyramid/predicates.py16
-rw-r--r--pyramid/tests/test_config/test_routes.py18
-rw-r--r--pyramid/tests/test_config/test_util.py27
-rw-r--r--pyramid/tests/test_config/test_views.py35
-rw-r--r--pyramid/tests/test_integration.py236
10 files changed, 183 insertions, 320 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 3934c5aed..e1f782e60 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -113,6 +113,17 @@ Deprecations
implementation if you're still using these features.
See https://github.com/Pylons/pyramid/pull/3353
+- Media ranges are deprecated in the ``accept`` argument of
+ ``pyramid.config.Configurator.add_route``. Use a list of explicit
+ media types to ``add_route`` to support multiple types.
+
+- Media ranges are deprecated in the ``accept`` argument of
+ ``pyramid.config.Configurator.add_view``. There is no replacement for
+ ranges to ``add_view``, but after much discussion the workflow is
+ fundamentally ambiguous in the face of various client-supplied values for
+ the ``Accept`` header.
+ See https://github.com/Pylons/pyramid/pull/3326
+
Backward Incompatibilities
--------------------------
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index c44109661..238599528 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -1093,23 +1093,17 @@ Default Accept Ordering
~~~~~~~~~~~~~~~~~~~~~~~
:app:`Pyramid` will always sort multiple views with the same ``(name, context, route_name)`` first by the specificity of the ``accept`` offer.
-This means that ``text/plain`` will always be offered before ``text/*``.
-Similarly ``text/plain;charset=utf8`` will always be offered before ``text/plain``.
-The following order is always preserved between the following offers (more preferred to less preferred):
+For any set of media type offers with the same ``type/subtype``, the offers with params will weigh more than the bare ``type/subtype`` offer.
+This means that ``text/plain;charset=utf8`` will always be offered before ``text/plain``.
-- ``type/subtype;params``
-- ``type/subtype``
-- ``type/*``
-- ``*/*``
-
-Within each of these levels of specificity, the ordering is ambiguous and may be controlled using :meth:`pyramid.config.Configurator.add_accept_view_order`. For example, to sort ``text/plain`` higher than ``text/html`` and to prefer a ``charset=utf8`` versus a ``charset=latin-1`` within the ``text/plain`` media type:
+By default, within a given ``type/subtype``, the order of offers is ambiguous. For example, ``text/plain;charset=utf8`` versus ``text/plain;charset=latin1`` are sorted in an unspecified way. Similarly, between media types the order is also unspecified other than the defaults described below. For example, ``image/jpeg`` versus ``image/png`` versus ``application/pdf``. In these cases, the ordering may be controlled using :meth:`pyramid.config.Configurator.add_accept_view_order`. For example, to sort ``text/plain`` higher than ``text/html`` and to prefer a ``charset=utf8`` versus a ``charset=latin-1`` within the ``text/plain`` media type:
.. code-block:: python
config.add_accept_view_order('text/plain', weighs_more_than='text/html')
config.add_accept_view_order('text/plain;charset=utf8', weighs_more_than='text/plain;charset=latin-1')
-It is an error to try and sort accept headers across levels of specificity. You can only sort a ``type/subtype`` against another ``type/subtype``, not against a ``type/*``. That ordering is a hard requirement.
+It is an error to try and sort accept headers across levels of specificity. You can only sort a ``type/subtype`` against another ``type/subtype``, not against a ``type/subtype;params``. That ordering is a hard requirement.
By default, :app:`Pyramid` defines a very simple priority ordering for views that prefers human-readable responses over JSON:
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
index 01917537d..4d1f830c8 100644
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -22,6 +22,7 @@ import pyramid.predicates
from pyramid.config.util import (
action_method,
+ normalize_accept_offer,
predvalseq,
)
@@ -228,16 +229,24 @@ class RoutesConfiguratorMixin(object):
A :term:`media type` that will be matched against the ``Accept``
HTTP request header. This value may be a specific media type such
- as ``text/html``, or a range like ``text/*``, or a list of the same.
- If the media type is acceptable by the ``Accept`` header of the
- request, or if the ``Accept`` header isn't set at all in the
- request, this predicate will match. If this does not match the
- ``Accept`` header of the request, route matching continues.
+ as ``text/html``, or a list of the same. If the media type is
+ acceptable by the ``Accept`` header of the request, or if the
+ ``Accept`` header isn't set at all in the request, this predicate
+ will match. If this does not match the ``Accept`` header of the
+ request, route matching continues.
If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is
not taken into consideration when deciding whether or not to select
the route.
+ .. versionchanged:: 1.10
+
+ Specifying a media range is deprecated due to changes in WebOb
+ and ambiguities that occur when trying to match ranges against
+ ranges in the ``Accept`` header. Support will be removed in
+ :app:`Pyramid` 2.0. Use a list of specific media types to match
+ more than one type.
+
effective_principals
If specified, this value should be a :term:`principal` identifier or
@@ -297,9 +306,22 @@ class RoutesConfiguratorMixin(object):
if accept is not None:
if not is_nonstr_iter(accept):
- accept = [accept]
-
- accept = [accept_option.lower() for accept_option in accept]
+ if '*' in accept:
+ warnings.warn(
+ ('Passing a media range to the "accept" argument of '
+ 'Configurator.add_route is deprecated as of Pyramid '
+ '1.10. Use a list of explicit media types.'),
+ DeprecationWarning,
+ stacklevel=3,
+ )
+ # XXX switch this to verify=True when range support is dropped
+ accept = [normalize_accept_offer(accept, verify=False)]
+
+ else:
+ accept = [
+ normalize_accept_offer(accept_option)
+ for accept_option in accept
+ ]
# these are route predicates; if they do not match, the next route
# in the routelist will be tried
diff --git a/pyramid/config/util.py b/pyramid/config/util.py
index 7024fa862..8ebc8e45c 100644
--- a/pyramid/config/util.py
+++ b/pyramid/config/util.py
@@ -221,16 +221,18 @@ class PredicateList(object):
return order, preds, phash.hexdigest()
+def normalize_accept_offer(offer, verify=True):
+ if verify:
+ Accept.parse_offer(offer)
+ return offer.lower()
+
+
def sort_accept_offers(offers, order=None):
"""
- Sort a list of offers by specificity and preference.
+ Sort a list of offers by preference.
- Supported offers are of the following forms, ordered by specificity
- (higher to lower):
-
- - ``type/subtype;params`` and ``type/subtype``
- - ``type/*``
- - ``*/*``
+ For a given ``type/subtype`` category of offers, this algorithm will
+ always sort offers with params higher than the bare offer.
:param offers: A list of offers to be sorted.
:param order: A weighted list of offers where items closer to the start of
@@ -249,20 +251,10 @@ def sort_accept_offers(offers, order=None):
def offer_sort_key(value):
"""
- (category, type_weight, params_weight)
-
- category:
- 1 - foo/bar and foo/bar;params
- 2 - foo/*
- 3 - */*
+ (type_weight, params_weight)
type_weight:
- if category 1 & 2:
- - index of type/* in order list
- - ``max_weight`` if no match is found
-
- - index of type/subtype in order list
- - index of type/* in order list + ``max_weight``
+ - index of specific ``type/subtype`` in order list
- ``max_weight * 2`` if no match is found
params_weight:
@@ -273,17 +265,10 @@ def sort_accept_offers(offers, order=None):
"""
parsed = Accept.parse_offer(value)
- if value == '*/*':
- return (3, 0, 0)
-
- elif parsed.subtype == '*':
- type_w = find_order_index(value, max_weight)
- return (2, type_w, 0)
-
- type_w = find_order_index(parsed.type + '/' + parsed.subtype, None)
- if type_w is None:
- type_w = max_weight + find_order_index(
- parsed.type + '/*', max_weight)
+ type_w = find_order_index(
+ parsed.type + '/' + parsed.subtype,
+ max_weight,
+ )
if parsed.params:
param_w = find_order_index(value, max_weight)
@@ -291,6 +276,6 @@ def sort_accept_offers(offers, order=None):
else:
param_w = max_weight + 1
- return (1, type_w, param_w)
+ return (type_w, param_w)
return sorted(offers, key=offer_sort_key)
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 15ceadcbc..ca299c4b5 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -88,6 +88,7 @@ from pyramid.config.util import (
action_method,
DEFAULT_PHASH,
MAX_ORDER,
+ normalize_accept_offer,
predvalseq,
sort_accept_offers,
)
@@ -125,7 +126,7 @@ class MultiView(object):
self.views[i] = (order, view, phash)
return
- if accept is None:
+ if accept is None or '*' in accept:
self.views.append((order, view, phash))
self.views.sort(key=operator.itemgetter(0))
else:
@@ -676,8 +677,8 @@ class ViewsConfiguratorMixin(object):
accept
A :term:`media type` that will be matched against the ``Accept``
- HTTP request header. This value may be a specific media type such
- as ``text/html``, or a range like ``text/*``. If the media type is
+ HTTP request header. This value must be a specific media type such
+ as ``text/html`` or ``text/html;level=1``. If the media type is
acceptable by the ``Accept`` header of the request, or if the
``Accept`` header isn't set at all in the request, this predicate
will match. If this does not match the ``Accept`` header of the
@@ -689,6 +690,12 @@ class ViewsConfiguratorMixin(object):
See :ref:`accept_content_negotiation` for more information.
+ .. versionchanged:: 1.10
+
+ Specifying a media range is deprecated and will be removed in
+ :app:`Pyramid` 2.0. Use explicit media types to avoid any
+ ambiguities in content negotiation.
+
path_info
This value represents a regular expression pattern that will
@@ -821,7 +828,17 @@ class ViewsConfiguratorMixin(object):
raise ConfigurationError(
'A list is not supported in the "accept" view predicate.',
)
- accept = accept.lower()
+ if '*' in accept:
+ warnings.warn(
+ ('Passing a media range to the "accept" argument of '
+ 'Configurator.add_view is deprecated as of Pyramid 1.10. '
+ 'Use explicit media types to avoid ambiguities in '
+ 'content negotiation that may impact your users.'),
+ DeprecationWarning,
+ stacklevel=4,
+ )
+ # XXX when media ranges are gone, switch verify=True
+ accept = normalize_accept_offer(accept, verify=False)
view = self.maybe_dotted(view)
context = self.maybe_dotted(context)
@@ -1273,46 +1290,38 @@ class ViewsConfiguratorMixin(object):
.. versionadded:: 1.10
"""
- if value == '*/*':
- raise ConfigurationError(
- 'cannot specify an ordering for an offer of */*')
-
- def normalize_type(type):
- return type.lower()
-
def check_type(than):
than_type, than_subtype, than_params = Accept.parse_offer(than)
- if (
- # text/* vs text/plain
- (offer_subtype == '*') ^ (than_subtype == '*')
- # text/plain vs text/html;charset=utf8
- or (bool(offer_params) ^ bool(than_params))
- ):
+ # text/plain vs text/html;charset=utf8
+ if bool(offer_params) ^ bool(than_params):
raise ConfigurationError(
- 'cannot compare across media range specificity levels')
+ 'cannot compare a media type with params to one without '
+ 'params')
# text/plain;charset=utf8 vs text/html;charset=utf8
if offer_params and (
offer_subtype != than_subtype or offer_type != than_type
):
raise ConfigurationError(
- 'cannot compare params across media types')
+ 'cannot compare params across different media types')
+
+ def normalize_types(thans):
+ thans = [normalize_accept_offer(o, verify=False) for o in thans]
+ for o in thans:
+ check_type(o)
+ return thans
- value = normalize_type(value)
+ value = normalize_accept_offer(value, verify=False)
offer_type, offer_subtype, offer_params = Accept.parse_offer(value)
if weighs_more_than:
if not is_nonstr_iter(weighs_more_than):
weighs_more_than = [weighs_more_than]
- weighs_more_than = [normalize_type(w) for w in weighs_more_than]
- for than in weighs_more_than:
- check_type(than)
+ weighs_more_than = normalize_types(weighs_more_than)
if weighs_less_than:
if not is_nonstr_iter(weighs_less_than):
weighs_less_than = [weighs_less_than]
- weighs_less_than = [normalize_type(w) for w in weighs_less_than]
- for than in weighs_less_than:
- check_type(than)
+ weighs_less_than = normalize_types(weighs_less_than)
discriminator = ('accept view order', value)
intr = self.introspectable(
diff --git a/pyramid/predicates.py b/pyramid/predicates.py
index 5bd98fdf2..97edae8a0 100644
--- a/pyramid/predicates.py
+++ b/pyramid/predicates.py
@@ -130,10 +130,16 @@ class HeaderPredicate(object):
return self.val.match(val) is not None
class AcceptPredicate(object):
- def __init__(self, val, config):
- if not is_nonstr_iter(val):
- val = (val,)
- self.values = val
+ _is_using_deprecated_ranges = False
+
+ def __init__(self, values, config):
+ if not is_nonstr_iter(values):
+ values = (values,)
+ # deprecated media ranges were only supported in versions of the
+ # predicate that didn't support lists, so check it here
+ if len(values) == 1 and '*' in values[0]:
+ self._is_using_deprecated_ranges = True
+ self.values = values
def text(self):
return 'accept = %s' % (', '.join(self.values),)
@@ -141,6 +147,8 @@ class AcceptPredicate(object):
phash = text
def __call__(self, context, request):
+ if self._is_using_deprecated_ranges:
+ return self.values[0] in request.accept
return bool(request.accept.acceptable_offers(self.values))
class ContainmentPredicate(object):
diff --git a/pyramid/tests/test_config/test_routes.py b/pyramid/tests/test_config/test_routes.py
index afae4db33..9f4ce9bc6 100644
--- a/pyramid/tests/test_config/test_routes.py
+++ b/pyramid/tests/test_config/test_routes.py
@@ -203,6 +203,18 @@ class RoutesConfiguratorMixinTests(unittest.TestCase):
request.accept = DummyAccept('text/html')
self.assertEqual(predicate(None, request), False)
+ def test_add_route_with_wildcard_accept(self):
+ config = self._makeOne(autocommit=True)
+ config.add_route('name', 'path', accept='text/*')
+ route = self._assertRoute(config, 'name', 'path', 1)
+ predicate = route.predicates[0]
+ request = self._makeRequest(config)
+ request.accept = DummyAccept('text/xml', contains=True)
+ self.assertEqual(predicate(None, request), True)
+ request = self._makeRequest(config)
+ request.accept = DummyAccept('application/json', contains=False)
+ self.assertEqual(predicate(None, request), False)
+
def test_add_route_no_pattern_with_path(self):
config = self._makeOne(autocommit=True)
config.add_route('name', path='path')
@@ -270,8 +282,9 @@ class DummyRequest:
self.cookies = {}
class DummyAccept(object):
- def __init__(self, *matches):
+ def __init__(self, *matches, **kw):
self.matches = list(matches)
+ self.contains = kw.pop('contains', False)
def acceptable_offers(self, offers):
results = []
@@ -279,3 +292,6 @@ class DummyAccept(object):
if match in offers:
results.append((match, 1.0))
return results
+
+ def __contains__(self, value):
+ return self.contains
diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py
index bbda615c9..540f3d14c 100644
--- a/pyramid/tests/test_config/test_util.py
+++ b/pyramid/tests/test_config/test_util.py
@@ -437,18 +437,11 @@ class Test_sort_accept_offers(unittest.TestCase):
return sort_accept_offers(offers, order)
def test_default_specificities(self):
- result = self._callFUT(['*/*', 'text/*', 'text/html', 'text/html;charset=utf8'])
+ result = self._callFUT(['text/html', 'text/html;charset=utf8'])
self.assertEqual(result, [
- 'text/html;charset=utf8', 'text/html', 'text/*', '*/*',
+ 'text/html;charset=utf8', 'text/html',
])
- def test_wildcard_type_order(self):
- result = self._callFUT(
- ['*/*', 'text/*', 'image/*'],
- ['image/*', 'text/*'],
- )
- self.assertEqual(result, ['image/*', 'text/*', '*/*'])
-
def test_specific_type_order(self):
result = self._callFUT(
['text/html', 'application/json', 'text/html;charset=utf8', 'text/plain'],
@@ -474,22 +467,6 @@ class Test_sort_accept_offers(unittest.TestCase):
)
self.assertEqual(result, ['text/plain;charset=latin1', 'text/html;charset=utf8'])
- def test_params_inherit_wildcard_prefs(self):
- result = self._callFUT(
- ['image/png;progressive=1', 'text/html;charset=utf8'],
- ['text/*', 'image/*'],
- )
- self.assertEqual(result, ['text/html;charset=utf8', 'image/png;progressive=1'])
-
- def test_type_overrides_wildcard_prefs(self):
- result = self._callFUT(
- ['text/html;charset=utf8', 'image/png', 'foo/bar', 'text/bar'],
- ['foo/*', 'text/*', 'image/*', 'image/png', 'text/html'],
- )
- self.assertEqual(result, [
- 'image/png', 'text/html;charset=utf8', 'foo/bar', 'text/bar',
- ])
-
class DummyCustomPredicate(object):
def __init__(self):
self.__text__ = 'custom predicate'
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index abc10d3f5..6565a35d5 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -1477,6 +1477,25 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request.accept = DummyAccept('text/html')
self._assertNotFound(wrapper, None, request)
+ def test_add_view_with_range_accept_match(self):
+ from pyramid.renderers import null_renderer
+ view = lambda *arg: 'OK'
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, accept='text/*', renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ request = self._makeRequest(config)
+ request.accept = DummyAccept('text/html', contains=True)
+ self.assertEqual(wrapper(None, request), 'OK')
+
+ def test_add_view_with_range_accept_nomatch(self):
+ view = lambda *arg: 'OK'
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, accept='text/*')
+ wrapper = self._getViewCallable(config)
+ request = self._makeRequest(config)
+ request.accept = DummyAccept('application/json', contains=False)
+ self._assertNotFound(wrapper, None, request)
+
def test_add_view_with_containment_true(self):
from pyramid.renderers import null_renderer
from zope.interface import directlyProvides
@@ -2431,20 +2450,19 @@ class TestViewsConfigurationMixin(unittest.TestCase):
])
def test_add_accept_view_order_throws_on_wildcard(self):
- from pyramid.exceptions import ConfigurationError
config = self._makeOne(autocommit=True)
self.assertRaises(
- ConfigurationError, config.add_accept_view_order, '*/*',
+ ValueError, config.add_accept_view_order, '*/*',
)
def test_add_accept_view_order_throws_on_type_mismatch(self):
config = self._makeOne(autocommit=True)
self.assertRaises(
- ConfigurationError, config.add_accept_view_order,
+ ValueError, config.add_accept_view_order,
'text/*', weighs_more_than='text/html',
)
self.assertRaises(
- ConfigurationError, config.add_accept_view_order,
+ ValueError, config.add_accept_view_order,
'text/html', weighs_less_than='application/*',
)
self.assertRaises(
@@ -2577,7 +2595,8 @@ class TestMultiView(unittest.TestCase):
self.assertEqual(set(mv.accepts), set(['text/xml', 'text/html']))
self.assertEqual(mv.views, [(99, 'view2', None), (100, 'view', None)])
mv.add('view6', 98, accept='text/*')
- self.assertEqual(mv.media_views['text/*'], [(98, 'view6', None)])
+ self.assertEqual(mv.views, [
+ (98, 'view6', None), (99, 'view2', None), (100, 'view', None)])
def test_add_with_phash(self):
mv = self._makeOne()
@@ -3503,8 +3522,9 @@ class DummyContext:
pass
class DummyAccept(object):
- def __init__(self, *matches):
+ def __init__(self, *matches, **kw):
self.matches = list(matches)
+ self.contains = kw.pop('contains', False)
def acceptable_offers(self, offers):
results = []
@@ -3513,6 +3533,9 @@ class DummyAccept(object):
results.append((match, 1.0))
return results
+ def __contains__(self, value):
+ return self.contains
+
class DummyConfig:
def __init__(self):
self.registry = DummyRegistry()
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index 1bf2abb4f..eedc145ad 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -759,219 +759,37 @@ class AcceptContentTypeTest(unittest.TestCase):
res = app.get('/hello', headers={'Accept': 'something/else'}, status=200)
self.assertEqual(res.content_type, 'text/x-fallback')
-class AddViewAcceptArgMediaRangeAllTest(unittest.TestCase):
- def setUp(self):
- def view(request):
- return 'text/plain'
- from pyramid.config import Configurator
- config = Configurator()
- config.add_route('root', '/')
- config.add_view(
- view, route_name='root', accept='*/*', renderer='string',
- )
- app = config.make_wsgi_app()
- self.testapp = TestApp(app)
-
- def tearDown(self):
- import pyramid.config
- pyramid.config.global_registries.empty()
-
- def test_no_header(self):
- res = self.testapp.get('/', headers={}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_all(self):
- res = self.testapp.get('/', headers={'Accept': '*/*'}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_all_subtypes_of_type(self):
- res = self.testapp.get('/', headers={'Accept': 'text/*'}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_specific_media_type(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/plain'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_ruled_out_by_specific_media_type_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/plain;q=0, */*'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_ruled_out_by_type_range_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/*;q=0, text/html'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_ruled_out_by_all_range_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': '*/*;q=0, text/html'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
-class AddViewAcceptArgMediaRangeAllSubtypesOfTypeTest(unittest.TestCase):
- def setUp(self):
- def view(request):
- return 'text/plain'
- from pyramid.config import Configurator
- config = Configurator()
- config.add_route('root', '/')
- config.add_view(
- view, route_name='root', accept='text/*', renderer='string',
- )
- app = config.make_wsgi_app()
- self.testapp = TestApp(app)
-
- def tearDown(self):
- import pyramid.config
- pyramid.config.global_registries.empty()
-
- def test_no_header(self):
- res = self.testapp.get('/', headers={}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_all(self):
- res = self.testapp.get('/', headers={'Accept': '*/*'}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_all_subtypes_of_type(self):
- res = self.testapp.get('/', headers={'Accept': 'text/*'}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_specific_media_type(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/plain'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_none_acceptable(self):
- self.testapp.get('/', headers={'Accept': 'application/*'}, status=404)
-
- def test_header_ruled_out_by_specific_media_type_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/plain;q=0, */*'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_ruled_out_by_type_range_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/*;q=0, text/html'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_ruled_out_by_all_range_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': '*/*;q=0, text/html'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
-class AddRouteAcceptArgMediaRangeAllTest(unittest.TestCase):
- def setUp(self):
- def view(request):
- return 'text/plain'
- from pyramid.config import Configurator
- config = Configurator()
- config.add_route('root', '/', accept='*/*')
- config.add_view(view, route_name='root', renderer='string')
- app = config.make_wsgi_app()
- self.testapp = TestApp(app)
-
- def tearDown(self):
- import pyramid.config
- pyramid.config.global_registries.empty()
-
- def test_no_header(self):
- res = self.testapp.get('/', headers={}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_all(self):
- res = self.testapp.get('/', headers={'Accept': '*/*'}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_all_subtypes_of_type(self):
- res = self.testapp.get('/', headers={'Accept': 'text/*'}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_specific_media_type(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/plain'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_ruled_out_by_specific_media_type_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/plain;q=0, */*'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_ruled_out_by_type_range_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/*;q=0, text/html'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_ruled_out_by_all_range_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': '*/*;q=0, text/html'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
-class AddRouteAcceptArgMediaRangeAllSubtypesOfTypeTest(unittest.TestCase):
- def setUp(self):
- def view(request):
- return 'text/plain'
- from pyramid.config import Configurator
- config = Configurator()
- config.add_route('root', '/', accept='text/*')
- config.add_view(view, route_name='root', renderer='string')
- app = config.make_wsgi_app()
- self.testapp = TestApp(app)
-
- def tearDown(self):
- import pyramid.config
- pyramid.config.global_registries.empty()
-
- def test_no_header(self):
- res = self.testapp.get('/', headers={}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_all(self):
- res = self.testapp.get('/', headers={'Accept': '*/*'}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_all_subtypes_of_type(self):
- res = self.testapp.get('/', headers={'Accept': 'text/*'}, status=200)
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_specific_media_type(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/plain'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
-
- def test_header_none_acceptable(self):
- self.testapp.get('/', headers={'Accept': 'application/*'}, status=404)
-
- def test_header_ruled_out_by_specific_media_type_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/plain;q=0, */*'}, status=200,
- )
+ def test_deprecated_ranges_in_route_predicate(self):
+ config = self._makeConfig()
+ config.add_route('foo', '/foo', accept='text/*')
+ config.add_view(lambda r: 'OK', route_name='foo', renderer='string')
+ app = self._makeTestApp(config)
+ res = app.get('/foo', headers={
+ 'Accept': 'application/json; q=1.0, text/plain; q=0.9',
+ }, status=200)
self.assertEqual(res.content_type, 'text/plain')
+ self.assertEqual(res.body, b'OK')
+ res = app.get('/foo', headers={
+ 'Accept': 'application/json',
+ }, status=404)
+ self.assertEqual(res.content_type, 'application/json')
- def test_header_ruled_out_by_type_range_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': 'text/*;q=0, text/html'}, status=200,
- )
+ def test_deprecated_ranges_in_view_predicate(self):
+ config = self._makeConfig()
+ config.add_route('foo', '/foo')
+ config.add_view(lambda r: 'OK', route_name='foo',
+ accept='text/*', renderer='string')
+ app = self._makeTestApp(config)
+ res = app.get('/foo', headers={
+ 'Accept': 'application/json; q=1.0, text/plain; q=0.9',
+ }, status=200)
self.assertEqual(res.content_type, 'text/plain')
+ self.assertEqual(res.body, b'OK')
+ res = app.get('/foo', headers={
+ 'Accept': 'application/json',
+ }, status=404)
+ self.assertEqual(res.content_type, 'application/json')
- def test_header_ruled_out_by_all_range_q0(self):
- res = self.testapp.get(
- '/', headers={'Accept': '*/*;q=0, text/html'}, status=200,
- )
- self.assertEqual(res.content_type, 'text/plain')
class DummyContext(object):
pass