Typed sync + async Python client for Radarr v3 and Sonarr v3 APIs
Project description
arr-py-client
Typed Python client + MCP server + declarative config sync + workflow primitives for Radarr, Sonarr, and Prowlarr.
What's in the box
- Typed sync + async client — pydantic v2 models,
httpxtransport, full Radarr v3 + Sonarr v3 + Prowlarr v1 endpoint coverage, shipspy.typed. - MCP server (
arr-py-mcp) — 56 tools exposing the client as LLM-callable operations, with uniform_meta.action_hintsso chains self-suggest. - Composed workflows —
config_sync(plan/apply against a YAML desired-state),queue.janitor(...)(policy-based cleanup with named bundles),library.backfill(...)(rate-limited missing-content search with.estimate()),releases.explain(...)(grading + plain-English.advice()). - Webhook receivers — parse typed events and dispatch via WSGI or FastAPI handlers.
- Testing utilities —
make_fake_radarr()/make_fake_sonarr()fakes +@replay(...)fixture decorator for record-on-miss / replay-on-hit. - Zero-dep CLI (
arr-py) — status + basic add only; workflows live in Python and MCP on purpose. - Python 3.11–3.14.
Install
pip install arr-py-client # core SDK
pip install arr-py-client[mcp] # + MCP server
pip install arr-py-client[config] # + YAML loader for config_sync
pip install arr-py-client[webhooks] # + FastAPI receiver helper
Quickstart (SDK)
from arr_py_client import RadarrClient
with RadarrClient(base_url="http://radarr:7878", api_key="YOUR_KEY") as client:
movies = client.movies.list()
for m in movies[:5]:
print(m.id, m.title, m.year)
Or via env / .env (RADARR_BASE_URL, RADARR_API_KEY):
from arr_py_client import RadarrClient
with RadarrClient() as client:
print(len(client.movies.list()))
Async mirrors the sync API via AsyncRadarrClient / AsyncSonarrClient.
Quickstart (MCP)
pip install arr-py-client[mcp]
export RADARR_BASE_URL=http://radarr:7878 RADARR_API_KEY=...
export SONARR_BASE_URL=http://sonarr:8989 SONARR_API_KEY=...
arr-py-mcp # stdio MCP server; register with Claude, Cursor, etc.
Every list/get tool returns a projected envelope with
_meta.action_hints — the LLM client can read the suggested next tool
calls directly from the response.
Quickstart (config sync)
Put this in config.yaml (see docs/examples/config-sync/
for the full schema):
tags: [4k, anime, kids]
custom_formats:
- name: x265
specifications:
- name: x265
implementation: ReleaseTitleSpecification
required: true
fields: [{ name: value, value: "(h|x).?265" }]
quality_profiles:
- name: HD-Bluray
upgradeAllowed: true
cutoff: 7
formatItems:
- { name: x265, score: -10000 }
Apply it:
from arr_py_client import RadarrClient
from arr_py_client.config_sync import load, plan, apply
with RadarrClient() as client:
desired = load("config.yaml")
plan_ = plan(client, desired)
print(plan_.summary())
report = apply(client, plan_, dry_run=False)
Quickstart (queue janitor)
Named policy bundles for common opinions:
from arr_py_client import RadarrClient, POLICIES
with RadarrClient() as client:
report = client.queue.janitor(
policies=POLICIES.default, # or .conservative / .aggressive / .ratio_preserving
protected_trackers=("private-tracker.example",),
dry_run=False,
)
print(report.total_matches)
Quickstart (webhook receiver)
Parse-only:
from arr_py_client.webhooks import parse_event, OnGrab
event = parse_event(request.json())
if isinstance(event, OnGrab):
notify(f"Grabbed {event.movie.title if event.movie else '?'}")
With FastAPI:
from fastapi import FastAPI
from arr_py_client.webhooks import fastapi_router
app = FastAPI()
app.include_router(fastapi_router(on_event), prefix="/webhooks/arr")
Or zero-dep WSGI:
from wsgiref.simple_server import make_server
from arr_py_client.webhooks import wsgi_app
make_server("0.0.0.0", 9000, wsgi_app(on_event)).serve_forever() # noqa: S104
Comparison
| arr-py-client | pyarr | Recyclarr | |
|---|---|---|---|
| Pydantic v2 models | yes | no (dicts) | n/a |
| Async | yes | no | n/a |
| Radarr / Sonarr v3 coverage | yes | yes | partial (config only) |
| Prowlarr v1 coverage | yes | yes | yes |
| Lidarr / Readarr | planned | yes | yes |
| MCP server | yes (56 tools) | no | no |
| Declarative config sync | yes (YAML/JSON/TOML) | no | yes |
| Queue janitor / backfill / release explain | yes | no | no |
| Webhook receiver helper | yes | no | no |
Ships py.typed |
yes | no | n/a |
Documentation
- API reference: https://allada-homelab.github.io/arr-py-client/
- Architecture: docs/architecture.md
- Roadmap: docs/roadmap.md
- Contributing: CONTRIBUTING.md
Development
git clone https://github.com/allada-homelab/arr-py-client
cd arr-py-client
uv sync --all-extras --all-groups
just test
Integration tests (require Docker):
just test-int
Regenerate clients from upstream specs:
just gen-radarr <radarr-tag>
just gen-sonarr <sonarr-tag>
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
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 arr_py_client-0.6.0.tar.gz.
File metadata
- Download URL: arr_py_client-0.6.0.tar.gz
- Upload date:
- Size: 248.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8482688b58a02fd6dbfc2fe2625063d8d012a18c69c614abb8af8feffecf8d35
|
|
| MD5 |
e261b57794d6661ffa5a5a54e7c9c355
|
|
| BLAKE2b-256 |
dcaf242dc4483af546f41b1cbf711d801ab20b71134ecbc8274214975e649035
|
Provenance
The following attestation bundles were made for arr_py_client-0.6.0.tar.gz:
Publisher:
release.yml on allada-homelab/arr-py-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arr_py_client-0.6.0.tar.gz -
Subject digest:
8482688b58a02fd6dbfc2fe2625063d8d012a18c69c614abb8af8feffecf8d35 - Sigstore transparency entry: 1351490258
- Sigstore integration time:
-
Permalink:
allada-homelab/arr-py-client@ff4593571d590d0fd5d440a2046fe654f4429ef9 -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/allada-homelab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
self-hosted -
Publication workflow:
release.yml@ff4593571d590d0fd5d440a2046fe654f4429ef9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file arr_py_client-0.6.0-py3-none-any.whl.
File metadata
- Download URL: arr_py_client-0.6.0-py3-none-any.whl
- Upload date:
- Size: 374.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21f5025e91b54ca7f683047029cae252247859ccfcd190499d3d8ec4c6387f5c
|
|
| MD5 |
e6bdb6711f2a54c21ad9913b5457ece5
|
|
| BLAKE2b-256 |
e7aa74e26b3a549c1a26d667f60720c3300d77aa9669865eb71076ee032f9da2
|
Provenance
The following attestation bundles were made for arr_py_client-0.6.0-py3-none-any.whl:
Publisher:
release.yml on allada-homelab/arr-py-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
arr_py_client-0.6.0-py3-none-any.whl -
Subject digest:
21f5025e91b54ca7f683047029cae252247859ccfcd190499d3d8ec4c6387f5c - Sigstore transparency entry: 1351490580
- Sigstore integration time:
-
Permalink:
allada-homelab/arr-py-client@ff4593571d590d0fd5d440a2046fe654f4429ef9 -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/allada-homelab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
self-hosted -
Publication workflow:
release.yml@ff4593571d590d0fd5d440a2046fe654f4429ef9 -
Trigger Event:
push
-
Statement type: