CMS 3D CMS Logo

progressbar.py
Go to the documentation of this file.
1 import os
2 import sys
3 import time
4 import math
5 
6 
7 
8 
9 try:
10  from abc import ABCMeta, abstractmethod
11 except ImportError:
12  AbstractWidget = object
13  abstractmethod = lambda fn: fn
14 else:
15  AbstractWidget = ABCMeta('AbstractWidget', (object,), {})
16 class UnknownLength: pass
18  '''The base class for all widgets
19 
20  The ProgressBar will call the widget's update value when the widget should
21  be updated. The widget's size may change between calls, but the widget may
22  display incorrectly if the size changes drastically and repeatedly.
23 
24  The boolean TIME_SENSITIVE informs the ProgressBar that it should be
25  updated more often because it is time sensitive.
26  '''
27 
28  TIME_SENSITIVE = False
29  __slots__ = ()
30 
31  @abstractmethod
32  def update(self, pbar):
33  '''Updates the widget.
34 
35  pbar - a reference to the calling ProgressBar
36  '''
37 class Timer(Widget):
38  'Widget which displays the elapsed seconds.'
39 
40  __slots__ = ('format',)
41  TIME_SENSITIVE = True
42 
43  def __init__(self, format='Elapsed Time: %s'):
44  self.format = format
45 
46  @staticmethod
47  def format_time(seconds):
48  'Formats time as the string "HH:MM:SS".'
49 
50  return str(datetime.timedelta(seconds=int(seconds)))
51 
52 
53  def update(self, pbar):
54  'Updates the widget to show the elapsed time.'
55 
56  return self.format % self.format_time(pbar.seconds_elapsed)
58  '''The base class for all variable width widgets.
59 
60  This widget is much like the \\hfill command in TeX, it will expand to
61  fill the line. You can use more than one in the same line, and they will
62  all have the same width, and together will fill the line.
63  '''
64 
65  @abstractmethod
66  def update(self, pbar, width):
67  '''Updates the widget providing the total width the widget must fill.
68 
69  pbar - a reference to the calling ProgressBar
70  width - The total width the widget must fill
71  '''
72 class Bar(WidgetHFill):
73  'A progress bar which stretches to fill the line.'
74 
75  __slots__ = ('marker', 'left', 'right', 'fill', 'fill_left')
76 
77  def __init__(self, marker='#', left='|', right='|', fill=' ',
78  fill_left=True):
79  '''Creates a customizable progress bar.
80 
81  marker - string or updatable object to use as a marker
82  left - string or updatable object to use as a left border
83  right - string or updatable object to use as a right border
84  fill - character to use for the empty part of the progress bar
85  fill_left - whether to fill from the left or the right
86  '''
87  self.marker = marker
88  self.left = left
89  self.right = right
90  self.fill = fill
91  self.fill_left = fill_left
92 
93 
94  def update(self, pbar, width):
95  'Updates the progress bar and its subcomponents'
96 
97  left, marked, right = (format_updatable(i, pbar) for i in
98  (self.left, self.marker, self.right))
99 
100  width -= len(left) + len(right)
101  # Marked must *always* have length of 1
102  if pbar.maxval:
103  marked *= int(pbar.currval / pbar.maxval * width)
104  else:
105  marked = ''
106 
107  if self.fill_left:
108  return '%s%s%s' % (left, marked.ljust(width, self.fill), right)
109  else:
110  return '%s%s%s' % (left, marked.rjust(width, self.fill), right)
112  def update(self, pbar, width):
113  'Updates the progress bar and its subcomponents'
114 
115  left, marker, right = (format_updatable(i, pbar) for i in
116  (self.left, self.marker, self.right))
117 
118  width -= len(left) + len(right)
119 
120  if pbar.finished: return '%s%s%s' % (left, width * marker, right)
121 
122  position = int(pbar.currval % (width * 2 - 1))
123  if position > width: position = width * 2 - position
124  lpad = self.fill * (position - 1)
125  rpad = self.fill * (width - len(marker) - len(lpad))
126 
127  # Swap if we want to bounce the other way
128  if not self.fill_left: rpad, lpad = lpad, rpad
129 
130  return '%s%s%s%s%s' % (left, lpad, marker, rpad, right)
131 
133  'Displays a formatted label'
134 
135  mapping = {
136  'elapsed': ('seconds_elapsed', Timer.format_time),
137  'finished': ('finished', None),
138  'last_update': ('last_update_time', None),
139  'max': ('maxval', None),
140  'seconds': ('seconds_elapsed', None),
141  'start': ('start_time', None),
142  'value': ('currval', None)
143  }
144 
145  __slots__ = ('format',)
146  def __init__(self, format):
147  self.format = format
148 
149  def update(self, pbar):
150  context = {}
151  for name, (key, transform) in self.mapping.items():
152  try:
153  value = getattr(pbar, key)
154 
155  if transform is None:
156  context[name] = value
157  else:
158  context[name] = transform(value)
159  except: pass
160 
161  return self.format % context
162 
164  '''The ProgressBar class which updates and prints the bar.
165 
166  A common way of using it is like:
167  >>> pbar = ProgressBar().start()
168  >>> for i in range(100):
169  ... # do something
170  ... pbar.update(i+1)
171  ...
172  >>> pbar.finish()
173 
174  You can also use a ProgressBar as an iterator:
175  >>> progress = ProgressBar()
176  >>> for i in progress(some_iterable):
177  ... # do something
178  ...
179 
180  Since the progress bar is incredibly customizable you can specify
181  different widgets of any type in any order. You can even write your own
182  widgets! However, since there are already a good number of widgets you
183  should probably play around with them before moving on to create your own
184  widgets.
185 
186  The term_width parameter represents the current terminal width. If the
187  parameter is set to an integer then the progress bar will use that,
188  otherwise it will attempt to determine the terminal width falling back to
189  80 columns if the width cannot be determined.
190 
191  When implementing a widget's update method you are passed a reference to
192  the current progress bar. As a result, you have access to the
193  ProgressBar's methods and attributes. Although there is nothing preventing
194  you from changing the ProgressBar you should treat it as read only.
195 
196  Useful methods and attributes include (Public API):
197  - currval: current progress (0 <= currval <= maxval)
198  - maxval: maximum (and final) value
199  - finished: True if the bar has finished (reached 100%)
200  - start_time: the time when start() method of ProgressBar was called
201  - seconds_elapsed: seconds elapsed since start_time and last call to
202  update
203  - percentage(): progress in percent [0..100]
204  '''
205 
206  __slots__ = ('currval', 'fd', 'finished', 'last_update_time',
207  'left_justify', 'maxval', 'next_update', 'num_intervals',
208  'poll', 'seconds_elapsed', 'signal_set', 'start_time',
209  'term_width', 'update_interval', 'widgets', '_time_sensitive',
210  '__iterable')
211 
212  _DEFAULT_MAXVAL = 100
213  _DEFAULT_TERMSIZE = 80
214 
215  def __init__(self, maxval=None, widgets=None, term_width=None, poll=1,
216  left_justify=True, fd=sys.stderr):
217  '''Initializes a progress bar with sane defaults'''
218 
219  self.maxval = maxval
220  self.widgets = widgets
221  self.fd = fd
222  self.left_justify = left_justify
223 
224  self.signal_set = False
225  if term_width is not None:
226  self.term_width = term_width
227  else:
228  try:
229  self._handle_resize()
230  signal.signal(signal.SIGWINCH, self._handle_resize)
231  self.signal_set = True
232  except (SystemExit, KeyboardInterrupt): raise
233  except:
234  self.term_width = self._env_size()
235 
236  self.__iterable = None
237  self._update_widgets()
238  self.currval = 0
239  self.finished = False
240  self.last_update_time = None
241  self.poll = poll
243  self.start_time = None
245 
246 
247  def __call__(self, iterable):
248  'Use a ProgressBar to iterate through an iterable'
249 
250  try:
251  self.maxval = len(iterable)
252  except:
253  if self.maxval is None:
254  self.maxval = UnknownLength
255 
256  self.__iterable = iter(iterable)
257  return self
258 
259 
260  def __iter__(self):
261  return self
262 
263 
264  def __next__(self):
265  try:
266  value = next(self.__iterable)
267  if self.start_time is None: self.start()
268  else: self.update(self.currval + 1)
269  return value
270  except StopIteration:
271  self.finish()
272  raise
273 
274 
275  # Create an alias so that Python 2.x won't complain about not being
276  # an iterator.
277  next = __next__
278 
279 
280  def _env_size(self):
281  'Tries to find the term_width from the environment.'
282 
283  return int(os.environ.get('COLUMNS', self._DEFAULT_TERMSIZE)) - 1
284 
285 
286  def _handle_resize(self, signum=None, frame=None):
287  'Tries to catch resize signals sent from the terminal.'
288 
289  h, w = array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0' * 8))[:2]
290  self.term_width = w
291 
292 
293  def percentage(self):
294  'Returns the progress as a percentage.'
295  return self.currval * 100.0 / self.maxval
296 
297  percent = property(percentage)
298 
299 
300  def _format_widgets(self):
301  result = []
302  expanding = []
303  width = self.term_width
304 
305  for index, widget in enumerate(self.widgets):
306  if isinstance(widget, WidgetHFill):
307  result.append(widget)
308  expanding.insert(0, index)
309  else:
310  widget = format_updatable(widget, self)
311  result.append(widget)
312  width -= len(widget)
313 
314  count = len(expanding)
315  while count:
316  portion = max(int(math.ceil(width * 1. / count)), 0)
317  index = expanding.pop()
318  count -= 1
319 
320  widget = result[index].update(self, portion)
321  width -= len(widget)
322  result[index] = widget
323 
324  return result
325 
326 
327  def _format_line(self):
328  'Joins the widgets and justifies the line'
329 
330  widgets = ''.join(self._format_widgets())
331 
332  if self.left_justify: return widgets.ljust(self.term_width)
333  else: return widgets.rjust(self.term_width)
334 
335 
336  def _need_update(self):
337  'Returns whether the ProgressBar should redraw the line.'
338  if self.currval >= self.next_update or self.finished: return True
339 
340  delta = time.time() - self.last_update_time
341  return self._time_sensitive and delta > self.poll
342 
343 
344  def _update_widgets(self):
345  'Checks all widgets for the time sensitive bit'
346 
347  self._time_sensitive = any(getattr(w, 'TIME_SENSITIVE', False)
348  for w in self.widgets)
349 
350 
351  def update(self, value=None):
352  'Updates the ProgressBar to a new value.'
353 
354  if value is not None and value is not UnknownLength:
355  if (self.maxval is not UnknownLength
356  and not 0 <= value <= self.maxval):
357 
358  raise ValueError('Value out of range')
359 
360  self.currval = value
361 
362 
363  if not self._need_update(): return
364  if self.start_time is None:
365  raise RuntimeError('You must call "start" before calling "update"')
366 
367  now = time.time()
368  self.seconds_elapsed = now - self.start_time
370  self.fd.write(self._format_line() + '\r')
371  self.last_update_time = now
372 
373 
374  def start(self):
375  '''Starts measuring time, and prints the bar at 0%.
376 
377  It returns self so you can use it like this:
378  >>> pbar = ProgressBar().start()
379  >>> for i in range(100):
380  ... # do something
381  ... pbar.update(i+1)
382  ...
383  >>> pbar.finish()
384  '''
385 
386  if self.maxval is None:
387  self.maxval = self._DEFAULT_MAXVAL
388 
389  self.num_intervals = max(100, self.term_width)
390  self.next_update = 0
391 
392  if self.maxval is not UnknownLength:
393  if self.maxval < 0: raise ValueError('Value out of range')
394  self.update_interval = self.maxval / self.num_intervals
395 
396 
397  self.start_time = self.last_update_time = time.time()
398  self.update(0)
399 
400  return self
401 
402 
403  def finish(self):
404  'Puts the ProgressBar bar in the finished state.'
405 
406  self.finished = True
407  self.update(self.maxval)
408  self.fd.write('\n')
409  if self.signal_set:
410  signal.signal(signal.SIGWINCH, signal.SIG_DFL)
411 def format_updatable(updatable, pbar):
412  if hasattr(updatable, 'update'): return updatable.update(pbar)
413  else: return updatable
414 
415 
417  def __init__(self):
418  self.n = 1
419  def __iter__(self):
420  return self
421  def next(self):
422  return 1
def __call__(self, iterable)
Definition: progressbar.py:247
def update(self, pbar)
Definition: progressbar.py:53
def update(self, pbar)
Definition: progressbar.py:32
def update(self, pbar, width)
Definition: progressbar.py:112
def format_updatable(updatable, pbar)
Definition: progressbar.py:411
def __init__(self, format='Elapsed Time:%s')
Definition: progressbar.py:43
def update(self, pbar, width)
Definition: progressbar.py:94
def __init__(self, maxval=None, widgets=None, term_width=None, poll=1, left_justify=True, fd=sys.stderr)
Definition: progressbar.py:216
def format_time(seconds)
Definition: progressbar.py:47
def update(self, pbar)
Definition: progressbar.py:149
bool any(const std::vector< T > &v, const T &what)
Definition: ECalSD.cc:37
def _handle_resize(self, signum=None, frame=None)
Definition: progressbar.py:286
PROGRESSBAR Classes END ####.
Definition: progressbar.py:416
static std::string join(char **cmd)
Definition: RemoteFile.cc:19
def __init__(self, format)
Definition: progressbar.py:146
def update(self, pbar, width)
Definition: progressbar.py:66
def __init__(self, marker='#', left='|', right='|', fill=' ', fill_left=True)
Definition: progressbar.py:78
def update(self, value=None)
Definition: progressbar.py:351
#define str(s)
unsigned transform(const HcalDetId &id, unsigned transformCode)