Skip to main content
SnapDog

pysnapdog: async Python client for the SnapDog multi-room audio server

Typed. Fully asynchronous. Built for Home Assistant, usable everywhere.

CI License: Apache-2.0 Python 3.14+ Typed


pysnapdog talks to a SnapDog server over its REST API and, as the library grows, over its WebSocket. It is the client library behind the SnapDog integration for Home Assistant, and it has no Home Assistant dependency of its own: you hand it an aiohttp session and it hands you typed objects.

Features

⚡ Async only Built on aiohttp. You own the session, the library never opens or closes one.
🧩 Typed models Frozen dataclasses with slots. A response that lacks a field raises an error instead of producing a half-filled object.
📡 Live updates SnapDogListener keeps the WebSocket open, reconnects with a growing wait, and delivers events only after your initial read, so nothing is lost in between.
📜 Checked against the contract The tests run against a fake server built from the server's own openapi.json and asyncapi.json, which are pinned in spec/.
🔐 API key in the header Authorization: Bearer, never in a URL. TLS with or without certificate verification.
🧯 One error family SnapDogConnectionError, SnapDogAuthError, SnapDogResponseError (with SnapDogNotFoundError, SnapDogUnsupportedError, SnapDogBusyError), all derived from SnapDogError.
🧪 Strict typing mypy --strict clean, ships py.typed.
🪶 No extra dependencies aiohttp and nothing else.

Installation

pip install pysnapdog

Requires Python 3.14 or newer, the version Home Assistant 2026.10 runs on.

Quick start

import asyncio

import aiohttp

from pysnapdog import SnapDogClient


async def main() -> None:
    async with aiohttp.ClientSession() as session:
        client = SnapDogClient(session, "snapdog.local", api_key="…")

        version = await client.version()
        print(f"{version.name} {version.version}")

        for zone in await client.zones():
            print(f"{zone.index}: {zone.name} ({zone.playback}, volume {zone.volume})")


asyncio.run(main())

api_key is only needed when the server has api_keys configured. For a server behind TLS pass tls=True, and verify_tls=False for a self-signed certificate.

Control a zone

Indexes of zones and clients start at 1, as in the server's API. Zone commands return nothing, the new state arrives as an event. Calls on clients return the new value.

await client.set_zone_volume(1, 35)  # absolute, or "+5" / "-3" for relative
await client.play_playlist(1, playlist=2)
await client.set_zone_repeat(1, RepeatMode.PLAYLIST)
await client.apply_zone_eq_preset(1, "rock")
await client.set_client_zone(3, 2)  # move client 3 into zone 2
cover = await client.zone_cover(1)  # Image(content=b"...", content_type="image/jpeg")

Live updates

The server sends no initial state. SnapDogListener opens the socket first, lets you read the state over REST, and only then delivers the events that arrived meanwhile, in order. It does the same again after every reconnect.

from pysnapdog import SnapDogListener, ZoneChanged, ZoneVolumeChanged

zones: dict[int, Zone] = {}


async def load() -> None:
    zones.update({z.index: z for z in await client.zones()})


async def on_event(event) -> None:
    if isinstance(event, ZoneVolumeChanged):
        print(f"zone {event.zone}: volume {event.volume}, muted {event.muted}")


listener = SnapDogListener(client, on_event=on_event, on_connected=load)
listener.start()  # from a running event loop
...
await listener.stop()

Events carry absolute values, never a delta. A client that falls behind is closed by the server with code 1013, and the listener reconnects and calls on_connected again. A rejected API key ends the listener, because retrying cannot help: the error is in listener.stopped_reason.

Errors

from pysnapdog import (
    SnapDogAuthError,
    SnapDogConnectionError,
    SnapDogError,
    SnapDogUnsupportedError,
)

try:
    eq = await client.client_eq(3)
except SnapDogAuthError:
    ...  # the key is missing or wrong
except SnapDogUnsupportedError:
    ...  # HTTP 422: the client is not a SnapDog client, so it has no EQ
except SnapDogConnectionError:
    ...  # the server is away, or the connection broke off
except SnapDogError:
    ...  # anything else the library raises

SnapDogResponseError carries status and the server's own message in detail.

What is covered

Area Status
System: version, status, listeners, self-update ✅
Zones: transport, volume, mute, shuffle, repeat, seek, presence ✅
Zones: playlists, play track, playlist, Subsonic track, URL, cover ✅
Clients: volume, mute, latency, zone, name ✅
Equalizer for zones and clients, presets, speaker correction ✅
Media: playlists, tracks, covers ✅
KNX programming mode, product info, .knxprod download ✅
WebSocket: seven event types, reconnect, resync ✅

The server has 100 REST operations. Every one is either a library call that a test exercises, or on a short list of single-field reads that the Zone, Client, TrackMetadata and ZonePlaylist models already carry. scripts/check_coverage.py compares the library with the pinned openapi.json and asyncapi.json and fails when an operation or event is neither covered nor excluded with a reason. CI runs it, so the gap cannot grow unnoticed.

Development

The project uses uv.

git clone https://github.com/SnapDogRocks/pysnapdog.git
cd pysnapdog
uv sync

uv run ruff check .          # lint
uv run ruff format --check . # format
uv run mypy                  # types, strict
uv run pytest --cov          # tests, coverage floor 95 %
uv run python scripts/check_coverage.py   # every operation and event of the server

Commits follow Conventional Commits. A local hook checks them once you run lefthook install (see CONTRIBUTING.md).

  • SnapDog — the server this library talks to.
  • snapdog-homeassistant — the Home Assistant integration built on this library (in preparation).

License

Apache License 2.0.

Metadata

Release files for pysnapdog 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pysnapdog 0.1.0
File Size Uploaded Provenance Actions
pysnapdog-0.1.0.tar.gz 130.8 kB Without provenance Details for pysnapdog-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pysnapdog 0.1.0
File Interpreter ABI Platform Actions
pysnapdog-0.1.0-py3-none-any.whl Python 3 none any Details for pysnapdog-0.1.0-py3-none-any.whl

Total release size: 154.9 kB

Release files / pysnapdog-0.1.0.tar.gz

Download URL pysnapdog-0.1.0.tar.gz
Size 130.8 kB
Tags Source
SHA-256 checksum
How to use checksums
cbf7fb468fb76ea47b93bbd81bbafa44a0615de1f3f3209c51b660b1956639ea
BLAKE2b-256 checksum
How to use checksums
5e4f8e21098d969f1fa637f6d6adadd0e3205aa602de4d012ae7bbc9af261ef3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / pysnapdog-0.1.0-py3-none-any.whl

Download URL pysnapdog-0.1.0-py3-none-any.whl
Size 24.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7d5cc8b66c9a2ae507e0ba24256e44baf02c2a729ca20aac78f4ebb4f2c5a6fb
BLAKE2b-256 checksum
How to use checksums
6fdbf874d1242944914f75b5316fcffbd4e3606bb4550ab40a2be09ecbec7ef1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for pysnapdog 0.1.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page