From d579f2104de139e0b0fc5d6c81aabb2f826e5e54 Mon Sep 17 00:00:00 2001
From: Michael Merickel <michael@merickel.org>
Date: Fri, 19 Oct 2018 03:45:13 +0200
Subject: [PATCH] move predicate-related code into pyramid.config.predicates

---
 src/pyramid/config/__init__.py       |   45 ---
 src/pyramid/config/tweens.py         |    2 
 src/pyramid/config/adapters.py       |    2 
 src/pyramid/config/rendering.py      |    2 
 tests/test_config/test_views.py      |    4 
 src/pyramid/config/testing.py        |    2 
 tests/test_viewderivers.py           |    2 
 src/pyramid/config/assets.py         |    2 
 /dev/null                            |  276 ---------------------
 src/pyramid/config/actions.py        |   64 ++++
 src/pyramid/config/predicates.py     |  257 +++++++++++++++++++
 tests/test_config/test_predicates.py |   62 ----
 src/pyramid/config/views.py          |    4 
 src/pyramid/config/i18n.py           |    2 
 src/pyramid/config/routes.py         |   10 
 src/pyramid/config/factories.py      |    2 
 tests/test_config/test_actions.py    |   37 ++
 src/pyramid/config/security.py       |    2 
 18 files changed, 379 insertions(+), 398 deletions(-)

diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py
index e4dcc6b..5cff207 100644
--- a/src/pyramid/config/__init__.py
+++ b/src/pyramid/config/__init__.py
@@ -9,7 +9,6 @@
 from pyramid.interfaces import (
     IDebugLogger,
     IExceptionResponse,
-    IPredicateList,
     PHASE0_CONFIG,
     PHASE1_CONFIG,
     PHASE2_CONFIG,
@@ -40,13 +39,15 @@
 
 from pyramid.util import WeakOrderedSet, object_description
 
-from pyramid.config.util import PredicateList, action_method, not_
+from pyramid.config.actions import action_method
+from pyramid.config.predicates import not_
 
 from pyramid.config.actions import ActionConfiguratorMixin
 from pyramid.config.adapters import AdaptersConfiguratorMixin
 from pyramid.config.assets import AssetsConfiguratorMixin
 from pyramid.config.factories import FactoriesConfiguratorMixin
 from pyramid.config.i18n import I18NConfiguratorMixin
+from pyramid.config.predicates import PredicateConfiguratorMixin
 from pyramid.config.rendering import RenderingConfiguratorMixin
 from pyramid.config.routes import RoutesConfiguratorMixin
 from pyramid.config.security import SecurityConfiguratorMixin
@@ -71,6 +72,7 @@
 
 class Configurator(
     ActionConfiguratorMixin,
+    PredicateConfiguratorMixin,
     TestingConfiguratorMixin,
     TweensConfiguratorMixin,
     SecurityConfiguratorMixin,
@@ -530,45 +532,6 @@
     introspector = property(
         _get_introspector, _set_introspector, _del_introspector
     )
-
-    def get_predlist(self, name):
-        predlist = self.registry.queryUtility(IPredicateList, name=name)
-        if predlist is None:
-            predlist = PredicateList()
-            self.registry.registerUtility(predlist, IPredicateList, name=name)
-        return predlist
-
-    def _add_predicate(
-        self, type, name, factory, weighs_more_than=None, weighs_less_than=None
-    ):
-        factory = self.maybe_dotted(factory)
-        discriminator = ('%s option' % type, name)
-        intr = self.introspectable(
-            '%s predicates' % type,
-            discriminator,
-            '%s predicate named %s' % (type, name),
-            '%s predicate' % type,
-        )
-        intr['name'] = name
-        intr['factory'] = factory
-        intr['weighs_more_than'] = weighs_more_than
-        intr['weighs_less_than'] = weighs_less_than
-
-        def register():
-            predlist = self.get_predlist(type)
-            predlist.add(
-                name,
-                factory,
-                weighs_more_than=weighs_more_than,
-                weighs_less_than=weighs_less_than,
-            )
-
-        self.action(
-            discriminator,
-            register,
-            introspectables=(intr,),
-            order=PHASE1_CONFIG,
-        )  # must be registered early
 
     def include(self, callable, route_prefix=None):
         """Include a configuration callable, to support imperative
diff --git a/src/pyramid/config/actions.py b/src/pyramid/config/actions.py
index 353ed5e..9c1227d 100644
--- a/src/pyramid/config/actions.py
+++ b/src/pyramid/config/actions.py
@@ -1,18 +1,19 @@
+import functools
 import itertools
 import operator
 import sys
+import traceback
+from zope.interface import implementer
 
 from pyramid.compat import reraise
-
-from pyramid.config.util import ActionInfo
-
 from pyramid.exceptions import (
     ConfigurationConflictError,
     ConfigurationError,
     ConfigurationExecutionError,
 )
-
+from pyramid.interfaces import IActionInfo
 from pyramid.registry import undefer
+from pyramid.util import is_nonstr_iter
 
 
 class ActionConfiguratorMixin(object):
@@ -152,6 +153,7 @@
         finally:
             self.end()
         self.action_state = ActionState()  # old actions have been processed
+
 
 # this class is licensed under the ZPL (stolen from Zope)
 class ActionState(object):
@@ -523,3 +525,57 @@
         order=order,
         introspectables=introspectables,
     )
+
+
+@implementer(IActionInfo)
+class ActionInfo(object):
+    def __init__(self, file, line, function, src):
+        self.file = file
+        self.line = line
+        self.function = function
+        self.src = src
+
+    def __str__(self):
+        srclines = self.src.split('\n')
+        src = '\n'.join('    %s' % x for x in srclines)
+        return 'Line %s of file %s:\n%s' % (self.line, self.file, src)
+
+
+def action_method(wrapped):
+    """ Wrapper to provide the right conflict info report data when a method
+    that calls Configurator.action calls another that does the same.  Not a
+    documented API but used by some external systems."""
+
+    def wrapper(self, *arg, **kw):
+        if self._ainfo is None:
+            self._ainfo = []
+        info = kw.pop('_info', None)
+        # backframes for outer decorators to actionmethods
+        backframes = kw.pop('_backframes', 0) + 2
+        if is_nonstr_iter(info) and len(info) == 4:
+            # _info permitted as extract_stack tuple
+            info = ActionInfo(*info)
+        if info is None:
+            try:
+                f = traceback.extract_stack(limit=4)
+
+                # Work around a Python 3.5 issue whereby it would insert an
+                # extra stack frame. This should no longer be necessary in
+                # Python 3.5.1
+                last_frame = ActionInfo(*f[-1])
+                if last_frame.function == 'extract_stack':  # pragma: no cover
+                    f.pop()
+                info = ActionInfo(*f[-backframes])
+            except Exception:  # pragma: no cover
+                info = ActionInfo(None, 0, '', '')
+        self._ainfo.append(info)
+        try:
+            result = wrapped(self, *arg, **kw)
+        finally:
+            self._ainfo.pop()
+        return result
+
+    if hasattr(wrapped, '__name__'):
+        functools.update_wrapper(wrapper, wrapped)
+    wrapper.__docobj__ = wrapped
+    return wrapper
diff --git a/src/pyramid/config/adapters.py b/src/pyramid/config/adapters.py
index e5668c4..54c239a 100644
--- a/src/pyramid/config/adapters.py
+++ b/src/pyramid/config/adapters.py
@@ -8,7 +8,7 @@
 
 from pyramid.util import takes_one_arg
 
-from pyramid.config.util import action_method
+from pyramid.config.actions import action_method
 
 
 class AdaptersConfiguratorMixin(object):
diff --git a/src/pyramid/config/assets.py b/src/pyramid/config/assets.py
index fd8b2ee..e505fd2 100644
--- a/src/pyramid/config/assets.py
+++ b/src/pyramid/config/assets.py
@@ -9,7 +9,7 @@
 from pyramid.exceptions import ConfigurationError
 from pyramid.threadlocal import get_current_registry
 
-from pyramid.config.util import action_method
+from pyramid.config.actions import action_method
 
 
 class OverrideProvider(pkg_resources.DefaultProvider):
diff --git a/src/pyramid/config/factories.py b/src/pyramid/config/factories.py
index 2ec1558..1621198 100644
--- a/src/pyramid/config/factories.py
+++ b/src/pyramid/config/factories.py
@@ -15,7 +15,7 @@
 
 from pyramid.util import get_callable_name, InstancePropertyHelper
 
-from pyramid.config.util import action_method
+from pyramid.config.actions import action_method
 
 
 class FactoriesConfiguratorMixin(object):
diff --git a/src/pyramid/config/i18n.py b/src/pyramid/config/i18n.py
index 6e73344..92c324f 100644
--- a/src/pyramid/config/i18n.py
+++ b/src/pyramid/config/i18n.py
@@ -3,7 +3,7 @@
 from pyramid.exceptions import ConfigurationError
 from pyramid.path import AssetResolver
 
-from pyramid.config.util import action_method
+from pyramid.config.actions import action_method
 
 
 class I18NConfiguratorMixin(object):
diff --git a/src/pyramid/config/predicates.py b/src/pyramid/config/predicates.py
index cdbf68c..8f16f74 100644
--- a/src/pyramid/config/predicates.py
+++ b/src/pyramid/config/predicates.py
@@ -1,3 +1,256 @@
-import zope.deprecation
+from hashlib import md5
+from webob.acceptparse import Accept
 
-zope.deprecation.moved('pyramid.predicates', 'Pyramid 1.10')
+from pyramid.compat import bytes_, is_nonstr_iter
+from pyramid.exceptions import ConfigurationError
+from pyramid.interfaces import IPredicateList, PHASE1_CONFIG
+from pyramid.predicates import Notted
+from pyramid.registry import predvalseq
+from pyramid.util import TopologicalSorter
+
+
+MAX_ORDER = 1 << 30
+DEFAULT_PHASH = md5().hexdigest()
+
+
+class PredicateConfiguratorMixin(object):
+    def get_predlist(self, name):
+        predlist = self.registry.queryUtility(IPredicateList, name=name)
+        if predlist is None:
+            predlist = PredicateList()
+            self.registry.registerUtility(predlist, IPredicateList, name=name)
+        return predlist
+
+    def _add_predicate(
+        self, type, name, factory, weighs_more_than=None, weighs_less_than=None
+    ):
+        factory = self.maybe_dotted(factory)
+        discriminator = ('%s option' % type, name)
+        intr = self.introspectable(
+            '%s predicates' % type,
+            discriminator,
+            '%s predicate named %s' % (type, name),
+            '%s predicate' % type,
+        )
+        intr['name'] = name
+        intr['factory'] = factory
+        intr['weighs_more_than'] = weighs_more_than
+        intr['weighs_less_than'] = weighs_less_than
+
+        def register():
+            predlist = self.get_predlist(type)
+            predlist.add(
+                name,
+                factory,
+                weighs_more_than=weighs_more_than,
+                weighs_less_than=weighs_less_than,
+            )
+
+        self.action(
+            discriminator,
+            register,
+            introspectables=(intr,),
+            order=PHASE1_CONFIG,
+        )  # must be registered early
+
+
+class not_(object):
+    """
+
+    You can invert the meaning of any predicate value by wrapping it in a call
+    to :class:`pyramid.config.not_`.
+
+    .. code-block:: python
+       :linenos:
+
+       from pyramid.config import not_
+
+       config.add_view(
+           'mypackage.views.my_view',
+           route_name='ok',
+           request_method=not_('POST')
+           )
+
+    The above example will ensure that the view is called if the request method
+    is *not* ``POST``, at least if no other view is more specific.
+
+    This technique of wrapping a predicate value in ``not_`` can be used
+    anywhere predicate values are accepted:
+
+    - :meth:`pyramid.config.Configurator.add_view`
+
+    - :meth:`pyramid.config.Configurator.add_route`
+
+    - :meth:`pyramid.config.Configurator.add_subscriber`
+
+    - :meth:`pyramid.view.view_config`
+
+    - :meth:`pyramid.events.subscriber`
+
+    .. versionadded:: 1.5
+    """
+
+    def __init__(self, value):
+        self.value = value
+
+
+# under = after
+# over = before
+
+
+class PredicateList(object):
+    def __init__(self):
+        self.sorter = TopologicalSorter()
+        self.last_added = None
+
+    def add(self, name, factory, weighs_more_than=None, weighs_less_than=None):
+        # Predicates should be added to a predicate list in (presumed)
+        # computation expense order.
+        # if weighs_more_than is None and weighs_less_than is None:
+        #     weighs_more_than = self.last_added or FIRST
+        #     weighs_less_than = LAST
+        self.last_added = name
+        self.sorter.add(
+            name, factory, after=weighs_more_than, before=weighs_less_than
+        )
+
+    def names(self):
+        # Return the list of valid predicate names.
+        return self.sorter.names
+
+    def make(self, config, **kw):
+        # Given a configurator and a list of keywords, a predicate list is
+        # computed.  Elsewhere in the code, we evaluate predicates using a
+        # generator expression.  All predicates associated with a view or
+        # route must evaluate true for the view or route to "match" during a
+        # request.  The fastest predicate should be evaluated first, then the
+        # next fastest, and so on, as if one returns false, the remainder of
+        # the predicates won't need to be evaluated.
+        #
+        # While we compute predicates, we also compute a predicate hash (aka
+        # phash) that can be used by a caller to identify identical predicate
+        # lists.
+        ordered = self.sorter.sorted()
+        phash = md5()
+        weights = []
+        preds = []
+        for n, (name, predicate_factory) in enumerate(ordered):
+            vals = kw.pop(name, None)
+            if vals is None:  # XXX should this be a sentinel other than None?
+                continue
+            if not isinstance(vals, predvalseq):
+                vals = (vals,)
+            for val in vals:
+                realval = val
+                notted = False
+                if isinstance(val, not_):
+                    realval = val.value
+                    notted = True
+                pred = predicate_factory(realval, config)
+                if notted:
+                    pred = Notted(pred)
+                hashes = pred.phash()
+                if not is_nonstr_iter(hashes):
+                    hashes = [hashes]
+                for h in hashes:
+                    phash.update(bytes_(h))
+                weights.append(1 << n + 1)
+                preds.append(pred)
+        if kw:
+            from difflib import get_close_matches
+
+            closest = []
+            names = [name for name, _ in ordered]
+            for name in kw:
+                closest.extend(get_close_matches(name, names, 3))
+
+            raise ConfigurationError(
+                'Unknown predicate values: %r (did you mean %s)'
+                % (kw, ','.join(closest))
+            )
+        # A "order" is computed for the predicate list.  An order is
+        # a scoring.
+        #
+        # Each predicate is associated with a weight value.  The weight of a
+        # predicate symbolizes the relative potential "importance" of the
+        # predicate to all other predicates.  A larger weight indicates
+        # greater importance.
+        #
+        # All weights for a given predicate list are bitwise ORed together
+        # to create a "score"; this score is then subtracted from
+        # MAX_ORDER and divided by an integer representing the number of
+        # predicates+1 to determine the order.
+        #
+        # For views, the order represents the ordering in which a "multiview"
+        # ( a collection of views that share the same context/request/name
+        # triad but differ in other ways via predicates) will attempt to call
+        # its set of views.  Views with lower orders will be tried first.
+        # The intent is to a) ensure that views with more predicates are
+        # always evaluated before views with fewer predicates and b) to
+        # ensure a stable call ordering of views that share the same number
+        # of predicates.  Views which do not have any predicates get an order
+        # of MAX_ORDER, meaning that they will be tried very last.
+        score = 0
+        for bit in weights:
+            score = score | bit
+        order = (MAX_ORDER - score) / (len(preds) + 1)
+        return order, preds, phash.hexdigest()
+
+
+def normalize_accept_offer(offer, allow_range=False):
+    if allow_range and '*' in offer:
+        return offer.lower()
+    return str(Accept.parse_offer(offer))
+
+
+def sort_accept_offers(offers, order=None):
+    """
+    Sort a list of offers by preference.
+
+    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
+                  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):
+        """
+        (type_weight, params_weight)
+
+        type_weight:
+            - index of specific ``type/subtype`` in order list
+            - ``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)
+
+        type_w = find_order_index(
+            parsed.type + '/' + parsed.subtype, max_weight
+        )
+
+        if parsed.params:
+            param_w = find_order_index(value, max_weight)
+
+        else:
+            param_w = max_weight + 1
+
+        return (type_w, param_w)
+
+    return sorted(offers, key=offer_sort_key)
diff --git a/src/pyramid/config/rendering.py b/src/pyramid/config/rendering.py
index 9481996..7e5b767 100644
--- a/src/pyramid/config/rendering.py
+++ b/src/pyramid/config/rendering.py
@@ -1,7 +1,7 @@
 from pyramid.interfaces import IRendererFactory, PHASE1_CONFIG
 
 from pyramid import renderers
-from pyramid.config.util import action_method
+from pyramid.config.actions import action_method
 
 DEFAULT_RENDERERS = (
     ('json', renderers.json_renderer_factory),
diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py
index 7a76e9e..a146623 100644
--- a/src/pyramid/config/routes.py
+++ b/src/pyramid/config/routes.py
@@ -10,18 +10,14 @@
 )
 
 from pyramid.exceptions import ConfigurationError
+import pyramid.predicates
 from pyramid.request import route_request_iface
 from pyramid.urldispatch import RoutesMapper
 
 from pyramid.util import as_sorted_tuple, is_nonstr_iter
 
-import pyramid.predicates
-
-from pyramid.config.util import (
-    action_method,
-    normalize_accept_offer,
-    predvalseq,
-)
+from pyramid.config.actions import action_method
+from pyramid.config.predicates import normalize_accept_offer, predvalseq
 
 
 class RoutesConfiguratorMixin(object):
diff --git a/src/pyramid/config/security.py b/src/pyramid/config/security.py
index 3b55c41..08e7cb8 100644
--- a/src/pyramid/config/security.py
+++ b/src/pyramid/config/security.py
@@ -14,7 +14,7 @@
 from pyramid.exceptions import ConfigurationError
 from pyramid.util import as_sorted_tuple
 
-from pyramid.config.util import action_method
+from pyramid.config.actions import action_method
 
 
 class SecurityConfiguratorMixin(object):
diff --git a/src/pyramid/config/testing.py b/src/pyramid/config/testing.py
index 1655df5..bba5054 100644
--- a/src/pyramid/config/testing.py
+++ b/src/pyramid/config/testing.py
@@ -11,7 +11,7 @@
 
 from pyramid.traversal import decode_path_info, split_path_info
 
-from pyramid.config.util import action_method
+from pyramid.config.actions import action_method
 
 
 class TestingConfiguratorMixin(object):
diff --git a/src/pyramid/config/tweens.py b/src/pyramid/config/tweens.py
index b74a57a..7fc786a 100644
--- a/src/pyramid/config/tweens.py
+++ b/src/pyramid/config/tweens.py
@@ -10,7 +10,7 @@
 
 from pyramid.util import is_string_or_iterable, TopologicalSorter
 
-from pyramid.config.util import action_method
+from pyramid.config.actions import action_method
 
 
 class TweensConfiguratorMixin(object):
diff --git a/src/pyramid/config/util.py b/src/pyramid/config/util.py
deleted file mode 100644
index 8723b77..0000000
--- a/src/pyramid/config/util.py
+++ /dev/null
@@ -1,276 +0,0 @@
-import functools
-from hashlib import md5
-import traceback
-from webob.acceptparse import Accept
-from zope.interface import implementer
-
-from pyramid.compat import bytes_, is_nonstr_iter
-from pyramid.interfaces import IActionInfo
-
-from pyramid.exceptions import ConfigurationError
-from pyramid.predicates import Notted
-from pyramid.registry import predvalseq
-from pyramid.util import TopologicalSorter, takes_one_arg
-
-TopologicalSorter = TopologicalSorter  # support bw-compat imports
-takes_one_arg = takes_one_arg  # support bw-compat imports
-
-
-@implementer(IActionInfo)
-class ActionInfo(object):
-    def __init__(self, file, line, function, src):
-        self.file = file
-        self.line = line
-        self.function = function
-        self.src = src
-
-    def __str__(self):
-        srclines = self.src.split('\n')
-        src = '\n'.join('    %s' % x for x in srclines)
-        return 'Line %s of file %s:\n%s' % (self.line, self.file, src)
-
-
-def action_method(wrapped):
-    """ Wrapper to provide the right conflict info report data when a method
-    that calls Configurator.action calls another that does the same.  Not a
-    documented API but used by some external systems."""
-
-    def wrapper(self, *arg, **kw):
-        if self._ainfo is None:
-            self._ainfo = []
-        info = kw.pop('_info', None)
-        # backframes for outer decorators to actionmethods
-        backframes = kw.pop('_backframes', 0) + 2
-        if is_nonstr_iter(info) and len(info) == 4:
-            # _info permitted as extract_stack tuple
-            info = ActionInfo(*info)
-        if info is None:
-            try:
-                f = traceback.extract_stack(limit=4)
-
-                # Work around a Python 3.5 issue whereby it would insert an
-                # extra stack frame. This should no longer be necessary in
-                # Python 3.5.1
-                last_frame = ActionInfo(*f[-1])
-                if last_frame.function == 'extract_stack':  # pragma: no cover
-                    f.pop()
-                info = ActionInfo(*f[-backframes])
-            except Exception:  # pragma: no cover
-                info = ActionInfo(None, 0, '', '')
-        self._ainfo.append(info)
-        try:
-            result = wrapped(self, *arg, **kw)
-        finally:
-            self._ainfo.pop()
-        return result
-
-    if hasattr(wrapped, '__name__'):
-        functools.update_wrapper(wrapper, wrapped)
-    wrapper.__docobj__ = wrapped
-    return wrapper
-
-
-MAX_ORDER = 1 << 30
-DEFAULT_PHASH = md5().hexdigest()
-
-
-class not_(object):
-    """
-
-    You can invert the meaning of any predicate value by wrapping it in a call
-    to :class:`pyramid.config.not_`.
-
-    .. code-block:: python
-       :linenos:
-
-       from pyramid.config import not_
-
-       config.add_view(
-           'mypackage.views.my_view',
-           route_name='ok',
-           request_method=not_('POST')
-           )
-
-    The above example will ensure that the view is called if the request method
-    is *not* ``POST``, at least if no other view is more specific.
-
-    This technique of wrapping a predicate value in ``not_`` can be used
-    anywhere predicate values are accepted:
-
-    - :meth:`pyramid.config.Configurator.add_view`
-
-    - :meth:`pyramid.config.Configurator.add_route`
-
-    - :meth:`pyramid.config.Configurator.add_subscriber`
-
-    - :meth:`pyramid.view.view_config`
-
-    - :meth:`pyramid.events.subscriber`
-
-    .. versionadded:: 1.5
-    """
-
-    def __init__(self, value):
-        self.value = value
-
-
-# under = after
-# over = before
-
-
-class PredicateList(object):
-    def __init__(self):
-        self.sorter = TopologicalSorter()
-        self.last_added = None
-
-    def add(self, name, factory, weighs_more_than=None, weighs_less_than=None):
-        # Predicates should be added to a predicate list in (presumed)
-        # computation expense order.
-        # if weighs_more_than is None and weighs_less_than is None:
-        #     weighs_more_than = self.last_added or FIRST
-        #     weighs_less_than = LAST
-        self.last_added = name
-        self.sorter.add(
-            name, factory, after=weighs_more_than, before=weighs_less_than
-        )
-
-    def names(self):
-        # Return the list of valid predicate names.
-        return self.sorter.names
-
-    def make(self, config, **kw):
-        # Given a configurator and a list of keywords, a predicate list is
-        # computed.  Elsewhere in the code, we evaluate predicates using a
-        # generator expression.  All predicates associated with a view or
-        # route must evaluate true for the view or route to "match" during a
-        # request.  The fastest predicate should be evaluated first, then the
-        # next fastest, and so on, as if one returns false, the remainder of
-        # the predicates won't need to be evaluated.
-        #
-        # While we compute predicates, we also compute a predicate hash (aka
-        # phash) that can be used by a caller to identify identical predicate
-        # lists.
-        ordered = self.sorter.sorted()
-        phash = md5()
-        weights = []
-        preds = []
-        for n, (name, predicate_factory) in enumerate(ordered):
-            vals = kw.pop(name, None)
-            if vals is None:  # XXX should this be a sentinel other than None?
-                continue
-            if not isinstance(vals, predvalseq):
-                vals = (vals,)
-            for val in vals:
-                realval = val
-                notted = False
-                if isinstance(val, not_):
-                    realval = val.value
-                    notted = True
-                pred = predicate_factory(realval, config)
-                if notted:
-                    pred = Notted(pred)
-                hashes = pred.phash()
-                if not is_nonstr_iter(hashes):
-                    hashes = [hashes]
-                for h in hashes:
-                    phash.update(bytes_(h))
-                weights.append(1 << n + 1)
-                preds.append(pred)
-        if kw:
-            from difflib import get_close_matches
-
-            closest = []
-            names = [name for name, _ in ordered]
-            for name in kw:
-                closest.extend(get_close_matches(name, names, 3))
-
-            raise ConfigurationError(
-                'Unknown predicate values: %r (did you mean %s)'
-                % (kw, ','.join(closest))
-            )
-        # A "order" is computed for the predicate list.  An order is
-        # a scoring.
-        #
-        # Each predicate is associated with a weight value.  The weight of a
-        # predicate symbolizes the relative potential "importance" of the
-        # predicate to all other predicates.  A larger weight indicates
-        # greater importance.
-        #
-        # All weights for a given predicate list are bitwise ORed together
-        # to create a "score"; this score is then subtracted from
-        # MAX_ORDER and divided by an integer representing the number of
-        # predicates+1 to determine the order.
-        #
-        # For views, the order represents the ordering in which a "multiview"
-        # ( a collection of views that share the same context/request/name
-        # triad but differ in other ways via predicates) will attempt to call
-        # its set of views.  Views with lower orders will be tried first.
-        # The intent is to a) ensure that views with more predicates are
-        # always evaluated before views with fewer predicates and b) to
-        # ensure a stable call ordering of views that share the same number
-        # of predicates.  Views which do not have any predicates get an order
-        # of MAX_ORDER, meaning that they will be tried very last.
-        score = 0
-        for bit in weights:
-            score = score | bit
-        order = (MAX_ORDER - score) / (len(preds) + 1)
-        return order, preds, phash.hexdigest()
-
-
-def normalize_accept_offer(offer, allow_range=False):
-    if allow_range and '*' in offer:
-        return offer.lower()
-    return str(Accept.parse_offer(offer))
-
-
-def sort_accept_offers(offers, order=None):
-    """
-    Sort a list of offers by preference.
-
-    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
-                  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):
-        """
-        (type_weight, params_weight)
-
-        type_weight:
-            - index of specific ``type/subtype`` in order list
-            - ``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)
-
-        type_w = find_order_index(
-            parsed.type + '/' + parsed.subtype, max_weight
-        )
-
-        if parsed.params:
-            param_w = find_order_index(value, max_weight)
-
-        else:
-            param_w = max_weight + 1
-
-        return (type_w, param_w)
-
-    return sorted(offers, key=offer_sort_key)
diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py
index cc5b48e..bd1b693 100644
--- a/src/pyramid/config/views.py
+++ b/src/pyramid/config/views.py
@@ -74,8 +74,8 @@
     wraps_view,
 )
 
-from pyramid.config.util import (
-    action_method,
+from pyramid.config.actions import action_method
+from pyramid.config.predicates import (
     DEFAULT_PHASH,
     MAX_ORDER,
     normalize_accept_offer,
diff --git a/tests/test_config/test_actions.py b/tests/test_config/test_actions.py
index ba8b8c1..098f735 100644
--- a/tests/test_config/test_actions.py
+++ b/tests/test_config/test_actions.py
@@ -50,7 +50,7 @@
         self.assertEqual(config.action('discrim', kw={'a': 1}), None)
 
     def test_action_autocommit_with_introspectables(self):
-        from pyramid.config.util import ActionInfo
+        from pyramid.config.actions import ActionInfo
 
         config = self._makeOne(autocommit=True)
         intr = DummyIntrospectable()
@@ -1029,6 +1029,41 @@
         self.assertRaises(ConfigurationConflictError, list, result)
 
 
+class TestActionInfo(unittest.TestCase):
+    def _getTargetClass(self):
+        from pyramid.config.actions import ActionInfo
+
+        return ActionInfo
+
+    def _makeOne(self, filename, lineno, function, linerepr):
+        return self._getTargetClass()(filename, lineno, function, linerepr)
+
+    def test_class_conforms(self):
+        from zope.interface.verify import verifyClass
+        from pyramid.interfaces import IActionInfo
+
+        verifyClass(IActionInfo, self._getTargetClass())
+
+    def test_instance_conforms(self):
+        from zope.interface.verify import verifyObject
+        from pyramid.interfaces import IActionInfo
+
+        verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f'))
+
+    def test_ctor(self):
+        inst = self._makeOne('filename', 10, 'function', 'src')
+        self.assertEqual(inst.file, 'filename')
+        self.assertEqual(inst.line, 10)
+        self.assertEqual(inst.function, 'function')
+        self.assertEqual(inst.src, 'src')
+
+    def test___str__(self):
+        inst = self._makeOne('filename', 0, 'function', '   linerepr  ')
+        self.assertEqual(
+            str(inst), "Line 0 of file filename:\n       linerepr  "
+        )
+
+
 def _conflictFunctions(e):
     conflicts = e._conflicts.values()
     for conflict in conflicts:
diff --git a/tests/test_config/test_util.py b/tests/test_config/test_predicates.py
similarity index 89%
rename from tests/test_config/test_util.py
rename to tests/test_config/test_predicates.py
index 50d143b..079652b 100644
--- a/tests/test_config/test_util.py
+++ b/tests/test_config/test_predicates.py
@@ -3,44 +3,9 @@
 from pyramid.compat import text_
 
 
-class TestActionInfo(unittest.TestCase):
-    def _getTargetClass(self):
-        from pyramid.config.util import ActionInfo
-
-        return ActionInfo
-
-    def _makeOne(self, filename, lineno, function, linerepr):
-        return self._getTargetClass()(filename, lineno, function, linerepr)
-
-    def test_class_conforms(self):
-        from zope.interface.verify import verifyClass
-        from pyramid.interfaces import IActionInfo
-
-        verifyClass(IActionInfo, self._getTargetClass())
-
-    def test_instance_conforms(self):
-        from zope.interface.verify import verifyObject
-        from pyramid.interfaces import IActionInfo
-
-        verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f'))
-
-    def test_ctor(self):
-        inst = self._makeOne('filename', 10, 'function', 'src')
-        self.assertEqual(inst.file, 'filename')
-        self.assertEqual(inst.line, 10)
-        self.assertEqual(inst.function, 'function')
-        self.assertEqual(inst.src, 'src')
-
-    def test___str__(self):
-        inst = self._makeOne('filename', 0, 'function', '   linerepr  ')
-        self.assertEqual(
-            str(inst), "Line 0 of file filename:\n       linerepr  "
-        )
-
-
 class TestPredicateList(unittest.TestCase):
     def _makeOne(self):
-        from pyramid.config.util import PredicateList
+        from pyramid.config.predicates import PredicateList
         from pyramid import predicates
 
         inst = PredicateList()
@@ -71,7 +36,7 @@
         self.assertTrue(order1 < order2)
 
     def test_ordering_number_of_predicates(self):
-        from pyramid.config.util import predvalseq
+        from pyramid.config.predicates import predvalseq
 
         order1, _, _ = self._callFUT(
             xhr='xhr',
@@ -169,7 +134,7 @@
         self.assertTrue(order12 > order10)
 
     def test_ordering_importance_of_predicates(self):
-        from pyramid.config.util import predvalseq
+        from pyramid.config.predicates import predvalseq
 
         order1, _, _ = self._callFUT(xhr='xhr')
         order2, _, _ = self._callFUT(request_method='request_method')
@@ -194,7 +159,7 @@
         self.assertTrue(order9 > order10)
 
     def test_ordering_importance_and_number(self):
-        from pyramid.config.util import predvalseq
+        from pyramid.config.predicates import predvalseq
 
         order1, _, _ = self._callFUT(
             xhr='xhr', request_method='request_method'
@@ -233,7 +198,7 @@
         self.assertTrue(order1 > order2)
 
     def test_different_custom_predicates_with_same_hash(self):
-        from pyramid.config.util import predvalseq
+        from pyramid.config.predicates import predvalseq
 
         class PredicateWithHash(object):
             def __hash__(self):
@@ -286,7 +251,7 @@
         )
 
     def test_custom_predicates_can_affect_traversal(self):
-        from pyramid.config.util import predvalseq
+        from pyramid.config.predicates import predvalseq
 
         def custom(info, request):
             m = info['match']
@@ -312,7 +277,7 @@
         )
 
     def test_predicate_text_is_correct(self):
-        from pyramid.config.util import predvalseq
+        from pyramid.config.predicates import predvalseq
 
         _, predicates, _ = self._callFUT(
             xhr='xhr',
@@ -420,20 +385,9 @@
         self.assertEqual(predicates[2](None, request), True)
 
 
-class TestDeprecatedPredicates(unittest.TestCase):
-    def test_it(self):
-        import warnings
-
-        with warnings.catch_warnings(record=True) as w:
-            warnings.filterwarnings('always')
-            from pyramid.config.predicates import XHRPredicate  # noqa: F401
-
-            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
+        from pyramid.config.predicates import sort_accept_offers
 
         return sort_accept_offers(offers, order)
 
diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py
index 977944f..5e722c9 100644
--- a/tests/test_config/test_views.py
+++ b/tests/test_config/test_views.py
@@ -664,7 +664,7 @@
         self.assertEqual(wrapper(None, request), 'OK')
 
     def test_add_view_default_phash_overrides_default_phash(self):
-        from pyramid.config.util import DEFAULT_PHASH
+        from pyramid.config.predicates import DEFAULT_PHASH
         from pyramid.renderers import null_renderer
         from zope.interface import Interface
         from pyramid.interfaces import IRequest
@@ -690,7 +690,7 @@
         self.assertEqual(wrapper(None, request), 'OK')
 
     def test_add_view_exc_default_phash_overrides_default_phash(self):
-        from pyramid.config.util import DEFAULT_PHASH
+        from pyramid.config.predicates import DEFAULT_PHASH
         from pyramid.renderers import null_renderer
         from zope.interface import implementedBy
         from pyramid.interfaces import IRequest
diff --git a/tests/test_viewderivers.py b/tests/test_viewderivers.py
index a1455ed..f01cb49 100644
--- a/tests/test_viewderivers.py
+++ b/tests/test_viewderivers.py
@@ -1265,7 +1265,7 @@
         self.assertEqual(result(None, None), response)
 
     def test_attr_wrapped_view_branching_default_phash(self):
-        from pyramid.config.util import DEFAULT_PHASH
+        from pyramid.config.predicates import DEFAULT_PHASH
 
         def view(context, request):  # pragma: no cover
             pass

--
Gitblit v1.9.3