summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-11-24 02:56:40 -0500
committerChris McDonough <chrism@plope.com>2011-11-24 02:56:40 -0500
commit412b4abe9bb05aa0509508bead2498dfa2bd5f41 (patch)
tree58fca3e9aa5b76aec163af2e38c9f8d68f5c9c54
parent3b5ccb38eb98e05604a5c19d89e8c77fb6104429 (diff)
downloadpyramid-412b4abe9bb05aa0509508bead2498dfa2bd5f41.tar.gz
pyramid-412b4abe9bb05aa0509508bead2498dfa2bd5f41.tar.bz2
pyramid-412b4abe9bb05aa0509508bead2498dfa2bd5f41.zip
ditch the miserable tuple-reordering stupidity of zope-style action execution (use a dictionary for all Pyramid-related actions and don't try to minimize them); add a text method to iintrospectable (temporarily); methd name changes wrt iintrospectable
-rw-r--r--pyramid/config/__init__.py172
-rw-r--r--pyramid/config/introspection.py24
-rw-r--r--pyramid/interfaces.py21
-rw-r--r--pyramid/tests/test_config/test_init.py210
-rw-r--r--pyramid/tests/test_config/test_routes.py3
5 files changed, 267 insertions, 163 deletions
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py
index 951f9b7ab..d78d46cb8 100644
--- a/pyramid/config/__init__.py
+++ b/pyramid/config/__init__.py
@@ -1,5 +1,6 @@
import inspect
import logging
+import operator
import os
import sys
import types
@@ -449,7 +450,7 @@ class Configurator(
info = ''
return info
- def action(self, discriminator, callable=None, args=(), kw=None, order=0,
+ def action(self, discriminator, callable=None, args=(), kw=None, order=None,
introspectables=()):
""" Register an action which will be executed when
:meth:`pyramid.config.Configurator.commit` is called (or executed
@@ -485,11 +486,11 @@ class Configurator(
else:
self.action_state.action(
- discriminator,
- callable,
- args,
- kw,
- order,
+ discriminator=discriminator,
+ callable=callable,
+ args=args,
+ kw=kw,
+ order=order,
info=action_info,
includepath=self.includepath,
introspectables=introspectables,
@@ -870,21 +871,22 @@ class ActionState(object):
self._seen_files.add(spec)
return True
- def action(self, discriminator, callable=None, args=(), kw=None, order=0,
+ def action(self, discriminator, callable=None, args=(), kw=None, order=None,
includepath=(), info='', introspectables=()):
"""Add an action with the given discriminator, callable and arguments
"""
- # NB: note that the ordering and composition of the action tuple should
- # not change without first ensuring that ``pyramid_zcml`` appends
- # similarly-composed actions to our .actions variable (as silly as
- # the composition and ordering is).
if kw is None:
kw = {}
- action = (discriminator, callable, args, kw, includepath, info, order,
- introspectables)
- # remove trailing false items
- while (len(action) > 2) and not action[-1]:
- action = action[:-1]
+ action = dict(
+ discriminator=discriminator,
+ callable=callable,
+ args=args,
+ kw=kw,
+ includepath=includepath,
+ info=info,
+ order=order,
+ introspectables=introspectables,
+ )
self.actions.append(action)
def execute_actions(self, clear=True, introspector=None):
@@ -936,10 +938,14 @@ class ActionState(object):
"""
+
try:
for action in resolveConflicts(self.actions):
- (_, callable, args, kw, _, info, _,
- introspectables) = expand_action(*action)
+ callable = action['callable']
+ args = action['args']
+ kw = action['kw']
+ info = action['info']
+ introspectables = action['introspectables']
if callable is None:
continue
try:
@@ -966,125 +972,73 @@ def resolveConflicts(actions):
"""Resolve conflicting actions
Given an actions list, identify and try to resolve conflicting actions.
- Actions conflict if they have the same non-null discriminator.
+ Actions conflict if they have the same non-None discriminator.
Conflicting actions can be resolved if the include path of one of
the actions is a prefix of the includepaths of the other
conflicting actions and is unequal to the include paths in the
other conflicting actions.
-
- Here are some examples to illustrate how this works:
-
- >>> from zope.configmachine.tests.directives import f
- >>> from pprint import PrettyPrinter
- >>> pprint=PrettyPrinter(width=60).pprint
- >>> pprint(resolveConflicts([
- ... (None, f),
- ... (1, f, (1,), {}, (), 'first'),
- ... (1, f, (2,), {}, ('x',), 'second'),
- ... (1, f, (3,), {}, ('y',), 'third'),
- ... (4, f, (4,), {}, ('y',), 'should be last', 99999),
- ... (3, f, (3,), {}, ('y',)),
- ... (None, f, (5,), {}, ('y',)),
- ... ]))
- [(None, f),
- (1, f, (1,), {}, (), 'first'),
- (3, f, (3,), {}, ('y',)),
- (None, f, (5,), {}, ('y',)),
- (4, f, (4,), {}, ('y',), 'should be last')]
-
- >>> try:
- ... v = resolveConflicts([
- ... (None, f),
- ... (1, f, (2,), {}, ('x',), 'eek'),
- ... (1, f, (3,), {}, ('y',), 'ack'),
- ... (4, f, (4,), {}, ('y',)),
- ... (3, f, (3,), {}, ('y',)),
- ... (None, f, (5,), {}, ('y',)),
- ... ])
- ... except ConfigurationConflictError, v:
- ... pass
- >>> print v
- Conflicting configuration actions
- For: 1
- eek
- ack
-
"""
# organize actions by discriminators
unique = {}
output = []
for i in range(len(actions)):
- (discriminator, callable, args, kw, includepath, info, order,
- introspectables
- ) = expand_action(*(actions[i]))
-
- order = order or i
+ action = actions[i]
+ if not isinstance(action, dict):
+ # old-style ZCML tuple action
+ action = expand_action(*action)
+ order = action['order']
+ if order is None:
+ action['order'] = i
+ discriminator = action['discriminator']
if discriminator is None:
- # The discriminator is None, so this directive can
- # never conflict. We can add it directly to the
- # configuration actions.
- output.append(
- (order, discriminator, callable, args, kw, includepath, info,
- introspectables)
- )
+ # The discriminator is None, so this action can never
+ # conflict. We can add it directly to the result.
+ output.append(action)
continue
-
-
- a = unique.setdefault(discriminator, [])
- a.append(
- (includepath, order, callable, args, kw, info, introspectables)
- )
+ L = unique.setdefault(discriminator, [])
+ L.append(action)
# Check for conflicts
conflicts = {}
for discriminator, dups in unique.items():
-
# We need to sort the actions by the paths so that the shortest
# path with a given prefix comes first:
- def allbutfunc(stupid):
- # f me with a shovel, py3 cant cope with sorting when the
- # callable function is in the list
- return stupid[0:2] + stupid[3:]
- dups.sort(key=allbutfunc)
- (basepath, i, callable, args, kw, baseinfo, introspectables) = dups[0]
- output.append(
- (i, discriminator, callable, args, kw, basepath, baseinfo,
- introspectables)
- )
- for (includepath, i, callable, args, kw, info,
- introspectables) in dups[1:]:
+ def bypath(action):
+ return (action['includepath'], action['order'])
+ dups.sort(key=bypath)
+ output.append(dups[0])
+ basepath = dups[0]['includepath']
+ baseinfo = dups[0]['info']
+ discriminator = dups[0]['discriminator']
+ for dup in dups[1:]:
+ includepath = dup['includepath']
# Test whether path is a prefix of opath
if (includepath[:len(basepath)] != basepath # not a prefix
- or
- (includepath == basepath)
- ):
- if discriminator not in conflicts:
- conflicts[discriminator] = [baseinfo]
- conflicts[discriminator].append(info)
-
+ or includepath == basepath):
+ L = conflicts.setdefault(discriminator, [baseinfo])
+ L.append(dup['info'])
if conflicts:
raise ConfigurationConflictError(conflicts)
- # Now put the output back in the original order, and return it:
- output.sort()
- r = []
- for o in output:
- action = o[1:]
- while len(action) > 2 and not action[-1]:
- action = action[:-1]
- r.append(action)
-
- return r
+ output.sort(key=operator.itemgetter('order'))
+ return output
-# this function is licensed under the ZPL (stolen from Zope)
def expand_action(discriminator, callable=None, args=(), kw=None,
- includepath=(), info='', order=0, introspectables=()):
+ includepath=(), info='', order=None, introspectables=()):
if kw is None:
kw = {}
- return (discriminator, callable, args, kw, includepath, info, order,
- introspectables)
+ return dict(
+ discriminator=discriminator,
+ callable=callable,
+ args=args,
+ kw=kw,
+ includepath=includepath,
+ info=info,
+ order=order,
+ introspectables=introspectables,
+ )
global_registries = WeakOrderedSet()
diff --git a/pyramid/config/introspection.py b/pyramid/config/introspection.py
index b30b8c4a1..18e9f94b1 100644
--- a/pyramid/config/introspection.py
+++ b/pyramid/config/introspection.py
@@ -40,12 +40,20 @@ class Introspector(object):
intr = category.get(discriminator, default)
return intr
- def getall(self, category_name, sort_fn=None):
+ def get_category(self, category_name, sort_fn=None):
if sort_fn is None:
sort_fn = operator.attrgetter('order')
category = self._categories[category_name]
values = category.values()
- return sorted(set(values), key=sort_fn)
+ values = sorted(set(values), key=sort_fn)
+ return [{'introspectable':intr, 'related':self.related(intr)} for
+ intr in values]
+
+ def categorized(self, sort_fn=None):
+ L = []
+ for category_name in sorted(self._categories.keys()):
+ L.append((category_name, self.get_category(category_name, sort_fn)))
+ return L
def remove(self, category_name, discriminator):
intr = self.get(category_name, discriminator)
@@ -130,6 +138,13 @@ class Introspectable(dict):
def related(self, introspector):
return introspector.related(self)
+ def text(self):
+ result = [repr(self.discriminator)]
+ for k, v in self.items():
+ result.append('%s: %s' % (k, v))
+ result.append('action_info: %s' % (self.action_info,))
+ return '\n'.join(result)
+
def __hash__(self):
return hash((self.category_name,) + (self.discriminator,))
@@ -137,6 +152,11 @@ class Introspectable(dict):
return '<%s category %r, discriminator %r>' % (self.__class__.__name__,
self.category_name,
self.discriminator)
+
+ def __nonzero__(self):
+ return True
+
+ __bool__ = __nonzero__ # py3
class IntrospectionConfiguratorMixin(object):
introspectable = Introspectable
diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py
index 3f26c4386..9cae9e642 100644
--- a/pyramid/interfaces.py
+++ b/pyramid/interfaces.py
@@ -873,10 +873,23 @@ class IIntrospector(Interface):
discriminator (or discriminator hash) ``discriminator``. If it does
not exist in the introspector, return ``default`` """
- def getall(category_name, sort_fn=None):
- """ Get a sequence of IIntrospectable objects related to
- ``category_name`` . If ``sort_fn`` is ``None``, the sequence of
- introspectable objects will be returned in the order they were added
+ def get_category(category_name, sort_fn=None):
+ """ Get a sequence of dictionaries in the form
+ ``[{'introspectable':IIntrospectable, 'related':[sequence of related
+ IIntrospectables]}, ...]`` where each introspectable is part of the
+ category associated with ``category_name`` . If ``sort_fn`` is
+ ``None``, the sequence will be returned in the order the
+ introspectables were added to the introspector. Otherwise, sort_fn
+ should be a function that accepts an IIntrospectable and returns a
+ value from it (ala the ``key`` function of Python's ``sorted``
+ callable)."""
+
+ def categorized(sort_fn=None):
+ """ Get a sequence of tuples in the form ``[(category_name,
+ [{'introspectable':IIntrospectable, 'related':[sequence of related
+ IIntrospectables]}, ...])]`` representing all known
+ introspectables. If ``sort_fn`` is ``None``, each introspectables
+ sequence will be returned in the order the introspectables were added
to the introspector. Otherwise, sort_fn should be a function that
accepts an IIntrospectable and returns a value from it (ala the
``key`` function of Python's ``sorted`` callable)."""
diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py
index a65032143..e80557096 100644
--- a/pyramid/tests/test_config/test_init.py
+++ b/pyramid/tests/test_config/test_init.py
@@ -662,10 +662,10 @@ pyramid.tests.test_config.dummy_include2""",
after = config.action_state
actions = after.actions
self.assertEqual(len(actions), 1)
- self.assertEqual(
- after.actions[0][:3],
- ('discrim', None, test_config),
- )
+ action = after.actions[0]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
def test_include_with_python_callable(self):
from pyramid.tests import test_config
@@ -674,10 +674,10 @@ pyramid.tests.test_config.dummy_include2""",
after = config.action_state
actions = after.actions
self.assertEqual(len(actions), 1)
- self.assertEqual(
- actions[0][:3],
- ('discrim', None, test_config),
- )
+ action = actions[0]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
def test_include_with_module_defaults_to_includeme(self):
from pyramid.tests import test_config
@@ -686,10 +686,10 @@ pyramid.tests.test_config.dummy_include2""",
after = config.action_state
actions = after.actions
self.assertEqual(len(actions), 1)
- self.assertEqual(
- actions[0][:3],
- ('discrim', None, test_config),
- )
+ action = actions[0]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
def test_include_with_route_prefix(self):
root_config = self._makeOne(autocommit=True)
@@ -749,9 +749,15 @@ pyramid.tests.test_config.dummy_include2""",
config.action('discrim', kw={'a':1})
self.assertEqual(
state.actions,
- [(('discrim', None, (), {'a': 1}, 0),
- {'info': 'abc', 'includepath':(), 'introspectables':()})]
- )
+ [((),
+ {'args': (),
+ 'callable': None,
+ 'discriminator': 'discrim',
+ 'includepath': (),
+ 'info': 'abc',
+ 'introspectables': (),
+ 'kw': {'a': 1},
+ 'order': None})])
def test_action_branching_nonautocommit_without_config_info(self):
config = self._makeOne(autocommit=False)
@@ -763,9 +769,15 @@ pyramid.tests.test_config.dummy_include2""",
config.action('discrim', kw={'a':1})
self.assertEqual(
state.actions,
- [(('discrim', None, (), {'a': 1}, 0),
- {'info': 'z', 'includepath':(), 'introspectables':()})]
- )
+ [((),
+ {'args': (),
+ 'callable': None,
+ 'discriminator': 'discrim',
+ 'includepath': (),
+ 'info': 'z',
+ 'introspectables': (),
+ 'kw': {'a': 1},
+ 'order': None})])
def test_scan_integration(self):
from zope.interface import alsoProvides
@@ -1313,10 +1325,10 @@ class TestConfigurator_add_directive(unittest.TestCase):
self.assertTrue(hasattr(config, 'dummy_extend'))
config.dummy_extend('discrim')
after = config.action_state
- self.assertEqual(
- after.actions[-1][:3],
- ('discrim', None, test_config),
- )
+ action = after.actions[-1]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
def test_extend_with_python_callable(self):
from pyramid.tests import test_config
@@ -1326,10 +1338,10 @@ class TestConfigurator_add_directive(unittest.TestCase):
self.assertTrue(hasattr(config, 'dummy_extend'))
config.dummy_extend('discrim')
after = config.action_state
- self.assertEqual(
- after.actions[-1][:3],
- ('discrim', None, test_config),
- )
+ action = after.actions[-1]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], test_config)
def test_extend_same_name_doesnt_conflict(self):
config = self.config
@@ -1340,10 +1352,10 @@ class TestConfigurator_add_directive(unittest.TestCase):
self.assertTrue(hasattr(config, 'dummy_extend'))
config.dummy_extend('discrim')
after = config.action_state
- self.assertEqual(
- after.actions[-1][:3],
- ('discrim', None, config.registry),
- )
+ action = after.actions[-1]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], config.registry)
def test_extend_action_method_successful(self):
config = self.config
@@ -1361,10 +1373,10 @@ class TestConfigurator_add_directive(unittest.TestCase):
after = config2.action_state
actions = after.actions
self.assertEqual(len(actions), 1)
- self.assertEqual(
- after.actions[0][:3],
- ('discrim', None, config2.package),
- )
+ action = actions[0]
+ self.assertEqual(action['discriminator'], 'discrim')
+ self.assertEqual(action['callable'], None)
+ self.assertEqual(action['args'], config2.package)
class TestActionState(unittest.TestCase):
def _makeOne(self):
@@ -1380,32 +1392,94 @@ class TestActionState(unittest.TestCase):
c = self._makeOne()
c.actions = []
c.action(1, f, (1,), {'x':1})
- self.assertEqual(c.actions, [(1, f, (1,), {'x': 1})])
+ self.assertEqual(
+ c.actions,
+ [{'args': (1,),
+ 'callable': f,
+ 'discriminator': 1,
+ 'includepath': (),
+ 'info': '',
+ 'introspectables': (),
+ 'kw': {'x': 1},
+ 'order': None}])
c.action(None)
- self.assertEqual(c.actions, [(1, f, (1,), {'x': 1}), (None, None)])
+ self.assertEqual(
+ c.actions,
+ [{'args': (1,),
+ 'callable': f,
+ 'discriminator': 1,
+ 'includepath': (),
+ 'info': '',
+ 'introspectables': (),
+ 'kw': {'x': 1},
+ 'order': None},
+
+ {'args': (),
+ 'callable': None,
+ 'discriminator': None,
+ 'includepath': (),
+ 'info': '',
+ 'introspectables': (),
+ 'kw': {},
+ 'order': None},])
def test_action_with_includepath(self):
c = self._makeOne()
c.actions = []
c.action(None, includepath=('abc',))
- self.assertEqual(c.actions, [(None, None, (), {}, ('abc',))])
+ self.assertEqual(
+ c.actions,
+ [{'args': (),
+ 'callable': None,
+ 'discriminator': None,
+ 'includepath': ('abc',),
+ 'info': '',
+ 'introspectables': (),
+ 'kw': {},
+ 'order': None}])
def test_action_with_info(self):
c = self._makeOne()
c.action(None, info='abc')
- self.assertEqual(c.actions, [(None, None, (), {}, (), 'abc')])
+ self.assertEqual(
+ c.actions,
+ [{'args': (),
+ 'callable': None,
+ 'discriminator': None,
+ 'includepath': (),
+ 'info': 'abc',
+ 'introspectables': (),
+ 'kw': {},
+ 'order': None}])
def test_action_with_includepath_and_info(self):
c = self._makeOne()
c.action(None, includepath=('spec',), info='bleh')
- self.assertEqual(c.actions,
- [(None, None, (), {}, ('spec',), 'bleh')])
+ self.assertEqual(
+ c.actions,
+ [{'args': (),
+ 'callable': None,
+ 'discriminator': None,
+ 'includepath': ('spec',),
+ 'info': 'bleh',
+ 'introspectables': (),
+ 'kw': {},
+ 'order': None}])
def test_action_with_order(self):
c = self._makeOne()
c.actions = []
c.action(None, order=99999)
- self.assertEqual(c.actions, [(None, None, (), {}, (), '', 99999)])
+ self.assertEqual(
+ c.actions,
+ [{'args': (),
+ 'callable': None,
+ 'discriminator': None,
+ 'includepath': (),
+ 'info': '',
+ 'introspectables': (),
+ 'kw': {},
+ 'order': 99999}])
def test_processSpec(self):
c = self._makeOne()
@@ -1458,12 +1532,54 @@ class Test_resolveConflicts(unittest.TestCase):
(3, f, (3,), {}, ('y',)),
(None, f, (5,), {}, ('y',)),
])
- self.assertEqual(result,
- [(None, f),
- (1, f, (1,), {}, (), 'first'),
- (3, f, (3,), {}, ('y',)),
- (None, f, (5,), {}, ('y',)),
- (4, f, (4,), {}, ('y',), 'should be last')])
+ self.assertEqual(
+ result,
+ [{'info': '',
+ 'args': (),
+ 'callable': f,
+ 'introspectables': (),
+ 'kw': {},
+ 'discriminator': None,
+ 'includepath': (),
+ 'order': 0},
+
+ {'info': 'first',
+ 'args': (1,),
+ 'callable': f,
+ 'introspectables': (),
+ 'kw': {},
+ 'discriminator': 1,
+ 'includepath': (),
+ 'order': 1},
+
+ {'info': '',
+ 'args': (3,),
+ 'callable': f,
+ 'introspectables': (),
+ 'kw': {},
+ 'discriminator': 3,
+ 'includepath': ('y',),
+ 'order': 5},
+
+ {'info': '',
+ 'args': (5,),
+ 'callable': f,
+ 'introspectables': (),
+ 'kw': {},
+ 'discriminator': None,
+ 'includepath': ('y',),
+ 'order': 6},
+
+ {'info': 'should be last',
+ 'args': (4,),
+ 'callable': f,
+ 'introspectables': (),
+ 'kw': {},
+ 'discriminator': 4,
+ 'includepath': ('y',),
+ 'order': 99999}
+ ]
+ )
def test_it_conflict(self):
from pyramid.tests.test_config import dummyfactory as f
diff --git a/pyramid/tests/test_config/test_routes.py b/pyramid/tests/test_config/test_routes.py
index 1646561cd..140a4aa73 100644
--- a/pyramid/tests/test_config/test_routes.py
+++ b/pyramid/tests/test_config/test_routes.py
@@ -52,7 +52,8 @@ class RoutesConfiguratorMixinTests(unittest.TestCase):
def test_add_route_discriminator(self):
config = self._makeOne()
config.add_route('name', 'path')
- self.assertEqual(config.action_state.actions[-1][0], ('route', 'name'))
+ self.assertEqual(config.action_state.actions[-1]['discriminator'],
+ ('route', 'name'))
def test_add_route_with_factory(self):
config = self._makeOne(autocommit=True)