Components

Components are the blocks allowing to build a flexible and well decoupled code. They are based on the component addon, which can as well be used separately.

Core Components

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:

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
Mapper

The mappers transform a external record into an Odoo record or conversely.

Most common usages:

  • import.mapper
  • export.mapper
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
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
class connector.components.core.BaseConnectorComponent(work_context)[source]

Bases : odoo.addons.component.core.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'
backend_record

Backend record we are working with

binder_for(model=None)[source]

Shortcut to get Binder for a model

Equivalent to: self.component(usage='binder', model_name='xxx')

advisory_lock_or_retry(lock, retry_seconds=1)[source]

Acquire a Postgres transactional advisory lock or retry job

When the lock cannot be acquired, it raises a odoo.addons.queue_job.exception.RetryableJobError so the job is retried after n retry_seconds.

Usage example:

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 odoo.addons.connector.connector.pg_try_advisory_lock() for details.

Paramètres:
  • 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
  • retry_seconds – number of seconds after which a job should be retried when the lock cannot be acquired.
_module = 'connector'

Connector Components

Binders

Binders are components that know how to find the external ID for an Odoo ID, how to find the Odoo ID for an external ID and how to create the binding between them.

class connector.components.binder.Binder(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Pour un enregistrement d’un modèle, capable de trouver un ID externe ou interne, ou crée la liaison entre eux

This is a default implementation that can be inherited or reimplemented in the connectors.

This implementation assumes that binding models are _inherits of the models they are binding.

_name = 'base.binder'
_inherit = 'base.connector'
_usage = 'binder'
_external_field = 'external_id'
_backend_field = 'backend_id'
_odoo_field = 'odoo_id'
_sync_date_field = 'sync_date'
to_internal(external_id, unwrap=False)[source]

Give the Odoo recordset for an external ID

Paramètres:
  • external_id – ID externe pour lequel on veut l’ID Odoo
  • unwrap – if True, returns the normal record else return the binding record
Retourne:

a recordset, depending on the value of unwrap, or an empty recordset if the external_id is not mapped

Type retourné:

recordset

to_external(binding, wrap=False)[source]

Give the external ID for an Odoo binding ID

Paramètres:
  • binding – Odoo binding for which we want the external id
  • wrap – if True, binding is a normal record, the method will search the corresponding binding and return the external id of the binding
Retourne:

ID externe de l’enregistrement

bind(external_id, binding)[source]

Crée le lien entre un ID externe et un ID Odoo

Paramètres:
  • external_id – ID externe à lier
  • binding (int) – Odoo record to bind
unwrap_binding(binding)[source]

Pour un enregistrement de liaison, donne l’enregistrement normal

Exemple : si appelé avec un ID de magento.product.product, il va renvoyer l’ID correspondant du product.product.

Paramètres:browse – si Vrai, renvoie une instance de browse_record à la place d’un ID
unwrap_model()[source]

Pour un modèle de liaison, donne le modèle normal.

Exemple : si appelé sur une binder pour magento.product.product, il va renvoyer product.product.

_module = 'connector'

Mappeurs

Mappers are the components responsible to transform external records into Odoo records and conversely.

connector.components.mapper.mapping(func)[source]

Decorator, declare that a method is a mapping method.

C’est ensuite utilisé par le Mapper pour convertir les enregistrements.

Utilisation

@mapping
def any(self, record):
    return {'output_field': record['input_field']}
connector.components.mapper.changed_by(*args)[source]

Décorateur pour les méthodes de mapping (mapping())

Lorsque des champs sont modifié dans Odoo, nous ne devrions exporter que les champs modifiés. En utilisant ce décorateur, nous pouvons préciser quelles modifications de champs doivent déclencher quelle méthode de mapping.

Si changed_by est vide, le mapping est toujours actif.

Autant que possible, ce décorateur devrait être utilisé pour les exports. De cette façon lorsqu’on fait une modification sur une petite partie des champs d’un enregistrement, la taille de l’enregistrement en sortie sera limitée aux seuls champs devant être exportés.

Utilisation

@changed_by('input_field')
@mapping
def any(self, record):
    return {'output_field': record['input_field']}
Paramètres:*args – noms de champs qui déclenchent le mapping à la modification
connector.components.mapper.only_create(func)[source]

Décorateur pour les méthodes de mapping (mapping())

Un mapping décoré avec only_create indique qu’il doit être utilisé uniquement pour a création d’enregistrements.

Utilisation

@only_create
@mapping
def any(self, record):
    return {'output_field': record['input_field']}
connector.components.mapper.none(field)[source]

Un modificateur destiné à être utilisé sur les mapping directs

Remplace les valeurs similaires à False par None. Ceci peut être utilisé dans un enchaînement de modificateurs quand .

Exemples

direct = [(none('source'), 'target'),
          (none(m2o_to_external('rel_id'), 'rel_id')]
Paramètres:
  • field – nom du champ source dans l’enregistrement
  • binding – True si la relation est un enregistrement de binding
connector.components.mapper.convert(field, conv_type)[source]

Un modificateur destiné à être utilisé sur les mapping directs

Convertit une valeur de champ vers un type donné

Exemples

direct = [(convert('source', str), 'target')]
Paramètres:
  • field – nom du champ source dans l’enregistrement
  • binding – True si la relation est un enregistrement de binding
connector.components.mapper.m2o_to_external(field, binding=None)[source]

Un modificateur destiné à être utilisé sur les mapping directs

For a many2one, get the external ID and returns it.

Quand la relation du champ n’est pas un binding (càd ne pointe pas vers quelque chose comme magento.*), le modèle de liaison doit être fourni avec l’argument nommé binding.

Exemples

direct = [(m2o_to_external('country_id',
                           binding='magento.res.country'), 'country'),
          (m2o_to_external('magento_country_id'), 'country')]
Paramètres:
  • field – nom du champ source dans l’enregistrement
  • binding – nom du modèle de liaison si la relation n’est pas une liaison
connector.components.mapper.external_to_m2o(field, binding=None)[source]

Un modificateur destiné à être utilisé sur les mapping directs

For a field from a backend which is an ID, search the corresponding binding in Odoo and returns it.

Quand la relation du champ n’est pas un binding (càd ne pointe pas vers quelque chose comme magento.*), le modèle de liaison doit être fourni avec l’argument nommé binding.

Exemples

direct = [(external_to_m2o('country', binding='magento.res.country'),
           'country_id'),
          (external_to_m2o('country'), 'magento_country_id')]
Paramètres:
  • field – nom du champ source dans l’enregistrement
  • binding – nom du modèle de liaison si la relation n’est pas une liaison
connector.components.mapper.follow_m2o_relations(field)[source]

A modifier intended to be used on direct mappings.

“Follows” Many2one relations and return the final field value.

Exemples :

Assuming model is product.product:

direct = [
    (follow_m2o_relations('product_tmpl_id.categ_id.name'), 'cat')]
Paramètres:field – field « path », using dots for relations as usual in Odoo
class connector.components.mapper.MappingDefinition(changed_by, only_create)

Bases : tuple

_asdict()

Return a new OrderedDict which maps field names to their values

_fields = ('changed_by', 'only_create')
classmethod _make(iterable, new=<built-in method __new__ of type object at 0x908840>, len=<built-in function len>)

Make a new MappingDefinition object from a sequence or iterable

_replace(_self, **kwds)

Return a new MappingDefinition object replacing specified fields with new values

changed_by

Alias pour le champ numéro 0

only_create

Alias pour le champs numéro 1

class connector.components.mapper.MapChild(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

MapChild est responsable de la conversion des items.

Les items sont des sous-enregistrements d’un enregistrement principal. Dans cet exemple, les items sont les enregistrements qui sont dans lines

sales = {'name': 'SO10',
         'lines': [{'product_id': 1, 'quantity': 2},
                   {'product_id': 2, 'quantity': 2}]}

Un MapChild est toujours appelé depuis un autre Mapper qui fournit une configuration children.

Compte tenu de l’exemple ci-dessus, le Mapper « principal » renverrait quelque chose comme ça

{'name': 'SO10',
         'lines': [(0, 0, {'product_id': 11, 'quantity': 2}),
                   (0, 0, {'product_id': 12, 'quantity': 2})]}

Un MapChild est responsable de :

  • Trouver le Mapper pour convertir les items
  • Éventuellement exclure certaines lignes (en surchargeant skip_item())
  • Convertir les enregistrements des items en utilisant le Mapper trouvé
  • Formater les valeurs de sortie vers le format attendu par Odoo ou par le backend (comme vu ci-dessus avec (0, 0, {values})

A MapChild can be extended like any other Component. However, it is not mandatory to explicitly create a MapChild for each children mapping, the default one will be used (ImportMapChild or ExportMapChild).

L’implémentation par défaut ne gère pas les mises à jour : si j’importe une commande deux fois, les lignes seront dupliquées. Ceci n’est pas un problème à condition qu’un import ne prenne en charge que la création (typique pour les commandes). Il peut être implémenté au cas par cas en héritant de get_item_values() et format_items().

_name = 'base.map.child'
_inherit = 'base.connector'
_child_mapper()[source]
skip_item(map_record)[source]

Point d’accroche à implémenter dans les sous-classes lorsque certains enregistrements enfants doivent être sautés.

L’enregistrement parent est accessible dans map_record. S’il renvoie True, l’enregistrement enfant en cours est sauté.

Paramètres:map_record (MapRecord) – enregistrement que nous sommes en train de convertir
get_items(items, parent, to_attr, options)[source]

Renvoie la sortie formatée des valeurs des items pour un enregistrement principal

Paramètres:
  • items (list) – liste d’enregistrements d’items
  • parent – enregistrement parent
  • to_attr (str) – champ destination (peut être utilisé pour inspecter la relation)
  • options – dictionnaire des options, hérité du mappeur principal
Retourne:

valeurs de sortie formatées pour l’item

get_item_values(map_record, to_attr, options)[source]

Récupère la valeur brute des Mappeurs enfants depuis les items.

I peut être surchargé par exemple pour :

  • Changer des options
  • Utiliser un Binder pour savoir si un item existe déjà afin de le modifier au lieu de l’ajouter
Paramètres:
  • map_record (MapRecord) – enregistrement que nous sommes en train de convertir
  • to_attr (str) – champ destination (peut être utilisé pour inspecter la relation)
  • options – dictionnaire des options, hérité du mappeur principal
format_items(items_values)[source]

Formate les valeurs des items mappés depuis les Mappeurs enfants.

Il peut être surchargé par exemple pour ajouter les commandes de relation d’Odoo (6, 0, [IDs]), …

Par exemple, il peut être modifié pour pour gérer la mise à jour d’items existants : vérifier si un “id” a bien été défini par get_item_values() puis utilise la commande (1, ID, {valeurs})

Paramètres:items_values (list) – valeurs traduites pour les items
_module = 'connector'
class connector.components.mapper.ImportMapChild(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

MapChild pour les imports

_name = 'base.map.child.import'
_inherit = 'base.map.child'
_usage = 'import.map.child'
_child_mapper()[source]
format_items(items_values)[source]

Formate les valeurs des items mappés depuis les Mappeurs enfants.

Il peut être surchargé par exemple pour ajouter les commandes de relation d’Odoo (6, 0, [IDs]), …

Par exemple, il peut être modifié pour pour gérer la mise à jour d’items existants : vérifier si un “id” a bien été défini par get_item_values() puis utilise la commande (1, ID, {valeurs})

Paramètres:items_values (list) – liste des valeurs pour les items à créer
_module = 'connector'
class connector.components.mapper.ExportMapChild(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

MapChild pour les exports

_name = 'base.map.child.export'
_inherit = 'base.map.child'
_usage = 'export.map.child'
_child_mapper()[source]
_module = 'connector'
class connector.components.mapper.Mapper(work)[source]

Bases : odoo.addons.component.core.AbstractComponent

Un Mappeur convertit un enregistrement externe en enregistrement Odoo et vice versa. La sortie d’un Traducteur est un dict.

3 types de mappings sont pris en charge :

Mappings directs

Exemples

direct = [('source', 'target')]

Ici le champs source sera copié vers le champ target.

Un modificateur peut être utilisé dans l’item source. Le modificateur sera appliqué au champ source avant d’être copié dans le champ destination. Il doit être une fonction de type closure pour respecter cet idiome

def a_function(field):
    ''' ``field`` is the name of the source field.

        Naming the arg: ``field`` is required for the conversion'''
    def modifier(self, record, to_attr):
        ''' self is the current Mapper,
            record is the current record to map,
            to_attr is the target field'''
        return record[field]
    return modifier

Et utilisé de cette façon

direct = [
        (a_function('source'), 'target'),
]

Un exemple plus concret de modificateur

def convert(field, conv_type):
    ''' Convert the source field to a defined ``conv_type``
    (ex. str) before returning it'''
    def modifier(self, record, to_attr):
        value = record[field]
        if not value:
            return None
        return conv_type(value)
    return modifier

Et utilisé de cette façon

direct = [
    (convert('myfield', float), 'target_field'),
]

D’autres exemples de modificateurs

Mappings par méthode

Un méthode de mapping permet d’exécuter un code arbitraire et de renvoyer un ou plusieurs champs

@mapping
def compute_state(self, record):
    # compute some state, using the ``record`` or not
    state = 'pending'
    return {'state': state}

Nous pouvons aussi préciser qu’une méthode de mapping doit être appliquée lorsqu’un objet est créé, mais jamais appliquée sur les mises à jour ultérieures

@only_create
@mapping
def default_warehouse(self, record):
    # get default warehouse
    warehouse_id = ...
    return {'warehouse_id': warehouse_id}
Sous-mappings

Lorsqu’un enregistrement contient des sous-items, comme les lignes d’une commande, nous pouvons convertir les enfants grâce à une autre Mappeur

children = [('items', 'line_ids', 'model.name')]

Il permet de créer des commandes et toutes ses lignes dans le même appel à odoo.models.BaseModel.create().

En utilisant des children pour les items d’un enregistrement, nous devons créer un Mapper pour le modèle des items, et de manière optionnelle un MapChild.

Utilisation d’un Mappeur

>>> mapper = self.component(usage='mapper')
>>> map_record = mapper.map_record(record)
>>> values = map_record.values()
>>> values = map_record.values(only_create=True)
>>> values = map_record.values(fields=['name', 'street'])
_name = 'base.mapper'
_inherit = 'base.connector'
_usage = 'mapper'
direct = []
children = []
_map_methods = None
_map_child_usage = None
_map_child_fallback = None
classmethod _build_mapper_component()[source]

Build a Mapper component

When a Mapper component is built, we will look into every of its bases and look for methods decorated by @mapping or @changed_by. We keep the definitions in a _map_methods attribute for later use by the Mapper instances.

The __bases__ of a newly generated Component are of 2 kinds:

  • other dynamically generated components (below “base” and “second.mapper”)
  • « real » Python classes applied on top of existing components (here ThirdMapper)
>>> cls.__bases__
(<class 'odoo.addons.connector.tests.test_mapper.ThirdMapper'>,
 <class 'odoo.addons.component.core.second.mapper'>,
 <class 'odoo.addons.component.core.base'>)

This method traverses these bases, from the bottom to the top, and merges the mapping definitions. It reuses the computed definitions for the generated components (for which this code already ran), and inspect the real classes to find mapping methods.

classmethod _complete_component_build()[source]
_map_direct(record, from_attr, to_attr)[source]

Apply the direct mappings.

Paramètres:
  • record – record to convert from a source to a target
  • from_attr (callable | str) – name of the source attribute or a callable
  • to_attr (str) – name of the target attribute
_map_children(record, attr, model)[source]
map_methods

Émets toutes les méthodes décorées avec @mapping

_get_map_child_component(model_name)[source]
_map_child(map_record, from_attr, to_attr, model_name)[source]

Convert items of the record as defined by children

_mapping_options(*args, **kwds)[source]

Change the mapping options for the Mapper.

Context Manager to use in order to alter the behavior of the mapping, when using _apply or finalize.

options

Des options peuvent être accédées dans les méthodes de mapping avec self.options.

changed_by_fields()[source]

Build a set of fields used by the mapper

It takes in account the direct fields and the fields used by the decorator: changed_by.

_direct_source_field_name(direct_entry)[source]

Get the mapping field name. Goes through the function modifiers.

Ex:

[(none(convert(field_name, str)), out_field_name)]

It assumes that the modifier has field as first argument like:

def modifier(field, args):
map_record(record, parent=None)[source]

Récupère un MapRecord avec enregistrement, prêt à être converti avec le Mappeur en cours.

Paramètres:
  • record – enregistrement à transformer
  • parent – enregistrement parent optionnel, pour les items
_apply(map_record, options=None)[source]

Apply the mappings on a MapRecord

Paramètres:map_record (MapRecord) – source record to convert
_apply_with_options(map_record)[source]

Apply the mappings on a MapRecord with contextual options (the options given in MapRecord.values() are accessible in self.options)

Paramètres:map_record (MapRecord) – source record to convert
finalize(map_record, values)[source]

Appelé à la fin du mapping.

Can be used to modify the values generated by all the mappings before returning them.

Paramètres:
  • map_record (MapRecord) – map_record source
  • values – valeurs mappées
Retourne:

valeurs mappées

Type retourné:

dict

_module = 'connector'
class connector.components.mapper.ImportMapper(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Mapper pour les imports.

Transforme un enregistrement depuis un backend vers un enregistrement Odoo

_name = 'base.import.mapper'
_inherit = 'base.mapper'
_usage = 'import.mapper'
_map_child_usage = 'import.map.child'
_map_child_fallback = 'base.map.child.import'
_map_direct(record, from_attr, to_attr)[source]

Apply the direct mappings.

Paramètres:
  • record – record to convert from a source to a target
  • from_attr (callable | str) – name of the source attribute or a callable
  • to_attr (str) – name of the target attribute
_module = 'connector'
class connector.components.mapper.ExportMapper(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Mapper pour les exports.

Transforme un enregistrement depuis Odoo vers un enregistrement du backend

_name = 'base.export.mapper'
_inherit = 'base.mapper'
_usage = 'export.mapper'
_map_child_usage = 'export.map.child'
_map_child_fallback = 'base.map.child.export'
_map_direct(record, from_attr, to_attr)[source]

Apply the direct mappings.

Paramètres:
  • record – record to convert from a source to a target
  • from_attr (callable | str) – name of the source attribute or a callable
  • to_attr (str) – name of the target attribute
_module = 'connector'
class connector.components.mapper.MapRecord(mapper, source, parent=None)[source]

Bases : object

Un enregistrement préparé à être converti en utilisans un Mapper.

Les instances de MapRecord sont préparées par Mapper.map_record().

Utilisation

>>> map_record = mapper.map_record(record)
>>> output_values = map_record.values()

Voir values() pour avoir plus d’informations sur les arguments disponibles.

source

Enregistrement source à convertir

parent

Enregistrement parent si l’enregistrement en cours est un item

values(for_create=None, fields=None, **kwargs)[source]

Construit et renvoie les valeurs converties selon les options.

Utilisation

>>> map_record = mapper.map_record(record)
>>> output_values = map_record.values()
Création des enregistrements

En utilisant l’option for_create, seuls les mappings décorés avec @only_create seront mappés.

>>> output_values = map_record.values(for_create=True)
Filtrer sur les champs

En utilisant l’argument fields, les mappings seront filtrés en utilisant soit la clé source dans les arguments direct, soit les arguments changed_by pour les méthodes de mapping.

>>> output_values = map_record.values(
        fields=['name', 'street']
    )
Options spécifiques

Des clé et valeurs arbitraires peuvent être définies dans les arguments kwargs. Elles peuvent être ensuites utilisées dans les méthodes de mapping en utilisant self.options.

>>> output_values = map_record.values(tax_include=True)
Paramètres:
  • for_create (boolean) – précise si seuls les mappings de création (@only_create) doivent être mappés.
  • fields (list) – filtre sur les champs
  • **kwargs – options spécifiques, elles peuvent être utilisées plus tard dans les méthodes de mapping
update(*args, **kwargs)[source]

Force des valeurs à appliquer après une conversion.

Utilisation

>>> map_record = mapper.map_record(record)
>>> map_record.update(a=1)
>>> output_values = map_record.values()
# output_values will at least contain {'a': 1}

Les valeurs affectées avec update() sont appliquées dans tous les cas, elles ont une priorité supérieure que les valeurs converties.

class connector.components.mapper.MapOptions[source]

Bases : dict

Conteneur pour les options des mappeurs.

Des options peuvent être accédées grâce aux attributs de l’instance. Quand une option est accédée mais n’existe pas, ceci renvoie None.

Adaptateur de backend

An external adapter has a common interface to speak with the backend. It translates the basic orders (search, read, write) to the protocol used by the backend.

class connector.components.backend_adapter.BackendAdapter(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Adaptateur de backend de base pour les connecteurs

_name = 'base.backend.adapter'
_inherit = 'base.connector'
_usage = 'backend.adapter'
_module = 'connector'
class connector.components.backend_adapter.CRUDAdapter(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Adaptateur externe de base spécialisé dans la gestion des enregistrements des systèmes externes.

This is an empty shell, Components can inherit and implement their own implementation for the methods.

_name = 'base.backend.adapter.crud'
_inherit = 'base.backend.adapter'
_usage = 'backend.adapter'
search(*args, **kwargs)[source]

Recherche des enregistrements correspondant à certains critères et renvoie une liste d’IDs

read(*args, **kwargs)[source]

Renvoie des informations sur l’enregistrement

search_read(*args, **kwargs)[source]

Recherche des enregistrements correspondant à certains critères et renvoie leurs données

create(*args, **kwargs)[source]

Crée un enregistrement dans le système externe

write(*args, **kwargs)[source]

Met à jour les enregistrements sur le système externe

delete(*args, **kwargs)[source]

Supprime un enregistrement sur le système externe

_module = 'connector'

Synchroniseur

A synchronizer orchestrates a synchronization with a backend. It can be a record’s import or export, a deletion of something, or anything else. For instance, it will use the mappings to convert the data between both systems, the backend adapters to read or write data on the backend and the binders to create the link between them.

class connector.components.synchronizer.Synchronizer(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Classe de base des synchroniseurs

_name = 'base.synchronizer'
_inherit = 'base.connector'
_base_mapper_usage = 'mapper'
_base_backend_adapter_usage = 'backend.adapter'
run()[source]

Lance la synchronisation

mapper

Renvoie une instance de mappeur Mapper pour la synchronisation

L’instanciation est retardée parce que certaines synchronisations n’ont pas besoin d’une telle unité et celle-ci peut ne pas exister.

It looks for a Component with _usage being equal to _base_mapper_usage.

Type retourné:odoo.addons.component.core.Component
binder

Renvoie une instance de liaison Binder pour la synchronisation.

L’instanciation est retardée parce que certaines synchronisations n’ont pas besoin d’une telle unité et celle-ci peut ne pas exister.

Type retourné:odoo.addons.component.core.Component
backend_adapter

Renvoie une instance de BackendAdapter pour la synchronisation.

L’instanciation est retardée parce que certaines synchronisations n’ont pas besoin d’une telle unité et celle-ci peut ne pas exister.

It looks for a Component with _usage being equal to _base_backend_adapter_usage.

Type retourné:odoo.addons.component.core.Component
_module = 'connector'
class connector.components.synchronizer.Exporter(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Synchroniseur pour exporter des donnés d’Odoo vers un backend

_name = 'base.exporter'
_inherit = 'base.synchronizer'
_usage = 'exporter'
_base_mapper_usage = 'export.mapper'
_module = 'connector'
class connector.components.synchronizer.Importer(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Synchroniseur pour importer des données d’un backend vers Odoo

_name = 'base.importer'
_inherit = 'base.synchronizer'
_usage = 'importer'
_base_mapper_usage = 'import.mapper'
_module = 'connector'
class connector.components.synchronizer.Deleter(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Synchroniseur pour effacer un enregistrement du backend

_name = 'base.deleter'
_inherit = 'base.synchronizer'
_usage = 'deleter'
_module = 'connector'

Listeners

Listeners are Components notified when events happen. Documentation in odoo.addons.component_event.components.event

The base listener for the connectors add a method ConnectorListener.no_connector_export() which can be used with odoo.addons.component_event.skip_if().

class connector.components.listener.ConnectorListener(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Adaptateur de backend de base pour les connecteurs

_name = 'base.connector.listener'
_inherit = ['base.connector', 'base.event.listener']
no_connector_export(record)[source]

Return if the “connector_no_export” has been set in context

To be used with odoo.addons.component_event.skip_if() on Events:

from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if


class MyEventListener(Component):
    _name = 'my.event.listener'
    _inherit = 'base.connector.event.listener'
    _apply_on = ['magento.res.partner']

    @skip_if(lambda: self, record, *args, **kwargs:
             self.no_connector_export(record))
    def on_record_write(self, record, fields=None):
        record.with_delay().export_record()
_module = 'connector'

Components Collection Model

Collection Model

This is the base Model shared by all the Collections. In the context of the Connector, a collection is the Backend. The _name given to the Collection Model will be the name to use in the _collection of the Components usable for the Backend.

class odoo.addons.component.models.collection.Collection(pool, cr)[source]

Bases : odoo.models.BaseModel

The model on which components are subscribed

It would be for instance the backend for the connectors.

Exemples

class MagentoBackend(models.Model):
    _name = 'magento.backend'  # name of the collection
    _inherit = 'collection.base'


class MagentoSaleImporter(Component):
    _name = 'magento.sale.importer'
    _apply_on = 'magento.sale.order'
    _collection = 'magento.backend'  # name of the collection

    def run(self, magento_id):
        mapper = self.component(usage='import.mapper')
        extra_mappers = self.many_components(
            usage='import.mapper.extra',
        )
        # ...

Use it:

>>> backend = self.env['magento.backend'].browse(1)
>>> with backend.work_on('magento.sale.order') as work:
...     importer = work.component(usage='magento.sale.importer')
...     importer.run(1)

See also: odoo.addons.component.core.WorkContext

work_on(*args, **kwds)[source]

Entry-point for the components, context manager

Start a work using the components on the model. Any keyword argument will be assigned to the work context. See documentation of odoo.addons.component.core.WorkContext.

It is a context manager, so you can attach objects and clean them at the end of the work session, such as:

@contextmanager
@api.multi
def work_on(self, model_name, **kwargs):
    self.ensure_one()
    magento_location = MagentoLocation(
        self.location,
        self.username,
        self.password,
    )
    # We create a Magento Client API here, so we can create the
    # client once (lazily on the first use) and propagate it
    # through all the sync session, instead of recreating a client
    # in each backend adapter usage.
    with MagentoAPI(magento_location) as magento_api:
        _super = super(MagentoBackend, self)
        # from the components we'll be able to do:
        # self.work.magento_api
        with _super.work_on(
                model_name, magento_api=magento_api, **kwargs
                ) as work:
            yield work

Components Exceptions

exception odoo.addons.component.exception.ComponentException[source]

Bases : exceptions.Exception

Base Exception for the components

exception odoo.addons.component.exception.NoComponentError[source]

Bases : odoo.addons.component.exception.ComponentException

No component has been found

exception odoo.addons.component.exception.SeveralComponentError[source]

Bases : odoo.addons.component.exception.ComponentException

More than one component have been found

Components Internals

Low-level APIs of the Components.

Core

Core classes for the components. The most common classes used publicly are:

class odoo.addons.component.core.ComponentDatabases[source]

Bases : dict

Holds a registry of components for each database

class odoo.addons.component.core.ComponentRegistry(cachesize=512)[source]

Bases : object

Store all the components and allow to find them using criteria

The key is the _name of the components.

This is an OrderedDict, because we want to keep the registration order of the components, addons loaded first have their components found first.

The ready attribute must be set to True when all the components are loaded.

get(key, default=None)[source]
load_components(module)[source]
lookup(*args, **kwargs)[source]

Find and return a list of components for a usage

If a component is not registered in a particular collection (no _collection), it might will be returned in any case (as far as the usage and model_name match). This is useful to share generic components across different collections.

If no collection name is given, components from any collection will be returned.

Then, the components of a collection are filtered by usage and/or model. The _usage is mandatory on the components. When the _model_name is empty, it means it can be used for every models, and it will ignore the model_name argument.

The abstract components are never returned.

This is a rather low-level function, usually you will use the high-level AbstractComponent.component(), AbstractComponent.many_components() or even AbstractComponent.component_by_name().

Paramètres:
  • collection_name – the name of the collection the component is registered into.
  • usage – the usage of component we are looking for
  • model_name – filter on components that apply on this model
class odoo.addons.component.core.WorkContext(model_name=None, collection=None, components_registry=None, **kwargs)[source]

Bases : object

Transport the context required to work with components

It is propagated through all the components, so any data or instance (like a random RPC client) that need to be propagated transversally to the components should be kept here.

Including:

model_name

Name of the model we are working with. It means that any lookup for a component will be done for this model. It also provides a shortcut as a model attribute to use directly with the Odoo model from the components

collection

The collection we are working with. The collection is an Odoo Model that inherit from “collection.base”. The collection attribute can be a record or an « empty » model.

model

Odoo Model for model_name with the same Odoo Environment than the collection attribute.

This is also the entrypoint to work with the components.

collection = self.env['my.collection'].browse(1)
work = WorkContext(model_name='res.partner', collection=collection)
component = work.component(usage='record.importer')

Usually you will use the context manager on the collection.base Model:

collection = self.env['my.collection'].browse(1)
with collection.work_on('res.partner') as work:
    component = work.component(usage='record.importer')

It supports any arbitrary keyword arguments that will become attributes of the instance, and be propagated throughout all the components.

collection = self.env['my.collection'].browse(1)
with collection.work_on('res.partner', hello='world') as work:
    assert work.hello == 'world'

When you need to work on a different model, a new work instance will be created for you when you are using the high-level API. This is what happens under the hood:

collection = self.env['my.collection'].browse(1)
with collection.work_on('res.partner', hello='world') as work:
    assert work.model_name == 'res.partner'
    assert work.hello == 'world'
    work2 = work.work_on('res.users')
    # => spawn a new WorkContext with a copy of the attributes
    assert work2.model_name == 'res.users'
    assert work2.hello == 'world'
env

Return the current Odoo env

This is the environment of the current collection.

work_on(model_name=None, collection=None)[source]

Create a new work context for another model keeping attributes

Used when one need to lookup components for another model.

component_by_name(name, model_name=None)[source]

Return a component by its name

If the component exists, an instance of it will be returned, initialized with the current WorkContext.

A odoo.addons.component.exception.NoComponentError is raised if:

  • no component with this name exists
  • the _apply_on of the found component does not match with the current working model

In the latter case, it can be an indication that you need to switch to a different model, you can do so by providing the model_name argument.

component(usage=None, model_name=None)[source]

Find a component by usage and model for the current collection

It searches a component using the rules of ComponentRegistry.lookup(). When a component is found, it initialize it with the current WorkContext and returned.

A odoo.addons.component.exception.SeveralComponentError is raised if more than one component match for the provided usage/model_name.

A odoo.addons.component.exception.NoComponentError is raised if no component is found for the provided usage/model_name.

many_components(usage=None, model_name=None)[source]

Find many components by usage and model for the current collection

It searches a component using the rules of ComponentRegistry.lookup(). When components are found, they initialized with the current WorkContext and returned as a list.

If no component is found, an empty list is returned.

class odoo.addons.component.core.MetaComponent(name, bases, attrs)[source]

Bases : type

Metaclass for Components

Every new Component will be added to _modules_components, that will be used by the component builder.

apply_on_models
class odoo.addons.component.core.AbstractComponent(work_context)[source]

Bases : object

Main Component Model

All components have a Python inheritance either on AbstractComponent or either on Component.

Abstract Components will not be returned by lookups on components, however they can be used as a base for other Components through inheritance (using _inherit).

Inheritance mechanism

The inheritance mechanism is like the Odoo’s one for Models. Each component has a _name. This is the absolute minimum in a Component class.

class MyComponent(Component):
    _name = 'my.component'

    def speak(self, message):
        print message

Every component implicitly inherit from the “base” component.

There are two close but distinct inheritance types, which look familiar if you already know Odoo. The first uses _inherit with an existing name, the name of the component we want to extend. With the following example, my.component is now able to speak and to yell.

class MyComponent(Component):  # name of the class does not matter
    _inherit = 'my.component'

    def yell(self, message):
        print message.upper()

The second has a different _name, it creates a new component, including the behavior of the inherited component, but without modifying it. In the following example, my.component is still able to speak and to yell (brough by the previous inherit), but not to sing. another.component is able to speak, to yell and to sing.

class AnotherComponent(Component):
    _name = 'another.component'
    _inherit = 'my.component'

    def sing(self, message):
        print message.upper()
Registration and lookups

It is handled by 3 attributes on the class:

_collection
The name of the collection where we want to register the component. This is not strictly mandatory as a component can be shared across several collections. But usually, you want to set a collection to segregate the components for a domain. A collection can be for instance magento.backend. It is also the name of a model that inherits from collection.base. See also WorkContext and Collection.
_apply_on
List of names or name of the Odoo model(s) for which the component can be used. When not set, the component can be used on any model.
_usage
The collection and the model (_apply_on) will help to filter the candidate components according to our working context (e.g. I’m working on magento.backend with the model magento.res.partner). The usage will define what kind of task the component we are looking for serves to. For instance, it might be record.importer, export.mapper`… but you can be as creative as you want.

Now, to get a component, you’ll likely use WorkContext.component() when you start to work with components in your flow, but then from within your components, you are more likely to use one of:

Declaration of some Components can look like:

class FooBar(models.Model):
    _name = 'foo.bar.collection'
    _inherit = 'collection.base'  # this inherit is required


class FooBarBase(AbstractComponent):
    _name = 'foo.bar.base'
    _collection = 'foo.bar.collection'  # name of the model above


class Foo(Component):
    _name = 'foo'
    _inherit = 'foo.bar.base'  # we will inherit the _collection
    _apply_on = 'res.users'
    _usage = 'speak'

    def utter(self, message):
        print message


class Bar(Component):
    _name = 'bar'
    _inherit = 'foo.bar.base'  # we will inherit the _collection
    _apply_on = 'res.users'
    _usage = 'yell'

    def utter(self, message):
        print message.upper() + '!!!'


class Vocalizer(Component):
    _name = 'vocalizer'
    _inherit = 'foo.bar.base'
    _usage = 'vocalizer'
    # can be used for any model

    def vocalize(action, message):
        self.component(usage=action).utter(message)

And their usage:

>>> coll = self.env['foo.bar.collection'].browse(1)
>>> with coll.work_on('res.users') as work:
...     vocalizer = work.component(usage='vocalizer')
...     vocalizer.vocalize('speak', 'hello world')
...
hello world
...     vocalizer.vocalize('yell', 'hello world')
HELLO WORLD!!!

Hints:

  • If you want to create components without _apply_on, choose a _usage that will not conflict other existing components.
  • Unless this is what you want and in that case you use many_components() which will return all components for a usage with a matching or a not set _apply_on.
  • It is advised to namespace the names of the components (e.g. magento.xxx) to prevent conflicts between addons.
collection

Collection we are working with

env

Current Odoo environment, the one of the collection record

model

The model instance we are working with

component_by_name(name, model_name=None)[source]

Return a component by its name

Shortcut to meth:~WorkContext.component_by_name

component(usage=None, model_name=None)[source]

Return a component

Shortcut to meth:~WorkContext.component

many_components(usage=None, model_name=None)[source]

Return several components

Shortcut to meth:~WorkContext.many_components

class odoo.addons.component.core.Component(work_context)[source]

Bases : odoo.addons.component.core.AbstractComponent

Concrete Component class

This is the class you inherit from when you want your component to be registered in the component collections.

Look in AbstractComponent for more details.

Components Builder

Build the components at the build of a registry.

class odoo.addons.component.builder.ComponentBuilder(pool, cr)[source]

Bases : odoo.models.BaseModel

Build the component classes

And register them in a global registry.

Every time an Odoo registry is built, the know components are cleared and rebuilt as well. The Component classes are built using the same mechanism than Odoo’s Models: a final class is created, taking every Components with a _name and applying Components with an _inherits upon them.

The final Component classes are registered in global registry.

This class is an Odoo model, allowing us to hook the build of the components at the end of the Odoo’s registry loading, using _register_hook. This method is called after all modules are loaded, so we are sure that we have all the components Classes and in the correct order.

build_registry(components_registry, states=None, exclude_addons=None)[source]
load_components(module, components_registry=None)[source]

Build every component known by MetaComponent for an odoo module

The final component (composed by all the Component classes in this module) will be pushed into the registry.

Paramètres:
  • module (str | unicode) – the name of the addon for which we want to load the components
  • registry (ComponentRegistry) – the registry in which we want to put the Component