Skip to main content

Async Python SDK for the Rocksky XRPC API

Project description

rocksky — Python SDK

Async Python SDK for the Rocksky XRPC API.

  • Async-first (asyncio, built on httpx.AsyncClient)
  • Typed — Pydantic v2 models for every common entity, snake_case API
  • Pythonic — resource-style namespaces (client.actor, client.scrobble, …)
  • Escape hatchclient.call(method) for any XRPC method not yet wrapped
  • Works on Python 3.10+

Install

This package is uv-native. From a project of your own:

uv add rocksky

Or with pip:

pip install rocksky

To work on the SDK itself:

git clone https://github.com/tsirysndr/rocksky
cd rocksky/sdk/python
uv sync           # creates .venv, installs runtime + dev deps
uv run pytest     # run the test suite

Quickstart

import asyncio
from rocksky import Client

async def main() -> None:
    async with Client() as rocksky:
        me = await rocksky.actor.get_profile("tsiry-sandratraina.com")
        print(me.display_name, "—", me.did)

        recent = await rocksky.scrobble.list(did=me.did, limit=10)
        for s in recent:
            print(f"  {s.artist}{s.title}")

asyncio.run(main())

Authenticating

The Rocksky API uses a JWT bearer token. Pass it to the client:

async with Client(token="eyJhbGciOi…") as rocksky:
    await rocksky.scrobble.create(
        title="Hounds of Love",
        artist="Kate Bush",
        album="Hounds of Love",
        duration=298000,
    )

You can also flip tokens mid-session:

rocksky.set_token(new_jwt)

Self-hosting / custom base URL

Default base URL is https://api.rocksky.app. Override for self-hosted instances:

Client(base_url="http://localhost:8000", token=...)

Fluent builder

If you prefer chainable configuration over a wide keyword-arg constructor — or you want to add retries, logging hooks, or custom headers — use ClientBuilder:

from rocksky import ClientBuilder

rocksky = (
    ClientBuilder()
    .base_url("https://api.rocksky.app")
    .token(os.environ["ROCKSKY_TOKEN"])
    .timeout(10.0)
    .user_agent("my-app/1.0")
    .header("x-request-id", "trace-abc")
    .retries(3, backoff=0.5)              # retry transport errors + 5xx
    .on_request(lambda r: log.debug("→ %s %s", r.method, r.url))
    .on_response(lambda r: log.debug("← %s", r.status_code))
    .build()
)

async with rocksky:
    profile = await rocksky.actor.get_profile("tsiry-sandratraina.com")

A few notes:

  • Every setter returns self, so chain freely.
  • Hooks may be sync or async — the SDK awaits them when needed. They fire on every attempt (useful for tracing retries).
  • retries(n) retries on TransportError and any 5xx response with exponential backoff (backoff * 2**attempt). 4xx responses are surfaced immediately.
  • Client.builder() is a shortcut if you only imported Client.
  • All builder options are also available as keyword args to Client(...) — the builder is sugar, not the only path.

Try it in IPython

The SDK is async-only, so the regular Python REPL needs asyncio.run(...) for every call. IPython's autoawait is much friendlier — await works at the prompt:

uv run --with ipython ipython

Then:

In [1]: %autoawait
Out[1]: {'autoawait': True, 'autoawait_loop': 'asyncio'}

In [2]: from rocksky import Client

In [3]: rocksky = Client()       # base_url defaults to https://api.rocksky.app

In [4]: me = await rocksky.actor.get_profile("tsiry-sandratraina.com")

In [5]: me.display_name
Out[5]: 'Tsiry Sandratraina'

In [6]: recent = await rocksky.scrobble.list(did=me.did, limit=5)

In [7]: [(s.artist, s.title) for s in recent]
Out[7]: [('Kate Bush', 'Hounds of Love'), ]

In [8]: await rocksky.aclose()    # tidy up when done

Jupyter notebooks behave the same — await works at the top level of a cell out of the box. For other shells (ptpython, plain python -m asyncio), see your REPL's autoawait support.

Resources

The client groups endpoints by namespace. Selected highlights:

Namespace Methods
actor get_profile, get_albums, get_artists, get_songs, get_scrobbles, get_loved_songs, get_playlists, get_neighbours, get_compatibility
album get, list, get_tracks
artist get, list, get_albums, get_tracks, get_listeners, get_recent_listeners
song get, list, match, get_recent_listeners, create
scrobble get, list, create
charts top_tracks, top_artists, scrobbles_chart
feed get, search, stories, recommendations, artist_recommendations, album_recommendations, get_generator, list_generators
graph follow, unfollow, get_followers, get_follows, get_known_followers
shout create, reply, remove, report, for_profile, for_album, for_artist, for_track, replies
like like_song, dislike_song, like_shout, dislike_shout
playlist get, list, create, remove, start, insert_files, insert_directory
player currently_playing, queue, play, pause, next, previous, seek, play_file, play_directory, add_items_to_queue, add_directory_to_queue
spotify currently_playing, play, pause, next, previous, seek
apikey list, create, update, remove
stats get, wrapped
mirror list_sources, put_source
dropbox / googledrive list_files, get_file, download_file, …

For any endpoint that isn't wrapped (or hasn't been added yet), use the generic escape hatch:

raw = await rocksky.call(
    "app.rocksky.feed.describeFeedGenerator", verb="GET"
)

Errors

All errors derive from RockskyError:

from rocksky import (
    APIError,
    AuthenticationError,    # 401
    PermissionError,        # 403
    NotFoundError,          # 404
    RateLimitError,         # 429
    ServerError,            # 5xx
    TransportError,         # network / timeout
)

try:
    await rocksky.song.get(uri="at://does-not-exist")
except NotFoundError as e:
    print(e.status_code, e.error, e.message)

APIError exposes status_code, method, error, message, and body.

Testing your code against the SDK

Inject your own httpx.AsyncClient so you can mount a mock transport:

import httpx
from rocksky import Client

transport = httpx.MockTransport(lambda req: httpx.Response(200, json={"hits": []}))
external = httpx.AsyncClient(transport=transport)

async with Client(http_client=external) as rocksky:
    await rocksky.feed.search("kate bush")

await external.aclose()

The SDK's own tests use respx — see the tests/ directory for patterns.

Examples

Runnable example scripts live in examples/:

  • examples/quickstart.py — fetch a profile and recent scrobbles
  • examples/scrobble.py — submit a scrobble (requires ROCKSKY_TOKEN)
  • examples/wrapped.py — print someone's year-in-review summary
  • examples/search.py — search and pretty-print hits
  • examples/follow_feed.py — page through the follow-graph feed
  • examples/with_builder.py — fluent builder with retries + request/response hooks

Run them with:

uv run python examples/quickstart.py tsiry-sandratraina.com

License

MIT © Tsiry Sandratraina.

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

rocksky-0.1.0.tar.gz (66.6 kB view details)

Uploaded Source

Built Distribution

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

rocksky-0.1.0-py3-none-any.whl (30.1 kB view details)

Uploaded Python 3

File details

Details for the file rocksky-0.1.0.tar.gz.

File metadata

  • Download URL: rocksky-0.1.0.tar.gz
  • Upload date:
  • Size: 66.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for rocksky-0.1.0.tar.gz
Algorithm Hash digest
SHA256 26449fcff5d08145a407b3f8481eb5f7f382efbd1a258d58afd550573d3d9c1f
MD5 68f4f5df1628a2d3b7300ab62b14d3b7
BLAKE2b-256 85e9dac987ef99b7efb0fb85fd627e3679c78554f85dbe10133591bf720f7e16

See more details on using hashes here.

File details

Details for the file rocksky-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: rocksky-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for rocksky-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f6650346a4e5620d223e6f2ea1f4869769e25ac2eb55bd59eae0c4f6d937e0ee
MD5 5db47edba5febcdf165bb44e4a045e2b
BLAKE2b-256 39642f410d0e81756cac793d5c48100ae9242ad4fcfefa7cf857f82f84c56f1b

See more details on using hashes here.

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