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 def retry_transaction(retry): |
| 31 """Decorator to retry a transaction if failed. The decorated method |
| 32 will be run retry times in case of DatabaseOperationalError. |
| 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 |
| 48 |
| 49 |
30 class Tryton(object): | 50 class Tryton(object): |
31 "Control the Tryton integration to one or more Flask applications." | 51 "Control the Tryton integration to one or more Flask applications." |
32 def __init__(self, app=None): | 52 def __init__(self, app=None): |
33 self.context_callback = None | 53 self.context_callback = None |
34 self.database_retry = None | 54 self.database_retry = None |
35 if app is not None: | 55 if app is not None: |
36 self.init_app(app) | 56 self.init_app(app) |
37 | 57 |
38 def init_app(self, app): | 58 def init_app(self, app): |
39 "Initialize an application for the use with this Tryton setup." | 59 "Initialize an application for the use with this Tryton setup." |
(...skipping 23 matching lines...) Expand all Loading... |
63 | 83 |
64 def default_context(self, callback): | 84 def default_context(self, callback): |
65 "Set the callback for the default transaction context" | 85 "Set the callback for the default transaction context" |
66 self.context_callback = callback | 86 self.context_callback = callback |
67 return callback | 87 return callback |
68 | 88 |
69 def _readonly(self): | 89 def _readonly(self): |
70 return not (request | 90 return not (request |
71 and request.method in ('PUT', 'POST', 'DELETE', 'PATCH')) | 91 and request.method in ('PUT', 'POST', 'DELETE', 'PATCH')) |
72 | 92 |
73 def retry_transaction(self, retry=None): | 93 @staticmethod |
74 """Decorator to retry a transaction if failed. The decorated method | 94 def transaction(readonly=None, user=None, context=None): |
75 will be run retry times in case of DatabaseOperationalError | |
76 | |
77 If retry is None the value from tryton configuration will be used | |
78 """ | |
79 DatabaseOperationalError = backend.get('DatabaseOperationalError') | |
80 if retry is None: | |
81 retry = self.database_retry | |
82 | |
83 def decorator(func): | |
84 @wraps(func) | |
85 def wrapper(*args, **kwargs): | |
86 for count in range(retry, -1, -1): | |
87 try: | |
88 return func(*args, **kwargs) | |
89 except DatabaseOperationalError: | |
90 if count and not Transaction().readonly: | |
91 continue | |
92 raise | |
93 return wrapper | |
94 return decorator | |
95 | |
96 def transaction(self, readonly=None, user=None, context=None): | |
97 """Decorator to run inside a Tryton transaction. | 95 """Decorator to run inside a Tryton transaction. |
98 The decorated method could be run multiple times in case of | 96 The decorated method could be run multiple times in case of |
99 database operational error. | 97 database operational error. |
100 | 98 |
101 If readonly is None then the transaction will be readonly except for | 99 If readonly is None then the transaction will be readonly except for |
102 PUT, POST, DELETE and PATCH request methods. | 100 PUT, POST, DELETE and PATCH request methods. |
103 | 101 |
104 If user is None then TRYTON_USER will be used. | 102 If user is None then TRYTON_USER will be used. |
105 | 103 |
106 readonly, user and context can also be callable. | 104 readonly, user and context can also be callable. |
107 """ | 105 """ |
108 DatabaseOperationalError = backend.get('DatabaseOperationalError') | 106 DatabaseOperationalError = backend.get('DatabaseOperationalError') |
109 | 107 |
110 def get_value(value): | 108 def get_value(value): |
111 return value() if callable(value) else value | 109 return value() if callable(value) else value |
112 | 110 |
113 def instanciate(value): | 111 def instanciate(value): |
114 if isinstance(value, _BaseProxy): | 112 if isinstance(value, _BaseProxy): |
115 return value() | 113 return value() |
116 return value | 114 return value |
117 | 115 |
118 def decorator(func): | 116 def decorator(func): |
119 @self.retry_transaction() | 117 @retry_transaction(self.database_retry) |
120 @wraps(func) | 118 @wraps(func) |
121 def wrapper(*args, **kwargs): | 119 def wrapper(*args, **kwargs): |
122 tryton = current_app.extensions['Tryton'] | 120 tryton = current_app.extensions['Tryton'] |
123 database = current_app.config['TRYTON_DATABASE'] | 121 database = current_app.config['TRYTON_DATABASE'] |
124 if (5, 1) > trytond_version >= (3, 3): | 122 if (5, 1) > trytond_version >= (3, 3): |
125 with Transaction().start(database, 0): | 123 with Transaction().start(database, 0): |
126 Cache.clean(database) | 124 Cache.clean(database) |
127 elif trytond_version < (3, 3): | 125 elif trytond_version < (3, 3): |
128 Cache.clean(database) | 126 Cache.clean(database) |
129 | 127 |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
240 | 238 |
241 def __init__(self, map, model): | 239 def __init__(self, map, model): |
242 super(RecordsConverter, self).__init__(map) | 240 super(RecordsConverter, self).__init__(map) |
243 self.model = model | 241 self.model = model |
244 | 242 |
245 def to_python(self, value): | 243 def to_python(self, value): |
246 return _RecordsProxy(self.model, map(int, value.split(','))) | 244 return _RecordsProxy(self.model, map(int, value.split(','))) |
247 | 245 |
248 def to_url(self, value): | 246 def to_url(self, value): |
249 return ','.join(map(int, value)) | 247 return ','.join(map(int, value)) |
LEFT | RIGHT |