6
6
import textwrap as _textwrap
7
7
import re
8
8
from typing import Tuple , Dict
9
- from multiprocessing import Process
9
+ from multiprocessing import Process , Queue
10
10
import psutil
11
11
import turnkeyml .common .printing as printing
12
12
import turnkeyml .common .exceptions as exp
15
15
from turnkeyml .state import State
16
16
17
17
18
- def _spinner (message ):
18
+ def _spinner (message , q : Queue ):
19
+ """
20
+ Displays a moving "..." indicator so that the user knows that the
21
+ Tool is still working. Tools can optionally use a multiprocessing
22
+ Queue to display the percent progress of the Tool.
23
+ """
24
+ percent_complete = None
25
+
19
26
try :
20
27
parent_process = psutil .Process (pid = os .getppid ())
21
28
while parent_process .status () == psutil .STATUS_RUNNING :
22
29
for cursor in [" " , ". " , ".. " , "..." ]:
23
30
time .sleep (0.5 )
24
- status = f" { message } { cursor } \r "
31
+ if not q .empty ():
32
+ percent_complete = q .get ()
33
+ if percent_complete is not None :
34
+ status = f" { message } ({ percent_complete :.1f} %){ cursor } \r "
35
+ else :
36
+ status = f" { message } { cursor } \r "
25
37
sys .stdout .write (status )
26
38
sys .stdout .flush ()
27
39
except psutil .NoSuchProcess :
@@ -146,17 +158,22 @@ def status_line(self, successful, verbosity):
146
158
success_tick = "+"
147
159
fail_tick = "x"
148
160
161
+ if self .percent_progress is None :
162
+ progress_indicator = ""
163
+ else :
164
+ progress_indicator = f" ({ self .percent_progress :.1f} %)"
165
+
149
166
if successful is None :
150
167
# Initialize the message
151
168
printing .logn (f" { self .monitor_message } " )
152
169
elif successful :
153
170
# Print success message
154
171
printing .log (f" { success_tick } " , c = printing .Colors .OKGREEN )
155
- printing .logn (self .monitor_message + " " )
172
+ printing .logn (self .monitor_message + progress_indicator + " " )
156
173
else :
157
174
# successful == False, print failure message
158
175
printing .log (f" { fail_tick } " , c = printing .Colors .FAIL )
159
- printing .logn (self .monitor_message + " " )
176
+ printing .logn (self .monitor_message + progress_indicator + " " )
160
177
161
178
def __init__ (
162
179
self ,
@@ -169,6 +186,8 @@ def __init__(
169
186
self .duration_key = f"{ fs .Keys .TOOL_DURATION } :{ self .__class__ .unique_name } "
170
187
self .monitor_message = monitor_message
171
188
self .progress = None
189
+ self .progress_queue = None
190
+ self .percent_progress = None
172
191
self .logfile_path = None
173
192
# Tools can disable build.Logger, which captures all stdout and stderr from
174
193
# the Tool, by setting enable_logger=False
@@ -192,6 +211,18 @@ def parser() -> argparse.ArgumentParser:
192
211
line interface for this Tool.
193
212
"""
194
213
214
+ def set_percent_progress (self , percent_progress : float ):
215
+ """
216
+ Update the progress monitor with a percent progress to let the user
217
+ know how much progress the Tool has made.
218
+ """
219
+
220
+ if not isinstance (percent_progress , float ):
221
+ raise ValueError (f"Input argument must be a float, got { percent_progress } " )
222
+
223
+ self .progress_queue .put (percent_progress )
224
+ self .percent_progress = percent_progress
225
+
195
226
# pylint: disable=unused-argument
196
227
def parse (self , state : State , args , known_only = True ) -> argparse .Namespace :
197
228
"""
@@ -250,7 +281,10 @@ def run_helper(
250
281
)
251
282
252
283
if monitor :
253
- self .progress = Process (target = _spinner , args = [self .monitor_message ])
284
+ self .progress_queue = Queue ()
285
+ self .progress = Process (
286
+ target = _spinner , args = (self .monitor_message , self .progress_queue )
287
+ )
254
288
self .progress .start ()
255
289
256
290
try :
0 commit comments