s6r-odoo-sh
Resolve the current build of an odoo.sh branch — its build number and its live SSH host — from Python.
Odoo.sh recycles old dev builds, so the SSH host of a branch changes on every
rebuild and any host cached elsewhere quickly dies. The current build lives only
in the odoo.sh dashboard (a GitHub-OAuth-gated Odoo instance), whose generic RPC
is locked down. s6r-odoo-sh reads it through the dashboard's own JSON routes.
How it works
- Runtime is httpx-only (light). It reuses a persisted browser session and, if the odoo.sh session expired but the GitHub session is still alive, the GitHub OAuth code flow replays transparently while following redirects.
- Login is interactive and one-time (the "PyCharm /
gh auth login" model): a browser window opens once for the GitHub login, the session is persisted, and every subsequent call runs headless. This step needs the optionalloginextra (Playwright); the runtime does not.
Install
pip install s6r-odoo-sh # runtime only (httpx)
pip install "s6r-odoo-sh[login]" # + Playwright, for the one-time login
s6r-odoo-sh --install-browser # download the Playwright browser (once)
Developing against a consumer project (editable install)
When working on this package alongside a project that depends on it (e.g.
scalidev), install it in editable mode from this checkout instead of a
published release:
pip install -e ~/Projects/Interne/s6r-odoo-sh
This points the consumer's Python environment straight at this repo's
src/s6r_odoo_sh/ — no copy is made into site-packages/. Any edit made here
takes effect the next time a process in that environment imports the package
(just restart it; no pip install needed after the first time). Check with:
pip show s6r-odoo-sh # look for "Editable project location: ..."
To go back to the published version pinned in the consumer's
requirements.txt, reinstall normally there (e.g.
pip install --no-build-isolation -r requirements.txt).
CLI
# one-time interactive login (opens a browser window)
s6r-odoo-sh --login -p myproject BRANCH
# current build of a branch (headless afterwards)
s6r-odoo-sh migration_19_v1 -p myproject
s6r-odoo-sh migration_19_v1 -p myproject --ssh-host # host string only
s6r-odoo-sh --list -p myproject # all branches
# ODOO_SH_PROJECT / ODOO_SH_STATE env vars are honored as defaults
Example output:
{
"project": "myproject",
"branch": "migration_19_v1",
"stage": "dev",
"build_id": 34305175,
"host_slug": "myproject-migration-19-v1-34305175",
"status": "done",
"result": "success",
"ssh_host": "34305175@myproject-migration-19-v1-34305175.dev.odoo.com"
}
Python API
from s6r_odoo_sh import OdooShClient, resolve
# one-shot (auto-login on first use)
build = resolve("myproject", "migration_19_v1")
print(build["build_id"], build["ssh_host"])
# reusable client, custom session path
client = OdooShClient(state_path="/path/to/state.json")
host = client.get_ssh_host("myproject", "migration_19_v1", auto_login=True)
for b in client.list_branches("myproject"):
print(b["name"], b["last_build_id"])
OdooShClient(state_path=None, base_url="https://www.odoo.sh", login_browser="firefox", login_timeout=300) —
state_path defaults to $XDG_CONFIG_HOME/odoo-sh/state.json. NeedLogin is
raised when no valid session exists and auto_login is not set.
Watching a build (CI: build + unit tests)
s6r-odoo-sh --build-status -p myproject BRANCH # current status/result/status_info
# after `git push`: wait for the new build to start, then for the tests to finish
s6r-odoo-sh --wait-build -p myproject BRANCH --commit "$(git rev-parse HEAD)"
s6r-odoo-sh --wait-build -p myproject BRANCH --after-build 34345516 # or by baseline build id
res = client.wait_for_build("myproject", "BRANCH", commit="abc123…")
# → {build_id, status: "done", result: "success"|"failed"|"warning", status_info, run_time, …}
status_info carries the test outcome (e.g. which modules failed). Match the build you pushed
with commit (SHA) or after_build_id (a build id captured before pushing).
Backups & database dumps
# create & download a dump of a branch in one go: triggers it, waits for the
# "Database dump ready" notification, then downloads the ZIP
s6r-odoo-sh --create-dump -p myproject BRANCH # → Downloads folder, auto-named
s6r-odoo-sh --create-dump ./db.zip -p myproject BRANCH # → explicit path (file or dir)
# --filestore include the filestore --prod production dump (default: neutralized test dump)
# download an ALREADY-prepared dump (no new dump) — pick it by build id or by date
s6r-odoo-sh --download-dump -p myproject --build 34345516
s6r-odoo-sh --download-dump -p myproject --backup-datetime "2026-07-02 13:40:53"
# create a persistent backup of a branch's current build
s6r-odoo-sh --create-backup -p myproject BRANCH --comment "before migration"
# any command targeting a build accepts --build <id> instead of a branch
s6r-odoo-sh --create-dump -p myproject --build 34345516
# inspect
s6r-odoo-sh --backups -p myproject # repository backups
s6r-odoo-sh --dump-notifs -p myproject # ready "database dump" notifications (with URLs)
res = client.create_dump("myproject", "BRANCH", "db.zip") # → {notif_id, url, build_id, backup_datetime_utc, dest}
client.create_backup("myproject", "BRANCH", comment="…")
client.dump_notifications("myproject") # ready dumps + their download URLs
client.download_url(url, "db.zip") # download any dump URL
Dumps are asynchronous: odoo.sh assigns the backup_datetime_utc and publishes a
notification carrying the authoritative download URL, which create_dump waits for.
It only accepts a notification newer than the ones present before the request and
whose build id and test_dump/filestore options match what it triggered, so a
different in-flight dump is not picked up by mistake. (odoo.sh returns no request id,
so two identical dumps of the same build launched at the same instant cannot be told
apart — an inherent, documented limitation.) The download is a ZIP (dump.sql +
filestore/); the worker authorizes it from the session via a redirect, and prepared
test dumps stay available for ~2h.
Monitoring (CPU, memory, IOPS, HTTP, logs)
The Monitor tab is a reverse-proxied Grafana instance; these calls read its data directly instead of scraping the rendered dashboard — the same numbers, ready to feed your own chart.
s6r-odoo-sh --monitoring-metrics -p myproject BRANCH --from "2026-10-05" --to "2026-10-09"
s6r-odoo-sh --monitoring-snapshot -p myproject BRANCH # always "last 24h/7 days", see below
s6r-odoo-sh --monitoring-annotations -p myproject BRANCH --from "2026-10-05" --to "2026-10-09"
from datetime import datetime, timezone
metrics = client.get_monitoring_metrics(
"myproject", "BRANCH",
start=datetime(2026, 10, 5, tzinfo=timezone.utc),
end=datetime(2026, 10, 9, tzinfo=timezone.utc),
)
# metrics["pad"]: buckets (width adapts to the range, ~5min for a short one) —
# cpu_http_sum/cpu_pg_sum/.../mem_user_sum/mem_pg_sum, iops_read_total_sum/
# iops_write_total_sum, http_success_sum/http_error_sum/http_concurrency_max,
# mail_in_sum/mail_out_sum, duration_sum (in seconds). *_sum fields are SUMS over the
# bucket — divide by duration_sum for a plottable average, e.g.:
mem_mb = [p["mem_user_sum"] / p["duration_sum"] for p in metrics["pad"]]
# Already filtered to [start, end) — odoo.sh's own routes return far more than asked
# (confirmed live: pad starts 24h before `start` and runs past `end` to "now").
# metrics["pa"]: coarser buckets (~3h) — size_database_avg/size_container_avg/size_toast_avg
# (size_database_avg already includes the toast size).
snapshot = client.get_monitoring_snapshot("myproject", "BRANCH")
# snapshot["storage"]: {filestore_mb, database_mb, logs_mb, remaining_mb, repositories_mb,
# sessions_mb} — converted from odoo.sh's raw kilobytes.
# snapshot["database"] / snapshot["filestore"]: lists of
# {table, size_mb, index_mb, toast_mb} / {mimetype, file_count, size_mb} dicts.
# snapshot["log_counter_daily"]["memory_limit"], snapshot["slowest_routes_weekly"], ...
annotations = client.get_monitoring_annotations(
"myproject", "BRANCH",
start=datetime(2026, 10, 5, tzinfo=timezone.utc),
end=datetime(2026, 10, 9, tzinfo=timezone.utc),
)
# [{"time": "2026-10-05T15:02:02+00:00", "text": "Commit made by <a ...", "color": "#E02F44"}, ...]
# A "#2596be" entry is a retention notice ("data before this point has been aggregated"),
# not a deploy — see s6r_odoo_sh.client.RETENTION_NOTICE_COLOR.
get_monitoring_snapshot ignores any notion of a selected period — its panels are
Grafana's own "Last 24h"/"Last 7 days", always relative to now, never to an arbitrary past
date (and the underlying data itself can lag up to 24h on odoo.sh's side). Only
get_monitoring_metrics/get_monitoring_annotations support historical ranges. Route keys in
slowest_routes_daily/slowest_routes_weekly are redacted of their query string by default
(redact=True) since Odoo's own request log can carry live OAuth tokens there.
License
LGPL-3.0-or-later.
Metadata
Release files for s6r-odoo-sh 0.2.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| s6r_odoo_sh-0.2.8.tar.gz | 33.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| s6r_odoo_sh-0.2.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.4 kB
Release files / s6r_odoo_sh-0.2.8.tar.gz
| Download URL | s6r_odoo_sh-0.2.8.tar.gz |
|---|---|
| Size | 33.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6e1fdde90930ae4c44a33c516d55e3437b67da467380f13ade98602f0bb3033a
|
|
BLAKE2b-256 checksum How to use checksums |
6c9a90e3f05f3626c715fc3fa59c7786f6254e47e43b3f7dc5c41658e29534ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / s6r_odoo_sh-0.2.8-py3-none-any.whl
| Download URL | s6r_odoo_sh-0.2.8-py3-none-any.whl |
|---|---|
| Size | 29.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4d78c19ffa43fd9f96fd3797a0c5c47a25b428ac3b44c77be6c3dd26853bc79c
|
|
BLAKE2b-256 checksum How to use checksums |
d903a2404c14f21b52bec5f165b2bd986818b870c0785dfdb65b6b614af7aea2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|