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