dcicutils 8.8.2.1b1__py3-none-any.whl → 8.8.2.1b3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,281 @@
1
+ from collections import namedtuple
2
+ from signal import signal, SIGINT
3
+ import sys
4
+ import threading
5
+ import time
6
+ from tqdm import tqdm
7
+ from types import FrameType as frame
8
+ from typing import Callable, Optional, Union
9
+ from contextlib import contextmanager
10
+ from dcicutils.command_utils import yes_or_no
11
+
12
+
13
+ class TQDM(tqdm):
14
+
15
+ """
16
+ def moveto(self, n):
17
+ # Hack/workaround for apparent tqdm bug where (for example) if we use this twice
18
+ # in a row, and we interrupt (CTRL-C) the first one and abort the task, then the
19
+ # output for the second usage gets a bit garble; on the wrong line and whatnot;
20
+ # somehow, state is stuck across usages; can't quite see how from the tqdm code.
21
+ # This is a bit worrying but so far no other deleterious effects observed.
22
+ # This looks maybe promising:
23
+ return
24
+ """
25
+
26
+ # Nevermind the above; found a more pointed solution from here:
27
+ # https://stackoverflow.com/questions/41707229/why-is-tqdm-printing-to-a-newline-instead-of-updating-the-same-line
28
+ # Why in the world would tqdm be maintaining state across instances?? Whatever, this fixes it.
29
+ def __init__(self, *args, **kwargs):
30
+ super()._instances.clear() if super()._instances else None
31
+ super().__init__(*args, **kwargs)
32
+
33
+
34
+ # Wrapper around tqdm command-line progress bar.
35
+ class ProgressBar:
36
+
37
+ @staticmethod
38
+ @contextmanager
39
+ def define(*args, **kwargs):
40
+ progress_bar = None
41
+ try:
42
+ progress_bar = ProgressBar(*args, **kwargs)
43
+ yield progress_bar
44
+ finally:
45
+ if progress_bar:
46
+ progress_bar.done()
47
+
48
+ def __init__(self, total: Optional[int] = None,
49
+ description: Optional[str] = None,
50
+ catch_interrupt: bool = True,
51
+ interrupt: Optional[Callable] = None,
52
+ interrupt_continue: Optional[Callable] = None,
53
+ interrupt_stop: Optional[Callable] = None,
54
+ interrupt_exit: bool = False,
55
+ interrupt_exit_message: Optional[Union[Callable, str]] = None,
56
+ interrupt_message: Optional[str] = None,
57
+ printf: Optional[Callable] = None,
58
+ tidy_output_hack: bool = True) -> None:
59
+ self._bar = None
60
+ self._disabled = False
61
+ self._done = False
62
+ self._printf = printf if callable(printf) else print
63
+ self._tidy_output_hack = (tidy_output_hack is True)
64
+ self._started = time.time()
65
+ self._stop_requested = False
66
+ # Interrupt handling. We do not do the actual (signal) interrupt setup
67
+ # in self._initialiaze as that could be called from a (sub) thread; and in
68
+ # Python we can only set a signal (SIGINT in our case) on the main thread.
69
+ self._catch_interrupt = (catch_interrupt is True)
70
+ self._interrupt = interrupt if callable(interrupt) else None
71
+ self._interrupt_continue = interrupt_continue if callable(interrupt_continue) else None
72
+ self._interrupt_stop = interrupt_stop if callable(interrupt_stop) else None
73
+ if interrupt_exit in [True, False]:
74
+ if not self._interrupt_stop:
75
+ self._interrupt_stop = lambda _: interrupt_exit
76
+ self._interrupt_exit = interrupt_exit
77
+ else:
78
+ self._interrupt_exit = None
79
+ self._interrupt_message = interrupt_message if isinstance(interrupt_message, str) else None
80
+ if isinstance(interrupt_exit_message, str):
81
+ self._interrupt_exit_message = lambda bar: interrupt_exit_message
82
+ elif isinstance(interrupt_exit_message, Callable):
83
+ self._interrupt_exit_message = interrupt_exit_message
84
+ else:
85
+ self._interrupt_exit_message = None
86
+ self._interrupt_handler = None
87
+ if self._catch_interrupt:
88
+ self._interrupt_handler = self._define_interrupt_handler()
89
+ if self._tidy_output_hack is True:
90
+ self._tidy_output_hack = self._define_tidy_output_hack()
91
+ self._total = total if isinstance(total, int) and total >= 0 else 0
92
+ self._description = self._format_description(description)
93
+ # self._initialize()
94
+
95
+ def _initialize(self) -> bool:
96
+ # Do not actually create the tqdm object unless/until we have a positive total.
97
+ if (self._bar is None) and (self._total > 0):
98
+ bar_format = "{l_bar}{bar}| {n_fmt}/{total_fmt} | {rate_fmt} | {elapsed}{postfix} | ETA: {remaining} "
99
+ self._bar = TQDM(total=self._total, desc=self._description,
100
+ dynamic_ncols=True, bar_format=bar_format, unit="", file=sys.stdout)
101
+ if self._disabled:
102
+ self._bar.disable = True
103
+ return True
104
+ return False
105
+
106
+ def set_total(self, value: int, reset_eta: bool = False) -> None:
107
+ if value == self._total:
108
+ # If the total has not changed since last set then do nothing.
109
+ if reset_eta and self._bar is not None:
110
+ self._bar.reset()
111
+ return
112
+ if isinstance(value, int) and value > 0:
113
+ self._total = value
114
+ if self._bar is not None:
115
+ # This reset is needed to get the ETA to reset properly when we reset
116
+ # the total during the course of a single ProgressBar instance.
117
+ self._bar.reset()
118
+ self._bar.total = value
119
+
120
+ def reset_eta(self) -> None:
121
+ # Since set_total does nothing if total is the same, provide
122
+ # a way to reset the ETA if starting over with the same total.
123
+ if self._bar is not None:
124
+ self._bar.reset()
125
+
126
+ def set_progress(self, value: int) -> None:
127
+ if isinstance(value, int) and value >= 0:
128
+ if (self._bar is not None) or self._initialize():
129
+ self._bar.n = value
130
+ self._bar.refresh()
131
+
132
+ def increment_progress(self, value: int) -> None:
133
+ if isinstance(value, int) and value > 0:
134
+ if (self._bar is not None) or self._initialize():
135
+ self._bar.update(value)
136
+ self._bar.refresh()
137
+
138
+ def set_description(self, value: str) -> None:
139
+ self._description = self._format_description(value)
140
+ if self._bar is not None:
141
+ self._bar.set_description(self._description)
142
+
143
+ def done(self) -> None:
144
+ if self._done or self._bar is None:
145
+ return
146
+ self._ended = time.time()
147
+ self._done = True
148
+ self.set_progress(self.total) # xyzzy
149
+ self._bar.set_description(self._description)
150
+ self._bar.refresh()
151
+ # FYI: Do NOT do a bar.disable = True before a bar.close() or it messes up output
152
+ # on multiple calls; found out the hard way; a couple hours will never get back :-/
153
+ self._bar.close()
154
+ if self._tidy_output_hack:
155
+ self._tidy_output_hack.restore()
156
+ if self._interrupt_handler:
157
+ self._interrupt_handler.restore()
158
+
159
+ def disable(self, value: bool = True) -> None:
160
+ self._disabled = (value is True)
161
+ if self._bar is not None:
162
+ self._bar.disable = self._disabled
163
+
164
+ def enable(self, value: bool = True) -> None:
165
+ self.disable(not value)
166
+
167
+ @property
168
+ def total(self) -> int:
169
+ return self._bar.total if self._bar else 0
170
+
171
+ @property
172
+ def progress(self) -> int:
173
+ return self._bar.n if self._bar else 0
174
+
175
+ @property
176
+ def disabled(self) -> bool:
177
+ return self._disabled
178
+
179
+ @property
180
+ def enabled(self) -> bool:
181
+ return not self.disabled
182
+
183
+ @property
184
+ def stop_requested(self) -> bool:
185
+ return self._stop_requested
186
+
187
+ @property
188
+ def started(self) -> None:
189
+ return self._started
190
+
191
+ @property
192
+ def duration(self) -> None:
193
+ return time.time() - self._started
194
+
195
+ def _format_description(self, value: str) -> str:
196
+ if not isinstance(value, str):
197
+ value = ""
198
+ if self._tidy_output_hack and not value.endswith(self._tidy_output_hack.sentinel):
199
+ value += self._tidy_output_hack.sentinel
200
+ return value
201
+
202
+ def _define_interrupt_handler(self) -> None:
203
+ def handle_interrupt(signum: int, frame: frame) -> None: # noqa
204
+ nonlocal self
205
+ def handle_secondary_interrupt(signum: int, frame: frame) -> None: # noqa
206
+ nonlocal self
207
+ self._printf("\nEnter 'yes' or 'no' or CTRL-\\ to completely abort ...")
208
+ self.disable()
209
+ self._interrupt(self) if self._interrupt else None
210
+ set_interrupt_handler(handle_secondary_interrupt)
211
+ if yes_or_no(f"\nALERT! You have interrupted this {self._interrupt_message or 'process'}."
212
+ f" Do you want to stop{' (exit)' if self._interrupt_exit else ''}?"):
213
+ # Here there was an interrupt (CTRL-C) and the user confirmed (yes)
214
+ # that they want to stop the process; if the interrupt_stop handler
215
+ # is defined and returns True, then we exit the entire process here,
216
+ # rather than simply returning, which is the default.
217
+ if self._interrupt_stop:
218
+ interrupt_stop = self._interrupt_stop(self)
219
+ if (interrupt_stop is True) or ((interrupt_stop is None) and (self._interrupt_exit is True)):
220
+ self.done()
221
+ restore_interrupt_handler()
222
+ if self._interrupt_exit_message:
223
+ if isinstance(interrupt_exit_message := self._interrupt_exit_message(self), str):
224
+ self._printf(interrupt_exit_message)
225
+ exit(1)
226
+ elif interrupt_stop is False or ((interrupt_stop is None) and (self._interrupt_exit is False)):
227
+ set_interrupt_handler(handle_interrupt)
228
+ interrupt_continue = self._interrupt_continue(self) if self._interrupt_continue else None
229
+ if not (interrupt_continue is False):
230
+ self.enable()
231
+ return
232
+ self._stop_requested = True
233
+ return
234
+ set_interrupt_handler(handle_interrupt)
235
+ self._interrupt_continue(self) if self._interrupt_continue else None
236
+ self.enable()
237
+ def restore_interrupt_handler() -> None: # noqa
238
+ nonlocal self, previous_interrupt_handler
239
+ set_interrupt_handler(previous_interrupt_handler)
240
+ def set_interrupt_handler(interrupt_handler: Callable) -> Optional[Callable]: # noqa
241
+ nonlocal self
242
+ if callable(interrupt_handler) and (threading.current_thread() == threading.main_thread()):
243
+ return signal(SIGINT, interrupt_handler)
244
+ return None
245
+ previous_interrupt_handler = set_interrupt_handler(handle_interrupt)
246
+ return namedtuple("interrupt_handler", ["restore"])(restore_interrupt_handler)
247
+
248
+ def _define_tidy_output_hack(self) -> None:
249
+ # Some minor tqdm output tidying-up which for annoying anomalies; tqdm forces
250
+ # a colon (:) before the percentage, e.g. ": 25%|"; and while we're at it do
251
+ # a little ASCII progress animation, requiring a special ([progress]) sentinal
252
+ # string in the display string where the progress bar should actually go,
253
+ # which we do in _format_description. Other minor things too; see below.
254
+ sys_stdout_write = sys.stdout.write
255
+ def tidy_stdout_write(text: str) -> None: # noqa
256
+ nonlocal self, sys_stdout_write, sentinel_internal, spina, spini, spinn
257
+ def replace_first(value: str, match: str, replacement: str) -> str: # noqa
258
+ return value[:i] + replacement + value[i + len(match):] if (i := value.find(match)) >= 0 else value
259
+ if (self._disabled or self._done) and sentinel_internal in text:
260
+ # Another hack to really disable output on interrupt; in this case we set
261
+ # tqdm.disable to True, but output can still dribble out, so if the output
262
+ # looks like it is from tqdm and we are disabled/done then do no output.
263
+ return
264
+ if sentinel_internal in text:
265
+ spinc = spina[spini % spinn] if not ("100%|" in text) else "| ✓" ; spini += 1 # noqa
266
+ text = replace_first(text, sentinel_internal, f" {spinc}")
267
+ text = replace_first(text, "%|", "% ◀|")
268
+ # Another oddity: for the rate sometimes tqdm intermittently prints
269
+ # something like "1.54s/" rather than "1.54/s"; something to do with
270
+ # the unit we gave, which is empty; idunno; just replace it here.
271
+ text = replace_first(text, "s/ ", "/s ")
272
+ sys_stdout_write(text)
273
+ sys.stdout.flush()
274
+ def restore_stdout_write() -> None: # noqa
275
+ nonlocal sys_stdout_write
276
+ if sys_stdout_write is not None:
277
+ sys.stdout.write = sys_stdout_write
278
+ sys.stdout.write = tidy_stdout_write
279
+ spina = ["|", "/", "—", "◦", "\\"] ; spini = 0 ; spinn = len(spina) # noqa
280
+ sentinel = "[progress]" ; sentinel_internal = f"{sentinel}:" # noqa
281
+ return namedtuple("tidy_output_hack", ["restore", "sentinel"])(restore_stdout_write, sentinel)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 8.8.2.1b1
3
+ Version: 8.8.2.1b3
4
4
  Summary: Utility package for interacting with the 4DN Data Portal and other 4DN resources
5
5
  Home-page: https://github.com/4dn-dcic/utils
6
6
  License: MIT
@@ -48,6 +48,7 @@ dcicutils/obfuscation_utils.py,sha256=fo2jOmDRC6xWpYX49u80bVNisqRRoPskFNX3ymFAmj
48
48
  dcicutils/opensearch_utils.py,sha256=V2exmFYW8Xl2_pGFixF4I2Cc549Opwe4PhFi5twC0M8,1017
49
49
  dcicutils/portal_object_utils.py,sha256=gDXRgPsRvqCFwbC8WatsuflAxNiigOnqr0Hi93k3AgE,15422
50
50
  dcicutils/portal_utils.py,sha256=oBoI3KWRp6YrbsuVGbmPQ3kATB5cVVsQo7-qmnYXWqg,30260
51
+ dcicutils/progress_bar.py,sha256=vQeVz6TMM5OkCWsQ1EDUMKpvbjvp7SshmOxIHFIQnbU,13384
51
52
  dcicutils/project_utils.py,sha256=qPdCaFmWUVBJw4rw342iUytwdQC0P-XKpK4mhyIulMM,31250
52
53
  dcicutils/qa_checkers.py,sha256=cdXjeL0jCDFDLT8VR8Px78aS10hwNISOO5G_Zv2TZ6M,20534
53
54
  dcicutils/qa_utils.py,sha256=TT0SiJWiuxYvbsIyhK9VO4uV_suxhB6CpuC4qPacCzQ,160208
@@ -71,8 +72,8 @@ dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
71
72
  dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
72
73
  dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
73
74
  dcicutils/zip_utils.py,sha256=rnjNv_k6L9jT2SjDSgVXp4BEJYLtz9XN6Cl2Fy-tqnM,2027
74
- dcicutils-8.8.2.1b1.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
75
- dcicutils-8.8.2.1b1.dist-info/METADATA,sha256=y0E16FdQzr2HBqpXc6B8c9GlSegNf6FHe5K-NjWdhp8,3356
76
- dcicutils-8.8.2.1b1.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
77
- dcicutils-8.8.2.1b1.dist-info/entry_points.txt,sha256=51Q4F_2V10L0282W7HFjP4jdzW4K8lnWDARJQVFy_hw,270
78
- dcicutils-8.8.2.1b1.dist-info/RECORD,,
75
+ dcicutils-8.8.2.1b3.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
76
+ dcicutils-8.8.2.1b3.dist-info/METADATA,sha256=uWWYit7e1yh-LNLXIOtDXxPyDTi2mIB62N112vQJaas,3356
77
+ dcicutils-8.8.2.1b3.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
78
+ dcicutils-8.8.2.1b3.dist-info/entry_points.txt,sha256=51Q4F_2V10L0282W7HFjP4jdzW4K8lnWDARJQVFy_hw,270
79
+ dcicutils-8.8.2.1b3.dist-info/RECORD,,