pyodio
Async Python client for odio — the live server state as high-level Python objects.
Part of the odio project — full documentation.
pyodio
Async Python client for odio-api — the universal remote for your Linux multimedia server.
pyodio gives you a high-level, stateful, event-driven API: connect once, and the library keeps a live mirror of the server state (MPRIS players, audio streams and outputs, systemd services, Bluetooth, upgrades) through the odio-api SSE stream, with automatic reconnection and resynchronization. Every entity carries both its current state and the commands that act on it.
Install
pip install pyodio
Requires Python ≥ 3.12. The only dependency is aiohttp.
Quick start
import asyncio
import pyodio
async def main():
async with pyodio.connect("http://odio.local:8018") as odio:
print(f"Connected to {odio.server.hostname} (odio-api {odio.server.api_version})")
# MPRIS players — live objects with transport controls
player = odio.players.find("spotify")
if player:
print(f"{player.title} — {player.artist} [{player.playback_status}]")
await player.play_pause()
await player.set_volume(0.5)
# Master volume / outputs
await odio.audio.set_volume(0.4)
for output in odio.audio.outputs.values():
print(f"{'*' if output.is_default else ' '} {output.description}")
# React to live changes pushed by the server
odio.players.on_change(lambda change, p: print(f"[{change}] {p.app_name}: {p.title}"))
await asyncio.sleep(60)
asyncio.run(main())
The URL defaults to http://localhost:8018; pyodio.connect() also works with the odio = await pyodio.connect(...) style (then call await odio.close() yourself).
The two layers
OdioHub — high-level (recommended)
pyodio.connect() / pyodio.OdioHub fetches a full snapshot, then applies SSE deltas forever:
| Domain | State | Commands |
|---|---|---|
odio.players |
live Player entities by bus name, find(), playing |
play/pause/play_pause/stop/next/previous/seek/set_position/set_volume/set_loop/set_shuffle, cover_url |
odio.audio |
master volume/muted, clients, outputs, default_output |
set_volume, set_muted, per-client/output volume & mute, make_default() |
odio.services |
Service entities by scope/name |
start/stop/restart/enable/disable |
odio.bluetooth |
adapter state + devices by MAC |
power_up/down, pairing_mode, scan, connect/disconnect |
odio.power |
can_reboot, can_power_off |
reboot(), power_off() |
odio.upgrade |
available, versions, live progress_percent |
check(), start() |
Only the domains whose backend is enabled server-side (odio.backends) are populated.
Niceties handled for you:
- Positions extrapolated:
player.positionprojects the last server beacon with the playback rate, so it is accurate between events (microseconds, like MPRIS). - Mute semantics: the server only supports toggling mute;
set_muted(True/False)compares with the live state and toggles only when needed. - Reconnection: the SSE stream reconnects with exponential backoff (1 s → 5 min) and re-snapshots the whole state after every reconnect. Watch it with
odio.connected/odio.on_connection_change(cb).
Subscriptions (all return an unsubscribe callable, listeners must not block):
odio.players.on_change(cb) # cb(change, player) change: added/updated/removed/position
odio.audio.on_change(cb) # cb(change, client_or_output)
odio.services.on_change(cb)
odio.bluetooth.on_change(cb) # includes "discovered" during scans
odio.upgrade.on_change(cb) # includes "progress" during upgrades
odio.on_event(cb) # every raw SSE event (pyodio.OdioEvent)
odio.on_connection_change(cb) # cb(connected: bool)
OdioClient — low-level
A stateless, typed, one-method-per-endpoint REST client, if you want full control:
from pyodio import OdioClient
async with OdioClient("http://odio.local:8018") as client:
info = await client.get_server_info()
players = await client.get_players()
await client.player_play(players[0].bus_name)
Pass an existing aiohttp.ClientSession as second argument to reuse it (e.g. Home Assistant's shared session) — the client then never closes it. pyodio.stream_events(client) exposes the raw SSE stream as an async generator, and pyodio.EventStream adds supervision (reconnect, listeners) without the stateful hub.
Errors
All errors derive from pyodio.OdioError:
OdioConnectionError— server unreachable, stream lostOdioTimeoutError— request or keepalive timeoutOdioApiError— HTTP error from the server (.status,.message)
Development
uv sync
uv run pytest
uv run ruff check src tests
uv run mypy src
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyodio-0.1.0.tar.gz.
File metadata
- Download URL: pyodio-0.1.0.tar.gz
- Upload date:
- Size: 94.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
002c14c112e7d4b4f2a843ded7d8c058749129f368745763ed467dd4cc5c684f
|
|
| MD5 |
de52a79078b0d4ce03f529ef97b30bbc
|
|
| BLAKE2b-256 |
42844615dcfe48efbfade0fb392ac81b125e48ebe664fe74ab92bf03a0ed579a
|
Provenance
The following attestation bundles were made for pyodio-0.1.0.tar.gz:
Publisher:
build.yml on b0bbywan/pyodio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyodio-0.1.0.tar.gz -
Subject digest:
002c14c112e7d4b4f2a843ded7d8c058749129f368745763ed467dd4cc5c684f - Sigstore transparency entry: 2227510091
- Sigstore integration time:
-
Permalink:
b0bbywan/pyodio@5870db3e5e4c5130ae94f335d8d1302c0f7545c8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/b0bbywan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@5870db3e5e4c5130ae94f335d8d1302c0f7545c8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyodio-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyodio-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7de8baaba13e7ad02ddea7080c73d3f189523002075418cfaa240c2c3c475abf
|
|
| MD5 |
8b00c9a18b94b4bbb4d3130decf1bfd4
|
|
| BLAKE2b-256 |
8ed93ac05d74e66acac88a11fdef102e65aca767dc3f5baeeda954f905a58a14
|
Provenance
The following attestation bundles were made for pyodio-0.1.0-py3-none-any.whl:
Publisher:
build.yml on b0bbywan/pyodio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyodio-0.1.0-py3-none-any.whl -
Subject digest:
7de8baaba13e7ad02ddea7080c73d3f189523002075418cfaa240c2c3c475abf - Sigstore transparency entry: 2227510178
- Sigstore integration time:
-
Permalink:
b0bbywan/pyodio@5870db3e5e4c5130ae94f335d8d1302c0f7545c8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/b0bbywan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@5870db3e5e4c5130ae94f335d8d1302c0f7545c8 -
Trigger Event:
push
-
Statement type: