77from pathlib import Path
88from typing import TYPE_CHECKING
99
10+ from alive_progress import alive_bar # type: ignore[import-untyped]
11+
12+ import sift_client as _sift_client_module
1013from sift_client ._internal .low_level_wrappers .jobs import JobsLowLevelClient
1114from sift_client ._internal .util .executor import run_sync_function
1215from sift_client ._internal .util .file import download_file , extract_zip
@@ -169,6 +172,7 @@ async def wait_until_complete(
169172 * ,
170173 polling_interval_secs : int = 5 ,
171174 timeout_secs : int | None = None ,
175+ show_progress : bool | None = None ,
172176 ) -> Job :
173177 """Wait until the job is complete or the timeout is reached.
174178
@@ -180,20 +184,45 @@ async def wait_until_complete(
180184 polling_interval_secs: Seconds between status polls. Defaults to 5s.
181185 timeout_secs: Maximum seconds to wait. If None, polls indefinitely.
182186 Defaults to None (indefinite).
187+ show_progress: If True, display an animated progress spinner alongside
188+ the job status while polling. Defaults to True for sync, False
189+ for async. Use ``sift_client.config.show_progress = False`` to disable
190+ globally for sync.
183191
184192 Returns:
185193 The Job in the completed state.
186194 """
187195 job_id = job ._id_or_error if isinstance (job , Job ) else job
196+ if show_progress is None :
197+ global_setting = _sift_client_module .config .show_progress
198+ if global_setting is not None :
199+ show_progress = global_setting
200+ elif getattr (self , "_is_sync" , False ):
201+ show_progress = True
202+ else :
203+ show_progress = False
188204
189205 start = time .monotonic ()
190- while True :
191- job = await self .get (job_id )
192- if job .job_status in (JobStatus .FINISHED , JobStatus .FAILED , JobStatus .CANCELLED ):
193- return job
194- if timeout_secs is not None and (time .monotonic () - start ) >= timeout_secs :
195- raise TimeoutError (f"Job { job_id } did not complete within { timeout_secs } seconds" )
196- await asyncio .sleep (polling_interval_secs )
206+ with alive_bar (
207+ title = f"Job { job_id } : polling" ,
208+ bar = None ,
209+ spinner_length = 7 ,
210+ spinner = "dots_waves" ,
211+ monitor = False ,
212+ stats = False ,
213+ disable = not show_progress ,
214+ ) as bar :
215+ while True :
216+ job = await self .get (job_id )
217+ bar .title (f"Job { job_id } ({ job .job_type .value .lower ()} ): { job .job_status .value } " )
218+ bar ()
219+ if job .job_status in (JobStatus .FINISHED , JobStatus .FAILED , JobStatus .CANCELLED ):
220+ return job
221+ if timeout_secs is not None and (time .monotonic () - start ) >= timeout_secs :
222+ raise TimeoutError (
223+ f"Job { job_id } did not complete within { timeout_secs } seconds"
224+ )
225+ await asyncio .sleep (polling_interval_secs )
197226
198227 async def wait_and_download (
199228 self ,
@@ -203,6 +232,7 @@ async def wait_and_download(
203232 timeout_secs : int | None = None ,
204233 output_dir : str | Path | None = None ,
205234 extract : bool = True ,
235+ show_progress : bool | None = None ,
206236 ) -> list [Path ]:
207237 """Wait for a job to complete and download the result files.
208238
@@ -219,6 +249,10 @@ async def wait_and_download(
219249 extract it and delete the archive, returning paths to the
220250 extracted files. Non-zip files are returned as-is regardless
221251 of this flag.
252+ show_progress: If True, display an animated progress spinner
253+ while waiting and a download progress bar. Defaults to True
254+ for sync, False for async. Use ``sift_client.config.show_progress = False``
255+ to disable globally for sync.
222256
223257 Returns:
224258 List of paths to the downloaded/extracted files.
@@ -228,11 +262,20 @@ async def wait_and_download(
228262 TimeoutError: If the job does not complete within timeout_secs.
229263 """
230264 job_id = job ._id_or_error if isinstance (job , Job ) else job
265+ if show_progress is None :
266+ global_setting = _sift_client_module .config .show_progress
267+ if global_setting is not None :
268+ show_progress = global_setting
269+ elif getattr (self , "_is_sync" , False ):
270+ show_progress = True
271+ else :
272+ show_progress = False
231273
232274 completed_job = await self .wait_until_complete (
233275 job = job_id ,
234276 polling_interval_secs = polling_interval_secs ,
235277 timeout_secs = timeout_secs ,
278+ show_progress = show_progress ,
236279 )
237280 if completed_job .job_status == JobStatus .FAILED :
238281 if (
@@ -259,7 +302,9 @@ async def wait_and_download(
259302 # Run the synchronous download in a thread pool to avoid blocking the event loop
260303 rest_client = self .client .rest_client
261304 await run_sync_function (
262- lambda : download_file (presigned_url , download_path , rest_client = rest_client )
305+ lambda : download_file (
306+ presigned_url , download_path , rest_client = rest_client , show_progress = show_progress
307+ )
263308 )
264309
265310 if not extract or not zipfile .is_zipfile (download_path ):
0 commit comments