summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-08-07 11:25:53 -0400
committerChris McDonough <chrism@plope.com>2011-08-07 11:25:53 -0400
commit8517d44072ee79535097ccfda25764f72ec45f81 (patch)
tree9fe02d371cea1d683316d2909e04fdb3efb1a631
parent6c3f78b0a5563a642b98f7a0eb01d1854c45e6b0 (diff)
downloadpyramid-8517d44072ee79535097ccfda25764f72ec45f81.tar.gz
pyramid-8517d44072ee79535097ccfda25764f72ec45f81.tar.bz2
pyramid-8517d44072ee79535097ccfda25764f72ec45f81.zip
first cut at topological sorting of tweens given hints provided by the framework extender
-rw-r--r--pyramid/config.py62
-rw-r--r--pyramid/paster.py14
-rw-r--r--pyramid/tests/test_config.py54
-rw-r--r--pyramid/tests/test_paster.py31
-rw-r--r--pyramid/tests/test_router.py4
-rw-r--r--pyramid/tests/test_tweens.py180
-rw-r--r--pyramid/tweens.py147
7 files changed, 381 insertions, 111 deletions
diff --git a/pyramid/config.py b/pyramid/config.py
index a12df8ef7..6ae9a70ce 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -80,6 +80,9 @@ from pyramid.traversal import DefaultRootFactory
from pyramid.traversal import find_interface
from pyramid.traversal import traversal_path
from pyramid.tweens import excview_tween_factory
+from pyramid.tweens import Tweens
+from pyramid.tweens import tween_factory_name
+from pyramid.tweens import MAIN
from pyramid.urldispatch import RoutesMapper
from pyramid.util import DottedNameResolver
from pyramid.util import WeakOrderedSet
@@ -733,9 +736,6 @@ class Configurator(object):
# cope with WebOb exc objects not decoratored with IExceptionResponse
from webob.exc import WSGIHTTPException as WebobWSGIHTTPException
registry.registerSelfAdapter((WebobResponse,), IResponse)
- # add a handler manager
- for factory in tweens:
- self._add_tween(factory, explicit=True)
if debug_logger is None:
debug_logger = logging.getLogger(self.package_name)
@@ -784,6 +784,8 @@ class Configurator(object):
self.commit()
for inc in includes:
self.include(inc)
+ for factory in tweens:
+ self._add_tween(factory, explicit=True)
def hook_zca(self):
""" Call :func:`zope.component.getSiteManager.sethook` with
@@ -903,7 +905,7 @@ class Configurator(object):
return self._derive_view(view, attr=attr, renderer=renderer)
@action_method
- def add_tween(self, tween_factory):
+ def add_tween(self, tween_factory, below=None, atop=None):
"""
Add a 'tween factory'. A :term:`tween` (think: 'between') is a bit
of code that sits between the Pyramid router's main request handling
@@ -920,9 +922,10 @@ class Configurator(object):
.. note:: This feature is new as of Pyramid 1.1.1.
"""
- return self._add_tween(tween_factory, explicit=False)
+ return self._add_tween(tween_factory, below=below, atop=atop,
+ explicit=False)
- def _add_tween(self, tween_factory, explicit):
+ def _add_tween(self, tween_factory, below=None, atop=None, explicit=False):
tween_factory = self.maybe_dotted(tween_factory)
name = tween_factory_name(tween_factory)
def register():
@@ -931,11 +934,12 @@ class Configurator(object):
if tweens is None:
tweens = Tweens()
registry.registerUtility(tweens, ITweens)
- tweens.add(
- tween_factory_name(excview_tween_factory),
- excview_tween_factory,
- explicit=False)
- tweens.add(name, tween_factory, explicit)
+ tweens.add_implicit(tween_factory_name(excview_tween_factory),
+ excview_tween_factory, below=MAIN)
+ if explicit:
+ tweens.add_explicit(name, tween_factory)
+ else:
+ tweens.add_implicit(name, tween_factory, below=below, atop=atop)
self.action(('tween', name, explicit), register)
@action_method
@@ -3378,39 +3382,3 @@ def isexception(o):
global_registries = WeakOrderedSet()
-class Tweens(object):
- implements(ITweens)
- def __init__(self):
- self.explicit = []
- self.implicit = []
-
- def add(self, name, factory, explicit=False):
- if explicit:
- self.explicit.append((name, factory))
- else:
- self.implicit.append((name, factory))
-
- def __call__(self, handler, registry):
- factories = self.implicit
- if self.explicit:
- factories = self.explicit
- for name, factory in factories:
- handler = factory(handler, registry)
- return handler
-
-def tween_factory_name(factory):
- if (hasattr(factory, '__name__') and
- hasattr(factory, '__module__')):
- # function or class
- name = '.'.join([factory.__module__,
- factory.__name__])
- elif hasattr(factory, '__module__'):
- # instance
- name = '.'.join([factory.__module__,
- factory.__class__.__name__,
- str(id(factory))])
- else:
- raise ConfigurationError(
- 'A tween factory must be a class, an instance, or a function; '
- '%s is not a suitable tween factory' % factory)
- return name
diff --git a/pyramid/paster.py b/pyramid/paster.py
index 54f5a51a6..e7ec8fb93 100644
--- a/pyramid/paster.py
+++ b/pyramid/paster.py
@@ -574,27 +574,31 @@ class PTweensCommand(PCommand):
registry = env['registry']
tweens = self._get_tweens(registry)
if tweens is not None:
+ implicit = tweens.implicit()
+ explicit = tweens.explicit
ordering = []
- if tweens.explicit:
+ if explicit:
self.out('"pyramid.tweens" config value set '
'(explicitly ordered tweens used)')
self.out('')
- ordering.append((tweens.explicit,
+ ordering.append((explicit,
'Explicit Tween Chain (used)'))
- ordering.append((tweens.implicit,
+ ordering.append((implicit,
'Implicit Tween Chain (not used)'))
else:
self.out('"pyramid.tweens" config value NOT set '
'(implicitly ordered tweens used)')
self.out('')
- ordering.append((tweens.implicit, ''))
+ ordering.append((implicit, ''))
for L, title in ordering:
if title:
self.out(title)
self.out('')
- fmt = '%-8s %-30s'
+ fmt = '%-10s %-30s'
self.out(fmt % ('Position', 'Name'))
self.out(fmt % ('-'*len('Position'), '-'*len('Name')))
+ self.out(fmt % ('(implied)', 'main'))
for pos, (name, item) in enumerate(L):
self.out(fmt % (pos, name))
+ self.out(fmt % ('(implied)', 'ingress'))
self.out('')
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index d73fd7f7d..598362ccb 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -592,6 +592,7 @@ pyramid.tests.test_config.dummy_include2""",
'pyramid.tweens': 'pyramid.tests.test_config.dummy_tween_factory'
}
config.setup_registry(settings=settings)
+ config.commit()
tweens = config.registry.getUtility(ITweens)
self.assertEqual(tweens.explicit,
[('pyramid.tests.test_config.dummy_tween_factory',
@@ -639,7 +640,7 @@ pyramid.tests.test_config.dummy_include2""",
config.commit()
tweens = config.registry.queryUtility(ITweens)
self.assertEqual(
- tweens.implicit,
+ tweens.implicit(),
[('pyramid.tweens.excview_tween_factory', excview_tween_factory),
('pyramid.tests.test_config.factory1', factory1),
('pyramid.tests.test_config.factory2', factory2)])
@@ -652,7 +653,7 @@ pyramid.tests.test_config.dummy_include2""",
config.commit()
tweens = config.registry.queryUtility(ITweens)
self.assertEqual(
- tweens.implicit,
+ tweens.implicit(),
[
('pyramid.tweens.excview_tween_factory', excview_tween_factory),
('pyramid.tests.test_config.dummy_tween_factory',
@@ -668,13 +669,15 @@ pyramid.tests.test_config.dummy_include2""",
config.add_tween(atween)
config.commit()
tweens = config.registry.queryUtility(ITweens)
- self.assertEqual(len(tweens.implicit), 2)
+ implicit = tweens.implicit()
+ self.assertEqual(len(implicit), 2)
self.assertEqual(
- tweens.implicit[0],
+ implicit[0],
('pyramid.tweens.excview_tween_factory', excview_tween_factory))
self.assertTrue(
- tweens.implicit[1][0].startswith('pyramid.tests.test_config.ATween.'))
- self.assertEqual(tweens.implicit[1][1], atween)
+ implicit[1][0].startswith(
+ 'pyramid.tests.test_config.ATween.'))
+ self.assertEqual(implicit[1][1], atween)
def test_add_tween_unsuitable(self):
from pyramid.exceptions import ConfigurationError
@@ -5511,45 +5514,6 @@ class Test_isexception(unittest.TestCase):
pass
self.assertEqual(self._callFUT(ISubException), True)
-class TestTweens(unittest.TestCase):
- def _makeOne(self):
- from pyramid.config import Tweens
- return Tweens()
-
- def test_add_explicit(self):
- tweens = self._makeOne()
- tweens.add('name', 'factory', explicit=True)
- self.assertEqual(tweens.explicit, [('name', 'factory')])
- tweens.add('name2', 'factory2', explicit=True)
- self.assertEqual(tweens.explicit, [('name', 'factory'),
- ('name2', 'factory2')])
-
- def test_add_implicit(self):
- tweens = self._makeOne()
- tweens.add('name', 'factory', explicit=False)
- self.assertEqual(tweens.implicit, [('name', 'factory')])
- tweens.add('name2', 'factory2', explicit=False)
- self.assertEqual(tweens.implicit, [('name', 'factory'),
- ('name2', 'factory2')])
-
- def test___call___explicit(self):
- tweens = self._makeOne()
- def factory1(handler, registry):
- return handler
- def factory2(handler, registry):
- return '123'
- tweens.explicit = [('name', factory1), ('name', factory2)]
- self.assertEqual(tweens(None, None), '123')
-
- def test___call___implicit(self):
- tweens = self._makeOne()
- def factory1(handler, registry):
- return handler
- def factory2(handler, registry):
- return '123'
- tweens.implicit = [('name', factory1), ('name', factory2)]
- self.assertEqual(tweens(None, None), '123')
-
class DummyRequest:
subpath = ()
matchdict = None
diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py
index 58ed73d2c..be7aa386e 100644
--- a/pyramid/tests/test_paster.py
+++ b/pyramid/tests/test_paster.py
@@ -856,9 +856,11 @@ class TestPTweensCommand(unittest.TestCase):
L,
['"pyramid.tweens" config value NOT set (implicitly ordered tweens used)',
'',
- 'Position Name ',
- '-------- ---- ',
- '0 name ',
+ 'Position Name ',
+ '-------- ---- ',
+ '(implied) main ',
+ '0 name ',
+ '(implied) ingress ',
''])
def test_command_implicit_and_explicit_tweens(self):
@@ -875,17 +877,20 @@ class TestPTweensCommand(unittest.TestCase):
'',
'Explicit Tween Chain (used)',
'',
- 'Position Name ',
- '-------- ---- ',
- '0 name2 ',
+ 'Position Name ',
+ '-------- ---- ',
+ '(implied) main ',
+ '0 name2 ',
+ '(implied) ingress ',
'',
'Implicit Tween Chain (not used)',
'',
- 'Position Name ',
- '-------- ---- ',
- '0 name ',
- ''
- ])
+ 'Position Name ',
+ '-------- ---- ',
+ '(implied) main ',
+ '0 name ',
+ '(implied) ingress ',
+ ''])
def test__get_tweens(self):
command = self._makeOne()
@@ -894,8 +899,10 @@ class TestPTweensCommand(unittest.TestCase):
class DummyTweens(object):
def __init__(self, implicit, explicit):
- self.implicit = implicit
+ self._implicit = implicit
self.explicit = explicit
+ def implicit(self):
+ return self._implicit
class Dummy:
pass
diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py
index 6b0354468..19134813f 100644
--- a/pyramid/tests/test_router.py
+++ b/pyramid/tests/test_router.py
@@ -159,8 +159,8 @@ class TestRouter(unittest.TestCase):
wrapper.name = 'two'
wrapper.child = handler
return wrapper
- tweens.add('one', tween_factory1)
- tweens.add('two', tween_factory2)
+ tweens.add_implicit('one', tween_factory1)
+ tweens.add_implicit('two', tween_factory2)
router = self._makeOne()
self.assertEqual(router.handle_request.name, 'two')
self.assertEqual(router.handle_request.child.name, 'one')
diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py
new file mode 100644
index 000000000..7d5ae11b2
--- /dev/null
+++ b/pyramid/tests/test_tweens.py
@@ -0,0 +1,180 @@
+import unittest
+
+class TestTweens(unittest.TestCase):
+ def _makeOne(self):
+ from pyramid.config import Tweens
+ return Tweens()
+
+ def test_add_explicit(self):
+ tweens = self._makeOne()
+ tweens.add_explicit('name', 'factory')
+ self.assertEqual(tweens.explicit, [('name', 'factory')])
+ tweens.add_explicit('name2', 'factory2')
+ self.assertEqual(tweens.explicit, [('name', 'factory'),
+ ('name2', 'factory2')])
+
+ def test_add_implicit(self):
+ from pyramid.tweens import INGRESS
+ tweens = self._makeOne()
+ tweens.add_implicit('name', 'factory')
+ self.assertEqual(tweens.implicit_names, ['name'])
+ self.assertEqual(tweens.implicit_factories,
+ {'name':'factory'})
+ self.assertEqual(tweens.implicit_ingress_names, ['name'])
+ self.assertEqual(tweens.implicit_order, [('name', INGRESS)])
+ tweens.add_implicit('name2', 'factory2')
+ self.assertEqual(tweens.implicit_names, ['name', 'name2'])
+ self.assertEqual(tweens.implicit_factories,
+ {'name':'factory', 'name2':'factory2'})
+ self.assertEqual(tweens.implicit_ingress_names, ['name', 'name2'])
+ self.assertEqual(tweens.implicit_order,
+ [('name', INGRESS), ('name2', INGRESS)])
+ tweens.add_implicit('name3', 'factory3', below='name2')
+ self.assertEqual(tweens.implicit_names, ['name', 'name2', 'name3'])
+ self.assertEqual(tweens.implicit_factories,
+ {'name':'factory', 'name2':'factory2',
+ 'name3':'factory3'})
+ self.assertEqual(tweens.implicit_ingress_names, ['name', 'name2'])
+ self.assertEqual(tweens.implicit_order,
+ [('name', INGRESS), ('name2', INGRESS),
+ ('name2', 'name3')])
+
+ def test___call___explicit(self):
+ tweens = self._makeOne()
+ def factory1(handler, registry):
+ return handler
+ def factory2(handler, registry):
+ return '123'
+ tweens.explicit = [('name', factory1), ('name', factory2)]
+ self.assertEqual(tweens(None, None), '123')
+
+ def test___call___implicit(self):
+ tweens = self._makeOne()
+ def factory1(handler, registry):
+ return handler
+ def factory2(handler, registry):
+ return '123'
+ tweens.implicit_names = ['name', 'name2']
+ tweens.implicit_factories = {'name':factory1, 'name2':factory2}
+ self.assertEqual(tweens(None, None), '123')
+
+ def test_implicit_ordering_1(self):
+ tweens = self._makeOne()
+ tweens.add_implicit('name1', 'factory1')
+ tweens.add_implicit('name2', 'factory2')
+ self.assertEqual(tweens.implicit(), [('name1', 'factory1'),
+ ('name2', 'factory2')])
+
+ def test_implicit_ordering_2(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ tweens.add_implicit('name1', 'factory1')
+ tweens.add_implicit('name2', 'factory2', below=MAIN)
+ self.assertEqual(tweens.implicit(),
+ [('name2', 'factory2'), ('name1', 'factory1')])
+
+ def test_implicit_ordering_3(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('auth', 'auth_factory', atop='browserid')
+ add('dbt', 'dbt_factory')
+ add('retry', 'retry_factory', below='txnmgr', atop='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('txnmgr', 'txnmgr_factory', atop='exceptionview')
+ add('exceptionview', 'excview_factory', below=MAIN)
+ self.assertEqual(tweens.implicit(),
+ [('txnmgr', 'txnmgr_factory'),
+ ('retry', 'retry_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('auth', 'auth_factory'),
+ ('browserid', 'browserid_factory'),
+ ('dbt', 'dbt_factory')])
+
+ def test_implicit_ordering_4(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('exceptionview', 'excview_factory', below=MAIN)
+ add('auth', 'auth_factory', atop='browserid')
+ add('retry', 'retry_factory', below='txnmgr', atop='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('txnmgr', 'txnmgr_factory', atop='exceptionview')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(tweens.implicit(),
+ [('txnmgr', 'txnmgr_factory'),
+ ('retry', 'retry_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('auth', 'auth_factory'),
+ ('browserid', 'browserid_factory'),
+ ('dbt', 'dbt_factory')])
+
+ def test_implicit_ordering_missing_partial(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('exceptionview', 'excview_factory', below=MAIN)
+ add('auth', 'auth_factory', atop='browserid')
+ add('retry', 'retry_factory', below='txnmgr', atop='exceptionview')
+ add('browserid', 'browserid_factory')
+ add('dbt', 'dbt_factory')
+ self.assertEqual(tweens.implicit(),
+ [('retry', 'retry_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('auth', 'auth_factory'),
+ ('browserid', 'browserid_factory'),
+ ('dbt', 'dbt_factory')])
+
+ def test_implicit_ordering_missing_partial2(self):
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('dbt', 'dbt_factory')
+ add('auth', 'auth_factory', atop='browserid')
+ add('retry', 'retry_factory', below='txnmgr', atop='exceptionview')
+ add('browserid', 'browserid_factory')
+ self.assertEqual(tweens.implicit(),
+ [('dbt', 'dbt_factory'),
+ ('auth', 'auth_factory'),
+ ('browserid', 'browserid_factory'),
+ ('retry', 'retry_factory')])
+
+ def test_implicit_ordering_missing_partial3(self):
+ from pyramid.tweens import MAIN
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('exceptionview', 'excview_factory', below=MAIN)
+ add('retry', 'retry_factory', below='txnmgr', atop='exceptionview')
+ add('browserid', 'browserid_factory')
+ self.assertEqual(tweens.implicit(),
+ [('retry', 'retry_factory'),
+ ('exceptionview', 'excview_factory'),
+ ('browserid', 'browserid_factory')])
+
+ def test_implicit_ordering_conflict_direct(self):
+ from pyramid.tweens import CyclicDependencyError
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('browserid', 'browserid_factory')
+ add('auth', 'auth_factory', atop='browserid', below='browserid')
+ self.assertRaises(CyclicDependencyError, tweens.implicit)
+
+ def test_implicit_ordering_conflict_indirect(self):
+ from pyramid.tweens import CyclicDependencyError
+ tweens = self._makeOne()
+ add = tweens.add_implicit
+ add('browserid', 'browserid_factory')
+ add('auth', 'auth_factory', atop='browserid')
+ add('dbt', 'dbt_factory', below='browserid', atop='auth')
+ self.assertRaises(CyclicDependencyError, tweens.implicit)
+
+class TestCyclicDependencyError(unittest.TestCase):
+ def _makeOne(self, cycles):
+ from pyramid.tweens import CyclicDependencyError
+ return CyclicDependencyError(cycles)
+
+ def test___str__(self):
+ exc = self._makeOne({'a':['c', 'd'], 'c':['a']})
+ result = str(exc)
+ self.assertEqual(result,
+ "'a' sorts atop ['c', 'd']; 'c' sorts atop ['a']")
+
diff --git a/pyramid/tweens.py b/pyramid/tweens.py
index f7673a738..93a564cfc 100644
--- a/pyramid/tweens.py
+++ b/pyramid/tweens.py
@@ -1,9 +1,12 @@
import sys
+from pyramid.exceptions import ConfigurationError
from pyramid.interfaces import IExceptionViewClassifier
from pyramid.interfaces import IView
+from pyramid.interfaces import ITweens
from pyramid.events import NewResponse
from zope.interface import providedBy
+from zope.interface import implements
def excview_tween_factory(handler, registry):
""" A :term:`tween` factory which produces a tween that catches an
@@ -43,3 +46,147 @@ def excview_tween_factory(handler, registry):
return excview_tween
+class CyclicDependencyError(Exception):
+ def __init__(self, cycles):
+ self.cycles = cycles
+
+ def __str__(self):
+ L = []
+ cycles = self.cycles
+ for cycle in cycles:
+ dependent = cycle
+ dependees = cycles[cycle]
+ L.append('%r sorts atop %r' % (dependent, dependees))
+ msg = '; '.join(L)
+ return msg
+
+MAIN = 'main'
+INGRESS = 'ingress'
+
+class Tweens(object):
+ implements(ITweens)
+ def __init__(self):
+ self.explicit = []
+ self.implicit_names = []
+ self.implicit_factories = {}
+ self.implicit_order = []
+ self.implicit_ingress_names = []
+
+ def add_explicit(self, name, factory):
+ self.explicit.append((name, factory))
+
+ def add_implicit(self, name, factory, below=None, atop=None):
+ if below is None and atop is None:
+ atop = 'ingress'
+ self.implicit_ingress_names.append(name)
+ if below is not None:
+ order = (below, name)
+ self.implicit_order.append(order)
+ if atop is not None:
+ order = (name, atop)
+ self.implicit_order.append(order)
+ self.implicit_names.append(name)
+ self.implicit_factories[name] = factory
+
+ def implicit(self):
+ roots = []
+ graph = {}
+
+ def add_node(graph, node):
+ if not graph.has_key(node):
+ roots.append(node)
+ graph[node] = [0] # 0 = number of arcs coming into this node
+
+ def add_arc(graph, fromnode, tonode):
+ graph[fromnode].append(tonode)
+ graph[tonode][0] += 1
+ if tonode in roots:
+ roots.remove(tonode)
+
+ names = [MAIN, INGRESS] + self.implicit_names
+
+ orders = {}
+
+ for pos, (first, second) in enumerate(self.implicit_order):
+ has_first = first in names
+ has_second = second in names
+ if (not has_first) or (not has_second):
+ self.implicit_order[pos] = None, None # FFF
+ else:
+ orders[first] = orders[second] = True
+
+ for v in names:
+ # any name 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 below or atop
+ if (not v in orders) and (v not in (INGRESS, MAIN)):
+ self.implicit_order.append((v, INGRESS))
+ self.implicit_ingress_names.append(v)
+ add_node(graph, v)
+
+ for a, b in self.implicit_order:
+ if a is not None and b is not None: # see FFF above
+ add_arc(graph, a, b)
+
+ def sortroots(name):
+ # sort roots so that roots (and their children) that depend only on
+ # the ingress sort nearer the end (nearer the ingress)
+ if name in self.implicit_ingress_names:
+ return 1
+ children = graph[name][1:]
+ for child in children:
+ if sortroots(child) == 1:
+ return 1
+ return -1
+
+ roots.sort(key=sortroots)
+
+ sorted_tweens = []
+
+ while roots:
+ root = roots.pop(0)
+ sorted_tweens.append(root)
+ children = graph[root][1:]
+ for child in children:
+ arcs = graph[child][0]
+ arcs -= 1
+ graph[child][0] = arcs
+ if arcs == 0:
+ roots.insert(0, child)
+ del graph[root]
+
+ if graph:
+ # loop in input
+ cycledeps = {}
+ for k, v in graph.items():
+ cycledeps[k] = v[1:]
+ raise CyclicDependencyError(cycledeps)
+
+ return [ (name, self.implicit_factories[name]) for name in
+ sorted_tweens if name not in (MAIN, INGRESS) ]
+
+ def __call__(self, handler, registry):
+ if self.explicit:
+ factories = self.explicit
+ else:
+ factories = self.implicit()
+ for name, factory in factories:
+ handler = factory(handler, registry)
+ return handler
+
+def tween_factory_name(factory):
+ if (hasattr(factory, '__name__') and
+ hasattr(factory, '__module__')):
+ # function or class
+ name = '.'.join([factory.__module__,
+ factory.__name__])
+ elif hasattr(factory, '__module__'):
+ # instance
+ name = '.'.join([factory.__module__,
+ factory.__class__.__name__,
+ str(id(factory))])
+ else:
+ raise ConfigurationError(
+ 'A tween factory must be a class, an instance, or a function; '
+ '%s is not a suitable tween factory' % factory)
+ return name