audd 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
audd-1.4.0/.gitignore ADDED
@@ -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
@@ -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`).
audd-1.4.0/LICENSE ADDED
@@ -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.
audd-1.4.0/PKG-INFO ADDED
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: audd
3
+ Version: 1.4.0
4
+ Summary: Official Python SDK for the AudD music recognition API.
5
+ Project-URL: Homepage, https://audd.io
6
+ Project-URL: Documentation, https://docs.audd.io
7
+ Project-URL: Source, https://github.com/AudDMusic/audd-python
8
+ Project-URL: Issues, https://github.com/AudDMusic/audd-python/issues
9
+ Author-email: AudD <api@audd.io>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: audd,audio,fingerprinting,music-recognition
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.9
25
+ Requires-Dist: anyio>=4
26
+ Requires-Dist: httpx<1,>=0.27
27
+ Requires-Dist: pydantic<3,>=2.5
28
+ Provides-Extra: dev
29
+ Requires-Dist: jsonschema>=4.21; extra == 'dev'
30
+ Requires-Dist: mypy>=1.10; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
32
+ Requires-Dist: pytest>=8; extra == 'dev'
33
+ Requires-Dist: pyyaml>=6; extra == 'dev'
34
+ Requires-Dist: referencing>=0.35; extra == 'dev'
35
+ Requires-Dist: respx>=0.21; extra == 'dev'
36
+ Requires-Dist: ruff>=0.5; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # audd
40
+
41
+ [![CI](https://github.com/AudDMusic/audd-python/actions/workflows/ci.yml/badge.svg)](https://github.com/AudDMusic/audd-python/actions/workflows/ci.yml)
42
+ [![Contract](https://github.com/AudDMusic/audd-python/actions/workflows/contract.yml/badge.svg)](https://github.com/AudDMusic/audd-python/actions/workflows/contract.yml)
43
+ [![PyPI](https://img.shields.io/pypi/v/audd.svg)](https://pypi.org/project/audd/)
44
+ [![Python versions](https://img.shields.io/pypi/pyversions/audd.svg)](https://pypi.org/project/audd/)
45
+
46
+ Official Python SDK for the [AudD](https://audd.io) music recognition API.
47
+
48
+ ## Quickstart
49
+
50
+ ```bash
51
+ pip install audd
52
+ ```
53
+
54
+ ```python
55
+ from audd import AudD
56
+
57
+ audd = AudD(api_token="test") # use your token from https://dashboard.audd.io
58
+ result = audd.recognize("https://audd.tech/example.mp3")
59
+ if result:
60
+ print(f"{result.artist} — {result.title}")
61
+ ```
62
+
63
+ ## Capabilities
64
+
65
+ | What | How |
66
+ |---|---|
67
+ | Recognize a short clip (≤25s) | `audd.recognize(source)` |
68
+ | Recognize a long file (hours, days) | `audd.recognize_enterprise(source, limit=...)` |
69
+ | Manage stream recognition (set callback, longpoll for events) | `audd.streams.*` |
70
+
71
+ `source` accepts a URL, a file path, a file-like object, or raw bytes — auto-detected.
72
+
73
+ ## Async
74
+
75
+ Use `AsyncAudD` instead — same surface:
76
+
77
+ ```python
78
+ from audd import AsyncAudD
79
+
80
+ async def main():
81
+ audd = AsyncAudD(api_token="test")
82
+ try:
83
+ result = await audd.recognize("https://audd.tech/example.mp3")
84
+ print(result)
85
+ finally:
86
+ await audd.aclose()
87
+ ```
88
+
89
+ ## Errors
90
+
91
+ Every server error becomes a typed exception:
92
+
93
+ ```python
94
+ from audd import AudD, AudDAuthenticationError, AudDSubscriptionError
95
+
96
+ try:
97
+ AudD(api_token="bad").recognize("https://x.mp3")
98
+ except AudDAuthenticationError as e:
99
+ print(f"check your token: {e.error_code} {e.message}")
100
+ except AudDSubscriptionError:
101
+ print("this endpoint isn't enabled on your token")
102
+ ```
103
+
104
+ The full hierarchy is documented in [`src/audd/errors.py`](src/audd/errors.py). Every `AudDAPIError` carries `error_code`, `message`, `http_status`, `request_id`, `requested_params`, `request_method`, `branded_message`, and `raw_response`.
105
+
106
+ ## Forward compatibility
107
+
108
+ Models accept and round-trip unknown server fields via `model_extra`:
109
+
110
+ ```python
111
+ result = audd.recognize("https://example.mp3", return_=["apple_music"])
112
+ print(result.apple_music.url) # typed
113
+ print(result.model_extra) # any other unknown fields
114
+ ```
115
+
116
+ If AudD adds a new metadata block tomorrow (e.g., `tidal`), you can read it as `result.model_extra["tidal"]` *today* — no SDK release needed. The next SDK release adds the typed `tidal` field, and both paths keep working.
117
+
118
+ ## Streams
119
+
120
+ Manage real-time stream recognition (set callback, longpoll for events):
121
+
122
+ ```python
123
+ audd.streams.set_callback_url("https://your.server/cb")
124
+ audd.streams.add("https://your.stream.url", radio_id=42)
125
+ for event in audd.streams.list():
126
+ print(event)
127
+ ```
128
+
129
+ ### Receiving events without exposing your token
130
+
131
+ For browser widgets and other contexts where shipping the api_token would leak it,
132
+ derive a `category` server-side and share that with the consumer:
133
+
134
+ ```python
135
+ from audd import LongpollConsumer
136
+
137
+ # `category` is derived server-side via AudD(...).streams.derive_longpoll_category(radio_id),
138
+ # then shared with the browser/widget. The consumer carries no api_token.
139
+ consumer = LongpollConsumer(category="abc123def")
140
+ for event in consumer.iterate(timeout=30):
141
+ print(event)
142
+ ```
143
+
144
+ ## Configuration
145
+
146
+ ```python
147
+ import httpx
148
+ from audd import AudD
149
+
150
+ audd = AudD(
151
+ api_token="...",
152
+ max_retries=3, # retry budget per call
153
+ backoff_factor=0.5, # initial backoff seconds (jittered)
154
+ httpx_client=httpx.Client(proxies="http://corp-proxy:8080"),
155
+ )
156
+ ```
157
+
158
+ Default timeouts: 30s connect / 60s read for standard endpoints, 30s connect / **1 hour** read for the enterprise endpoint. Pass `timeout=` per call to override.
159
+
160
+ **Concurrency:** `AudD` and `AsyncAudD` are safe for concurrent use — share one instance across threads or asyncio tasks. `set_api_token(...)` rotates the token safely; in-flight requests continue with the old token, subsequent requests use the new one.
161
+
162
+ ## Custom catalog (advanced — not for music recognition)
163
+
164
+ > ⚠ **The custom-catalog endpoint is NOT how you submit audio for music recognition.**
165
+ > For recognition, use `recognize()` or `recognize_enterprise()`. The custom-catalog
166
+ > endpoint adds songs to your private fingerprint database for *your* account.
167
+ > Requires special access — contact api@audd.io if you need it.
168
+
169
+ ```python
170
+ audd.custom_catalog.add(audio_id=42, source="https://my.song.mp3")
171
+ ```
172
+
173
+ ## Advanced
174
+
175
+ For endpoints not yet wrapped by typed methods on this SDK, use the raw-request escape hatch:
176
+
177
+ ```python
178
+ raw = audd.advanced.raw_request("someNewMethod", {"q": "x"})
179
+ ```
180
+
181
+ ## Spec contract
182
+
183
+ This SDK builds against the [`audd-openapi`](https://github.com/AudDMusic/audd-openapi) spec. The contract tests in `tests/contract/` validate the parser against the canonical fixture set on every push, on a daily cron, and on every spec update.
184
+
185
+ ## License
186
+
187
+ MIT — see [LICENSE](./LICENSE).
188
+
189
+ ## Support
190
+
191
+ - Documentation: https://docs.audd.io
192
+ - Tokens: https://dashboard.audd.io
193
+ - Email: api@audd.io
audd-1.4.0/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # audd
2
+
3
+ [![CI](https://github.com/AudDMusic/audd-python/actions/workflows/ci.yml/badge.svg)](https://github.com/AudDMusic/audd-python/actions/workflows/ci.yml)
4
+ [![Contract](https://github.com/AudDMusic/audd-python/actions/workflows/contract.yml/badge.svg)](https://github.com/AudDMusic/audd-python/actions/workflows/contract.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/audd.svg)](https://pypi.org/project/audd/)
6
+ [![Python versions](https://img.shields.io/pypi/pyversions/audd.svg)](https://pypi.org/project/audd/)
7
+
8
+ Official Python SDK for the [AudD](https://audd.io) music recognition API.
9
+
10
+ ## Quickstart
11
+
12
+ ```bash
13
+ pip install audd
14
+ ```
15
+
16
+ ```python
17
+ from audd import AudD
18
+
19
+ audd = AudD(api_token="test") # use your token from https://dashboard.audd.io
20
+ result = audd.recognize("https://audd.tech/example.mp3")
21
+ if result:
22
+ print(f"{result.artist} — {result.title}")
23
+ ```
24
+
25
+ ## Capabilities
26
+
27
+ | What | How |
28
+ |---|---|
29
+ | Recognize a short clip (≤25s) | `audd.recognize(source)` |
30
+ | Recognize a long file (hours, days) | `audd.recognize_enterprise(source, limit=...)` |
31
+ | Manage stream recognition (set callback, longpoll for events) | `audd.streams.*` |
32
+
33
+ `source` accepts a URL, a file path, a file-like object, or raw bytes — auto-detected.
34
+
35
+ ## Async
36
+
37
+ Use `AsyncAudD` instead — same surface:
38
+
39
+ ```python
40
+ from audd import AsyncAudD
41
+
42
+ async def main():
43
+ audd = AsyncAudD(api_token="test")
44
+ try:
45
+ result = await audd.recognize("https://audd.tech/example.mp3")
46
+ print(result)
47
+ finally:
48
+ await audd.aclose()
49
+ ```
50
+
51
+ ## Errors
52
+
53
+ Every server error becomes a typed exception:
54
+
55
+ ```python
56
+ from audd import AudD, AudDAuthenticationError, AudDSubscriptionError
57
+
58
+ try:
59
+ AudD(api_token="bad").recognize("https://x.mp3")
60
+ except AudDAuthenticationError as e:
61
+ print(f"check your token: {e.error_code} {e.message}")
62
+ except AudDSubscriptionError:
63
+ print("this endpoint isn't enabled on your token")
64
+ ```
65
+
66
+ The full hierarchy is documented in [`src/audd/errors.py`](src/audd/errors.py). Every `AudDAPIError` carries `error_code`, `message`, `http_status`, `request_id`, `requested_params`, `request_method`, `branded_message`, and `raw_response`.
67
+
68
+ ## Forward compatibility
69
+
70
+ Models accept and round-trip unknown server fields via `model_extra`:
71
+
72
+ ```python
73
+ result = audd.recognize("https://example.mp3", return_=["apple_music"])
74
+ print(result.apple_music.url) # typed
75
+ print(result.model_extra) # any other unknown fields
76
+ ```
77
+
78
+ If AudD adds a new metadata block tomorrow (e.g., `tidal`), you can read it as `result.model_extra["tidal"]` *today* — no SDK release needed. The next SDK release adds the typed `tidal` field, and both paths keep working.
79
+
80
+ ## Streams
81
+
82
+ Manage real-time stream recognition (set callback, longpoll for events):
83
+
84
+ ```python
85
+ audd.streams.set_callback_url("https://your.server/cb")
86
+ audd.streams.add("https://your.stream.url", radio_id=42)
87
+ for event in audd.streams.list():
88
+ print(event)
89
+ ```
90
+
91
+ ### Receiving events without exposing your token
92
+
93
+ For browser widgets and other contexts where shipping the api_token would leak it,
94
+ derive a `category` server-side and share that with the consumer:
95
+
96
+ ```python
97
+ from audd import LongpollConsumer
98
+
99
+ # `category` is derived server-side via AudD(...).streams.derive_longpoll_category(radio_id),
100
+ # then shared with the browser/widget. The consumer carries no api_token.
101
+ consumer = LongpollConsumer(category="abc123def")
102
+ for event in consumer.iterate(timeout=30):
103
+ print(event)
104
+ ```
105
+
106
+ ## Configuration
107
+
108
+ ```python
109
+ import httpx
110
+ from audd import AudD
111
+
112
+ audd = AudD(
113
+ api_token="...",
114
+ max_retries=3, # retry budget per call
115
+ backoff_factor=0.5, # initial backoff seconds (jittered)
116
+ httpx_client=httpx.Client(proxies="http://corp-proxy:8080"),
117
+ )
118
+ ```
119
+
120
+ Default timeouts: 30s connect / 60s read for standard endpoints, 30s connect / **1 hour** read for the enterprise endpoint. Pass `timeout=` per call to override.
121
+
122
+ **Concurrency:** `AudD` and `AsyncAudD` are safe for concurrent use — share one instance across threads or asyncio tasks. `set_api_token(...)` rotates the token safely; in-flight requests continue with the old token, subsequent requests use the new one.
123
+
124
+ ## Custom catalog (advanced — not for music recognition)
125
+
126
+ > ⚠ **The custom-catalog endpoint is NOT how you submit audio for music recognition.**
127
+ > For recognition, use `recognize()` or `recognize_enterprise()`. The custom-catalog
128
+ > endpoint adds songs to your private fingerprint database for *your* account.
129
+ > Requires special access — contact api@audd.io if you need it.
130
+
131
+ ```python
132
+ audd.custom_catalog.add(audio_id=42, source="https://my.song.mp3")
133
+ ```
134
+
135
+ ## Advanced
136
+
137
+ For endpoints not yet wrapped by typed methods on this SDK, use the raw-request escape hatch:
138
+
139
+ ```python
140
+ raw = audd.advanced.raw_request("someNewMethod", {"q": "x"})
141
+ ```
142
+
143
+ ## Spec contract
144
+
145
+ This SDK builds against the [`audd-openapi`](https://github.com/AudDMusic/audd-openapi) spec. The contract tests in `tests/contract/` validate the parser against the canonical fixture set on every push, on a daily cron, and on every spec update.
146
+
147
+ ## License
148
+
149
+ MIT — see [LICENSE](./LICENSE).
150
+
151
+ ## Support
152
+
153
+ - Documentation: https://docs.audd.io
154
+ - Tokens: https://dashboard.audd.io
155
+ - Email: api@audd.io
@@ -0,0 +1,91 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "audd"
7
+ dynamic = ["version"]
8
+ description = "Official Python SDK for the AudD music recognition API."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "AudD", email = "api@audd.io" }]
13
+ keywords = ["audd", "music-recognition", "audio", "fingerprinting"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Multimedia :: Sound/Audio :: Analysis",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = [
28
+ "httpx>=0.27,<1",
29
+ "pydantic>=2.5,<3",
30
+ "anyio>=4",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://audd.io"
35
+ Documentation = "https://docs.audd.io"
36
+ Source = "https://github.com/AudDMusic/audd-python"
37
+ Issues = "https://github.com/AudDMusic/audd-python/issues"
38
+
39
+ [project.optional-dependencies]
40
+ dev = [
41
+ "pytest>=8",
42
+ "pytest-asyncio>=0.23",
43
+ "respx>=0.21",
44
+ "mypy>=1.10",
45
+ "ruff>=0.5",
46
+ "pyyaml>=6",
47
+ "jsonschema>=4.21",
48
+ "referencing>=0.35",
49
+ ]
50
+
51
+ [tool.hatch.version]
52
+ path = "src/audd/_version.py"
53
+
54
+ [tool.hatch.build.targets.sdist]
55
+ include = ["src/audd", "README.md", "LICENSE", "CHANGELOG.md"]
56
+
57
+ [tool.hatch.build.targets.wheel]
58
+ packages = ["src/audd"]
59
+
60
+ [tool.pytest.ini_options]
61
+ asyncio_mode = "auto"
62
+ testpaths = ["tests"]
63
+ addopts = "-ra --strict-markers"
64
+ markers = [
65
+ "integration: opt-in tests that hit the live AudD API (requires AUDD_API_TOKEN env var)",
66
+ ]
67
+
68
+ [tool.mypy]
69
+ strict = true
70
+ python_version = "3.9"
71
+ files = ["src/audd"]
72
+
73
+ [[tool.mypy.overrides]]
74
+ module = "tests.*"
75
+ disallow_untyped_defs = false
76
+
77
+ [tool.ruff]
78
+ line-length = 100
79
+ target-version = "py39"
80
+
81
+ [tool.ruff.lint]
82
+ select = ["E", "F", "W", "I", "B", "UP", "N", "PL", "RUF"]
83
+ ignore = [
84
+ "PLR0913", # too many args — recognize_enterprise legitimately has many kwargs
85
+ "PLC0415", # lazy imports inside namespace getters break circular imports
86
+ ]
87
+
88
+ [tool.ruff.lint.per-file-ignores]
89
+ "tests/**/*.py" = ["PLR2004", "PLR0915", "S101", "E501"] # magic values, long tests, asserts, line length
90
+ "examples/**/*.py" = ["PLR2004"] # argv-count comparisons in example scripts
91
+ "src/audd/models.py" = ["N815"] # Apple Music/Napster ship camelCase JSON keys; field names must match
@@ -0,0 +1,46 @@
1
+ """Official Python SDK for the AudD music recognition API."""
2
+ from audd._version import __version__
3
+ from audd.client import AsyncAudD, AudD
4
+ from audd.errors import (
5
+ AudDAPIError,
6
+ AudDAuthenticationError,
7
+ AudDBlockedError,
8
+ AudDConnectionError,
9
+ AudDCustomCatalogAccessError,
10
+ AudDError,
11
+ AudDInvalidAudioError,
12
+ AudDInvalidRequestError,
13
+ AudDNeedsUpdateError,
14
+ AudDNotReleasedError,
15
+ AudDQuotaError,
16
+ AudDRateLimitError,
17
+ AudDSerializationError,
18
+ AudDServerError,
19
+ AudDStreamLimitError,
20
+ AudDSubscriptionError,
21
+ )
22
+ from audd.longpoll import AsyncLongpollConsumer, LongpollConsumer
23
+
24
+ __all__ = [
25
+ "AsyncAudD",
26
+ "AsyncLongpollConsumer",
27
+ "AudD",
28
+ "AudDAPIError",
29
+ "AudDAuthenticationError",
30
+ "AudDBlockedError",
31
+ "AudDConnectionError",
32
+ "AudDCustomCatalogAccessError",
33
+ "AudDError",
34
+ "AudDInvalidAudioError",
35
+ "AudDInvalidRequestError",
36
+ "AudDNeedsUpdateError",
37
+ "AudDNotReleasedError",
38
+ "AudDQuotaError",
39
+ "AudDRateLimitError",
40
+ "AudDSerializationError",
41
+ "AudDServerError",
42
+ "AudDStreamLimitError",
43
+ "AudDSubscriptionError",
44
+ "LongpollConsumer",
45
+ "__version__",
46
+ ]