Odoo is a business application which can be separated, from a technical point of view, in three parts:
- the business application itself (the framework + modules, Root, dispatching requests of the Web client to the Web controllers) ;
- an XML-RPC web service layer
- and the Web application server ;
The first two are WSGI applications, and the last one is the WSGI server (used to serve the WSGI applications). The application server can be replaced by any other server compatible with WSGI, like Gunicorn, uWSGI, mod_wsgi (for Apache)...
In this article we will see how to integrate another WSGI application (a SOAP web service here) inside the Web application server of Odoo 8.0, and take advantage of the Odoo framework from this one. The integration will also be done as a normal module.
The WSGI application
Quoting Wikipedia, "the Web Server Gateway Interface (WSGI) is a specification for simple and universal interface between web servers and web applications or frameworks for the Python programming language.".
Basically, it is an application processing requests provided by the WSGI server. In our case, the WSGI application is a SOAP web service, and we will use the Spyne library to create it.
For the exercise, the purpose of our SOAP web service will be to return the available stock quantity of a given product.
First, let's create the SOAP web service without any Odoo related code in
from spyne import Application, ServiceBase, rpc from spyne import String, Integer from spyne import Mandatory as M from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication class MyService(ServiceBase): @rpc(M(String), _returns=M(Integer)) def get_stock_quantity(self, product_code): # TODO print "PRODUCT:", product_code return 0 # Spyne application application = Application( [MyService], 'http://example.com/soap/', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11()) # WSGI application wsgi_application = WsgiApplication(application)
As a test you can run it with the Python's built-in wsgi server, like this:
from wsgiref.simple_server import make_server from my_service import wsgi_application server = make_server('0.0.0.0', 8000, wsgi_application) server.serve_forever()
You can check the WSDL file at http://localhost:8000/soap/?wsdl
Integration with the Odoo Web server
As said before, the Odoo web server is used to serve the Odoo WSGI
applications. First, put your
my_service.py file into a new module in your
addons_path, and integrate the
wsgi_application to the Web server
from openerp.service.wsgi_server import module_handlers from .my_service import wsgi_application module_handlers.insert(0, wsgi_application)
Here we insert our WSGI app in first position to give it the priority to
process HTTP requests. We do not use the
openerp.service.wsgi_server.register_wsgi_handler() helper function here because the
application of Odoo (see: openerp/http.py)
try to handle all requests, without giving any chance to the others WSGI
applications to process them.
So by inserting it in the first position our WSGI app will handle all
requests... and that's not exactly what we want neither: our application should
just processing HTTP requests related to the
/soap/ URL, but as we have
the control of our own code, we will fix this situation by overloading the
WsgiApplication class provided by the Spyne library.
# ... class SOAPWsgiApplication(WsgiApplication): def __call__(self, req_env, start_response, wsgi_url=None): """Only match URL requests starting with '/soap/'.""" if req_env['PATH_INFO'].startswith('/soap/'): return super(SOAPWsgiApplication, self).__call__( req_env, start_response, wsgi_url) # return None: let's the other WSGI applications (like the Root one) # try to handle the request # Spyne application application = Application( [MyService], 'http://example.com/soap/', in_protocol=Soap11(validator='lxml'), out_protocol=Soap11()) # WSGI application wsgi_application = SOAPWsgiApplication(application)
Test the SOAP service
After installing your module, write a simple script using the suds library to request the new SOAP Web service:
#!/usr/bin/env python # -*- coding: utf-8 -*- from suds.client import Client HOST = 'localhost' PORT = 8069 client = Client('http://%s:%s/soap/?wsdl' % (HOST, PORT)) response = client.service.get_stock_quantity(product_code='P01') print response
$ chmod +x test_soap.py $ ./test_soap.py 0
It works! We get
0, which is the current hard-coded result returned by the
Access to the Odoo ORM from your WSGI app
For the simplicity of the exercice here, we are hardcoding the database to use
to perform queries (we take the first one) with the ORM, feel free to improve
this part (fetch the name of the database from the config file,
manage authentication...). In your
my_service.py module, insert this piece
of code at the top:
# [spyne imports...] from openerp.service import db from openerp.modules.registry import RegistryManager from openerp import SUPERUSER_ID def get_registry_cr_uid_context(): if db.exp_list(): db_name = db.exp_list() registry = RegistryManager.get(db_name) cr = registry.cursor() context = registry['res.users'].context_get(cr, SUPERUSER_ID) return registry, cr, SUPERUSER_ID, context raise Warning(u"NO DATABASE FOUND") # ...
We are now able to identify the product in our
and to return its available quantity:
# ... class MyService(ServiceBase): @rpc(M(String), _returns=M(Integer)) def get_stock_quantity(self, product_code): registry, cr, uid, context = get_registry_cr_uid_context() product_model = registry['product.product'] product_id = product_model.search( cr, uid, [('default_code', '=', product_code)], context=context) if product_id: product = product_model.browse( cr, uid, product_id, context=context) return product.qty_available raise ValueError(u"NO PRODUCT FOUND") # ...
Run again the script to request the SOAP service:
$ ./test_soap.py 12
We saw that Odoo was able to host any WSGI application, and you can use this feature to separate concepts by building dedicated and horizontal services alongside your Odoo application. This article shown the integration of a SOAP Web service built with Spyne, but you could host a Django, Flask, Pylons or a Pyramid application as well.