Chris McDonough
2010-12-15 af46cfa87c2d5e038e0ab593e2f23471c9e9a375
- Fix conflict exceptions so they have the right ``info`` when one
configuration method is called from another.
3 files modified
153 ■■■■ changed files
TODO.txt 3 ●●●●● patch | view | raw | blame | history
pyramid/config.py 67 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config.py 83 ●●●●● patch | view | raw | blame | history
TODO.txt
@@ -6,9 +6,6 @@
- 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).
pyramid/config.py
@@ -103,6 +103,27 @@
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 @@
    manager = manager # for testing injection
    venusian = venusian # for testing injection
    _ctx = None
    _ainfo = None
    def __init__(self,
                 registry=None,
@@ -274,7 +296,7 @@
        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 @@
            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 @@
        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 @@
                                          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 @@
                # 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 @@
            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 @@
            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 @@
            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 @@
        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 @@
          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 @@
        discriminator = tuple(discriminator)
        self.action(discriminator, register)
    #@action_method
    @action_method
    def add_route(self,
                  name,
                  pattern=None,
@@ -1768,7 +1790,7 @@
            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 @@
        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 @@
        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 @@
            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 @@
            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 @@
            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 @@
            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 @@
            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 @@
            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 @@
        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 @@
            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 @@
            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 @@
                                      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
pyramid/tests/test_config.py
@@ -3319,7 +3319,14 @@
            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 @@
        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