From 4a9f4f43684c3a754f43935b97013057340c305d Mon Sep 17 00:00:00 2001 From: Michael Merickel <michael@merickel.org> Date: Wed, 10 Oct 2018 07:04:43 +0200 Subject: [PATCH] deprecate range support --- pyramid/config/util.py | 47 +--- pyramid/config/routes.py | 36 +++ pyramid/config/views.py | 61 +++-- CHANGES.rst | 11 + pyramid/tests/test_config/test_views.py | 35 +++ docs/narr/viewconfig.rst | 14 - pyramid/tests/test_config/test_routes.py | 18 + pyramid/predicates.py | 16 + pyramid/tests/test_config/test_util.py | 27 -- pyramid/tests/test_integration.py | 236 +++----------------------- 10 files changed, 182 insertions(+), 319 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3934c5a..e1f782e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -113,6 +113,17 @@ 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 c441096..2385995 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -1093,23 +1093,17 @@ ~~~~~~~~~~~~~~~~~~~~~~~ :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 0191753..4d1f830 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -22,6 +22,7 @@ from pyramid.config.util import ( action_method, + normalize_accept_offer, predvalseq, ) @@ -228,15 +229,23 @@ 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 @@ -297,9 +306,22 @@ if accept is not None: if not is_nonstr_iter(accept): - accept = [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)] - accept = [accept_option.lower() for accept_option in accept] + 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 7024fa8..8ebc8e4 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -221,16 +221,18 @@ 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 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 @@ """ 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 @@ 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 15ceadc..ca299c4 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -88,6 +88,7 @@ action_method, DEFAULT_PHASH, MAX_ORDER, + normalize_accept_offer, predvalseq, sort_accept_offers, ) @@ -125,7 +126,7 @@ 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 @@ 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 @@ -688,6 +689,12 @@ the associated view callable. 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 @@ -821,7 +828,17 @@ 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 @@ .. 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') - value = normalize_type(value) + 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_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 5bd98fd..97edae8 100644 --- a/pyramid/predicates.py +++ b/pyramid/predicates.py @@ -130,10 +130,16 @@ 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 @@ 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 afae4db..9f4ce9b 100644 --- a/pyramid/tests/test_config/test_routes.py +++ b/pyramid/tests/test_config/test_routes.py @@ -203,6 +203,18 @@ 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 @@ 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 @@ 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 bbda615..540f3d1 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -437,17 +437,10 @@ 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( @@ -473,22 +466,6 @@ ['text/plain', 'text/html'], ) 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): diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index abc10d3..6565a35 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -1477,6 +1477,25 @@ 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 @@ ]) 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 @@ 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 @@ 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 @@ 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 1bf2abb..eedc145 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -759,219 +759,37 @@ 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) + 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_all(self): - res = self.testapp.get('/', headers={'Accept': '*/*'}, 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_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, - ) - 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 DummyContext(object): pass -- Gitblit v1.9.3