131 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Python
		
	
	
# -*- 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)
 |