discogskit 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.
- discogskit-0.1.0/PKG-INFO +353 -0
- discogskit-0.1.0/README.md +325 -0
- discogskit-0.1.0/pyproject.toml +79 -0
- discogskit-0.1.0/src/discogskit/__init__.py +0 -0
- discogskit-0.1.0/src/discogskit/_console.py +20 -0
- discogskit-0.1.0/src/discogskit/cli.py +403 -0
- discogskit-0.1.0/src/discogskit/decompress.py +52 -0
- discogskit-0.1.0/src/discogskit/entities/__init__.py +77 -0
- discogskit-0.1.0/src/discogskit/entities/_split.py +53 -0
- discogskit-0.1.0/src/discogskit/entities/artists.py +261 -0
- discogskit-0.1.0/src/discogskit/entities/labels.py +235 -0
- discogskit-0.1.0/src/discogskit/entities/masters.py +287 -0
- discogskit-0.1.0/src/discogskit/entities/releases.py +693 -0
- discogskit-0.1.0/src/discogskit/pipeline.py +367 -0
- discogskit-0.1.0/src/discogskit/py.typed +0 -0
- discogskit-0.1.0/src/discogskit/writers/__init__.py +60 -0
- discogskit-0.1.0/src/discogskit/writers/_ipc.py +11 -0
- discogskit-0.1.0/src/discogskit/writers/jsonl.py +115 -0
- discogskit-0.1.0/src/discogskit/writers/parquet.py +105 -0
- discogskit-0.1.0/src/discogskit/writers/postgresql.py +470 -0
- discogskit-0.1.0/src/discogskit/writers/sqlite.py +247 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: discogskit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Discogs Data Dumps Toolkit
|
|
5
|
+
Keywords: cli,discogs,discogs-dump,music-database
|
|
6
|
+
Author: Jean-Marc Fontaine
|
|
7
|
+
Author-email: Jean-Marc Fontaine <jm@jmfontaine.net>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
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: Topic :: Database
|
|
19
|
+
Requires-Dist: adbc-driver-postgresql>=1.10.0
|
|
20
|
+
Requires-Dist: lxml>=6.0.2
|
|
21
|
+
Requires-Dist: psycopg>=3.3.3
|
|
22
|
+
Requires-Dist: pyarrow>=23.0.1
|
|
23
|
+
Requires-Dist: rapidgzip>=0.16.0
|
|
24
|
+
Requires-Dist: rich>=14.3.3
|
|
25
|
+
Requires-Dist: typer>=0.24.1
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# discogskit
|
|
30
|
+
|
|
31
|
+
A fast tool for converting and loading [Discogs data dumps](https://www.discogs.com/data/) into Parquet, JSONL, SQLite,
|
|
32
|
+
and PostgreSQL.
|
|
33
|
+
|
|
34
|
+
## Why discogskit?
|
|
35
|
+
|
|
36
|
+
- **Fast.** Parallel parsing and writing squeeze maximum performance out of your machine. See [Benchmarks](#benchmarks)
|
|
37
|
+
for numbers.
|
|
38
|
+
- **Easy to use.** A single command does the job. No multi-step workflows, no manual schema setup.
|
|
39
|
+
- **Flexible outputs.** Convert to Parquet or JSONL for quick analysis without standing up a database, or load directly
|
|
40
|
+
into SQLite or PostgreSQL.
|
|
41
|
+
- **Reliable.** Comprehensive unit and integration tests run against every release.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
Requires Python 3.10+.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pipx install discogskit
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Install locally
|
|
55
|
+
uv tool install discogskit
|
|
56
|
+
|
|
57
|
+
# Run without installing
|
|
58
|
+
uvx discogskit
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
|
|
65
|
+
Usage: discogskit [OPTIONS] COMMAND [ARGS]...
|
|
66
|
+
|
|
67
|
+
discogskit: Discogs Data Dumps Toolkit
|
|
68
|
+
|
|
69
|
+
╭─ Options ────────────────────────────────────────────────────────────────────────────╮
|
|
70
|
+
│ --version Show version and exit. │
|
|
71
|
+
│ --install-completion Install completion for the current shell. │
|
|
72
|
+
│ --show-completion Show completion for the current shell, to copy it or │
|
|
73
|
+
│ customize the installation. │
|
|
74
|
+
│ --help Show this message and exit. │
|
|
75
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
76
|
+
╭─ Commands ───────────────────────────────────────────────────────────────────────────╮
|
|
77
|
+
│ convert Convert Discogs XML dumps into flat files (Parquet or JSONL). │
|
|
78
|
+
│ load Load Discogs XML dumps into a database. │
|
|
79
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### discogskit convert
|
|
84
|
+
|
|
85
|
+
Convert Discogs XML dumps into flat files.
|
|
86
|
+
|
|
87
|
+
| Option | Values |
|
|
88
|
+
|-----------------|-------------------------------|
|
|
89
|
+
| Output formats | `parquet`, `jsonl` |
|
|
90
|
+
| Compression (Parquet) | `zstd` (default), `snappy`, `gzip`, `none` |
|
|
91
|
+
| Compression (JSONL) | `gzip`, `bzip2`, `none` (default) |
|
|
92
|
+
|
|
93
|
+
<details>
|
|
94
|
+
<summary>Full command help</summary>
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
|
|
98
|
+
Usage: discogskit convert [OPTIONS] PATHS...
|
|
99
|
+
|
|
100
|
+
Convert Discogs XML dumps into flat files (Parquet or JSONL).
|
|
101
|
+
|
|
102
|
+
╭─ Arguments ──────────────────────────────────────────────────────────────────────────╮
|
|
103
|
+
│ * paths PATHS... One or more .xml.gz files or directories containing them │
|
|
104
|
+
│ [required] │
|
|
105
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
106
|
+
╭─ Options ────────────────────────────────────────────────────────────────────────────╮
|
|
107
|
+
│ --format -f TEXT Output format: parquet or jsonl │
|
|
108
|
+
│ [default: parquet] │
|
|
109
|
+
│ --output PATH Output directory [default: .] │
|
|
110
|
+
│ --compression TEXT Compression codec. Parquet: gzip, │
|
|
111
|
+
│ snappy, zstd (default), none. JSONL: │
|
|
112
|
+
│ bzip2, gzip, none (default). │
|
|
113
|
+
│ --parse-workers INTEGER Number of parallel parse workers │
|
|
114
|
+
│ [default: 4] │
|
|
115
|
+
│ --chunk-mb INTEGER Split XML into chunks of roughly │
|
|
116
|
+
│ this size (MB) │
|
|
117
|
+
│ [default: 256] │
|
|
118
|
+
│ --write-queue INTEGER Max chunks buffered in memory before │
|
|
119
|
+
│ writes must catch up │
|
|
120
|
+
│ [default: 2] │
|
|
121
|
+
│ --keep-xml --no-keep-xml Keep decompressed XML file after │
|
|
122
|
+
│ converting │
|
|
123
|
+
│ [default: no-keep-xml] │
|
|
124
|
+
│ --overwrite --no-overwrite Overwrite existing output files │
|
|
125
|
+
│ [default: no-overwrite] │
|
|
126
|
+
│ --profile --no-profile Print detailed per-table timing │
|
|
127
|
+
│ breakdown after convert │
|
|
128
|
+
│ [default: no-profile] │
|
|
129
|
+
│ --progress --no-progress Show a progress bar instead of │
|
|
130
|
+
│ per-chunk output │
|
|
131
|
+
│ [default: progress] │
|
|
132
|
+
│ --strict --no-strict Warn about unhandled XML elements │
|
|
133
|
+
│ during parsing │
|
|
134
|
+
│ [default: no-strict] │
|
|
135
|
+
│ --help Show this message and exit. │
|
|
136
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
</details>
|
|
141
|
+
|
|
142
|
+
#### Examples
|
|
143
|
+
|
|
144
|
+
```shell
|
|
145
|
+
# Convert releases to Parquet (default)
|
|
146
|
+
discogskit convert --format parquet discogs_20260301_releases.xml.gz
|
|
147
|
+
|
|
148
|
+
# Convert to JSONL with gzip compression
|
|
149
|
+
discogskit convert --format jsonl --compression gzip discogs_20260301_artists.xml.gz
|
|
150
|
+
|
|
151
|
+
# Convert all dump files in the current directory
|
|
152
|
+
discogskit convert --format parquet .
|
|
153
|
+
|
|
154
|
+
# Keep decompressed XML after converting
|
|
155
|
+
discogskit convert --format parquet --keep-xml discogs_20260301_releases.xml.gz
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### discogskit load
|
|
159
|
+
|
|
160
|
+
Load Discogs XML dumps into a database.
|
|
161
|
+
|
|
162
|
+
| Database | Versions |
|
|
163
|
+
|----------|----------|
|
|
164
|
+
| SQLite | 3.x |
|
|
165
|
+
| PostgreSQL | 14+ |
|
|
166
|
+
|
|
167
|
+
<details>
|
|
168
|
+
<summary>Full command help</summary>
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
|
|
172
|
+
Usage: discogskit load [OPTIONS] PATHS...
|
|
173
|
+
|
|
174
|
+
Load Discogs XML dumps into a database.
|
|
175
|
+
|
|
176
|
+
╭─ Arguments ──────────────────────────────────────────────────────────────────────────╮
|
|
177
|
+
│ * paths PATHS... One or more .xml.gz files or directories containing them │
|
|
178
|
+
│ [required] │
|
|
179
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
180
|
+
╭─ Options ────────────────────────────────────────────────────────────────────────────╮
|
|
181
|
+
│ --dsn TEXT Database DSN (e.g., │
|
|
182
|
+
│ postgresql://localhost/postgres) or │
|
|
183
|
+
│ path to SQLite file │
|
|
184
|
+
│ [env var: DATABASE_URL] │
|
|
185
|
+
│ [default: │
|
|
186
|
+
│ postgresql://localhost/discogskit] │
|
|
187
|
+
│ --parse-workers INTEGER Number of parallel parse workers │
|
|
188
|
+
│ [default: 4] │
|
|
189
|
+
│ --write-workers INTEGER Number of parallel database write │
|
|
190
|
+
│ workers │
|
|
191
|
+
│ [default: 1] │
|
|
192
|
+
│ --index-workers INTEGER Number of parallel index creation │
|
|
193
|
+
│ workers │
|
|
194
|
+
│ [default: 2] │
|
|
195
|
+
│ --chunk-mb INTEGER Split XML into chunks of roughly this │
|
|
196
|
+
│ size (MB) │
|
|
197
|
+
│ [default: 256] │
|
|
198
|
+
│ --write-queue INTEGER Max chunks buffered in memory before │
|
|
199
|
+
│ writes must catch up │
|
|
200
|
+
│ [default: 2] │
|
|
201
|
+
│ --keep-xml --no-keep-xml Keep decompressed XML file after │
|
|
202
|
+
│ loading │
|
|
203
|
+
│ [default: no-keep-xml] │
|
|
204
|
+
│ --overwrite --no-overwrite Overwrite existing tables in the │
|
|
205
|
+
│ database │
|
|
206
|
+
│ [default: no-overwrite] │
|
|
207
|
+
│ --profile --no-profile Print detailed per-table timing │
|
|
208
|
+
│ breakdown after load │
|
|
209
|
+
│ [default: no-profile] │
|
|
210
|
+
│ --progress --no-progress Show a progress bar instead of │
|
|
211
|
+
│ per-chunk output │
|
|
212
|
+
│ [default: progress] │
|
|
213
|
+
│ --strict --no-strict Warn about unhandled XML elements │
|
|
214
|
+
│ during parsing │
|
|
215
|
+
│ [default: no-strict] │
|
|
216
|
+
│ --help Show this message and exit. │
|
|
217
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
218
|
+
╭─ PostgreSQL ─────────────────────────────────────────────────────────────────────────╮
|
|
219
|
+
│ --pg-unlogged --no-pg-unlogged Skip WAL for faster writes (tables stay │
|
|
220
|
+
│ unlogged; data lost on crash) │
|
|
221
|
+
│ [default: no-pg-unlogged] │
|
|
222
|
+
│ --pg-tune --no-pg-tune Temporarily apply settings optimized for bulk │
|
|
223
|
+
│ loading │
|
|
224
|
+
│ [default: no-pg-tune] │
|
|
225
|
+
│ --pg-fk --no-pg-fk Add foreign key constraints after load │
|
|
226
|
+
│ [default: no-pg-fk] │
|
|
227
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
</details>
|
|
232
|
+
|
|
233
|
+
#### Examples
|
|
234
|
+
|
|
235
|
+
```shell
|
|
236
|
+
# Load releases into PostgreSQL (default DSN: postgresql://localhost/discogskit)
|
|
237
|
+
discogskit load discogs_20260301_releases.xml.gz
|
|
238
|
+
|
|
239
|
+
# Load into a specific PostgreSQL database
|
|
240
|
+
discogskit load --dsn "postgresql://user:pass@localhost/discogs" discogs_20260301_releases.xml.gz
|
|
241
|
+
|
|
242
|
+
# Load into SQLite
|
|
243
|
+
discogskit load --dsn discogs.db discogs_20260301_releases.xml.gz
|
|
244
|
+
|
|
245
|
+
# Load all dump files from a directory
|
|
246
|
+
discogskit load --dsn discogs.db .
|
|
247
|
+
|
|
248
|
+
# Use UNLOGGED tables for faster PostgreSQL writes (~2x speedup)
|
|
249
|
+
discogskit load --pg-unlogged discogs_20260301_releases.xml.gz
|
|
250
|
+
|
|
251
|
+
# Temporarily tune PostgreSQL for bulk loading
|
|
252
|
+
discogskit load --pg-tune discogs_20260301_releases.xml.gz
|
|
253
|
+
|
|
254
|
+
# Add foreign key constraints after load
|
|
255
|
+
discogskit load --pg-fk discogs_20260301_releases.xml.gz
|
|
256
|
+
|
|
257
|
+
# Use multiple write workers for parallel database inserts
|
|
258
|
+
discogskit load --write-workers 4 discogs_20260301_releases.xml.gz
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Benchmarks
|
|
262
|
+
|
|
263
|
+
Full load of the `20260301` data dump (artists, labels, masters, releases) into PostgreSQL 18
|
|
264
|
+
on a 24 GB Apple MacBook Air M3.
|
|
265
|
+
|
|
266
|
+
| | discogs-xml2db Python | discogs-xml2db .NET | discogskit | discogskit `--pg-unlogged` |
|
|
267
|
+
|---|---:|---:|---:|---:|
|
|
268
|
+
| Parse + load | 0:59:11 | 1:01:08 | 18:55 | 9:25 |
|
|
269
|
+
| Indexes | 0:43:24 | 0:43:24 | 14:53 | 1:56 |
|
|
270
|
+
| **Total** | **1:42:35** | **1:44:32** | **33:49** | **11:22** |
|
|
271
|
+
| **Speedup** | **baseline** | **0.98x** | **3.0x** | **9.0x** |
|
|
272
|
+
|
|
273
|
+
<details>
|
|
274
|
+
<summary>Commands and detailed output</summary>
|
|
275
|
+
|
|
276
|
+
**discogs-xml2db Python**
|
|
277
|
+
|
|
278
|
+
```shell
|
|
279
|
+
python3 run.py --apicounts --export artist --export label --export master --export release --output ./csv-dir [path]
|
|
280
|
+
python3 postgresql/psql.py < postgresql/sql/CreateTables.sql
|
|
281
|
+
python3 postgresql/importcsv.py ./csv-dir/*
|
|
282
|
+
python3 postgresql/psql.py < postgresql/sql/CreatePrimaryKeys.sql
|
|
283
|
+
python3 postgresql/psql.py < postgresql/sql/CreateFKConstraints.sql
|
|
284
|
+
python3 postgresql/psql.py < postgresql/sql/CreateIndexes.sql
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
| Step | Time |
|
|
288
|
+
|---|---:|
|
|
289
|
+
| Export to CSV | 0:41:14 |
|
|
290
|
+
| Table creation | 0:00:01 |
|
|
291
|
+
| Data import | 0:17:56 |
|
|
292
|
+
| Primary keys | 0:19:21 |
|
|
293
|
+
| Foreign keys | 0:02:12 |
|
|
294
|
+
| Indexes | 0:21:51 |
|
|
295
|
+
| **Total** | **1:42:35** |
|
|
296
|
+
|
|
297
|
+
**discogs-xml2db .NET**
|
|
298
|
+
|
|
299
|
+
```shell
|
|
300
|
+
discogs [paths]
|
|
301
|
+
python3 postgresql/psql.py < postgresql/sql/CreateTables.sql
|
|
302
|
+
python3 postgresql/importcsv.py ./csv-dir/*
|
|
303
|
+
python3 postgresql/psql.py < postgresql/sql/CreatePrimaryKeys.sql
|
|
304
|
+
python3 postgresql/psql.py < postgresql/sql/CreateFKConstraints.sql
|
|
305
|
+
python3 postgresql/psql.py < postgresql/sql/CreateIndexes.sql
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
| Step | Time |
|
|
309
|
+
|---|---:|
|
|
310
|
+
| Export to CSV | 0:43:11 |
|
|
311
|
+
| Table creation | 0:00:01 |
|
|
312
|
+
| Data import | 0:17:56 |
|
|
313
|
+
| Primary keys | 0:19:21 |
|
|
314
|
+
| Foreign keys | 0:02:12 |
|
|
315
|
+
| Indexes | 0:21:51 |
|
|
316
|
+
| **Total** | **1:44:32** |
|
|
317
|
+
|
|
318
|
+
**discogskit**
|
|
319
|
+
|
|
320
|
+
```shell
|
|
321
|
+
discogskit load --dsn postgresql://localhost:5432/discogskit --chunk-mb 256 \
|
|
322
|
+
--parse-workers 6 --write-workers 3 --index-workers 6 [path]
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
| Entity | Records | Parse + load | Indexes | Total |
|
|
326
|
+
|---|---:|---:|---:|---:|
|
|
327
|
+
| Artists | 9,957,079 | 40.40s | 14.77s | 55.15s |
|
|
328
|
+
| Labels | 2,349,729 | 9.27s | 0.67s | 9.95s |
|
|
329
|
+
| Masters | 2,530,697 | 34.45s | 19.94s | 54.36s |
|
|
330
|
+
| Releases | 18,952,204 | 1,051.37s | 857.89s | 1,909.24s |
|
|
331
|
+
| **Total** | **33,789,709** | **1,135.49s** | **893.27s** | **2,028.70s** |
|
|
332
|
+
|
|
333
|
+
**discogskit `--pg-unlogged`**
|
|
334
|
+
|
|
335
|
+
```shell
|
|
336
|
+
discogskit load --dsn postgresql://localhost:5432/discogskit --chunk-mb 256 --pg-unlogged \
|
|
337
|
+
--parse-workers 6 --write-workers 3 --index-workers 6 [path]
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
| Entity | Records | Parse + load | Indexes | Total |
|
|
341
|
+
|---|---:|---:|---:|---:|
|
|
342
|
+
| Artists | 9,957,079 | 27.73s | 2.52s | 30.22s |
|
|
343
|
+
| Labels | 2,349,729 | 8.84s | 0.51s | 9.34s |
|
|
344
|
+
| Masters | 2,530,697 | 23.38s | 1.93s | 25.29s |
|
|
345
|
+
| Releases | 18,952,204 | 505.45s | 111.46s | 616.86s |
|
|
346
|
+
| **Total** | **33,789,709** | **565.40s** | **116.42s** | **681.71s** |
|
|
347
|
+
|
|
348
|
+
</details>
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
## License
|
|
352
|
+
|
|
353
|
+
discogskit is licensed under the [Apache License 2.0](LICENSE.txt).
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# discogskit
|
|
2
|
+
|
|
3
|
+
A fast tool for converting and loading [Discogs data dumps](https://www.discogs.com/data/) into Parquet, JSONL, SQLite,
|
|
4
|
+
and PostgreSQL.
|
|
5
|
+
|
|
6
|
+
## Why discogskit?
|
|
7
|
+
|
|
8
|
+
- **Fast.** Parallel parsing and writing squeeze maximum performance out of your machine. See [Benchmarks](#benchmarks)
|
|
9
|
+
for numbers.
|
|
10
|
+
- **Easy to use.** A single command does the job. No multi-step workflows, no manual schema setup.
|
|
11
|
+
- **Flexible outputs.** Convert to Parquet or JSONL for quick analysis without standing up a database, or load directly
|
|
12
|
+
into SQLite or PostgreSQL.
|
|
13
|
+
- **Reliable.** Comprehensive unit and integration tests run against every release.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Requires Python 3.10+.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pipx install discogskit
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Install locally
|
|
27
|
+
uv tool install discogskit
|
|
28
|
+
|
|
29
|
+
# Run without installing
|
|
30
|
+
uvx discogskit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
|
|
37
|
+
Usage: discogskit [OPTIONS] COMMAND [ARGS]...
|
|
38
|
+
|
|
39
|
+
discogskit: Discogs Data Dumps Toolkit
|
|
40
|
+
|
|
41
|
+
╭─ Options ────────────────────────────────────────────────────────────────────────────╮
|
|
42
|
+
│ --version Show version and exit. │
|
|
43
|
+
│ --install-completion Install completion for the current shell. │
|
|
44
|
+
│ --show-completion Show completion for the current shell, to copy it or │
|
|
45
|
+
│ customize the installation. │
|
|
46
|
+
│ --help Show this message and exit. │
|
|
47
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
48
|
+
╭─ Commands ───────────────────────────────────────────────────────────────────────────╮
|
|
49
|
+
│ convert Convert Discogs XML dumps into flat files (Parquet or JSONL). │
|
|
50
|
+
│ load Load Discogs XML dumps into a database. │
|
|
51
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### discogskit convert
|
|
56
|
+
|
|
57
|
+
Convert Discogs XML dumps into flat files.
|
|
58
|
+
|
|
59
|
+
| Option | Values |
|
|
60
|
+
|-----------------|-------------------------------|
|
|
61
|
+
| Output formats | `parquet`, `jsonl` |
|
|
62
|
+
| Compression (Parquet) | `zstd` (default), `snappy`, `gzip`, `none` |
|
|
63
|
+
| Compression (JSONL) | `gzip`, `bzip2`, `none` (default) |
|
|
64
|
+
|
|
65
|
+
<details>
|
|
66
|
+
<summary>Full command help</summary>
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
|
|
70
|
+
Usage: discogskit convert [OPTIONS] PATHS...
|
|
71
|
+
|
|
72
|
+
Convert Discogs XML dumps into flat files (Parquet or JSONL).
|
|
73
|
+
|
|
74
|
+
╭─ Arguments ──────────────────────────────────────────────────────────────────────────╮
|
|
75
|
+
│ * paths PATHS... One or more .xml.gz files or directories containing them │
|
|
76
|
+
│ [required] │
|
|
77
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
78
|
+
╭─ Options ────────────────────────────────────────────────────────────────────────────╮
|
|
79
|
+
│ --format -f TEXT Output format: parquet or jsonl │
|
|
80
|
+
│ [default: parquet] │
|
|
81
|
+
│ --output PATH Output directory [default: .] │
|
|
82
|
+
│ --compression TEXT Compression codec. Parquet: gzip, │
|
|
83
|
+
│ snappy, zstd (default), none. JSONL: │
|
|
84
|
+
│ bzip2, gzip, none (default). │
|
|
85
|
+
│ --parse-workers INTEGER Number of parallel parse workers │
|
|
86
|
+
│ [default: 4] │
|
|
87
|
+
│ --chunk-mb INTEGER Split XML into chunks of roughly │
|
|
88
|
+
│ this size (MB) │
|
|
89
|
+
│ [default: 256] │
|
|
90
|
+
│ --write-queue INTEGER Max chunks buffered in memory before │
|
|
91
|
+
│ writes must catch up │
|
|
92
|
+
│ [default: 2] │
|
|
93
|
+
│ --keep-xml --no-keep-xml Keep decompressed XML file after │
|
|
94
|
+
│ converting │
|
|
95
|
+
│ [default: no-keep-xml] │
|
|
96
|
+
│ --overwrite --no-overwrite Overwrite existing output files │
|
|
97
|
+
│ [default: no-overwrite] │
|
|
98
|
+
│ --profile --no-profile Print detailed per-table timing │
|
|
99
|
+
│ breakdown after convert │
|
|
100
|
+
│ [default: no-profile] │
|
|
101
|
+
│ --progress --no-progress Show a progress bar instead of │
|
|
102
|
+
│ per-chunk output │
|
|
103
|
+
│ [default: progress] │
|
|
104
|
+
│ --strict --no-strict Warn about unhandled XML elements │
|
|
105
|
+
│ during parsing │
|
|
106
|
+
│ [default: no-strict] │
|
|
107
|
+
│ --help Show this message and exit. │
|
|
108
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
</details>
|
|
113
|
+
|
|
114
|
+
#### Examples
|
|
115
|
+
|
|
116
|
+
```shell
|
|
117
|
+
# Convert releases to Parquet (default)
|
|
118
|
+
discogskit convert --format parquet discogs_20260301_releases.xml.gz
|
|
119
|
+
|
|
120
|
+
# Convert to JSONL with gzip compression
|
|
121
|
+
discogskit convert --format jsonl --compression gzip discogs_20260301_artists.xml.gz
|
|
122
|
+
|
|
123
|
+
# Convert all dump files in the current directory
|
|
124
|
+
discogskit convert --format parquet .
|
|
125
|
+
|
|
126
|
+
# Keep decompressed XML after converting
|
|
127
|
+
discogskit convert --format parquet --keep-xml discogs_20260301_releases.xml.gz
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### discogskit load
|
|
131
|
+
|
|
132
|
+
Load Discogs XML dumps into a database.
|
|
133
|
+
|
|
134
|
+
| Database | Versions |
|
|
135
|
+
|----------|----------|
|
|
136
|
+
| SQLite | 3.x |
|
|
137
|
+
| PostgreSQL | 14+ |
|
|
138
|
+
|
|
139
|
+
<details>
|
|
140
|
+
<summary>Full command help</summary>
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
|
|
144
|
+
Usage: discogskit load [OPTIONS] PATHS...
|
|
145
|
+
|
|
146
|
+
Load Discogs XML dumps into a database.
|
|
147
|
+
|
|
148
|
+
╭─ Arguments ──────────────────────────────────────────────────────────────────────────╮
|
|
149
|
+
│ * paths PATHS... One or more .xml.gz files or directories containing them │
|
|
150
|
+
│ [required] │
|
|
151
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
152
|
+
╭─ Options ────────────────────────────────────────────────────────────────────────────╮
|
|
153
|
+
│ --dsn TEXT Database DSN (e.g., │
|
|
154
|
+
│ postgresql://localhost/postgres) or │
|
|
155
|
+
│ path to SQLite file │
|
|
156
|
+
│ [env var: DATABASE_URL] │
|
|
157
|
+
│ [default: │
|
|
158
|
+
│ postgresql://localhost/discogskit] │
|
|
159
|
+
│ --parse-workers INTEGER Number of parallel parse workers │
|
|
160
|
+
│ [default: 4] │
|
|
161
|
+
│ --write-workers INTEGER Number of parallel database write │
|
|
162
|
+
│ workers │
|
|
163
|
+
│ [default: 1] │
|
|
164
|
+
│ --index-workers INTEGER Number of parallel index creation │
|
|
165
|
+
│ workers │
|
|
166
|
+
│ [default: 2] │
|
|
167
|
+
│ --chunk-mb INTEGER Split XML into chunks of roughly this │
|
|
168
|
+
│ size (MB) │
|
|
169
|
+
│ [default: 256] │
|
|
170
|
+
│ --write-queue INTEGER Max chunks buffered in memory before │
|
|
171
|
+
│ writes must catch up │
|
|
172
|
+
│ [default: 2] │
|
|
173
|
+
│ --keep-xml --no-keep-xml Keep decompressed XML file after │
|
|
174
|
+
│ loading │
|
|
175
|
+
│ [default: no-keep-xml] │
|
|
176
|
+
│ --overwrite --no-overwrite Overwrite existing tables in the │
|
|
177
|
+
│ database │
|
|
178
|
+
│ [default: no-overwrite] │
|
|
179
|
+
│ --profile --no-profile Print detailed per-table timing │
|
|
180
|
+
│ breakdown after load │
|
|
181
|
+
│ [default: no-profile] │
|
|
182
|
+
│ --progress --no-progress Show a progress bar instead of │
|
|
183
|
+
│ per-chunk output │
|
|
184
|
+
│ [default: progress] │
|
|
185
|
+
│ --strict --no-strict Warn about unhandled XML elements │
|
|
186
|
+
│ during parsing │
|
|
187
|
+
│ [default: no-strict] │
|
|
188
|
+
│ --help Show this message and exit. │
|
|
189
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
190
|
+
╭─ PostgreSQL ─────────────────────────────────────────────────────────────────────────╮
|
|
191
|
+
│ --pg-unlogged --no-pg-unlogged Skip WAL for faster writes (tables stay │
|
|
192
|
+
│ unlogged; data lost on crash) │
|
|
193
|
+
│ [default: no-pg-unlogged] │
|
|
194
|
+
│ --pg-tune --no-pg-tune Temporarily apply settings optimized for bulk │
|
|
195
|
+
│ loading │
|
|
196
|
+
│ [default: no-pg-tune] │
|
|
197
|
+
│ --pg-fk --no-pg-fk Add foreign key constraints after load │
|
|
198
|
+
│ [default: no-pg-fk] │
|
|
199
|
+
╰──────────────────────────────────────────────────────────────────────────────────────╯
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
</details>
|
|
204
|
+
|
|
205
|
+
#### Examples
|
|
206
|
+
|
|
207
|
+
```shell
|
|
208
|
+
# Load releases into PostgreSQL (default DSN: postgresql://localhost/discogskit)
|
|
209
|
+
discogskit load discogs_20260301_releases.xml.gz
|
|
210
|
+
|
|
211
|
+
# Load into a specific PostgreSQL database
|
|
212
|
+
discogskit load --dsn "postgresql://user:pass@localhost/discogs" discogs_20260301_releases.xml.gz
|
|
213
|
+
|
|
214
|
+
# Load into SQLite
|
|
215
|
+
discogskit load --dsn discogs.db discogs_20260301_releases.xml.gz
|
|
216
|
+
|
|
217
|
+
# Load all dump files from a directory
|
|
218
|
+
discogskit load --dsn discogs.db .
|
|
219
|
+
|
|
220
|
+
# Use UNLOGGED tables for faster PostgreSQL writes (~2x speedup)
|
|
221
|
+
discogskit load --pg-unlogged discogs_20260301_releases.xml.gz
|
|
222
|
+
|
|
223
|
+
# Temporarily tune PostgreSQL for bulk loading
|
|
224
|
+
discogskit load --pg-tune discogs_20260301_releases.xml.gz
|
|
225
|
+
|
|
226
|
+
# Add foreign key constraints after load
|
|
227
|
+
discogskit load --pg-fk discogs_20260301_releases.xml.gz
|
|
228
|
+
|
|
229
|
+
# Use multiple write workers for parallel database inserts
|
|
230
|
+
discogskit load --write-workers 4 discogs_20260301_releases.xml.gz
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Benchmarks
|
|
234
|
+
|
|
235
|
+
Full load of the `20260301` data dump (artists, labels, masters, releases) into PostgreSQL 18
|
|
236
|
+
on a 24 GB Apple MacBook Air M3.
|
|
237
|
+
|
|
238
|
+
| | discogs-xml2db Python | discogs-xml2db .NET | discogskit | discogskit `--pg-unlogged` |
|
|
239
|
+
|---|---:|---:|---:|---:|
|
|
240
|
+
| Parse + load | 0:59:11 | 1:01:08 | 18:55 | 9:25 |
|
|
241
|
+
| Indexes | 0:43:24 | 0:43:24 | 14:53 | 1:56 |
|
|
242
|
+
| **Total** | **1:42:35** | **1:44:32** | **33:49** | **11:22** |
|
|
243
|
+
| **Speedup** | **baseline** | **0.98x** | **3.0x** | **9.0x** |
|
|
244
|
+
|
|
245
|
+
<details>
|
|
246
|
+
<summary>Commands and detailed output</summary>
|
|
247
|
+
|
|
248
|
+
**discogs-xml2db Python**
|
|
249
|
+
|
|
250
|
+
```shell
|
|
251
|
+
python3 run.py --apicounts --export artist --export label --export master --export release --output ./csv-dir [path]
|
|
252
|
+
python3 postgresql/psql.py < postgresql/sql/CreateTables.sql
|
|
253
|
+
python3 postgresql/importcsv.py ./csv-dir/*
|
|
254
|
+
python3 postgresql/psql.py < postgresql/sql/CreatePrimaryKeys.sql
|
|
255
|
+
python3 postgresql/psql.py < postgresql/sql/CreateFKConstraints.sql
|
|
256
|
+
python3 postgresql/psql.py < postgresql/sql/CreateIndexes.sql
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
| Step | Time |
|
|
260
|
+
|---|---:|
|
|
261
|
+
| Export to CSV | 0:41:14 |
|
|
262
|
+
| Table creation | 0:00:01 |
|
|
263
|
+
| Data import | 0:17:56 |
|
|
264
|
+
| Primary keys | 0:19:21 |
|
|
265
|
+
| Foreign keys | 0:02:12 |
|
|
266
|
+
| Indexes | 0:21:51 |
|
|
267
|
+
| **Total** | **1:42:35** |
|
|
268
|
+
|
|
269
|
+
**discogs-xml2db .NET**
|
|
270
|
+
|
|
271
|
+
```shell
|
|
272
|
+
discogs [paths]
|
|
273
|
+
python3 postgresql/psql.py < postgresql/sql/CreateTables.sql
|
|
274
|
+
python3 postgresql/importcsv.py ./csv-dir/*
|
|
275
|
+
python3 postgresql/psql.py < postgresql/sql/CreatePrimaryKeys.sql
|
|
276
|
+
python3 postgresql/psql.py < postgresql/sql/CreateFKConstraints.sql
|
|
277
|
+
python3 postgresql/psql.py < postgresql/sql/CreateIndexes.sql
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
| Step | Time |
|
|
281
|
+
|---|---:|
|
|
282
|
+
| Export to CSV | 0:43:11 |
|
|
283
|
+
| Table creation | 0:00:01 |
|
|
284
|
+
| Data import | 0:17:56 |
|
|
285
|
+
| Primary keys | 0:19:21 |
|
|
286
|
+
| Foreign keys | 0:02:12 |
|
|
287
|
+
| Indexes | 0:21:51 |
|
|
288
|
+
| **Total** | **1:44:32** |
|
|
289
|
+
|
|
290
|
+
**discogskit**
|
|
291
|
+
|
|
292
|
+
```shell
|
|
293
|
+
discogskit load --dsn postgresql://localhost:5432/discogskit --chunk-mb 256 \
|
|
294
|
+
--parse-workers 6 --write-workers 3 --index-workers 6 [path]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
| Entity | Records | Parse + load | Indexes | Total |
|
|
298
|
+
|---|---:|---:|---:|---:|
|
|
299
|
+
| Artists | 9,957,079 | 40.40s | 14.77s | 55.15s |
|
|
300
|
+
| Labels | 2,349,729 | 9.27s | 0.67s | 9.95s |
|
|
301
|
+
| Masters | 2,530,697 | 34.45s | 19.94s | 54.36s |
|
|
302
|
+
| Releases | 18,952,204 | 1,051.37s | 857.89s | 1,909.24s |
|
|
303
|
+
| **Total** | **33,789,709** | **1,135.49s** | **893.27s** | **2,028.70s** |
|
|
304
|
+
|
|
305
|
+
**discogskit `--pg-unlogged`**
|
|
306
|
+
|
|
307
|
+
```shell
|
|
308
|
+
discogskit load --dsn postgresql://localhost:5432/discogskit --chunk-mb 256 --pg-unlogged \
|
|
309
|
+
--parse-workers 6 --write-workers 3 --index-workers 6 [path]
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
| Entity | Records | Parse + load | Indexes | Total |
|
|
313
|
+
|---|---:|---:|---:|---:|
|
|
314
|
+
| Artists | 9,957,079 | 27.73s | 2.52s | 30.22s |
|
|
315
|
+
| Labels | 2,349,729 | 8.84s | 0.51s | 9.34s |
|
|
316
|
+
| Masters | 2,530,697 | 23.38s | 1.93s | 25.29s |
|
|
317
|
+
| Releases | 18,952,204 | 505.45s | 111.46s | 616.86s |
|
|
318
|
+
| **Total** | **33,789,709** | **565.40s** | **116.42s** | **681.71s** |
|
|
319
|
+
|
|
320
|
+
</details>
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
## License
|
|
324
|
+
|
|
325
|
+
discogskit is licensed under the [Apache License 2.0](LICENSE.txt).
|