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