Skip to main content

ampachedata

Client library for the Ampache JSON API with a SQLite write-through data layer.

Every API response is persisted to SQLite first, then read back - the database is the single source of truth, and callers never receive raw server payloads. Zero runtime dependencies, stdlib only (pure Python + sqlite3).

  • Live-proven against real Ampache 8.x, 7.x servers
  • Session persistence across process restarts, with silent re-auth + one retry on expiry
  • Two layers: AmpacheClient for network + write-through, repositories for offline reads (no network)
  • Cleartext passwords are accepted once, hashed in memory, and never stored or logged

Requirements

  • Python 3.11+
  • A SQLite database with the schema - create one with the opt-in createDatabase() helper (see Quick start; the library never creates or alters schema on its own, by design)

Installation

pip install ampachedata

Optional dev extras (pytest):

pip install ampachedata[dev]

From source:

git clone https://github.com/icefields/Ampache-Data-Library
cd Ampache-Data-Library
pip install -e .

Quick start

1. Create the database (one time). The library deliberately never creates or alters schema on its own - you own the file. The opt-in helper builds it from the schema bundled inside the package:

from ampachedata import createDatabase

createDatabase("musicdb.db")

createDatabase refuses to touch an existing file - it raises DatabaseError rather than altering your database.

2. Store credentials (one time per user/server). The cleartext password is accepted once, hashed with SHA256 in memory, and only the digest is persisted:

from ampachedata import storeCredentialsFromPassword

storeCredentialsFromPassword(
    dbPath="musicdb.db",
    username="alice",
    serverUrl="https://ampache.example.com",
    cleartextPassword="secret",
)

Or use the CLI (hidden prompt, stdin, or env var - no --password flag, so cleartext never lands in argv or shell history):

python -m ampachedata init-credentials --db-path musicdb.db
# or fully scripted:
python -m ampachedata init-credentials --db-path musicdb.db \
    --username alice --server-url https://ampache.example.com --password-stdin

If you already hold a pre-hashed Ampache API key (64 lowercase hex chars), use storeCredentialsFromKey or the --key-stdin / --key flags instead.

3. Use the client. The first authenticated call triggers the handshake from the stored credentials automatically - no explicit login step:

from ampachedata import AmpacheClient

client = AmpacheClient(dbPath="musicdb.db")

artists = client.getArtists(limit=50)
print(artists[0].name)

# Write-through: everything fetched is already in the DB
songs = client.getSongs(filter="Supernaut", exact=1)

# Build a playable URL (no network call, nothing persisted)
url = client.getStreamUrl(songs[0].id, stats=0)

# Done for this session - tears down the server session cleanly
client.goodbye()

Architecture: write-through, both directions

                ┌───────────────────────────────┐
   Ampache ────▶│  AmpacheClient (network layer)│────▶ SQLite (musicdb.db)
   JSON API     │  persist first, read back     │           │
                └───────────────────────────────┘           ▼
                ┌───────────────────────────────┐
   Your UI ◀─── │ Repositories (offline layer)  │◀─── typed entities
   (no network) │  list / search / count / page │
                └───────────────────────────────┘
  • Network layer (AmpacheClient): every library-data fetch persists the response to SQLite first, then returns typed entities read back from the database - never raw JSON. (The media-URL builders are pure string builders: no network, no DB write. ping without a stored session is an anonymous probe that persists nothing.)
  • Offline layer (repositories): query what has been cached. Zero network. Built for UI list views, search boxes, and pagination.

Consequence: offline reads only see what has been fetched at least once. Start from an empty DB and listSongs() returns nothing until you fetch. This is the intended cold-cache model, not a bug.

AmpacheClient reference

Constructed as AmpacheClient(dbPath, transport=None) - transport is an optional HTTP transport injection seam (used by the test suite; the default is a stdlib urllib transport).

All list methods auto-paginate server-side (loop until short page) - you never page by hand. Entity ids are strings throughout; returned entities are immutable frozen dataclasses with clean field names (no JSON keys, no DB-only columns).

Unfiltered list calls sync everything. getSongs() with no filter and no limit fetches the entire server library into your DB in one call (same for getArtists(), getAlbums(), …). That is the intended write-through sync behavior - but be deliberate about it.

Session & health

Method Returns Notes
ping() PingResult Health check: authenticated, api, server, version, sessionExpire. Some servers answer authenticated optimistically - don't rely on ping as a session probe.
goodbye() OperationResult Tears down the server session and deletes the persisted token. After goodbye, authenticated calls raise InvalidHandshakeError (no resurrection on this instance) - but ping() falls back to anonymous mode and returns authenticated=False. Create a new AmpacheClient to re-auth.
lastPayload (property) dict or None The raw envelope of the last call (informational: total_count, md5, …).

Sessions persist across restarts and process kills (the token lives in the DB). On expiry (error 4701 / HTTP 401/403), the client silently re-authenticates and retries once; a second failure raises InvalidHandshakeError.

Artists

Method Returns
getArtists(filter="", exact=None, add=None, update=None, include=None, albumArtist=None, offset=None, limit=None, cond=None, sort=None) list[Artist]
getArtist(filter, include=None) Artist - include="albums,songs" upserts nested rows

Albums

Method Returns
getAlbums(filter="", exact=None, offset=None, limit=None, add=None, update=None, cond=None, sort=None) list[Album]
getAlbum(filter, include=None) Album
getAlbumsFromArtist(artistId, albumArtist=None, offset=None, limit=None, cond=None, sort=None) list[Album]
getAlbumSongs(albumId, offset=None, limit=None, cond=None, sort=None) list[Song]

Songs

Method Returns
getSongs(filter="", exact=None, add=None, update=None, offset=None, limit=None, cond=None, sort=None) list[Song]
getSong(filter) Song
getArtistSongs(artistId, top50=None, offset=None, limit=None, cond=None, sort=None) list[Song]

cond and sort are string parameters, passed through to the server verbatim (cond is a ;-separated filter string per the Ampache API; anything you pass is stringified into the query - build the string yourself). Power-user parameters; everything else is the common case.

Stats (play-derived)

All take (userId=None, username=None, offset=None, limit=None).

Songs Albums
getRecentSongs() getRecentAlbums()
getFrequentSongs() getFrequentAlbums()
getForgottenSongs() getForgottenAlbums()
getRandomSongs() getRandomAlbums()
getNewestSongs() getNewestAlbums()
getHighestSongs() getHighestAlbums()

Playlists

Method Returns
getPlaylists(filter="", hideSearch=None, showDupes=None, exact=None, add=None, update=None, offset=None, limit=None, cond=None, sort=None) list[Playlist]
getPlaylist(filter) Playlist
getSongsFromPlaylist(playlistId, random=None, offset=None, limit=None) list[Song] - order matches the server's playlist positions exactly (no renumbering)

Media URLs

Method Returns Notes
getStreamUrl(songId, format=None, bitrate=None, offset=None, stats=None) str Pure URL builder: no network, no DB write. Song-only per API spec.
getDownloadUrl(songId, format=None, bitrate=None, stats=None) str Same - download flavor.

The URLs embed the live session token as a query parameter - treat them as secrets: don't log them, share them, or paste them into bug reports. The library never logs or stores built URLs. Pass stats=0 for any fetch that is not a real user play (preloading, probing, artwork) - otherwise the server records a play and pollutes play counts.

Interactions (mutate server state)

Method Returns Notes
flag(objectType, objectId, flagged) the re-fetched, refreshed entity Applies, re-fetches via the type's getter, upserts, verifies - raises CacheVerificationError on mismatch.
rate(objectType, objectId, rating) the re-fetched, refreshed entity rating validated locally as 0–5 before any network call - out of range raises plain ValueError, not an AmpacheError. Same verify discipline.

objectType accepts an ObjectType enum member or its plain string value ("song", "album", "artist", "playlist").

Offline query tier

Import Database plus the repositories and read what's cached - no network, no handshake needed:

from ampachedata import Database, SongRepository, ArtistRepository

db = Database("musicdb.db")
songs = SongRepository(db)
artists = ArtistRepository(db)

page = songs.listSongs(order="recent", limit=100, offset=0)
print(len(page.rows), "of", page.total)      # total = honest SQL COUNT

hits = songs.searchSongs("Supernaut")         # LIKE search, % and _ are
                                              # escaped - input is literal
byName = artists.searchArtists("megadeth")    # case-insensitive (ASCII)

PageResult carries rows and total; total is the SQL COUNT over the full filtered set - unlike server envelopes, it never lies, so you can compute page counts reliably.

Repository Read methods
SongRepository listSongs(order, limit, offset, artistId, albumId), searchSongs(query, limit, offset), songCount(), playlistSongs(playlistId) (position order; getPlaylistSongs() is an alias), getSong(songId), getAlbumSongs(albumId), getArtistSongs(artistId), getSongsByLastPlayed(ascending=False), getSongsByPlayCount(), getSongs()
ArtistRepository listArtists(), searchArtists(query), artistCount(), getArtist(artistId), getArtists()
AlbumRepository listAlbums(), searchAlbums(query), albumCount(), getAlbum(albumId), getAlbumsFromArtist(artistId), getAlbums()
PlaylistRepository listPlaylists(), searchPlaylists(query), playlistCount(), getPlaylist(playlistId), getPlaylists()

order for listSongs is one of "title", "artist", "album", "recent" (whitelisted - arbitrary SQL cannot be injected).

Case-insensitive search is ASCII-only (e.g. Cyrillic matches case-sensitively) - that is SQLite LIKE semantics, documented rather than worked around.

Errors

Every exception derives from AmpacheError. Catch the family, or the exact class:

AmpacheError
├── DatabaseError                # musicdb.db missing or unusable
├── CredentialValidationError    # bootstrap input failed validation
├── CacheVerificationError       # post-flag/rate read-back mismatch
└── ApiError                     # Ampache error envelope
    ├── InvalidHandshakeError     # 4701 / HTTP 401/403 - triggers auto re-auth (except after goodbye)
    ├── AccessDeniedError         # 4703
    ├── NotFoundError             # 4704
    ├── DeprecatedError           # 4706
    ├── BadRequestError           # 4710
    └── UnknownApiError           # anything else

ApiError carries .code and .message. Error messages never contain the cleartext password or the stored hash.

Best practices

  • Let the library own writes. All upserts (history, playlist positions, ratings) derive from server payloads - never write entity rows by hand.
  • Don't trust server total_count envelopes - some Ampache builds report stale/wrong counts. The client auto-paginates until a short page; for offline paging use PageResult.total instead.
  • Pass stats=0 on non-playback media fetches so plays aren't recorded.
  • Treat built stream/download URLs as secrets - they embed the live session token as a query parameter.
  • Call goodbye() when done - it's the clean logout, and it deletes the stored session token. Reuse one client per session rather than one per call.
  • Create the client fresh after goodbye() - a post-goodbye client raises InvalidHandshakeError by design (no resurrection).
  • Exact filters with special characters (?, %, _) can behave inconsistently on some servers; prefer narrow, plain-text filters.
  • Songs removed from a playlist stay in the cache - playlist join rows are upsert-only (no deletion), so offline reads keep showing them until the server's next payload omits them.
  • Not thread-safe - the client holds one SQLite connection (sqlite3's default check_same_thread behavior); using one client instance across threads raises. Create one client per thread.
  • Keep the DB per-user - history rows are keyed by user + media id.

Server compatibility

Developed and live-tested against Ampache 8.0.1 (API 8) and Ampache 7.9.2 (API 6) (json.server.php, JSON API). The method set (handshake, ping, artists/albums/songs, stats, playlists, flag, rate, stream, download, goodbye) is long-standing Ampache JSON API surface, live-proven on both generations. Python 3.11–3.14 supported.

License

GPL-3.0-only - the full license text ships in the sdist and wheel.

Links

Download files

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

Source Distribution

ampachedata-0.1.6.tar.gz (89.4 kB view details)

Uploaded Source

Built Distribution

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

ampachedata-0.1.6-py3-none-any.whl (74.3 kB view details)

Uploaded Python 3

File details

Details for the file ampachedata-0.1.6.tar.gz.

File metadata

  • Download URL: ampachedata-0.1.6.tar.gz
  • Upload date:
  • Size: 89.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for ampachedata-0.1.6.tar.gz
Algorithm Hash digest
SHA256 0f769cedd7c3c1363e8f25b9648a816752376b09738714da3ee2ac569a75a4fb
MD5 e316c9aab9512c5d356826fb81cb9215
BLAKE2b-256 2074989afc95b4482f1039b45b6b8a03cd1dc7bab4532a13cccad7392b98474d

See more details on using hashes here.

File details

Details for the file ampachedata-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: ampachedata-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 74.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.7

File hashes

Hashes for ampachedata-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 a4b816f3b3b9741b984c91abdd9b96965cefe815eab3d28d6a09f084a0eeb725
MD5 6dda015d8fca17cdc1edb15762b5d3ed
BLAKE2b-256 9c2e267aed4cdcd21fa6a28e8fb21ad6217e8537ceb8ad14108805489a7b033b

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.6 This release

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

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