diff options
| author | Chris McDonough <chrism@plope.com> | 2011-09-03 23:08:56 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-09-03 23:08:56 -0400 |
| commit | b074e399b91f1facb17c03bcff4777f51fef4457 (patch) | |
| tree | d1b7a4d59cb012c12f70052705d626e5275d43e6 | |
| parent | c2aae1f09786f1044ded7c61a83961fed87cc7d9 (diff) | |
| parent | 061154d55814c29598b80510a932396305a425a9 (diff) | |
| download | pyramid-b074e399b91f1facb17c03bcff4777f51fef4457.tar.gz pyramid-b074e399b91f1facb17c03bcff4777f51fef4457.tar.bz2 pyramid-b074e399b91f1facb17c03bcff4777f51fef4457.zip | |
Merge branch 'feature.actionstate'
| -rw-r--r-- | CHANGES.txt | 5 | ||||
| -rw-r--r-- | docs/whatsnew-1.2.rst | 2 | ||||
| -rw-r--r-- | pyramid/config/__init__.py | 386 | ||||
| -rw-r--r-- | pyramid/exceptions.py | 32 | ||||
| -rw-r--r-- | pyramid/registry.py | 4 | ||||
| -rw-r--r-- | pyramid/testing.py | 2 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_init.py | 294 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_routes.py | 2 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_security.py | 6 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_tweens.py | 3 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_views.py | 13 | ||||
| -rw-r--r-- | pyramid/tests/test_exceptions.py | 29 | ||||
| -rw-r--r-- | pyramid/tests/test_registry.py | 4 | ||||
| -rw-r--r-- | setup.py | 3 |
14 files changed, 616 insertions, 169 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 9f2dc67ef..b06a972ef 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,11 @@ Bug Fixes when registering routes in certain circumstances. See https://github.com/Pylons/pyramid/issues/260 +Dependencies +------------ + +- The ``zope.configuration`` package is no longer a dependency. + 1.2a4 (2011-09-02) ================== diff --git a/docs/whatsnew-1.2.rst b/docs/whatsnew-1.2.rst index bebc1e0be..ba92e3542 100644 --- a/docs/whatsnew-1.2.rst +++ b/docs/whatsnew-1.2.rst @@ -277,3 +277,5 @@ Dependency Changes - Pyramid now requires Venusian 1.0a1 or better to support the ``onerror`` keyword argument to :meth:`pyramid.config.Configurator.scan`. + +- The ``zope.configuration`` package is no longer a dependency. diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index d5dd5cf35..03bb13f8c 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -1,6 +1,7 @@ import inspect import logging import os +import sys import types import warnings @@ -9,17 +10,15 @@ import venusian from webob.exc import WSGIHTTPException as WebobWSGIHTTPException -from zope.configuration.config import GroupingContextDecorator -from zope.configuration.config import ConfigurationMachine -from zope.configuration.xmlconfig import registerCommonDirectives - from pyramid.interfaces import IExceptionResponse from pyramid.interfaces import IDebugLogger from pyramid.asset import resolve_asset_spec from pyramid.authorization import ACLAuthorizationPolicy from pyramid.events import ApplicationCreated -from pyramid.exceptions import ConfigurationError # bw compat +from pyramid.exceptions import ConfigurationError +from pyramid.exceptions import ConfigurationConflictError +from pyramid.exceptions import ConfigurationExecutionError from pyramid.httpexceptions import default_exceptionresponse_view from pyramid.path import caller_package from pyramid.path import package_of @@ -206,8 +205,10 @@ class Configurator( manager = manager # for testing injection venusian = venusian # for testing injection - _ctx = None _ainfo = None + basepath = None + includepath = () + info = '' def __init__(self, registry=None, @@ -369,8 +370,7 @@ class Configurator( self.include(inc) def _make_spec(self, path_or_spec): - package, filename = resolve_asset_spec(path_or_spec, - self.package_name) + package, filename = resolve_asset_spec(path_or_spec, self.package_name) if package is None: return filename # absolute filename return '%s:%s' % (package, filename) @@ -411,14 +411,6 @@ class Configurator( info=info, event=event) _registry.registerSelfAdapter = registerSelfAdapter - def _make_context(self, autocommit=False): - context = PyramidConfigurationMachine() - registerCommonDirectives(context) - context.registry = self.registry - context.autocommit = autocommit - context.route_prefix = self.route_prefix - return context - # API def action(self, discriminator, callable=None, args=(), kw=None, order=0): @@ -445,31 +437,48 @@ class Configurator( if kw is None: kw = {} - context = self._ctx - - if context is None: - autocommit = self.autocommit - else: - autocommit = context.autocommit + autocommit = self.autocommit if autocommit: if callable is not None: callable(*args, **kw) + else: - if context is None: # defer expensive creation of context - context = self._ctx = self._make_context(self.autocommit) - if not context.info: + info = self.info + if not info: # Try to provide more accurate info for conflict reports by # wrapping the context in a decorator and attaching caller info # to it, unless the context already has info (if it already has # info, it's likely a context generated by a ZCML directive). - context = GroupingContextDecorator(context) if self._ainfo: info = self._ainfo[0] else: info = '' - context.info = info - context.action(discriminator, callable, args, kw, order) + self.action_state.action( + discriminator, + callable, + args, + kw, + order, + info=info, + includepath=self.includepath, + ) + + def _get_action_state(self): + registry = self.registry + try: + state = registry.action_state + except AttributeError: + state = ActionState() + registry.action_state = state + return state + + def _set_action_state(self, state): + self.registry.action_state = state + + action_state = property(_get_action_state, _set_action_state) + + _ctx = action_state # bw compat def commit(self): """ Commit any pending configuration actions. If a configuration @@ -478,11 +487,8 @@ class Configurator( of this error will be information about the source of the conflict, usually including file names and line numbers of the cause of the configuration conflicts.""" - if self._ctx is None: - return - self._ctx.execute_actions() - # unwrap and reset the context - self._ctx = None + self.action_state.execute_actions() + self.action_state = ActionState() # old actions have been processed def include(self, callable, route_prefix=None): """Include a configuration callables, to support imperative @@ -506,7 +512,7 @@ class Configurator( Python :term:`module`, in which case, the module will be searched for a callable named ``includeme``, which will be treated as the configuration callable. - + For example, if the ``includeme`` function below lives in a module named ``myapp.myconfig``: @@ -579,9 +585,7 @@ class Configurator( """ # """ <-- emacs - _context = self._ctx - if _context is None: - _context = self._ctx = self._make_context(self.autocommit) + action_state = self.action_state if route_prefix is None: route_prefix = '' @@ -604,15 +608,18 @@ class Configurator( c = getattr(module, 'includeme') spec = module.__name__ + ':' + c.__name__ sourcefile = inspect.getsourcefile(c) - if _context.processSpec(spec): - context = GroupingContextDecorator(_context) - context.basepath = os.path.dirname(sourcefile) - context.includepath = _context.includepath + (spec,) - context.package = package_of(module) - context.route_prefix = route_prefix - config = self.__class__.with_context(context) - c(config) + if action_state.processSpec(spec): + configurator = self.__class__( + registry=self.registry, + package=package_of(module), + autocommit=self.autocommit, + route_prefix=route_prefix, + ) + configurator.basepath = os.path.dirname(sourcefile) + configurator.includepath = self.includepath + (spec,) + c(configurator) + def add_directive(self, name, directive, action_wrap=True): """ Add a directive method to the configurator. @@ -662,10 +669,15 @@ class Configurator( :meth:`pyramid.config.Configurator.with_package`, and :meth:`pyramid.config.Configurator.include` to obtain a configurator with 'the right' context. Returns a new Configurator instance.""" - configurator = cls(registry=context.registry, package=context.package, - autocommit=context.autocommit, - route_prefix=context.route_prefix) - configurator._ctx = context + configurator = cls( + registry=context.registry, + package=context.package, + autocommit=context.autocommit, + route_prefix=context.route_prefix + ) + configurator.basepath = context.basepath + configurator.includepath = context.includepath + configurator.info = context.info return configurator def with_package(self, package): @@ -674,12 +686,16 @@ class Configurator( ``package`` argument to the new configurator. ``package`` may be an actual Python package object or a :term:`dotted Python name` representing a package.""" - context = self._ctx - if context is None: - context = self._ctx = self._make_context(self.autocommit) - context = GroupingContextDecorator(context) - context.package = package - return self.__class__.with_context(context) + configurator = self.__class__( + registry=self.registry, + package=package, + autocommit=self.autocommit, + route_prefix=self.route_prefix, + ) + configurator.basepath = self.basepath + configurator.includepath = self.includepath + configurator.info = self.info + return configurator def maybe_dotted(self, dotted): """ Resolve the :term:`dotted Python name` ``dotted`` to a @@ -800,9 +816,10 @@ class Configurator( return app -class PyramidConfigurationMachine(ConfigurationMachine): - autocommit = False - route_prefix = None +class ActionState(object): + def __init__(self): + self.actions = [] + self._seen_files = set() def processSpec(self, spec): """Check whether a callable needs to be processed. The ``spec`` @@ -818,5 +835,258 @@ class PyramidConfigurationMachine(ConfigurationMachine): self._seen_files.add(spec) return True + def action(self, discriminator, callable=None, args=(), kw=None, order=0, + includepath=(), info=''): + """Add an action with the given discriminator, callable and arguments + + For testing purposes, the callable and arguments may be omitted. + In that case, a default noop callable is used. + + The discriminator must be given, but it can be None, to indicate that + the action never conflicts. + + Let's look at some examples: + + >>> c = ConfigurationContext() + + Normally, the context gets actions from subclasses. We'll provide + an actions attribute ourselves: + + >>> c.actions = [] + + We'll use a test callable that has a convenient string representation + + >>> from zope.configmachine.tests.directives import f + + >>> c.action(1, f, (1, ), {'x': 1}) + >>> c.actions + [(1, f, (1,), {'x': 1})] + + >>> c.action(None) + >>> c.actions + [(1, f, (1,), {'x': 1}), (None, None)] + + Now set the include path and info: + + >>> c.includepath = ('foo.zcml',) + >>> c.info = "?" + >>> c.action(None) + >>> c.actions[-1] + (None, None, (), {}, ('foo.zcml',), '?') + + We can add an order argument to crudely control the order + of execution: + + >>> c.action(None, order=99999) + >>> c.actions[-1] + (None, None, (), {}, ('foo.zcml',), '?', 99999) + + We can also pass an includepath argument, which will be used as the the + includepath for the action. (if includepath is None, self.includepath + will be used): + + >>> c.action(None, includepath=('abc',)) + >>> c.actions[-1] + (None, None, (), {}, ('abc',), '?') + + We can also pass an info argument, which will be used as the the + source line info for the action. (if info is None, self.info will be + used): + + >>> c.action(None, info='abc') + >>> c.actions[-1] + (None, None, (), {}, ('foo.zcml',), 'abc') + + """ + if kw is None: + kw = {} + action = (discriminator, callable, args, kw, includepath, info, order) + # remove trailing false items + while (len(action) > 2) and not action[-1]: + action = action[:-1] + self.actions.append(action) + + def execute_actions(self, clear=True): + """Execute the configuration actions + + This calls the action callables after resolving conflicts + + For example: + + >>> output = [] + >>> def f(*a, **k): + ... output.append(('f', a, k)) + >>> context = ConfigurationMachine() + >>> context.actions = [ + ... (1, f, (1,)), + ... (1, f, (11,), {}, ('x', )), + ... (2, f, (2,)), + ... ] + >>> context.execute_actions() + >>> output + [('f', (1,), {}), ('f', (2,), {})] + + If the action raises an error, we convert it to a + ConfigurationExecutionError. + + >>> output = [] + >>> def bad(): + ... bad.xxx + >>> context.actions = [ + ... (1, f, (1,)), + ... (1, f, (11,), {}, ('x', )), + ... (2, f, (2,)), + ... (3, bad, (), {}, (), 'oops') + ... ] + >>> try: + ... v = context.execute_actions() + ... except ConfigurationExecutionError, v: + ... pass + >>> print v + exceptions.AttributeError: 'function' object has no attribute 'xxx' + in: + oops + + + Note that actions executed before the error still have an effect: + + >>> output + [('f', (1,), {}), ('f', (2,), {})] + + + """ + try: + for action in resolveConflicts(self.actions): + _, callable, args, kw, _, info, _ = expand_action(*action) + if callable is None: + continue + try: + callable(*args, **kw) + except (KeyboardInterrupt, SystemExit): # pragma: no cover + raise + except: + t, v, tb = sys.exc_info() + try: + raise ConfigurationExecutionError(t, v, info), None, tb + finally: + del t, v, tb + finally: + if clear: + del self.actions[:] + +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. + 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 + ) = expand_action(*(actions[i])) + + order = order or i + 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) + ) + continue + + + a = unique.setdefault(discriminator, []) + a.append( + (includepath, order, callable, args, kw, info) + ) + + # 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: + dups.sort() + (basepath, i, callable, args, kw, baseinfo) = dups[0] + output.append( + (i, discriminator, callable, args, kw, basepath, baseinfo) + ) + for includepath, i, callable, args, kw, info in dups[1:]: + # 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) + + + 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 + +def expand_action(discriminator, callable=None, args=(), kw=None, + includepath=(), info='', order=0): + if kw is None: + kw = {} + return (discriminator, callable, args, kw, includepath, info, order) + global_registries = WeakOrderedSet() diff --git a/pyramid/exceptions.py b/pyramid/exceptions.py index 151fc241f..cafdb93f0 100644 --- a/pyramid/exceptions.py +++ b/pyramid/exceptions.py @@ -1,5 +1,3 @@ -from zope.configuration.exceptions import ConfigurationError as ZCE - from pyramid.httpexceptions import HTTPNotFound from pyramid.httpexceptions import HTTPForbidden @@ -26,8 +24,36 @@ class URLDecodeError(UnicodeDecodeError): decoded. """ -class ConfigurationError(ZCE): +class ConfigurationError(Exception): """ Raised when inappropriate input values are supplied to an API method of a :term:`Configurator`""" +class ConfigurationConflictError(ConfigurationError): + """ Raised when a configuration conflict is detected during action + processing""" + + def __init__(self, conflicts): + self._conflicts = conflicts + + def __str__(self): + r = ["Conflicting configuration actions"] + items = self._conflicts.items() + items.sort() + for discriminator, infos in items: + r.append(" For: %s" % (discriminator, )) + for info in infos: + for line in unicode(info).rstrip().split(u'\n'): + r.append(u" "+line) + + return "\n".join(r) + + +class ConfigurationExecutionError(ConfigurationError): + """An error occurred during execution of a configuration action + """ + + def __init__(self, etype, evalue, info): + self.etype, self.evalue, self.info = etype, evalue, info + def __str__(self): + return "%s: %s\n in:\n %s" % (self.etype, self.evalue, self.info) diff --git a/pyramid/registry.py b/pyramid/registry.py index 6aaf44c2f..390ae8841 100644 --- a/pyramid/registry.py +++ b/pyramid/registry.py @@ -23,6 +23,10 @@ class Registry(Components, dict): has_listeners = False _settings = None + def __nonzero__(self): + # defeat bool determination via dict.__len__ + return True + def registerSubscriptionAdapter(self, *arg, **kw): result = Components.registerSubscriptionAdapter(self, *arg, **kw) self.has_listeners = True diff --git a/pyramid/testing.py b/pyramid/testing.py index 86333b5fc..07f523868 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -1,7 +1,6 @@ import copy import os -from zope.configuration.xmlconfig import _clearContext from zope.deprecation import deprecated from zope.interface import implements @@ -836,7 +835,6 @@ def tearDown(unhook_zca=True): # however maybe somebody's using a registry we don't # understand, let's not blow up pass - _clearContext() # XXX why? def cleanUp(*arg, **kw): """ :func:`pyramid.testing.cleanUp` is an alias for diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 2a83a0c7a..1dccd00e1 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -14,6 +14,10 @@ try: except: __pypy__ = None +from pyramid.exceptions import ConfigurationExecutionError +from pyramid.exceptions import ConfigurationConflictError +from pyramid.exceptions import ConfigurationError + class ConfiguratorTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator @@ -141,7 +145,6 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(policy, result) def test_ctor_authorization_policy_only(self): - from zope.configuration.config import ConfigurationExecutionError policy = object() config = self._makeOne(authorization_policy=policy) self.assertRaises(ConfigurationExecutionError, config.commit) @@ -240,23 +243,22 @@ class ConfiguratorTests(unittest.TestCase): newconfig = config.with_package(pyramid.tests.test_config) self.assertEqual(newconfig.package, pyramid.tests.test_config) - def test_with_package_context_is_not_None(self): - import pyramid.tests.test_config - config = self._makeOne() - config._ctx = DummyContext() - config._ctx.registry = None - config._ctx.autocommit = True - config._ctx.route_prefix = None - newconfig = config.with_package(pyramid.tests.test_config) - self.assertEqual(newconfig.package, pyramid.tests.test_config) - - def test_with_package_context_is_None(self): - import pyramid.tests.test_config + def test_with_package(self): + import pyramid.tests config = self._makeOne() - config._ctx = None - newconfig = config.with_package(pyramid.tests.test_config) - self.assertEqual(newconfig.package, pyramid.tests.test_config) - self.assertEqual(config._ctx.package, None) + config.basepath = 'basepath' + config.info = 'info' + config.includepath = ('spec',) + config.autocommit = True + config.route_prefix = 'prefix' + newconfig = config.with_package(pyramid.tests) + self.assertEqual(newconfig.package, pyramid.tests) + self.assertEqual(newconfig.registry, config.registry) + self.assertEqual(newconfig.autocommit, True) + self.assertEqual(newconfig.route_prefix, 'prefix') + self.assertEqual(newconfig.info, 'info') + self.assertEqual(newconfig.basepath, 'basepath') + self.assertEqual(newconfig.includepath, ('spec',)) def test_maybe_dotted_string_success(self): import pyramid.tests.test_config @@ -266,8 +268,7 @@ class ConfiguratorTests(unittest.TestCase): def test_maybe_dotted_string_fail(self): config = self._makeOne() - self.assertRaises(ImportError, - config.maybe_dotted, 'cant.be.found') + self.assertRaises(ImportError, config.maybe_dotted, 'cant.be.found') def test_maybe_dotted_notstring_success(self): import pyramid.tests.test_config @@ -463,7 +464,6 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(result, pyramid.tests.test_config) def test_setup_registry_authorization_policy_only(self): - from zope.configuration.config import ConfigurationExecutionError from pyramid.registry import Registry policy = object() reg = Registry() @@ -658,53 +658,38 @@ pyramid.tests.test_config.dummy_include2""", def test_include_with_dotted_name(self): from pyramid.tests import test_config config = self._makeOne() - context_before = config._make_context() - config._ctx = context_before config.include('pyramid.tests.test_config.dummy_include') - context_after = config._ctx - actions = context_after.actions + after = config.action_state + actions = after.actions self.assertEqual(len(actions), 1) self.assertEqual( - context_after.actions[0][:3], + after.actions[0][:3], ('discrim', None, test_config), ) - self.assertEqual(context_after.basepath, None) - self.assertEqual(context_after.includepath, ()) - self.assertTrue(context_after is context_before) def test_include_with_python_callable(self): from pyramid.tests import test_config config = self._makeOne() - context_before = config._make_context() - config._ctx = context_before config.include(dummy_include) - context_after = config._ctx - actions = context_after.actions + after = config.action_state + actions = after.actions self.assertEqual(len(actions), 1) self.assertEqual( actions[0][:3], ('discrim', None, test_config), ) - self.assertEqual(context_after.basepath, None) - self.assertEqual(context_after.includepath, ()) - self.assertTrue(context_after is context_before) def test_include_with_module_defaults_to_includeme(self): from pyramid.tests import test_config config = self._makeOne() - context_before = config._make_context() - config._ctx = context_before config.include('pyramid.tests.test_config') - context_after = config._ctx - actions = context_after.actions + after = config.action_state + actions = after.actions self.assertEqual(len(actions), 1) self.assertEqual( actions[0][:3], ('discrim', None, test_config), ) - self.assertEqual(context_after.basepath, None) - self.assertEqual(context_after.includepath, ()) - self.assertTrue(context_after is context_before) def test_include_with_route_prefix(self): root_config = self._makeOne(autocommit=True) @@ -730,9 +715,22 @@ pyramid.tests.test_config.dummy_include2""", def test_with_context(self): config = self._makeOne() - ctx = config._make_context() - newconfig = config.with_context(ctx) - self.assertEqual(newconfig._ctx, ctx) + context = DummyZCMLContext() + context.basepath = 'basepath' + context.includepath = ('spec',) + context.package = 'pyramid' + context.autocommit = True + context.registry = 'abc' + context.route_prefix = 'buz' + context.info = 'info' + newconfig = config.with_context(context) + self.assertEqual(newconfig.package_name, 'pyramid') + self.assertEqual(newconfig.autocommit, True) + self.assertEqual(newconfig.registry, 'abc') + self.assertEqual(newconfig.route_prefix, 'buz') + self.assertEqual(newconfig.basepath, 'basepath') + self.assertEqual(newconfig.includepath, ('spec',)) + self.assertEqual(newconfig.info, 'info') def test_action_branching_kw_is_None(self): config = self._makeOne(autocommit=True) @@ -742,29 +740,32 @@ pyramid.tests.test_config.dummy_include2""", config = self._makeOne(autocommit=True) self.assertEqual(config.action('discrim', kw={'a':1}), None) - def test_action_branching_nonautocommit_without_context_info(self): + def test_action_branching_nonautocommit_with_config_info(self): config = self._makeOne(autocommit=False) - config._ctx = DummyContext() - config._ctx.info = None - config._ctx.autocommit = False - config._ctx.actions = [] - self.assertEqual(config.action('discrim', kw={'a':1}), None) - self.assertEqual(config._ctx.actions, [('discrim', None, (), {'a': 1})]) - # info is not set on ctx, it's set on the groupingcontextdecorator, - # and then lost + config.info = 'abc' + state = DummyActionState() + state.autocommit = False + config.action_state = state + config.action('discrim', kw={'a':1}) + self.assertEqual( + state.actions, + [(('discrim', None, (), {'a': 1}, 0), + {'info': 'abc', 'includepath':()})] + ) - def test_action_branching_nonautocommit_with_context_info(self): + def test_action_branching_nonautocommit_without_config_info(self): config = self._makeOne(autocommit=False) - config._ctx = DummyContext() - config._ctx.info = 'abc' - config._ctx.autocommit = False - config._ctx.actions = [] - config._ctx.action = lambda *arg, **kw: self.assertEqual( - arg, - ('discrim', None, (), {'a': 1}, 0)) - self.assertEqual(config.action('discrim', kw={'a':1}), None) - self.assertEqual(config._ctx.actions, []) - self.assertEqual(config._ctx.info, 'abc') + config.info = '' + config._ainfo = ['z'] + state = DummyActionState() + config.action_state = state + state.autocommit = False + config.action('discrim', kw={'a':1}) + self.assertEqual( + state.actions, + [(('discrim', None, (), {'a': 1}, 0), + {'info': 'z', 'includepath':()})] + ) def test_scan_integration(self): from zope.interface import alsoProvides @@ -909,7 +910,6 @@ pyramid.tests.test_config.dummy_include2""", sys.path.remove(path) def test_scan_integration_conflict(self): - from zope.configuration.config import ConfigurationConflictError from pyramid.tests.test_config.pkgs import selfscan from pyramid.config import Configurator c = Configurator() @@ -956,7 +956,6 @@ pyramid.tests.test_config.dummy_include2""", getSiteManager.reset() def test_commit_conflict_simple(self): - from zope.configuration.config import ConfigurationConflictError config = self._makeOne() def view1(request): pass def view2(request): pass @@ -977,7 +976,6 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual(registeredview.__name__, 'view1') def test_commit_conflict_with_two_includes(self): - from zope.configuration.config import ConfigurationConflictError config = self._makeOne() def view1(request): pass def view2(request): pass @@ -1026,7 +1024,6 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual(registeredview.__name__, 'view3') def test_conflict_set_notfound_view(self): - from zope.configuration.config import ConfigurationConflictError config = self._makeOne() def view1(request): pass def view2(request): pass @@ -1042,7 +1039,6 @@ pyramid.tests.test_config.dummy_include2""", raise AssertionError def test_conflict_set_forbidden_view(self): - from zope.configuration.config import ConfigurationConflictError config = self._makeOne() def view1(request): pass def view2(request): pass @@ -1283,7 +1279,6 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): self.assertTrue(hasattr(wrapper, '__call_permissive__')) def test_conflict_route_with_view(self): - from zope.configuration.config import ConfigurationConflictError config = self._makeOne() def view1(request): pass def view2(request): pass @@ -1315,9 +1310,9 @@ class TestConfigurator_add_directive(unittest.TestCase): 'dummy_extend', 'pyramid.tests.test_config.dummy_extend') self.assert_(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') - context_after = config._ctx + after = config.action_state self.assertEqual( - context_after.actions[-1][:3], + after.actions[-1][:3], ('discrim', None, test_config), ) @@ -1328,9 +1323,9 @@ class TestConfigurator_add_directive(unittest.TestCase): 'dummy_extend', dummy_extend) self.assert_(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') - context_after = config._ctx + after = config.action_state self.assertEqual( - context_after.actions[-1][:3], + after.actions[-1][:3], ('discrim', None, test_config), ) @@ -1342,14 +1337,13 @@ class TestConfigurator_add_directive(unittest.TestCase): 'dummy_extend', dummy_extend2) self.assert_(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') - context_after = config._ctx + after = config.action_state self.assertEqual( - context_after.actions[-1][:3], + after.actions[-1][:3], ('discrim', None, config.registry), ) def test_extend_action_method_successful(self): - from zope.configuration.config import ConfigurationConflictError config = self.config config.add_directive( 'dummy_extend', dummy_extend) @@ -1358,27 +1352,130 @@ class TestConfigurator_add_directive(unittest.TestCase): self.assertRaises(ConfigurationConflictError, config.commit) def test_directive_persists_across_configurator_creations(self): - from zope.configuration.config import GroupingContextDecorator config = self.config config.add_directive('dummy_extend', dummy_extend) - context = config._make_context(autocommit=False) - context = GroupingContextDecorator(context) - config2 = config.with_context(context) + config2 = config.with_package('pyramid.tests') config2.dummy_extend('discrim') - context_after = config2._ctx - actions = context_after.actions + after = config2.action_state + actions = after.actions self.assertEqual(len(actions), 1) self.assertEqual( - context_after.actions[0][:3], + after.actions[0][:3], ('discrim', None, config2.package), ) -class TestPyramidConfigurationMachine(unittest.TestCase): +class TestActionState(unittest.TestCase): + def _makeOne(self): + from pyramid.config import ActionState + return ActionState() + def test_it(self): - from pyramid.config import PyramidConfigurationMachine - m = PyramidConfigurationMachine() - self.assertEqual(m.autocommit, False) - self.assertEqual(m.route_prefix, None) + c = self._makeOne() + self.assertEqual(c.actions, []) + + def test_action_simple(self): + from pyramid.tests.test_config import dummyfactory as f + c = self._makeOne() + c.actions = [] + c.action(1, f, (1,), {'x':1}) + self.assertEqual(c.actions, [(1, f, (1,), {'x': 1})]) + c.action(None) + self.assertEqual(c.actions, [(1, f, (1,), {'x': 1}), (None, None)]) + + def test_action_with_includepath(self): + c = self._makeOne() + c.actions = [] + c.action(None, includepath=('abc',)) + self.assertEqual(c.actions, [(None, None, (), {}, ('abc',))]) + + def test_action_with_info(self): + c = self._makeOne() + c.action(None, info='abc') + self.assertEqual(c.actions, [(None, None, (), {}, (), 'abc')]) + + 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')]) + + def test_action_with_order(self): + c = self._makeOne() + c.actions = [] + c.action(None, order=99999) + self.assertEqual(c.actions, [(None, None, (), {}, (), '', 99999)]) + + def test_processSpec(self): + c = self._makeOne() + self.assertTrue(c.processSpec('spec')) + self.assertFalse(c.processSpec('spec')) + + def test_execute_actions_simple(self): + output = [] + def f(*a, **k): + output.append(('f', a, k)) + c = self._makeOne() + c.actions = [ + (1, f, (1,)), + (1, f, (11,), {}, ('x', )), + (2, f, (2,)), + (None, None), + ] + c.execute_actions() + self.assertEqual(output, [('f', (1,), {}), ('f', (2,), {})]) + + def test_execute_actions_error(self): + output = [] + def f(*a, **k): + output.append(('f', a, k)) + def bad(): + raise NotImplementedError + c = self._makeOne() + c.actions = [ + (1, f, (1,)), + (1, f, (11,), {}, ('x', )), + (2, f, (2,)), + (3, bad, (), {}, (), 'oops') + ] + self.assertRaises(ConfigurationExecutionError, c.execute_actions) + self.assertEqual(output, [('f', (1,), {}), ('f', (2,), {})]) + +class Test_resolveConflicts(unittest.TestCase): + def _callFUT(self, actions): + from pyramid.config import resolveConflicts + return resolveConflicts(actions) + + def test_it_success(self): + from pyramid.tests.test_config import dummyfactory as f + result = self._callFUT([ + (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',)), + ]) + self.assertEqual(result, + [(None, f), + (1, f, (1,), {}, (), 'first'), + (3, f, (3,), {}, ('y',)), + (None, f, (5,), {}, ('y',)), + (4, f, (4,), {}, ('y',), 'should be last')]) + + def test_it_conflict(self): + from pyramid.tests.test_config import dummyfactory as f + self.assertRaises( + ConfigurationConflictError, + self._callFUT, [ + (None, f), + (1, f, (2,), {}, ('x',), 'eek'), + (1, f, (3,), {}, ('y',), 'ack'), + (4, f, (4,), {}, ('y',)), + (3, f, (3,), {}, ('y',)), + (None, f, (5,), {}, ('y',)), + ] + ) class TestGlobalRegistriesIntegration(unittest.TestCase): def setUp(self): @@ -1466,3 +1563,20 @@ def _conflictFunctions(e): for confinst in conflict: yield confinst[2] +class DummyActionState(object): + autocommit = False + info = '' + def __init__(self): + self.actions = [] + def action(self, *arg, **kw): + self.actions.append((arg, kw)) + +class DummyZCMLContext(object): + package = None + registry = None + autocommit = False + route_prefix = None + basepath = None + includepath = () + info = '' + diff --git a/pyramid/tests/test_config/test_routes.py b/pyramid/tests/test_config/test_routes.py index 6eea92a70..1646561cd 100644 --- a/pyramid/tests/test_config/test_routes.py +++ b/pyramid/tests/test_config/test_routes.py @@ -52,7 +52,7 @@ class RoutesConfiguratorMixinTests(unittest.TestCase): def test_add_route_discriminator(self): config = self._makeOne() config.add_route('name', 'path') - self.assertEqual(config._ctx.actions[-1][0], ('route', 'name')) + self.assertEqual(config.action_state.actions[-1][0], ('route', 'name')) def test_add_route_with_factory(self): config = self._makeOne(autocommit=True) diff --git a/pyramid/tests/test_config/test_security.py b/pyramid/tests/test_config/test_security.py index d5b8c339f..d05d1d471 100644 --- a/pyramid/tests/test_config/test_security.py +++ b/pyramid/tests/test_config/test_security.py @@ -1,5 +1,8 @@ import unittest +from pyramid.exceptions import ConfigurationExecutionError +from pyramid.exceptions import ConfigurationError + class ConfiguratorSecurityMethodsTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator @@ -7,14 +10,12 @@ class ConfiguratorSecurityMethodsTests(unittest.TestCase): return config def test_set_authentication_policy_no_authz_policy(self): - from zope.configuration.config import ConfigurationExecutionError config = self._makeOne() policy = object() config.set_authentication_policy(policy) self.assertRaises(ConfigurationExecutionError, config.commit) def test_set_authentication_policy_no_authz_policy_autocommit(self): - from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) policy = object() self.assertRaises(ConfigurationError, @@ -45,7 +46,6 @@ class ConfiguratorSecurityMethodsTests(unittest.TestCase): config.registry.getUtility(IAuthenticationPolicy), authn_policy) def test_set_authorization_policy_no_authn_policy(self): - from zope.configuration.config import ConfigurationExecutionError config = self._makeOne() policy = object() config.set_authorization_policy(policy) diff --git a/pyramid/tests/test_config/test_tweens.py b/pyramid/tests/test_config/test_tweens.py index 335b745e2..0d96bf601 100644 --- a/pyramid/tests/test_config/test_tweens.py +++ b/pyramid/tests/test_config/test_tweens.py @@ -3,6 +3,8 @@ import unittest from pyramid.tests.test_config import dummy_tween_factory from pyramid.tests.test_config import dummy_tween_factory2 +from pyramid.exceptions import ConfigurationConflictError + class TestTweensConfiguratorMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator @@ -118,7 +120,6 @@ class TestTweensConfiguratorMixin(unittest.TestCase): self.assertRaises(ConfigurationError, config.add_tween, MAIN) def test_add_tweens_conflict(self): - from zope.configuration.config import ConfigurationConflictError config = self._makeOne() config.add_tween('pyramid.tests.test_config.dummy_tween_factory') config.add_tween('pyramid.tests.test_config.dummy_tween_factory') diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index a4a53bd3a..872528c6c 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -5,6 +5,10 @@ from pyramid.tests.test_config import IDummy from pyramid.tests.test_config import dummy_view +from pyramid.exceptions import ConfigurationError +from pyramid.exceptions import ConfigurationExecutionError +from pyramid.exceptions import ConfigurationConflictError + class TestViewsConfigurationMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator @@ -69,12 +73,10 @@ class TestViewsConfigurationMixin(unittest.TestCase): return route def test_add_view_view_callable_None_no_renderer(self): - from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) self.assertRaises(ConfigurationError, config.add_view) def test_add_view_with_request_type_and_route_name(self): - from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) view = lambda *arg: 'OK' self.assertRaises(ConfigurationError, config.add_view, view, '', None, @@ -952,7 +954,6 @@ class TestViewsConfigurationMixin(unittest.TestCase): self.assertEqual(result, 'OK') def test_add_view_with_request_type_as_noniface(self): - from pyramid.exceptions import ConfigurationError view = lambda *arg: 'OK' config = self._makeOne() self.assertRaises(ConfigurationError, @@ -972,7 +973,6 @@ class TestViewsConfigurationMixin(unittest.TestCase): def test_add_view_with_nonexistant_route_name(self): from pyramid.renderers import null_renderer - from zope.configuration.config import ConfigurationExecutionError view = lambda *arg: 'OK' config = self._makeOne() config.add_view(view=view, route_name='foo', renderer=null_renderer) @@ -1030,7 +1030,6 @@ class TestViewsConfigurationMixin(unittest.TestCase): def test_add_view_with_request_method_sequence_conflict(self): from pyramid.renderers import null_renderer - from zope.configuration.config import ConfigurationConflictError view = lambda *arg: 'OK' config = self._makeOne() config.add_view(view=view, request_method=('POST', 'GET'), @@ -1107,7 +1106,6 @@ class TestViewsConfigurationMixin(unittest.TestCase): self._assertNotFound(wrapper, None, request) def test_add_view_with_header_badregex(self): - from pyramid.exceptions import ConfigurationError view = lambda *arg: 'OK' config = self._makeOne() self.assertRaises(ConfigurationError, @@ -1214,7 +1212,6 @@ class TestViewsConfigurationMixin(unittest.TestCase): self.assertEqual(wrapper(context, None), 'OK') def test_add_view_with_path_info_badregex(self): - from pyramid.exceptions import ConfigurationError view = lambda *arg: 'OK' config = self._makeOne() self.assertRaises(ConfigurationError, @@ -1299,7 +1296,6 @@ class TestViewsConfigurationMixin(unittest.TestCase): self.assertEqual(wrapper(None, request), 'OK') def test_add_view_same_predicates(self): - from zope.configuration.config import ConfigurationConflictError view2 = lambda *arg: 'second' view1 = lambda *arg: 'first' config = self._makeOne() @@ -2992,7 +2988,6 @@ class TestViewDeriver(unittest.TestCase): self.assertFalse('Cache-Control' in headers) def test_http_cached_view_bad_tuple(self): - from pyramid.exceptions import ConfigurationError deriver = self._makeOne(http_cache=(None,)) def view(request): pass self.assertRaises(ConfigurationError, deriver, view) diff --git a/pyramid/tests/test_exceptions.py b/pyramid/tests/test_exceptions.py index 50182ee5c..773767d89 100644 --- a/pyramid/tests/test_exceptions.py +++ b/pyramid/tests/test_exceptions.py @@ -45,3 +45,32 @@ class TestForbidden(unittest.TestCase): from pyramid.httpexceptions import HTTPForbidden self.assertTrue(Forbidden is HTTPForbidden) +class TestConfigurationConflictError(unittest.TestCase): + def _makeOne(self, conflicts): + from pyramid.exceptions import ConfigurationConflictError + return ConfigurationConflictError(conflicts) + + def test_str(self): + conflicts = {'a':('1', '2', '3'), 'b':('4', '5', '6')} + exc = self._makeOne(conflicts) + self.assertEqual(str(exc), +"""\ +Conflicting configuration actions + For: a + 1 + 2 + 3 + For: b + 4 + 5 + 6""") + +class TestConfigurationExecutionError(unittest.TestCase): + def _makeOne(self, etype, evalue, info): + from pyramid.exceptions import ConfigurationExecutionError + return ConfigurationExecutionError(etype, evalue, info) + + def test_str(self): + exc = self._makeOne('etype', 'evalue', 'info') + self.assertEqual(str(exc), 'etype: evalue\n in:\n info') + diff --git a/pyramid/tests/test_registry.py b/pyramid/tests/test_registry.py index 3d94cb645..6a20eaa5d 100644 --- a/pyramid/tests/test_registry.py +++ b/pyramid/tests/test_registry.py @@ -8,6 +8,10 @@ class TestRegistry(unittest.TestCase): def _makeOne(self): return self._getTargetClass()() + def test___nonzero__(self): + registry = self._makeOne() + self.assertEqual(registry.__nonzero__(), True) + def test_registerHandler_and_notify(self): registry = self._makeOne() self.assertEqual(registry.has_listeners, False) @@ -35,9 +35,8 @@ install_requires=[ 'repoze.lru', 'setuptools', 'zope.component >= 3.6.0', # independent of zope.hookable - 'zope.configuration', - 'zope.deprecation', 'zope.interface >= 3.5.1', # 3.5.0 comment: "allow to bootstrap on jython" + 'zope.deprecation', 'venusian >= 1.0a1', # ``onerror`` 'translationstring', ] |
