Source code for connector.components.core
# Copyright 2017 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
"""
Base Component
==============
The connector proposes a 'base' Component, which can be used in
the ``_inherit`` of your own components. This is not a
requirement. It is already inherited by every component
provided by the Connector.
Components are organized according to different usages. The connector suggests
5 main kinds of Components. Each might have a few different usages. You can be
as creative as you want when it comes to creating new ones though.
One "usage" is responsible for a specific work, and alongside with the
collection (the backend) and the model, the usage will be used to find the
needed component for a task.
Some of the Components have an implementation in the ``Connector`` addon, but
some are empty shells that need to be implemented in the different connectors.
The usual categories are:
:py:class:`~connector.components.binder.Binder`
The ``binders`` give the external ID or Odoo ID from respectively an
Odoo ID or an external ID. A default implementation is available.
Most common usages:
* ``binder``
:py:class:`~connector.components.mapper.Mapper`
The ``mappers`` transform a external record into an Odoo record or
conversely.
Most common usages:
* ``import.mapper``
* ``export.mapper``
:py:class:`~connector.components.backend_adapter.BackendAdapter`
The ``backend.adapters`` implements the discussion with the ``backend's``
APIs. They usually adapt their APIs to a common interface (CRUD).
Most common usages:
* ``backend.adapter``
:py:class:`~connector.components.synchronizer.Synchronizer`
A ``synchronizer`` is the main piece of a synchronization. It
orchestrates the flow of a synchronization and use the other
Components
Most common usages:
* ``record.importer``
* ``record.exporter``
* ``batch.importer``
* ``batch.exporter``
The possibilities for components do not stop there, look at the
:class:`~connector.components.locker.RecordLocker` for an example of
single-purpose, decoupled component.
"""
from odoo.addons.component.core import AbstractComponent
from odoo.addons.queue_job.exception import RetryableJobError
from ..database import pg_try_advisory_lock
[docs]class BaseConnectorComponent(AbstractComponent):
""" Base component for the connector
Is inherited by every components of the Connector (Binder, Mapper, ...)
and adds a few methods which are of common usage in the connectors.
"""
_name = "base.connector"
@property
def backend_record(self):
""" Backend record we are working with """
# backward compatibility
return self.work.collection
[docs] def binder_for(self, model=None):
""" Shortcut to get Binder for a model
Equivalent to: ``self.component(usage='binder', model_name='xxx')``
"""
return self.component(usage="binder", model_name=model)
[docs] def advisory_lock_or_retry(self, lock, retry_seconds=1):
""" Acquire a Postgres transactional advisory lock or retry job
When the lock cannot be acquired, it raises a
:exc:`odoo.addons.queue_job.exception.RetryableJobError` so the job
is retried after n ``retry_seconds``.
Usage example:
.. code-block:: python
lock_name = 'import_record({}, {}, {}, {})'.format(
self.backend_record._name,
self.backend_record.id,
self.model._name,
self.external_id,
)
self.advisory_lock_or_retry(lock_name, retry_seconds=2)
See :func:`odoo.addons.connector.connector.pg_try_advisory_lock` for
details.
:param lock: The lock name. Can be anything convertible to a
string. It needs to represent what should not be synchronized
concurrently, usually the string will contain at least: the
action, the backend name, the backend id, the model name, the
external id
:param retry_seconds: number of seconds after which a job should
be retried when the lock cannot be acquired.
"""
if not pg_try_advisory_lock(self.env, lock):
raise RetryableJobError(
"Could not acquire advisory lock",
seconds=retry_seconds,
ignore_retry=True,
)