Skip to content

Commit 2987f98

Browse files
committed
Merge pull request #169 from acsone/9.0-from-8.0
9.0 from 8.0
2 parents 21bb854 + bdf9bbf commit 2987f98

40 files changed

+1303
-911
lines changed

connector/AUTHORS

+4
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@
1818
* Leonardo Donelli at MONK Software
1919
* Mathias Colpaert
2020
* Yannick Vaucher at Camptocamp
21+
* Nicolas Piganeau at NDP Systèmes
22+
* Florent Thomas at Mind And Go
23+
* Matthieu Dietrich at Camptocamp
24+
* Olivier Laurent at Acsone

connector/CHANGES.rst

+26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
Changelog
22
---------
33

4+
.. Future (?)
5+
.. ~~~~~~~~~~
6+
..
7+
.. *
8+
9+
10+
8.0.3.3.0 (2016-02-29)
11+
~~~~~~~~~~~~~~~~~~~~~~
12+
13+
* Allow to define seconds when raising a RetryableJobError (https://github.com/OCA/connector/pull/124)
14+
* Allow to ignore the retry counter when raising a RetryableJobError (https://github.com/OCA/connector/pull/124)
15+
* Add 'mock_job_delay_to_direct' to ease tests on jobs (https://github.com/OCA/connector/pull/123)
16+
* Add helper function to acquire Posgres advisory locks (https://github.com/OCA/connector/pull/138, https://github.com/OCA/connector/pull/139)
17+
* Improvement of 'is_module_installed' which now uses the registry instead of db + cache (https://github.com/OCA/connector/pull/130)
18+
* Security: Prevent to unpickle globals which are not jobs or whitelisted types (https://github.com/OCA/connector/pull/170)
19+
* Fix: Manage non-ascii Postgres errors (https://github.com/OCA/connector/pull/167)
20+
* Fix: ignore dbfilter containing %d or %h (https://github.com/OCA/connector/pull/166)
21+
* Fix: correctly obtain the list of database with odoo is started with --no-database-list (https://github.com/OCA/connector/pull/164)
22+
* Fix: Set job back to 'pending' in case of exception (https://github.com/OCA/connector/pull/150, https://github.com/OCA/connector/pull/151, https://github.com/OCA/connector/pull/152, https://github.com/OCA/connector/pull/155)
23+
* Fix: Clear environment caches and recomputations upon failures (https://github.com/OCA/connector/pull/131)
24+
* Fix: when a job fails, inactive users are no longer added to its followers (https://github.com/OCA/connector/pull/137)
25+
* Fix: Set job to failed after non-retryable OperationalError (https://github.com/OCA/connector/pull/132)
26+
* Fix: wrong model in connector_base_product's views (https://github.com/OCA/connector/pull/119)
27+
* Various documentation improvements
28+
29+
430
3.2.0 (2015-09-10)
531
~~~~~~~~~~~~~~~~~~
632

connector/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@
77
from . import checkpoint
88
from . import controllers
99
from . import jobrunner
10-
from . import ir_module_module

connector/__openerp__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
##############################################################################
2121

2222
{'name': 'Connector',
23-
'version': '8.0.3.2.0',
23+
'version': '9.0.1.0.0',
2424
'author': 'Camptocamp,Openerp Connector Core Editors,'
2525
'Odoo Community Association (OCA)',
2626
'website': 'http://odoo-connector.com',

connector/connector.py

+111-60
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
#
2020
##############################################################################
2121

22+
import hashlib
2223
import logging
24+
import struct
25+
2326
from contextlib import contextmanager
2427
from openerp import models, fields
2528

26-
from .deprecate import log_deprecate, DeprecatedClass
29+
from .exception import RetryableJobError
2730

2831
_logger = logging.getLogger(__name__)
2932

@@ -47,33 +50,14 @@ def _get_openerp_module_name(module_path):
4750
return module_name
4851

4952

50-
def install_in_connector():
51-
log_deprecate("This call to 'install_in_connector()' has no effect and is "
52-
"not required.")
53-
54-
5553
def is_module_installed(env, module_name):
5654
""" Check if an Odoo addon is installed.
5755
58-
The function might be called before `connector` is even installed;
59-
in such case, `ir_module_module.is_module_installed()` is not available yet
60-
and this is why we first check the installation of `connector` by looking
61-
up for a model in the registry.
62-
63-
:param module_name: name of the addon to check being 'connector' or
64-
an addon depending on it
65-
56+
:param module_name: name of the addon
6657
"""
67-
if env.registry.get('connector.backend'):
68-
if module_name == 'connector':
69-
# fast-path: connector is necessarily installed because
70-
# the model is in the registry
71-
return True
72-
# for another addon, check in ir.module.module
73-
return env['ir.module.module'].is_module_installed(module_name)
74-
75-
# connector module is not installed neither any sub-addons
76-
return False
58+
# the registry maintains a set of fully loaded modules so we can
59+
# lookup for our module there
60+
return module_name in env.registry._init_modules
7761

7862

7963
def get_openerp_module(cls_or_func):
@@ -94,11 +78,6 @@ class MetaConnectorUnit(type):
9478
the state of the module (installed or not).
9579
"""
9680

97-
@property
98-
def model_name(cls):
99-
log_deprecate('renamed to for_model_names')
100-
return cls.for_model_names
101-
10281
@property
10382
def for_model_names(cls):
10483
""" Returns the list of models on which a
@@ -149,11 +128,6 @@ def __init__(self, connector_env):
149128
self.backend_record = self.connector_env.backend_record
150129
self.session = self.connector_env.session
151130

152-
@property
153-
def environment(self):
154-
log_deprecate('renamed to connector_env')
155-
return self.connector_env
156-
157131
@classmethod
158132
def match(cls, session, model):
159133
""" Returns True if the current class correspond to the
@@ -222,24 +196,45 @@ def unit_for(self, connector_unit_class, model=None):
222196

223197
return env.get_connector_unit(connector_unit_class)
224198

225-
def get_connector_unit_for_model(self, connector_unit_class, model=None):
226-
""" Deprecated in favor of :meth:`~unit_for` """
227-
log_deprecate('renamed to unit_for()')
228-
return self.unit_for(connector_unit_class, model=model)
229-
230199
def binder_for(self, model=None):
231200
""" Returns an new instance of the correct ``Binder`` for
232201
a model """
233202
return self.unit_for(Binder, model)
234203

235-
def get_binder_for_model(self, model=None):
236-
""" Returns an new instance of the correct ``Binder`` for
237-
a model
204+
def advisory_lock_or_retry(self, lock, retry_seconds=1):
205+
""" Acquire a Postgres transactional advisory lock or retry job
206+
207+
When the lock cannot be acquired, it raises a
208+
``RetryableJobError`` so the job is retried after n
209+
``retry_seconds``.
210+
211+
Usage example:
238212
239-
Deprecated, use ``binder_for`` now.
213+
::
214+
215+
lock_name = 'import_record({}, {}, {}, {})'.format(
216+
self.backend_record._name,
217+
self.backend_record.id,
218+
self.model._name,
219+
self.external_id,
220+
)
221+
self.advisory_lock_or_retry(lock_name, retry_seconds=2)
222+
223+
See :func:``openerp.addons.connector.connector.pg_try_advisory_lock``
224+
for details.
225+
226+
:param lock: The lock name. Can be anything convertible to a
227+
string. It needs to represent what should not be synchronized
228+
concurrently, usually the string will contain at least: the
229+
action, the backend type, the backend id, the model name, the
230+
external id
231+
:param retry_seconds: number of seconds after which a job should
232+
be retried when the lock cannot be acquired.
240233
"""
241-
log_deprecate('renamed to binder_for()')
242-
return self.binder_for(model=model)
234+
if not pg_try_advisory_lock(self.env, lock):
235+
raise RetryableJobError('Could not acquire advisory lock',
236+
seconds=retry_seconds,
237+
ignore_retry=True)
243238

244239

245240
class ConnectorEnvironment(object):
@@ -304,18 +299,6 @@ def pool(self):
304299
def env(self):
305300
return self.session.env
306301

307-
@contextmanager
308-
def set_lang(self, code):
309-
""" Change the working language in the environment.
310-
311-
It changes the ``lang`` key in the session's context.
312-
313-
314-
"""
315-
raise DeprecationWarning('ConnectorEnvironment.set_lang has been '
316-
'deprecated. session.change_context should '
317-
'be used instead.')
318-
319302
def get_connector_unit(self, base_class):
320303
""" Searches and returns an instance of the
321304
:py:class:`~connector.connector.ConnectorUnit` for the current
@@ -354,9 +337,6 @@ def create_environment(cls, backend_record, session, model,
354337
else:
355338
return cls(backend_record, session, model)
356339

357-
Environment = DeprecatedClass('Environment',
358-
ConnectorEnvironment)
359-
360340

361341
class Binder(ConnectorUnit):
362342
""" For one record of a model, capable to find an external or
@@ -480,3 +460,74 @@ def unwrap_model(self):
480460
'Cannot unwrap model %s, because it has no %s fields'
481461
% (self.model._name, self._openerp_field))
482462
return column.comodel_name
463+
464+
465+
def pg_try_advisory_lock(env, lock):
466+
""" Try to acquire a Postgres transactional advisory lock.
467+
468+
The function tries to acquire a lock, returns a boolean indicating
469+
if it could be obtained or not. An acquired lock is released at the
470+
end of the transaction.
471+
472+
A typical use is to acquire a lock at the beginning of an importer
473+
to prevent 2 jobs to do the same import at the same time. Since the
474+
record doesn't exist yet, we can't put a lock on a record, so we put
475+
an advisory lock.
476+
477+
Example:
478+
- Job 1 imports Partner A
479+
- Job 2 imports Partner B
480+
- Partner A has a category X which happens not to exist yet
481+
- Partner B has a category X which happens not to exist yet
482+
- Job 1 import category X as a dependency
483+
- Job 2 import category X as a dependency
484+
485+
Since both jobs are executed concurrently, they both create a record
486+
for category X so we have duplicated records. With this lock:
487+
488+
- Job 1 imports Partner A, it acquires a lock for this partner
489+
- Job 2 imports Partner B, it acquires a lock for this partner
490+
- Partner A has a category X which happens not to exist yet
491+
- Partner B has a category X which happens not to exist yet
492+
- Job 1 import category X as a dependency, it acquires a lock for
493+
this category
494+
- Job 2 import category X as a dependency, try to acquire a lock
495+
but can't, Job 2 is retried later, and when it is retried, it
496+
sees the category X created by Job 1.
497+
498+
The lock is acquired until the end of the transaction.
499+
500+
Usage example:
501+
502+
::
503+
504+
lock_name = 'import_record({}, {}, {}, {})'.format(
505+
self.backend_record._name,
506+
self.backend_record.id,
507+
self.model._name,
508+
self.external_id,
509+
)
510+
if pg_try_advisory_lock(lock_name):
511+
# do sync
512+
else:
513+
raise RetryableJobError('Could not acquire advisory lock',
514+
seconds=2,
515+
ignore_retry=True)
516+
517+
:param env: the Odoo Environment
518+
:param lock: The lock name. Can be anything convertible to a
519+
string. It needs to represents what should not be synchronized
520+
concurrently so usually the string will contain at least: the
521+
action, the backend type, the backend id, the model name, the
522+
external id
523+
:return True/False whether lock was acquired.
524+
"""
525+
hasher = hashlib.sha1()
526+
hasher.update('{}'.format(lock))
527+
# pg_lock accepts an int8 so we build an hash composed with
528+
# contextual information and we throw away some bits
529+
int_lock = struct.unpack('q', hasher.digest()[:8])
530+
531+
env.cr.execute('SELECT pg_try_advisory_xact_lock(%s);', (int_lock,))
532+
acquired = env.cr.fetchone()[0]
533+
return acquired

connector/connector_menu.xml

-5
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@
2323
sequence="14"
2424
parent="menu_queue"/>
2525

26-
<menuitem id="menu_queue_worker"
27-
action="action_queue_worker"
28-
sequence="16"
29-
parent="menu_queue"/>
30-
3126
<menuitem id="menu_queue_job"
3227
action="action_queue_job"
3328
sequence="18"

0 commit comments

Comments
 (0)