Skip to main content

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.

CI PyPI Python Coverage License

What's in the box

  • Typed sync + async client — pydantic v2 models, httpx transport, full Radarr v3 + Sonarr v3 + Prowlarr v1 endpoint coverage, ships py.typed.
  • MCP server (arr-py-mcp) — 60+ tools exposing the client as LLM-callable operations, with uniform _meta.action_hints so chains self-suggest. Single-tenant via env vars or multi-tenant via a ClientProvider + OAuth 2.1 — see docs/guides/mcp-multi-tenant.md.
  • Composed workflowsconfig_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 utilitiesmake_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.
arr-py-mcp --transport http   # streamable-http (MCP 2025-11 preferred remote transport)

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.

Multi-tenant deployments

For embedding the MCP server in a larger app where each user has their own Radarr / Sonarr / Prowlarr — including users with multiple instances of the same brand — implement a ClientProvider:

from fastapi import FastAPI
from arr_py_client.mcp import (
    build_server, CallbackTokenVerifier, InMemoryCachedProvider,
)

class MyProvider(InMemoryCachedProvider):
    async def identity(self, ctx):
        return ctx.principal.id

    async def build_client(self, ctx, brand, instance_id, identity):
        # look up encrypted creds in your DB and return AsyncRadarrClient(...)
        ...

mcp = build_server(
    provider=MyProvider(),
    token_verifier=CallbackTokenVerifier(verify=app_auth.to_principal),
    auth=AuthSettings(issuer_url=..., resource_server_url=...),
)
app = FastAPI()
app.mount("/mcp", mcp.streamable_http_app())

Per-tool scope enforcement (mcp:arr:read vs mcp:arr:mutate), RFC 6750 §3.1 403 + WWW-Authenticate on scope failure, and an audit= callback come for free. Full guide: docs/guides/mcp-multi-tenant.md.

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

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


Download files

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

Source Distribution

arr_py_client-0.8.0.tar.gz (293.9 kB view details)

Uploaded Source

Built Distribution

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

arr_py_client-0.8.0-py3-none-any.whl (425.5 kB view details)

Uploaded Python 3

File details

Details for the file arr_py_client-0.8.0.tar.gz.

File metadata

  • Download URL: arr_py_client-0.8.0.tar.gz
  • Upload date:
  • Size: 293.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for arr_py_client-0.8.0.tar.gz
Algorithm Hash digest
SHA256 b6c9c8da919c94e001c3f9a1ab86992b2d2d9cb1c5b07b4d43307916995d72a9
MD5 20949cc835a0f52e70c4b6277f55e6c4
BLAKE2b-256 1774620cf65e9943e51f19c48be15e9ded2837aa6003ac1aecd13eb029e73fa2

See more details on using hashes here.

Provenance

The following attestation bundles were made for arr_py_client-0.8.0.tar.gz:

Publisher: release.yml on allada-homelab/arr-py-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file arr_py_client-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: arr_py_client-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 425.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for arr_py_client-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8dbbc109684bb93f520e33e62880166749643bd4b3bb34f63eacb1c3fa16c8fb
MD5 5f86a2d27886db1dcb4305f5076e07c7
BLAKE2b-256 c31175bf291784cea10c74f34272b6313e8b7de002e0c0f775a5b94e88195961

See more details on using hashes here.

Provenance

The following attestation bundles were made for arr_py_client-0.8.0-py3-none-any.whl:

Publisher: release.yml on allada-homelab/arr-py-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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