# -*- coding: utf-8 -*- # Copyright 2017 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.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`` """ from odoo.addons.component.core import AbstractComponent from odoo.addons.queue_job.exception import RetryableJobError from ..database import pg_try_advisory_lock 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 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) 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)