← All posts

Odoo XMLRPC Wrapper 2.0.0: Python Automation for Odoo

September 23, 2026 odoopythonautomationxml-rpcerpopen-source
Teal glass integration hub connecting contact profiles, CRM bars, sales documents, and a model metadata grid

I have released Odoo XMLRPC Wrapper 2.0.0, a new major version of my Python library for working with the Odoo XML-RPC API. It is available on PyPI, and you can install it with pip:

python -m pip install --upgrade "odoo-xmlrpc-wrapper==2.0.0"

The library gives you a reusable Bot connection for reading, creating, updating, and deleting Odoo records, searching models, inspecting fields, and calling public model methods. Its purpose is straightforward: make the Odoo part of a Python automation script easier to write and maintain.

This release focuses on the details that matter when those scripts become recurring jobs: correct return values, explicit failures, bounded network responses, and connection cleanup. I also added four practical reporting examples covering contacts, CRM opportunities, sales orders, and model metadata. They show how the same small client can support useful workflows around an existing Odoo installation.

Version 2.0.0 requires Python 3.10 or newer. If you are upgrading from 1.1.1, review the behavior changes below before updating an existing job. The GitHub release was published on September 22, 2026.

What Is Odoo XMLRPC Wrapper?

Odoo stores business data in models such as res.partner for contacts, crm.lead for CRM leads and opportunities, and sale.order for sales orders. Its external API lets another application work with those models using an authenticated user’s access rights.

With a direct XML-RPC client, your script has to manage authentication and repeatedly assemble calls containing the database, user ID, credentials, model, method, and arguments. The wrapper keeps that connection setup in one place and exposes methods such as search_read(), count(), create(), and update().

The project started with that small scope in 2023. Version 2.0.0 keeps the same approach while improving the behavior around it. It remains a synchronous Python library that you call from your own application. Scheduling, data transformation, notifications, and workflow state belong to the application using it.

The source is available under the MIT license in the Odoo XMLRPC Wrapper repository.

Where It Fits in Odoo Automation

A common integration starts with a simple requirement: read something from Odoo, apply a rule, and do something useful with the result. That might become a daily sales digest, a CRM follow-up report, or a process that synchronizes contact details with another system.

The wrapper provides the Odoo connection in workflows such as these:

WorkflowRole of the Python script
Contact synchronizationFind records using a stable external identifier, compare selected fields, and create or update records as needed.
CRM reportingCount open opportunities, group them by stage, and prepare a team digest.
Sales monitoringRead orders for a date range and state, then prepare an operational summary.
Integration setupInspect field names, types, and relations before mapping data between systems.
Scheduled checksQuery a bounded set of records and notify another service when a condition is met.

These are workflows you can build around the library; the package does not include a scheduler, a synchronization engine, or notification delivery. A Python worker can run under cron, a task queue, or a Kubernetes CronJob. A workflow tool such as n8n could orchestrate a service that uses the library, with the connection and business logic living in Python.

That is also where the new examples help. They make the data access part concrete before you add scheduling or delivery.

Connect to Odoo from Python

After installing the package, configure ODOO_HOST, ODOO_DB, ODOO_USERNAME, and ODOO_PASSWORD in the process environment. Use the actual Odoo database name. An API key can replace the password where your server supports it.

This example reads a small company-contact report:

import os

from odoo_xmlrpc_wrapper import Bot

with Bot(
    host=os.environ["ODOO_HOST"],
    db=os.environ["ODOO_DB"],
    userlogin=os.environ["ODOO_USERNAME"],
    password=os.environ["ODOO_PASSWORD"],
    timeout=30,
) as bot:
    companies = bot.search_read(
        "res.partner",
        constraints=[("is_company", "=", True)],
        fields=["name", "city"],
        limit=10,
    )

    for company in companies:
        print(company["name"], company.get("city") or "")

HTTPS is the default. The with block closes the connections when it exits, including when the body raises an exception. Construction authenticates silently; use print(bot.status()) when you explicitly want connection information.

For an unattended job, inject credentials through your deployment’s secret configuration and use an account with the permissions that job needs. Select only the required fields and keep reads bounded. The wrapper preserves Odoo’s access rules, so a report reflects what the authenticated account can see.

Four New Reporting Examples

The v2.0.0 examples directory includes four reports and a separate connection smoke test. To get the scripts as well as the installed library:

git clone --branch v2.0.0 --depth 1 https://github.com/cagatayuresin/odoo-xmlrpc-wrapper.git
cd odoo-xmlrpc-wrapper
python -m pip install "odoo-xmlrpc-wrapper==2.0.0"

The reporting scripts ask for connection details and a hidden password or API key. Host, database, and username can default from the environment, but the password is always entered interactively. The commands below are therefore manual exploration tools. For unattended automation, reuse their query patterns in a worker with its own credential handling, as in the Python example above.

All four reports read data without creating, updating, or deleting business records. Display limits are restricted to 1–100 rows. CRM and sales reports require the corresponding Odoo modules and read permissions.

Contacts and Companies

python examples/contacts.py --companies --limit 10
python examples/contacts.py --query "Acme" --limit 5

contacts.py uses count() and search_read() on res.partner. It shows active contacts or companies, including location information, and distinguishes the full matching count from the number of displayed rows.

This is a useful starting point for a contact export or synchronization job: define the selection, request the fields you need, and inspect the result before adding write operations. A company contact can also be a vendor, so --companies should not be treated as a customer-only filter.

CRM Pipeline and Opportunity Counts

python examples/crm_pipeline.py --limit 15
python examples/crm_pipeline.py --mine --limit 10

crm_pipeline.py selects active opportunities whose stage is not marked as won. It displays opportunity details and uses Odoo’s read_group method to calculate counts by stage on the server. The --mine option restricts the report to opportunities assigned to the authenticated user.

The automation opportunity here is a recurring pipeline digest. Server-side grouping avoids downloading every opportunity just to count stages. Each displayed stage count covers its full matching group, while the stage list and detail table are limited. Expected revenue retains its currency; the example does not combine different currencies into one total.

Recent Sales Orders

python examples/sales_orders.py --limit 10
python examples/sales_orders.py --state all --limit 20
python examples/sales_orders.py --since 2026-09-01 --state draft --mine --limit 10

sales_orders.py shows recent orders, their states, customers, and amounts. By default, it selects orders in the sale state from the past 30 days. You can change the cutoff date, state, and salesperson filter. The results are explicitly sorted newest first through custom(..., "search_read", kwargs=...).

This query pattern can support order monitoring or a daily operational digest. The displayed subtotals include tax, cover only the displayed orders, and remain separate by currency. They are not full-period totals or posted invoice revenue.

Model Field Metadata

python examples/model_fields.py --model crm.lead --query revenue
python examples/model_fields.py --model res.partner --query country

model_fields.py uses get_fields() to inspect technical field names, labels, types, required and readonly flags, and related models. The search matches field names and labels locally.

This is particularly useful when adapting an integration to an unfamiliar database or custom module. You can inspect the accessible schema before writing a field mapping. The display limit applies after metadata is fetched, and a readonly flag is field metadata rather than a complete test of the user’s permissions.

The examples guide documents the filters and output semantics in more detail.

What Changed in Version 2.0.0?

The examples are the most visible addition. Underneath them, the release fixes several behaviors that affect real automation scripts.

Correct CRUD Results and More Efficient Counts

create() now returns the new record ID. update() and delete() return Odoo’s result, normally True, instead of discarding it. The ID validation bug in update() is also fixed.

That matters when one operation feeds another: a script can retain the ID returned by creation and use it in a later step. Existing callers that assumed these methods returned None need review.

count() now calls Odoo’s search_count directly. A monitoring job asking how many records match a condition no longer needs to retrieve every matching ID just to calculate their number.

Connection Lifecycle and Custom Keyword Arguments

You can now import Bot directly, use a context manager, or call close() explicitly. Cleanup also handles failed initialization, and a closed instance cannot be reused.

custom() now accepts keyword arguments for public model methods. The CRM and sales examples use this to pass aggregation and ordering options without adding a separate wrapper method for each case.

The older import style and active-model behavior remain available. For scripts that work across several models, I prefer passing the model explicitly at each call so the target is visible. Use a separate Bot instance per thread: both the active model and the connection are shared state within an instance.

Bounded Requests and Hardened XML Parsing

Version 2.0.0 adds a default 30-second socket timeout and limits XML responses to 30 MiB after decompression. Large exports should use pagination. The timeout applies to socket operations rather than defining a deadline for the entire job.

HTTPS certificate and hostname verification remain enabled. The release adds stricter URL validation and uses defusedxml to reject DTDs, entities, and external references in responses. Those parser protections are local to this client rather than a process-wide XML-RPC patch.

Requests are also no longer automatically replayed after a disconnect. This is an important detail for write workflows: if Odoo created a record but the connection failed before the response arrived, blindly retrying could create a duplicate. The application should reconcile the server state before repeating an operation whose outcome is unknown. A stable external identifier and duplicate detection belong in the synchronization logic.

Packaging, Tests, and Publishing

Package metadata now lives in pyproject.toml, dependencies have hash-locked requirements files, and CI covers Python 3.10–3.14. The pipeline also installs and tests the built wheel outside the source tree, checking the distributed package as well as the checkout.

The release reports 80 passing offline tests and 95% package coverage with branch coverage enabled. CI enforces a 90% minimum. PyPI publishing uses GitHub OIDC through Trusted Publishing, and the project includes dependency, source, and workflow security checks.

The release notes also record successful live read checks against Odoo 16.0-20250909 and successful runs of all four reports. Create, update, and delete behavior is covered by offline tests; it was not validated against that live deployment.

Upgrading from 1.1.1

This is a major version because some observable behavior changes. Alongside the Python 3.10 minimum, review these points in existing integrations:

AreaWhat to check in your application
Return valuesUpdate logic that relied on create(), update(), or delete() returning None.
ValidationHandle ValueError for invalid local arguments and PermissionError for rejected authentication.
LoggingCall status() explicitly if you need connection details; construction no longer prints them.
Fields and paginationExplicit empty lists and zero pagination values now reach Odoo unchanged. Do not use limit=0 to mean “return no rows.”
Network behaviorChoose an appropriate socket timeout and paginate responses that could exceed 30 MiB.
Retry logicReconcile uncertain write outcomes before retrying; the client does not automatically replay requests.
Custom methodsThe legacy default remains att=[[]]; pass att=[] for a method with no positional arguments.

Start with the migration notes, then run the manual read smoke test against your target installation:

python examples/live_smoke_test.py

It checks authentication, record reads, searches, counts, metadata, and a custom read call. Validate any write workflow separately with dedicated test records before updating a recurring job.

Odoo Compatibility and the XML-RPC Roadmap

The server must expose /xmlrpc/2/common and /xmlrpc/2/object, and the account must have the API and model access your workflow needs. The tested Odoo 16 deployment is a concrete validation point, not a compatibility guarantee for every version, module combination, or hosted plan.

Odoo has deprecated the external XML-RPC API in favor of JSON-2. This package continues to implement XML-RPC, so its immediate use is in installations and integrations that still expose those endpoints. For a new integration or an Odoo upgrade, check the official external API migration notice against your target version.

Conclusion

Odoo XMLRPC Wrapper 2.0.0 keeps the library small while making its behavior more useful for repeatable automation. The new reports provide concrete starting points; the corrected results, connection cleanup, and explicit failure behavior help when you turn those queries into maintained jobs.

If your Odoo installation exposes XML-RPC, start with the report closest to your use case, inspect the data your account can access, and build the surrounding workflow from there. The 2.0.0 package is on PyPI, and the source and examples are on GitHub.