Skip to content

About

Python bindings to use MusicBrainz Web Service (json/async/modern approach)

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Latest commit

 

History

54 Commits

Folders and files

Repository files navigation

MusicBrainzPy

Modern Python bindings for the MusicBrainz JSON API.

Thin wrapper around the MusicBrainz Web Service v2 — handles rate limiting, authentication, and (de)serialization via Pydantic models. All responses use the JSON API (Accept: application/json).

Why musicbrainzpy over musicbrainzngs?

musicbrainzngs musicbrainzpy
API format XML (parsed to dicts) JSON (native)
Return types Plain dicts with -list suffixes Pydantic models with IDE autocompletion
Architecture Global module state Instance-based clients
Async No Yes (+ sync client)
Auth Digest only Digest + OAuth2 (PKCE, refresh, revoke)
Error handling Generic exceptions Typed exceptions + automatic retry with backoff
Maintenance Inactive since 2023 Active
Entity coverage Missing newer entities/fields All 13 entity types, forward-compatible with new API fields
Python 2.7+ 3.10+
HTTP urllib httpx (connection pooling, HTTP/2, timeouts)
Cover Art Archive Raw bytes only Typed client with image listings and metadata

Key improvements:

  • One method instead of dozens — lookup_typed("artist", mbid) replaces get_artist_by_id, get_release_by_id, get_recording_by_id, etc.
  • Lucene queries directly — no leaky abstraction that builds queries from kwargs
  • Multiple clients — different configs, auth, rate limits running concurrently without conflicts
  • Resilient — automatic retry on transient failures (connection errors, 429/503), respects Retry-After
  • Zero-config — set MUSICBRAINZPY_* env vars once, construct clients with no arguments

Coming from musicbrainzngs? See the migration guide.

Requirements

Installation

As a dependency in another project:

uv add "musicbrainzpy @ git+https://github.com/zas/musicbrainzpy.git"

From source:

git clone https://github.com/zas/musicbrainzpy.git
cd musicbrainzpy
uv sync

Usage

Async client

import asyncio
from musicbrainzpy import MusicBrainzClient

async def main():
    async with MusicBrainzClient("myapp", "1.0", "me@example.com") as client:
        # Search for an artist
        result = await client.search_typed("artist", "Metallica")
        artist = result.items[0]
        print(f"{artist.name} ({artist.country})")

        # Look up by MBID with includes
        artist = await client.lookup_typed("artist", artist.id, includes=["tags", "genres"])

        # Browse releases by artist
        releases = await client.browse_typed(
            "release", linked_type="artist", linked_id=artist.id, limit=10
        )
        for r in releases.items:
            print(f"  {r.title}")

asyncio.run(main())

Sync client

from musicbrainzpy import SyncMusicBrainzClient

with SyncMusicBrainzClient("myapp", "1.0", "me@example.com") as client:
    result = client.search_typed("artist", "Metallica")
    print(result.items[0].name)

Raw dict responses

# All typed methods have raw equivalents returning plain dicts:
data = await client.lookup("artist", mbid)
data = await client.search("artist", "Metallica")
data = await client.browse("release", linked_type="artist", linked_id=mbid)

Non-MBID lookups

recordings = await client.lookup_by_isrc("USEE10100063")
works = await client.lookup_by_iswc("T-070.116.274-5")
releases = await client.lookup_by_discid(discid, toc="1+12+267257+150")
data = await client.lookup_by_url("https://www.metallica.com/")

# Resolve which entity a URL belongs to (e.g. find an artist from their
# Spotify/Discogs URL) by requesting relationship includes:
data = await client.lookup_by_url(
    "https://open.spotify.com/artist/5YEPudiLsVYgkZmABzsttS",
    includes=["artist-rels"],
)
artist = data["relations"][0]["artist"]  # {"id": ..., "name": ...}

Submissions (require authentication)

# Option 1: Digest auth
client = MusicBrainzClient("myapp", "1.0", "me@example.com",
                           username="user", password="pass")

# Option 2: OAuth2 (recommended)
from musicbrainzpy import OAuthHandler
oauth = OAuthHandler("client-id", "client-secret", "http://localhost:8080/callback")
await oauth.exchange_code("authorization-code")
client = MusicBrainzClient("myapp", "1.0", "me@example.com", oauth=oauth)

# Then submit
await client.submit_tags({"artist": {mbid: ["rock", "metal"]}})
await client.submit_ratings({"artist": {mbid: 80}})
await client.submit_barcodes({release_mbid: "4050538793819"})
await client.submit_isrcs({recording_mbid: ["USEE10100063"]})

# Collection management
await client.collection_add(collection_mbid, "releases", [mbid])
await client.collection_remove(collection_mbid, "releases", [mbid])

Cover Art Archive

from musicbrainzpy import SyncCoverArtClient

with SyncCoverArtClient("myapp", "1.0", "me@example.com") as caa:
    # List images for a release
    image_list = caa.get_image_list(release_mbid)
    for img in image_list.images:
        print(f"  {img.id}: {img.types} ({img.front=})")

    # Download front cover (full-size or thumbnail)
    data = caa.get_front(release_mbid)
    data = caa.get_front(release_mbid, size=500)  # 250, 500, or 1200

    # Get image metadata without downloading
    info = caa.image_info(release_mbid, "front")
    print(f"  {info['content_type']}, {info['content_length']} bytes")

Async version: CoverArtClient with the same methods (all await-able).

Annotation helpers

from musicbrainzpy import annotation_to_text, annotation_to_markdown

# Convert MusicBrainz wiki markup to plain text or Markdown
plain = annotation_to_text(artist.annotation)
md = annotation_to_markdown(artist.annotation)

Environment variables

Client defaults can be set via environment variables (explicit constructor args always win):

Variable Overrides
MUSICBRAINZPY_APP app_name
MUSICBRAINZPY_VERSION app_version
MUSICBRAINZPY_CONTACT app_contact
MUSICBRAINZPY_BASE_URL base_url
MUSICBRAINZPY_USERNAME username
MUSICBRAINZPY_PASSWORD password
MUSICBRAINZPY_DEBUG Enable debug logging (set to any value)
export MUSICBRAINZPY_APP=myapp
export MUSICBRAINZPY_VERSION=1.0
export MUSICBRAINZPY_CONTACT=me@example.com
# No args needed when env vars are set
client = SyncMusicBrainzClient()

See docs/oauth2.md for the full OAuth2 guide with PKCE, token refresh, and examples.

More examples in the examples/ directory:

uv run python examples/search_artists.py
uv run python examples/cover_art.py

Development

uv sync
uv run pytest tests/ -v
uv run ruff check .
uv run ruff format .
uv run ty check

License

GPL-3.0-or-later

About

Python bindings to use MusicBrainz Web Service (json/async/modern approach)

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages