summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/narr/hooks.rst16
-rw-r--r--pyramid/config.py25
-rw-r--r--pyramid/tests/test_config.py16
-rw-r--r--pyramid/tests/test_tweens.py100
-rw-r--r--pyramid/tweens.py51
5 files changed, 151 insertions, 57 deletions
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index cd2109c5c..c8efc057c 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -949,6 +949,10 @@ Allowable values for ``under`` or ``over`` (or both) are:
- 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 @@ this::
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
diff --git a/pyramid/config.py b/pyramid/config.py
index 827144828..920b70319 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -978,8 +978,8 @@ class Configurator(object):
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 @@ class Configurator(object):
- 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 @@ class Configurator(object):
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 @@ class Configurator(object):
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
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index 4620c05aa..ec04f177b 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -763,6 +763,14 @@ pyramid.tests.test_config.dummy_include2""",
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 @@ pyramid.tests.test_config.dummy_include2""",
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
diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py
index 5fa999e8a..67cfee8a9 100644
--- a/pyramid/tests/test_tweens.py
+++ b/pyramid/tests/test_tweens.py
@@ -2,7 +2,7 @@ import unittest
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
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 @@ class TestTweens(unittest.TestCase):
('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,27 +282,30 @@ class TestTweens(unittest.TestCase):
('retry', 'retry_factory'),
])
- def test_implicit_ordering_missing_partial2(self):
+ def test_implicit_ordering_missing_under_partial_with_fallback(self):
+ from pyramid.tweens import MAIN
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('exceptionview', 'excview_factory', over=MAIN)
+ 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(),
[
- ('retry', 'retry_factory'),
+ ('dbt', 'dbt_factory'),
('browserid', 'browserid_factory'),
('auth', 'auth_factory'),
- ('dbt', 'dbt_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('retry', 'retry_factory'),
])
- def test_implicit_ordering_missing_partial3(self):
+ def test_implicit_ordering_missing_partial_with_aliases(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('exceptionview', 'excview_factory', alias='e', over=MAIN)
+ add('retry', 'retry_factory', over=('txnmgr',MAIN), under='e')
add('browserid', 'browserid_factory')
self.assertEqual(tweens.implicit(),
[
@@ -280,13 +314,27 @@ class TestTweens(unittest.TestCase):
('retry', 'retry_factory'),
])
- def test_implicit_ordering_missing_partial_with_aliases(self):
+ 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', over='txnmgr', under='e')
- add('browserid', 'browserid_factory')
+ add('retry', 'retry_factory', under='e')
+ add('browserid', 'browserid_factory', over=('retry', 'e'))
self.assertEqual(tweens.implicit(),
[
('browserid', 'browserid_factory'),
@@ -294,6 +342,16 @@ class TestTweens(unittest.TestCase):
('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()
diff --git a/pyramid/tweens.py b/pyramid/tweens.py
index 5ada88b24..73b96d375 100644
--- a/pyramid/tweens.py
+++ b/pyramid/tweens.py
@@ -65,9 +65,10 @@ class Tweens(object):
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 @@ class Tweens(object):
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 @@ class Tweens(object):
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 @@ class Tweens(object):
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