summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-09-03 23:08:56 -0400
committerChris McDonough <chrism@plope.com>2011-09-03 23:08:56 -0400
commitb074e399b91f1facb17c03bcff4777f51fef4457 (patch)
treed1b7a4d59cb012c12f70052705d626e5275d43e6
parentc2aae1f09786f1044ded7c61a83961fed87cc7d9 (diff)
parent061154d55814c29598b80510a932396305a425a9 (diff)
downloadpyramid-b074e399b91f1facb17c03bcff4777f51fef4457.tar.gz
pyramid-b074e399b91f1facb17c03bcff4777f51fef4457.tar.bz2
pyramid-b074e399b91f1facb17c03bcff4777f51fef4457.zip
Merge branch 'feature.actionstate'
-rw-r--r--CHANGES.txt5
-rw-r--r--docs/whatsnew-1.2.rst2
-rw-r--r--pyramid/config/__init__.py386
-rw-r--r--pyramid/exceptions.py32
-rw-r--r--pyramid/registry.py4
-rw-r--r--pyramid/testing.py2
-rw-r--r--pyramid/tests/test_config/test_init.py294
-rw-r--r--pyramid/tests/test_config/test_routes.py2
-rw-r--r--pyramid/tests/test_config/test_security.py6
-rw-r--r--pyramid/tests/test_config/test_tweens.py3
-rw-r--r--pyramid/tests/test_config/test_views.py13
-rw-r--r--pyramid/tests/test_exceptions.py29
-rw-r--r--pyramid/tests/test_registry.py4
-rw-r--r--setup.py3
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)
diff --git a/setup.py b/setup.py
index 9a19b8a5b..18260b992 100644
--- a/setup.py
+++ b/setup.py
@@ -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',
]