Skip to main content

FastAPI service exposing Odoo instance status and module/package diagnostics

Project description

Odoo Instance API

A small FastAPI service that exposes a single endpoint (/status) for retrieving diagnostic information about a local Odoo installation in a structured JSON format.

It is designed to run on the same host as Odoo, read the Odoo configuration (/etc/odoo/odoo.conf by default), and query Postgres for installed databases and non-core modules. It also checks the Odoo pyenv environment for installed packages and queries PyPI for available updates (including pre-releases).

Finally, the app maps installed Odoo packages to database modules using the manifest path heuristic and reports version drifts across DB module version, installed package version, and PyPI latest version.

The intended use case is machine consumption, but the endpoint also supports HTML rendering for easy human inspection when needed

Rationale

Despite we can list modules with a Prometheus exporter, this is an antipattern for several reasons:

  • Performance: Querying ir_module_module across multiple databases and making PyPI API calls on every scrape would be very expensive (around ~20 seconds per scrape) and could lead to timeouts or high latency in Prometheus, with no real benefit since this data is not needed in real-time for alerting or monitoring.
  • Cardinality: The number of non-core modules can vary widely across databases and instances. Exposing this via a Prometheus exporter would require complex label management and could lead to high cardinality issues in Prometheus.
  • Execute on demand: This service is intended to be hit on-demand (e.g., via a button in Odoo Instances) rather than being scraped frequently. It can provide a comprehensive snapshot of the Odoo instance state without impacting performance.

🚀 Getting started (dev)

1) Create an app pyenv virtualenv and install dependencies

pyenv virtualenv 3.11.8 odoo-instance-api
pyenv activate odoo-instance-api
pip install -r requirements.txt
export ODOO_VENV_PATH=/home/odoo/pyenv/versions/odoo-16/bin/python

2) Run the service

uvicorn odoo_instance_api.main:app --host 127.0.0.1 --port 8000

Or via the package CLI entrypoint:

odoo-instance-api --host 127.0.0.1 --port 8000

Run with debug logs:

odoo-instance-api --host 127.0.0.1 --port 8000 --log-level debug

3) Hit the endpoint

  • JSON: curl http://127.0.0.1:8000/status
  • HTML: curl "http://127.0.0.1:8000/status?format=html"

In HTML mode, these same filters are available as in-page controls (checkbox/select/input) and are synced to the URL query string for shareable links.

Filter examples (work for both JSON and HTML):

  • Only DB↔Installed drifts: curl "http://127.0.0.1:8000/status?only_drift=true"
  • One database only: curl "http://127.0.0.1:8000/status?database=odoov16"
  • Limit relation rows: curl "http://127.0.0.1:8000/status?limit=50"
  • Combined: curl "http://127.0.0.1:8000/status?database=odoov16&only_drift=true&limit=20"

🔧 How it works (core design)

  • Single endpoint: /status returns a JSON payload (or HTML when requested).
  • Entrypoint implementation: odoo_instance_api/main.py
  • Odoo inspection environment: Uses ODOO_VENV_PATH (defaults to /home/odoo/pyenv/versions/odoo-16/bin/python) to run pip list --format=json and detect installed Odoo packages.
  • PyPI checks: For any odoo* packages found, it queries https://pypi.org/pypi/<package>/json and compares versions (including pre-releases) using packaging.version.
  • Database discovery: Reads Postgres credentials from /etc/odoo/odoo.conf (override via ODOO_CONF_PATH) and lists non-template databases, excluding postgres.
  • Non-core module listing: For each database, it queries ir_module_module with an Odoo-core author filter and supports DB schemas using either installed_version or latest_version.
  • Package↔module mapping (Phase A): It inspects installed odoo* distributions in the Odoo pyenv env, derives addon names from __manifest__.py/__openerp__.py paths, and matches those names against DB module names.
  • Version drift diagnostics: For matched pairs, it reports whether versions differ across DB module version, installed package version, and PyPI latest version.

🧩 Environment overrides

Runtime model:

  • The API itself runs in an app pyenv (for FastAPI/Uvicorn/dependencies).
  • Odoo package inspection runs in the Odoo pyenv pointed by ODOO_VENV_PATH.
Env var Purpose Default
ODOO_VENV_PATH Path to the Odoo Python executable (typically from pyenv) /home/odoo/pyenv/versions/odoo-16/bin/python
ODOO_CONF_PATH Path to the Odoo config file (Postgres credentials) /etc/odoo/odoo.conf
ODOO_SOURCE_CODE_PATH Odoo source root or addons dir; modules under addons are treated as core-distributed auto-discovered from ODOO_VENV_PATH when unset
OIA_LOG_LEVEL Application log level (critical, error, warning, info, debug) INFO

Centralized assumptions

Schema/runtime assumptions are centralized in odoo_instance_api/assumptions.py.

When Odoo/Postgres conventions change (table names, preferred version columns, core author filters, default paths), update assumptions once instead of patching multiple modules.

You can also override assumptions at runtime via environment variables:

  • OIA_DEFAULT_ODOO_PYTHON_PATH
  • OIA_DEFAULT_ODOO_CONF_PATH
  • OIA_DEFAULT_ODOO_SOURCE_CODE_PATH
  • OIA_IR_MODULE_TABLE
  • OIA_IR_MODULE_VERSION_COLUMNS (comma-separated)
  • OIA_ODOO_CORE_AUTHORS (comma-separated)
  • OIA_ODOO_CORE_MODULES (comma-separated)
  • OIA_ODOO_PACKAGE_NAME_PREFIXES (comma-separated)
  • OIA_ADDON_MANIFEST_FILENAMES (comma-separated)
  • OIA_PYPI_PACKAGE_JSON_URL_TEMPLATE
  • OIA_PYPI_HTTP_TIMEOUT_SECONDS

Compatibility shortcuts remain supported:

  • ODOO_VENV_PATH (takes precedence over OIA_DEFAULT_ODOO_PYTHON_PATH)
  • ODOO_CONF_PATH (takes precedence over OIA_DEFAULT_ODOO_CONF_PATH)

Logging

  • CLI flag: --log-level {critical,error,warning,info,debug}
  • Environment override: OIA_LOG_LEVEL (used as default when --log-level is not provided)

Example:

export OIA_LOG_LEVEL=debug
odoo-instance-api --host 127.0.0.1 --port 8000

📦 Mapping output in /status

Optional query params

  • only_drift=true: keeps only rows where DB module and installed package versions are mismatched.
  • database=<name>: filters output to a single database name.
  • limit=<n>: limits package_module_relation_table.rows to first n rows.

Defaults remain unchanged when params are omitted.

The JSON response includes two mapping-oriented blocks:

  • package_module_mapping: detailed diagnostics grouped by database
  • package_module_relation_table: flattened table with one row per (database, pypi_package, db_module) relation
  • uninstalled_module_matches: packages whose candidate modules exist in DB but are currently uninstalled
  • core_module_exclusions: configured module names treated as core and excluded from unmatched-module accounting

package_module_mapping includes:

  • method: currently manifest_path_match
  • by_database[]: mapping diagnostics per database, including package_to_modules[], matched_modules[], unmatched_packages[], and unmatched_modules[]
  • stats: global counters (matched_pairs, unmatched_packages, unmatched_modules)

mapping_summary.by_database[] also includes explicit unmatched details (not only counts):

  • unmatched_package_names[]
  • unmatched_module_names[]

mapping_summary.totals includes deduplicated global lists:

  • unmatched_package_names[]
  • unmatched_module_names[]

Each matched module includes version_drift flags:

  • db_vs_package_installed
  • db_vs_pypi_latest
  • package_installed_vs_pypi_latest

package_module_relation_table includes:

  • count
  • rows[] with: database, pypi_package, db_module, db_module_version, package_installed_version, package_pypi_latest_version, drift flags, and mapping confidence/reason.

Quick inspect command:

curl -s http://127.0.0.1:8000/status | jq '.package_module_relation_table.rows[] | {database, pypi_package, db_module, db_module_version, package_installed_version, package_pypi_latest_version, drift_db_vs_package_installed, drift_db_vs_pypi_latest, drift_package_installed_vs_pypi_latest}'

🔒 Recommended proxy configuration (NGINX)

This service is intended to run behind a proxy (e.g., NGINX) that can handle authentication, TLS, and routing.

Example NGINX snippet:

location /status {
    proxy_pass http://127.0.0.1:8000/status;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 60;
    proxy_connect_timeout 10;
}

🪲 Troubleshooting

  • No databases returned? Verify the service user can read /etc/odoo/odoo.conf and that the Postgres user/credentials can connect.
  • No Odoo packages listed? Verify ODOO_VENV_PATH points at the Odoo runtime pyenv environment and that pip list works there.
  • The service crashes on startup? It should not crash; errors are printed and the service continues with partial results.
  • Need more diagnostics? Start with --log-level debug (or set OIA_LOG_LEVEL=debug) and inspect logs for package counts, interpreter path selection, candidate counts, database discovery, and mapping stats.

🗺 Roadmap (next improvements)

  1. Add optional fuzzy fallback mapping
    • Add a secondary heuristic layer for packages that do not expose manifest paths in wheel metadata.
    • Keep confidence tiering explicit (high/medium/low) and avoid auto-accepting ambiguous matches.

✅ Running as a systemd service

This repo includes an example systemd unit file: odoo-instance-api.service.

Install / enable

  1. Copy the unit file to systemd's directory:
sudo cp odoo-instance-api.service /etc/systemd/system/
  1. Reload systemd and enable the service:
sudo systemctl daemon-reload
sudo systemctl enable --now odoo-instance-api.service
  1. Check the service status:
sudo systemctl status odoo-instance-api.service
sudo journalctl -u odoo-instance-api.service -f

Notes

  • The unit file assumes the code lives at /opt/odoo_instance_api, runs Uvicorn from the app pyenv (/home/odoo/pyenv/versions/odoo-instance-api/bin/uvicorn) with module odoo_instance_api.main:app, and sets ODOO_VENV_PATH to the Odoo pyenv Python (/home/odoo/pyenv/versions/odoo-16/bin/python).
  • Update User= and the paths in ExecStart= as needed for your environment.
  • Authentication and TLS should be handled by a reverse proxy (e.g., NGINX) in front of this service.

🐳 Running with Docker Compose

Use Compose when you want the API isolated but still able to read host Odoo config/source and inspect Odoo's Python env.

1) Prepare environment values

cp .env.docker.example .env

Adjust at least these values in .env:

  • HOST_UID and HOST_GID (must match host odoo UID/GID)
  • POSTGRES_GID (GID of host postgres group)
  • HOST_ODOO_ETC_DIR (usually /etc/odoo_16)
  • HOST_ODOO_PYENV_DIR (usually /home/odoo/pyenv)
  • HOST_ODOO_HOME_DIR (usually /home/odoo)
  • HOST_ODOO_SOURCE_DIR (usually /opt/odoo_16)
  • ODOO_VENV_PATH (container path to the Odoo interpreter, for example /opt/pyenv/versions/3.11.13/envs/odoo-16/bin/python)

To get identity values:

id -u odoo
id -g odoo
getent group postgres | cut -d: -f3

Then copy those numbers into .env as literals, for example:

HOST_UID=1001
HOST_GID=1001
POSTGRES_GID=114

If DB connectivity through odoo.conf does not work from the container, set PG overrides:

  • PGHOST
  • PGPORT
  • PGUSER
  • PGPASSWORD

2) Build and run

docker compose up -d --build --force-recreate

If your Docker installation uses the standalone binary, use:

docker-compose up -d --build --force-recreate

Check logs:

docker compose logs -f odoo-instance-api

3) Test endpoint

curl http://127.0.0.1:8000/status
curl "http://127.0.0.1:8000/status?format=html"

Compose notes

  • The Compose service uses network_mode: host (Linux). This avoids bridge-network issues when Odoo/Postgres use localhost-bound settings.
  • The Compose service runs with host UID/GID + POSTGRES_GID to satisfy PostgreSQL peer-auth and socket permissions.
  • Pyenv is mounted at both /opt/pyenv and /home/odoo/pyenv so absolute symlinks inside virtualenvs remain valid.
  • security_opt: apparmor:unconfined is enabled to avoid host-mounted interpreter exec restrictions on AppArmor-based systems.
  • Runtime command comes from container entrypoint and supports extra args (for example --log-level debug).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

odoo_instance_api-0.1.1.tar.gz (31.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

odoo_instance_api-0.1.1-py3-none-any.whl (29.8 kB view details)

Uploaded Python 3

File details

Details for the file odoo_instance_api-0.1.1.tar.gz.

File metadata

  • Download URL: odoo_instance_api-0.1.1.tar.gz
  • Upload date:
  • Size: 31.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.8.12

File hashes

Hashes for odoo_instance_api-0.1.1.tar.gz
Algorithm Hash digest
SHA256 7a5e7da345fd2c26c35806f8e8b91ed0ad87f444ed02d2dc7f4c2ff7468ac7b5
MD5 9dbc54fea6b15ec572a8eefea66fbac8
BLAKE2b-256 34e72aa622a8f22c6500bcac83058d7af8e23d637015025efec688a8549d6892

See more details on using hashes here.

File details

Details for the file odoo_instance_api-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for odoo_instance_api-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9b987ecfdd568e3c3c2f6adb2e6934bd10611e25d104435e1bce253e26b3a02d
MD5 0a7fbaea49c46afd1b56cdac67d08adc
BLAKE2b-256 625562fc82eb7dc1c33add87618d5054223d7fc41584198b17ddec852d97d781

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page