proteus 7.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
proteus/config.py ADDED
@@ -0,0 +1,423 @@
1
+ # This file is part of Tryton. The COPYRIGHT file at the top level of
2
+ # this repository contains the full copyright notices and license terms.
3
+ """
4
+ Configuration functions for the proteus package for Tryton.
5
+ """
6
+
7
+ import base64
8
+ import datetime
9
+ import os
10
+ import threading
11
+ import urllib.parse
12
+ import xmlrpc.client
13
+ from contextlib import contextmanager
14
+ from decimal import Decimal
15
+
16
+ import defusedxml.xmlrpc
17
+
18
+ __all__ = ['set_trytond', 'set_xmlrpc', 'get_config']
19
+
20
+ defusedxml.xmlrpc.monkey_patch()
21
+
22
+
23
+ def dump_decimal(self, value, write):
24
+ write('<value><bigdecimal>')
25
+ write(str(Decimal(value)))
26
+ write('</bigdecimal></value>')
27
+
28
+
29
+ def dump_date(self, value, write):
30
+ value = {'__class__': 'date',
31
+ 'year': value.year,
32
+ 'month': value.month,
33
+ 'day': value.day,
34
+ }
35
+ self.dump_struct(value, write)
36
+
37
+
38
+ def dump_time(self, value, write):
39
+ value = {'__class__': 'time',
40
+ 'hour': value.hour,
41
+ 'minute': value.minute,
42
+ 'second': value.second,
43
+ 'microsecond': value.microsecond,
44
+ }
45
+ self.dump_struct(value, write)
46
+
47
+
48
+ def dump_timedelta(self, value, write):
49
+ value = {'__class__': 'timedelta',
50
+ 'seconds': value.total_seconds(),
51
+ }
52
+ self.dump_struct(value, write)
53
+
54
+
55
+ def dump_long(self, value, write):
56
+ try:
57
+ self.dump_long(value, write)
58
+ except OverflowError:
59
+ write('<value><biginteger>')
60
+ write(str(int(value)))
61
+ write('</biginteger></value>\n')
62
+
63
+
64
+ xmlrpc.client.Marshaller.dispatch[Decimal] = dump_decimal
65
+ xmlrpc.client.Marshaller.dispatch[datetime.date] = dump_date
66
+ xmlrpc.client.Marshaller.dispatch[datetime.time] = dump_time
67
+ xmlrpc.client.Marshaller.dispatch[datetime.timedelta] = dump_timedelta
68
+ xmlrpc.client.Marshaller.dispatch[int] = dump_long
69
+
70
+
71
+ def dump_struct(self, value, write, escape=xmlrpc.client.escape):
72
+ converted_value = {}
73
+ for k, v in value.items():
74
+ if isinstance(k, int):
75
+ k = str(k)
76
+ elif isinstance(k, float):
77
+ k = repr(k)
78
+ converted_value[k] = v
79
+ return self.dump_struct(converted_value, write, escape=escape)
80
+
81
+
82
+ xmlrpc.client.Marshaller.dispatch[dict] = dump_struct
83
+
84
+
85
+ class XMLRPCDecoder(object):
86
+
87
+ decoders = {}
88
+
89
+ @classmethod
90
+ def register(cls, klass, decoder):
91
+ assert klass not in cls.decoders
92
+ cls.decoders[klass] = decoder
93
+
94
+ def __call__(self, dct):
95
+ if dct.get('__class__') in self.decoders:
96
+ return self.decoders[dct['__class__']](dct)
97
+ return dct
98
+
99
+
100
+ XMLRPCDecoder.register('date',
101
+ lambda dct: datetime.date(dct['year'], dct['month'], dct['day']))
102
+ XMLRPCDecoder.register('time',
103
+ lambda dct: datetime.time(dct['hour'], dct['minute'], dct['second'],
104
+ dct['microsecond']))
105
+ XMLRPCDecoder.register('timedelta',
106
+ lambda dct: datetime.timedelta(seconds=dct['seconds']))
107
+ XMLRPCDecoder.register('Decimal', lambda dct: Decimal(dct['decimal']))
108
+
109
+
110
+ def end_struct(self, data):
111
+ mark = self._marks.pop()
112
+ # map structs to Python dictionaries
113
+ dct = {}
114
+ items = self._stack[mark:]
115
+ for i in range(0, len(items), 2):
116
+ dct[items[i]] = items[i + 1]
117
+ dct = XMLRPCDecoder()(dct)
118
+ self._stack[mark:] = [dct]
119
+ self._value = 0
120
+
121
+
122
+ xmlrpc.client.Unmarshaller.dispatch['struct'] = end_struct
123
+
124
+ _CONFIG = threading.local()
125
+ _CONFIG.current = None
126
+
127
+
128
+ class ContextManager(object):
129
+ 'Context Manager for the tryton context'
130
+
131
+ def __init__(self, config):
132
+ self.config = config
133
+ self.context = config.context
134
+
135
+ def __enter__(self):
136
+ return self
137
+
138
+ def __exit__(self, exc_type, exc_value, traceback):
139
+ self.config._context = self.context
140
+
141
+
142
+ class Config(object):
143
+ 'Config interface'
144
+
145
+ def __init__(self):
146
+ super().__init__()
147
+ self._context = {}
148
+
149
+ @property
150
+ def context(self):
151
+ return self._context.copy()
152
+
153
+ def set_context(self, context=None, **kwargs):
154
+ ctx_manager = ContextManager(self)
155
+
156
+ if context is None:
157
+ context = {}
158
+ self._context = self.context
159
+ self._context.update(context)
160
+ self._context.update(kwargs)
161
+ return ctx_manager
162
+
163
+ def reset_context(self):
164
+ ctx_manager = ContextManager(self)
165
+ self._context = {}
166
+ return ctx_manager
167
+
168
+ def get_proxy(self, name):
169
+ raise NotImplementedError
170
+
171
+ def get_proxy_methods(self, name):
172
+ raise NotImplementedError
173
+
174
+
175
+ class _TrytondMethod(object):
176
+
177
+ def __init__(self, name, model, config):
178
+ super().__init__()
179
+ self._name = name
180
+ self._object = model
181
+ self._config = config
182
+
183
+ def __call__(self, *args, **kwargs):
184
+ from trytond.rpc import RPC, RPCReturnException
185
+ from trytond.tools import is_instance_method
186
+ from trytond.transaction import Transaction, TransactionError
187
+ from trytond.worker import run_task
188
+
189
+ if self._name in self._object.__rpc__:
190
+ rpc = self._object.__rpc__[self._name]
191
+ elif self._name in getattr(self._object, '_buttons', {}):
192
+ rpc = RPC(readonly=False, instantiate=0)
193
+ else:
194
+ raise TypeError('%s is not callable' % self._name)
195
+
196
+ extras = {}
197
+ while True:
198
+ with Transaction().start(self._config.database_name,
199
+ self._config.user, readonly=rpc.readonly,
200
+ **extras) as transaction:
201
+ try:
202
+ (c_args, c_kwargs,
203
+ transaction.context, transaction.timestamp) \
204
+ = rpc.convert(self._object, *args, **kwargs)
205
+ if self._config.skip_warning:
206
+ transaction.context['_skip_warnings'] = True
207
+ meth = getattr(self._object, self._name)
208
+ if (rpc.instantiate is None
209
+ or not is_instance_method(
210
+ self._object, self._name)):
211
+ result = rpc.result(meth(*c_args, **c_kwargs))
212
+ else:
213
+ assert rpc.instantiate == 0
214
+ inst = c_args.pop(0)
215
+ if hasattr(inst, self._name):
216
+ result = rpc.result(
217
+ meth(inst, *c_args, **c_kwargs))
218
+ else:
219
+ result = [rpc.result(meth(i, *c_args, **c_kwargs))
220
+ for i in inst]
221
+ except TransactionError as e:
222
+ transaction.rollback()
223
+ e.fix(extras)
224
+ continue
225
+ except RPCReturnException as e:
226
+ transaction.rollback()
227
+ transaction.tasks.clear()
228
+ result = e.result()
229
+ transaction.commit()
230
+ break
231
+ while transaction.tasks:
232
+ task_id = transaction.tasks.pop()
233
+ run_task(self._config.database_name, task_id)
234
+ return result
235
+
236
+
237
+ class TrytondProxy(object):
238
+ 'Proxy for function call for trytond'
239
+
240
+ def __init__(self, name, config, type='model'):
241
+ super().__init__()
242
+ self._config = config
243
+ self._object = config.pool.get(name, type=type)
244
+ __init__.__doc__ = object.__init__.__doc__
245
+
246
+ def __getattr__(self, name):
247
+ 'Return attribute value'
248
+ return _TrytondMethod(name, self._object, self._config)
249
+
250
+
251
+ class TrytondConfig(Config):
252
+ 'Configuration for trytond'
253
+
254
+ def __init__(self, database=None, user='admin', config_file=None):
255
+ super().__init__()
256
+ if not database:
257
+ database = os.environ.get('TRYTOND_DATABASE_URI')
258
+ elif (os.environ.get('TRYTOND_DATABASE_URI')
259
+ and not urllib.parse.urlparse(database).scheme):
260
+ url = urllib.parse.urlparse(os.environ['TRYTOND_DATABASE_URI'])
261
+ os.environ['TRYTOND_DATABASE_URI'] = urllib.parse.urlunparse(
262
+ url._replace(path=database))
263
+ else:
264
+ os.environ['TRYTOND_DATABASE_URI'] = database
265
+ if not config_file:
266
+ config_file = os.environ.get('TRYTOND_CONFIG')
267
+ import trytond.config as config
268
+ config.update_etc(config_file)
269
+ from trytond.pool import Pool
270
+ from trytond.transaction import Transaction
271
+ self.database = database
272
+ database_name = None
273
+ if database:
274
+ uri = urllib.parse.urlparse(database)
275
+ database_name = uri.path.strip('/')
276
+ if not database_name:
277
+ database_name = os.environ['DB_NAME']
278
+ self.database_name = database_name
279
+ self._user = user
280
+ self.config_file = config_file
281
+ self.skip_warning = False
282
+
283
+ Pool.start()
284
+ self.pool = Pool(database_name)
285
+ self.pool.init()
286
+
287
+ with Transaction().start(self.database_name, 0) as transaction:
288
+ User = self.pool.get('res.user')
289
+ transaction.context = self.context
290
+ with transaction.set_context(active_test=False):
291
+ self.user = User.search([
292
+ ('login', '=', user),
293
+ ], limit=1)[0].id
294
+ with transaction.set_user(self.user):
295
+ self._context = User.get_preferences(context_only=True)
296
+ __init__.__doc__ = object.__init__.__doc__
297
+
298
+ def __repr__(self):
299
+ return ("proteus.config.TrytondConfig"
300
+ "(%s, %s, config_file=%s)"
301
+ % (repr(self.database), repr(self._user), repr(self.config_file)))
302
+ __repr__.__doc__ = object.__repr__.__doc__
303
+
304
+ def __eq__(self, other):
305
+ if not isinstance(other, TrytondConfig):
306
+ raise NotImplementedError
307
+ return (self.database_name == other.database_name
308
+ and self._user == other._user
309
+ and self.database == other.database
310
+ and self.config_file == other.config_file)
311
+
312
+ def __hash__(self):
313
+ return hash((self.database_name, self._user,
314
+ self.database, self.config_file))
315
+
316
+ def get_proxy(self, name, type='model'):
317
+ 'Return Proxy class'
318
+ return TrytondProxy(name, self, type=type)
319
+
320
+ def get_proxy_methods(self, name, type='model'):
321
+ 'Return list of methods'
322
+ proxy = self.get_proxy(name, type=type)
323
+ methods = [x for x in proxy._object.__rpc__]
324
+ if hasattr(proxy._object, '_buttons'):
325
+ methods += [x for x in proxy._object._buttons]
326
+ return methods
327
+
328
+
329
+ def set_trytond(database=None, user='admin',
330
+ config_file=None):
331
+ 'Set trytond package as backend'
332
+ _CONFIG.current = TrytondConfig(database, user, config_file=config_file)
333
+ return _CONFIG.current
334
+
335
+
336
+ class XmlrpcProxy(object):
337
+ 'Proxy for function call for XML-RPC'
338
+
339
+ def __init__(self, name, config, type='model'):
340
+ super().__init__()
341
+ self._config = config
342
+ self._object = getattr(config.server, '%s.%s' % (type, name))
343
+ __init__.__doc__ = object.__init__.__doc__
344
+
345
+ def __getattr__(self, name):
346
+ 'Return attribute value'
347
+ return getattr(self._object, name)
348
+
349
+
350
+ class XmlrpcConfig(Config):
351
+ 'Configuration for XML-RPC'
352
+
353
+ def __init__(self, url, **kwargs):
354
+ super().__init__()
355
+ self.url = url
356
+ self.server = xmlrpc.client.ServerProxy(
357
+ url, allow_none=True, use_builtin_types=True, **kwargs)
358
+ # TODO add user
359
+ self.user = None
360
+ self._context = self.server.model.res.user.get_preferences(True, {})
361
+ __init__.__doc__ = object.__init__.__doc__
362
+
363
+ def __repr__(self):
364
+ return "proteus.config.XmlrpcConfig(%s)" % repr(self.url)
365
+ __repr__.__doc__ = object.__repr__.__doc__
366
+
367
+ def __eq__(self, other):
368
+ if not isinstance(other, XmlrpcConfig):
369
+ raise NotImplementedError
370
+ return self.url == other.url
371
+
372
+ def __hash__(self):
373
+ return hash(self.url)
374
+
375
+ def get_proxy(self, name, type='model'):
376
+ 'Return Proxy class'
377
+ return XmlrpcProxy(name, self, type=type)
378
+
379
+ def get_proxy_methods(self, name, type='model'):
380
+ 'Return list of methods'
381
+ object_ = '%s.%s' % (type, name)
382
+ return [x[len(object_) + 1:]
383
+ for x in self.server.system.listMethods()
384
+ if x.startswith(object_)
385
+ and '.' not in x[len(object_) + 1:]]
386
+
387
+
388
+ def set_xmlrpc(url, **kwargs):
389
+ '''
390
+ Set XML-RPC as backend.
391
+ It pass the keyword arguments received to xmlrpclib.ServerProxy()
392
+ '''
393
+ _CONFIG.current = XmlrpcConfig(url, **kwargs)
394
+ return _CONFIG.current
395
+
396
+
397
+ @contextmanager
398
+ def set_xmlrpc_session(
399
+ url, username, password=None, parameters=None, **kwargs):
400
+ """
401
+ Set XML-RPC as backend using session.
402
+ """
403
+ if parameters is None:
404
+ parameters = {}
405
+ else:
406
+ parameters = parameters.copy()
407
+ if password:
408
+ parameters['password'] = password
409
+ server = xmlrpc.client.ServerProxy(
410
+ url, allow_none=True, use_builtin_types=True, **kwargs)
411
+ user_id, session, _ = server.common.db.login(username, parameters)
412
+ session = ':'.join(map(str, [username, user_id, session]))
413
+ auth = base64.encodebytes(session.encode('utf-8')).decode('ascii')
414
+ auth = ''.join(auth.split()) # get rid of whitespace
415
+ kwargs.setdefault('headers', []).append(
416
+ ('Authorization', 'Session ' + auth))
417
+ config = set_xmlrpc(url, **kwargs)
418
+ yield config
419
+ config.server.common.db.logout()
420
+
421
+
422
+ def get_config():
423
+ return _CONFIG.current