Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 # This file is part of flask_tryton. The COPYRIGHT file at the top level of | 1 # This file is part of flask_tryton. The COPYRIGHT file at the top level of |
2 # this repository contains the full copyright notices and license terms. | 2 # this repository contains the full copyright notices and license terms. |
3 | 3 |
4 from functools import wraps | 4 from functools import wraps |
5 | 5 |
6 from flask import request, current_app | 6 from flask import request, current_app |
7 from werkzeug.routing import BaseConverter | 7 from werkzeug.routing import BaseConverter |
8 from werkzeug.exceptions import BadRequest | 8 from werkzeug.exceptions import BadRequest |
9 | 9 |
10 try: | 10 try: |
11 from trytond import __version__ as trytond_version | 11 from trytond import __version__ as trytond_version |
12 except ImportError: | 12 except ImportError: |
13 from trytond.version import VERSION as trytond_version | 13 from trytond.version import VERSION as trytond_version |
14 trytond_version = tuple(map(int, trytond_version.split('.'))) | 14 trytond_version = tuple(map(int, trytond_version.split('.'))) |
15 try: | 15 try: |
16 from trytond.config import config | 16 from trytond.config import config |
17 except ImportError: | 17 except ImportError: |
18 from trytond.config import CONFIG as config | 18 from trytond.config import CONFIG as config |
19 | 19 |
20 from trytond.pool import Pool | 20 from trytond.pool import Pool |
21 from trytond.transaction import Transaction | 21 from trytond.transaction import Transaction |
22 from trytond.cache import Cache | 22 from trytond.cache import Cache |
23 from trytond import backend | 23 from trytond import backend |
24 from trytond.exceptions import UserError, UserWarning, ConcurrencyException | 24 from trytond.exceptions import UserError, UserWarning, ConcurrencyException |
25 | 25 |
26 __version__ = '0.8.1' | 26 __version__ = '0.8.1' |
27 __all__ = ['Tryton', 'tryton_transaction'] | 27 __all__ = ['Tryton', 'tryton_transaction'] |
28 | 28 |
29 | 29 |
30 if trytond_version >= (3, 3): | 30 def retry_transaction(retry): |
31 retry = config.getint('database', 'retry') | 31 """Decorator to retry a transaction if failed. The decorated method |
ced
2019/11/27 18:55:35
This can not be parsed at module import because co
pokoli
2019/11/28 17:14:00
Done.
| |
32 else: | 32 will be run retry times in case of DatabaseOperationalError. |
33 retry = int(config['retry']) | 33 """ |
34 DatabaseOperationalError = backend.get('DatabaseOperationalError') | |
35 | |
36 def decorator(func): | |
37 @wraps(func) | |
38 def wrapper(*args, **kwargs): | |
39 for count in range(retry, -1, -1): | |
40 try: | |
41 return func(*args, **kwargs) | |
42 except DatabaseOperationalError: | |
43 if count and not Transaction().readonly: | |
44 continue | |
45 raise | |
46 return wrapper | |
47 return decorator | |
34 | 48 |
35 | 49 |
36 class Tryton(object): | 50 class Tryton(object): |
37 "Control the Tryton integration to one or more Flask applications." | 51 "Control the Tryton integration to one or more Flask applications." |
38 def __init__(self, app=None): | 52 def __init__(self, app=None): |
39 self.context_callback = None | 53 self.context_callback = None |
54 self.database_retry = None | |
40 if app is not None: | 55 if app is not None: |
41 self.init_app(app) | 56 self.init_app(app) |
42 | 57 |
43 def init_app(self, app): | 58 def init_app(self, app): |
44 "Initialize an application for the use with this Tryton setup." | 59 "Initialize an application for the use with this Tryton setup." |
45 database = app.config.setdefault('TRYTON_DATABASE', None) | 60 database = app.config.setdefault('TRYTON_DATABASE', None) |
46 user = app.config.setdefault('TRYTON_USER', 0) | 61 user = app.config.setdefault('TRYTON_USER', 0) |
47 configfile = app.config.setdefault('TRYTON_CONFIG', None) | 62 configfile = app.config.setdefault('TRYTON_CONFIG', None) |
48 | 63 |
49 config.update_etc(configfile) | 64 config.update_etc(configfile) |
50 | 65 |
51 # 3.0 compatibility | 66 # 3.0 compatibility |
52 if hasattr(config, 'set_timezone'): | 67 if hasattr(config, 'set_timezone'): |
53 config.set_timezone() | 68 config.set_timezone() |
69 if trytond_version >= (3, 3): | |
70 self.database_retry = config.getint('database', 'retry') | |
71 else: | |
72 self.database_retry = int(config['retry']) | |
54 | 73 |
55 self.pool = Pool(database) | 74 self.pool = Pool(database) |
56 with Transaction().start(database, user, readonly=True): | 75 with Transaction().start(database, user, readonly=True): |
57 self.pool.init() | 76 self.pool.init() |
58 | 77 |
59 if not hasattr(app, 'extensions'): | 78 if not hasattr(app, 'extensions'): |
60 app.extensions = {} | 79 app.extensions = {} |
61 app.extensions['Tryton'] = self | 80 app.extensions['Tryton'] = self |
62 app.url_map.converters['record'] = RecordConverter | 81 app.url_map.converters['record'] = RecordConverter |
63 app.url_map.converters['records'] = RecordsConverter | 82 app.url_map.converters['records'] = RecordsConverter |
(...skipping 23 matching lines...) Expand all Loading... | |
87 DatabaseOperationalError = backend.get('DatabaseOperationalError') | 106 DatabaseOperationalError = backend.get('DatabaseOperationalError') |
88 | 107 |
89 def get_value(value): | 108 def get_value(value): |
90 return value() if callable(value) else value | 109 return value() if callable(value) else value |
91 | 110 |
92 def instanciate(value): | 111 def instanciate(value): |
93 if isinstance(value, _BaseProxy): | 112 if isinstance(value, _BaseProxy): |
94 return value() | 113 return value() |
95 return value | 114 return value |
96 | 115 |
97 def retry_transaction(func): | |
ced
2019/11/27 18:55:35
I think this method could be at top level and take
pokoli
2019/11/28 17:14:00
Done.
| |
98 @wraps(func) | |
99 def wrapper(*args, **kwargs): | |
100 for count in range(retry, -1, -1): | |
101 try: | |
102 return func(*args, **kwargs) | |
103 except DatabaseOperationalError: | |
104 if count: | |
ced
2019/11/27 18:55:35
I think we should keep the is_readonly test. The v
pokoli
2019/11/28 17:14:00
Done.
| |
105 continue | |
106 raise | |
107 return wrapper | |
108 | |
109 def decorator(func): | 116 def decorator(func): |
110 @retry_transaction | 117 @retry_transaction(self.database_retry) |
111 @wraps(func) | 118 @wraps(func) |
ced
2019/11/27 18:55:35
I'm wondering if it is the proper decorator order.
pokoli
2019/11/28 17:14:00
I've done some testing and yes it is.
| |
112 def wrapper(*args, **kwargs): | 119 def wrapper(*args, **kwargs): |
113 tryton = current_app.extensions['Tryton'] | 120 tryton = current_app.extensions['Tryton'] |
114 database = current_app.config['TRYTON_DATABASE'] | 121 database = current_app.config['TRYTON_DATABASE'] |
115 if (5, 1) > trytond_version >= (3, 3): | 122 if (5, 1) > trytond_version >= (3, 3): |
116 with Transaction().start(database, 0): | 123 with Transaction().start(database, 0): |
117 Cache.clean(database) | 124 Cache.clean(database) |
118 elif trytond_version < (3, 3): | 125 elif trytond_version < (3, 3): |
119 Cache.clean(database) | 126 Cache.clean(database) |
120 | 127 |
121 if user is None: | 128 if user is None: |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
231 | 238 |
232 def __init__(self, map, model): | 239 def __init__(self, map, model): |
233 super(RecordsConverter, self).__init__(map) | 240 super(RecordsConverter, self).__init__(map) |
234 self.model = model | 241 self.model = model |
235 | 242 |
236 def to_python(self, value): | 243 def to_python(self, value): |
237 return _RecordsProxy(self.model, map(int, value.split(','))) | 244 return _RecordsProxy(self.model, map(int, value.split(','))) |
238 | 245 |
239 def to_url(self, value): | 246 def to_url(self, value): |
240 return ','.join(map(int, value)) | 247 return ','.join(map(int, value)) |
LEFT | RIGHT |