Antony Lesuisse (OpenERP) has proposed merging 
lp:~openerp-dev/openobject-server/trunk-xmlrpc-61-vmt into lp:openobject-server.

Requested reviews:
  OpenERP Core Team (openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-xmlrpc-61-vmt/+merge/77200

New xmlrpc api on /openerp/ better exception handling
-- 
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-xmlrpc-61-vmt/+merge/77200
Your team OpenERP R&D Team is subscribed to branch 
lp:~openerp-dev/openobject-server/trunk-xmlrpc-61-vmt.
=== modified file 'openerp/addons/base/res/res_users.py'
--- openerp/addons/base/res/res_users.py	2011-09-27 09:53:45 +0000
+++ openerp/addons/base/res/res_users.py	2011-09-27 16:50:58 +0000
@@ -35,6 +35,7 @@
 from osv.orm import browse_record
 from service import security
 from tools.translate import _
+import openerp.exceptions
 
 class groups(osv.osv):
     _name = "res.groups"
@@ -437,14 +438,14 @@
         if passwd == tools.config['admin_passwd']:
             return True
         else:
-            raise security.ExceptionNoTb('AccessDenied')
+            raise openerp.exceptions.AccessDenied()
 
     def check(self, db, uid, passwd):
         """Verifies that the given (uid, password) pair is authorized for the database ``db`` and
            raise an exception if it is not."""
         if not passwd:
             # empty passwords disallowed for obvious security reasons
-            raise security.ExceptionNoTb('AccessDenied')
+            raise openerp.exceptions.AccessDenied()
         if self._uid_cache.get(db, {}).get(uid) == passwd:
             return
         cr = pooler.get_db(db).cursor()
@@ -453,7 +454,7 @@
                         (int(uid), passwd, True))
             res = cr.fetchone()[0]
             if not res:
-                raise security.ExceptionNoTb('AccessDenied')
+                raise openerp.exceptions.AccessDenied()
             if self._uid_cache.has_key(db):
                 ulist = self._uid_cache[db]
                 ulist[uid] = passwd
@@ -470,7 +471,7 @@
             cr.execute('SELECT id FROM res_users WHERE id=%s AND password=%s', (uid, passwd))
             res = cr.fetchone()
             if not res:
-                raise security.ExceptionNoTb('Bad username or password')
+                raise openerp.exceptions.AccessDenied()
             return res[0]
         finally:
             cr.close()
@@ -481,7 +482,7 @@
         password is not used to authenticate requests.
 
         :return: True
-        :raise: security.ExceptionNoTb when old password is wrong
+        :raise: openerp.exceptions.AccessDenied when old password is wrong
         :raise: except_osv when new password is not set or empty
         """
         self.check(cr.dbname, uid, old_passwd)

=== added file 'openerp/exceptions.py'
--- openerp/exceptions.py	1970-01-01 00:00:00 +0000
+++ openerp/exceptions.py	2011-09-27 16:50:58 +0000
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+""" OpenERP core exceptions.
+
+This module defines a few exception types. Those types are understood by the
+RPC layer. Any other exception type bubbling until the RPC layer will be
+treated as a 'Server error'.
+
+"""
+
+class Warning(Exception):
+    pass
+
+class AccessDenied(Exception):
+    """ Login/password error. No message, no traceback. """
+    def __init__(self):
+        import random
+        super(AccessDenied, self).__init__('Try again. %s out of %s characters are correct.' % (random.randint(0, 30), 30))
+        self.traceback = ('', '', '')
+
+class AccessError(Exception):
+    """ Access rights error. """
+
+class DeferredException(Exception):
+    """ Exception object holding a traceback for asynchronous reporting.
+
+    Some RPC calls (database creation and report generation) happen with
+    an initial request followed by multiple, polling requests. This class
+    is used to store the possible exception occuring in the thread serving
+    the first request, and is then sent to a polling request.
+
+    ('Traceback' is misleading, this is really a exc_info() triple.)
+    """
+    def __init__(self, msg, tb):
+        self.message = msg
+        self.traceback = tb
+        self.args = (msg, tb)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== modified file 'openerp/netsvc.py'
--- openerp/netsvc.py	2011-09-02 15:02:26 +0000
+++ openerp/netsvc.py	2011-09-27 16:50:58 +0000
@@ -37,6 +37,7 @@
 # TODO modules that import netsvc only for things from loglevels must be changed to use loglevels.
 from loglevels import *
 import tools
+import openerp.exceptions
 
 def close_socket(sock):
     """ Closes a socket instance cleanly
@@ -60,11 +61,12 @@
 #.apidoc title: Common Services: netsvc
 #.apidoc module-mods: member-order: bysource
 
-def abort_response(error, description, origin, details):
-    if not tools.config['debug_mode']:
-        raise Exception("%s -- %s\n\n%s"%(origin, description, details))
+def abort_response(dummy_1, description, dummy_2, details):
+    # TODO Replace except_{osv,orm} with these directly.
+    if description == 'AccessError':
+        raise openerp.exceptions.AccessError(details)
     else:
-        raise
+        raise openerp.exceptions.Warning(details)
 
 class Service(object):
     """ Base class for *Local* services
@@ -96,12 +98,9 @@
 class ExportService(object):
     """ Proxy for exported services.
 
-    All methods here should take an AuthProxy as their first parameter. It
-    will be appended by the calling framework.
-
     Note that this class has no direct proxy, capable of calling
     eservice.method(). Rather, the proxy should call
-    dispatch(method,auth,params)
+    dispatch(method, params)
     """
 
     _services = {}
@@ -118,7 +117,7 @@
 
     # Dispatch a RPC call w.r.t. the method name. The dispatching
     # w.r.t. the service (this class) is done by OpenERPDispatcher.
-    def dispatch(self, method, auth, params):
+    def dispatch(self, method, params):
         raise Exception("stub dispatch at %s" % self.__name)
 
 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10)
@@ -371,11 +370,6 @@
     def _close_socket(self):
         close_socket(self.socket)
 
-class OpenERPDispatcherException(Exception):
-    def __init__(self, exception, traceback):
-        self.exception = exception
-        self.traceback = traceback
-
 def replace_request_password(args):
     # password is always 3rd argument in a request, we replace it in RPC logs
     # so it's easier to forward logs for diagnostics/debugging purposes...
@@ -393,7 +387,7 @@
             logger.log(channel, indent+line)
             indent=indent_after
 
-def dispatch_rpc(service_name, method, params, auth):
+def dispatch_rpc(service_name, method, params):
     """ Handle a RPC call.
 
     This is pure Python code, the actual marshalling (from/to XML-RPC or
@@ -408,7 +402,7 @@
             _log('service', tuple(replace_request_password(params)), depth=None, fn='%s.%s'%(service_name,method))
         if logger.isEnabledFor(logging.DEBUG_RPC):
             start_time = time.time()
-        result = ExportService.getService(service_name).dispatch(method, auth, params)
+        result = ExportService.getService(service_name).dispatch(method, params)
         if logger.isEnabledFor(logging.DEBUG_RPC):
             end_time = time.time()
         if not logger.isEnabledFor(logging.DEBUG_RPC_ANSWER):
@@ -416,13 +410,24 @@
         _log('execution time', '%.3fs' % (end_time - start_time), channel=logging.DEBUG_RPC_ANSWER)
         _log('result', result, channel=logging.DEBUG_RPC_ANSWER)
         return result
+    except openerp.exceptions.AccessError:
+        raise
+    except openerp.exceptions.AccessDenied:
+        raise
+    except openerp.exceptions.Warning:
+        raise
+    except openerp.exceptions.DeferredException, e:
+        _log('exception', tools.exception_to_unicode(e))
+        post_mortem(e.traceback)
+        raise
     except Exception, e:
         _log('exception', tools.exception_to_unicode(e))
-        tb = getattr(e, 'traceback', sys.exc_info())
-        tb_s = "".join(traceback.format_exception(*tb))
-        if tools.config['debug_mode'] and isinstance(tb[2], types.TracebackType):
-            import pdb
-            pdb.post_mortem(tb[2])
-        raise OpenERPDispatcherException(e, tb_s)
+        post_mortem(sys.exc_info())
+        raise
+
+def post_mortem(info):
+    if tools.config['debug_mode'] and isinstance(info[2], types.TracebackType):
+        import pdb
+        pdb.post_mortem(info[2])
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== modified file 'openerp/osv/osv.py'
--- openerp/osv/osv.py	2011-07-29 08:38:24 +0000
+++ openerp/osv/osv.py	2011-09-27 16:50:58 +0000
@@ -30,14 +30,10 @@
 from openerp.tools.func import wraps
 from openerp.tools.translate import translate
 from openerp.osv.orm import MetaModel
-
-
-class except_osv(Exception):
-    def __init__(self, name, value, exc_type='warning'):
-        self.name = name
-        self.exc_type = exc_type
-        self.value = value
-        self.args = (exc_type, name)
+import openerp.exceptions
+
+# For backward compatibility
+except_osv = openerp.exceptions.Warning
 
 service = None
 
@@ -121,7 +117,7 @@
                     self.logger.debug("AccessError", exc_info=True)
                 netsvc.abort_response(1, inst.name, 'warning', inst.value)
             except except_osv, inst:
-                netsvc.abort_response(1, inst.name, inst.exc_type, inst.value)
+                netsvc.abort_response(1, inst.name, 'warning', inst.value)
             except IntegrityError, inst:
                 osv_pool = pooler.get_pool(dbname)
                 for key in osv_pool._sql_error.keys():

=== modified file 'openerp/service/__init__.py'
--- openerp/service/__init__.py	2011-09-25 15:02:32 +0000
+++ openerp/service/__init__.py	2011-09-27 16:50:58 +0000
@@ -57,7 +57,6 @@
 
     # Initialize the HTTP stack.
     #http_server.init_servers()
-    #http_server.init_xmlrpc()
     #http_server.init_static_http()
     netrpc_server.init_servers()
 

=== modified file 'openerp/service/http_server.py'
--- openerp/service/http_server.py	2011-09-25 01:20:39 +0000
+++ openerp/service/http_server.py	2011-09-27 16:50:58 +0000
@@ -64,53 +64,6 @@
 except ImportError:
     class SSLError(Exception): pass
 
-class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer):
-    """ A threaded httpd server, with all the necessary functionality for us.
-
-        It also inherits the xml-rpc dispatcher, so that some xml-rpc functions
-        will be available to the request handler
-    """
-    encoding = None
-    allow_none = False
-    allow_reuse_address = 1
-    _send_traceback_header = False
-    i = 0
-
-    def __init__(self, addr, requestHandler, proto='http',
-                 logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
-        self.logRequests = logRequests
-
-        SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
-        HTTPServer.__init__(self, addr, requestHandler)
-        
-        self.numThreads = 0
-        self.proto = proto
-        self.__threadno = 0
-
-        # [Bug #1222790] If possible, set close-on-exec flag; if a
-        # method spawns a subprocess, the subprocess shouldn't have
-        # the listening socket open.
-        if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
-            flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
-            flags |= fcntl.FD_CLOEXEC
-            fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
-
-    def handle_error(self, request, client_address):
-        """ Override the error handler
-        """
-        
-        logging.getLogger("init").exception("Server error in request from %s:" % (client_address,))
-
-    def _mark_start(self, thread):
-        self.numThreads += 1
-
-    def _mark_end(self, thread):
-        self.numThreads -= 1
-
-
-    def _get_next_name(self):
-        self.__threadno += 1
-        return 'http-client-%d' % self.__threadno
 class HttpLogHandler:
     """ helper class for uniform log handling
     Please define self._logger at each class that is derived from this
@@ -129,136 +82,6 @@
     def log_request(self, code='-', size='-'):
         self._logger.log(netsvc.logging.DEBUG_RPC, '"%s" %s %s',
                         self.requestline, str(code), str(size))
-    
-class MultiHandler2(HttpLogHandler, MultiHTTPHandler):
-    _logger = logging.getLogger('http')
-
-
-class SecureMultiHandler2(HttpLogHandler, SecureMultiHTTPHandler):
-    _logger = logging.getLogger('https')
-
-    def getcert_fnames(self):
-        tc = tools.config
-        fcert = tc.get('secure_cert_file', 'server.cert')
-        fkey = tc.get('secure_pkey_file', 'server.key')
-        return (fcert,fkey)
-
-class BaseHttpDaemon(threading.Thread, netsvc.Server):
-    _RealProto = '??'
-
-    def __init__(self, interface, port, handler):
-        threading.Thread.__init__(self, name='%sDaemon-%d'%(self._RealProto, port))
-        netsvc.Server.__init__(self)
-        self.__port = port
-        self.__interface = interface
-
-        try:
-            self.server = ThreadedHTTPServer((interface, port), handler, proto=self._RealProto)
-            self.server.logRequests = True
-            self.server.timeout = self._busywait_timeout
-            logging.getLogger("web-services").info(
-                        "starting %s service at %s port %d" %
-                        (self._RealProto, interface or '0.0.0.0', port,))
-        except Exception, e:
-            logging.getLogger("httpd").exception("Error occured when starting the server daemon.")
-            raise
-
-    @property
-    def socket(self):
-        return self.server.socket
-
-    def attach(self, path, gw):
-        pass
-
-    def stop(self):
-        self.running = False
-        self._close_socket()
-
-    def run(self):
-        self.running = True
-        while self.running:
-            try:
-                self.server.handle_request()
-            except (socket.error, select.error), e:
-                if self.running or e.args[0] != errno.EBADF:
-                    raise
-        return True
-
-    def stats(self):
-        res = "%sd: " % self._RealProto + ((self.running and "running") or  "stopped")
-        if self.server:
-            res += ", %d threads" % (self.server.numThreads,)
-        return res
-
-# No need for these two classes: init_server() below can initialize correctly
-# directly the BaseHttpDaemon class.
-class HttpDaemon(BaseHttpDaemon):
-    _RealProto = 'HTTP'
-    def __init__(self, interface, port):
-        super(HttpDaemon, self).__init__(interface, port,
-                                         handler=MultiHandler2)
-
-class HttpSDaemon(BaseHttpDaemon):
-    _RealProto = 'HTTPS'
-    def __init__(self, interface, port):
-        try:
-            super(HttpSDaemon, self).__init__(interface, port,
-                                              handler=SecureMultiHandler2)
-        except SSLError, e:
-            logging.getLogger('httpsd').exception( \
-                        "Can not load the certificate and/or the private key files")
-            raise
-
-httpd = None
-httpsd = None
-
-def init_servers():
-    global httpd, httpsd
-    if tools.config.get('xmlrpc'):
-        httpd = HttpDaemon(tools.config.get('xmlrpc_interface', ''),
-                           int(tools.config.get('xmlrpc_port', 8069)))
-
-    if tools.config.get('xmlrpcs'):
-        httpsd = HttpSDaemon(tools.config.get('xmlrpcs_interface', ''),
-                             int(tools.config.get('xmlrpcs_port', 8071)))
-
-import SimpleXMLRPCServer
-class XMLRPCRequestHandler(FixSendError,HttpLogHandler,SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
-    rpc_paths = []
-    protocol_version = 'HTTP/1.1'
-    _logger = logging.getLogger('xmlrpc')
-
-    def _dispatch(self, method, params):
-        try:
-            service_name = self.path.split("/")[-1]
-            auth = getattr(self, 'auth_provider', None)
-            return netsvc.dispatch_rpc(service_name, method, params, auth)
-        except netsvc.OpenERPDispatcherException, e:
-            raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback)
-
-    def handle(self):
-        pass
-
-    def finish(self):
-        pass
-
-    def setup(self):
-        self.connection = dummyconn()
-        self.rpc_paths = map(lambda s: '/%s' % s, netsvc.ExportService._services.keys())
-
-
-def init_xmlrpc():
-    if tools.config.get('xmlrpc', False):
-        # Example of http file serving:
-        # reg_http_service('/test/', HTTPHandler)
-        reg_http_service('/xmlrpc/', XMLRPCRequestHandler)
-        logging.getLogger("web-services").info("Registered XML-RPC over HTTP")
-
-    if tools.config.get('xmlrpcs', False) \
-            and not tools.config.get('xmlrpc', False):
-        # only register at the secure server
-        reg_http_service('/xmlrpc/', XMLRPCRequestHandler, secure_only=True)
-        logging.getLogger("web-services").info("Registered XML-RPC over HTTPS only")
 
 class StaticHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler):
     _logger = logging.getLogger('httpd')

=== modified file 'openerp/service/netrpc_server.py'
--- openerp/service/netrpc_server.py	2011-09-25 01:42:43 +0000
+++ openerp/service/netrpc_server.py	2011-09-27 16:50:58 +0000
@@ -65,21 +65,13 @@
             except socket.timeout:
                 #terminate this channel because other endpoint is gone
                 break
-            except netsvc.OpenERPDispatcherException, e:
-                try:
-                    new_e = Exception(tools.exception_to_unicode(e.exception)) # avoid problems of pickeling
-                    logging.getLogger('web-services').debug("netrpc: rpc-dispatching exception", exc_info=True)
-                    ts.mysend(new_e, exception=True, traceback=e.traceback)
-                except Exception:
-                    #terminate this channel if we can't properly send back the error
-                    logging.getLogger('web-services').exception("netrpc: cannot deliver exception message to client")
-                    break
             except Exception, e:
                 try:
+                    new_e = Exception(tools.exception_to_unicode(e)) # avoid problems of pickeling
                     tb = getattr(e, 'traceback', sys.exc_info())
                     tb_s = "".join(traceback.format_exception(*tb))
                     logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
-                    ts.mysend(e, exception=True, traceback=tb_s)
+                    ts.mysend(new_e, exception=True, traceback=tb_s)
                     break
                 except Exception, ex:
                     #terminate this channel if we can't properly send back the error

=== modified file 'openerp/service/security.py'
--- openerp/service/security.py	2011-06-23 09:04:57 +0000
+++ openerp/service/security.py	2011-09-27 16:50:58 +0000
@@ -19,18 +19,12 @@
 #
 ##############################################################################
 
+import openerp.exceptions
 import openerp.pooler as pooler
 import openerp.tools as tools
 
 #.apidoc title: Authentication helpers
 
-class ExceptionNoTb(Exception):
-    """ When rejecting a password, hide the traceback
-    """
-    def __init__(self, msg):
-        super(ExceptionNoTb, self).__init__(msg)
-        self.traceback = ('','','')
-
 def login(db, login, password):
     pool = pooler.get_pool(db)
     user_obj = pool.get('res.users')
@@ -40,7 +34,7 @@
     if passwd == tools.config['admin_passwd']:
         return True
     else:
-        raise ExceptionNoTb('AccessDenied: Invalid super administrator password.')
+        raise openerp.exceptions.AccessDenied()
 
 def check(db, uid, passwd):
     pool = pooler.get_pool(db)

=== modified file 'openerp/service/web_services.py'
--- openerp/service/web_services.py	2011-09-08 12:38:18 +0000
+++ openerp/service/web_services.py	2011-09-27 16:50:58 +0000
@@ -38,6 +38,7 @@
 import openerp.sql_db as sql_db
 import openerp.tools as tools
 import openerp.modules
+import openerp.exceptions
 
 #.apidoc title: Exported Service methods
 #.apidoc module-mods: member-order: bysource
@@ -93,7 +94,7 @@
 
         self._pg_psw_env_var_is_set = False # on win32, pg_dump need the PGPASSWORD env var
 
-    def dispatch(self, method, auth, params):
+    def dispatch(self, method, params):
         if method in [ 'create', 'get_progress', 'drop', 'dump',
             'restore', 'rename',
             'change_admin_password', 'migrate_databases',
@@ -161,7 +162,7 @@
                 self.actions.pop(id)
                 return (1.0, users)
             else:
-                e = self.actions[id]['exception']
+                e = self.actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
                 self.actions.pop(id)
                 raise Exception, e
 
@@ -302,7 +303,7 @@
 
     def exp_list(self, document=False):
         if not tools.config['list_db'] and not document:
-            raise Exception('AccessDenied')
+            raise openerp.exceptions.AccessDenied()
 
         db = sql_db.db_connect('template1')
         cr = db.cursor()
@@ -356,7 +357,7 @@
             except except_orm, inst:
                 netsvc.abort_response(1, inst.name, 'warning', inst.value)
             except except_osv, inst:
-                netsvc.abort_response(1, inst.name, inst.exc_type, inst.value)
+                netsvc.abort_response(1, inst.name, 'warning', inst.value)
             except Exception:
                 import traceback
                 tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
@@ -368,20 +369,14 @@
     def __init__(self,name="common"):
         netsvc.ExportService.__init__(self,name)
 
-    def dispatch(self, method, auth, params):
+    def dispatch(self, method, params):
         logger = netsvc.Logger()
         if method == 'login':
-            # At this old dispatcher, we do NOT update the auth proxy
             res = security.login(params[0], params[1], params[2])
             msg = res and 'successful login' or 'bad login or password'
             # TODO log the client ip address..
             logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, params[1], params[0].lower()))
             return res or False
-        elif method == 'logout':
-            if auth:
-                auth.logout(params[1]) # TODO I didn't see any AuthProxy implementing this method.
-            logger.notifyChannel("web-service", netsvc.LOG_INFO,'Logout %s from database %s'%(login,db))
-            return True
         elif method in ['about', 'timezone_get', 'get_server_environment',
                         'login_message','get_stats', 'check_connectivity',
                         'list_http_services']:
@@ -562,7 +557,7 @@
     def __init__(self, name="object"):
         netsvc.ExportService.__init__(self,name)
 
-    def dispatch(self, method, auth, params):
+    def dispatch(self, method, params):
         (db, uid, passwd ) = params[0:3]
         params = params[3:]
         if method == 'obj_list':
@@ -595,7 +590,7 @@
         self.wiz_name = {}
         self.wiz_uid = {}
 
-    def dispatch(self, method, auth, params):
+    def dispatch(self, method, params):
         (db, uid, passwd ) = params[0:3]
         params = params[3:]
         if method not in ['execute','create']:
@@ -628,9 +623,9 @@
             if self.wiz_uid[wiz_id] == uid:
                 return self._execute(db, uid, wiz_id, datas, action, context)
             else:
-                raise Exception, 'AccessDenied'
+                raise openerp.exceptions.AccessDenied()
         else:
-            raise Exception, 'WizardNotFound'
+            raise openerp.exceptions.Warning('Wizard not found.')
 
 #
 # TODO: set a maximum report number per user to avoid DOS attacks
@@ -639,12 +634,6 @@
 #     False -> True
 #
 
-class ExceptionWithTraceback(Exception):
-    def __init__(self, msg, tb):
-        self.message = msg
-        self.traceback = tb
-        self.args = (msg, tb)
-
 class report_spool(netsvc.ExportService):
     def __init__(self, name='report'):
         netsvc.ExportService.__init__(self, name)
@@ -652,7 +641,7 @@
         self.id = 0
         self.id_protect = threading.Semaphore()
 
-    def dispatch(self, method, auth, params):
+    def dispatch(self, method, params):
         (db, uid, passwd ) = params[0:3]
         params = params[3:]
         if method not in ['report', 'report_get', 'render_report']:
@@ -683,7 +672,7 @@
             (result, format) = obj.create(cr, uid, ids, datas, context)
             if not result:
                 tb = sys.exc_info()
-                self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb)
+                self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
             self._reports[id]['result'] = result
             self._reports[id]['format'] = format
             self._reports[id]['state'] = True
@@ -695,9 +684,9 @@
             logger.notifyChannel('web-services', netsvc.LOG_ERROR,
                     'Exception: %s\n%s' % (str(exception), tb_s))
             if hasattr(exception, 'name') and hasattr(exception, 'value'):
-                self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value))
+                self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
             else:
-                self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb)
+                self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
             self._reports[id]['state'] = True
         cr.commit()
         cr.close()
@@ -726,7 +715,7 @@
                 (result, format) = obj.create(cr, uid, ids, datas, context)
                 if not result:
                     tb = sys.exc_info()
-                    self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb)
+                    self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
                 self._reports[id]['result'] = result
                 self._reports[id]['format'] = format
                 self._reports[id]['state'] = True
@@ -738,9 +727,9 @@
                 logger.notifyChannel('web-services', netsvc.LOG_ERROR,
                         'Exception: %s\n%s' % (str(exception), tb_s))
                 if hasattr(exception, 'name') and hasattr(exception, 'value'):
-                    self._reports[id]['exception'] = ExceptionWithTraceback(tools.ustr(exception.name), tools.ustr(exception.value))
+                    self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
                 else:
-                    self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb)
+                    self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
                 self._reports[id]['state'] = True
             cr.commit()
             cr.close()

=== modified file 'openerp/service/websrv_lib.py'
--- openerp/service/websrv_lib.py	2011-09-09 12:28:56 +0000
+++ openerp/service/websrv_lib.py	2011-09-27 16:50:58 +0000
@@ -232,305 +232,3 @@
         """
         return opts
 
-class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler):
-    """ this is a multiple handler, that will dispatch each request
-        to a nested handler, iff it matches
-
-        The handler will also have *one* dict of authentication proxies,
-        groupped by their realm.
-    """
-
-    protocol_version = "HTTP/1.1"
-    default_request_version = "HTTP/0.9"    # compatibility with py2.5
-
-    auth_required_msg = """ <html><head><title>Authorization required</title></head>
-    <body>You must authenticate to use this service</body><html>\r\r"""
-
-    def __init__(self, request, client_address, server):
-        self.in_handlers = {}
-        SocketServer.StreamRequestHandler.__init__(self,request,client_address,server)
-        self.log_message("MultiHttpHandler init for %s" %(str(client_address)))
-
-    def _handle_one_foreign(self, fore, path):
-        """ This method overrides the handle_one_request for *children*
-            handlers. It is required, since the first line should not be
-            read again..
-
-        """
-        fore.raw_requestline = "%s %s %s\n" % (self.command, path, self.version)
-        if not fore.parse_request(): # An error code has been sent, just exit
-            return
-        if fore.headers.status:
-            self.log_error("Parse error at headers: %s", fore.headers.status)
-            self.close_connection = 1
-            self.send_error(400,"Parse error at HTTP headers")
-            return
-
-        self.request_version = fore.request_version
-        if hasattr(fore, 'auth_provider'):
-            try:
-                fore.auth_provider.checkRequest(fore,path)
-            except AuthRequiredExc,ae:
-                # Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the
-                # authorisation features of HTTP/1.1 
-                if self.request_version != 'HTTP/1.1' and ('Darwin/9.' not in fore.headers.get('User-Agent', '')):
-                    self.log_error("Cannot require auth at %s", self.request_version)
-                    self.send_error(403)
-                    return
-                self._get_ignore_body(fore) # consume any body that came, not loose sync with input
-                self.send_response(401,'Authorization required')
-                self.send_header('WWW-Authenticate','%s realm="%s"' % (ae.atype,ae.realm))
-                self.send_header('Connection', 'keep-alive')
-                self.send_header('Content-Type','text/html')
-                self.send_header('Content-Length',len(self.auth_required_msg))
-                self.end_headers()
-                self.wfile.write(self.auth_required_msg)
-                return
-            except AuthRejectedExc,e:
-                self.log_error("Rejected auth: %s" % e.args[0])
-                self.send_error(403,e.args[0])
-                self.close_connection = 1
-                return
-        mname = 'do_' + fore.command
-        if not hasattr(fore, mname):
-            if fore.command == 'OPTIONS':
-                self.do_OPTIONS()
-                return
-            self.send_error(501, "Unsupported method (%r)" % fore.command)
-            return
-        fore.close_connection = 0
-        method = getattr(fore, mname)
-        try:
-            method()
-        except (AuthRejectedExc, AuthRequiredExc):
-            raise
-        except Exception, e:
-            if hasattr(self, 'log_exception'):
-                self.log_exception("Could not run %s", mname)
-            else:
-                self.log_error("Could not run %s: %s", mname, e)
-            self.send_error(500, "Internal error")
-            # may not work if method has already sent data
-            fore.close_connection = 1
-            self.close_connection = 1
-            if hasattr(fore, '_flush'):
-                fore._flush()
-            return
-        
-        if fore.close_connection:
-            # print "Closing connection because of handler"
-            self.close_connection = fore.close_connection
-        if hasattr(fore, '_flush'):
-            fore._flush()
-
-
-    def parse_rawline(self):
-        """Parse a request (internal).
-
-        The request should be stored in self.raw_requestline; the results
-        are in self.command, self.path, self.request_version and
-        self.headers.
-
-        Return True for success, False for failure; on failure, an
-        error is sent back.
-
-        """
-        self.command = None  # set in case of error on the first line
-        self.request_version = version = self.default_request_version
-        self.close_connection = 1
-        requestline = self.raw_requestline
-        if requestline[-2:] == '\r\n':
-            requestline = requestline[:-2]
-        elif requestline[-1:] == '\n':
-            requestline = requestline[:-1]
-        self.requestline = requestline
-        words = requestline.split()
-        if len(words) == 3:
-            [command, path, version] = words
-            if version[:5] != 'HTTP/':
-                self.send_error(400, "Bad request version (%r)" % version)
-                return False
-            try:
-                base_version_number = version.split('/', 1)[1]
-                version_number = base_version_number.split(".")
-                # RFC 2145 section 3.1 says there can be only one "." and
-                #   - major and minor numbers MUST be treated as
-                #      separate integers;
-                #   - HTTP/2.4 is a lower version than HTTP/2.13, which in
-                #      turn is lower than HTTP/12.3;
-                #   - Leading zeros MUST be ignored by recipients.
-                if len(version_number) != 2:
-                    raise ValueError
-                version_number = int(version_number[0]), int(version_number[1])
-            except (ValueError, IndexError):
-                self.send_error(400, "Bad request version (%r)" % version)
-                return False
-            if version_number >= (1, 1):
-                self.close_connection = 0
-            if version_number >= (2, 0):
-                self.send_error(505,
-                          "Invalid HTTP Version (%s)" % base_version_number)
-                return False
-        elif len(words) == 2:
-            [command, path] = words
-            self.close_connection = 1
-            if command != 'GET':
-                self.log_error("Junk http request: %s", self.raw_requestline)
-                self.send_error(400,
-                                "Bad HTTP/0.9 request type (%r)" % command)
-                return False
-        elif not words:
-            return False
-        else:
-            #self.send_error(400, "Bad request syntax (%r)" % requestline)
-            return False
-        self.request_version = version
-        self.command, self.path, self.version = command, path, version
-        return True
-
-    def handle_one_request(self):
-        """Handle a single HTTP request.
-           Dispatch to the correct handler.
-        """
-        self.request.setblocking(True)
-        self.raw_requestline = self.rfile.readline()
-        if not self.raw_requestline:
-            self.close_connection = 1
-            # self.log_message("no requestline, connection closed?")
-            return
-        if not self.parse_rawline():
-            self.log_message("Could not parse rawline.")
-            return
-        # self.parse_request(): # Do NOT parse here. the first line should be the only
-        
-        if self.path == '*' and self.command == 'OPTIONS':
-            # special handling of path='*', must not use any vdir at all.
-            if not self.parse_request():
-                return
-            self.do_OPTIONS()
-            return
-        vdir = find_http_service(self.path, self.server.proto == 'HTTPS')
-        if vdir:
-            p = vdir.path
-            npath = self.path[len(p):]
-            if not npath.startswith('/'):
-                npath = '/' + npath
-
-            if not self.in_handlers.has_key(p):
-                self.in_handlers[p] = vdir.instanciate_handler(noconnection(self.request),self.client_address,self.server)
-            hnd = self.in_handlers[p]
-            hnd.rfile = self.rfile
-            hnd.wfile = self.wfile
-            self.rlpath = self.raw_requestline
-            try:
-                self._handle_one_foreign(hnd, npath)
-            except IOError, e:
-                if e.errno == errno.EPIPE:
-                    self.log_message("Could not complete request %s," \
-                            "client closed connection", self.rlpath.rstrip())
-                else:
-                    raise
-        else: # no match:
-            self.send_error(404, "Path not found: %s" % self.path)
-
-    def _get_ignore_body(self,fore):
-        if not fore.headers.has_key("content-length"):
-            return
-        max_chunk_size = 10*1024*1024
-        size_remaining = int(fore.headers["content-length"])
-        got = ''
-        while size_remaining:
-            chunk_size = min(size_remaining, max_chunk_size)
-            got = fore.rfile.read(chunk_size)
-            size_remaining -= len(got)
-
-
-class SecureMultiHTTPHandler(MultiHTTPHandler):
-    def getcert_fnames(self):
-        """ Return a pair with the filenames of ssl cert,key
-
-            Override this to direct to other filenames
-        """
-        return ('server.cert','server.key')
-
-    def setup(self):
-        import ssl
-        certfile, keyfile = self.getcert_fnames()
-        try:
-            self.connection = ssl.wrap_socket(self.request,
-                                server_side=True,
-                                certfile=certfile,
-                                keyfile=keyfile,
-                                ssl_version=ssl.PROTOCOL_SSLv23)
-            self.rfile = self.connection.makefile('rb', self.rbufsize)
-            self.wfile = self.connection.makefile('wb', self.wbufsize)
-            self.log_message("Secure %s connection from %s",self.connection.cipher(),self.client_address)
-        except Exception:
-            self.request.shutdown(socket.SHUT_RDWR)
-            raise
-
-    def finish(self):
-        # With ssl connections, closing the filehandlers alone may not
-        # work because of ref counting. We explicitly tell the socket
-        # to shutdown.
-        MultiHTTPHandler.finish(self)
-        try:
-            self.connection.shutdown(socket.SHUT_RDWR)
-        except Exception:
-            pass
-
-import threading
-class ConnThreadingMixIn:
-    """Mix-in class to handle each _connection_ in a new thread.
-
-       This is necessary for persistent connections, where multiple
-       requests should be handled synchronously at each connection, but
-       multiple connections can run in parallel.
-    """
-
-    # Decides how threads will act upon termination of the
-    # main process
-    daemon_threads = False
-
-    def _get_next_name(self):
-        return None
-
-    def _handle_request_noblock(self):
-        """Start a new thread to process the request."""
-        if not threading: # happens while quitting python
-            return
-        t = threading.Thread(name=self._get_next_name(), target=self._handle_request2)
-        if self.daemon_threads:
-            t.setDaemon (1)
-        t.start()
-
-    def _mark_start(self, thread):
-        """ Mark the start of a request thread """
-        pass
-
-    def _mark_end(self, thread):
-        """ Mark the end of a request thread """
-        pass
-
-    def _handle_request2(self):
-        """Handle one request, without blocking.
-
-        I assume that select.select has returned that the socket is
-        readable before this function was called, so there should be
-        no risk of blocking in get_request().
-        """
-        try:
-            self._mark_start(threading.currentThread())
-            request, client_address = self.get_request()
-            if self.verify_request(request, client_address):
-                try:
-                    self.process_request(request, client_address)
-                except Exception:
-                    self.handle_error(request, client_address)
-                    self.close_request(request)
-        except socket.error:
-            return
-        finally:
-            self._mark_end(threading.currentThread())
-
-#eof

=== modified file 'openerp/wsgi.py'
--- openerp/wsgi.py	2011-09-25 01:42:43 +0000
+++ openerp/wsgi.py	2011-09-27 16:50:58 +0000
@@ -36,62 +36,98 @@
 import sys
 import threading
 import time
+import traceback
 
 import openerp
+import openerp.modules
 import openerp.tools.config as config
 import service.websrv_lib as websrv_lib
 
+# XML-RPC fault codes. Some care must be taken when changing these: the
+# constants are also defined client-side and must remain in sync.
+# User code must use the exceptions defined in ``openerp.exceptions`` (not
+# create directly ``xmlrpclib.Fault`` objects).
+XML_RPC_FAULT_CODE_APPLICATION_ERROR = 1
+XML_RPC_FAULT_CODE_DEFERRED_APPLICATION_ERROR = 2
+XML_RPC_FAULT_CODE_ACCESS_DENIED = 3
+XML_RPC_FAULT_CODE_WARNING = 4
+XML_RPC_FAULT_CODE_ACCESS_ERROR = 5
+
 def xmlrpc_return(start_response, service, method, params):
-    """ Helper to call a service's method with some params, using a
-    wsgi-supplied ``start_response`` callback."""
-    # This mimics SimpleXMLRPCDispatcher._marshaled_dispatch() for exception
-    # handling.
+    """
+    Helper to call a service's method with some params, using a wsgi-supplied
+    ``start_response`` callback.
+
+    This is the place to look at to see the mapping between core exceptions
+    and XML-RPC fault codes.
+    """
+    # Map OpenERP core exceptions to XML-RPC fault codes. Specific exceptions
+    # defined in ``openerp.exceptions`` are mapped to specific fault codes;
+    # all the other exceptions are mapped to the generic
+    # XML_RPC_FAULT_CODE_APPLICATION_ERROR value.
+    # This also mimics SimpleXMLRPCDispatcher._marshaled_dispatch() for
+    # exception handling.
     try:
-        result = openerp.netsvc.dispatch_rpc(service, method, params, None) # TODO auth
+        result = openerp.netsvc.dispatch_rpc(service, method, params)
         response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None)
-    except openerp.netsvc.OpenERPDispatcherException, e:
-        fault = xmlrpclib.Fault(openerp.tools.exception_to_unicode(e.exception), e.traceback)
-        response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
-    except:
-        exc_type, exc_value, exc_tb = sys.exc_info()
-        fault = xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value))
+    except openerp.exceptions.AccessError, e:
+        fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_ERROR, str(e))
+        response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+    except openerp.exceptions.Warning, e:
+        fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_WARNING, str(e))
+        response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+    except openerp.exceptions.AccessDenied, e:
+        fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_DENIED, str(e))
+        response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+    except openerp.exceptions.DeferredException, e:
+        info = e.traceback
+        # Which one is the best ?
+        formatted_info = "".join(traceback.format_exception(*info))
+        #formatted_info = openerp.tools.exception_to_unicode(e) + '\n' + info
+        fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_DEFERRED_APPLICATION_ERROR, formatted_info)
+        response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+    except Exception, e:
+        info = sys.exc_info()
+        # Which one is the best ?
+        formatted_info = "".join(traceback.format_exception(*info))
+        #formatted_info = openerp.tools.exception_to_unicode(e) + '\n' + info
+        fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
         response = xmlrpclib.dumps(fault, allow_none=None, encoding=None)
     start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))])
     return [response]
 
 def wsgi_xmlrpc(environ, start_response):
     """ The main OpenERP WSGI handler."""
-    if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/openerp/xmlrpc'):
+    if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/openerp/6.1/xmlrpc'):
         length = int(environ['CONTENT_LENGTH'])
         data = environ['wsgi.input'].read(length)
 
         params, method = xmlrpclib.loads(data)
 
-        path = environ['PATH_INFO'][len('/openerp/xmlrpc'):]
+        path = environ['PATH_INFO'][len('/openerp/6.1/xmlrpc'):]
         if path.startswith('/'): path = path[1:]
         if path.endswith('/'): p = path[:-1]
         path = path.split('/')
 
-        # All routes are hard-coded. Need a way to register addons-supplied handlers.
+        # All routes are hard-coded.
 
         # No need for a db segment.
         if len(path) == 1:
             service = path[0]
 
             if service == 'common':
-                if method in ('create_database', 'list', 'server_version'):
-                    return xmlrpc_return(start_response, 'db', method, params)
-                else:
-                    return xmlrpc_return(start_response, 'common', method, params)
+                if method in ('server_version',):
+                    service = 'db'
+            return xmlrpc_return(start_response, service, method, params)
+
         # A db segment must be given.
         elif len(path) == 2:
             service, db_name = path
             params = (db_name,) + params
 
             if service == 'model':
-                return xmlrpc_return(start_response, 'object', method, params)
-            elif service == 'report':
-                return xmlrpc_return(start_response, 'report', method, params)
+                service = 'object'
+            return xmlrpc_return(start_response, service, method, params)
 
         # TODO the body has been read, need to raise an exception (not return None).
 

=== modified file 'tests/test_xmlrpc.py'
--- tests/test_xmlrpc.py	2011-09-23 13:34:08 +0000
+++ tests/test_xmlrpc.py	2011-09-27 16:50:58 +0000
@@ -27,6 +27,10 @@
 db_proxy_60 = None
 object_proxy_60 = None
 
+common_proxy_61 = None
+db_proxy_61 = None
+model_proxy_61 = None
+
 def setUpModule():
     """
     Start the OpenERP server similary to the openerp-server script and
@@ -40,19 +44,32 @@
     global db_proxy_60
     global object_proxy_60
 
+    global common_proxy_61
+    global db_proxy_61
+    global model_proxy_61
+
     # Use the old (pre 6.1) API.
     url = 'http://%s:%d/xmlrpc/' % (HOST, PORT)
     common_proxy_60 = xmlrpclib.ServerProxy(url + 'common')
     db_proxy_60 = xmlrpclib.ServerProxy(url + 'db')
     object_proxy_60 = xmlrpclib.ServerProxy(url + 'object')
 
+    # Use the new (6.1) API.
+    url = 'http://%s:%d/openerp/6.1/xmlrpc/' % (HOST, PORT)
+    common_proxy_61 = xmlrpclib.ServerProxy(url + 'common')
+    db_proxy_61 = xmlrpclib.ServerProxy(url + 'db')
+    model_proxy_61 = xmlrpclib.ServerProxy(url + 'model/' + DB)
+
+    # Mmm need to make sure the server is listening for XML-RPC requests.
+    time.sleep(10)
+
 def tearDownModule():
     """ Shutdown the OpenERP server similarly to a single ctrl-c. """
     openerp.service.stop_services()
 
 class test_xmlrpc(unittest2.TestCase):
 
-    def test_xmlrpc_create_database_polling(self):
+    def test_00_xmlrpc_create_database_polling(self):
         """
         Simulate a OpenERP client requesting the creation of a database and
         polling the server until the creation is complete.
@@ -80,6 +97,13 @@
             'ir.model', 'search', [], {})
         assert ids
 
+    def test_xmlrpc_61_ir_model_search(self):
+        """ Try a search on the object service. """
+        ids = model_proxy_61.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [])
+        assert ids
+        ids = model_proxy_61.execute(ADMIN_USER_ID, ADMIN_PASSWORD, 'ir.model', 'search', [], {})
+        assert ids
+
 if __name__ == '__main__':
     unittest2.main()
 

_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help   : https://help.launchpad.net/ListHelp

Reply via email to