Skip to content

ADR-0001: Python as the Implementation Language

Status: Accepted

Date: 2025-01-15


Context

We need to build a passive music downloader that: - Monitors Spotify and YouTube Music playlists - Downloads new tracks automatically - Embeds metadata and album artwork - Runs as a background service on a homelab server

The initial prototype was started as a Bun TypeScript project, but we need to decide on the final implementation language.

Technology Landscape

For Spotify integration: - TypeScript: @spotify/web-api-ts-sdk, or direct HTTP with undici - Python: spotipy (mature, well-maintained)

For YouTube Music integration: - TypeScript: No official library; would require ytmusicapi as subprocess wrapper - Python: ytmusicapi (official unofficial library, actively maintained)

For downloading: - TypeScript: spotdl (Python), or yt-dlp as subprocess - Python: yt-dlp as native library, spotdl for matching

For metadata tagging: - TypeScript: node-id3, music-tag-native (NAPI), or FFmpeg subprocess - Python: mutagen (mature, cross-format)


Decision

We will implement this tool in Python 3.11+ using uv for package management.

Rationale

1. Library Ecosystem Alignment

The music downloading space is dominated by Python. The three critical libraries for this project—ytmusicapi, spotipy, and yt-dlp—are all written in Python and work best as native libraries:

Component TypeScript Approach Python Approach
YouTube Music Subprocess wrapper (fragile) ytmusicapi native
Spotify HTTP SDK or manual HTTP spotipy native
Download engine spotdl subprocess or yt-dlp subprocess yt-dlp as library
Metadata FFmpeg subprocess or NAPI wrapper mutagen native

Using Python means we can call these libraries directly, avoiding subprocess complexity, improving error handling, and gaining access to the full API surface.

2. spotdl as a Reference Implementation

spotdl (24k+ GitHub stars) is the de facto standard for Spotify-to-YouTube downloading. It solves the hardest problem in this space: matching Spotify tracks to YouTube videos. While we won't use spotdl directly (it brings heavy dependencies), studying its approach confirms that Python is the right ecosystem:

  • ytmusicapi for playlist access
  • yt-dlp for downloading
  • Matching logic via metadata comparison

Our architecture follows the same patterns that have proven reliable in spotdl.

3. Type Safety via ty and Pydantic

Python's type system has matured significantly. We will enforce strict type hints throughout:

  • ty for static type checking (fast, from Astral)
  • Pydantic for runtime validation of config and data models
  • All function signatures include full type annotations

ty is Astral's blazing-fast type checker written in Rust—10x to 100x faster than mypy. Combined with Pydantic for runtime validation, this provides safety comparable to TypeScript while retaining Python's ecosystem advantages.

4. uv as a Fast Package Manager

uv provides: - Sub-second package resolution - Deterministic builds - Virtual environment management - Same speed as Bun for JS/TS

Performance is not a concern—we're I/O bound on downloads anyway.

5. Simpler Deployment

  • No Node.js runtime to install
  • FFmpeg remains the only system dependency
  • Virtual environment via uv venv or uv run
  • Familiar Python deployment patterns for homelab servers

Consequences

Positive

  • Native library access: ytmusicapi, spotipy, and yt-dlp work as first-class libraries
  • Proven patterns: spotdl demonstrates this approach works at scale
  • Strong typing: ty + Pydantic provides comparable safety to TypeScript
  • Fast tooling: uv matches Bun's performance
  • Simpler deps: FFmpeg is the only system dependency

Negative

  • Runtime overhead: Python has more startup overhead than a compiled Bun binary
  • Two runtimes: If we ever add a frontend, we'd need Node.js anyway
  • GIL considerations: Threading is limited (but we use async I/O anyway)

Neutral

  • Language preference: Subjective; some prefer TypeScript's compile-time checking
  • Ecosystem maturity: Python's async ecosystem is good but not as ergonomic as Node.js

Alternatives Considered

TypeScript (Bun)

Rejected because: - ytmusicapi is Python-only; would require subprocess wrapper - spotdl matching logic is Python; would need to reimplement - Fewer options for metadata tagging (no mature mutagen equivalent)

Rust

Rejected because: - ytmusicapi and spotipy are not available as Rust libraries - Would require calling Python libraries via FFI or subprocess anyway - Steeper learning curve for the specific problem domain

Go

Rejected for the same reasons as Rust, plus: - No mature music-specific libraries exist


References