Skip core_handle, add a context manager to handle cubicweb errors
authorChristophe de Vienne <christophe@unlish.com>
Tue, 15 Jul 2014 17:37:50 +0200
changeset 11487 04252e9ff549
parent 11486 cadcedf11b7e
child 11488 baa0988a6029
Skip core_handle, add a context manager to handle cubicweb errors The context manager is also used to catch errors in render_view. It handles the 'external' errors raised by cubicweb code. The more internal errors, the one that should occur only in url resolving and cubicweb controllers, are handled directly in CubicWebPyramidHandler. ValidationError is handled by CubicWebPyramidHandler for now, but should probably be handled by cw_to_pyramid Related to #4291173
pyramid_cubicweb/__init__.py
pyramid_cubicweb/handler.py
--- a/pyramid_cubicweb/__init__.py	Tue Jul 15 15:30:49 2014 +0200
+++ b/pyramid_cubicweb/__init__.py	Tue Jul 15 17:37:50 2014 +0200
@@ -1,3 +1,8 @@
+from contextlib import contextmanager
+from warnings import warn
+
+import rql
+
 from cubicweb.web.request import CubicWebRequestBase
 from cubicweb.cwconfig import CubicWebConfiguration
 from cubicweb import repoapi
@@ -5,18 +10,48 @@
 import cubicweb
 import cubicweb.web
 
-from pyramid import security
+from pyramid import security, httpexceptions
 from pyramid.httpexceptions import HTTPSeeOther
 
 from pyramid_cubicweb import authplugin
 
-import weakref
-
 import logging
 
 log = logging.getLogger(__name__)
 
 
+@contextmanager
+def cw_to_pyramid(request):
+    """Wrap a call to the cubicweb API.
+
+    All CW exceptions will be transformed into their pyramid equivalent.
+    When needed, some CW reponse bits may be converted too (mainly headers)"""
+    try:
+        yield
+    except cubicweb.web.Redirect as ex:
+        assert 300 <= ex.status < 400
+        raise httpexceptions.status_map[ex.status](ex.location)
+    except cubicweb.web.StatusResponse as ex:
+        warn('[3.16] StatusResponse is deprecated use req.status_out',
+             DeprecationWarning, stacklevel=2)
+        request.body = ex.content
+        request.status_int = ex.status
+    except cubicweb.web.Unauthorized as ex:
+        raise httpexceptions.HTTPForbidden(
+            request.cw_request._(
+                'You\'re not authorized to access this page. '
+                'If you think you should, please contact the site '
+                'administrator.'))
+    except cubicweb.web.Forbidden:
+        raise httpexceptions.HTTPForbidden(
+            request.cw_request._(
+                'This action is forbidden. '
+                'If you think it should be allowed, please contact the site '
+                'administrator.'))
+    except (rql.BadRQLQuery, cubicweb.web.RequestError) as ex:
+        raise
+
+
 class CubicWebPyramidRequest(CubicWebRequestBase):
     def __init__(self, request):
         self._request = request
@@ -69,11 +104,11 @@
     # On the other hand, we could refine the View concept and decide it works
     # with a cnx, and never with a WebRequest
 
-    view = vreg['views'].select(vid, request.cw_request, **kwargs)
-
-    view.set_stream()
-    view.render()
-    return view._stream.getvalue()
+    with cw_to_pyramid(request):
+        view = vreg['views'].select(vid, request.cw_request, **kwargs)
+        view.set_stream()
+        view.render()
+        return view._stream.getvalue()
 
 
 def login(request):
--- a/pyramid_cubicweb/handler.py	Tue Jul 15 15:30:49 2014 +0200
+++ b/pyramid_cubicweb/handler.py	Tue Jul 15 17:37:50 2014 +0200
@@ -1,11 +1,15 @@
 from pyramid import security
 from pyramid.httpexceptions import HTTPSeeOther
+from pyramid import httpexceptions
+
+import cubicweb
+import cubicweb.web
 
 from cubicweb.web.application import CubicWebPublisher
 
-from cubicweb.web import (
-    StatusResponse, DirectResponse, Redirect, NotFound, LogOut,
-    RemoteCallFailed, InvalidSession, RequestError, PublishException)
+from cubicweb.web import LogOut, cors
+
+from pyramid_cubicweb import cw_to_pyramid
 
 
 class PyramidSessionHandler(object):
@@ -28,36 +32,69 @@
 
     def __call__(self, request):
         """
-        Handler that mimics what CubicWebPublisher.main_handle_request does and
-        call CubicWebPyramidHandler.core_handle.
-
+        Handler that mimics what CubicWebPublisher.main_handle_request and
+        CubicWebPublisher.core_handle do
         """
-        # XXX In a later version of this handler, we need to by-pass
-        # core_handle and CubicWebPublisher altogether so that the various CW
-        # exceptions are converted to their pyramid equivalent.
-
-        req = request.cw_request
 
         # XXX The main handler of CW forbid anonymous https connections
         # I guess we can drop this "feature" but in doubt I leave this comment
         # so we don't forget about it. (cdevienne)
 
+        req = request.cw_request
+        vreg = request.registry['cubicweb.registry']
+
         try:
-            content = self.appli.core_handle(req, req.path)
+            try:
+                with cw_to_pyramid(request):
+                    cors.process_request(req, vreg.config)
+                    ctrlid, rset = self.appli.url_resolver.process(req, req.path)
+
+                    try:
+                        controller = vreg['controllers'].select(
+                            ctrlid, req, appli=self.appli)
+                    except cubicweb.NoSelectableObject:
+                        raise httpexceptions.HTTPUnauthorized(
+                            req._('not authorized'))
+
+                    req.update_search_state()
+                    content = controller.publish(rset=rset)
+
+                    # XXX this auto-commit should be handled by the cw_request cleanup
+                    # or the pyramid transaction manager.
+                    # It is kept here to have the ValidationError handling bw
+                    # compatible
+                    if req.cnx:
+                        txuuid = req.cnx.commit()
+                        # commited = True
+                        if txuuid is not None:
+                            req.data['last_undoable_transaction'] = txuuid
+            except cors.CORSPreflight:
+                request.response.status_int = 200
+            except cubicweb.web.ValidationError as ex:
+                # XXX The validation_error_handler implementation is light, we
+                # should redo it better in cw_to_pyramid, so it can be properly
+                # handled when raised from a cubicweb view.
+                content = self.appli.validation_error_handler(req, ex)
+            except cubicweb.web.RemoteCallFailed as ex:
+                # XXX The default pyramid error handler (or one that we provide
+                # for this exception) should be enough
+                # content = self.appli.ajax_error_handler(req, ex)
+                raise
 
             if content is not None:
                 request.response.body = content
+
             request.response.headers.clear()
+
             for k, v in req.headers_out.getAllRawHeaders():
                 for item in v:
                     request.response.headers.add(k, item)
+
         except LogOut as ex:
             # The actual 'logging out' logic should be in separated function
             # that is accessible by the pyramid views
             headers = security.forget(request)
             raise HTTPSeeOther(ex.url, headers=headers)
-        except Redirect as ex:
-            raise HTTPSeeOther(ex.url)
         # except AuthenticationError:
         # XXX I don't think it makes sens to catch this ex here (cdevienne)