Skip to content

Commit 403ea13

Browse files
authored
[SYNPY-1488] Patch nested tqdm progress bars and messages to logger (#1177)
* Patch tqdm progress bars for nested bars, and migrating print/sysout messages to logger
1 parent 2096516 commit 403ea13

16 files changed

+185
-148
lines changed

synapseclient/__main__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,12 @@ def show(args, syn):
359359

360360
ent = syn.get(args.id, downloadFile=False)
361361
syn.printEntity(ent)
362-
sys.stdout.write("Provenance:\n")
362+
syn.logger.info("Provenance:")
363363
try:
364364
prov = syn.getProvenance(ent)
365365
syn.logger.info(prov)
366366
except SynapseHTTPError:
367-
syn.logger.error(" No Activity specified.\n")
367+
syn.logger.exception("No Activity specified.")
368368

369369

370370
def delete(args, syn):
@@ -1785,11 +1785,11 @@ def perform_main(args, syn):
17851785
)
17861786
try:
17871787
args.func(args, syn)
1788-
except Exception as ex:
1788+
except Exception:
17891789
if args.debug:
17901790
raise
17911791
else:
1792-
sys.stderr.write(utils._synapse_error_msg(ex))
1792+
syn.logger.exception(f"Failed to perform command: {args.func}")
17931793
sys.exit(1)
17941794
else:
17951795
# if no command provided print out help and quit

synapseclient/client.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from opentelemetry.instrumentation.threading import ThreadingInstrumentor
4444
from opentelemetry.instrumentation.urllib import URLLibInstrumentor
4545
from opentelemetry.trace import Span
46+
from tqdm import tqdm
4647

4748
import synapseclient
4849
import synapseclient.core.multithread_download as multithread_download
@@ -5129,22 +5130,27 @@ def _waitForAsync(self, uri, request, endpoint=None):
51295130
# https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/asynch/AsynchronousJobStatus.html
51305131
sleep = self.table_query_sleep
51315132
start_time = time.time()
5132-
lastMessage, lastProgress, lastTotal, progressed = "", 0, 1, False
5133+
lastMessage, lastProgress, lastTotal = "", 0, 1
5134+
progress_bar = tqdm(desc=uri, unit_scale=True, smoothing=0, leave=None)
51335135
while time.time() - start_time < self.table_query_timeout:
51345136
result = self.restGET(
51355137
uri + "/get/%s" % async_job_id["token"], endpoint=endpoint
51365138
)
51375139
if result.get("jobState", None) == "PROCESSING":
5138-
progressed = True
51395140
message = result.get("progressMessage", lastMessage)
51405141
progress = result.get("progressCurrent", lastProgress)
51415142
total = result.get("progressTotal", lastTotal)
5143+
if total and progress_bar.total != total:
5144+
progress_bar.total = total
5145+
progress_bar.refresh()
5146+
51425147
if message != "":
5143-
self._print_transfer_progress(
5144-
progress, total, message, isBytes=False
5145-
)
5148+
progress_bar.desc = message
5149+
progress_bar.refresh()
5150+
51465151
# Reset the time if we made progress (fix SYNPY-214)
51475152
if message != lastMessage or lastProgress != progress:
5153+
progress_bar.update(progress)
51485154
start_time = time.time()
51495155
lastMessage, lastProgress, lastTotal = message, progress, total
51505156
sleep = min(
@@ -5165,8 +5171,10 @@ def _waitForAsync(self, uri, request, endpoint=None):
51655171
+ result.get("errorDetails", None),
51665172
asynchronousJobStatus=result,
51675173
)
5168-
if progressed:
5169-
self._print_transfer_progress(total, total, message, isBytes=False)
5174+
if progress_bar.total and progress_bar.n:
5175+
progress_bar.update(progress_bar.total - progress_bar.n)
5176+
progress_bar.refresh()
5177+
progress_bar.close()
51705178
return result
51715179

51725180
def getColumn(self, id):

synapseclient/core/cumulative_transfer_progress.py

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import sys
21
import threading
32
import time
43
from contextlib import contextmanager
54

5+
from tqdm import tqdm
6+
67
from synapseclient.core import utils
78

89
# Defines a mechanism for printing file transfer progress that includes potentially multiple file transfers to the
@@ -42,7 +43,7 @@ def __init__(self, label, start=None):
4243
self._lock = threading.Lock()
4344
self._label = label
4445

45-
self._spinner = utils.Spinner()
46+
self._progress_bar: tqdm = None
4647
self._start = start if start is not None else time.time()
4748

4849
self._total_transferred = 0
@@ -61,51 +62,47 @@ def accumulate_progress(self):
6162

6263
def printTransferProgress(
6364
self,
64-
transferred,
65-
toBeTransferred,
66-
prefix="",
67-
postfix="",
68-
isBytes=True,
69-
dt=None,
70-
previouslyTransferred=0,
71-
):
65+
transferred: int,
66+
toBeTransferred: int,
67+
prefix: str = "",
68+
postfix: str = "",
69+
isBytes: bool = True,
70+
dt: None = None,
71+
previouslyTransferred: int = 0,
72+
) -> None:
7273
"""
7374
Parameters match those of synapseclient.core.utils.printTransferProgress.
74-
"""
75-
76-
if not sys.stdout.isatty():
77-
return
7875
76+
Arguments:
77+
transferred: The number of bytes transferred in the current transfer.
78+
toBeTransferred: The total number of bytes to be transferred in the current
79+
transfer.
80+
prefix: A string to prepend to the progress bar.
81+
postfix: A string to append to the progress bar.
82+
isBytes: If True, the progress bar will display bytes. If False, the
83+
progress bar will display the unit of the transferred data.
84+
dt: Deprecated.
85+
previouslyTransferred: Deprecated.
86+
87+
Returns:
88+
None
89+
"""
7990
with self._lock:
80-
if toBeTransferred == 0 or float(transferred) / toBeTransferred >= 1:
81-
# if the individual transfer is complete then we pass through the print
82-
# to the underlying utility method which will print a complete 100%
83-
# progress bar on a newline.
84-
utils.printTransferProgress(
85-
transferred,
86-
toBeTransferred,
87-
prefix=prefix,
91+
if not self._progress_bar:
92+
self._progress_bar = tqdm(
93+
desc=prefix if prefix else "Transfer Progress",
94+
unit_scale=True,
95+
total=toBeTransferred,
96+
smoothing=0,
8897
postfix=postfix,
89-
isBytes=isBytes,
90-
dt=dt,
91-
previouslyTransferred=previouslyTransferred,
98+
unit="B" if isBytes else None,
99+
leave=None,
92100
)
93-
94101
# in order to know how much of the transferred data is newly transferred
95102
# we subtract the previously reported amount. this assumes that the printing
96103
# of the progress for any particular transfer is always conducted by the same
97104
# thread, which is true for all current transfer implementations.
98105
self._total_transferred += transferred - _thread_local.thread_transferred
99106
_thread_local.thread_transferred = transferred
100107

101-
cumulative_dt = time.time() - self._start
102-
rate = self._total_transferred / float(cumulative_dt)
103-
rate = "(%s/s)" % utils.humanizeBytes(rate) if isBytes else rate
104-
105-
# we print a rotating tick with each update
106-
self._spinner.print_tick()
107-
108-
sys.stdout.write(
109-
f"{self._label} {utils.humanizeBytes(self._total_transferred)} {rate}"
110-
)
111-
sys.stdout.flush()
108+
self._progress_bar.update(transferred - _thread_local.thread_transferred)

synapseclient/core/remote_file_storage_wrappers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ def upload_file(
215215
unit_scale=True,
216216
postfix=filename,
217217
smoothing=0,
218+
leave=None,
218219
)
219220
progress_callback = S3ClientWrapper._create_progress_callback_func(
220221
progress_bar
@@ -291,6 +292,7 @@ def upload_file(
291292
unit_scale=True,
292293
smoothing=0,
293294
postfix=filepath,
295+
leave=None,
294296
)
295297

296298
def progress_callback(*args, **kwargs) -> None:

synapseclient/core/transfer_bar.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ def get_or_create_download_progress_bar(
159159
unit_scale=True,
160160
smoothing=0,
161161
postfix=postfix,
162+
leave=None,
162163
)
163164
_thread_local.progress_bar_download = progress_bar
164165
else:

synapseclient/core/upload/multipart_upload.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import requests
1818
from opentelemetry import trace
19+
from tqdm import tqdm
1920

2021
from synapseclient.core import pool_provider
2122
from synapseclient.core.constants import concrete_types
@@ -33,7 +34,7 @@
3334
get_file_chunk,
3435
get_part_size,
3536
)
36-
from synapseclient.core.utils import MB, Spinner, md5_fn, md5_for_file
37+
from synapseclient.core.utils import MB, md5_fn, md5_for_file
3738

3839
# AWS limits
3940
MAX_NUMBER_OF_PARTS = 10000
@@ -468,8 +469,21 @@ def multipart_upload_file(
468469
mime_type, _ = mimetypes.guess_type(file_path, strict=False)
469470
content_type = mime_type or "application/octet-stream"
470471

471-
callback_func = Spinner().print_tick if not syn.silent else None
472-
md5_hex = md5 or md5_for_file(file_path, callback=callback_func).hexdigest()
472+
if md5:
473+
md5_hex = md5
474+
else:
475+
progress_bar = (
476+
tqdm(
477+
desc=f"Calculating MD5: {os.path.basename(file_path)}",
478+
unit="B",
479+
unit_scale=True,
480+
total=file_size,
481+
leave=None,
482+
)
483+
if not syn.silent
484+
else None
485+
)
486+
md5_hex = md5_for_file(file_path, progress_bar=progress_bar).hexdigest()
473487

474488
part_size = get_part_size(
475489
part_size or DEFAULT_PART_SIZE,

synapseclient/core/upload/multipart_upload_async.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ async def _upload_parts(
357357
unit_scale=True,
358358
postfix=self._dest_file_name,
359359
smoothing=0,
360+
leave=None,
360361
)
361362
self._progress_bar.update(completed_part_count)
362363
else:
@@ -374,6 +375,7 @@ async def _upload_parts(
374375
unit_scale=True,
375376
postfix=self._dest_file_name,
376377
smoothing=0,
378+
leave=None,
377379
)
378380
self._progress_bar.update(previously_transferred)
379381

synapseclient/core/utils.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
import zipfile
2727
from dataclasses import asdict, fields, is_dataclass
2828
from email.message import Message
29-
from typing import TYPE_CHECKING, List, TypeVar
29+
from typing import TYPE_CHECKING, List, Optional, TypeVar
3030

3131
import requests
32+
from deprecated import deprecated
3233
from opentelemetry import trace
34+
from tqdm import tqdm
3335

3436
from synapseclient.core.logging_setup import DEFAULT_LOGGER_NAME
3537

@@ -57,7 +59,10 @@
5759

5860

5961
def md5_for_file(
60-
filename: str, block_size: int = 2 * MB, callback: typing.Callable = None
62+
filename: str,
63+
block_size: int = 2 * MB,
64+
callback: typing.Callable = None,
65+
progress_bar: Optional[tqdm] = None,
6166
):
6267
"""
6368
Calculates the MD5 of the given file.
@@ -82,8 +87,14 @@ def md5_for_file(
8287
callback()
8388
data = f.read(block_size)
8489
if not data:
90+
if progress_bar:
91+
progress_bar.update(progress_bar.total - progress_bar.n)
92+
progress_bar.refresh()
93+
progress_bar.close()
8594
break
8695
md5.update(data)
96+
if progress_bar:
97+
progress_bar.update(len(data))
8798
del data
8899
# Garbage collect every 100 iterations
89100
if loop_iteration % 100 == 0:
@@ -617,23 +628,36 @@ def make_bogus_binary_file(
617628
Returns:
618629
The name of the file
619630
"""
620-
631+
progress_bar = (
632+
tqdm(
633+
desc=f"Generating {filepath}",
634+
unit_scale=True,
635+
total=n,
636+
smoothing=0,
637+
unit="B",
638+
leave=None,
639+
)
640+
if printprogress
641+
else None
642+
)
621643
with (
622644
open(filepath, "wb")
623645
if filepath
624646
else tempfile.NamedTemporaryFile(mode="wb", suffix=".dat", delete=False)
625647
) as f:
626648
if not filepath:
627649
filepath = f.name
628-
progress = 0
629650
remaining = n
630651
while remaining > 0:
631652
buff_size = int(min(remaining, 1 * KB))
632653
f.write(os.urandom(buff_size))
654+
if progress_bar:
655+
progress_bar.update(buff_size)
633656
remaining -= buff_size
634-
if printprogress:
635-
progress += buff_size
636-
printTransferProgress(progress, n, "Generated ", filepath)
657+
if progress_bar:
658+
progress_bar.update(progress_bar.total - progress_bar.n)
659+
progress_bar.refresh()
660+
progress_bar.close()
637661
return normalize_path(filepath)
638662

639663

@@ -1361,6 +1385,11 @@ def wrapper(*args, **kwargs):
13611385
return wrapper
13621386

13631387

1388+
@deprecated(
1389+
version="4.8.0",
1390+
reason="To be removed in 5.0.0. "
1391+
"This is removed in favor of using the tqdm library for progress bars.",
1392+
)
13641393
class Spinner:
13651394
def __init__(self, msg=""):
13661395
self._tick = 0

synapseclient/models/mixins/table_operator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,6 +2262,7 @@ async def main():
22622262
desc="Querying & Updating rows",
22632263
unit_scale=True,
22642264
smoothing=0,
2265+
leave=None,
22652266
)
22662267
for individual_chunk in chunk_list:
22672268
select_statement = self._construct_select_statement_for_upsert(
@@ -3116,6 +3117,7 @@ async def _chunk_and_upload_csv(
31163117
unit_scale=True,
31173118
smoothing=0,
31183119
unit="B",
3120+
leave=None,
31193121
)
31203122
# The original file is read twice, the reason is that on the first pass we
31213123
# are calculating the size of the chunks that we will be uploading and the
@@ -3316,6 +3318,7 @@ async def _chunk_and_upload_df(
33163318
unit_scale=True,
33173319
smoothing=0,
33183320
unit="B",
3321+
leave=None,
33193322
)
33203323

33213324
changes = []

0 commit comments

Comments
 (0)