Skip to content

Exceptions

All Vodoo exceptions inherit from VodooError, allowing consumers to catch a single base class.

The Odoo server-side exceptions (OdooUserError and subclasses) mirror the hierarchy from odoo/exceptions.py. When the transport layer receives an error whose data.name matches a known Odoo exception, it raises the corresponding Vodoo exception — so you can handle specific failure modes without parsing error strings.

VodooError
├── ConfigurationError
├── AuthenticationError
├── RecordNotFoundError
├── RecordOperationError
├── TransportError
│   └── OdooUserError              ← odoo.exceptions.UserError
│       ├── OdooAccessDeniedError  ← odoo.exceptions.AccessDenied
│       ├── OdooAccessError        ← odoo.exceptions.AccessError
│       ├── OdooMissingError       ← odoo.exceptions.MissingError
│       └── OdooValidationError    ← odoo.exceptions.ValidationError
└── FieldParsingError

Usage

from vodoo import (
    VodooError,
    AuthenticationError,
    TransportError,
    RecordNotFoundError,
)
from vodoo.exceptions import (
    OdooAccessError,
    OdooValidationError,
    OdooMissingError,
)

try:
    client.write("res.partner", [999999], {"name": "test"})
except OdooAccessError:
    print("You don't have permission for this operation")
except OdooMissingError:
    print("Record no longer exists")
except OdooValidationError as e:
    print(f"Constraint violated: {e}")
except RecordNotFoundError as e:
    print(f"Not found: {e.model} #{e.record_id}")
except TransportError as e:
    print(f"RPC error [{e.code}]: {e}")
except VodooError:
    print("Something else went wrong")

Reference

exceptions

Vodoo exception hierarchy.

All public exceptions inherit from :class:VodooError so that library consumers can catch a single base class when desired.

The Odoo-server error classes (:class:OdooUserError and its subclasses) mirror the hierarchy defined in odoo/exceptions.py on the server. When the transport layer receives a JSON-RPC or JSON-2 error whose data.name matches a known Odoo exception class, it raises the corresponding Vodoo exception so callers can handle specific failure modes without parsing error strings.

Hierarchy overview::

VodooError
├── ConfigurationError
├── AuthenticationError
├── AuthenticationError
├── RecordNotFoundError
├── RecordOperationError
├── TransportError
│   └── OdooUserError           ← odoo.exceptions.UserError
│       ├── OdooAccessDeniedError    ← odoo.exceptions.AccessDenied
│       ├── OdooAccessError     ← odoo.exceptions.AccessError
│       ├── OdooMissingError    ← odoo.exceptions.MissingError
│       └── OdooValidationError ← odoo.exceptions.ValidationError
└── FieldParsingError

VodooError

Bases: Exception

Base exception for all Vodoo errors.

ConfigurationError

Bases: VodooError

Raised when the configuration is invalid or incomplete.

AuthenticationError

Bases: VodooError

Raised when authentication with the Odoo server fails.

RecordNotFoundError

RecordNotFoundError(model: str, record_id: int)

Bases: VodooError

Raised when an expected Odoo record does not exist.

Source code in src/vodoo/exceptions.py
def __init__(self, model: str, record_id: int) -> None:
    self.model = model
    self.record_id = record_id
    super().__init__(f"Record {record_id} not found in {model}")

RecordOperationError

Bases: VodooError

Raised when a write/create/unlink operation fails.

TransportError

TransportError(message: str, code: int = -1, data: dict[str, Any] | None = None)

Bases: VodooError

Raised on JSON-RPC / HTTP transport failures.

Carries the numeric error code and the raw data dict returned by the server so that callers can inspect details without string-parsing.

Source code in src/vodoo/exceptions.py
def __init__(
    self,
    message: str,
    code: int = -1,
    data: dict[str, Any] | None = None,
) -> None:
    self.code = code
    self.data = data or {}
    super().__init__(f"[{code}] {message}")

OdooUserError

OdooUserError(message: str, code: int = -1, data: dict[str, Any] | None = None)

Bases: TransportError

The server raised odoo.exceptions.UserError.

This is the generic Odoo end-user error — semantically a 400-class problem (bad request / invalid operation given the current state).

Source code in src/vodoo/exceptions.py
def __init__(
    self,
    message: str,
    code: int = -1,
    data: dict[str, Any] | None = None,
) -> None:
    self.code = code
    self.data = data or {}
    super().__init__(f"[{code}] {message}")

OdooAccessDeniedError

OdooAccessDeniedError(message: str, code: int = -1, data: dict[str, Any] | None = None)

Bases: OdooUserError

The server raised odoo.exceptions.AccessDenied.

Login or API-key authentication failed.

Source code in src/vodoo/exceptions.py
def __init__(
    self,
    message: str,
    code: int = -1,
    data: dict[str, Any] | None = None,
) -> None:
    self.code = code
    self.data = data or {}
    super().__init__(f"[{code}] {message}")

OdooAccessError

OdooAccessError(message: str, code: int = -1, data: dict[str, Any] | None = None)

Bases: OdooUserError

The server raised odoo.exceptions.AccessError.

The authenticated user lacks the required access rights (ACL / record rules) for the attempted operation.

Source code in src/vodoo/exceptions.py
def __init__(
    self,
    message: str,
    code: int = -1,
    data: dict[str, Any] | None = None,
) -> None:
    self.code = code
    self.data = data or {}
    super().__init__(f"[{code}] {message}")

OdooMissingError

OdooMissingError(message: str, code: int = -1, data: dict[str, Any] | None = None)

Bases: OdooUserError

The server raised odoo.exceptions.MissingError.

The record(s) referenced in the operation no longer exist.

Source code in src/vodoo/exceptions.py
def __init__(
    self,
    message: str,
    code: int = -1,
    data: dict[str, Any] | None = None,
) -> None:
    self.code = code
    self.data = data or {}
    super().__init__(f"[{code}] {message}")

OdooValidationError

OdooValidationError(message: str, code: int = -1, data: dict[str, Any] | None = None)

Bases: OdooUserError

The server raised odoo.exceptions.ValidationError.

A Python constraint or SQL constraint was violated during create/write.

Source code in src/vodoo/exceptions.py
def __init__(
    self,
    message: str,
    code: int = -1,
    data: dict[str, Any] | None = None,
) -> None:
    self.code = code
    self.data = data or {}
    super().__init__(f"[{code}] {message}")

FieldParsingError

Bases: VodooError

Raised when a field=value assignment cannot be parsed.

transport_error_from_data

transport_error_from_data(message: str, code: int = -1, data: dict[str, Any] | None = None) -> TransportError

Create the most specific :class:TransportError subclass for data.

Inspects data["name"] (set by Odoo's serialize_exception) and returns an instance of the matching :class:OdooUserError subclass when possible, falling back to plain :class:TransportError.

Source code in src/vodoo/exceptions.py
def transport_error_from_data(
    message: str,
    code: int = -1,
    data: dict[str, Any] | None = None,
) -> TransportError:
    """Create the most specific :class:`TransportError` subclass for *data*.

    Inspects ``data["name"]`` (set by Odoo's ``serialize_exception``) and
    returns an instance of the matching :class:`OdooUserError` subclass
    when possible, falling back to plain :class:`TransportError`.
    """
    if data:
        exc_name = data.get("name", "")
        cls = ODOO_EXCEPTION_MAP.get(exc_name, TransportError)
    else:
        cls = TransportError
    return cls(message, code=code, data=data)