@@ -121,18 +121,40 @@ with ApiClient(Configuration(api_key="...", workspace_id="...")) as client:
121121(` size ` is inferred for all three; a file object is read from its current
122122position to the end). The SDK picks single vs. multipart from the size,
123123auto-scales the part size, and bounds part concurrency to a peak-memory budget
124- (override with ` part_size ` / ` max_concurrency ` / ` part_retry ` ). Storage ` PUT ` s go
125- through a dedicated, header-isolated connection pool, so the SDK's auth and
126- workspace headers never reach object storage (which would otherwise reject the
127- upload). Finalize is sent with retries disabled so the exactly-once call is never
128- accidentally replayed.
129-
130- Failures surface as a typed hierarchy under ` hotdata.uploads.UploadError ` :
131- ` StorageError ` (storage returned a non-2xx), ` StorageTransportError ` (the PUT
132- failed before any response), ` MissingETagError ` , ` MalformedSessionError ` , and
133- ` SizeLimitError ` . Opening the session or finalizing raises the usual
134- ` hotdata.exceptions.ApiException ` — for example a ` 501 ` ` PRESIGN_UNSUPPORTED ` ,
135- meaning the backend cannot issue upload URLs.
124+ (override with ` part_size ` / ` max_concurrency ` / ` part_retry ` ). A file larger than
125+ 8 MiB uploads via a ** streaming** session — the SDK mints each part's URL just
126+ before its ` PUT ` , so a presigned URL can't expire mid-transfer on a slow upload.
127+ Storage ` PUT ` s go through a dedicated, header-isolated connection pool (auth and
128+ workspace headers never reach object storage, which would otherwise reject the
129+ upload) with a 30s connect timeout so a dead endpoint fails fast. Finalize is sent
130+ with retries disabled so the exactly-once call is never accidentally replayed.
131+
132+ Every failure is a subclass of ` hotdata.uploads.UploadError ` (also importable as
133+ ` from hotdata import UploadError ` ), so a single ` except UploadError ` catches the
134+ whole flow: ` SessionCreateError ` (opening the session — check ` .status ` for a
135+ ` 501 ` ` PRESIGN_UNSUPPORTED ` ), ` StorageError ` (storage returned a non-2xx; ` .exhausted `
136+ is ` True ` if it outlived every retry round), ` StorageTransportError ` (the PUT
137+ failed before any response), ` MissingETagError ` , ` MintPartError ` (minting a part
138+ URL), ` FinalizeError ` , ` MalformedSessionError ` , ` SizeLimitError ` , and
139+ ` UploadCancelledError ` . The phase errors that wrap a control-plane call chain the
140+ underlying ` hotdata.exceptions.ApiException ` as ` __cause__ ` (and expose it as
141+ ` .api_exception ` / ` .status ` ). A local file read error surfaces as ` OSError ` .
142+
143+ The ` progress ` callback receives a ** cumulative** ` (bytes_done, total) ` — for a
144+ tqdm bar (whose ` update(n) ` wants a delta, and which isn't thread-safe under
145+ multipart) use the ready-made adapter:
146+
147+ ``` python
148+ from hotdata import tqdm_progress
149+ from tqdm import tqdm
150+
151+ with tqdm(total = size, unit = " B" , unit_scale = True ) as bar:
152+ uploads.upload_file(" data.parquet" , progress = tqdm_progress(bar))
153+ ```
154+
155+ Pass a ` threading.Event ` as ` cancel_event ` to abort an in-flight upload; tune the
156+ control-plane calls with ` request_timeout ` (storage-PUT timeouts are automatic and
157+ size-scaled).
136158
137159For that fallback (or to upload from a non-seekable stream), use ` upload_stream ` ,
138160which sends the bytes to the legacy ` POST /v1/files ` endpoint in one request,
0 commit comments