Mapper

Mappers

Mappers are the ConnectorUnit classes responsible to transform external records into OpenERP records and conversely.

class connector.unit.mapper.ExportMapChild(connector_env)[source]

Bases: connector.unit.mapper.MapChild

MapChild for the Exports

class connector.unit.mapper.ExportMapper(connector_env)[source]

Bases: connector.unit.mapper.Mapper

Mapper for exports.

Transform a record from OpenERP to a backend record

class connector.unit.mapper.ImportMapChild(connector_env)[source]

Bases: connector.unit.mapper.MapChild

MapChild for the Imports

format_items(items_values)[source]

Format the values of the items mapped from the child Mappers.

It can be overridden for instance to add the OpenERP relationships commands (6, 0, [IDs]), …

As instance, it can be modified to handle update of existing items: check if an ‘id’ has been defined by get_item_values() then use the (1, ID, {values}) command

Parameters:items_values (list) – list of values for the items to create
class connector.unit.mapper.ImportMapper(connector_env)[source]

Bases: connector.unit.mapper.Mapper

Mapper for imports.

Transform a record from a backend to an OpenERP record

class connector.unit.mapper.MapChild(connector_env)[source]

Bases: connector.connector.ConnectorUnit

MapChild is responsible to convert items.

Items are sub-records of a main record. In this example, the items are the records in lines:

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

A MapChild is always called from another Mapper which provides a children configuration.

Considering the example above, the “main” Mapper would returns something as follows:

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

A MapChild is responsible to:

  • Find the Mapper to convert the items
  • Possibly filter out some lines (can be done by inheriting skip_item())
  • Convert the items’ records using the found Mapper
  • Format the output values to the format expected by OpenERP or the backend (as seen above with (0, 0, {values})

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

The implementation by default does not take care of the updates: if I import a sales order 2 times, the lines will be duplicated. This is not a problem as long as an importation should only support the creation (typical for sales orders). It can be implemented on a case-by-case basis by inheriting get_item_values() and format_items().

format_items(items_values)[source]

Format the values of the items mapped from the child Mappers.

It can be overridden for instance to add the OpenERP relationships commands (6, 0, [IDs]), …

As instance, it can be modified to handle update of existing items: check if an ‘id’ has been defined by get_item_values() then use the (1, ID, {values}) command

Parameters:items_values (list) – mapped values for the items
get_item_values(map_record, to_attr, options)[source]

Get the raw values from the child Mappers for the items.

It can be overridden for instance to:

  • Change options
  • Use a Binder to know if an item already exists to modify an existing item, rather than to add it
Parameters:
  • map_record (MapRecord) – record that we are converting
  • to_attr (str) – destination field (can be used for introspecting the relation)
  • options – dict of options, herited from the main mapper
get_items(items, parent, to_attr, options)[source]

Returns the formatted output values of items from a main record

Parameters:
  • items (list) – list of item records
  • parent – parent record
  • to_attr (str) – destination field (can be used for introspecting the relation)
  • options – dict of options, herited from the main mapper
Returns:

formatted output values for the item

skip_add_item(item_values)[source]

Hook to implement in sub-classes when some child records should be skipped according to its mapped values.

If it returns True, the current child record is skipped.

Parameters:item_values – the mapped values for the child record
skip_item(map_record)[source]

Hook to implement in sub-classes when some child records should be skipped.

The parent record is accessible in map_record. If it returns True, the current child record is skipped.

Parameters:map_record (MapRecord) – record that we are converting
class connector.unit.mapper.MapOptions[source]

Bases: dict

Container for the options of mappings.

Options can be accessed using attributes of the instance. When an option is accessed and does not exist, it returns None.

class connector.unit.mapper.MapRecord(mapper, source, parent=None)[source]

Bases: object

A record prepared to be converted using a Mapper.

MapRecord instances are prepared by Mapper.map_record().

Usage:

mapper = SomeMapper(env)
map_record = mapper.map_record(record)
output_values = map_record.values()

See values() for more information on the available arguments.

parent

Parent record if the current record is an item

source

Source record to be converted

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

Force values to be applied after a mapping.

Usage:

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

The values assigned with update() are in any case applied, they have a greater priority than the mapping values.

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

Build and returns the mapped values according to the options.

Usage:

mapper = SomeMapper(env)
map_record = mapper.map_record(record)
output_values = map_record.values()
Creation of records

When using the option for_create, only the mappings decorated with @only_create will be mapped.

output_values = map_record.values(for_create=True)
Filter on fields

When using the fields argument, the mappings will be filtered using either the source key in direct arguments, either the changed_by arguments for the mapping methods.

output_values = map_record.values(fields=['name', 'street'])
Custom options

Arbitrary key and values can be defined in the kwargs arguments. They can later be used in the mapping methods using self.options.

output_values = map_record.values(tax_include=True)
Parameters:
  • for_create (boolean) – specify if only the mappings for creation (@only_create) should be mapped.
  • fields (list) – filter on fields
  • **kwargs

    custom options, they can later be used in the mapping methods

class connector.unit.mapper.Mapper(connector_env)[source]

Bases: connector.connector.ConnectorUnit

A Mapper translates an external record to an OpenERP record and conversely. The output of a Mapper is a dict.

3 types of mappings are supported:

Direct Mappings

Example:

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

Here, the source field will be copied in the target field.

A modifier can be used in the source item. The modifier will be applied to the source field before being copied in the target field. It should be a closure function respecting this idiom:

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

And used like that:

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

A more concrete example of modifier:

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

And used like that:

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

More examples of modifiers:

Method Mappings

A mapping method allows to execute arbitrary code and return one or many fields:

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

We can also specify that a mapping methods should be applied only when an object is created, and never applied on further updates:

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

When a record contains sub-items, like the lines of a sales order, we can convert the children using another Mapper:

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

It allows to create the sales order and all its lines with the same call to openerp.models.BaseModel.create().

When using children for items of a record, we need to create a Mapper for the model of the items, and optionally a MapChild.

Usage of a Mapper:

mapper = Mapper(env)
map_record = mapper.map_record(record)
values = map_record.values()
values = map_record.values(only_create=True)
values = map_record.values(fields=['name', 'street'])
children = []
direct = []
finalize(map_record, values)[source]

Called at the end of the mapping.

Can be used to modify the values before returning them, as the on_change.

Parameters:
  • map_record (MapRecord) – source map_record
  • values – mapped values
Returns:

mapped values

Return type:

dict

map_methods

Yield all the methods decorated with @mapping

map_record(record, parent=None)[source]

Get a MapRecord with record, ready to be converted using the current Mapper.

Parameters:
  • record – record to transform
  • parent – optional parent record, for items
options

Options can be accessed in the mapping methods with self.options.

class connector.unit.mapper.MappingDefinition(changed_by, only_create)

Bases: tuple

changed_by

Alias for field number 0

only_create

Alias for field number 1

class connector.unit.mapper.MetaMapper(name, bases, attrs)[source]

Bases: connector.connector.MetaConnectorUnit

Metaclass for Mapper

Build a _map_methods dict of mappings methods. The keys of the dict are the method names. The values of the dict are a namedtuple containing:

connector.unit.mapper.backend_to_m2o(field, binding=None)[source]

A modifier intended to be used on the direct mappings.

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

When the field’s relation is not a binding (i.e. it does not point to something like magento.*), the binding model needs to be provided in the binding keyword argument.

Example:

direct = [(backend_to_m2o('country', binding='magento.res.country'),
           'country_id'),
          (backend_to_m2o('country'), 'magento_country_id')]
Parameters:
  • field – name of the source field in the record
  • binding – name of the binding model is the relation is not a binding
connector.unit.mapper.changed_by(*args)[source]

Decorator for the mapping methods (mapping())

When fields are modified in OpenERP, we want to export only the modified fields. Using this decorator, we can specify which fields updates should trigger which mapping method.

If changed_by is empty, the mapping is always active.

As far as possible, this decorator should be used for the exports, thus, when we do an update on only a small number of fields on a record, the size of the output record will be limited to only the fields really having to be exported.

Usage:

@changed_by('input_field')
@mapping
def any(self, record):
    return {'output_field': record['input_field']}
Parameters:*args

field names which trigger the mapping when modified

connector.unit.mapper.convert(field, conv_type)[source]

A modifier intended to be used on the direct mappings.

Convert a field’s value to a given type.

Example:

direct = [(convert('source', str), 'target')]
Parameters:
  • field – name of the source field in the record
  • binding – True if the relation is a binding record
connector.unit.mapper.follow_m2o_relations(field)[source]

A modifier intended to be used on direct mappings.

‘Follows’ Many2one relations and return the final field value.

Examples:

Assuming model is product.product:

direct = [
(follow_m2o_relations(‘product_tmpl_id.categ_id.name’), ‘cat’)]
Parameters:field – field “path”, using dots for relations as usual in Odoo
connector.unit.mapper.m2o_to_backend(field, binding=None)[source]

A modifier intended to be used on the direct mappings.

For a many2one, get the ID on the backend and returns it.

When the field’s relation is not a binding (i.e. it does not point to something like magento.*), the binding model needs to be provided in the binding keyword argument.

Example:

direct = [(m2o_to_backend('country_id', binding='magento.res.country'),
           'country'),
          (m2o_to_backend('magento_country_id'), 'country')]
Parameters:
  • field – name of the source field in the record
  • binding – name of the binding model is the relation is not a binding
connector.unit.mapper.mapping(func)[source]

Declare that a method is a mapping method.

It is then used by the Mapper to convert the records.

Usage:

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

A modifier intended to be used on the direct mappings.

Replace the False-ish values by None. It can be used in a pipeline of modifiers when .

Example:

direct = [(none('source'), 'target'),
          (none(m2o_to_backend('rel_id'), 'rel_id')]
Parameters:
  • field – name of the source field in the record
  • binding – True if the relation is a binding record
connector.unit.mapper.only_create(func)[source]

Decorator for the mapping methods (mapping())

A mapping decorated with only_create means that it has to be used only for the creation of the records.

Usage:

@only_create
@mapping
def any(self, record):
    return {'output_field': record['input_field']}