|
| 1 | +# Changelog |
| 2 | + |
| 3 | +All notable changes to `stemsplit-python` are documented here. The format |
| 4 | +is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and |
| 5 | +this project adheres to [Semantic Versioning](https://semver.org/). |
| 6 | + |
| 7 | +## [0.1.0] - 2026-05-21 — Initial release: sync SDK for the StemSplit API |
| 8 | + |
| 9 | +First public release. Synchronous Python client for the official |
| 10 | +[StemSplit](https://stemsplit.io) hosted stem-separation API. |
| 11 | + |
| 12 | +### Added |
| 13 | + |
| 14 | +- **`StemSplit` sync client** (`AsyncStemSplit` placeholder pointing at |
| 15 | + v0.2). Bearer-auth, env-var key default (`STEMSPLIT_API_KEY`), |
| 16 | + configurable base URL, soft `sk_live_` prefix validation. |
| 17 | +- **Resources for every public endpoint:** |
| 18 | + - `client.jobs` — create (file / bytes / file-like / `source_url` / |
| 19 | + `upload_key`), get, list, `iter_all`, with the killer ergonomic |
| 20 | + layer: `JobHandle.wait()`, `.iter_progress()`, `.refresh()`, |
| 21 | + `.download_all()`. |
| 22 | + - `client.youtube_jobs` — create from YouTube URL, get, list, |
| 23 | + `iter_all`, same `wait()` / `download_all()` ergonomics. Surfaces |
| 24 | + video metadata (`video_title`, `video_duration`, `channel_name`, |
| 25 | + `youtube_url`) and the YouTube-specific `full_audio` output. |
| 26 | + - `client.uploads` — presigned-URL helpers (`create_ticket`, |
| 27 | + `upload_bytes`) for the rare case where you want to manage uploads |
| 28 | + yourself. |
| 29 | + - `client.account` — `balance()` / `get()` returning typed `Balance`. |
| 30 | + - `client.webhooks` — `create`, `list`, `delete`. Capture `secret` |
| 31 | + from `create()`; the API never returns it again. |
| 32 | +- **Webhook signature verification** (`stemsplit_python.webhooks`): |
| 33 | + `verify_signature` for raw-bytes verification and `verify_and_parse` |
| 34 | + that returns a typed `WebhookEvent`. Constant-time HMAC-SHA256 |
| 35 | + comparison, accepts both `sha256=<hex>` and bare hex. |
| 36 | +- **Stripe-style error hierarchy** mapping API responses 1:1 to typed |
| 37 | + exceptions: `BadRequestError` (400), `AuthenticationError` (401), |
| 38 | + `InsufficientCreditsError` (402, with `.required_seconds` / |
| 39 | + `.purchase_url`), `PermissionDeniedError` (403), `NotFoundError` |
| 40 | + (404), `ConflictError` (409), `UnprocessableEntityError` (422), |
| 41 | + `RateLimitError` (429, with `.retry_after` / `.limit` / |
| 42 | + `.remaining` / `.reset_at`), `InternalServerError` (5xx). Plus |
| 43 | + logical errors `JobFailedError`, `JobExpiredError`, |
| 44 | + `SignatureVerificationError`. |
| 45 | +- **Retries with backoff** on `429` and `5xx` for safe-to-replay verbs |
| 46 | + (default `max_retries=3`). Honors `Retry-After` when present. |
| 47 | +- **Rate-limit awareness:** parses `X-RateLimit-*` headers on every |
| 48 | + response, exposed as `client.last_rate_limit`. |
| 49 | +- **Idempotency-Key passthrough** on `jobs.create`, `youtube_jobs.create`, |
| 50 | + `webhooks.create`. Forwarded as a header today; lights up |
| 51 | + automatically when the API ships server-side support. |
| 52 | +- **Audio metadata (BPM / key)** on completed jobs: `Job.audio_metadata` |
| 53 | + and `YouTubeJob.audio_metadata` typed as `AudioMetadata { bpm, key }`. |
| 54 | + Both fields are optional and degrade gracefully if the analysis |
| 55 | + doesn't run. |
| 56 | +- **Pydantic v2 models** with `frozen=True`, `extra="allow"` (forward- |
| 57 | + compatible against new server fields), snake_case Python attributes |
| 58 | + aliased to the camelCase wire fields, and `Literal[...]` unions for |
| 59 | + every status / quality / format value (no `enum.Enum`). |
| 60 | +- **Typing:** `py.typed` marker shipped, `mypy --strict` clean. |
| 61 | +- **Three runtime dependencies only:** `httpx`, `pydantic`, |
| 62 | + `typing-extensions` (Python 3.10 backport only). No PyTorch, no |
| 63 | + numpy, no native code. |
| 64 | +- **Snapshot of the live OpenAPI 3.1 spec** at |
| 65 | + `openapi/openapi.json` so future versions can diff API drift. |
| 66 | + |
| 67 | +### Tested |
| 68 | + |
| 69 | +- 62 tests, ~92 % line coverage, all `respx`-backed mocks. Full status- |
| 70 | + code → exception matrix, retry behavior, end-to-end |
| 71 | + `jobs.create(audio=Path)` chain, `wait()` happy / failed / timeout |
| 72 | + paths, webhook signature round-trip + tamper detection, and YouTube |
| 73 | + job lifecycle. |
| 74 | + |
| 75 | +### Deferred |
| 76 | + |
| 77 | +- **`AsyncStemSplit`** — lands in v0.2. Constructing it today raises |
| 78 | + `NotImplementedError` with a pointer at the tracking issue. |
| 79 | +- **`job.cancel()`** — the API does not yet expose `DELETE /jobs/{id}`; |
| 80 | + honest omission rather than a no-op stub. Will land alongside the |
| 81 | + endpoint. |
| 82 | +- **CLI** — a separate `stemsplit-cli` add-on is planned for v0.2 so |
| 83 | + the SDK stays dependency-light. |
| 84 | + |
| 85 | +[0.1.0]: https://github.com/StemSplit/stemsplit-python/releases/tag/v0.1.0 |
0 commit comments