Skip to content

Commit 49289ab

Browse files
authored
[DE-8304] Deprecate synchronous upload capability (#468)
* Deprecate synchronous upload capability * Disallow mixed file upload types in one call * Address greptile * Update tests that still use sync upload * Remove sync append in deduplication test * Update add_items_from_dir to be async only * Fix test_create_update_dataset_from_dir * Remove deprecated asynchronous=True flag from test files * Update CLAUDE.md
1 parent e672e41 commit 49289ab

26 files changed

Lines changed: 253 additions & 564 deletions

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ All notable changes to the [Nucleus Python Client](https://github.com/scaleapi/n
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.19.0](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.19.0) - 2026-07-07
9+
10+
### Changed
11+
- **Breaking:** `dataset.append()` now always uses the async pipeline and returns an `AsyncJob`. The `asynchronous` and `batch_size` parameters are deprecated and ignored. All uploads (local and remote) go through the async Step Function pipeline, which handles phash computation, image optimization, and NLS search indexing.
12+
- `dataset.add_items_from_dir()` now returns the `AsyncJob` for the upload (or `None` when no items are found) instead of blocking. Call `job.sleep_until_complete()` to wait until items are queryable and to surface upload errors.
13+
14+
### Removed
15+
- Synchronous upload paths for images and videos. All uploads now use the async pipeline. Use `job.sleep_until_complete()` to block until processing finishes.
16+
- `UploadResponse` class — `append()` now returns `AsyncJob`.
17+
- `construct_append_payload()` and `construct_append_scenes_payload()` functions.
18+
- `check_all_paths_remote()` function.
19+
- The already deprecated `dataset.append_scenes()` method — use `dataset.append()` instead.
20+
- Synchronous branches from `_append_scenes()` and `_append_video_scenes()`.
21+
822
## [0.18.8](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.18.8) - 2026-06-17
923

1024
### Fixed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Pure refactors / doc-only PRs (#456) sometimes skip the version bump. When in do
3535

3636
- `nucleus/__init__.py``NucleusClient`, top-level operations.
3737
- `nucleus/dataset.py``Dataset` class. Most user-facing methods live here (item upload/fetch, generators, queries, slices, autotags, exports). Generators page through the backend via `nucleus/utils.py:paginate_generator`.
38+
- **Uploads are async-only.** `Dataset.append()` always returns an `AsyncJob` (the `asynchronous`/`batch_size` params are deprecated and ignored). Items aren't queryable until the job finishes — callers and test fixtures must `job.sleep_until_complete()` before reading `dataset.items`, creating slices, or uploading annotations/predictions keyed by `reference_id`. `add_items_from_dir()` returns the job; `create_dataset_from_dir()` awaits it internally.
3839
- `nucleus/dataset_item.py``DatasetItem` dataclass. **`DatasetItem.from_json` is the single deserialization entry point** for items coming back from the API — every SDK method that returns a `DatasetItem` (generators, queries, `iloc`/`refloc`/`loc`, the `items` property) routes through it. To expose a new server-side field on items, add it to the dataclass + `from_json` and you're done on the SDK side.
3940
- `nucleus/utils.py``convert_export_payload` and `format_dataset_item_response` are the shared shapers used by the export and single-item endpoints. They wrap raw JSON into typed objects via the respective `from_json` classmethods.
4041
- `nucleus/constants.py` — All API payload keys are constants here. When adding a new field, add a `*_KEY` constant first and reference it from `from_json` / `to_payload` rather than inlining the string.

conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def CLIENT():
4646
@pytest.fixture()
4747
def dataset(CLIENT: "NucleusClient"):
4848
test_dataset = CLIENT.create_dataset(TEST_DATASET_NAME, is_scene=False)
49-
test_dataset.append(TEST_DATASET_ITEMS)
49+
job = test_dataset.append(TEST_DATASET_ITEMS)
50+
job.sleep_until_complete()
5051
yield test_dataset
5152

5253

nucleus/__init__.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@
171171
from .model_run import ModelRun
172172
from .payload_constructor import (
173173
construct_annotation_payload,
174-
construct_append_payload,
175174
construct_box_predictions_payload,
176175
construct_model_creation_payload,
177176
construct_segmentation_payload,
@@ -190,7 +189,6 @@
190189
from .retry_strategy import RetryStrategy
191190
from .scene import Frame, LidarScene, VideoScene
192191
from .slice import Slice
193-
from .upload_response import UploadResponse
194192
from .utils import create_items_from_folder_crawl
195193
from .validate import Validate
196194

@@ -605,19 +603,6 @@ def delete_dataset_item(self, dataset_id: str, reference_id) -> dict:
605603
dataset = self.get_dataset(dataset_id)
606604
return dataset.delete_item(reference_id)
607605

608-
@deprecated("Use Dataset.append instead.")
609-
def populate_dataset(
610-
self,
611-
dataset_id: str,
612-
dataset_items: List[DatasetItem],
613-
batch_size: int = 20,
614-
update: bool = False,
615-
):
616-
dataset = self.get_dataset(dataset_id)
617-
return dataset.append(
618-
dataset_items, batch_size=batch_size, update=update
619-
)
620-
621606
@deprecated(msg="Use Dataset.ingest_tasks instead")
622607
def ingest_tasks(self, dataset_id: str, payload: dict):
623608
dataset = self.get_dataset(dataset_id)
@@ -1407,10 +1392,12 @@ def create_dataset_from_dir(
14071392
dataset = self.create_dataset(
14081393
name=dataset_name, use_privacy_mode=use_privacy_mode
14091394
)
1410-
dataset.add_items_from_dir(
1395+
job = dataset.add_items_from_dir(
14111396
existing_dirname=existing_dirname,
14121397
privacy_mode_proxy=privacy_mode_proxy,
14131398
allowed_file_types=allowed_file_types,
14141399
skip_size_warning=skip_size_warning,
14151400
)
1401+
if job is not None:
1402+
job.sleep_until_complete()
14161403
return dataset

nucleus/async_job.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ class AsyncJob:
5151
client = nucleus.NucleusClient(YOUR_SCALE_API_KEY)
5252
dataset = client.get_dataset("ds_bwkezj6g5c4g05gqp1eg")
5353
54-
# When kicking off an asynchronous job, store the return value as a variable
55-
job = dataset.append(items=YOUR_DATASET_ITEMS, asynchronous=True)
54+
# dataset.append() always returns an AsyncJob
55+
job = dataset.append(items=YOUR_DATASET_ITEMS)
5656
5757
# Poll for status or errors
5858
print(job.status())

0 commit comments

Comments
 (0)