diff options
| -rw-r--r-- | TODO.txt | 3 | ||||
| -rw-r--r-- | pyramid/config.py | 67 | ||||
| -rw-r--r-- | pyramid/tests/test_config.py | 83 |
3 files changed, 128 insertions, 25 deletions
@@ -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 |
