discogs-sdk 0.1.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.
Files changed (60) hide show
  1. discogs_sdk-0.1.0/PKG-INFO +339 -0
  2. discogs_sdk-0.1.0/README.md +310 -0
  3. discogs_sdk-0.1.0/pyproject.toml +80 -0
  4. discogs_sdk-0.1.0/src/discogs_sdk/__init__.py +134 -0
  5. discogs_sdk-0.1.0/src/discogs_sdk/_async/__init__.py +3 -0
  6. discogs_sdk-0.1.0/src/discogs_sdk/_async/_client.py +258 -0
  7. discogs_sdk-0.1.0/src/discogs_sdk/_async/_lazy.py +94 -0
  8. discogs_sdk-0.1.0/src/discogs_sdk/_async/_oauth.py +100 -0
  9. discogs_sdk-0.1.0/src/discogs_sdk/_async/_paginator.py +100 -0
  10. discogs_sdk-0.1.0/src/discogs_sdk/_async/_resource.py +92 -0
  11. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/__init__.py +75 -0
  12. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/artists.py +39 -0
  13. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/collection.py +248 -0
  14. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/exports.py +30 -0
  15. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/labels.py +32 -0
  16. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/lists.py +33 -0
  17. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/marketplace.py +122 -0
  18. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/masters.py +50 -0
  19. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/releases.py +96 -0
  20. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/search.py +22 -0
  21. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/uploads.py +35 -0
  22. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/users.py +168 -0
  23. discogs_sdk-0.1.0/src/discogs_sdk/_async/resources/wantlist.py +34 -0
  24. discogs_sdk-0.1.0/src/discogs_sdk/_base_client.py +177 -0
  25. discogs_sdk-0.1.0/src/discogs_sdk/_exceptions.py +60 -0
  26. discogs_sdk-0.1.0/src/discogs_sdk/_sync/__init__.py +6 -0
  27. discogs_sdk-0.1.0/src/discogs_sdk/_sync/_client.py +230 -0
  28. discogs_sdk-0.1.0/src/discogs_sdk/_sync/_lazy.py +72 -0
  29. discogs_sdk-0.1.0/src/discogs_sdk/_sync/_oauth.py +85 -0
  30. discogs_sdk-0.1.0/src/discogs_sdk/_sync/_paginator.py +89 -0
  31. discogs_sdk-0.1.0/src/discogs_sdk/_sync/_resource.py +66 -0
  32. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/__init__.py +78 -0
  33. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/artists.py +34 -0
  34. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/collection.py +228 -0
  35. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/exports.py +23 -0
  36. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/labels.py +29 -0
  37. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/lists.py +28 -0
  38. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/marketplace.py +101 -0
  39. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/masters.py +43 -0
  40. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/releases.py +81 -0
  41. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/search.py +19 -0
  42. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/uploads.py +28 -0
  43. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/users.py +160 -0
  44. discogs_sdk-0.1.0/src/discogs_sdk/_sync/resources/wantlist.py +30 -0
  45. discogs_sdk-0.1.0/src/discogs_sdk/models/__init__.py +109 -0
  46. discogs_sdk-0.1.0/src/discogs_sdk/models/_common.py +171 -0
  47. discogs_sdk-0.1.0/src/discogs_sdk/models/artist.py +34 -0
  48. discogs_sdk-0.1.0/src/discogs_sdk/models/collection.py +38 -0
  49. discogs_sdk-0.1.0/src/discogs_sdk/models/export.py +15 -0
  50. discogs_sdk-0.1.0/src/discogs_sdk/models/label.py +31 -0
  51. discogs_sdk-0.1.0/src/discogs_sdk/models/list_.py +38 -0
  52. discogs_sdk-0.1.0/src/discogs_sdk/models/marketplace.py +90 -0
  53. discogs_sdk-0.1.0/src/discogs_sdk/models/master.py +40 -0
  54. discogs_sdk-0.1.0/src/discogs_sdk/models/release.py +94 -0
  55. discogs_sdk-0.1.0/src/discogs_sdk/models/search.py +28 -0
  56. discogs_sdk-0.1.0/src/discogs_sdk/models/upload.py +17 -0
  57. discogs_sdk-0.1.0/src/discogs_sdk/models/user.py +46 -0
  58. discogs_sdk-0.1.0/src/discogs_sdk/models/wantlist.py +11 -0
  59. discogs_sdk-0.1.0/src/discogs_sdk/oauth.py +21 -0
  60. discogs_sdk-0.1.0/src/discogs_sdk/py.typed +0 -0
@@ -0,0 +1,339 @@
1
+ Metadata-Version: 2.4
2
+ Name: discogs-sdk
3
+ Version: 0.1.0
4
+ Summary: Modern Python client for the Discogs API
5
+ Keywords: api,discogs,music,python,sdk
6
+ Author: Jean-Marc Fontaine
7
+ Author-email: Jean-Marc Fontaine <jm@jmfontaine.net>
8
+ License-Expression: Apache-2.0
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: httpx>=0.28
20
+ Requires-Dist: pydantic>=2.12
21
+ Requires-Dist: typing-extensions>=4
22
+ Requires-Dist: hishel>=0.1 ; extra == 'cache'
23
+ Requires-Python: >=3.10
24
+ Project-URL: Homepage, https://github.com/jmfontaine/discogs-sdk
25
+ Project-URL: Issues, https://github.com/jmfontaine/discogs-sdk/issues
26
+ Project-URL: Repository, https://github.com/jmfontaine/discogs-sdk
27
+ Provides-Extra: cache
28
+ Description-Content-Type: text/markdown
29
+
30
+ # discogs-sdk
31
+
32
+ <p align="center">
33
+ <a href="https://pypi.org/project/discogs-sdk/"><img src="https://img.shields.io/pypi/v/discogs-sdk.svg" alt="PyPI version"></a>
34
+ <a href="https://pypi.org/project/discogs-sdk/"><img src="https://img.shields.io/pypi/pyversions/discogs-sdk.svg" alt="Python versions"></a>
35
+ <a href="https://github.com/jmfontaine/discogs-sdk/actions"><img src="https://github.com/jmfontaine/discogs-sdk/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
36
+ <a href="https://pypi.org/project/discogs-sdk/"><img src="https://img.shields.io/pypi/dm/discogs-sdk.svg" alt="PyPI downloads"></a>
37
+ <a href="https://github.com/jmfontaine/discogs-sdk/blob/main/LICENSE.txt"><img src="https://img.shields.io/pypi/l/discogs-sdk.svg" alt="License"></a>
38
+ </p>
39
+
40
+ A typed Python client for the [Discogs API](https://www.discogs.com/developers). Sync and async, with Pydantic models, automatic pagination, and lazy resource loading.
41
+
42
+ ```python
43
+ from discogs_sdk import Discogs
44
+
45
+ # Reads DISCOGS_TOKEN from environment
46
+ with Discogs() as client:
47
+ release = client.releases.get(352665)
48
+ print(f"{release.title} ({release.year})")
49
+ # The Downward Spiral (1994)
50
+
51
+ for result in client.search(query="Nine Inch Nails", type="artist"):
52
+ print(result.title)
53
+ ```
54
+
55
+ ## Features
56
+
57
+ - **Sync and async** — `Discogs` for synchronous code, `AsyncDiscogs` for async/await
58
+ - **Typed models** — every response is a Pydantic v2 model with `extra="allow"` for undocumented fields
59
+ - **Lazy resources** — `.get()` returns a lightweight proxy; HTTP fires only when you access data
60
+ - **Auto-pagination** — iterate results with `for`/`async for`; pages are fetched on demand
61
+ - **Sub-resource chaining** — `client.releases.get(id).rating.get()` — intermediate accessors never trigger HTTP
62
+ - **Automatic retries** — retries on 429/5xx with exponential backoff and `Retry-After` support
63
+ - **Three auth modes** — personal token, consumer key/secret, or full OAuth 1.0a
64
+ - **Optional caching** — `cache=True` enables [hishel](https://github.com/karpetrosyan/hishel)-backed HTTP caching
65
+
66
+ ## Why discogs-sdk?
67
+
68
+ - **Typed models** — Pydantic v2 models give you autocomplete and IDE support instead of untyped dicts
69
+ - **Lazy loading that respects rate limits** — `.get()` returns a proxy; HTTP fires only when you access data, minimizing calls against the 60 req/min limit
70
+ - **Sync + async from one codebase** — async-first source with auto-generated sync client, identical APIs
71
+ - **Auto-pagination** — iterate results with `for`/`async for`; no manual page math
72
+ - **Modern stack** — built on httpx and Pydantic v2, not requests and raw dicts
73
+
74
+ ### How it compares
75
+
76
+ | Feature | discogs-sdk | python3-discogs-client | Raw API |
77
+ |---|---|---|---|
78
+ | Typed models (Pydantic) | Yes | No | No |
79
+ | Async support | Yes | No | Manual |
80
+ | Auto-pagination | Yes | Yes | Manual |
81
+ | Lazy loading | Yes | No | N/A |
82
+ | Rate limit handling | Automatic retry | Manual | Manual |
83
+ | OAuth 1.0a | Yes | Yes | Manual |
84
+ | HTTP caching | Built-in opt-in | No | Manual |
85
+
86
+ ## Installation
87
+
88
+ ```bash
89
+ pip install discogs-sdk
90
+ # or
91
+ uv add discogs-sdk
92
+ ```
93
+
94
+ With HTTP caching support:
95
+
96
+ ```bash
97
+ pip install discogs-sdk[cache]
98
+ # or
99
+ uv add discogs-sdk[cache]
100
+ ```
101
+
102
+ Requires Python 3.10+.
103
+
104
+ ## Quick start
105
+
106
+ ### Authentication
107
+
108
+ Set your personal access token from [your Discogs developer settings](https://www.discogs.com/settings/developers) as an environment variable:
109
+
110
+ ```bash
111
+ export DISCOGS_TOKEN="your-token-here"
112
+ ```
113
+
114
+ ```python
115
+ from discogs_sdk import Discogs
116
+
117
+ client = Discogs() # reads DISCOGS_TOKEN from environment
118
+ ```
119
+
120
+ You can also pass credentials explicitly:
121
+
122
+ ```python
123
+ client = Discogs(token="your-token-here")
124
+ ```
125
+
126
+ The SDK supports three auth modes: personal token, consumer key/secret, and OAuth 1.0a. All credentials resolve via constructor arg > environment variable (`DISCOGS_TOKEN`, `DISCOGS_CONSUMER_KEY`, etc.). See [`examples/authentication.py`](examples/authentication.py) for the full OAuth flow.
127
+
128
+ > [!TIP]
129
+ > Use a `.env` file with [python-dotenv](https://pypi.org/project/python-dotenv/) or
130
+ > [direnv](https://direnv.net/) to avoid exporting tokens manually in every shell.
131
+
132
+ > [!NOTE]
133
+ > Discogs enforces a 60 requests/minute rate limit. The SDK handles this automatically
134
+ > with exponential backoff and `Retry-After` support — no manual throttling needed.
135
+
136
+ ### Fetching resources
137
+
138
+ ```python
139
+ # Releases, artists, masters, labels
140
+ release = client.releases.get(352665)
141
+ print(release.title) # lazy — HTTP fires here → "The Downward Spiral"
142
+
143
+ artist = client.artists.get(3857)
144
+ print(artist.name) # "Nine Inch Nails"
145
+
146
+ master = client.masters.get(3719)
147
+ label = client.labels.get(647)
148
+ ```
149
+
150
+ ### Search
151
+
152
+ ```python
153
+ for result in client.search(query="Pretty Hate Machine", type="release", year="1989"):
154
+ print(f"[{result.type}] {result.title}")
155
+ # [release] Nine Inch Nails - Pretty Hate Machine
156
+ ```
157
+
158
+ ### Sub-resources
159
+
160
+ ```python
161
+ # Community rating
162
+ rating = client.releases.get(352665).rating.get()
163
+ print(f"Average: {rating.rating.average}") # Average: 4.49
164
+
165
+ # Artist releases with sorting
166
+ for rel in client.artists.get(3857).releases.list(sort="year", sort_order="desc"):
167
+ print(f"{rel.title} ({rel.year})")
168
+
169
+ # Master versions with filters
170
+ for v in client.masters.get(3719).versions.list(format="Vinyl", country="US"):
171
+ print(f"{v.title} [{v.format}]")
172
+ ```
173
+
174
+ ### Async usage
175
+
176
+ ```python
177
+ import asyncio
178
+ from discogs_sdk import AsyncDiscogs
179
+
180
+ async def main():
181
+ async with AsyncDiscogs() as client: # reads DISCOGS_TOKEN from environment
182
+ # Must await lazy resources in async mode
183
+ release = await client.releases.get(352665)
184
+ print(release.title)
185
+
186
+ # Async iteration for paginated results
187
+ async for result in client.search(query="Nine Inch Nails"):
188
+ print(result.title)
189
+
190
+ asyncio.run(main())
191
+ ```
192
+
193
+ ### Marketplace
194
+
195
+ ```python
196
+ # Listings
197
+ listing = client.marketplace.listings.get(123456789)
198
+ new = client.marketplace.listings.create(
199
+ release_id=352665, condition="Very Good Plus (VG+)", price=25.00,
200
+ )
201
+ client.marketplace.listings.update(new.id, price=22.50)
202
+ client.marketplace.listings.delete(new.id)
203
+
204
+ # Orders
205
+ for order in client.marketplace.orders.list(status="Payment Received"):
206
+ print(f"Order {order.id}: {order.status}")
207
+
208
+ # Fee lookup
209
+ fee = client.marketplace.fee.get(price=25.00, currency="USD")
210
+ ```
211
+
212
+ ### Collection
213
+
214
+ ```python
215
+ user = client.users.get("your_username")
216
+
217
+ # Folders
218
+ folders = user.collection.folders.list()
219
+ user.collection.folders.create(name="Industrial")
220
+
221
+ # Browse folder contents
222
+ for item in user.collection.folders.get(0).releases.list(sort="added"):
223
+ print(item.basic_information.title)
224
+
225
+ # Add a release
226
+ user.collection.folders.get(1).releases.create(release_id=352665)
227
+
228
+ # Deep chaining: folder -> release -> instance -> fields
229
+ user.collection.folders.get(1).releases.get(352665).instances.get(
230
+ 98765
231
+ ).fields.update(field_id=1, value="Signed copy")
232
+
233
+ # Collection value
234
+ value = user.collection.value.get()
235
+ print(f"Median: {value.median}, Maximum: {value.maximum}")
236
+
237
+ # Wantlist
238
+ user.wantlist.create(release_id=352665, notes="Original pressing", rating=4)
239
+ for want in user.wantlist.list():
240
+ print(want.basic_information.title)
241
+ ```
242
+
243
+ ### Error handling
244
+
245
+ ```python
246
+ from discogs_sdk import NotFoundError, RateLimitError, AuthenticationError
247
+
248
+ try:
249
+ release = client.releases.get(999999999)
250
+ _ = release.title
251
+ except NotFoundError:
252
+ print("Not found")
253
+ except RateLimitError as exc:
254
+ print(f"Rate limited, retry after {exc.retry_after}s")
255
+ except AuthenticationError:
256
+ print("Bad credentials")
257
+ ```
258
+
259
+ The full exception hierarchy:
260
+
261
+ ```
262
+ DiscogsError
263
+ +-- DiscogsConnectionError
264
+ +-- DiscogsAPIError
265
+ +-- AuthenticationError (401)
266
+ +-- ForbiddenError (403)
267
+ +-- NotFoundError (404)
268
+ +-- ValidationError (422)
269
+ +-- RateLimitError (429)
270
+ ```
271
+
272
+ ## API coverage
273
+
274
+ | Area | Resources |
275
+ |---|---|
276
+ | **Database** | Releases, Artists, Masters, Labels, Search |
277
+ | **Marketplace** | Listings, Orders, Order Messages, Fees |
278
+ | **Collection** | Folders, Releases, Instances, Custom Fields, Value |
279
+ | **User** | Profile, Identity, Wantlist, Contributions, Submissions, Inventory, Lists |
280
+ | **Inventory** | Exports (request/download CSV), Uploads (add/change/delete CSV) |
281
+ | **Lists** | Get list by ID, browse user lists |
282
+
283
+ ## Examples
284
+
285
+ The [`examples/`](examples/) directory has runnable scripts for every feature:
286
+
287
+ - [`quickstart.py`](examples/quickstart.py) — first requests, lazy loading, search
288
+ - [`authentication.py`](examples/authentication.py) — all auth modes including OAuth 1.0a
289
+ - [`database.py`](examples/database.py) — releases, artists, masters, labels, search
290
+ - [`marketplace.py`](examples/marketplace.py) — listings, orders, fees, inventory
291
+ - [`collection.py`](examples/collection.py) — folders, instances, fields, wantlist
292
+ - [`async_usage.py`](examples/async_usage.py) — async client with await and async for
293
+ - [`advanced.py`](examples/advanced.py) — error handling, caching, custom HTTP clients, exports
294
+
295
+ ## Configuration
296
+
297
+ | Parameter | Default | Description |
298
+ |---|---|---|
299
+ | `token` | `None` | Personal access token |
300
+ | `consumer_key` | `None` | OAuth consumer key |
301
+ | `consumer_secret` | `None` | OAuth consumer secret |
302
+ | `access_token` | `None` | OAuth access token |
303
+ | `access_token_secret` | `None` | OAuth access token secret |
304
+ | `base_url` | `https://api.discogs.com` | API base URL |
305
+ | `timeout` | `30.0` | Request timeout in seconds |
306
+ | `max_retries` | `3` | Max retries on 429/5xx/connection errors |
307
+ | `cache` | `False` | Enable HTTP caching (requires `discogs-sdk[cache]`) |
308
+ | `http_client` | `None` | Custom `httpx.Client` or `httpx.AsyncClient` |
309
+
310
+ Credentials are resolved in order: constructor args > environment variables.
311
+
312
+ ## Field naming
313
+
314
+ Model fields use clean Python names. Where the Discogs API uses inconsistent or cryptic keys, the SDK provides a readable alias while still accepting the raw API name during deserialization:
315
+
316
+ | API field | Python attribute | Models |
317
+ |---|---|---|
318
+ | `uri150` | `uri_150` | `Image` |
319
+ | `anv` | `name_variation` | `ArtistCredit` |
320
+ | `extraartists` | `extra_artists` | `Release`, `Track` |
321
+ | `namevariations` | `name_variations` | `Artist` |
322
+ | `qty` | `quantity` | `Format` |
323
+ | `catno` | `catalog_number` | `LabelCredit`, `Company`, `LabelRelease`, `SearchResult` |
324
+ | `sublabels` | `sub_labels` | `Label` |
325
+ | `curr_abbr` | `currency_code` | `OriginalPrice`, `User` |
326
+ | `curr_id` | `currency_id` | `OriginalPrice` |
327
+ | `created_ts` | `created_at` | `Export`, `Upload`, `List_` |
328
+ | `finished_ts` | `finished_at` | `Export`, `Upload` |
329
+ | `modified_ts` | `modified_at` | `List_` |
330
+
331
+ Both names work when constructing models manually (`Image(uri150="...")` and `Image(uri_150="...")` are equivalent). When accessing attributes, use the Python name: `image.uri_150`.
332
+
333
+ ## Contributing
334
+
335
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
336
+
337
+ ## License
338
+
339
+ discogs-sdk is licensed under the [Apache License 2.0](LICENSE.txt).
@@ -0,0 +1,310 @@
1
+ # discogs-sdk
2
+
3
+ <p align="center">
4
+ <a href="https://pypi.org/project/discogs-sdk/"><img src="https://img.shields.io/pypi/v/discogs-sdk.svg" alt="PyPI version"></a>
5
+ <a href="https://pypi.org/project/discogs-sdk/"><img src="https://img.shields.io/pypi/pyversions/discogs-sdk.svg" alt="Python versions"></a>
6
+ <a href="https://github.com/jmfontaine/discogs-sdk/actions"><img src="https://github.com/jmfontaine/discogs-sdk/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
7
+ <a href="https://pypi.org/project/discogs-sdk/"><img src="https://img.shields.io/pypi/dm/discogs-sdk.svg" alt="PyPI downloads"></a>
8
+ <a href="https://github.com/jmfontaine/discogs-sdk/blob/main/LICENSE.txt"><img src="https://img.shields.io/pypi/l/discogs-sdk.svg" alt="License"></a>
9
+ </p>
10
+
11
+ A typed Python client for the [Discogs API](https://www.discogs.com/developers). Sync and async, with Pydantic models, automatic pagination, and lazy resource loading.
12
+
13
+ ```python
14
+ from discogs_sdk import Discogs
15
+
16
+ # Reads DISCOGS_TOKEN from environment
17
+ with Discogs() as client:
18
+ release = client.releases.get(352665)
19
+ print(f"{release.title} ({release.year})")
20
+ # The Downward Spiral (1994)
21
+
22
+ for result in client.search(query="Nine Inch Nails", type="artist"):
23
+ print(result.title)
24
+ ```
25
+
26
+ ## Features
27
+
28
+ - **Sync and async** — `Discogs` for synchronous code, `AsyncDiscogs` for async/await
29
+ - **Typed models** — every response is a Pydantic v2 model with `extra="allow"` for undocumented fields
30
+ - **Lazy resources** — `.get()` returns a lightweight proxy; HTTP fires only when you access data
31
+ - **Auto-pagination** — iterate results with `for`/`async for`; pages are fetched on demand
32
+ - **Sub-resource chaining** — `client.releases.get(id).rating.get()` — intermediate accessors never trigger HTTP
33
+ - **Automatic retries** — retries on 429/5xx with exponential backoff and `Retry-After` support
34
+ - **Three auth modes** — personal token, consumer key/secret, or full OAuth 1.0a
35
+ - **Optional caching** — `cache=True` enables [hishel](https://github.com/karpetrosyan/hishel)-backed HTTP caching
36
+
37
+ ## Why discogs-sdk?
38
+
39
+ - **Typed models** — Pydantic v2 models give you autocomplete and IDE support instead of untyped dicts
40
+ - **Lazy loading that respects rate limits** — `.get()` returns a proxy; HTTP fires only when you access data, minimizing calls against the 60 req/min limit
41
+ - **Sync + async from one codebase** — async-first source with auto-generated sync client, identical APIs
42
+ - **Auto-pagination** — iterate results with `for`/`async for`; no manual page math
43
+ - **Modern stack** — built on httpx and Pydantic v2, not requests and raw dicts
44
+
45
+ ### How it compares
46
+
47
+ | Feature | discogs-sdk | python3-discogs-client | Raw API |
48
+ |---|---|---|---|
49
+ | Typed models (Pydantic) | Yes | No | No |
50
+ | Async support | Yes | No | Manual |
51
+ | Auto-pagination | Yes | Yes | Manual |
52
+ | Lazy loading | Yes | No | N/A |
53
+ | Rate limit handling | Automatic retry | Manual | Manual |
54
+ | OAuth 1.0a | Yes | Yes | Manual |
55
+ | HTTP caching | Built-in opt-in | No | Manual |
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install discogs-sdk
61
+ # or
62
+ uv add discogs-sdk
63
+ ```
64
+
65
+ With HTTP caching support:
66
+
67
+ ```bash
68
+ pip install discogs-sdk[cache]
69
+ # or
70
+ uv add discogs-sdk[cache]
71
+ ```
72
+
73
+ Requires Python 3.10+.
74
+
75
+ ## Quick start
76
+
77
+ ### Authentication
78
+
79
+ Set your personal access token from [your Discogs developer settings](https://www.discogs.com/settings/developers) as an environment variable:
80
+
81
+ ```bash
82
+ export DISCOGS_TOKEN="your-token-here"
83
+ ```
84
+
85
+ ```python
86
+ from discogs_sdk import Discogs
87
+
88
+ client = Discogs() # reads DISCOGS_TOKEN from environment
89
+ ```
90
+
91
+ You can also pass credentials explicitly:
92
+
93
+ ```python
94
+ client = Discogs(token="your-token-here")
95
+ ```
96
+
97
+ The SDK supports three auth modes: personal token, consumer key/secret, and OAuth 1.0a. All credentials resolve via constructor arg > environment variable (`DISCOGS_TOKEN`, `DISCOGS_CONSUMER_KEY`, etc.). See [`examples/authentication.py`](examples/authentication.py) for the full OAuth flow.
98
+
99
+ > [!TIP]
100
+ > Use a `.env` file with [python-dotenv](https://pypi.org/project/python-dotenv/) or
101
+ > [direnv](https://direnv.net/) to avoid exporting tokens manually in every shell.
102
+
103
+ > [!NOTE]
104
+ > Discogs enforces a 60 requests/minute rate limit. The SDK handles this automatically
105
+ > with exponential backoff and `Retry-After` support — no manual throttling needed.
106
+
107
+ ### Fetching resources
108
+
109
+ ```python
110
+ # Releases, artists, masters, labels
111
+ release = client.releases.get(352665)
112
+ print(release.title) # lazy — HTTP fires here → "The Downward Spiral"
113
+
114
+ artist = client.artists.get(3857)
115
+ print(artist.name) # "Nine Inch Nails"
116
+
117
+ master = client.masters.get(3719)
118
+ label = client.labels.get(647)
119
+ ```
120
+
121
+ ### Search
122
+
123
+ ```python
124
+ for result in client.search(query="Pretty Hate Machine", type="release", year="1989"):
125
+ print(f"[{result.type}] {result.title}")
126
+ # [release] Nine Inch Nails - Pretty Hate Machine
127
+ ```
128
+
129
+ ### Sub-resources
130
+
131
+ ```python
132
+ # Community rating
133
+ rating = client.releases.get(352665).rating.get()
134
+ print(f"Average: {rating.rating.average}") # Average: 4.49
135
+
136
+ # Artist releases with sorting
137
+ for rel in client.artists.get(3857).releases.list(sort="year", sort_order="desc"):
138
+ print(f"{rel.title} ({rel.year})")
139
+
140
+ # Master versions with filters
141
+ for v in client.masters.get(3719).versions.list(format="Vinyl", country="US"):
142
+ print(f"{v.title} [{v.format}]")
143
+ ```
144
+
145
+ ### Async usage
146
+
147
+ ```python
148
+ import asyncio
149
+ from discogs_sdk import AsyncDiscogs
150
+
151
+ async def main():
152
+ async with AsyncDiscogs() as client: # reads DISCOGS_TOKEN from environment
153
+ # Must await lazy resources in async mode
154
+ release = await client.releases.get(352665)
155
+ print(release.title)
156
+
157
+ # Async iteration for paginated results
158
+ async for result in client.search(query="Nine Inch Nails"):
159
+ print(result.title)
160
+
161
+ asyncio.run(main())
162
+ ```
163
+
164
+ ### Marketplace
165
+
166
+ ```python
167
+ # Listings
168
+ listing = client.marketplace.listings.get(123456789)
169
+ new = client.marketplace.listings.create(
170
+ release_id=352665, condition="Very Good Plus (VG+)", price=25.00,
171
+ )
172
+ client.marketplace.listings.update(new.id, price=22.50)
173
+ client.marketplace.listings.delete(new.id)
174
+
175
+ # Orders
176
+ for order in client.marketplace.orders.list(status="Payment Received"):
177
+ print(f"Order {order.id}: {order.status}")
178
+
179
+ # Fee lookup
180
+ fee = client.marketplace.fee.get(price=25.00, currency="USD")
181
+ ```
182
+
183
+ ### Collection
184
+
185
+ ```python
186
+ user = client.users.get("your_username")
187
+
188
+ # Folders
189
+ folders = user.collection.folders.list()
190
+ user.collection.folders.create(name="Industrial")
191
+
192
+ # Browse folder contents
193
+ for item in user.collection.folders.get(0).releases.list(sort="added"):
194
+ print(item.basic_information.title)
195
+
196
+ # Add a release
197
+ user.collection.folders.get(1).releases.create(release_id=352665)
198
+
199
+ # Deep chaining: folder -> release -> instance -> fields
200
+ user.collection.folders.get(1).releases.get(352665).instances.get(
201
+ 98765
202
+ ).fields.update(field_id=1, value="Signed copy")
203
+
204
+ # Collection value
205
+ value = user.collection.value.get()
206
+ print(f"Median: {value.median}, Maximum: {value.maximum}")
207
+
208
+ # Wantlist
209
+ user.wantlist.create(release_id=352665, notes="Original pressing", rating=4)
210
+ for want in user.wantlist.list():
211
+ print(want.basic_information.title)
212
+ ```
213
+
214
+ ### Error handling
215
+
216
+ ```python
217
+ from discogs_sdk import NotFoundError, RateLimitError, AuthenticationError
218
+
219
+ try:
220
+ release = client.releases.get(999999999)
221
+ _ = release.title
222
+ except NotFoundError:
223
+ print("Not found")
224
+ except RateLimitError as exc:
225
+ print(f"Rate limited, retry after {exc.retry_after}s")
226
+ except AuthenticationError:
227
+ print("Bad credentials")
228
+ ```
229
+
230
+ The full exception hierarchy:
231
+
232
+ ```
233
+ DiscogsError
234
+ +-- DiscogsConnectionError
235
+ +-- DiscogsAPIError
236
+ +-- AuthenticationError (401)
237
+ +-- ForbiddenError (403)
238
+ +-- NotFoundError (404)
239
+ +-- ValidationError (422)
240
+ +-- RateLimitError (429)
241
+ ```
242
+
243
+ ## API coverage
244
+
245
+ | Area | Resources |
246
+ |---|---|
247
+ | **Database** | Releases, Artists, Masters, Labels, Search |
248
+ | **Marketplace** | Listings, Orders, Order Messages, Fees |
249
+ | **Collection** | Folders, Releases, Instances, Custom Fields, Value |
250
+ | **User** | Profile, Identity, Wantlist, Contributions, Submissions, Inventory, Lists |
251
+ | **Inventory** | Exports (request/download CSV), Uploads (add/change/delete CSV) |
252
+ | **Lists** | Get list by ID, browse user lists |
253
+
254
+ ## Examples
255
+
256
+ The [`examples/`](examples/) directory has runnable scripts for every feature:
257
+
258
+ - [`quickstart.py`](examples/quickstart.py) — first requests, lazy loading, search
259
+ - [`authentication.py`](examples/authentication.py) — all auth modes including OAuth 1.0a
260
+ - [`database.py`](examples/database.py) — releases, artists, masters, labels, search
261
+ - [`marketplace.py`](examples/marketplace.py) — listings, orders, fees, inventory
262
+ - [`collection.py`](examples/collection.py) — folders, instances, fields, wantlist
263
+ - [`async_usage.py`](examples/async_usage.py) — async client with await and async for
264
+ - [`advanced.py`](examples/advanced.py) — error handling, caching, custom HTTP clients, exports
265
+
266
+ ## Configuration
267
+
268
+ | Parameter | Default | Description |
269
+ |---|---|---|
270
+ | `token` | `None` | Personal access token |
271
+ | `consumer_key` | `None` | OAuth consumer key |
272
+ | `consumer_secret` | `None` | OAuth consumer secret |
273
+ | `access_token` | `None` | OAuth access token |
274
+ | `access_token_secret` | `None` | OAuth access token secret |
275
+ | `base_url` | `https://api.discogs.com` | API base URL |
276
+ | `timeout` | `30.0` | Request timeout in seconds |
277
+ | `max_retries` | `3` | Max retries on 429/5xx/connection errors |
278
+ | `cache` | `False` | Enable HTTP caching (requires `discogs-sdk[cache]`) |
279
+ | `http_client` | `None` | Custom `httpx.Client` or `httpx.AsyncClient` |
280
+
281
+ Credentials are resolved in order: constructor args > environment variables.
282
+
283
+ ## Field naming
284
+
285
+ Model fields use clean Python names. Where the Discogs API uses inconsistent or cryptic keys, the SDK provides a readable alias while still accepting the raw API name during deserialization:
286
+
287
+ | API field | Python attribute | Models |
288
+ |---|---|---|
289
+ | `uri150` | `uri_150` | `Image` |
290
+ | `anv` | `name_variation` | `ArtistCredit` |
291
+ | `extraartists` | `extra_artists` | `Release`, `Track` |
292
+ | `namevariations` | `name_variations` | `Artist` |
293
+ | `qty` | `quantity` | `Format` |
294
+ | `catno` | `catalog_number` | `LabelCredit`, `Company`, `LabelRelease`, `SearchResult` |
295
+ | `sublabels` | `sub_labels` | `Label` |
296
+ | `curr_abbr` | `currency_code` | `OriginalPrice`, `User` |
297
+ | `curr_id` | `currency_id` | `OriginalPrice` |
298
+ | `created_ts` | `created_at` | `Export`, `Upload`, `List_` |
299
+ | `finished_ts` | `finished_at` | `Export`, `Upload` |
300
+ | `modified_ts` | `modified_at` | `List_` |
301
+
302
+ Both names work when constructing models manually (`Image(uri150="...")` and `Image(uri_150="...")` are equivalent). When accessing attributes, use the Python name: `image.uri_150`.
303
+
304
+ ## Contributing
305
+
306
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
307
+
308
+ ## License
309
+
310
+ discogs-sdk is licensed under the [Apache License 2.0](LICENSE.txt).