summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.txt3
-rw-r--r--pyramid/config.py67
-rw-r--r--pyramid/tests/test_config.py83
3 files changed, 128 insertions, 25 deletions
diff --git a/TODO.txt b/TODO.txt
index 668cf0840..8f06d85c5 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -6,9 +6,6 @@ Must-Have (before 1.0)
- Narrative docs for ``Configurator.include`` and ``Configurator.commit``.
-- Fix conflict exceptions so they have the right ``info`` when one
- configuration method is called from another.
-
- Fix session behavior: when a Forbidden/NotFound hander is invoked, we'd
like to allow people to save the session (currently when the response has
an exception attribute, the session is tossed unconditionally).
diff --git a/pyramid/config.py b/pyramid/config.py
index 72561c493..1c6d1878c 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -103,6 +103,27 @@ if chameleon_text:
if chameleon_zpt:
DEFAULT_RENDERERS += (('.txt', chameleon_text.renderer_factory),)
+def action_method(wrapped):
+ """ Wrapper to provide the right conflict info report data when a method
+ that calls Configurator.action calls another that does the same"""
+ def wrapper(self, *arg, **kw):
+ if self._ainfo is None:
+ self._ainfo = []
+ try:
+ f = traceback.extract_stack(limit=3)
+ info = f[-2]
+ except: # pragma: no cover
+ info = ''
+ self._ainfo.append(info)
+ try:
+ result = wrapped(self, *arg, **kw)
+ finally:
+ self._ainfo.pop()
+ return result
+ wrapper.__name__ = wrapped.__name__
+ wrapper.__doc__ = wrapped.__doc__
+ return wrapper
+
class Configurator(object):
"""
A Configurator is used to configure a :app:`Pyramid`
@@ -227,6 +248,7 @@ class Configurator(object):
manager = manager # for testing injection
venusian = venusian # for testing injection
_ctx = None
+ _ainfo = None
def __init__(self,
registry=None,
@@ -274,7 +296,7 @@ class Configurator(object):
self.registry.settings = settings
return settings
- #@action_method
+ @action_method
def _set_root_factory(self, factory):
""" Add a :term:`root factory` to the current configuration
state. If the ``factory`` argument is ``None`` a default root
@@ -287,7 +309,7 @@ class Configurator(object):
self.registry.registerUtility(factory, IDefaultRootFactory) # b/c
self.action(IRootFactory, register)
- #@action_method
+ @action_method
def _set_authentication_policy(self, policy):
""" Add a :app:`Pyramid` :term:`authentication policy` to
the current configuration."""
@@ -295,7 +317,7 @@ class Configurator(object):
self.registry.registerUtility(policy, IAuthenticationPolicy)
self.action(IAuthenticationPolicy)
- #@action_method
+ @action_method
def _set_authorization_policy(self, policy):
""" Add a :app:`Pyramid` :term:`authorization policy` to
the current configuration state (also accepts a :term:`dotted
@@ -351,6 +373,7 @@ class Configurator(object):
name=pkg_name)
override.insert(path, override_pkg_name, override_prefix)
+ @action_method
def _set_security_policies(self, authentication, authorization=None):
if authorization is None:
authorization = ACLAuthorizationPolicy() # default
@@ -429,10 +452,9 @@ class Configurator(object):
# 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)
- try:
- f = traceback.extract_stack(limit=3)
- info = f[-3]
- except: # pragma: no cover
+ if self._ainfo:
+ info = self._ainfo[0]
+ else:
info = ''
context.info = info
context.action(discriminator, callable, args, kw, order)
@@ -736,7 +758,7 @@ class Configurator(object):
renderer = {'name':renderer, 'package':self.package}
return self._derive_view(view, attr=attr, renderer=renderer)
- #@action_method
+ @action_method
def add_subscriber(self, subscriber, iface=None):
"""Add an event :term:`subscriber` for the event stream
implied by the supplied ``iface`` interface. The
@@ -823,7 +845,6 @@ class Configurator(object):
self.manager.pop()
return app
- #@action_method
def load_zcml(self, spec='configure.zcml', lock=threading.Lock()):
""" Load configuration from a :term:`ZCML` file into the
current configuration state. The ``spec`` argument is an
@@ -865,6 +886,7 @@ class Configurator(object):
self.manager.pop()
return registry
+ @action_method
def add_handler(self, route_name, pattern, handler, action=None, **kw):
""" Add a Pylons-style view handler. This function adds a
@@ -978,7 +1000,7 @@ class Configurator(object):
return route
- #@action_method
+ @action_method
def add_view(self, view=None, name="", for_=None, permission=None,
request_type=None, route_name=None, request_method=None,
request_param=None, containment=None, attr=None,
@@ -1019,7 +1041,7 @@ class Configurator(object):
was used prior to this view registration. Pass the string
``__no_permission_required__`` as the permission argument to
explicitly indicate that the view should always be
- executable by entirely anonymous users, regardless of the
+ executable by entirely anonymous users, regardless of the
default permission, bypassing any :term:`authorization
policy` that may be in effect.
@@ -1406,7 +1428,7 @@ class Configurator(object):
discriminator = tuple(discriminator)
self.action(discriminator, register)
- #@action_method
+ @action_method
def add_route(self,
name,
pattern=None,
@@ -1768,7 +1790,7 @@ class Configurator(object):
self.registry.registerUtility(mapper, IRoutesMapper)
return mapper
- #@action_method
+ @action_method
def scan(self, package=None, categories=None):
""" Scan a Python package and any of its subpackages for
objects marked with :term:`configuration decoration` such as
@@ -1804,7 +1826,7 @@ class Configurator(object):
scanner = self.venusian.Scanner(config=self)
scanner.scan(package, categories=categories)
- #@action_method
+ @action_method
def add_renderer(self, name, factory):
"""
Add a :app:`Pyramid` :term:`renderer` factory to the
@@ -1836,7 +1858,7 @@ class Configurator(object):
self.registry.registerUtility(factory, IRendererFactory, name=name)
self.action((IRendererFactory, name), None)
- #@action_method
+ @action_method
def override_resource(self, to_override, override_with, _override=None):
""" Add a :app:`Pyramid` resource override to the current
configuration state.
@@ -1884,6 +1906,7 @@ class Configurator(object):
override(from_package, path, to_package, override_prefix)
self.action(None, register)
+ @action_method
def set_forbidden_view(self, view=None, attr=None, renderer=None,
wrapper=None):
""" Add a default forbidden view to the current configuration
@@ -1920,6 +1943,7 @@ class Configurator(object):
return view(context, request)
return self.add_view(bwcompat_view, context=Forbidden, wrapper=wrapper)
+ @action_method
def set_notfound_view(self, view=None, attr=None, renderer=None,
wrapper=None):
""" Add a default not found view to the current configuration
@@ -1958,7 +1982,7 @@ class Configurator(object):
return view(context, request)
return self.add_view(bwcompat_view, context=NotFound, wrapper=wrapper)
- #@action_method
+ @action_method
def set_request_factory(self, factory):
""" The object passed as ``factory`` should be an object (or a
:term:`dotted Python name` which refers to an object) which
@@ -1977,7 +2001,7 @@ class Configurator(object):
self.registry.registerUtility(factory, IRequestFactory)
self.action(IRequestFactory, register)
- #@action_method
+ @action_method
def set_renderer_globals_factory(self, factory):
""" The object passed as ``factory`` should be an callable (or
a :term:`dotted Python name` which refers to an callable) that
@@ -2001,7 +2025,7 @@ class Configurator(object):
self.registry.registerUtility(factory, IRendererGlobalsFactory)
self.action(IRendererGlobalsFactory, register)
- #@action_method
+ @action_method
def set_locale_negotiator(self, negotiator):
"""
Set the :term:`locale negotiator` for this application. The
@@ -2025,7 +2049,7 @@ class Configurator(object):
self.registry.registerUtility(negotiator, ILocaleNegotiator)
self.action(ILocaleNegotiator, register)
- #@action_method
+ @action_method
def set_default_permission(self, permission):
"""
Set the default permission to be used by all subsequent
@@ -2056,7 +2080,7 @@ class Configurator(object):
self.registry.registerUtility(permission, IDefaultPermission)
self.action(IDefaultPermission, None)
- #@action_method
+ @action_method
def set_session_factory(self, session_factory):
"""
Configure the application with a :term:`session factory`. If
@@ -2067,7 +2091,6 @@ class Configurator(object):
self.registry.registerUtility(session_factory, ISessionFactory)
self.action(ISessionFactory, register)
- #@action_method
def add_translation_dirs(self, *specs):
""" Add one or more :term:`translation directory` paths to the
current configuration state. The ``specs`` argument is a
@@ -2119,6 +2142,7 @@ class Configurator(object):
ctranslate = ChameleonTranslate(translator)
self.registry.registerUtility(ctranslate, IChameleonTranslate)
+ @action_method
def add_static_view(self, name, path, **kw):
""" Add a view used to render static resources such as images
and CSS files.
@@ -2287,6 +2311,7 @@ class Configurator(object):
ITraverser)
return models
+ @action_method
def testing_add_subscriber(self, event_iface=None):
"""Unit/integration testing helper: Registers a
:term:`subscriber` which listens for events of the type
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index a1e5e28f4..025eb595b 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -3319,7 +3319,14 @@ class ConfiguratorTests(unittest.TestCase):
config.add_view(view2)
config.include(includeme1)
config.include(includeme2)
- self.assertRaises(ConfigurationConflictError, config.commit)
+ try:
+ config.commit()
+ except ConfigurationConflictError, why:
+ c1, c2 = self._conflictFunctions(why)
+ self.assertEqual(c1, 'includeme1')
+ self.assertEqual(c2, 'includeme2')
+ else: #pragma: no cover
+ raise AssertionError
def test_commit_conflict_resolved_with_two_includes_and_local(self):
config = self._makeOne()
@@ -3349,6 +3356,80 @@ class ConfiguratorTests(unittest.TestCase):
registeredview = self._getViewCallable(config)
self.assertEqual(registeredview.__name__, 'view3')
+ def test_conflict_route_with_view(self):
+ from zope.configuration.config import ConfigurationConflictError
+ config = self._makeOne()
+ def view1(request): pass
+ def view2(request): pass
+ config.add_route('a', '/a', view=view1)
+ config.add_route('a', '/a', view=view2)
+ try:
+ config.commit()
+ except ConfigurationConflictError, why:
+ c1, c2, c3, c4 = self._conflictFunctions(why)
+ self.assertEqual(c1, 'test_conflict_route_with_view')
+ self.assertEqual(c2, 'test_conflict_route_with_view')
+ self.assertEqual(c3, 'test_conflict_route_with_view')
+ self.assertEqual(c4, 'test_conflict_route_with_view')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_conflict_set_notfound_view(self):
+ from zope.configuration.config import ConfigurationConflictError
+ config = self._makeOne()
+ def view1(request): pass
+ def view2(request): pass
+ config.set_notfound_view(view1)
+ config.set_notfound_view(view2)
+ try:
+ config.commit()
+ except ConfigurationConflictError, why:
+ c1, c2 = self._conflictFunctions(why)
+ self.assertEqual(c1, 'test_conflict_set_notfound_view')
+ self.assertEqual(c2, 'test_conflict_set_notfound_view')
+ else: # pragma: no cover
+ 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
+ config.set_forbidden_view(view1)
+ config.set_forbidden_view(view2)
+ try:
+ config.commit()
+ except ConfigurationConflictError, why:
+ c1, c2 = self._conflictFunctions(why)
+ self.assertEqual(c1, 'test_conflict_set_forbidden_view')
+ self.assertEqual(c2, 'test_conflict_set_forbidden_view')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def test_conflict_add_handler(self):
+ class AHandler(object):
+ def aview(self): pass
+ from zope.configuration.config import ConfigurationConflictError
+ config = self._makeOne()
+ config.add_handler('h1', '/h1', handler=AHandler)
+ config.add_handler('h1', '/h1', handler=AHandler)
+ try:
+ config.commit()
+ except ConfigurationConflictError, why:
+ c1, c2, c3, c4 = self._conflictFunctions(why)
+ self.assertEqual(c1, 'test_conflict_add_handler')
+ self.assertEqual(c2, 'test_conflict_add_handler')
+ self.assertEqual(c3, 'test_conflict_add_handler')
+ self.assertEqual(c3, 'test_conflict_add_handler')
+ else: # pragma: no cover
+ raise AssertionError
+
+ def _conflictFunctions(self, e):
+ conflicts = e._conflicts.values()
+ for conflict in conflicts:
+ for confinst in conflict:
+ yield confinst[2]
+
class Test__map_view(unittest.TestCase):
def setUp(self):
from pyramid.registry import Registry