1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# Copyright (C) Stephane Wirtel
5
# Copyright (C) 2011 Nicolas Vanhoren
6
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
9
# Redistribution and use in source and binary forms, with or without
10
# modification, are permitted provided that the following conditions are met:
11
# * Redistributions of source code must retain the above copyright
12
# notice, this list of conditions and the following disclaimer.
13
# * Redistributions in binary form must reproduce the above copyright
14
# notice, this list of conditions and the following disclaimer in the
15
# documentation and/or other materials provided with the distribution.
16
# * Neither the name of the <organization> nor the
17
# names of its contributors may be used to endorse or promote products
18
# derived from this software without specific prior written permission.
20
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
24
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
##############################################################################
34
OpenERP Client Library
36
Home page: http://pypi.python.org/pypi/openerp-client-lib
37
Code repository: https://code.launchpad.net/~niv-openerp/openerp-client-lib/trunk
45
import cPickle as pickle
50
import cStringIO as StringIO
54
_logger = logging.getLogger(__name__)
56
def _getChildLogger(logger, subname):
57
return logging.getLogger(logger.name + "." + subname)
59
class Connector(object):
61
The base abstract class representing a connection to an OpenERP Server.
64
__logger = _getChildLogger(_logger, 'connector')
66
def __init__(self, hostname, port):
68
Initilize by specifying an hostname and a port.
69
:param hostname: Host name of the server.
70
:param port: Port for the connection to the server.
72
self.hostname = hostname
75
class XmlRPCConnector(Connector):
77
A type of connector that uses the XMLRPC protocol.
81
__logger = _getChildLogger(_logger, 'connector.xmlrpc')
83
def __init__(self, hostname, port=8069):
85
Initialize by specifying the hostname and the port.
86
:param hostname: The hostname of the computer holding the instance of OpenERP.
87
:param port: The port used by the OpenERP instance for XMLRPC (default to 8069).
89
Connector.__init__(self, hostname, port)
90
self.url = 'http://%s:%d/xmlrpc' % (self.hostname, self.port)
92
def send(self, service_name, method, *args):
93
url = '%s/%s' % (self.url, service_name)
94
service = xmlrpclib.ServerProxy(url)
95
return getattr(service, method)(*args)
97
class NetRPC_Exception(Exception):
99
Exception for NetRPC errors.
101
def __init__(self, faultCode, faultString):
102
self.faultCode = faultCode
103
self.faultString = faultString
104
self.args = (faultCode, faultString)
106
class NetRPC(object):
108
Low level class for NetRPC protocol.
110
def __init__(self, sock=None):
112
self.sock = socket.socket(
113
socket.AF_INET, socket.SOCK_STREAM)
116
self.sock.settimeout(120)
117
def connect(self, host, port=False):
119
buf = host.split('//')[1]
120
host, port = buf.split(':')
121
self.sock.connect((host, int(port)))
123
def disconnect(self):
124
self.sock.shutdown(socket.SHUT_RDWR)
127
def mysend(self, msg, exception=False, traceback=None):
128
msg = pickle.dumps([msg,traceback])
130
self.sock.send('%8d' % size)
131
self.sock.send(exception and "1" or "0")
133
while totalsent < size:
134
sent = self.sock.send(msg[totalsent:])
136
raise RuntimeError, "socket connection broken"
137
totalsent = totalsent + sent
142
chunk = self.sock.recv(8 - len(buf))
144
raise RuntimeError, "socket connection broken"
147
buf = self.sock.recv(1)
153
while len(msg) < size:
154
chunk = self.sock.recv(size-len(msg))
156
raise RuntimeError, "socket connection broken"
158
msgio = StringIO.StringIO(msg)
159
unpickler = pickle.Unpickler(msgio)
160
unpickler.find_global = None
161
res = unpickler.load()
163
if isinstance(res[0],Exception):
165
raise NetRPC_Exception(str(res[0]), str(res[1]))
170
class NetRPCConnector(Connector):
172
A type of connector that uses the NetRPC protocol.
177
__logger = _getChildLogger(_logger, 'connector.netrpc')
179
def __init__(self, hostname, port=8070):
181
Initialize by specifying the hostname and the port.
182
:param hostname: The hostname of the computer holding the instance of OpenERP.
183
:param port: The port used by the OpenERP instance for NetRPC (default to 8070).
185
Connector.__init__(self, hostname, port)
187
def send(self, service_name, method, *args):
189
socket.connect(self.hostname, self.port)
190
socket.mysend((service_name, method, )+args)
191
result = socket.myreceive()
195
class Service(object):
197
A class to execute RPC calls on a specific service of the remote server.
199
def __init__(self, connector, service_name):
201
:param connector: A valid Connector instance.
202
:param service_name: The name of the service on the remote server.
204
self.connector = connector
205
self.service_name = service_name
206
self.__logger = _getChildLogger(_getChildLogger(_logger, 'service'),service_name)
208
def __getattr__(self, method):
210
:param method: The name of the method to execute on the service.
212
self.__logger.debug('method: %r', method)
215
:param args: A list of values for the method
217
self.__logger.debug('args: %r', args)
218
result = self.connector.send(self.service_name, method, *args)
219
self.__logger.debug('result: %r', result)
223
class Connection(object):
225
A class to represent a connection with authentication to an OpenERP Server.
226
It also provides utility methods to interact with the server more easily.
228
__logger = _getChildLogger(_logger, 'connection')
230
def __init__(self, connector,
236
Initialize with login information. The login information is facultative to allow specifying
237
it after the initialization of this object.
239
:param connector: A valid Connector instance to send messages to the remote server.
240
:param database: The name of the database to work on.
241
:param login: The login of the user.
242
:param password: The password of the user.
243
:param user_id: The user id is a number identifying the user. This is only useful if you
244
already know it, in most cases you don't need to specify it.
246
self.connector = connector
248
self.set_login_info(database, login, password, user_id)
250
def set_login_info(self, database, login, password, user_id=None):
252
Set login information after the initialisation of this object.
254
:param connector: A valid Connector instance to send messages to the remote server.
255
:param database: The name of the database to work on.
256
:param login: The login of the user.
257
:param password: The password of the user.
258
:param user_id: The user id is a number identifying the user. This is only useful if you
259
already know it, in most cases you don't need to specify it.
261
self.database, self.login, self.password = database, login, password
263
self.user_id = user_id
265
def check_login(self, force=True):
267
Checks that the login information is valid. Throws an AuthenticationError if the
268
authentication fails.
270
:param force: Force to re-check even if this Connection was already validated previously.
273
if self.user_id and not force:
276
if not self.database or not self.login or self.password is None:
277
raise AuthenticationError("Creditentials not provided")
279
self.user_id = self.get_service("common").login(self.database, self.login, self.password)
281
raise AuthenticationError("Authentication failure")
282
self.__logger.debug("Authenticated with user id %s", self.user_id)
284
def get_model(self, model_name):
286
Returns a Model instance to allow easy remote manipulation of an OpenERP model.
288
:param model_name: The name of the model.
290
return Model(self, model_name)
292
def get_service(self, service_name):
294
Returns a Service instance to allow easy manipulation of one of the services offered by the remote server.
295
Please note this Connection instance does not need to have valid authentication information since authentication
296
is only necessary for the "object" service that handles models.
298
:param service_name: The name of the service.
300
return Service(self.connector, service_name)
302
class AuthenticationError(Exception):
304
An error thrown when an authentication to an OpenERP server failed.
310
Useful class to dialog with one of the models provided by an OpenERP server.
311
An instance of this class depends on a Connection instance with valid authentication information.
314
def __init__(self, connection, model_name):
316
:param connection: A valid Connection instance with correct authentication information.
317
:param model_name: The name of the model.
319
self.connection = connection
320
self.model_name = model_name
321
self.__logger = _getChildLogger(_getChildLogger(_logger, 'object'), model_name)
323
def __getattr__(self, method):
325
Provides proxy methods that will forward calls to the model on the remote OpenERP server.
327
:param method: The method for the linked model (search, read, write, unlink, create, ...)
331
:param args: A list of values for the method
333
self.connection.check_login(False)
334
self.__logger.debug(args)
335
result = self.connection.get_service('object').execute(
336
self.connection.database,
337
self.connection.user_id,
338
self.connection.password,
343
if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
347
result = [index[x] for x in args[0]]
348
self.__logger.debug('result: %r', result)
352
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, context=None):
354
A shortcut method to combine a search() and a read().
356
:param domain: The domain for the search.
357
:param fields: The fields to extract (can be None or [] to extract all fields).
358
:param offset: The offset for the rows to read.
359
:param limit: The maximum number of rows to read.
360
:param order: The order to class the rows.
361
:param context: The context.
362
:return: A list of dictionaries containing all the specified fields.
364
record_ids = self.search(domain or [], offset, limit or False, order or False, context or {})
365
records = self.read(record_ids, fields or [], context or {})
368
def get_connector(hostname, protocol="xmlrpc", port="auto"):
370
A shortcut method to easily create a connector to a remote server using XMLRPC or NetRPC.
372
:param hostname: The hostname to the remote server.
373
:param protocol: The name of the protocol, must be "xmlrpc" or "netrpc".
374
:param port: The number of the port. Defaults to auto.
377
port = 8069 if protocol=="xmlrpc" else 8070
378
if protocol == "xmlrpc":
379
return XmlRPCConnector(hostname, port)
380
elif protocol == "netrpc":
381
return NetRPCConnector(hostname, port)
383
raise ValueError("You must choose xmlrpc or netrpc")
385
def get_connection(hostname, protocol="xmlrpc", port='auto', database=None,
386
login=None, password=None, user_id=None):
388
A shortcut method to easily create a connection to a remote OpenERP server.
390
:param hostname: The hostname to the remote server.
391
:param protocol: The name of the protocol, must be "xmlrpc" or "netrpc".
392
:param port: The number of the port. Defaults to auto.
393
:param connector: A valid Connector instance to send messages to the remote server.
394
:param database: The name of the database to work on.
395
:param login: The login of the user.
396
:param password: The password of the user.
397
:param user_id: The user id is a number identifying the user. This is only useful if you
398
already know it, in most cases you don't need to specify it.
400
return Connection(get_connector(hostname, protocol, port), database, login, password, user_id)