Module odoo_api_wrapper.api

Odoo API wrapper

A wrapper for Odoo's External API.

You can check out the official documentation here.

Api is the main class, Operations defines the operations used for Api.call(), raises APIError.

Usage Examples

Instantiate an Api

Create an instance of the API to start using it.

import odoo_api_wrapper

api = odoo_api_wrapper.Api("http://localhost:8069", "db", "1001", "password")

List records

Records can be listed and filtered via search().

api.search('res.partner', [[['is_company', '=', True]]])

Count records

Rather than retrieve a possibly gigantic list of records and count them, search_count() can be used to retrieve only the number of records matching the query. It takes the same domain filter as search() and no other parameter.

api.search_count('res.partner', [[['is_company', '=', True]]])

Read records

Record data are accessible via the read() method, which takes a list of ids (as returned by search()), and optionally a list of fields to fetch. By default, it fetches all the fields the current user can read, which tends to be a huge amount.

ids = api.search('res.partner', [[['is_company', '=', True]]], {'limit': 1})
[record] = api.read('res.partner', [ids])
# count the number of fields fetched by default
len(record)

List record fields

fields_get() can be used to inspect a model’s fields and check which ones seem to be of interest.

api.fields_get('res.partner', [], {'attributes': ['string', 'help', 'type']})

Search and read

Because it is a very common task, Odoo provides a search_read() shortcut which, as its name suggests, is equivalent to a search() followed by a read(), but avoids having to perform two requests and keep ids around.

api.search_read(
    'res.partner',
    [[['is_company', '=', True]]],
    {'fields': ['name', 'country_id', 'comment'], 'limit': 5},
)

Create records

Records of a model are created using create(). The method creates a single record and returns its database identifier.

id = api.create('res.partner', [{'name': "New Partner"}])

Update records

Records can be updated using write(). It takes a list of records to update and a mapping of updated fields to values similar to create().

api.write('res.partner', [[id], {'name': "Newer partner"}])

Delete records

Records can be deleted in bulk by providing their ids to unlink().

api.unlink('res.partner', [[id]])
Expand source code
""" Odoo API wrapper

A wrapper for [Odoo](https://www.odoo.com/)'s External API.

You can check out the official documentation
[here](https://www.odoo.com/documentation/master/developer/api/external_api.html).

`odoo_api_wrapper.api.Api` is the main class, `odoo_api_wrapper.api.Operations` defines
the operations used for `odoo_api_wrapper.api.Api.call`, raises
`odoo_api_wrapper.api.APIError`.

## Usage Examples

### Instantiate an `Api`
Create an instance of the API to start using it.
```python
import odoo_api_wrapper

api = odoo_api_wrapper.Api("http://localhost:8069", "db", "1001", "password")
```

### List records
Records can be listed and filtered via `search()`.
```python
api.search('res.partner', [[['is_company', '=', True]]])
```

### Count records
Rather than retrieve a possibly gigantic list of records and count them,
`search_count()` can be used to retrieve only the number of records matching the query.
It takes the same domain filter as `search()` and no other parameter.
```python
api.search_count('res.partner', [[['is_company', '=', True]]])
```

### Read records
Record data are accessible via the `read()` method, which takes a list of ids (as
returned by `search()`), and optionally a list of fields to fetch. By default, it
fetches all the fields the current user can read, which tends to be a huge amount.
```python
ids = api.search('res.partner', [[['is_company', '=', True]]], {'limit': 1})
[record] = api.read('res.partner', [ids])
# count the number of fields fetched by default
len(record)
```

### List record fields
`fields_get()` can be used to inspect a model’s fields and check which ones seem to be
of interest.
```python
api.fields_get('res.partner', [], {'attributes': ['string', 'help', 'type']})
```

### Search and read
Because it is a very common task, Odoo provides a `search_read()` shortcut which, as
its name suggests, is equivalent to a `search()` followed by a `read()`, but avoids
having to perform two requests and keep ids around.
```python
api.search_read(
    'res.partner',
    [[['is_company', '=', True]]],
    {'fields': ['name', 'country_id', 'comment'], 'limit': 5},
)
```

### Create records
Records of a model are created using `create()`. The method creates a single record and
returns its database identifier.
```python
id = api.create('res.partner', [{'name': "New Partner"}])
```

### Update records
Records can be updated using `write()`. It takes a list of records to update and a
mapping of updated fields to values similar to `create()`.
```python
api.write('res.partner', [[id], {'name': "Newer partner"}])
```

### Delete records
Records can be deleted in bulk by providing their ids to `unlink()`.
```python
api.unlink('res.partner', [[id]])
```

"""
import enum
import functools
import socket
import xmlrpc.client
from typing import Any
from typing import Callable
from typing import Dict
from typing import List


class Operations(enum.Enum):
    """Allowed API Operations"""

    WRITE = "write"
    CREATE = "create"
    READ = "read"
    SEARCH = "search"
    SEARCH_COUNT = "search_count"
    SEARCH_READ = "search_read"
    FIELDS_GET = "fields_get"
    UNLINK = "unlink"


class APIError(Exception):
    """API Error Base Class"""

    def __init__(self, description: str, *args: Any, **kwargs: Any):
        super().__init__(*args, **kwargs)
        self.description = description

    def __str__(self) -> str:
        return self.description


class Api:  # pylint:disable=too-few-public-methods
    """API Wrapper"""

    # define the methods we'll add dynamically
    write: Callable[[str, List, Dict[str, Any]], Any]
    create: Callable[[str, List, Dict[str, Any]], Any]
    read: Callable[[str, List, Dict[str, Any]], Any]
    search: Callable[[str, List, Dict[str, Any]], Any]
    search_count: Callable[[str, List, Dict[str, Any]], Any]
    search_read: Callable[[str, List, Dict[str, Any]], Any]
    fields_get: Callable[[str, List, Dict[str, Any]], Any]
    unlink: Callable[[str, List, Dict[str, Any]], Any]

    def __new__(cls, *args, **kwargs):  # pylint:disable=unused-argument
        instance = super().__new__(cls)

        # add the dynamic method
        for operation in Operations.__members__.values():
            setattr(
                instance,
                operation.value,
                functools.partial(instance.call, operation),
            )

        return instance

    def __init__(self, base_url: str, db_name: str, uid: str, password: str):
        self.base_url = base_url
        self.db_name = db_name
        self.uid = uid
        self.password = password

        self.server = xmlrpc.client.ServerProxy(f"{self.base_url}/xmlrpc/2/object")

    def call(
        self,
        operation: Operations,
        model: str,
        args: List,
        kwargs: Dict[str, Any] = None,
    ) -> Any:
        """Call the api with a model and an operation

        Args:
            operation: the operation to call
            model: the name of the model
            args: a list of parameters passed by position
            kwargs: a dict of parameters to pass by keyword (optional)
        """
        if not isinstance(operation, Operations):
            raise APIError("Invalid operation")

        kwargs = kwargs if kwargs else {}

        try:
            return self.server.execute_kw(
                self.db_name,
                self.uid,
                self.password,
                model,
                operation.value,
                args,
                kwargs,
            )
        except xmlrpc.client.Fault as error:
            raise APIError(error.faultString) from error
        except socket.gaierror as error:
            raise APIError(str(error)) from error

Classes

class APIError (description: str, *args: Any, **kwargs: Any)

API Error Base Class

Expand source code
class APIError(Exception):
    """API Error Base Class"""

    def __init__(self, description: str, *args: Any, **kwargs: Any):
        super().__init__(*args, **kwargs)
        self.description = description

    def __str__(self) -> str:
        return self.description

Ancestors

  • builtins.Exception
  • builtins.BaseException
class Api (base_url: str, db_name: str, uid: str, password: str)

API Wrapper

Expand source code
class Api:  # pylint:disable=too-few-public-methods
    """API Wrapper"""

    # define the methods we'll add dynamically
    write: Callable[[str, List, Dict[str, Any]], Any]
    create: Callable[[str, List, Dict[str, Any]], Any]
    read: Callable[[str, List, Dict[str, Any]], Any]
    search: Callable[[str, List, Dict[str, Any]], Any]
    search_count: Callable[[str, List, Dict[str, Any]], Any]
    search_read: Callable[[str, List, Dict[str, Any]], Any]
    fields_get: Callable[[str, List, Dict[str, Any]], Any]
    unlink: Callable[[str, List, Dict[str, Any]], Any]

    def __new__(cls, *args, **kwargs):  # pylint:disable=unused-argument
        instance = super().__new__(cls)

        # add the dynamic method
        for operation in Operations.__members__.values():
            setattr(
                instance,
                operation.value,
                functools.partial(instance.call, operation),
            )

        return instance

    def __init__(self, base_url: str, db_name: str, uid: str, password: str):
        self.base_url = base_url
        self.db_name = db_name
        self.uid = uid
        self.password = password

        self.server = xmlrpc.client.ServerProxy(f"{self.base_url}/xmlrpc/2/object")

    def call(
        self,
        operation: Operations,
        model: str,
        args: List,
        kwargs: Dict[str, Any] = None,
    ) -> Any:
        """Call the api with a model and an operation

        Args:
            operation: the operation to call
            model: the name of the model
            args: a list of parameters passed by position
            kwargs: a dict of parameters to pass by keyword (optional)
        """
        if not isinstance(operation, Operations):
            raise APIError("Invalid operation")

        kwargs = kwargs if kwargs else {}

        try:
            return self.server.execute_kw(
                self.db_name,
                self.uid,
                self.password,
                model,
                operation.value,
                args,
                kwargs,
            )
        except xmlrpc.client.Fault as error:
            raise APIError(error.faultString) from error
        except socket.gaierror as error:
            raise APIError(str(error)) from error

Class variables

var create : Callable[[str, List, Dict[str, Any]], Any]
var fields_get : Callable[[str, List, Dict[str, Any]], Any]
var read : Callable[[str, List, Dict[str, Any]], Any]
var search : Callable[[str, List, Dict[str, Any]], Any]
var search_count : Callable[[str, List, Dict[str, Any]], Any]
var search_read : Callable[[str, List, Dict[str, Any]], Any]
var write : Callable[[str, List, Dict[str, Any]], Any]

Methods

def call(self, operation: Operations, model: str, args: List, kwargs: Dict[str, Any] = None) ‑> Any

Call the api with a model and an operation

Args

operation
the operation to call
model
the name of the model
args
a list of parameters passed by position
kwargs
a dict of parameters to pass by keyword (optional)
Expand source code
def call(
    self,
    operation: Operations,
    model: str,
    args: List,
    kwargs: Dict[str, Any] = None,
) -> Any:
    """Call the api with a model and an operation

    Args:
        operation: the operation to call
        model: the name of the model
        args: a list of parameters passed by position
        kwargs: a dict of parameters to pass by keyword (optional)
    """
    if not isinstance(operation, Operations):
        raise APIError("Invalid operation")

    kwargs = kwargs if kwargs else {}

    try:
        return self.server.execute_kw(
            self.db_name,
            self.uid,
            self.password,
            model,
            operation.value,
            args,
            kwargs,
        )
    except xmlrpc.client.Fault as error:
        raise APIError(error.faultString) from error
    except socket.gaierror as error:
        raise APIError(str(error)) from error
class Operations (value, names=None, *, module=None, qualname=None, type=None, start=1)

Allowed API Operations

Expand source code
class Operations(enum.Enum):
    """Allowed API Operations"""

    WRITE = "write"
    CREATE = "create"
    READ = "read"
    SEARCH = "search"
    SEARCH_COUNT = "search_count"
    SEARCH_READ = "search_read"
    FIELDS_GET = "fields_get"
    UNLINK = "unlink"

Ancestors

  • enum.Enum

Class variables

var CREATE
var FIELDS_GET
var READ
var SEARCH
var SEARCH_COUNT
var SEARCH_READ
var WRITE