From f82c6503b7a194010f0a0bf3d29790bef04b898e Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 30 Sep 2011 10:50:12 +0200 Subject: [PATCH 1/7] [FIX] xmlrpc: handle old/new exceptions with old/new clients. bzr revid: vmt@openerp.com-20110930085012-pqzzb3qwvdwu9hxy --- openerp/exceptions.py | 3 +-- openerp/netsvc.py | 5 +--- openerp/osv/osv.py | 12 +++++---- openerp/wsgi.py | 63 +++++++++++++++++++++++++++++++++++++------ 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/openerp/exceptions.py b/openerp/exceptions.py index d12910c3448..fbf52c7f3a9 100644 --- a/openerp/exceptions.py +++ b/openerp/exceptions.py @@ -33,7 +33,7 @@ class Warning(Exception): class AccessDenied(Exception): """ Login/password error. No message, no traceback. """ def __init__(self): - super(AccessDenied, self).__init__('AccessDenied.') + super(AccessDenied, self).__init__('Access denied.') self.traceback = ('', '', '') class AccessError(Exception): @@ -52,6 +52,5 @@ class DeferredException(Exception): def __init__(self, msg, tb): self.message = msg self.traceback = tb - self.args = (msg, tb) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 1d0b2a25726..1d4bf0c2b58 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -63,10 +63,7 @@ def close_socket(sock): 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 openerp.exceptions.Warning(details) + raise openerp.osv.osv.except_osv(description, detail) class Service(object): """ Base class for *Local* services diff --git a/openerp/osv/osv.py b/openerp/osv/osv.py index 821c52a55ef..b3f2a045589 100644 --- a/openerp/osv/osv.py +++ b/openerp/osv/osv.py @@ -34,8 +34,12 @@ from openerp.tools.translate import translate from openerp.osv.orm import MetaModel, Model, TransientModel, AbstractModel import openerp.exceptions -# For backward compatibility -except_osv = openerp.exceptions.Warning +# Deprecated. +class except_osv(Exception): + def __init__(self, name, value): + self.name = name + self.value = value + self.args = (name, value) service = None @@ -115,9 +119,7 @@ class object_proxy(): raise except_osv('Database not ready', 'Currently, this database is not fully loaded and can not be used.') return f(self, dbname, *args, **kwargs) except orm.except_orm, inst: - if inst.name == 'AccessError': - self.logger.debug("AccessError", exc_info=True) - netsvc.abort_response(1, inst.name, 'warning', inst.value) + raise except_osv(inst.name, inst.value) except except_osv: raise except IntegrityError, inst: diff --git a/openerp/wsgi.py b/openerp/wsgi.py index e5aa378bf04..b334c72b3ef 100644 --- a/openerp/wsgi.py +++ b/openerp/wsgi.py @@ -48,12 +48,15 @@ import service.websrv_lib as websrv_lib # User code must use the exceptions defined in ``openerp.exceptions`` (not # create directly ``xmlrpclib.Fault`` objects). XML_RPC_FAULT_CODE_APPLICATION_ERROR = 1 +# Unused, deferred errors are indistinguishable from normal application +# errors. We keep them so we can use the word 'indistinguishable' twice +# in the same comment. XML_RPC_FAULT_CODE_DEFERRED_APPLICATION_ERROR = 2 XML_RPC_FAULT_CODE_ACCESS_DENIED = 3 XML_RPC_FAULT_CODE_ACCESS_ERROR = 4 XML_RPC_FAULT_CODE_WARNING = 5 -def xmlrpc_return(start_response, service, method, params): +def xmlrpc_return(start_response, service, method, params, legacy_exceptions=False): """ Helper to call a service's method with some params, using a wsgi-supplied ``start_response`` callback. @@ -70,6 +73,20 @@ def xmlrpc_return(start_response, service, method, params): try: result = openerp.netsvc.dispatch_rpc(service, method, params) response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None) + except Exception, e: + if legacy_exceptions: + response = xmlrpc_handle_exception_legacy(e) + else: + response = xmlrpc_handle_exception(e) + start_response("200 OK", [('Content-Type','text/xml'), ('Content-Length', str(len(response)))]) + return [response] + +def xmlrpc_handle_exception(e): + try: + raise e + except openerp.osv.osv.except_osv, e: # legacy + fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_WARNING, openerp.tools.ustr(e.value)) + 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) @@ -84,17 +101,47 @@ def xmlrpc_return(start_response, service, method, params): # 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) + fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info) + response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) + except Exception, e: + if hasattr(e, 'message') and e.message == 'AccessDenied': # legacy + fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_DENIED, str(e)) + response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) + else: + 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) + return response + +def xmlrpc_handle_exception_legacy(e): + try: + raise e + except openerp.osv.osv.except_osv, e: + fault = xmlrpclib.Fault('warning -- ' + e.name + '\n\n' + e.value, '') + response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) + except openerp.exceptions.Warning, e: + fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '') + response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) + except openerp.exceptions.AccessError, e: + fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '') + response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) + except openerp.exceptions.AccessDenied, e: + fault = xmlrpclib.Fault('AccessDenied', str(e)) + response = xmlrpclib.dumps(fault, allow_none=False, encoding=None) + except openerp.exceptions.DeferredException, e: + info = e.traceback + formatted_info = "".join(traceback.format_exception(*info)) + fault = xmlrpclib.Fault(openerp.tools.ustr(e.message), 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) + fault = xmlrpclib.Fault(openerp.tools.exception_to_unicode(e), 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] + return response def wsgi_xmlrpc(environ, start_response): """ The main OpenERP WSGI handler.""" @@ -138,7 +185,7 @@ def legacy_wsgi_xmlrpc(environ, start_response): path = environ['PATH_INFO'][len('/xmlrpc/'):] # expected to be one of db, object, ... params, method = xmlrpclib.loads(data) - return xmlrpc_return(start_response, path, method, params) + return xmlrpc_return(start_response, path, method, params, True) def wsgi_jsonrpc(environ, start_response): pass From d273e72f35fe560981639a999e5c55521b7399e7 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 30 Sep 2011 11:37:55 +0200 Subject: [PATCH 2/7] [IMP] xmlrpc/exceptions: added a model and a view to exercise exception handling. bzr revid: vmt@openerp.com-20110930093755-ee6569l3bhgrervp --- tests/addons/test_exceptions/__init__.py | 3 + tests/addons/test_exceptions/__openerp__.py | 15 +++++ tests/addons/test_exceptions/models.py | 33 ++++++++++ tests/addons/test_exceptions/view.xml | 68 +++++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 tests/addons/test_exceptions/__init__.py create mode 100644 tests/addons/test_exceptions/__openerp__.py create mode 100644 tests/addons/test_exceptions/models.py create mode 100644 tests/addons/test_exceptions/view.xml diff --git a/tests/addons/test_exceptions/__init__.py b/tests/addons/test_exceptions/__init__.py new file mode 100644 index 00000000000..fe4487156b1 --- /dev/null +++ b/tests/addons/test_exceptions/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +import models +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/tests/addons/test_exceptions/__openerp__.py b/tests/addons/test_exceptions/__openerp__.py new file mode 100644 index 00000000000..bac853e301a --- /dev/null +++ b/tests/addons/test_exceptions/__openerp__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'test-exceptions', + 'version': '0.1', + 'category': 'Tests', + 'description': """A module to generate exceptions.""", + 'author': 'OpenERP SA', + 'maintainer': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['base'], + 'data': ['view.xml'], + 'installable': True, + 'active': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/tests/addons/test_exceptions/models.py b/tests/addons/test_exceptions/models.py new file mode 100644 index 00000000000..45f0e6dbd47 --- /dev/null +++ b/tests/addons/test_exceptions/models.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +import openerp + +class m(openerp.osv.osv.Model): + """ This model exposes a few methods that will raise the different + exceptions that must be handled by the server (and its RPC layer) + and the clients. + """ + _name = 'test.exceptions.model' + + def generate_except_osv(self, cr, uid, ids, context=None): + # title is ignored in the new (6.1) exceptions + raise openerp.osv.osv.except_osv('title', 'description') + + def generate_except_orm(self, cr, uid, ids, context=None): + # title is ignored in the new (6.1) exceptions + raise openerp.osv.orm.except_orm('title', 'description') + + def generate_warning(self, cr, uid, ids, context=None): + raise openerp.exceptions.Warning('description') + + def generate_access_denied(self, cr, uid, ids, context=None): + raise openerp.exceptions.AccessDenied() + + def generate_access_error(self, cr, uid, ids, context=None): + raise openerp.exceptions.AccessError('description') + + def generate_exc_access_denied(self, cr, uid, ids, context=None): + raise Exception('AccessDenied') + + def generate_undefined(self, cr, uid, ids, context=None): + self.surely_undefined_sumbol +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/tests/addons/test_exceptions/view.xml b/tests/addons/test_exceptions/view.xml new file mode 100644 index 00000000000..296f862aec4 --- /dev/null +++ b/tests/addons/test_exceptions/view.xml @@ -0,0 +1,68 @@ + + + + + + Test exceptions + test.exceptions.model + form + +
+