Metadata-Version: 2.4
Name: deezer-python-gql
Version: 0.17.2
Summary: Async typed Python client for Deezer's Pipe GraphQL API.
Author-email: Julian Daberkow <jdaberkow@users.noreply.github.com>
License: Apache-2.0
Platform: any
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.13.5
Requires-Dist: pydantic>=2.0.0
Requires-Dist: yarl>=1.22.0
Provides-Extra: test
Requires-Dist: codespell==2.4.3; extra == "test"
Requires-Dist: mypy==2.3.1; extra == "test"
Requires-Dist: pre-commit==4.6.2; extra == "test"
Requires-Dist: pre-commit-hooks==6.0.0; extra == "test"
Requires-Dist: pytest==9.1.1; extra == "test"
Requires-Dist: pytest-asyncio==1.4.0; extra == "test"
Requires-Dist: pytest-cov==7.1.0; extra == "test"
Requires-Dist: ruff==0.16.5; extra == "test"
Provides-Extra: dev
Requires-Dist: ariadne-codegen[subscriptions]; extra == "dev"
Dynamic: license-file

# deezer-python-gql

Async typed Python client for Deezer's Pipe GraphQL API.

Built with [ariadne-codegen](https://github.com/mirumee/ariadne-codegen) — all
client methods and response models are generated from the GraphQL schema and
`.graphql` query files.

## Installation

```bash
uv add deezer-python-gql
```

## Quick Start

```python
import asyncio

from deezer_python_gql import DeezerGQLClient


async def main():
    client = DeezerGQLClient(arl="YOUR_ARL_COOKIE")

    # Current user
    me = await client.get_me()
    print(me)

    # Track with media URLs, lyrics, and contributors
    track = await client.get_track(track_id="3135556")
    print(track.title, track.duration)

    # Album with paginated track list
    album = await client.get_album(album_id="302127")
    print(album.display_title, album.tracks_count)

    # Artist with top tracks and discography
    artist = await client.get_artist(artist_id="27")
    print(artist.name, artist.fans_count)

    # Playlist with tracks
    playlist = await client.get_playlist(playlist_id="53362031")
    print(playlist.title, playlist.estimated_tracks_count)

    # Unified search across all entity types
    results = await client.search(query="Daft Punk")
    print(len(results.tracks.edges), "tracks found")


asyncio.run(main())
```

## Available Queries

### Content Retrieval

| Method                                        | Description                                                     |
| --------------------------------------------- | --------------------------------------------------------------- |
| `get_me()`                                    | Current authenticated user                                      |
| `get_track(track_id)`                         | Full track details — ISRC, media tokens, lyrics, contributors   |
| `get_album(album_id)`                         | Album with cover, label, paginated tracks, fallback             |
| `get_artist(artist_id)`                       | Artist with bio, top tracks, albums (ordered by release date)   |
| `get_playlist(playlist_id)`                   | Playlist with owner, picture, paginated tracks                  |
| `get_livestream(livestream_id)`               | Livestream (radio station) with streaming URLs and codec info   |
| `get_podcast(podcast_id)`                     | Podcast with paginated episodes and rights info                 |
| `get_podcast_episode(podcast_episode_id)`     | Single episode with media URL, codec, and parent podcast ref    |
| `get_audiobook(audiobook_id)`                 | Audiobook with paginated chapters, contributors, and fallback   |
| `get_audiobook_chapter(audiobook_chapter_id)` | Chapter with media token, estimated sizes, and streaming rights |

### Search & Discovery

| Method                                           | Description                                                                     |
| ------------------------------------------------ | ------------------------------------------------------------------------------- |
| `search(query, ...)`                             | Unified search across tracks, albums, artists, playlists, podcasts, livestreams |
| `search_flows(query)`                            | Discover all available Deezer flows via search                                  |
| `get_similar_tracks(track_id, nb)`               | Recommended tracks based on a given track                                       |
| `get_artist_mix(artist_ids, limit)`              | Track mix blended from given artists                                            |
| `get_track_mix(track_ids, limit)`                | Track mix blended around given tracks                                           |
| `get_flow()`                                     | User's default Flow with tracks                                                 |
| `get_flow_batch()`                               | 4 batches of Flow tracks in one request (via GraphQL aliases)                   |
| `get_flow_configs(moods_first, genres_first)`    | Mood & genre flow config lists for discovery                                    |
| `get_flow_config_tracks(flow_config_id)`         | Tracks for a specific mood/genre flow config                                    |
| `get_made_for_me(first)`                         | "Made For You" SmartTracklist & Flow items                                      |
| `get_smart_tracklist(smart_tracklist_id, first)` | Smart tracklist with paginated tracks                                           |
| `get_charts(country_code, ...)`                  | Country charts — tracks, albums, artists, playlists                             |
| `get_recommendations(playlists_first, ...)`      | Personalized recommendations across categories                                  |
| `get_recently_played(first)`                     | Recently played mixed content (albums, playlists, artists...)                   |
| `get_user_charts()`                              | Personal top tracks, artists, and albums                                        |

### Library & Favorites

| Method                                        | Description                                                 |
| --------------------------------------------- | ----------------------------------------------------------- |
| `get_favorite_artists(first, after)`          | Paginated favorite artists                                  |
| `get_favorite_albums(first, after)`           | Paginated favorite albums                                   |
| `get_favorite_tracks(first, after)`           | Paginated favorite tracks                                   |
| `get_favorite_playlists(first, after)`        | Paginated favorite playlists                                |
| `get_favorite_podcasts(first, after)`         | Paginated favorite podcasts                                 |
| `get_favorite_audiobooks()`                   | Favorite audiobook IDs with dates (via deprecated endpoint) |
| `get_podcast_episode_bookmarks(first, after)` | Bookmarked podcast episodes with playback position          |
| `get_user_playlists(first, after)`            | User's own playlists (not just favorites)                   |
| `get_personal_tracks(first, after)`           | User's uploaded/personal tracks (restricted access)         |

### Music Together (Collaborative Playlists)

| Method                                     | Description                                               |
| ------------------------------------------ | --------------------------------------------------------- |
| `get_music_together_groups(first)`         | User's Music Together groups                              |
| `get_music_together_group(group_id, mood)` | Single group with members, suggested & curated tracklists |
| `get_music_together_affinity(group_id)`    | Group member affinity scores and discovery content        |

## Available Mutations

### Favorites Management

| Method                                         | Description                                                 |
| ---------------------------------------------- | ----------------------------------------------------------- |
| `add_artist_to_favorite(artist_id)`            | Add artist to favorites                                     |
| `remove_artist_from_favorite(artist_id)`       | Remove artist from favorites                                |
| `add_album_to_favorite(album_id)`              | Add album to favorites                                      |
| `remove_album_from_favorite(album_id)`         | Remove album from favorites                                 |
| `add_track_to_favorite(track_id)`              | Add track to favorites                                      |
| `remove_track_from_favorite(track_id)`         | Remove track from favorites                                 |
| `add_playlist_to_favorite(playlist_id)`        | Add playlist to favorites                                   |
| `remove_playlist_from_favorite(playlist_id)`   | Remove playlist from favorites                              |
| `add_podcast_to_favorite(podcast_id)`          | Add podcast to favorites                                    |
| `remove_podcast_from_favorite(podcast_id)`     | Remove podcast from favorites                               |
| `add_audiobook_to_favorite(audiobook_id)`      | Add audiobook to favorites (deprecated but functional)      |
| `remove_audiobook_from_favorite(audiobook_id)` | Remove audiobook from favorites (deprecated but functional) |

### Playlist Management

| Method                                           | Description                                       |
| ------------------------------------------------ | ------------------------------------------------- |
| `create_playlist(title, ...)`                    | Create a new playlist                             |
| `update_playlist(playlist_id, ...)`              | Update playlist title, description, or visibility |
| `delete_playlist(playlist_id)`                   | Delete a playlist                                 |
| `add_tracks_to_playlist(playlist_id, track_ids)` | Add tracks to a playlist                          |
| `remove_tracks_from_playlist(playlist_id, ...)`  | Remove tracks from a playlist                     |

### Podcast Episode Management

| Method                                           | Description                                       |
| ------------------------------------------------ | ------------------------------------------------- |
| `bookmark_podcast_episode(episode_id, offset)`   | Bookmark episode with playback position (seconds) |
| `unbookmark_podcast_episode(episode_id)`         | Remove bookmark from episode                      |
| `mark_as_played_podcast_episode(episode_id)`     | Mark episode as played                            |
| `mark_as_not_played_podcast_episode(episode_id)` | Mark episode as not played                        |

### Music Together

| Method                                            | Description                                 |
| ------------------------------------------------- | ------------------------------------------- |
| `music_together_create_group(name, ...)`          | Create a new Music Together group           |
| `music_together_join_group(group_id)`             | Join an existing group                      |
| `music_together_leave_group(group_id)`            | Leave a group                               |
| `music_together_refresh_suggested_tracklist(...)` | Refresh the suggested tracklist for a group |
| `music_together_update_group_settings(...)`       | Update group settings (name, family mode)   |
| `music_together_generate_group_name()`            | Generate a random group name                |

### Utilities

| Method                           | Description                                                      |
| -------------------------------- | ---------------------------------------------------------------- |
| `check_audiobook_ids(album_ids)` | Batch-check which album IDs are audiobooks (aliased queries, chunked at 50 IDs) |

All methods return fully-typed Pydantic models generated from the GraphQL schema.

## Development

Requires **Python 3.12+** and [uv](https://docs.astral.sh/uv/).

```bash
# Install all dependencies (including codegen tooling)
make setup

# Re-generate the typed client from schema + queries
make generate

# Run linters and type checks
make lint

# Run tests
make test
```

### Adding a new query

1. Create a `.graphql` file in `queries/`.
2. Run `make generate` to produce the typed client method and response models.
3. Add tests in `tests/`.

## Exploring the API

To run ad-hoc GraphQL queries against the live Pipe API during development:

1. Create a `.env` file (already gitignored) with your ARL cookie:

   ```bash
   echo 'DEEZER_ARL=your_arl_cookie_value' > .env
   ```

2. Run queries:

   ```bash
   # Run a .graphql file
   uv run python scripts/explore.py queries/get_me.graphql

   # Run an inline query
   uv run python scripts/explore.py -q '{ me { id } }'

   # With variables
   uv run python scripts/explore.py -q 'query($id: String!) { track(trackId: $id) { title } }' \
       -v '{"id": "3135556"}'

   # Via make
   make explore Q=queries/get_me.graphql
   ```

The script handles JWT auth automatically — no manual token management needed.

## Authentication

The Pipe API uses short-lived JWTs obtained from an ARL cookie. The base client
handles token acquisition and refresh automatically — you only need to supply a
valid ARL value.

An external `aiohttp.ClientSession` may be shared by clients for different Deezer
accounts. Each client keeps Deezer's response cookies locally and masks cookies from
the shared session jar, so authentication state cannot cross between those accounts.

If the ARL is rejected (bad or expired), the client raises
`GraphQLClientAuthError` — catch it to prompt for a new ARL instead of retrying.
Transient server errors raise `GraphQLClientHttpError` instead.

## License

Apache-2.0
