[DE-8304] Deprecate synchronous upload capability#468
Conversation
|
@greptileai regarding your "P1 Only last batch job returned for multi-batch local uploads" concern: I believed I've already fixed at line 2178-2182 in All batch jobs except the last are waited on before returning. The caller's returned job is the final one, and by the time they receive it, all earlier batches have completed. |
|
You're right, and I apologize for the stale comment. Looking at the diff, jobs = [AsyncJob.from_json(r, self._client) for r in responses]
for job in jobs[:-1]:
job.sleep_until_complete()
return jobs[-1]All earlier batch jobs are waited on inside Tip: You can customize Greptile's behavior for this repo with |
Summary
Companion PR (to be merged first): https://github.com/scaleapi/scaleapi/pull/150037
All Nucleus uploads now go through the async pipeline (Step Functions). The synchronous upload path has been fully removed. This ensures every upload gets phash computation, image optimization, and Qdrant/NLS embedding — the sync path silently skipped NLS indexing (which was done on purpose as this was a planned change), making items invisible to natural language search in federal environments. This only affects image datasets anyway
The main reason for this change is two fold:
Old nucleus uploads:
New nucleus uploads:
Breaking changes
dataset.append()always returnsAsyncJob— previously returnedUploadResponsefor sync uploads. Usejob.sleep_until_complete()to block until processing finishes.asynchronousandbatch_sizeparameters are deprecated — they are accepted with aDeprecationWarningbut ignored. All uploads are async.asynchronous=Truenow work — previously raisedValueError. Files are sent as multipart to the backend which handles S3 upload internally.append()call are supported — local items go as multipart, remote items go as NDJSON.Removed
UploadResponseclass andnucleus/upload_response.pyconstruct_append_payload()andconstruct_append_scenes_payload()functionscheck_all_paths_remote()functiondataset.append_scenes()public methodNucleusClient.populate_dataset()methodDatasetItemUploader.upload()sync method and_process_append_requests()sync remote method_append_scenes()and_append_video_scenes()tests/test_upload_response.pyUpdated
AsyncJoband calljob.sleep_until_complete()before reading dataasync_job.pydocstring updated to removeasynchronous=Truefrom examplepayload_constructor.py0.19.0Test plan
poetry run pytest tests/test_dataset.py -k "test_dataset_append" -v— verify all append tests pass with AsyncJobpoetry run pytest tests/test_dataset.py -k "test_dataset_append_local" -v— verify local file async upload completespoetry run pytest tests/test_dataset.py -k "test_dataset_append_async_local" -v— verify async local upload returns AsyncJobpython -c "import nucleus"— verify no import errors after removing UploadResponsedataset.append(items, asynchronous=True)emits DeprecationWarningdataset.append(items, batch_size=20)emits DeprecationWarningdataset.append([])raises ValueErrorresolves https://linear.app/scale-epd/issue/DE-8304
Greptile Summary
This PR removes the synchronous upload path from the Nucleus Python client, routing all
dataset.append()calls through the async Step Function pipeline. The main motivation is ensuring every upload gets phash computation, image optimisation, and NLS search indexing — capabilities that the sync path silently skipped.append()now always returnsAsyncJob; the deprecatedasynchronousandbatch_sizeparameters emitDeprecationWarningand are ignored. Local file uploads go as multipart to/append?async=1; remote items are serialised to a presigned URL and submitted the existing way.ValueError(the PR description claims they are "supported", but the code and the newtest_dataset_append_rejects_mixed_local_and_remotetest both confirm they are rejected).UploadResponse,construct_append_payload,check_all_paths_remote, and the deprecatedpopulate_dataset/append_sceneswrappers are all removed; version bumped to 0.19.0.Confidence Score: 4/5
Safe to merge after addressing multi-batch local upload tracking: callers uploading 11+ local files only receive the last batch's AsyncJob, leaving earlier batches untracked.
The sync-to-async migration is clean and well-tested. The one structural concern (only the last batch job returned to the caller for multi-batch local uploads) was flagged in a prior review round and remains unresolved — it means a caller who uploads more than 10 local files in one call silently loses visibility into whether the first N-1 batches actually completed successfully from their end.
nucleus/dataset.py — specifically _upload_local_items_async and the multi-batch job-return strategy.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["dataset.append(items, ...)"] --> B{Deprecated params?} B -->|asynchronous or batch_size set| C[Emit DeprecationWarning] C --> D B -->|No| D{What type of items?} D -->|LidarScene| E["_append_scenes() → serialize to presigned URL → /upload_scenes?async=1 → AsyncJob"] D -->|VideoScene| F["_append_video_scenes() → serialize to presigned URL → /upload_video_scenes?async=1 → AsyncJob"] D -->|DatasetItem| G{Empty list?} G -->|Yes| H[raise ValueError] G -->|No| I{Mixed local + remote?} I -->|Yes| J[raise ValueError] I -->|Local only| K["_upload_local_items_async() → multipart batches → /append?async=1\nwait for all but last → return last AsyncJob"] I -->|Remote only| L["serialize_and_write_to_presigned_url() → /append?async=1 → AsyncJob"]%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD A["dataset.append(items, ...)"] --> B{Deprecated params?} B -->|asynchronous or batch_size set| C[Emit DeprecationWarning] C --> D B -->|No| D{What type of items?} D -->|LidarScene| E["_append_scenes() → serialize to presigned URL → /upload_scenes?async=1 → AsyncJob"] D -->|VideoScene| F["_append_video_scenes() → serialize to presigned URL → /upload_video_scenes?async=1 → AsyncJob"] D -->|DatasetItem| G{Empty list?} G -->|Yes| H[raise ValueError] G -->|No| I{Mixed local + remote?} I -->|Yes| J[raise ValueError] I -->|Local only| K["_upload_local_items_async() → multipart batches → /append?async=1\nwait for all but last → return last AsyncJob"] I -->|Remote only| L["serialize_and_write_to_presigned_url() → /append?async=1 → AsyncJob"]Comments Outside Diff (1)
nucleus/dataset.py, line 2462-2469 (link)_upload_local_items_asynccallsuploader.upload_local_async(...)which fires one concurrent request per batch (batch size =local_files_per_upload_request, default 10). Each request creates a separateAsyncJobon the backend. The method then returnsAsyncJob.from_json(responses[-1], self._client)— only the last batch's job. If a caller uploads 11+ local items (splitting into ≥ 2 batches), callingsleep_until_complete()on the returned job only waits for the last batch; the earlier batches' jobs are untracked and may still be running. The comment# Return the last job — all batches feed into the same datasetis misleading — they are independent async jobs.Prompt To Fix With AI
Reviews (3): Last reviewed commit: "Address greptile" | Re-trigger Greptile