~jfb-tempo-consulting/unifield-wm/sync-env-py3

« back to all changes in this revision

Viewing changes to openerplib103/main.py

  • Committer: Cecile Tonglet
  • Date: 2013-10-17 14:40:19 UTC
  • Revision ID: cto@openerp.com-20131017144019-grjgxx09qz1q311p
[FIX] Version problem with openerplib

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
# Copyright (C) Stephane Wirtel
 
5
# Copyright (C) 2011 Nicolas Vanhoren
 
6
# Copyright (C) 2011 OpenERP s.a. (<http://openerp.com>).
 
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
#     * 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.
 
19
 
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.
 
30
#
 
31
##############################################################################
 
32
 
 
33
"""
 
34
OpenERP Client Library
 
35
 
 
36
Home page: http://pypi.python.org/pypi/openerp-client-lib
 
37
Code repository: https://code.launchpad.net/~niv-openerp/openerp-client-lib/trunk
 
38
"""
 
39
 
 
40
import xmlrpclib
 
41
import logging 
 
42
import socket
 
43
 
 
44
try:
 
45
    import cPickle as pickle
 
46
except ImportError:
 
47
    import pickle
 
48
 
 
49
try:
 
50
    import cStringIO as StringIO
 
51
except ImportError:
 
52
    import StringIO
 
53
 
 
54
_logger = logging.getLogger(__name__)
 
55
 
 
56
def _getChildLogger(logger, subname):
 
57
    return logging.getLogger(logger.name + "." + subname)
 
58
 
 
59
class Connector(object):
 
60
    """
 
61
    The base abstract class representing a connection to an OpenERP Server.
 
62
    """
 
63
 
 
64
    __logger = _getChildLogger(_logger, 'connector')
 
65
 
 
66
    def __init__(self, hostname, port):
 
67
        """
 
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.
 
71
        """
 
72
        self.hostname = hostname
 
73
        self.port = port
 
74
 
 
75
class XmlRPCConnector(Connector):
 
76
    """
 
77
    A type of connector that uses the XMLRPC protocol.
 
78
    """
 
79
    PROTOCOL = 'xmlrpc'
 
80
    
 
81
    __logger = _getChildLogger(_logger, 'connector.xmlrpc')
 
82
 
 
83
    def __init__(self, hostname, port=8069):
 
84
        """
 
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).
 
88
        """
 
89
        Connector.__init__(self, hostname, port)
 
90
        self.url = 'http://%s:%d/xmlrpc' % (self.hostname, self.port)
 
91
 
 
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)
 
96
 
 
97
class NetRPC_Exception(Exception):
 
98
    """
 
99
    Exception for NetRPC errors.
 
100
    """
 
101
    def __init__(self, faultCode, faultString):
 
102
        self.faultCode = faultCode
 
103
        self.faultString = faultString
 
104
        self.args = (faultCode, faultString)
 
105
 
 
106
class NetRPC(object):
 
107
    """
 
108
    Low level class for NetRPC protocol.
 
109
    """
 
110
    def __init__(self, sock=None):
 
111
        if sock is None:
 
112
            self.sock = socket.socket(
 
113
            socket.AF_INET, socket.SOCK_STREAM)
 
114
        else:
 
115
            self.sock = sock
 
116
        self.sock.settimeout(120)
 
117
    def connect(self, host, port=False):
 
118
        if not port:
 
119
            buf = host.split('//')[1]
 
120
            host, port = buf.split(':')
 
121
        self.sock.connect((host, int(port)))
 
122
 
 
123
    def disconnect(self):
 
124
        self.sock.shutdown(socket.SHUT_RDWR)
 
125
        self.sock.close()
 
126
 
 
127
    def mysend(self, msg, exception=False, traceback=None):
 
128
        msg = pickle.dumps([msg,traceback])
 
129
        size = len(msg)
 
130
        self.sock.send('%8d' % size)
 
131
        self.sock.send(exception and "1" or "0")
 
132
        totalsent = 0
 
133
        while totalsent < size:
 
134
            sent = self.sock.send(msg[totalsent:])
 
135
            if sent == 0:
 
136
                raise RuntimeError, "socket connection broken"
 
137
            totalsent = totalsent + sent
 
138
 
 
139
    def myreceive(self):
 
140
        buf=''
 
141
        while len(buf) < 8:
 
142
            chunk = self.sock.recv(8 - len(buf))
 
143
            if chunk == '':
 
144
                raise RuntimeError, "socket connection broken"
 
145
            buf += chunk
 
146
        size = int(buf)
 
147
        buf = self.sock.recv(1)
 
148
        if buf != "0":
 
149
            exception = buf
 
150
        else:
 
151
            exception = False
 
152
        msg = ''
 
153
        while len(msg) < size:
 
154
            chunk = self.sock.recv(size-len(msg))
 
155
            if chunk == '':
 
156
                raise RuntimeError, "socket connection broken"
 
157
            msg = msg + chunk
 
158
        msgio = StringIO.StringIO(msg)
 
159
        unpickler = pickle.Unpickler(msgio)
 
160
        unpickler.find_global = None
 
161
        res = unpickler.load()
 
162
 
 
163
        if isinstance(res[0],Exception):
 
164
            if exception:
 
165
                raise NetRPC_Exception(str(res[0]), str(res[1]))
 
166
            raise res[0]
 
167
        else:
 
168
            return res[0]
 
169
 
 
170
class NetRPCConnector(Connector):
 
171
    """
 
172
    A type of connector that uses the NetRPC protocol.
 
173
    """
 
174
 
 
175
    PROTOCOL = 'netrpc'
 
176
    
 
177
    __logger = _getChildLogger(_logger, 'connector.netrpc')
 
178
 
 
179
    def __init__(self, hostname, port=8070):
 
180
        """
 
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).
 
184
        """
 
185
        Connector.__init__(self, hostname, port)
 
186
 
 
187
    def send(self, service_name, method, *args):
 
188
        socket = NetRPC()
 
189
        socket.connect(self.hostname, self.port)
 
190
        socket.mysend((service_name, method, )+args)
 
191
        result = socket.myreceive()
 
192
        socket.disconnect()
 
193
        return result
 
194
 
 
195
class Service(object):
 
196
    """
 
197
    A class to execute RPC calls on a specific service of the remote server.
 
198
    """
 
199
    def __init__(self, connector, service_name):
 
200
        """
 
201
        :param connector: A valid Connector instance.
 
202
        :param service_name: The name of the service on the remote server.
 
203
        """
 
204
        self.connector = connector
 
205
        self.service_name = service_name
 
206
        self.__logger = _getChildLogger(_getChildLogger(_logger, 'service'),service_name)
 
207
        
 
208
    def __getattr__(self, method):
 
209
        """
 
210
        :param method: The name of the method to execute on the service.
 
211
        """
 
212
        self.__logger.debug('method: %r', method)
 
213
        def proxy(*args):
 
214
            """
 
215
            :param args: A list of values for the method
 
216
            """
 
217
            self.__logger.debug('args: %r', args)
 
218
            result = self.connector.send(self.service_name, method, *args)
 
219
            self.__logger.debug('result: %r', result)
 
220
            return result
 
221
        return proxy
 
222
 
 
223
class Connection(object):
 
224
    """
 
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.
 
227
    """
 
228
    __logger = _getChildLogger(_logger, 'connection')
 
229
 
 
230
    def __init__(self, connector,
 
231
                 database=None,
 
232
                 login=None,
 
233
                 password=None,
 
234
                 user_id=None):
 
235
        """
 
236
        Initialize with login information. The login information is facultative to allow specifying
 
237
        it after the initialization of this object.
 
238
 
 
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.
 
245
        """
 
246
        self.connector = connector
 
247
 
 
248
        self.set_login_info(database, login, password, user_id)
 
249
 
 
250
    def set_login_info(self, database, login, password, user_id=None):
 
251
        """
 
252
        Set login information after the initialisation of this object.
 
253
 
 
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.
 
260
        """
 
261
        self.database, self.login, self.password = database, login, password
 
262
 
 
263
        self.user_id = user_id
 
264
        
 
265
    def check_login(self, force=True):
 
266
        """
 
267
        Checks that the login information is valid. Throws an AuthenticationError if the
 
268
        authentication fails.
 
269
 
 
270
        :param force: Force to re-check even if this Connection was already validated previously.
 
271
        Default to True.
 
272
        """
 
273
        if self.user_id and not force:
 
274
            return
 
275
        
 
276
        if not self.database or not self.login or self.password is None:
 
277
            raise AuthenticationError("Creditentials not provided")
 
278
        
 
279
        self.user_id = self.get_service("common").login(self.database, self.login, self.password)
 
280
        if not self.user_id:
 
281
            raise AuthenticationError("Authentication failure")
 
282
        self.__logger.debug("Authenticated with user id %s", self.user_id)
 
283
    
 
284
    def get_model(self, model_name):
 
285
        """
 
286
        Returns a Model instance to allow easy remote manipulation of an OpenERP model.
 
287
 
 
288
        :param model_name: The name of the model.
 
289
        """
 
290
        return Model(self, model_name)
 
291
 
 
292
    def get_service(self, service_name):
 
293
        """
 
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.
 
297
 
 
298
        :param service_name: The name of the service.
 
299
        """
 
300
        return Service(self.connector, service_name)
 
301
 
 
302
class AuthenticationError(Exception):
 
303
    """
 
304
    An error thrown when an authentication to an OpenERP server failed.
 
305
    """
 
306
    pass
 
307
 
 
308
class Model(object):
 
309
    """
 
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.
 
312
    """
 
313
 
 
314
    def __init__(self, connection, model_name):
 
315
        """
 
316
        :param connection: A valid Connection instance with correct authentication information.
 
317
        :param model_name: The name of the model.
 
318
        """
 
319
        self.connection = connection
 
320
        self.model_name = model_name
 
321
        self.__logger = _getChildLogger(_getChildLogger(_logger, 'object'), model_name)
 
322
 
 
323
    def __getattr__(self, method):
 
324
        """
 
325
        Provides proxy methods that will forward calls to the model on the remote OpenERP server.
 
326
 
 
327
        :param method: The method for the linked model (search, read, write, unlink, create, ...)
 
328
        """
 
329
        def proxy(*args):
 
330
            """
 
331
            :param args: A list of values for the method
 
332
            """
 
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,
 
339
                                                    self.model_name,
 
340
                                                    method,
 
341
                                                    *args)
 
342
            if method == "read":
 
343
                if isinstance(result, list) and len(result) > 0 and "id" in result[0]:
 
344
                    index = {}
 
345
                    for r in result:
 
346
                        index[r['id']] = r
 
347
                    result = [index[x] for x in args[0]]
 
348
            self.__logger.debug('result: %r', result)
 
349
            return result
 
350
        return proxy
 
351
 
 
352
    def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, context=None):
 
353
        """
 
354
        A shortcut method to combine a search() and a read().
 
355
 
 
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.
 
363
        """
 
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 {})
 
366
        return records
 
367
 
 
368
def get_connector(hostname, protocol="xmlrpc", port="auto"):
 
369
    """
 
370
    A shortcut method to easily create a connector to a remote server using XMLRPC or NetRPC.
 
371
 
 
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.
 
375
    """
 
376
    if port == '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)
 
382
    else:
 
383
        raise ValueError("You must choose xmlrpc or netrpc")
 
384
 
 
385
def get_connection(hostname, protocol="xmlrpc", port='auto', database=None,
 
386
                 login=None, password=None, user_id=None):
 
387
    """
 
388
    A shortcut method to easily create a connection to a remote OpenERP server.
 
389
 
 
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.
 
399
    """
 
400
    return Connection(get_connector(hostname, protocol, port), database, login, password, user_id)
 
401