|
| 1 | +# Changelog |
| 2 | + |
| 3 | +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 4 | + |
| 5 | +## [1.3.0] - 2026-05-05 |
| 6 | + |
| 7 | +Coordinated v1.3.0 stable release across the audd-sdks family. No breaking |
| 8 | +changes; the version bump signals API stability across all nine SDKs. |
| 9 | + |
| 10 | +The full v0.3.0 polish — env-var auto-pickup, streaming/preview helpers |
| 11 | +with metadata fallback, `onEvent` inspection hook, thread-safe token |
| 12 | +rotation, plus per-language work (JPMS module-info, Kotlin `Flow<LongpollEvent>`, |
| 13 | +Swift `Sendable` + DocC, .NET AOT/source-gen + `IServiceCollection`, Rust |
| 14 | +TLS feature flags + `Serialize`, PHP PSR-3 logger, Python `__repr__` + |
| 15 | +`pretty_print()`, Go `slog` example) is now the v1.3.0 baseline. |
| 16 | + |
| 17 | +## [0.3.0] - 2026-05-05 |
| 18 | + |
| 19 | +### Added |
| 20 | + |
| 21 | +- **`RecognitionResult.__repr__` and `EnterpriseMatch.__repr__`** — useful one-line representations for logs / REPL: `<RecognitionResult artist='X' title='Y' timecode='00:56' song_link='https://lis.tn/abc'>`. Empty fields are omitted; `timecode` is always present (it's required server-side). |
| 22 | +- **`RecognitionResult.pretty_print()` / `EnterpriseMatch.pretty_print()`** — debug helper that writes the full model state (typed fields plus any forward-compat extras) as indented JSON to stdout (or a caller-supplied stream). Useful when you want to see exactly what came back over the wire. |
| 23 | + |
| 24 | +### Documentation |
| 25 | + |
| 26 | +- README opens with an explicit "thread-safe / safe for concurrent use" statement. |
| 27 | + |
| 28 | +## [0.2.1] - 2026-05-05 |
| 29 | + |
| 30 | +### Improved |
| 31 | + |
| 32 | +- **`streaming_url(provider)` now falls back to the metadata block when `song_link` is non-lis.tn.** Previously returned `None` for YouTube `song_link` values; now picks the direct URL from `apple_music.url` / `spotify.external_urls.spotify` / `deezer.link` / `napster.href` when the corresponding metadata was requested via `return=`. Direct URLs are also now *preferred* over the lis.tn redirect when both paths resolve (no redirect = faster client-side load). YouTube as a provider has no metadata-block fallback (only the lis.tn redirect path). |
| 33 | +- `streaming_urls()` correspondingly returns more entries — every provider with either a direct URL or a lis.tn redirect available. |
| 34 | + |
| 35 | +## [0.2.0] - 2026-05-05 |
| 36 | + |
| 37 | +DX polish — env-var auto-pickup, streaming/preview helpers, on_event inspection hook, thread-safe token rotation. |
| 38 | + |
| 39 | +### Added |
| 40 | + |
| 41 | +- **`AUDD_API_TOKEN` env-var fallback** (§7.11): `Audd()` / `AsyncAudd()` constructed without an explicit `api_token` now reads it from `os.environ.get("AUDD_API_TOKEN")`. Raises `ValueError` with a dashboard-link hint if both are missing. |
| 42 | +- **`RecognitionResult.streaming_url(provider)`** (§4.3): returns the `lis.tn` redirect URL for `spotify` / `apple_music` / `deezer` / `napster` / `youtube` (e.g. `https://lis.tn/abc?spotify` 302-redirects to the song's Spotify page). Returns `None` for non-lis.tn `song_link` values (e.g., YouTube ones). |
| 43 | +- **`RecognitionResult.streaming_urls()`** (§4.3): convenience dict of all five providers' redirect URLs. |
| 44 | +- **`RecognitionResult.preview_url()`** (§4.3): first available 30-second preview URL from `apple_music.previews[0].url` → `spotify.preview_url` → `deezer.preview`. Documented caveat about provider TOS. |
| 45 | +- Same `streaming_url` / `streaming_urls` / `thumbnail_url` helpers on `EnterpriseMatch`. |
| 46 | +- **`set_api_token(new_token)`** on `Audd` / `AsyncAudd` (§7.10): thread-safe token rotation via a `threading.Lock`. In-flight requests continue with the old token; subsequent requests use the new one. Validates non-empty. |
| 47 | +- **`on_event` inspection hook** on `Audd` / `AsyncAudd` (§7.7a): a callable receiving `AuddEvent` lifecycle events (kind=`request`/`response`/`exception`, method, url, request_id, http_status, elapsed_ms, error_code, extras). Off by default. Never logs the api_token or request body. Hook exceptions are swallowed at debug level so observability never breaks the request path. |
| 48 | + |
| 49 | +### Internal |
| 50 | + |
| 51 | +- 26 new regression tests (`tests/unit/test_v0_2_polish.py`) covering all four feature areas. |
| 52 | + |
| 53 | +## [0.1.1] - 2026-05-04 |
| 54 | + |
| 55 | +Independent code review caught several issues. v0.1.1 fixes them before they propagate to the other 8 language SDKs being modeled on this implementation. |
| 56 | + |
| 57 | +### Fixed |
| 58 | + |
| 59 | +- **C1 — `prepare_source` now returns a per-attempt re-opener.** *Note: the reviewer flagged this as a "silent zero-byte upload on retry" bug, but verification (running the regression test against v0.1.0 source) showed that httpx auto-seeks file handles between `post()` calls, so audd-python v0.1.0 was actually correct.* The re-opener pattern is kept as **defense in depth**: it doesn't depend on the HTTP library's seeking behavior, raises cleanly on unseekable streams (rather than silently sending an empty body), and matches the mandatory pattern other SDKs need (their HTTP libraries may not auto-seek). |
| 60 | +- **C2 — `advanced.find_lyrics` was using READ retry policy.** Fixed to use RECOGNITION (the lyrics endpoint is metered; the spec §7.1 cost-aware retry policy explicitly groups it with `recognize`). |
| 61 | +- **C3 — Code 51 (deprecation warning) was raising unconditionally.** Per spec §6.5, when the server returns code 51 with a usable `result`, the SDK now emits a `DeprecationWarning` and returns the result. Only raises if `result` is absent. |
| 62 | +- **S2 — Non-JSON HTTP errors mapped to `AuddSerializationError`.** A 502 with an HTML body now correctly raises `AuddServerError` preserving the HTTP status; `AuddSerializationError` is reserved for 2xx-with-bad-JSON. |
| 63 | +- **S5 — `LongpollConsumer` silently swallowed HTTP errors.** A 401/403/500 returned `{}` and the consumer looped forever, especially painful in browsers. Now non-2xx raises `AuddServerError`; JSON decode failures raise `AuddSerializationError`. |
| 64 | +- **S6 — `LongpollConsumer` had no retry/timeout knobs.** Per spec §4.1 ("same retry, timeout, transport configurability as the authenticated client"), now accepts `max_retries` and `backoff_factor` and applies READ-class retries on connection failures + 5xx. |
| 65 | +- **S9 — Confusing `FileNotFoundError` for typo'd URLs.** A string source that's neither HTTP(S) nor an existing path now raises `TypeError` with a hint about the URL prefix. |
| 66 | + |
| 67 | +### Added |
| 68 | + |
| 69 | +- **Context-manager protocol** on `Audd`, `AsyncAudd`, `LongpollConsumer`, `AsyncLongpollConsumer` — `with Audd(...) as audd:` / `async with AsyncAudd(...) as audd:` now work. |
| 70 | + |
| 71 | +### Tests |
| 72 | + |
| 73 | +- Added 16 regression tests covering each finding (`tests/unit/test_review_fixes.py`, `tests/unit/test_review_fixes_longpoll.py`). Total: 108 unit + contract tests passing. |
| 74 | + |
| 75 | +## [0.1.0] - 2026-05-04 |
| 76 | + |
| 77 | +### Added |
| 78 | +- Sync (`Audd`) and async (`AsyncAudd`) clients with parity for every capability. |
| 79 | +- `recognize(source, *, return_, market, timeout)` — auto-detects URL / path / file-like / bytes sources. |
| 80 | +- `recognize_enterprise(source, ...)` with 1-hour read timeout default. |
| 81 | +- `streams.*` namespace: `set_callback_url(url, return_metadata=...)`, `get_callback_url`, `add`, `set_url`, `delete`, `list`, `longpoll(category, *, skip_callback_check=False)` with default-on preflight, plus pure helpers `derive_longpoll_category` and `parse_callback`. |
| 82 | +- `custom_catalog.add(audio_id, source)` — namespaced with NOT-for-recognition warning docstring; 904 errors raise `AuddCustomCatalogAccessError`. |
| 83 | +- `advanced.*` namespace: `find_lyrics(query)`, `raw_request(method, params)` escape hatch. |
| 84 | +- Tokenless `LongpollConsumer` / `AsyncLongpollConsumer` for browser/widget contexts. |
| 85 | +- Forward-compatible Pydantic v2 models with `extra="allow"` on every type. |
| 86 | +- Full error hierarchy mapped to the 25+ AudD error codes. |
| 87 | +- Cost-aware retry policy: read endpoints retry 408/429/5xx; recognition endpoints don't retry post-upload read timeouts (cost protection); mutating endpoints retry only pre-upload connection failures. |
| 88 | +- CI: ruff + mypy strict + pytest matrix on Py 3.9–3.13 (`ci.yml`); contract tests on push/PR + daily cron + `openapi-updated` repository_dispatch (`contract.yml`); tag-triggered PyPI publishing with Sigstore attestation (`release.yml`). |
0 commit comments