Michael Merickel
2011-08-12 f882fefeedfda182272554bad33687c82929bddd
Merge branch 'fix.tween-graph'
5 files modified
220 ■■■■ changed files
docs/narr/hooks.rst 16 ●●●● patch | view | raw | blame | history
pyramid/config.py 25 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config.py 16 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_tweens.py 112 ●●●● patch | view | raw | blame | history
pyramid/tweens.py 51 ●●●● patch | view | raw | blame | history
docs/narr/hooks.rst
@@ -949,6 +949,10 @@
- One of the constants :attr:`pyramid.tweens.MAIN`,
  :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`.
- An iterable of any combination of the above. This allows the user to specify
  fallbacks if the desired tween is not included, as well as compatibility
  with multiple other tweens.
Effectively, ``under`` means "closer to the main Pyramid application than",
``over`` means "closer to the request ingress than".
@@ -1000,10 +1004,14 @@
Specifying neither ``over`` nor ``under`` is equivalent to specifying
``under=INGRESS``.
If an ``under`` or ``over`` value is provided that does not match a tween
factory dotted name or alias in the current configuration, that value will be
ignored.  It is not an error to provide an ``under`` or ``over`` value that
matches an unused tween factory.
If all options for ``under`` (or ``over``) cannot be found in the current
configuration, it is an error. If some options are specified purely for
compatibilty with other tweens, just add a fallback of MAIN or INGRESS.
For example, ``under=('someothertween', 'someothertween2', INGRESS)``.
This constraint will require the tween to be located under both the
'someothertween' tween, the 'someothertween2' tween, and INGRESS. If any of
these is not in the current configuration, this constraint will only organize
itself based on the tweens that are present.
:meth:`~pyramid.config.Configurator.add_tween` also accepts an ``alias``
argument.  If ``alias`` is not ``None``, should be a string.  The string will
pyramid/config.py
@@ -978,8 +978,8 @@
        The ``under`` and ``over`` arguments allow the caller of
        ``add_tween`` to provide a hint about where in the tween chain this
        tween factory should be placed when an implicit tween chain is used.
        These hints are only used used when an explicit tween chain is not
        used (when the ``pyramid.tweens`` configuration value is not set).
        These hints are only used when an explicit tween chain is not used
        (when the ``pyramid.tweens`` configuration value is not set).
        Allowable values for ``under`` or ``over`` (or both) are:
        - ``None`` (the default).
@@ -994,6 +994,10 @@
        
        - One of the constants :attr:`pyramid.tweens.MAIN`,
          :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`.
        - An iterable of any combination of the above. This allows the user
          to specify fallbacks if the desired tween is not included, as well
          as compatibility with multiple other tweens.
        
        ``under`` means 'closer to the main Pyramid application than',
        ``over`` means 'closer to the request ingress than'.
@@ -1007,10 +1011,15 @@
        fictional) 'someothertween' tween factory (which was presumably added
        via ``add_tween(factory, alias='someothertween')``).
        If an ``under`` or ``over`` value is provided that does not match a
        tween factory dotted name or alias in the current configuration, that
        value will be ignored.  It is not an error to provide an ``under`` or
        ``over`` value that matches an unused tween factory.
        If all options for ``under`` (or ``over``) cannot be found in the
        current configuration, it is an error. If some options are specified
        purely for compatibilty with other tweens, just add a fallback of
        MAIN or INGRESS. For example,
        ``under=('someothertween', 'someothertween2', INGRESS)``.
        This constraint will require the tween to be located under both the
        'someothertween' tween, the 'someothertween2' tween, and INGRESS. If
        any of these is not in the current configuration, this constraint will
        only organize itself based on the tweens that are present.
        Specifying neither ``over`` nor ``under`` is equivalent to specifying
        ``under=INGRESS``.
@@ -1040,10 +1049,10 @@
        if alias in (MAIN, INGRESS):
            raise ConfigurationError('%s is a reserved tween name' % alias)
        if over is INGRESS:
        if over is INGRESS or hasattr(over, '__iter__') and INGRESS in over:
            raise ConfigurationError('%s cannot be over INGRESS' % name)
        if under is MAIN:
        if under is MAIN or hasattr(under, '__iter__') and MAIN in under:
            raise ConfigurationError('%s cannot be under MAIN' % name)
        registry = self.registry
pyramid/tests/test_config.py
@@ -763,6 +763,14 @@
            config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory',
            over=INGRESS)
    def test_add_tween_over_ingress_iterable(self):
        from pyramid.exceptions import ConfigurationError
        from pyramid.tweens import INGRESS
        config = self._makeOne()
        self.assertRaises(ConfigurationError,
            config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory',
            over=('a', INGRESS))
    def test_add_tween_under_main(self):
        from pyramid.exceptions import ConfigurationError
        from pyramid.tweens import MAIN
@@ -771,6 +779,14 @@
            config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory',
            under=MAIN)
    def test_add_tween_under_main_iterable(self):
        from pyramid.exceptions import ConfigurationError
        from pyramid.tweens import MAIN
        config = self._makeOne()
        self.assertRaises(ConfigurationError,
            config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory',
            under=('a', MAIN))
    def test_add_subscriber_defaults(self):
        from zope.interface import implements
        from zope.interface import Interface
pyramid/tests/test_tweens.py
@@ -2,7 +2,7 @@
class TestTweens(unittest.TestCase):
    def _makeOne(self):
        from pyramid.config import Tweens
        from pyramid.tweens import Tweens
        return Tweens()
    def test_add_explicit(self):
@@ -23,7 +23,6 @@
        self.assertEqual(tweens.alias_to_name['name'], 'name')
        self.assertEqual(tweens.name_to_alias['name'], 'name')
        self.assertEqual(tweens.order, [(INGRESS, 'name')])
        self.assertEqual(tweens.ingress_alias_names, ['name'])
        tweens.add_implicit('name2', 'factory2')
        self.assertEqual(tweens.names, ['name',  'name2'])
        self.assertEqual(tweens.factories,
@@ -32,7 +31,6 @@
        self.assertEqual(tweens.name_to_alias['name2'], 'name2')
        self.assertEqual(tweens.order,
                         [(INGRESS, 'name'), (INGRESS, 'name2')])
        self.assertEqual(tweens.ingress_alias_names, ['name', 'name2'])
        tweens.add_implicit('name3', 'factory3', over='name2')
        self.assertEqual(tweens.names,
                         ['name',  'name2', 'name3'])
@@ -44,7 +42,6 @@
        self.assertEqual(tweens.order,
                         [(INGRESS, 'name'), (INGRESS, 'name2'),
                          ('name3', 'name2')])
        self.assertEqual(tweens.ingress_alias_names, ['name', 'name2'])
    def test_add_implicit_withaliases(self):
        from pyramid.tweens import INGRESS
@@ -56,7 +53,6 @@
        self.assertEqual(tweens.alias_to_name['n1'], 'name1')
        self.assertEqual(tweens.name_to_alias['name1'], 'n1')
        self.assertEqual(tweens.order, [(INGRESS, 'n1')])
        self.assertEqual(tweens.ingress_alias_names, ['n1'])
        tweens.add_implicit('name2', 'factory2', alias='n2')
        self.assertEqual(tweens.names, ['name1',  'name2'])
        self.assertEqual(tweens.factories,
@@ -65,7 +61,6 @@
        self.assertEqual(tweens.name_to_alias['name2'], 'n2')
        self.assertEqual(tweens.order,
                         [(INGRESS, 'n1'), (INGRESS, 'n2')])
        self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2'])
        tweens.add_implicit('name3', 'factory3', alias='n3', over='name2')
        self.assertEqual(tweens.names,
                         ['name1',  'name2', 'name3'])
@@ -77,7 +72,6 @@
        self.assertEqual(tweens.order,
                         [(INGRESS, 'n1'), (INGRESS, 'n2'),
                          ('n3', 'name2')])
        self.assertEqual(tweens.ingress_alias_names, ['n1', 'n2'])
    def test___call___explicit(self):
        tweens = self._makeOne()
@@ -89,6 +83,7 @@
        self.assertEqual(tweens(None, None), '123')
    def test___call___implicit(self):
        from pyramid.tweens import INGRESS
        tweens = self._makeOne()
        def factory1(handler, registry):
            return handler
@@ -97,10 +92,13 @@
        tweens.names = ['name', 'name2']
        tweens.alias_to_name = {'name':'name', 'name2':'name2'}
        tweens.name_to_alias = {'name':'name', 'name2':'name2'}
        tweens.req_under = set(['name', 'name2'])
        tweens.order = [(INGRESS, 'name'), (INGRESS, 'name2')]
        tweens.factories = {'name':factory1, 'name2':factory2}
        self.assertEqual(tweens(None, None), '123')
    def test___call___implicit_with_aliasnames_different_than_names(self):
        from pyramid.tweens import INGRESS
        tweens = self._makeOne()
        def factory1(handler, registry):
            return handler
@@ -109,6 +107,8 @@
        tweens.names = ['name', 'name2']
        tweens.alias_to_name = {'foo1':'name', 'foo2':'name2'}
        tweens.name_to_alias = {'name':'foo1', 'name2':'foo2'}
        tweens.req_under = set(['foo1', 'foo2'])
        tweens.order = [(INGRESS, 'name'), (INGRESS, 'name2')]
        tweens.factories = {'name':factory1, 'name2':factory2}
        self.assertEqual(tweens(None, None), '123')
@@ -233,13 +233,44 @@
                             ('txnmgr', 'txnmgr_factory'),
                          ])
    def test_implicit_ordering_missing_partial(self):
    def test_implicit_ordering_missing_over_partial(self):
        from pyramid.exceptions import ConfigurationError
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('dbt', 'dbt_factory')
        add('auth', 'auth_factory', under='browserid')
        add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
        add('browserid', 'browserid_factory')
        self.assertRaises(ConfigurationError, tweens.implicit)
    def test_implicit_ordering_missing_under_partial(self):
        from pyramid.exceptions import ConfigurationError
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('dbt', 'dbt_factory')
        add('auth', 'auth_factory', under='txnmgr')
        add('retry', 'retry_factory', over='dbt', under='exceptionview')
        add('browserid', 'browserid_factory')
        self.assertRaises(ConfigurationError, tweens.implicit)
    def test_implicit_ordering_missing_over_and_under_partials(self):
        from pyramid.exceptions import ConfigurationError
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('dbt', 'dbt_factory')
        add('auth', 'auth_factory', under='browserid')
        add('retry', 'retry_factory', over='foo', under='txnmgr')
        add('browserid', 'browserid_factory')
        self.assertRaises(ConfigurationError, tweens.implicit)
    def test_implicit_ordering_missing_over_partial_with_fallback(self):
        from pyramid.tweens import MAIN
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('exceptionview', 'excview_factory', over=MAIN)
        add('auth', 'auth_factory', under='browserid')
        add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
        add('retry', 'retry_factory', over=('txnmgr',MAIN),
                                      under='exceptionview')
        add('browserid', 'browserid_factory')
        add('dbt', 'dbt_factory') 
        self.assertEqual(tweens.implicit(),
@@ -251,31 +282,20 @@
                             ('retry', 'retry_factory'),
                             ])
    def test_implicit_ordering_missing_partial2(self):
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('dbt', 'dbt_factory')
        add('auth', 'auth_factory', under='browserid')
        add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
        add('browserid', 'browserid_factory')
        self.assertEqual(tweens.implicit(),
                         [
                             ('retry', 'retry_factory'),
                             ('browserid', 'browserid_factory'),
                             ('auth', 'auth_factory'),
                             ('dbt', 'dbt_factory'),
                             ])
    def test_implicit_ordering_missing_partial3(self):
    def test_implicit_ordering_missing_under_partial_with_fallback(self):
        from pyramid.tweens import MAIN
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('exceptionview', 'excview_factory', over=MAIN)
        add('retry', 'retry_factory', over='txnmgr', under='exceptionview')
        add('auth', 'auth_factory', under=('txnmgr','browserid'))
        add('retry', 'retry_factory', under='exceptionview')
        add('browserid', 'browserid_factory')
        add('dbt', 'dbt_factory')
        self.assertEqual(tweens.implicit(),
                         [
                             ('dbt', 'dbt_factory'),
                             ('browserid', 'browserid_factory'),
                             ('auth', 'auth_factory'),
                             ('exceptionview', 'excview_factory'),
                             ('retry', 'retry_factory'),
                             ])
@@ -285,7 +305,7 @@
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('exceptionview', 'excview_factory', alias='e', over=MAIN)
        add('retry', 'retry_factory', over='txnmgr', under='e')
        add('retry', 'retry_factory', over=('txnmgr',MAIN), under='e')
        add('browserid', 'browserid_factory')
        self.assertEqual(tweens.implicit(),
                         [
@@ -294,6 +314,44 @@
                             ('retry', 'retry_factory'),
                             ])
    def test_implicit_ordering_with_partial_fallbacks(self):
        from pyramid.tweens import MAIN
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('exceptionview', 'excview_factory', alias='e', over=('b', MAIN))
        add('retry', 'retry_factory', under='e')
        add('browserid', 'browserid_factory', over=('txnmgr', 'e'))
        self.assertEqual(tweens.implicit(),
                         [
                             ('browserid', 'browserid_factory'),
                             ('exceptionview', 'excview_factory'),
                             ('retry', 'retry_factory'),
                             ])
    def test_implicit_ordering_with_multiple_matching_fallbacks(self):
        from pyramid.tweens import MAIN
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('exceptionview', 'excview_factory', alias='e', over=MAIN)
        add('retry', 'retry_factory', under='e')
        add('browserid', 'browserid_factory', over=('retry', 'e'))
        self.assertEqual(tweens.implicit(),
                         [
                             ('browserid', 'browserid_factory'),
                             ('exceptionview', 'excview_factory'),
                             ('retry', 'retry_factory'),
                             ])
    def test_implicit_ordering_with_missing_fallbacks(self):
        from pyramid.exceptions import ConfigurationError
        from pyramid.tweens import MAIN
        tweens = self._makeOne()
        add = tweens.add_implicit
        add('exceptionview', 'excview_factory', alias='e', over=MAIN)
        add('retry', 'retry_factory', under='e')
        add('browserid', 'browserid_factory', over=('txnmgr', 'auth'))
        self.assertRaises(ConfigurationError, tweens.implicit)
    def test_implicit_ordering_conflict_direct(self):
        from pyramid.tweens import CyclicDependencyError
        tweens = self._makeOne()
pyramid/tweens.py
@@ -65,9 +65,10 @@
    def __init__(self):
        self.explicit = []
        self.names = []
        self.req_over = set()
        self.req_under = set()
        self.factories = {}
        self.order = []
        self.ingress_alias_names = []
        self.alias_to_name = {INGRESS:INGRESS, MAIN:MAIN}
        self.name_to_alias = {INGRESS:INGRESS, MAIN:MAIN}
@@ -83,19 +84,22 @@
        self.factories[name] = factory
        if under is None and over is None:
            under = INGRESS
            self.ingress_alias_names.append(alias)
        if under is not None:
            self.order.append((under, alias))
            if not hasattr(under, '__iter__'):
                under = (under,)
            self.order += [(u, alias) for u in under]
            self.req_under.add(alias)
        if over is not None:
            self.order.append((alias, over))
            if not hasattr(over, '__iter__'):
                over = (over,)
            self.order += [(alias, o) for o in over]
            self.req_over.add(alias)
    def implicit(self):
        order = [(INGRESS, MAIN)]
        roots = []
        graph = {}
        has_order = {}
        aliases = [INGRESS, MAIN]
        ingress_alias_names = self.ingress_alias_names[:]
        for name in self.names:
            aliases.append(self.name_to_alias[name])
@@ -117,27 +121,26 @@
            if tonode in roots:
                roots.remove(tonode)
        # remove ordering information that mentions unknown names/aliases
        for pos, (first, second) in enumerate(order):
            has_first = first in aliases
            has_second = second in aliases
            if (not has_first) or (not has_second):
                order[pos] = None, None
            else:
                has_order[first] = has_order[second] = True
        for alias in aliases:
            # any alias that doesn't have an ordering after we detect all
            # nodes with orders should get an ordering relative to INGRESS,
            # as if it were added with no under or over in add_implicit
            if (not alias in has_order) and (alias not in (INGRESS, MAIN)):
                order.append((INGRESS, alias))
                ingress_alias_names.append(alias)
            add_node(alias)
        has_over, has_under = set(), set()
        for a, b in order:
            if a is not None and b is not None: # deal with removed orders
            if a in aliases and b in aliases: # deal with missing dependencies
                add_arc(a, b)
                has_over.add(a)
                has_under.add(b)
        if not self.req_over.issubset(has_over):
            raise ConfigurationError(
                'Detected tweens with no satisfied over dependencies: %s'
                % (', '.join(sorted(self.req_over - has_over)))
            )
        if not self.req_under.issubset(has_under):
            raise ConfigurationError(
                'Detected tweens with no satisfied under dependencies: %s'
                % (', '.join(sorted(self.req_under - has_under)))
            )
        sorted_aliases = []
@@ -163,8 +166,8 @@
        result = []
        for alias in sorted_aliases:
            if alias not in (MAIN, INGRESS):
                name = self.alias_to_name.get(alias, alias)
            name = self.alias_to_name.get(alias, alias)
            if name in self.names:
                result.append((name, self.factories[name]))
        return result