Antony Lesuisse (OpenERP) has proposed merging
lp:~openerp-dev/openobject-server/trunk-xmlrpc-1-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-1-vmt/+merge/77774
xmlrpc versioning and old style api exceptions
--
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-xmlrpc-1-vmt/+merge/77774
Your team OpenERP R&D Team is subscribed to branch
lp:~openerp-dev/openobject-server/trunk-xmlrpc-1-vmt.
=== modified file 'openerp/exceptions.py'
--- openerp/exceptions.py 2011-09-28 22:05:39 +0000
+++ openerp/exceptions.py 2011-10-01 00:54:24 +0000
@@ -33,7 +33,7 @@
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 @@
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-29 00:21:57 +0000
+++ openerp/netsvc.py 2011-10-01 00:54:24 +0000
@@ -63,10 +63,7 @@
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
=== modified file 'openerp/osv/osv.py'
--- openerp/osv/osv.py 2011-09-29 22:49:19 +0000
+++ openerp/osv/osv.py 2011-10-01 00:54:24 +0000
@@ -34,8 +34,12 @@
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 @@
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:
=== modified file 'openerp/service/web_services.py'
--- openerp/service/web_services.py 2011-09-29 00:21:57 +0000
+++ openerp/service/web_services.py 2011-10-01 00:54:24 +0000
@@ -49,6 +49,8 @@
procedures to be called. Each method has its own arguments footprint.
"""
+RPC_VERSION_1 = {'server_version': '6.1', 'protocol_version': 1}
+
# This should be moved to openerp.modules.db, along side initialize().
def _initialize_db(serv, id, db_name, demo, lang, user_password):
cr = None
@@ -377,7 +379,7 @@
return res or False
elif method in ['about', 'timezone_get', 'get_server_environment',
'login_message','get_stats', 'check_connectivity',
- 'list_http_services']:
+ 'list_http_services', 'version']:
pass
elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time', 'get_sqlcount']:
passwd = params[0]
@@ -389,6 +391,9 @@
fn = getattr(self, 'exp_'+method)
return fn(*params)
+ def exp_version(self):
+ return RPC_VERSION_1
+
def exp_about(self, extended=False):
"""Return information about the OpenERP Server.
=== modified file 'openerp/wsgi.py'
--- openerp/wsgi.py 2011-09-30 09:37:49 +0000
+++ openerp/wsgi.py 2011-10-01 00:54:24 +0000
@@ -47,13 +47,23 @@
# 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_CLIENT_ERROR = 1 # again, indistinguishable from app. error.
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):
+# The new (6.1) versioned RPC paths.
+XML_RPC_PATH = '/openerp/xmlrpc'
+XML_RPC_PATH_1 = '/openerp/xmlrpc/1'
+JSON_RPC_PATH = '/openerp/jsonrpc'
+JSON_RPC_PATH_1 = '/openerp/jsonrpc/1'
+
+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,41 +80,81 @@
try:
result = openerp.netsvc.dispatch_rpc(service, method, params)
response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None)
- except openerp.exceptions.Warning, e:
+ 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):
+ if isinstance(e, openerp.osv.osv.except_osv): # legacy
+ fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_WARNING, openerp.tools.ustr(e.value))
+ response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+ elif isinstance(e, openerp.exceptions.Warning):
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_WARNING, str(e))
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
- except openerp.exceptions.AccessError, e:
+ elif isinstance (e, openerp.exceptions.AccessError):
fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_ACCESS_ERROR, str(e))
response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
- except openerp.exceptions.AccessDenied, e:
+ elif isinstance(e, openerp.exceptions.AccessDenied):
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:
+ elif isinstance(e, openerp.exceptions.DeferredException):
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:
+ fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
+ response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+ else:
+ 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):
+ if isinstance(e, openerp.osv.osv.except_osv):
+ fault = xmlrpclib.Fault('warning -- ' + e.name + '\n\n' + e.value, '')
+ response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+ elif isinstance(e, openerp.exceptions.Warning):
+ fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '')
+ response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+ elif isinstance(e, openerp.exceptions.AccessError):
+ fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '')
+ response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+ elif isinstance(e, openerp.exceptions.AccessDenied):
+ fault = xmlrpclib.Fault('AccessDenied', str(e))
+ response = xmlrpclib.dumps(fault, allow_none=False, encoding=None)
+ elif isinstance(e, openerp.exceptions.DeferredException):
+ 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)
+ 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)
+ 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):
+def wsgi_xmlrpc_1(environ, start_response):
""" The main OpenERP WSGI handler."""
- if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/openerp/6.1/xmlrpc'):
+ if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith(XML_RPC_PATH_1):
length = int(environ['CONTENT_LENGTH'])
data = environ['wsgi.input'].read(length)
params, method = xmlrpclib.loads(data)
- path = environ['PATH_INFO'][len('/openerp/6.1/xmlrpc'):]
+ path = environ['PATH_INFO'][len(XML_RPC_PATH_1):]
if path.startswith('/'): path = path[1:]
if path.endswith('/'): p = path[:-1]
path = path.split('/')
@@ -125,11 +175,44 @@
service, db_name = path
params = (db_name,) + params
- if service == 'model':
- service = 'object'
- return xmlrpc_return(start_response, service, method, params)
-
- # TODO the body has been read, need to raise an exception (not return None).
+ return xmlrpc_return(start_response, service, method, params)
+
+ # A db segment and a model segment must be given.
+ elif len(path) == 3 and path[0] == 'model':
+ service, db_name, model_name = path
+ params = (db_name,) + params[:2] + (model_name,) + params[2:]
+ service = 'object'
+ return xmlrpc_return(start_response, service, method, params)
+
+ # The body has been read, need to raise an exception (not return None).
+ fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_CLIENT_ERROR, '')
+ 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):
+ """ WSGI handler to return the versions."""
+ if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith(XML_RPC_PATH):
+ length = int(environ['CONTENT_LENGTH'])
+ data = environ['wsgi.input'].read(length)
+
+ params, method = xmlrpclib.loads(data)
+
+ path = environ['PATH_INFO'][len(XML_RPC_PATH):]
+ if path.startswith('/'): path = path[1:]
+ if path.endswith('/'): p = path[:-1]
+ path = path.split('/')
+
+ # All routes are hard-coded.
+
+ if len(path) == 1 and path[0] == '' and method in ('version',):
+ return xmlrpc_return(start_response, 'common', method, ())
+
+ # The body has been read, need to raise an exception (not return None).
+ fault = xmlrpclib.Fault(XML_RPC_FAULT_CODE_CLIENT_ERROR, '')
+ 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 legacy_wsgi_xmlrpc(environ, start_response):
if environ['REQUEST_METHOD'] == 'POST' and environ['PATH_INFO'].startswith('/xmlrpc/'):
@@ -138,7 +221,7 @@
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
@@ -309,6 +392,7 @@
# Try all handlers until one returns some result (i.e. not None).
wsgi_handlers = [
+ wsgi_xmlrpc_1,
wsgi_xmlrpc,
wsgi_jsonrpc,
legacy_wsgi_xmlrpc,
=== added directory 'tests/addons'
=== added directory 'tests/addons/test_exceptions'
=== added file 'tests/addons/test_exceptions/__init__.py'
--- tests/addons/test_exceptions/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/addons/test_exceptions/__init__.py 2011-10-01 00:54:24 +0000
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import models
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added file 'tests/addons/test_exceptions/__openerp__.py'
--- tests/addons/test_exceptions/__openerp__.py 1970-01-01 00:00:00 +0000
+++ tests/addons/test_exceptions/__openerp__.py 2011-10-01 00:54:24 +0000
@@ -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:
=== added file 'tests/addons/test_exceptions/models.py'
--- tests/addons/test_exceptions/models.py 1970-01-01 00:00:00 +0000
+++ tests/addons/test_exceptions/models.py 2011-10-01 00:54:24 +0000
@@ -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:
=== added file 'tests/addons/test_exceptions/view.xml'
--- tests/addons/test_exceptions/view.xml 1970-01-01 00:00:00 +0000
+++ tests/addons/test_exceptions/view.xml 2011-10-01 00:54:24 +0000
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <record id="view_test_exceptions_model" model="ir.ui.view">
+ <field name="name">Test exceptions</field>
+ <field name="model">test.exceptions.model</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Test exceptions">
+ <label string="Each button generates a specific exception on the server. The text on the right is the expected representation of the exception when displayed on the client."/>
+ <group colspan="8" col="8">
+ <separator string="" colspan="8"/>
+ <button name="generate_except_osv" string="except_osv" type="object" icon="gtk-ok" colspan="1"/>
+ <label string="Warning-description"/>
+ </group>
+ <group colspan="8" col="8">
+ <button name="generate_except_orm" string="except_orm" type="object" icon="gtk-ok" colspan="1"/>
+ <label string="Warning-description"/>
+ </group>
+ <group colspan="8" col="8">
+ <button name="generate_warning" string="Warning" type="object" icon="gtk-ok" colspan="1"/>
+ <label string="Warning-description"/>
+ </group>
+ <group colspan="8" col="8">
+ <button name="generate_access_denied" string="AccessDenied" type="object" icon="gtk-ok" colspan="1"/>
+ <label string="Access denied-traceback"/>
+ </group>
+ <group colspan="8" col="8">
+ <button name="generate_access_error" string="AccessError" type="object" icon="gtk-ok" colspan="1"/>
+ <label string="Access rights error-description"/>
+ </group>
+ <group colspan="8" col="8">
+ <button name="generate_exc_access_denied" string="Exc AccessDenied" type="object" icon="gtk-ok" colspan="1"/>
+ <label string="Access denied-traceback"/>
+ </group>
+ <group colspan="8" col="8">
+ <button name="generate_undefined" string="Undefined" type="object" icon="gtk-ok" colspan="1"/>
+ <label string="Server error-traceback"/>
+ </group>
+ <group colspan="8" col="8">
+ <separator string="" colspan="8"/>
+ <label colspan="6" width="220"/>
+ <button special="cancel" string="Cancel" icon="gtk-cancel" colspan="1"/>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_test_exceptions" model="ir.actions.act_window">
+ <field name="name">Test exceptions</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">test.exceptions.model</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
+ <menuitem icon="STOCK_PREFERENCES" id="base.menu_tests" name="Tests"/>
+
+ <menuitem id="menu_test_exceptions" parent="base.menu_tests" name="Test exceptions"/>
+
+ <menuitem id="menu_test_exceptions_leaf"
+ name="Test exceptions"
+ action="action_test_exceptions"
+ parent="menu_test_exceptions"/>
+ </data>
+</openerp>
_______________________________________________
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