feat: add endpoint to download all dataset URLs as a JSON Lines file - #412
feat: add endpoint to download all dataset URLs as a JSON Lines file#412candleindark wants to merge 1 commit into
Conversation
Add `GET /api/v2/dataset-urls/all`, which returns every dataset URL as a single JSON Lines (`.jsonl`) file, one JSON object per line, so a consumer can fetch one file instead of paging `GET /api/v2/dataset-urls` hundreds of times. Each line has the same shape as an element of the `dataset_urls` array of the paginated endpoint, so no internal database columns are exposed. A binary `return_metadata` flag, the counterpart of the tri-state `return_metadata` query parameter of the paginated endpoint, controls whether each line includes the `metadata` field populated by content. The response is streamed with `yield_per` to avoid serializing all records in memory at once, and metadata is eagerly loaded via `selectinload` when requested to avoid an N+1 query. Implements the aggregated-dump approach discussed in datalad#407. Co-Authored-By: Claude Code 2.1.219 / Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #412 +/- ##
==========================================
+ Coverage 98.82% 98.84% +0.02%
==========================================
Files 55 55
Lines 2630 2690 +60
==========================================
+ Hits 2599 2659 +60
Misses 31 31 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| "the URL, depending on the `return_metadata` flag.", | ||
| "content": { | ||
| "application/x-ndjson": { | ||
| "schema": {"type": "string", "format": "binary"} |
There was a problem hiding this comment.
format: binary is correct as long as we are on OpenAPI 3.0. flask-openapi3 hardcodes openapi_version = "3.0.3", and in
3.0 {"type": "string", "format": "binary"} is the idiomatic way to declare an opaque file payload, which is what makes Swagger UI offer a download rather than trying to rende
r a schema. It would need to become contentMediaType if we ever move to 3.1. I will add a comment saying so.
Nevertheless, this will most likely be replaced by the new design/implementation.
yarikoptic
left a comment
There was a problem hiding this comment.
Let's implement celery task and thus caching right away, because if this is implemented and deployed, could put too much stress on the server. When caching implemented, just make API call return a redirect to target file, and ideally collect and fill out relevant to cached data information such as ETag (md5sum IIRC would suffice) and potentially other fields (like expiration date and when was generated etc).
| stream_with_context(gen_lines()), | ||
| mimetype="application/x-ndjson", | ||
| ) | ||
| resp.headers["Content-Disposition"] = "attachment; filename=dataset-urls.jsonl" |
There was a problem hiding this comment.
might be worth right away to compress it but better be done along with caching so we do not overburden the server. And then see what mime type etc to provide back for that .jsonl.gz.
For when caching implemented, make sure for atomic rename of the (re)generated file into the place where served from.
Agreed on doing the Celery task and caching up front, and on having the API return a redirect. Implementing that raises an infrastructure question since it depends on what the servers can host. The problem: the worker that would generate the dump runs on the primary, but the public-facing instance is the read-only replica. The two share no filesystem, so there is currently nowhere for the worker to write a file that the public instance can serve. What I would like: a location the primary can upload/write the generated dump to, and that is served publicly. |
|
Note: The follow query shows that, before gzip, jsonl file is about 9.6 MB without metadata and 70 MB with metadata. |
Toward #407. Adds a single-file bulk download of all dataset URLs so a consumer can grab one file instead of paging
GET /api/v2/dataset-urlshundreds of times.New endpoint:
GET /api/v2/dataset-urls/allReturns every dataset URL as a JSON Lines (
.jsonl) file, one JSON object per line. Each line has the same shape as an element of thedataset_urlsarray of the paginated endpoint (aDatasetURLRespModel), so no internal database columns are exposed. A binaryreturn_metadataflag (defaultfalse), the counterpart of the tri-statereturn_metadataquery parameter of the paginated endpoint, controls whether each line includes themetadatafield populated by content.Response details and behavior
Content-Type: application/x-ndjson, served as a download viaContent-Disposition: attachment; filename=dataset-urls.jsonl.return_metadata(default), each line omits themetadatafield.return_metadata=true, each line includesmetadataas a list of metadata objects by content, the same content form asreturn_metadata=contenton the paginated endpoint.yield_perso the full set of records is not serialized into memory at once, and metadata is eagerly loaded viaselectinloadwhen requested to avoid an N+1 query./alldoes not collide with the existing/<int:id>route, since static rules take precedence over converter rules in Werkzeug.Scope: what this intentionally leaves out
return_metadata; it is always a full snapshot of all dataset URLs.Test plan
black,isort,flake8,mypyclean on the changed files.test_dataset_urls.pymodule passes (148 tests, 9 new inTestAllDatasetURLs).application/x-ndjson200 response and thereturn_metadataparameter both appear at/openapi/openapi.json).New test coverage (
TestAllDatasetURLs)application/x-ndjsoncontent type.Content-Dispositionoffers the response asdataset-urls.jsonl.return_metadata: all dataset URLs are returned and no line has ametadatafield.return_metadata=true: each line includesmetadataby content, with the expected per-URL counts.return_metadatavalue is rejected with422.