jpayne@68: """ jpayne@68: IPython/Jupyter Notebook progressbar decorator for iterators. jpayne@68: Includes a default `range` iterator printing to `stderr`. jpayne@68: jpayne@68: Usage: jpayne@68: >>> from tqdm.notebook import trange, tqdm jpayne@68: >>> for i in trange(10): jpayne@68: ... ... jpayne@68: """ jpayne@68: # import compatibility functions and utilities jpayne@68: import re jpayne@68: import sys jpayne@68: from html import escape jpayne@68: from weakref import proxy jpayne@68: jpayne@68: # to inherit from the tqdm class jpayne@68: from .std import tqdm as std_tqdm jpayne@68: jpayne@68: if True: # pragma: no cover jpayne@68: # import IPython/Jupyter base widget and display utilities jpayne@68: IPY = 0 jpayne@68: try: # IPython 4.x jpayne@68: import ipywidgets jpayne@68: IPY = 4 jpayne@68: except ImportError: # IPython 3.x / 2.x jpayne@68: IPY = 32 jpayne@68: import warnings jpayne@68: with warnings.catch_warnings(): jpayne@68: warnings.filterwarnings( jpayne@68: 'ignore', message=".*The `IPython.html` package has been deprecated.*") jpayne@68: try: jpayne@68: import IPython.html.widgets as ipywidgets # NOQA: F401 jpayne@68: except ImportError: jpayne@68: pass jpayne@68: jpayne@68: try: # IPython 4.x / 3.x jpayne@68: if IPY == 32: jpayne@68: from IPython.html.widgets import HTML jpayne@68: from IPython.html.widgets import FloatProgress as IProgress jpayne@68: from IPython.html.widgets import HBox jpayne@68: IPY = 3 jpayne@68: else: jpayne@68: from ipywidgets import HTML jpayne@68: from ipywidgets import FloatProgress as IProgress jpayne@68: from ipywidgets import HBox jpayne@68: except ImportError: jpayne@68: try: # IPython 2.x jpayne@68: from IPython.html.widgets import HTML jpayne@68: from IPython.html.widgets import ContainerWidget as HBox jpayne@68: from IPython.html.widgets import FloatProgressWidget as IProgress jpayne@68: IPY = 2 jpayne@68: except ImportError: jpayne@68: IPY = 0 jpayne@68: IProgress = None jpayne@68: HBox = object jpayne@68: jpayne@68: try: jpayne@68: from IPython.display import display # , clear_output jpayne@68: except ImportError: jpayne@68: pass jpayne@68: jpayne@68: __author__ = {"github.com/": ["lrq3000", "casperdcl", "alexanderkuk"]} jpayne@68: __all__ = ['tqdm_notebook', 'tnrange', 'tqdm', 'trange'] jpayne@68: WARN_NOIPYW = ("IProgress not found. Please update jupyter and ipywidgets." jpayne@68: " See https://ipywidgets.readthedocs.io/en/stable" jpayne@68: "/user_install.html") jpayne@68: jpayne@68: jpayne@68: class TqdmHBox(HBox): jpayne@68: """`ipywidgets.HBox` with a pretty representation""" jpayne@68: def _json_(self, pretty=None): jpayne@68: pbar = getattr(self, 'pbar', None) jpayne@68: if pbar is None: jpayne@68: return {} jpayne@68: d = pbar.format_dict jpayne@68: if pretty is not None: jpayne@68: d["ascii"] = not pretty jpayne@68: return d jpayne@68: jpayne@68: def __repr__(self, pretty=False): jpayne@68: pbar = getattr(self, 'pbar', None) jpayne@68: if pbar is None: jpayne@68: return super().__repr__() jpayne@68: return pbar.format_meter(**self._json_(pretty)) jpayne@68: jpayne@68: def _repr_pretty_(self, pp, *_, **__): jpayne@68: pp.text(self.__repr__(True)) jpayne@68: jpayne@68: jpayne@68: class tqdm_notebook(std_tqdm): jpayne@68: """ jpayne@68: Experimental IPython/Jupyter Notebook widget using tqdm! jpayne@68: """ jpayne@68: @staticmethod jpayne@68: def status_printer(_, total=None, desc=None, ncols=None): jpayne@68: """ jpayne@68: Manage the printing of an IPython/Jupyter Notebook progress bar widget. jpayne@68: """ jpayne@68: # Fallback to text bar if there's no total jpayne@68: # DEPRECATED: replaced with an 'info' style bar jpayne@68: # if not total: jpayne@68: # return super(tqdm_notebook, tqdm_notebook).status_printer(file) jpayne@68: jpayne@68: # fp = file jpayne@68: jpayne@68: # Prepare IPython progress bar jpayne@68: if IProgress is None: # #187 #451 #558 #872 jpayne@68: raise ImportError(WARN_NOIPYW) jpayne@68: if total: jpayne@68: pbar = IProgress(min=0, max=total) jpayne@68: else: # No total? Show info style bar with no progress tqdm status jpayne@68: pbar = IProgress(min=0, max=1) jpayne@68: pbar.value = 1 jpayne@68: pbar.bar_style = 'info' jpayne@68: if ncols is None: jpayne@68: pbar.layout.width = "20px" jpayne@68: jpayne@68: ltext = HTML() jpayne@68: rtext = HTML() jpayne@68: if desc: jpayne@68: ltext.value = desc jpayne@68: container = TqdmHBox(children=[ltext, pbar, rtext]) jpayne@68: # Prepare layout jpayne@68: if ncols is not None: # use default style of ipywidgets jpayne@68: # ncols could be 100, "100px", "100%" jpayne@68: ncols = str(ncols) # ipywidgets only accepts string jpayne@68: try: jpayne@68: if int(ncols) > 0: # isnumeric and positive jpayne@68: ncols += 'px' jpayne@68: except ValueError: jpayne@68: pass jpayne@68: pbar.layout.flex = '2' jpayne@68: container.layout.width = ncols jpayne@68: container.layout.display = 'inline-flex' jpayne@68: container.layout.flex_flow = 'row wrap' jpayne@68: jpayne@68: return container jpayne@68: jpayne@68: def display(self, msg=None, pos=None, jpayne@68: # additional signals jpayne@68: close=False, bar_style=None, check_delay=True): jpayne@68: # Note: contrary to native tqdm, msg='' does NOT clear bar jpayne@68: # goal is to keep all infos if error happens so user knows jpayne@68: # at which iteration the loop failed. jpayne@68: jpayne@68: # Clear previous output (really necessary?) jpayne@68: # clear_output(wait=1) jpayne@68: jpayne@68: if not msg and not close: jpayne@68: d = self.format_dict jpayne@68: # remove {bar} jpayne@68: d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( jpayne@68: "{bar}", "") jpayne@68: msg = self.format_meter(**d) jpayne@68: jpayne@68: ltext, pbar, rtext = self.container.children jpayne@68: pbar.value = self.n jpayne@68: jpayne@68: if msg: jpayne@68: msg = msg.replace(' ', u'\u2007') # fix html space padding jpayne@68: # html escape special characters (like '&') jpayne@68: if '' in msg: jpayne@68: left, right = map(escape, re.split(r'\|?\|?', msg, maxsplit=1)) jpayne@68: else: jpayne@68: left, right = '', escape(msg) jpayne@68: jpayne@68: # Update description jpayne@68: ltext.value = left jpayne@68: # never clear the bar (signal: msg='') jpayne@68: if right: jpayne@68: rtext.value = right jpayne@68: jpayne@68: # Change bar style jpayne@68: if bar_style: jpayne@68: # Hack-ish way to avoid the danger bar_style being overridden by jpayne@68: # success because the bar gets closed after the error... jpayne@68: if pbar.bar_style != 'danger' or bar_style != 'success': jpayne@68: pbar.bar_style = bar_style jpayne@68: jpayne@68: # Special signal to close the bar jpayne@68: if close and pbar.bar_style != 'danger': # hide only if no error jpayne@68: try: jpayne@68: self.container.close() jpayne@68: except AttributeError: jpayne@68: self.container.visible = False jpayne@68: self.container.layout.visibility = 'hidden' # IPYW>=8 jpayne@68: jpayne@68: if check_delay and self.delay > 0 and not self.displayed: jpayne@68: display(self.container) jpayne@68: self.displayed = True jpayne@68: jpayne@68: @property jpayne@68: def colour(self): jpayne@68: if hasattr(self, 'container'): jpayne@68: return self.container.children[-2].style.bar_color jpayne@68: jpayne@68: @colour.setter jpayne@68: def colour(self, bar_color): jpayne@68: if hasattr(self, 'container'): jpayne@68: self.container.children[-2].style.bar_color = bar_color jpayne@68: jpayne@68: def __init__(self, *args, **kwargs): jpayne@68: """ jpayne@68: Supports the usual `tqdm.tqdm` parameters as well as those listed below. jpayne@68: jpayne@68: Parameters jpayne@68: ---------- jpayne@68: display : Whether to call `display(self.container)` immediately jpayne@68: [default: True]. jpayne@68: """ jpayne@68: kwargs = kwargs.copy() jpayne@68: # Setup default output jpayne@68: file_kwarg = kwargs.get('file', sys.stderr) jpayne@68: if file_kwarg is sys.stderr or file_kwarg is None: jpayne@68: kwargs['file'] = sys.stdout # avoid the red block in IPython jpayne@68: jpayne@68: # Initialize parent class + avoid printing by using gui=True jpayne@68: kwargs['gui'] = True jpayne@68: # convert disable = None to False jpayne@68: kwargs['disable'] = bool(kwargs.get('disable', False)) jpayne@68: colour = kwargs.pop('colour', None) jpayne@68: display_here = kwargs.pop('display', True) jpayne@68: super().__init__(*args, **kwargs) jpayne@68: if self.disable or not kwargs['gui']: jpayne@68: self.disp = lambda *_, **__: None jpayne@68: return jpayne@68: jpayne@68: # Get bar width jpayne@68: self.ncols = '100%' if self.dynamic_ncols else kwargs.get("ncols", None) jpayne@68: jpayne@68: # Replace with IPython progress bar display (with correct total) jpayne@68: unit_scale = 1 if self.unit_scale is True else self.unit_scale or 1 jpayne@68: total = self.total * unit_scale if self.total else self.total jpayne@68: self.container = self.status_printer(self.fp, total, self.desc, self.ncols) jpayne@68: self.container.pbar = proxy(self) jpayne@68: self.displayed = False jpayne@68: if display_here and self.delay <= 0: jpayne@68: display(self.container) jpayne@68: self.displayed = True jpayne@68: self.disp = self.display jpayne@68: self.colour = colour jpayne@68: jpayne@68: # Print initial bar state jpayne@68: if not self.disable: jpayne@68: self.display(check_delay=False) jpayne@68: jpayne@68: def __iter__(self): jpayne@68: try: jpayne@68: it = super().__iter__() jpayne@68: for obj in it: jpayne@68: # return super(tqdm...) will not catch exception jpayne@68: yield obj jpayne@68: # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt jpayne@68: except: # NOQA jpayne@68: self.disp(bar_style='danger') jpayne@68: raise jpayne@68: # NB: don't `finally: close()` jpayne@68: # since this could be a shared bar which the user will `reset()` jpayne@68: jpayne@68: def update(self, n=1): jpayne@68: try: jpayne@68: return super().update(n=n) jpayne@68: # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt jpayne@68: except: # NOQA jpayne@68: # cannot catch KeyboardInterrupt when using manual tqdm jpayne@68: # as the interrupt will most likely happen on another statement jpayne@68: self.disp(bar_style='danger') jpayne@68: raise jpayne@68: # NB: don't `finally: close()` jpayne@68: # since this could be a shared bar which the user will `reset()` jpayne@68: jpayne@68: def close(self): jpayne@68: if self.disable: jpayne@68: return jpayne@68: super().close() jpayne@68: # Try to detect if there was an error or KeyboardInterrupt jpayne@68: # in manual mode: if n < total, things probably got wrong jpayne@68: if self.total and self.n < self.total: jpayne@68: self.disp(bar_style='danger', check_delay=False) jpayne@68: else: jpayne@68: if self.leave: jpayne@68: self.disp(bar_style='success', check_delay=False) jpayne@68: else: jpayne@68: self.disp(close=True, check_delay=False) jpayne@68: jpayne@68: def clear(self, *_, **__): jpayne@68: pass jpayne@68: jpayne@68: def reset(self, total=None): jpayne@68: """ jpayne@68: Resets to 0 iterations for repeated use. jpayne@68: jpayne@68: Consider combining with `leave=True`. jpayne@68: jpayne@68: Parameters jpayne@68: ---------- jpayne@68: total : int or float, optional. Total to use for the new bar. jpayne@68: """ jpayne@68: if self.disable: jpayne@68: return super().reset(total=total) jpayne@68: _, pbar, _ = self.container.children jpayne@68: pbar.bar_style = '' jpayne@68: if total is not None: jpayne@68: pbar.max = total jpayne@68: if not self.total and self.ncols is None: # no longer unknown total jpayne@68: pbar.layout.width = None # reset width jpayne@68: return super().reset(total=total) jpayne@68: jpayne@68: jpayne@68: def tnrange(*args, **kwargs): jpayne@68: """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" jpayne@68: return tqdm_notebook(range(*args), **kwargs) jpayne@68: jpayne@68: jpayne@68: # Aliases jpayne@68: tqdm = tqdm_notebook jpayne@68: trange = tnrange