Skip to content

Commit cf5ebee

Browse files
author
AudD
committed
Initial release v1.4.0
0 parents  commit cf5ebee

57 files changed

Lines changed: 4964 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
name: tests + mypy + ruff
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-python@v5
20+
with:
21+
python-version: ${{ matrix.python }}
22+
cache: pip
23+
- name: Install
24+
run: |
25+
pip install --upgrade pip
26+
pip install -e '.[dev]'
27+
- name: Ruff
28+
run: python -m ruff check src/audd/ tests/
29+
- name: Mypy
30+
run: python -m mypy src/audd/
31+
- name: Unit tests
32+
run: python -m pytest tests/unit/ -v --tb=short

.github/workflows/contract.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Contract tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
- cron: '0 6 * * *' # daily at 06:00 UTC
10+
repository_dispatch:
11+
types: [openapi-updated]
12+
13+
jobs:
14+
contract:
15+
name: validate parser against latest audd-openapi fixtures
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
with:
20+
path: audd-python
21+
- name: Check out audd-openapi
22+
uses: actions/checkout@v4
23+
with:
24+
repository: AudDMusic/audd-openapi
25+
path: audd-openapi
26+
ref: main
27+
- uses: actions/setup-python@v5
28+
with:
29+
python-version: '3.12'
30+
cache: pip
31+
cache-dependency-path: audd-python/pyproject.toml
32+
- name: Install
33+
working-directory: audd-python
34+
run: pip install -e '.[dev]'
35+
- name: Run contract tests
36+
working-directory: audd-python
37+
env:
38+
AUDD_OPENAPI_FIXTURES: ${{ github.workspace }}/audd-openapi/fixtures
39+
run: python -m pytest tests/contract/ -v --tb=short
40+
- name: Open issue on failure (dispatched runs only)
41+
if: failure() && github.event_name == 'repository_dispatch'
42+
env:
43+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
TRIGGER_SHA: ${{ github.event.client_payload.trigger_sha }}
45+
run: |
46+
gh issue create \
47+
--title "Contract drift: audd-openapi spec change broke parser" \
48+
--body "An openapi-updated dispatch (trigger SHA: $TRIGGER_SHA) caused contract tests to fail. Investigate and update parsers." \
49+
--label "contract-drift" || true

.github/workflows/release.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'audd-python/v*'
7+
- 'v*'
8+
9+
permissions:
10+
contents: read
11+
id-token: write
12+
attestations: write
13+
14+
jobs:
15+
build-and-publish:
16+
name: Build and publish to PyPI
17+
runs-on: ubuntu-latest
18+
environment: pypi
19+
steps:
20+
- uses: actions/checkout@v4
21+
- uses: actions/setup-python@v5
22+
with:
23+
python-version: '3.12'
24+
- name: Install build tools
25+
run: |
26+
pip install --upgrade pip build
27+
- name: Build sdist + wheel
28+
run: python -m build
29+
- name: Generate Sigstore attestations
30+
uses: actions/attest-build-provenance@v1
31+
with:
32+
subject-path: 'dist/*'
33+
- name: Publish to PyPI
34+
uses: pypa/gh-action-pypi-publish@release/v1
35+
with:
36+
attestations: true

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
__pycache__/
2+
*.py[cod]
3+
.venv/
4+
venv/
5+
build/
6+
dist/
7+
*.egg-info/
8+
.pytest_cache/
9+
.mypy_cache/
10+
.ruff_cache/
11+
.coverage
12+
htmlcov/
13+
.env
14+
.DS_Store

CHANGELOG.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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.4.0] - 2026-05-05
6+
7+
### Changed
8+
9+
- **Brand casing fix.** Class names now match the brand `AudD` (capital D) instead of the previous `Au-d-d` PascalCase: `AudD`, `AsyncAudD`, `AudDError`, `AudDAPIError`, `AudDAuthenticationError`, `AudDQuotaError`, `AudDSubscriptionError`, `AudDCustomCatalogAccessError`, `AudDInvalidRequestError`, `AudDInvalidAudioError`, `AudDRateLimitError`, `AudDStreamLimitError`, `AudDNotReleasedError`, `AudDBlockedError`, `AudDNeedsUpdateError`, `AudDServerError`, `AudDConnectionError`, `AudDSerializationError`, `AudDEvent`. The package import path stays `audd` (lowercase), and the env var stays `AUDD_API_TOKEN`.
10+
- **README polish.** Streams gets a top-level section that includes tokenless longpoll as a subsection ("Receiving events without exposing your token"); the capability table groups longpoll with streams. Concurrency note moved into Configuration as a brief one-liner. Removed internal "design spec" references from public docstrings.
11+
12+
## [1.3.0] - 2026-05-05
13+
14+
Coordinated v1.3.0 stable release across the audd-sdks family. No breaking
15+
changes; the version bump signals API stability across all nine SDKs.
16+
17+
The full v0.3.0 polish — env-var auto-pickup, streaming/preview helpers
18+
with metadata fallback, `onEvent` inspection hook, thread-safe token
19+
rotation, plus per-language work (JPMS module-info, Kotlin `Flow<LongpollEvent>`,
20+
Swift `Sendable` + DocC, .NET AOT/source-gen + `IServiceCollection`, Rust
21+
TLS feature flags + `Serialize`, PHP PSR-3 logger, Python `__repr__` +
22+
`pretty_print()`, Go `slog` example) is now the v1.3.0 baseline.
23+
24+
## [0.3.0] - 2026-05-05
25+
26+
### Added
27+
28+
- **`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).
29+
- **`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.
30+
31+
### Documentation
32+
33+
- README opens with an explicit "thread-safe / safe for concurrent use" statement.
34+
35+
## [0.2.1] - 2026-05-05
36+
37+
### Improved
38+
39+
- **`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).
40+
- `streaming_urls()` correspondingly returns more entries — every provider with either a direct URL or a lis.tn redirect available.
41+
42+
## [0.2.0] - 2026-05-05
43+
44+
DX polish — env-var auto-pickup, streaming/preview helpers, on_event inspection hook, thread-safe token rotation.
45+
46+
### Added
47+
48+
- **`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.
49+
- **`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).
50+
- **`RecognitionResult.streaming_urls()`** (§4.3): convenience dict of all five providers' redirect URLs.
51+
- **`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.
52+
- Same `streaming_url` / `streaming_urls` / `thumbnail_url` helpers on `EnterpriseMatch`.
53+
- **`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.
54+
- **`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.
55+
56+
### Internal
57+
58+
- 26 new regression tests (`tests/unit/test_v0_2_polish.py`) covering all four feature areas.
59+
60+
## [0.1.1] - 2026-05-04
61+
62+
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.
63+
64+
### Fixed
65+
66+
- **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).
67+
- **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`).
68+
- **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.
69+
- **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.
70+
- **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`.
71+
- **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.
72+
- **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.
73+
74+
### Added
75+
76+
- **Context-manager protocol** on `AudD`, `AsyncAudD`, `LongpollConsumer`, `AsyncLongpollConsumer``with AudD(...) as audd:` / `async with AsyncAudD(...) as audd:` now work.
77+
78+
### Tests
79+
80+
- 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.
81+
82+
## [0.1.0] - 2026-05-04
83+
84+
### Added
85+
- Sync (`AudD`) and async (`AsyncAudD`) clients with parity for every capability.
86+
- `recognize(source, *, return_, market, timeout)` — auto-detects URL / path / file-like / bytes sources.
87+
- `recognize_enterprise(source, ...)` with 1-hour read timeout default.
88+
- `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`.
89+
- `custom_catalog.add(audio_id, source)` — namespaced with NOT-for-recognition warning docstring; 904 errors raise `AudDCustomCatalogAccessError`.
90+
- `advanced.*` namespace: `find_lyrics(query)`, `raw_request(method, params)` escape hatch.
91+
- Tokenless `LongpollConsumer` / `AsyncLongpollConsumer` for browser/widget contexts.
92+
- Forward-compatible Pydantic v2 models with `extra="allow"` on every type.
93+
- Full error hierarchy mapped to the 25+ AudD error codes.
94+
- 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.
95+
- 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`).

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 AudD (https://audd.io)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)