From f2294fb6969a7bc04642f2f987dec5ee131ad98c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 27 Sep 2018 22:18:56 -0500 Subject: move sort_accept_offers to pyramid.config.util keeping this code isolated for now as it's kind of crazy --- pyramid/config/util.py | 76 +++++++++++++++++++++++++++++++++ pyramid/config/views.py | 2 +- pyramid/tests/test_config/test_util.py | 59 ++++++++++++++++++++++++++ pyramid/tests/test_util.py | 60 -------------------------- pyramid/util.py | 77 ---------------------------------- 5 files changed, 136 insertions(+), 138 deletions(-) diff --git a/pyramid/config/util.py b/pyramid/config/util.py index aedebd9e2..7024fa862 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -1,6 +1,7 @@ import functools from hashlib import md5 import traceback +from webob.acceptparse import Accept from zope.interface import implementer from pyramid.compat import ( @@ -218,3 +219,78 @@ class PredicateList(object): score = score | bit order = (MAX_ORDER - score) / (len(preds) + 1) return order, preds, phash.hexdigest() + + +def sort_accept_offers(offers, order=None): + """ + Sort a list of offers by specificity and preference. + + Supported offers are of the following forms, ordered by specificity + (higher to lower): + + - ``type/subtype;params`` and ``type/subtype`` + - ``type/*`` + - ``*/*`` + + :param offers: A list of offers to be sorted. + :param order: A weighted list of offers where items closer to the start of + the list will be a preferred over items closer to the end. + :return: A list of offers sorted first by specificity (higher to lower) + then by ``order``. + + """ + if order is None: + order = [] + + max_weight = len(offers) + + def find_order_index(value, default=None): + return next((i for i, x in enumerate(order) if x == value), default) + + def offer_sort_key(value): + """ + (category, type_weight, params_weight) + + category: + 1 - foo/bar and foo/bar;params + 2 - foo/* + 3 - */* + + 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`` + - ``max_weight * 2`` if no match is found + + params_weight: + - index of specific ``type/subtype;params`` in order list + - ``max_weight`` if not found + - ``max_weight + 1`` if no params at all + + """ + 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) + + if parsed.params: + param_w = find_order_index(value, max_weight) + + else: + param_w = max_weight + 1 + + return (1, type_w, param_w) + + return sorted(offers, key=offer_sort_key) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index e40a851ff..26e86d7d3 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -68,7 +68,6 @@ from pyramid.view import AppendSlashNotFoundViewFactory from pyramid.util import ( as_sorted_tuple, - sort_accept_offers, TopologicalSorter, ) @@ -90,6 +89,7 @@ from pyramid.config.util import ( DEFAULT_PHASH, MAX_ORDER, predvalseq, + sort_accept_offers, ) urljoin = urlparse.urljoin diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index 99c67e8c6..bbda615c9 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -431,6 +431,65 @@ class TestDeprecatedPredicates(unittest.TestCase): from pyramid.config.predicates import XHRPredicate self.assertEqual(len(w), 1) +class Test_sort_accept_offers(unittest.TestCase): + def _callFUT(self, offers, order=None): + from pyramid.config.util import sort_accept_offers + return sort_accept_offers(offers, order) + + def test_default_specificities(self): + result = self._callFUT(['*/*', 'text/*', 'text/html', 'text/html;charset=utf8']) + self.assertEqual(result, [ + 'text/html;charset=utf8', 'text/html', 'text/*', '*/*', + ]) + + 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'], + ['application/json', 'text/html'], + ) + self.assertEqual(result, [ + 'application/json', 'text/html;charset=utf8', 'text/html', 'text/plain', + ]) + + def test_params_order(self): + result = self._callFUT( + ['text/html;charset=utf8', 'text/html;charset=latin1', 'text/html;foo=bar'], + ['text/html;charset=latin1', 'text/html;charset=utf8'], + ) + self.assertEqual(result, [ + 'text/html;charset=latin1', 'text/html;charset=utf8', 'text/html;foo=bar', + ]) + + def test_params_inherit_type_prefs(self): + result = self._callFUT( + ['text/html;charset=utf8', 'text/plain;charset=latin1'], + ['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): self.__text__ = 'custom predicate' diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 907ca7351..a76cd2017 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -1109,63 +1109,3 @@ class TestSimpleSerializer(unittest.TestCase): def test_dumps(self): inst = self._makeOne() self.assertEqual(inst.dumps('abc'), bytes_('abc')) - - -class Test_sort_accept_offers(unittest.TestCase): - def _callFUT(self, offers, order=None): - from pyramid.util import sort_accept_offers - return sort_accept_offers(offers, order) - - def test_default_specificities(self): - result = self._callFUT(['*/*', 'text/*', 'text/html', 'text/html;charset=utf8']) - self.assertEqual(result, [ - 'text/html;charset=utf8', 'text/html', 'text/*', '*/*', - ]) - - 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'], - ['application/json', 'text/html'], - ) - self.assertEqual(result, [ - 'application/json', 'text/html;charset=utf8', 'text/html', 'text/plain', - ]) - - def test_params_order(self): - result = self._callFUT( - ['text/html;charset=utf8', 'text/html;charset=latin1', 'text/html;foo=bar'], - ['text/html;charset=latin1', 'text/html;charset=utf8'], - ) - self.assertEqual(result, [ - 'text/html;charset=latin1', 'text/html;charset=utf8', 'text/html;foo=bar', - ]) - - def test_params_inherit_type_prefs(self): - result = self._callFUT( - ['text/html;charset=utf8', 'text/plain;charset=latin1'], - ['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', - ]) diff --git a/pyramid/util.py b/pyramid/util.py index 708931eea..6655455bf 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -1,5 +1,3 @@ -from collections import defaultdict -from collections import namedtuple from contextlib import contextmanager import functools try: @@ -9,7 +7,6 @@ except ImportError: # pragma: no cover compare_digest = None import inspect import weakref -from webob.acceptparse import Accept from pyramid.exceptions import ( ConfigurationError, @@ -652,77 +649,3 @@ class SimpleSerializer(object): def dumps(self, appstruct): return bytes_(appstruct) - -def sort_accept_offers(offers, order=None): - """ - Sort a list of offers by specificity and preference. - - Supported offers are of the following forms, ordered by specificity - (higher to lower): - - - ``type/subtype;params`` and ``type/subtype`` - - ``type/*`` - - ``*/*`` - - :param offers: A list of offers to be sorted. - :param order: A weighted list of offers where items closer to the start of - the list will be a preferred over items closer to the end. - :return: A list of offers sorted first by specificity (higher to lower) - then by ``order``. - - """ - if order is None: - order = [] - - max_weight = len(offers) - - def find_order_index(value, default=None): - return next((i for i, x in enumerate(order) if x == value), default) - - def offer_sort_key(value): - """ - (category, type_weight, params_weight) - - category: - 1 - foo/bar and foo/bar;params - 2 - foo/* - 3 - */* - - 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`` - - ``max_weight * 2`` if no match is found - - params_weight: - - index of specific ``type/subtype;params`` in order list - - ``max_weight`` if not found - - ``max_weight + 1`` if no params at all - - """ - 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) - - if parsed.params: - param_w = find_order_index(value, max_weight) - - else: - param_w = max_weight + 1 - - return (1, type_w, param_w) - - return sorted(offers, key=offer_sort_key) -- cgit v1.2.3