OLD | NEW |
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 from trytond import __version__ as trytond_version | 10 from trytond import __version__ as trytond_version |
11 from trytond.config import config | 11 from trytond.config import config |
12 | 12 |
13 from trytond.exceptions import UserError, UserWarning, ConcurrencyException | 13 from trytond.exceptions import UserError, UserWarning, ConcurrencyException |
14 | 14 |
15 trytond_version = tuple(map(int, trytond_version.split('.'))) | 15 trytond_version = tuple(map(int, trytond_version.split('.'))) |
16 __version__ = '0.10.1' | 16 __version__ = '0.10.1' |
17 __all__ = ['Tryton', 'tryton_transaction'] | 17 __all__ = ['Tryton', 'tryton_transaction'] |
18 | 18 |
19 | 19 |
20 def retry_transaction(retry): | 20 def retry_transaction(func): |
21 """Decorator to retry a transaction if failed. The decorated method | 21 """Decorator to retry a transaction if failed. The decorated method |
22 will be run retry times in case of DatabaseOperationalError. | 22 will be run retry times in case of DatabaseOperationalError. |
23 """ | 23 """ |
24 from trytond import backend | 24 from trytond import backend |
25 from trytond.transaction import Transaction | 25 from trytond.transaction import Transaction |
26 try: | 26 try: |
27 DatabaseOperationalError = backend.DatabaseOperationalError | 27 DatabaseOperationalError = backend.DatabaseOperationalError |
28 except AttributeError: | 28 except AttributeError: |
29 DatabaseOperationalError = backend.get('DatabaseOperationalError') | 29 DatabaseOperationalError = backend.get('DatabaseOperationalError') |
30 | 30 |
31 def decorator(func): | 31 @wraps(func) |
32 @wraps(func) | 32 def wrapper(*args, **kwargs): |
33 def wrapper(*args, **kwargs): | 33 tryton = current_app.extensions['Tryton'] |
34 for count in range(retry, -1, -1): | 34 retry = tryton.database_retry |
35 try: | 35 for count in range(retry, -1, -1): |
36 return func(*args, **kwargs) | 36 try: |
37 except DatabaseOperationalError: | 37 return func(*args, **kwargs) |
38 if count and not Transaction().readonly: | 38 except DatabaseOperationalError: |
39 continue | 39 if count and not Transaction().readonly: |
40 raise | 40 continue |
41 return wrapper | 41 raise |
42 return decorator | 42 return wrapper |
43 | 43 |
44 | 44 |
45 class Tryton(object): | 45 class Tryton(object): |
46 "Control the Tryton integration to one or more Flask applications." | 46 "Control the Tryton integration to one or more Flask applications." |
47 def __init__(self, app=None, configure_jinja=False): | 47 def __init__(self, app=None, configure_jinja=False): |
48 self.context_callback = None | 48 self.context_callback = None |
49 self.database_retry = None | 49 self.database_retry = None |
50 self._configure_jinja = configure_jinja | 50 self._configure_jinja = configure_jinja |
51 if app is not None: | 51 if app is not None: |
52 self.init_app(app) | 52 self.init_app(app) |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
122 return str(value) | 122 return str(value) |
123 if lang is None: | 123 if lang is None: |
124 lang = self.language | 124 lang = self.language |
125 return Report.format_timedelta( | 125 return Report.format_timedelta( |
126 value, converter=converter, lang=lang, *args, **kwargs) | 126 value, converter=converter, lang=lang, *args, **kwargs) |
127 | 127 |
128 def _readonly(self): | 128 def _readonly(self): |
129 return not (request | 129 return not (request |
130 and request.method in ('PUT', 'POST', 'DELETE', 'PATCH')) | 130 and request.method in ('PUT', 'POST', 'DELETE', 'PATCH')) |
131 | 131 |
132 def transaction(self, readonly=None, user=None, context=None): | 132 @staticmethod |
| 133 def transaction(readonly=None, user=None, context=None): |
133 """Decorator to run inside a Tryton transaction. | 134 """Decorator to run inside a Tryton transaction. |
134 The decorated method could be run multiple times in case of | 135 The decorated method could be run multiple times in case of |
135 database operational error. | 136 database operational error. |
136 | 137 |
137 If readonly is None then the transaction will be readonly except for | 138 If readonly is None then the transaction will be readonly except for |
138 PUT, POST, DELETE and PATCH request methods. | 139 PUT, POST, DELETE and PATCH request methods. |
139 | 140 |
140 If user is None then TRYTON_USER will be used. | 141 If user is None then TRYTON_USER will be used. |
141 | 142 |
142 readonly, user and context can also be callable. | 143 readonly, user and context can also be callable. |
143 """ | 144 """ |
144 from trytond import backend | 145 from trytond import backend |
145 from trytond.cache import Cache | 146 from trytond.cache import Cache |
146 from trytond.transaction import Transaction | 147 from trytond.transaction import Transaction |
147 try: | 148 try: |
148 DatabaseOperationalError = backend.DatabaseOperationalError | 149 DatabaseOperationalError = backend.DatabaseOperationalError |
149 except AttributeError: | 150 except AttributeError: |
150 DatabaseOperationalError = backend.get('DatabaseOperationalError') | 151 DatabaseOperationalError = backend.get('DatabaseOperationalError') |
151 | 152 |
152 def get_value(value): | 153 def get_value(value): |
153 return value() if callable(value) else value | 154 return value() if callable(value) else value |
154 | 155 |
155 def instanciate(value): | 156 def instanciate(value): |
156 if isinstance(value, _BaseProxy): | 157 if isinstance(value, _BaseProxy): |
157 return value() | 158 return value() |
158 return value | 159 return value |
159 | 160 |
160 def decorator(func): | 161 def decorator(func): |
161 @retry_transaction(self.database_retry) | 162 @retry_transaction |
162 @wraps(func) | 163 @wraps(func) |
163 def wrapper(*args, **kwargs): | 164 def wrapper(*args, **kwargs): |
164 tryton = current_app.extensions['Tryton'] | 165 tryton = current_app.extensions['Tryton'] |
165 database = current_app.config['TRYTON_DATABASE'] | 166 database = current_app.config['TRYTON_DATABASE'] |
166 if (5, 1) > trytond_version: | 167 if (5, 1) > trytond_version: |
167 with Transaction().start(database, 0): | 168 with Transaction().start(database, 0): |
168 Cache.clean(database) | 169 Cache.clean(database) |
169 if user is None: | 170 if user is None: |
170 transaction_user = get_value( | 171 transaction_user = get_value( |
171 int(current_app.config['TRYTON_USER'])) | 172 int(current_app.config['TRYTON_USER'])) |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
279 | 280 |
280 def __init__(self, map, model): | 281 def __init__(self, map, model): |
281 super(RecordsConverter, self).__init__(map) | 282 super(RecordsConverter, self).__init__(map) |
282 self.model = model | 283 self.model = model |
283 | 284 |
284 def to_python(self, value): | 285 def to_python(self, value): |
285 return _RecordsProxy(self.model, map(int, value.split(','))) | 286 return _RecordsProxy(self.model, map(int, value.split(','))) |
286 | 287 |
287 def to_url(self, value): | 288 def to_url(self, value): |
288 return ','.join(map(str, map(int, value))) | 289 return ','.join(map(str, map(int, value))) |
OLD | NEW |