Odoo XMLRPC Wrapper
A small Python library for connecting to Odoo and working with its XML-RPC API.
Create, read, update, delete, search, and call custom model methods with a reusable
Bot instance.
This README describes 2.0.0. See the changelog and migration notes before upgrading from 1.1.1; the minimum Python version and some API behavior have changed.
Compatibility and installation
The current source requires Python 3.10+; CI tests Python 3.10–3.14. It uses
Python's XML-RPC client and defusedxml for hardened response parsing.
# Install this release from PyPI
python -m pip install --upgrade "odoo-xmlrpc-wrapper==2.0.0"
# Current source, from the repository directory
python -m pip install .
The server must expose /xmlrpc/2/common and /xmlrpc/2/object. Tests validate
client behavior without a live Odoo server; they do not certify every Odoo
release or hosted plan. Odoo has deprecated these APIs in favor of JSON-2;
see the official compatibility and migration notice.
This library implements XML-RPC.
Connect
Configure ODOO_HOST, ODOO_DB, ODOO_USERNAME, and ODOO_PASSWORD in your
process environment. An Odoo API key can be supplied in place of the password
where the server supports it.
import os
from odoo_xmlrpc_wrapper import Bot
with Bot(
host=os.environ["ODOO_HOST"], # e.g. odoo.example.com
db=os.environ["ODOO_DB"],
userlogin=os.environ["ODOO_USERNAME"],
password=os.environ["ODOO_PASSWORD"],
timeout=30,
) as bot:
print(bot.status())
partners = bot.search_read(
"res.partner",
constraints=[("is_company", "=", True)],
fields=["name", "email"],
limit=20,
)
The legacy import still works:
from odoo_xmlrpc_wrapper import odoo_xmlrpc_wrapper as oxw
HTTPS uses an explicit verifying TLS context with Python's secure defaults to
validate server certificates and hostnames. host accepts a hostname,
optional port/base path, or a full URL matching secured. Embedded credentials,
query strings, and fragments are rejected. Plain HTTP requires
secured=False, for example with host="localhost:8069" in local development.
timeout must be a finite positive number of seconds and applies to socket
operations. Connections are closed when leaving the with block; alternatively,
call bot.close(). A closed instance cannot be reused. Construction authenticates
and reads the user's profile but does not print anything.
Bot(test=True) provisions an external demo through https://demo.odoo.com/start.
It needs internet access, uses the returned HTTPS endpoint, and ignores supplied
credentials. It is a convenience for manual exploration; the test suite does not
use it.
CRUD operations
These examples assume an open bot connection. Each explicit model becomes the
active model for later calls. Immediately after login, the active model is
res.users. You can also set bot.model = "res.partner" directly. Use a separate
instance per thread because the active model and connection are shared state.
# Create returns the server-assigned record ID.
partner_id = bot.create("res.partner", {"name": "John Doe"})
# Read accepts one positive ID or a list/tuple of IDs.
records = bot.read(ids=[partner_id], fields=["name"])
# Update and delete return the server's result (normally True).
updated = bot.update(the_id=partner_id, the_obj={"name": "Jane Doe"})
deleted = bot.delete(ids=[partner_id])
No fixed record IDs are assumed. update() requires one positive integer ID;
booleans, missing IDs, and invalid values raise ValueError before a request.
create() and update() require a dictionary of field values.
Search, count, and metadata
ids = bot.search(
"res.partner",
constraints=[("is_company", "=", True)],
offset=0,
limit=20,
)
records = bot.search_read(
"res.partner",
constraints=[("id", "in", ids)],
fields=["name", "email"],
limit=20,
)
# Runs Odoo's search_count; does not download all record IDs.
total = bot.count("res.partner", constraints=[("is_company", "=", True)])
fields = bot.get_fields("res.partner", attributes=["string", "type"])
Omitting constraints searches the whole active model. search_read() defaults to
fields=["name"]; read() defaults to the fields selected by Odoo. Explicit empty
field/attribute lists and limit=0/offset=0 are forwarded unchanged, so their
meaning follows the server's API. In particular, zero is not a client-side
"return nothing" shortcut. Paginate large reads: individual XML responses are
limited to 30 MiB after decompression.
Custom model methods
result = bot.custom(
"res.partner",
"name_search",
att=["Azure"],
kwargs={"limit": 10},
)
att supplies positional arguments and kwargs supplies keyword arguments to
execute_kw. For backward compatibility, omitted att becomes [[]]; pass []
for a method taking no positional arguments. Private method names are rejected.
The wrapper returns the server result and respects Odoo's access controls.
Errors and security
- Invalid configuration or method arguments raise
ValueErrorlocally. - Failed authentication raises
PermissionErrorwithout including credentials. - XML-RPC faults, transport errors, and timeouts propagate to the caller. Catch
xmlrpc.client.Fault,OSError, orTimeoutErroras appropriate for your app. - XML responses containing DTDs, entities, or external references are rejected by
defusedxml. Excessively large responses are rejected before parsing completes. - Requests are never automatically retried, including on connection resets. Check the server state before retrying a timed-out write; it may already have succeeded. HTTP error bodies are discarded without reading them into memory.
See SECURITY.md for the security policy and private reporting channel.
Manual test against your Odoo server
From your checkout, build and install the package as a real pip distribution:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --require-hashes -r requirements-dev.txt
python -m build --no-isolation --outdir dist/2.0.0
python -m twine check --strict dist/2.0.0/*.whl dist/2.0.0/*.tar.gz
python -m pip install --no-deps --force-reinstall dist/2.0.0/*.whl
python examples/live_smoke_test.py
The script prints the installed package version, then prompts for the host,
database name, login/email, and password/API key. Password input is hidden and is
not saved to disk or passed as a command-line argument. Use the exact database
name, not the website name or a URL. HTTPS is the default; use --http only when
you explicitly intend to send credentials over an unencrypted connection.
It checks authentication, read, search, search_read, count, get_fields,
and custom(search_count) against your own res.users record, then closes the
connection. It does not create, update, or delete business records. Odoo itself
may record login/audit metadata during authentication. The account needs API and
model read access; an access fault alone does not establish a wrapper bug.
Success ends with SUCCESS: all live read checks passed. A failure returns a
nonzero exit status and identifies the stage without printing credentials or
raw server fault details. Share the package/server versions and PASS/FAIL lines
when reporting the result. This script is manual and is never run against a
real server by CI. Creation/update/deletion need a separate test using a
dedicated test record after the read checks pass.
These commands install the wheel rather than an editable checkout. To resume
source development afterwards, run
python -m pip install --no-deps --no-build-isolation -e ..
Read-only reporting examples
With the package installed, run these from the repository directory:
python examples/contacts.py --companies --limit 10
python examples/crm_pipeline.py --limit 15
python examples/sales_orders.py --state all --limit 10
python examples/model_fields.py --model crm.lead --query revenue
These examples query contacts, CRM stage counts and opportunities, recent sales
orders, and model metadata. They prompt for connection details and a hidden
password/API key. You can reuse ODOO_HOST, ODOO_DB, and ODOO_USERNAME across
runs; the password is always entered interactively. Lists are limited to 1–100
rows, and sales subtotals cover only displayed rows with currencies kept separate.
CRM and sales examples need the corresponding Odoo modules and read permissions.
The maintainer reported successful live runs of the read smoke test on Odoo
16.0-20250909 and all four reporting examples on 2026-09-22. See
the examples guide for filters, commands, and method mappings.
Development and checks
python3 -m venv .venv
source .venv/bin/activate
# Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install --require-hashes -r requirements-dev.txt
python -m pip install --no-deps --no-build-isolation -e .
# Syntax and style
python -m compileall -q src tests examples
python -m ruff check src tests examples
python -m ruff format --check src tests examples
# Offline regression and security tests, with branch coverage (minimum 90%)
python -m coverage run -m unittest discover -s tests -v
python -m coverage report
python -m coverage xml
# Static security, workflow security, and dependency health
python -m bandit -r src
zizmor --offline .github/workflows
python -m pip check
python -m pip_audit --strict --require-hashes -r requirements.txt
python -m pip_audit --strict --require-hashes -r requirements-dev.txt
# Build both distributions; validate package metadata and README rendering
python -m build --no-isolation
python -m twine check --strict dist/*.whl dist/*.tar.gz
The tests mock the RPC boundary and use in-memory XML responses. They cover CRUD arguments/results, authentication, input validation, active models, connection cleanup, timeouts, certificate verification, and malicious XML. Test execution does not provision demo servers, access credentials, or modify a real database. Dependency installation and vulnerability database updates need internet access.
Install Trivy separately (CI pins version 0.74.0), then run:
trivy fs --scanners vuln,misconfig,secret --severity HIGH,CRITICAL \
--exit-code 1 --skip-dirs .git,.venv,build,dist,.trivy-cache,.audit-reports \
--file-patterns 'pip:requirements.*\.txt' .
Trivy scans the complete dependency locks and project files, and fails on HIGH or CRITICAL findings, including unfixed issues. The misconfiguration scanner applies to supported infrastructure files when present; GitHub Actions are checked by zizmor. The secret scan covers the current working tree, not full Git history. pip-audit checks known advisories at all severities. Passing scans are evidence about the checked files and current databases, not a guarantee of no vulnerabilities.
Updating dependencies
pyproject.toml is the source of runtime requirements; requirements-dev.in lists
development tools. Both generated .txt files pin the complete dependency graph
and artifact hashes. With uv installed:
uv pip compile pyproject.toml --universal --python-version 3.10 \
--generate-hashes --upgrade -o requirements.txt
uv pip compile requirements-dev.in --universal --python-version 3.10 \
--generate-hashes --upgrade -o requirements-dev.txt
Review both diffs, reinstall in a fresh environment, and rerun the checks.
Routine Dependabot version-update PRs for Python dependencies and GitHub Actions
are paused with open-pull-requests-limit: 0. Update them manually using the
commands above and review pinned Action SHAs separately. This setting does not
disable Dependabot alerts or security-update PRs enabled in GitHub repository
settings. It takes effect once the configuration reaches the default branch.
Existing update PRs can be reviewed or closed separately. See the
Dependabot configuration reference.
Continuous integration
GitHub Actions runs syntax, lint, formatting, tests, coverage, packaging, and security checks on pushes and pull requests, weekly, and on manual dispatch. Actions are pinned to full commit SHAs with read-only default permissions. The packaging job installs the built wheel into a fresh environment and checks imports and the full offline test suite outside the source tree.
GitHub CodeQL also scans Python and GitHub Actions. Dependabot alerts, secret scanning, push protection, and private vulnerability reporting are enabled. See SECURITY.md for reporting instructions and scan limitations.
Coverage on Codecov
Codecov displays
coverage history and file/line details. The README coverage badge follows
master. A separate Python 3.13 job runs the tests, enforces the 90% coverage
minimum, and uploads coverage.xml using GitHub OIDC authentication. It needs
no CODECOV_TOKEN; only this job receives id-token: write permission.
Before the first upload, sign in to Codecov with GitHub, open Configure / Setup
repo for cagatayuresin/odoo-xmlrpc-wrapper, and select GitHub Actions.
Ensure the Codecov GitHub App is installed and
has access to this repository. The checked-in workflow uses OIDC rather than
the upload token shown in the onboarding example. Commit and push the workflow
changes, then check the coverage job and the Codecov report for that commit.
The badge may display an unknown value until the first successful report is
processed. If the dashboard remains empty afterward, check that Codecov's
Configuration → General → Default branch is master.
Uploads run for pushes, scheduled/manual runs, and pull requests from this repository. Fork pull requests and Dependabot runs still execute the test matrix, but skip the Codecov job. Upload failures fail the coverage job so a missing report is visible. codecov.yml disables bot comments and makes Codecov's project/patch status checks informational; GitHub Actions continues to enforce the 90% minimum independently on every tested Python version.
See the official Codecov action's OIDC setup.
SonarCloud Automatic Analysis
SonarQube Cloud (SonarCloud) analyzes this repository through its GitHub integration. In the SonarCloud project, select Administration → Analysis Method and turn Automatic Analysis on. Source paths, test paths, encoding, and Python versions are configured in .sonarcloud.properties.
Commit the configuration and workflow changes together and push them to master.
Then check the SonarCloud result for that commit. A green GitHub Actions CI
run alone does not confirm the separate SonarCloud analysis passed. Do not rerun
an older workflow containing the Sonar scanner after enabling automatic analysis;
CI-based and automatic Sonar analyses cannot run together for the same project.
The GitHub Actions workflow does not run a Sonar scanner and does not need
SONAR_TOKEN. An existing repository secret with that name can be removed from
GitHub settings if nothing else uses it. Local checks require no SonarCloud account.
Automatic Analysis does not import coverage reports. GitHub Actions still runs the full test suite with branch coverage on Python 3.10–3.14 and enforces a 90% minimum. Read coverage results in the Python jobs' test step. Syntax, lint, packaging, Bandit, pip-audit, Trivy, and workflow security checks also run in CI.
See the official Automatic Analysis documentation for supported configuration and limitations.
See CONTRIBUTING.md for contribution guidance and CHANGELOG.md for release history. Python 3.7–3.9 users need an older release; the current source intentionally targets maintained Python versions. Maintainers can follow RELEASING.md to publish a verified package through GitHub Actions and PyPI Trusted Publishing. The release workflow builds and tests the tagged source, then publishes with OIDC authentication. Build outputs stay out of Git; the same distributions are available on PyPI and as GitHub Release assets.
License and support
MIT © Cagatay URESIN.
Release files for odoo-xmlrpc-wrapper 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| odoo_xmlrpc_wrapper-2.0.0.tar.gz | 81.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| odoo_xmlrpc_wrapper-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 96.5 kB
Release files / odoo_xmlrpc_wrapper-2.0.0.tar.gz
| Download URL | odoo_xmlrpc_wrapper-2.0.0.tar.gz |
|---|---|
| Size | 81.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
44c147ac5d77d9c9960e62c2cdabbc7c06595fe597d34c8a059e279fc1e8a4aa
|
|
BLAKE2b-256 checksum How to use checksums |
4e2e7fe6faf29597afb608b1f5b6cbaff5d24f9b2bb2b68f4124bb1c7294d152
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / odoo_xmlrpc_wrapper-2.0.0-py3-none-any.whl
| Download URL | odoo_xmlrpc_wrapper-2.0.0-py3-none-any.whl |
|---|---|
| Size | 14.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e23a6c41ab9680d4d1cd3b8382dbde375532b9125ebc5bf8c1c91daa9ee18685
|
|
BLAKE2b-256 checksum How to use checksums |
cd5a5ed540aafdb98a3124065b18d7947bd91d95cb166b4488edfa631d8f9f27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency log