Configuration reference#

Configuration and the OAuth client registry.

All client construction is explicit: a Config carries credentials location, output policy, quality, network policy, and the OAuth client (Tidal’s API-key/device pair) to authenticate as. There is no global state anywhere in the package.

Config is a pydantic model: every field is validated at construction (a bad value fails fast with ConfigError, not at first request), the camelCase aliases used by Config.as_dict() and Django’s JSONField are handled declaratively, and instances are immutable — Config.with_updates() returns a fresh, re-validated copy.

The client registry mirrors what the proven reference clients ship: each entry is a public device-OAuth client_id/client_secret pair with the quality tiers it is known to unlock. A Bearer token is only valid for the client that issued it, so the active client is part of the saved session (see tidalpy.session).

class tidalpy.config.Config(*, tokenPath=None, clientId=None, output=None, trackFormat='{number} {artist} - {title}{explicit}', albumFormat='{artist} - {album} {year}', playlistFormat='Playlist - {title}', includeEp=True, quality=Quality.LOSSLESS, apiBase=None, legacyApiBase=None, openapiBase=None, authBase=None, proxies=None, verifyTls=None, timeoutConnect=10.0, timeoutRead=60.0, maxRetries=3, retryBackoff=1.0, rateLimitMaxWait=90.0, rateLimitMaxPause=21600, assetNotReadyMaxWaits=6, userAgent=None, downloadWorkers=8, partSize=1048576, pacingBps=None, validateMediaUrls=True, writeTags=True, writeLyrics=False, lyricFile=False, downloadCover=True, saveAsFlac=True, verifyMedia=True, saveReceipts=False, skipBroken=True)[source]#

Bases: BaseModel

Validated, immutable configuration for a tidalpy.client.TidalClient.

Both snake_case field names and the camelCase aliases used by as_dict() are accepted by the constructor and from_dict(). Invalid values raise ConfigError immediately.

Parameters:
classmethod from_dict(data)[source]#

Build a Config from a plain dict (JSON-safe).

Accepts both snake_case and the camelCase keys used by as_dict() and Django’s JSONField settings. Unknown keys raise ConfigError (a typo should fail fast, not silently pick a default). Explicit null values are treated as “unspecified” and keep the model default (Django form/JSON round-trips serialize unset values as None; the reference client applies the same rule — a naive None would clobber a working default).

Parameters:

data (dict[str, object])

Return type:

Config

__init__(**data)[source]#

Construction is validated; invalid values raise ConfigError (not a raw ValidationError).

Parameters:

data (object)

Return type:

None

as_dict()[source]#

JSON-serialisable snapshot (for logs, task rows, admin UI).

Return type:

dict[str, object]

proxies_dict()[source]#
Return type:

dict[str, str] | None

user_agent_for(client)[source]#
Parameters:

client (OAuthClient)

Return type:

str

with_updates(**kwargs)[source]#

A copy of this config with the given fields replaced.

Runs the same validation as the constructor; an invalid value raises ConfigError and leaves this object untouched.

Parameters:

kwargs (object)

Return type:

Config

album_format: str#
api_base: str | None#
asset_not_ready_max_waits: int#
auth_base: str | None#
client_id: str | None#
download_cover: bool#
download_workers: int#
include_ep: bool#
legacy_api_base: str | None#
lyric_file: bool#
max_retries: int#
model_config: ClassVar[ConfigDict] = {'alias_generator': <function to_camel>, 'extra': 'forbid', 'frozen': True, 'populate_by_name': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

property oauth_client: OAuthClient#
openapi_base: str | None#
output: Path | None#
property output_dir: Path#
pacing_bps: float | None#
part_size: int#
playlist_format: str#
proxies: str | dict[str, str] | None#
quality: Quality#
rate_limit_max_pause: float#
rate_limit_max_wait: float#
retry_backoff: float#
save_as_flac: bool#
save_receipts: bool#
skip_broken: bool#
timeout_connect: float#
timeout_read: float#
property token_file: Path#
token_path: Path | None#
track_format: str#
user_agent: str | None#
validate_media_urls: bool#
verify_media: bool#
verify_tls: bool | str | None#
write_lyrics: bool#
write_tags: bool#
class tidalpy.config.OAuthClient(client_id, client_secret, platform, qualities=(), user_agent='TIDAL/3.34.0 (Android TV; Linux; U)', note='')[source]#

Bases: object

A public device-OAuth client (Tidal “API key”).

Parameters:
__init__(client_id, client_secret, platform, qualities=(), user_agent='TIDAL/3.34.0 (Android TV; Linux; U)', note='')#
Parameters:
Return type:

None

supports(quality)[source]#
Parameters:

quality (Quality)

Return type:

bool

client_id: str#
client_secret: str#
note: str = ''#
platform: str#
qualities: tuple[str, ...] = ()#
user_agent: str = 'TIDAL/3.34.0 (Android TV; Linux; U)'#
tidalpy.config.clients_for(quality=None)[source]#

Registered clients that can serve quality, best first.

The registry documents per-client quality support, but nothing used it: a host that needs “a client that can actually deliver this quality” (a login that should target the right client from the start) had to walk list_clients() and OAuthClient.supports() itself. quality takes the same labels as the rest of the library (aliases included, e.g. "24/192"); None/unknown resolve to the default profile.

The default client comes first when it claims the quality (its login is usually the one a host already has), then the other claiming clients in registry order (the stable id order). Raises ConfigError when no registered client claims the quality — a registry gap, not a host error.

Parameters:

quality (str | Quality | None)

Return type:

list[OAuthClient]

tidalpy.config.config_from_env(env=None)[source]#

Build a Config with TIDALPY_-prefixed environment overrides.

Every configurable field has a TIDALPY_ variable (the full table is _ENV_OVERRIDES): TIDALPY_TOKEN_PATH, TIDALPY_OUTPUT, TIDALPY_QUALITY, TIDALPY_PROXY, TIDALPY_WRITE_TAGS, TIDALPY_WRITE_LYRICS, TIDALPY_INCLUDE_EP, TIDALPY_DOWNLOAD_WORKERS, TIDALPY_PART_SIZE, TIDALPY_MAX_RETRIES, TIDALPY_TIMEOUT_CONNECT, TIDALPY_TIMEOUT_READ, and so on. Handy for the Django side to inject per-deployment settings without code changes.

Values are parsed with the same strictness as constructor arguments: an unparseable value raises ConfigError (TIDALPY_WRITE_TAGS=ture is a deploy bug, not a silent False). Unset or empty variables keep the default.

env defaults to os.environ; tests pass a plain dict.

Parameters:

env (dict[str, str] | None)

Return type:

Config

tidalpy.config.get_client(client_id=None)[source]#

Look up a registered client by id (default client when None).

Parameters:

client_id (str | None)

Return type:

OAuthClient

tidalpy.config.list_clients()[source]#
Return type:

list[OAuthClient]