├── .bzrignore ├── LICENSE ├── openerplib ├── __init__.py ├── dates.py └── main.py ├── setup.py ├── test.py └── README.rst /.bzrignore: -------------------------------------------------------------------------------- 1 | .* 2 | MANIFEST 3 | build 4 | dist 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2011 Nicolas Vanhoren 4 | Copyright (C) Stephane Wirtel 5 | Copyright (C) 2011 OpenERP s.a. (). 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /openerplib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Copyright (C) Stephane Wirtel 5 | # Copyright (C) 2011 Nicolas Vanhoren 6 | # Copyright (C) 2011 OpenERP s.a. (). 7 | # All rights reserved. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # 1. Redistributions of source code must retain the above copyright notice, this 13 | # list of conditions and the following disclaimer. 14 | # 2. Redistributions in binary form must reproduce the above copyright notice, 15 | # this list of conditions and the following disclaimer in the documentation 16 | # and/or other materials provided with the distribution. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | ############################################################################## 30 | 31 | from .main import * 32 | 33 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Copyright (C) Stephane Wirtel 5 | # Copyright (C) 2011 Nicolas Vanhoren 6 | # Copyright (C) 2011 OpenERP s.a. (). 7 | # All rights reserved. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # 1. Redistributions of source code must retain the above copyright notice, this 13 | # list of conditions and the following disclaimer. 14 | # 2. Redistributions in binary form must reproduce the above copyright notice, 15 | # this list of conditions and the following disclaimer in the documentation 16 | # and/or other materials provided with the distribution. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | ############################################################################## 30 | 31 | from distutils.core import setup 32 | import os.path 33 | 34 | 35 | setup(name='openerp-client-lib', 36 | version='1.1.2', 37 | description='OpenERP Client Library allows to easily interact with OpenERP in Python.', 38 | author='Nicolas Vanhoren', 39 | author_email='', 40 | url='', 41 | packages=["openerplib"], 42 | long_description="See the home page for any information: https://github.com/nicolas-van/openerp-client-lib .", 43 | keywords="openerp library com communication rpc xml-rpc net-rpc xmlrpc python client lib web service", 44 | license="BSD", 45 | classifiers=[ 46 | "License :: OSI Approved :: BSD License", 47 | "Programming Language :: Python", 48 | ], 49 | ) 50 | 51 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Copyright (C) Stephane Wirtel 5 | # Copyright (C) 2011 Nicolas Vanhoren 6 | # Copyright (C) 2011 OpenERP s.a. (). 7 | # All rights reserved. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # 1. Redistributions of source code must retain the above copyright notice, this 13 | # list of conditions and the following disclaimer. 14 | # 2. Redistributions in binary form must reproduce the above copyright notice, 15 | # this list of conditions and the following disclaimer in the documentation 16 | # and/or other materials provided with the distribution. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | ############################################################################## 30 | 31 | """ 32 | Some unit tests. They assume there is an OpenERP server running on localhost, on the default port 33 | with a database named 'test' and a user 'admin' with password 'a'. 34 | """ 35 | 36 | import openerplib 37 | import unittest 38 | 39 | class TestSequenceFunctions(unittest.TestCase): 40 | 41 | def setUp(self): 42 | pass 43 | 44 | def conn(self): 45 | return openerplib.get_connection(hostname="localhost", protocol="xmlrpc", 46 | database="test", login="admin", password="a") 47 | 48 | def test_simple(self): 49 | connection = self.conn() 50 | 51 | res = connection.get_model("res.users").read(1) 52 | 53 | self.assertEqual(res['id'], 1) 54 | 55 | 56 | def test_user_context(self): 57 | connection = self.conn() 58 | connection.get_user_context() 59 | 60 | 61 | 62 | if __name__ == '__main__': 63 | unittest.main() 64 | 65 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | OpenERP Client Library 3 | ====================== 4 | 5 | 6 | The OpenERP Client Library is a Python library to communicate with an OpenERP Server using its web 7 | services in an user-friendly way. It was created for those that doesn't want to code XML-RPC calls 8 | on the bare metal. It handles XML-RPC as well as JSON-RPC protocol and provides a bunch of syntaxic 9 | sugar to make things a lot easier. 10 | 11 | Guide 12 | ----- 13 | 14 | First install the library: :: 15 | 16 | sudo easy_install openerp-client-lib 17 | 18 | Now copy-paste the following script describing a simple interaction with an OpenERP server: :: 19 | 20 | import openerplib 21 | 22 | connection = openerplib.get_connection(hostname="localhost", database="my_db", \ 23 | login="my_user", password="xxx") 24 | user_model = connection.get_model("res.users") 25 | ids = user_model.search([("login", "=", "admin")]) 26 | user_info = user_model.read(ids[0], ["name"]) 27 | print user_info["name"] 28 | # will print "Administrator" 29 | 30 | In the previous script, the get_connection() method creates a Connection object that represents a 31 | communication channel with authentification to an OpenERP server. By default, get_connection() uses 32 | XML-RPC, but you can specify it to use JSON-RPC. You can also change the port. Example with a JSON-RPC 33 | communication on port 6080: :: 34 | 35 | connection = openerplib.get_connection(hostname="localhost", protocol="jsonrpc", port=6080, ...) 36 | 37 | The get_model() method on the Connection object creates a Model object. That object represents a 38 | remote model on the OpenERP server (for OpenERP addon programmers, those are also called osv). 39 | Model objects are dynamic proxies, which means you can remotely call methods in a natural way. 40 | In the previous script we demonstrate the usage of the search() and read() methods. That scenario 41 | is equivalent to the following interaction when you are coding an OpenERP addon and this code is 42 | executed on the server: :: 43 | 44 | user_osv = self.pool.get('res.users') 45 | ids = user_osv.search(cr, uid, [("login", "=", "admin")]) 46 | user_info = user_osv.read(cr, uid, ids[0], ["name"]) 47 | 48 | Also note that coding using Model objects offer some syntaxic sugar compared to vanilla addon coding: 49 | 50 | - You don't have to forward the "cr" and "uid" to all methods. 51 | - The read() method automatically sort rows according the order of the ids you gave it to. 52 | - The Model objects also provides the search_read() method that combines a search and a read, example: :: 53 | 54 | user_info = user_model.search_read([('login', '=', 'admin')], ["name"])[0] 55 | 56 | Here are also some considerations about coding using the OpenERP Client Library: 57 | 58 | - Since you are using remote procedure calls, every call is executed inside its own transaction. So it can 59 | be dangerous, for example, to code multiple interdependant row creations. You should consider coding a method 60 | inside an OpenERP addon for such cases. That way it will be executed on the OpenERP server and so it will be 61 | transactional. 62 | - The browse() method can not be used. That method returns a dynamic proxy that lazy loads the rows' data from 63 | the database. That behavior is not implemented in the OpenERP Client Library. 64 | 65 | Compatibility 66 | ------------- 67 | 68 | - XML-RPC: OpenERP version 6.1 and superior 69 | 70 | - JSON-RPC: OpenERP version 8.0 (upcoming) and superior 71 | 72 | 73 | -------------------------------------------------------------------------------- /openerplib/dates.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Copyright (C) Stephane Wirtel 5 | # Copyright (C) 2011 Nicolas Vanhoren 6 | # Copyright (C) 2011 OpenERP s.a. (). 7 | # All rights reserved. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # 1. Redistributions of source code must retain the above copyright notice, this 13 | # list of conditions and the following disclaimer. 14 | # 2. Redistributions in binary form must reproduce the above copyright notice, 15 | # this list of conditions and the following disclaimer in the documentation 16 | # and/or other materials provided with the distribution. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | ############################################################################## 30 | 31 | import datetime 32 | 33 | DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d" 34 | DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S" 35 | DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % ( 36 | DEFAULT_SERVER_DATE_FORMAT, 37 | DEFAULT_SERVER_TIME_FORMAT) 38 | 39 | def str_to_datetime(str): 40 | """ 41 | Converts a string to a datetime object using OpenERP's 42 | datetime string format (exemple: '2011-12-01 15:12:35'). 43 | 44 | No timezone information is added, the datetime is a naive instance, but 45 | according to OpenERP 6.1 specification the timezone is always UTC. 46 | """ 47 | if not str: 48 | return str 49 | return datetime.datetime.strptime(str.split(".")[0], DEFAULT_SERVER_DATETIME_FORMAT) 50 | 51 | def str_to_date(str): 52 | """ 53 | Converts a string to a date object using OpenERP's 54 | date string format (exemple: '2011-12-01'). 55 | """ 56 | if not str: 57 | return str 58 | return datetime.datetime.strptime(str, DEFAULT_SERVER_DATE_FORMAT).date() 59 | 60 | def str_to_time(str): 61 | """ 62 | Converts a string to a time object using OpenERP's 63 | time string format (exemple: '15:12:35'). 64 | """ 65 | if not str: 66 | return str 67 | return datetime.datetime.strptime(str.split(".")[0], DEFAULT_SERVER_TIME_FORMAT).time() 68 | 69 | def datetime_to_str(obj): 70 | """ 71 | Converts a datetime object to a string using OpenERP's 72 | datetime string format (exemple: '2011-12-01 15:12:35'). 73 | 74 | The datetime instance should not have an attached timezone and be in UTC. 75 | """ 76 | if not obj: 77 | return False 78 | return obj.strftime(DEFAULT_SERVER_DATETIME_FORMAT) 79 | 80 | def date_to_str(obj): 81 | """ 82 | Converts a date object to a string using OpenERP's 83 | date string format (exemple: '2011-12-01'). 84 | """ 85 | if not obj: 86 | return False 87 | return obj.strftime(DEFAULT_SERVER_DATE_FORMAT) 88 | 89 | def time_to_str(obj): 90 | """ 91 | Converts a time object to a string using OpenERP's 92 | time string format (exemple: '15:12:35'). 93 | """ 94 | if not obj: 95 | return False 96 | return obj.strftime(DEFAULT_SERVER_TIME_FORMAT) 97 | 98 | -------------------------------------------------------------------------------- /openerplib/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Copyright (C) Stephane Wirtel 5 | # Copyright (C) 2011 Nicolas Vanhoren 6 | # Copyright (C) 2011 OpenERP s.a. (). 7 | # All rights reserved. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions are met: 11 | # 12 | # 1. Redistributions of source code must retain the above copyright notice, this 13 | # list of conditions and the following disclaimer. 14 | # 2. Redistributions in binary form must reproduce the above copyright notice, 15 | # this list of conditions and the following disclaimer in the documentation 16 | # and/or other materials provided with the distribution. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | ############################################################################## 30 | 31 | """ 32 | OpenERP Client Library 33 | 34 | Home page: http://pypi.python.org/pypi/openerp-client-lib 35 | Code repository: https://code.launchpad.net/~niv-openerp/openerp-client-lib/trunk 36 | """ 37 | 38 | import sys 39 | 40 | if sys.version_info >= (3, 0, 0): 41 | from urllib.request import Request, urlopen 42 | from xmlrpc.client import ServerProxy 43 | else: 44 | from urllib2 import Request, urlopen 45 | from xmlrpclib import ServerProxy 46 | import logging 47 | import json 48 | import random 49 | 50 | _logger = logging.getLogger(__name__) 51 | 52 | def _getChildLogger(logger, subname): 53 | return logging.getLogger(logger.name + "." + subname) 54 | 55 | class Connector(object): 56 | """ 57 | The base abstract class representing a connection to an OpenERP Server. 58 | """ 59 | 60 | __logger = _getChildLogger(_logger, 'connector') 61 | 62 | def get_service(self, service_name): 63 | """ 64 | Returns a Service instance to allow easy manipulation of one of the services offered by the remote server. 65 | 66 | :param service_name: The name of the service. 67 | """ 68 | return Service(self, service_name) 69 | 70 | class XmlRPCConnector(Connector): 71 | """ 72 | A type of connector that uses the XMLRPC protocol. 73 | """ 74 | PROTOCOL = 'xmlrpc' 75 | 76 | __logger = _getChildLogger(_logger, 'connector.xmlrpc') 77 | 78 | def __init__(self, hostname, port=8069): 79 | """ 80 | Initialize by specifying the hostname and the port. 81 | :param hostname: The hostname of the computer holding the instance of OpenERP. 82 | :param port: The port used by the OpenERP instance for XMLRPC (default to 8069). 83 | """ 84 | self.url = 'http://%s:%d/xmlrpc' % (hostname, port) 85 | 86 | def send(self, service_name, method, *args): 87 | url = '%s/%s' % (self.url, service_name) 88 | service = ServerProxy(url) 89 | return getattr(service, method)(*args) 90 | 91 | class XmlRPCSConnector(XmlRPCConnector): 92 | """ 93 | A type of connector that uses the secured XMLRPC protocol. 94 | """ 95 | PROTOCOL = 'xmlrpcs' 96 | 97 | __logger = _getChildLogger(_logger, 'connector.xmlrpcs') 98 | 99 | def __init__(self, hostname, port=8069): 100 | super(XmlRPCSConnector, self).__init__(hostname, port) 101 | self.url = 'https://%s:%d/xmlrpc' % (hostname, port) 102 | 103 | class JsonRPCException(Exception): 104 | def __init__(self, error): 105 | self.error = error 106 | def __str__(self): 107 | return repr(self.error) 108 | 109 | def json_rpc(url, fct_name, params): 110 | data = { 111 | "jsonrpc": "2.0", 112 | "method": fct_name, 113 | "params": params, 114 | "id": random.randint(0, 1000000000), 115 | } 116 | req = Request(url=url, data=json.dumps(data), headers={ 117 | "Content-Type":"application/json", 118 | }) 119 | result = urlopen(req) 120 | result = json.load(result) 121 | if result.get("error", None): 122 | raise JsonRPCException(result["error"]) 123 | return result["result"] 124 | 125 | class JsonRPCConnector(Connector): 126 | """ 127 | A type of connector that uses the JsonRPC protocol. 128 | """ 129 | PROTOCOL = 'jsonrpc' 130 | 131 | __logger = _getChildLogger(_logger, 'connector.jsonrpc') 132 | 133 | def __init__(self, hostname, port=8069): 134 | """ 135 | Initialize by specifying the hostname and the port. 136 | :param hostname: The hostname of the computer holding the instance of OpenERP. 137 | :param port: The port used by the OpenERP instance for JsonRPC (default to 8069). 138 | """ 139 | self.url = 'http://%s:%d/jsonrpc' % (hostname, port) 140 | 141 | def send(self, service_name, method, *args): 142 | return json_rpc(self.url, "call", {"service": service_name, "method": method, "args": args}) 143 | 144 | class JsonRPCSConnector(Connector): 145 | """ 146 | A type of connector that uses the JsonRPC protocol. 147 | """ 148 | PROTOCOL = 'jsonrpcs' 149 | 150 | __logger = _getChildLogger(_logger, 'connector.jsonrpc') 151 | 152 | def __init__(self, hostname, port=8069): 153 | """ 154 | Initialize by specifying the hostname and the port. 155 | :param hostname: The hostname of the computer holding the instance of OpenERP. 156 | :param port: The port used by the OpenERP instance for JsonRPC (default to 8069). 157 | """ 158 | self.url = 'https://%s:%d/jsonrpc' % (hostname, port) 159 | 160 | def send(self, service_name, method, *args): 161 | return json_rpc(self.url, "call", {"service": service_name, "method": method, "args": args}) 162 | 163 | class Service(object): 164 | """ 165 | A class to execute RPC calls on a specific service of the remote server. 166 | """ 167 | def __init__(self, connector, service_name): 168 | """ 169 | :param connector: A valid Connector instance. 170 | :param service_name: The name of the service on the remote server. 171 | """ 172 | self.connector = connector 173 | self.service_name = service_name 174 | self.__logger = _getChildLogger(_getChildLogger(_logger, 'service'),service_name or "") 175 | 176 | def __getattr__(self, method): 177 | """ 178 | :param method: The name of the method to execute on the service. 179 | """ 180 | self.__logger.debug('method: %r', method) 181 | def proxy(*args): 182 | """ 183 | :param args: A list of values for the method 184 | """ 185 | self.__logger.debug('args: %r', args) 186 | result = self.connector.send(self.service_name, method, *args) 187 | self.__logger.debug('result: %r', result) 188 | return result 189 | return proxy 190 | 191 | class Connection(object): 192 | """ 193 | A class to represent a connection with authentication to an OpenERP Server. 194 | It also provides utility methods to interact with the server more easily. 195 | """ 196 | __logger = _getChildLogger(_logger, 'connection') 197 | 198 | def __init__(self, connector, 199 | database=None, 200 | login=None, 201 | password=None, 202 | user_id=None): 203 | """ 204 | Initialize with login information. The login information is facultative to allow specifying 205 | it after the initialization of this object. 206 | 207 | :param connector: A valid Connector instance to send messages to the remote server. 208 | :param database: The name of the database to work on. 209 | :param login: The login of the user. 210 | :param password: The password of the user. 211 | :param user_id: The user id is a number identifying the user. This is only useful if you 212 | already know it, in most cases you don't need to specify it. 213 | """ 214 | self.connector = connector 215 | 216 | self.set_login_info(database, login, password, user_id) 217 | self.user_context = None 218 | 219 | def set_login_info(self, database, login, password, user_id=None): 220 | """ 221 | Set login information after the initialisation of this object. 222 | 223 | :param connector: A valid Connector instance to send messages to the remote server. 224 | :param database: The name of the database to work on. 225 | :param login: The login of the user. 226 | :param password: The password of the user. 227 | :param user_id: The user id is a number identifying the user. This is only useful if you 228 | already know it, in most cases you don't need to specify it. 229 | """ 230 | self.database, self.login, self.password = database, login, password 231 | 232 | self.user_id = user_id 233 | 234 | def check_login(self, force=True): 235 | """ 236 | Checks that the login information is valid. Throws an AuthenticationError if the 237 | authentication fails. 238 | 239 | :param force: Force to re-check even if this Connection was already validated previously. 240 | Default to True. 241 | """ 242 | if self.user_id and not force: 243 | return 244 | 245 | if not self.database or not self.login or self.password is None: 246 | raise AuthenticationError("Credentials not provided") 247 | 248 | # TODO use authenticate instead of login 249 | self.user_id = self.get_service("common").login(self.database, self.login, self.password) 250 | if not self.user_id: 251 | raise AuthenticationError("Authentication failure") 252 | self.__logger.debug("Authenticated with user id %s", self.user_id) 253 | 254 | def get_user_context(self): 255 | """ 256 | Query the default context of the user. 257 | """ 258 | if not self.user_context: 259 | self.user_context = self.get_model('res.users').context_get() 260 | return self.user_context 261 | 262 | def get_model(self, model_name): 263 | """ 264 | Returns a Model instance to allow easy remote manipulation of an OpenERP model. 265 | 266 | :param model_name: The name of the model. 267 | """ 268 | return Model(self, model_name) 269 | 270 | def get_service(self, service_name): 271 | """ 272 | Returns a Service instance to allow easy manipulation of one of the services offered by the remote server. 273 | Please note this Connection instance does not need to have valid authentication information since authentication 274 | is only necessary for the "object" service that handles models. 275 | 276 | :param service_name: The name of the service. 277 | """ 278 | return self.connector.get_service(service_name) 279 | 280 | class AuthenticationError(Exception): 281 | """ 282 | An error thrown when an authentication to an OpenERP server failed. 283 | """ 284 | pass 285 | 286 | class Model(object): 287 | """ 288 | Useful class to dialog with one of the models provided by an OpenERP server. 289 | An instance of this class depends on a Connection instance with valid authentication information. 290 | """ 291 | 292 | def __init__(self, connection, model_name): 293 | """ 294 | :param connection: A valid Connection instance with correct authentication information. 295 | :param model_name: The name of the model. 296 | """ 297 | self.connection = connection 298 | self.model_name = model_name 299 | self.__logger = _getChildLogger(_getChildLogger(_logger, 'object'), model_name or "") 300 | 301 | def __getattr__(self, method): 302 | """ 303 | Provides proxy methods that will forward calls to the model on the remote OpenERP server. 304 | 305 | :param method: The method for the linked model (search, read, write, unlink, create, ...) 306 | """ 307 | def proxy(*args, **kw): 308 | """ 309 | :param args: A list of values for the method 310 | """ 311 | self.connection.check_login(False) 312 | self.__logger.debug(args) 313 | result = self.connection.get_service('object').execute_kw( 314 | self.connection.database, 315 | self.connection.user_id, 316 | self.connection.password, 317 | self.model_name, 318 | method, 319 | args, kw) 320 | if method == "read": 321 | if isinstance(result, list) and len(result) > 0 and "id" in result[0]: 322 | index = {} 323 | for r in result: 324 | index[r['id']] = r 325 | result = [index[x] for x in args[0] if x in index] 326 | self.__logger.debug('result: %r', result) 327 | return result 328 | return proxy 329 | 330 | def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, context=None): 331 | """ 332 | A shortcut method to combine a search() and a read(). 333 | 334 | :param domain: The domain for the search. 335 | :param fields: The fields to extract (can be None or [] to extract all fields). 336 | :param offset: The offset for the rows to read. 337 | :param limit: The maximum number of rows to read. 338 | :param order: The order to class the rows. 339 | :param context: The context. 340 | :return: A list of dictionaries containing all the specified fields. 341 | """ 342 | record_ids = self.search(domain or [], offset, limit or False, order or False, context or {}) 343 | if not record_ids: return [] 344 | records = self.read(record_ids, fields or [], context or {}) 345 | return records 346 | 347 | def get_connector(hostname=None, protocol="xmlrpc", port="auto"): 348 | """ 349 | A shortcut method to easily create a connector to a remote server using XMLRPC. 350 | 351 | :param hostname: The hostname to the remote server. 352 | :param protocol: The name of the protocol, must be "xmlrpc", "xmlrpcs", "jsonrpc" or "jsonrpcs". 353 | :param port: The number of the port. Defaults to auto. 354 | """ 355 | if port == 'auto': 356 | port = 8069 357 | if protocol == "xmlrpc": 358 | return XmlRPCConnector(hostname, port) 359 | elif protocol == "xmlrpcs": 360 | return XmlRPCSConnector(hostname, port) 361 | if protocol == "jsonrpc": 362 | return JsonRPCConnector(hostname, port) 363 | elif protocol == "jsonrpcs": 364 | return JsonRPCSConnector(hostname, port) 365 | else: 366 | raise ValueError("You must choose xmlrpc, xmlrpcs, jsonrpc or jsonrpcs") 367 | 368 | def get_connection(hostname=None, protocol="xmlrpc", port='auto', database=None, 369 | login=None, password=None, user_id=None): 370 | """ 371 | A shortcut method to easily create a connection to a remote OpenERP server. 372 | 373 | :param hostname: The hostname to the remote server. 374 | :param protocol: The name of the protocol, must be "xmlrpc", "xmlrpcs", "jsonrpc" or "jsonrpcs". 375 | :param port: The number of the port. Defaults to auto. 376 | :param connector: A valid Connector instance to send messages to the remote server. 377 | :param database: The name of the database to work on. 378 | :param login: The login of the user. 379 | :param password: The password of the user. 380 | :param user_id: The user id is a number identifying the user. This is only useful if you 381 | already know it, in most cases you don't need to specify it. 382 | """ 383 | return Connection(get_connector(hostname, protocol, port), database, login, password, user_id) 384 | 385 | --------------------------------------------------------------------------------