notebook.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. """
  2. IPython/Jupyter Notebook progressbar decorator for iterators.
  3. Includes a default `range` iterator printing to `stderr`.
  4. Usage:
  5. >>> from tqdm.notebook import trange, tqdm
  6. >>> for i in trange(10):
  7. ... ...
  8. """
  9. # import compatibility functions and utilities
  10. import re
  11. import sys
  12. from html import escape
  13. from weakref import proxy
  14. # to inherit from the tqdm class
  15. from .std import tqdm as std_tqdm
  16. if True: # pragma: no cover
  17. # import IPython/Jupyter base widget and display utilities
  18. IPY = 0
  19. try: # IPython 4.x
  20. import ipywidgets
  21. IPY = 4
  22. except ImportError: # IPython 3.x / 2.x
  23. IPY = 32
  24. import warnings
  25. with warnings.catch_warnings():
  26. warnings.filterwarnings(
  27. 'ignore', message=".*The `IPython.html` package has been deprecated.*")
  28. try:
  29. import IPython.html.widgets as ipywidgets # NOQA: F401
  30. except ImportError:
  31. pass
  32. try: # IPython 4.x / 3.x
  33. if IPY == 32:
  34. from IPython.html.widgets import HTML
  35. from IPython.html.widgets import FloatProgress as IProgress
  36. from IPython.html.widgets import HBox
  37. IPY = 3
  38. else:
  39. from ipywidgets import HTML
  40. from ipywidgets import FloatProgress as IProgress
  41. from ipywidgets import HBox
  42. except ImportError:
  43. try: # IPython 2.x
  44. from IPython.html.widgets import HTML
  45. from IPython.html.widgets import ContainerWidget as HBox
  46. from IPython.html.widgets import FloatProgressWidget as IProgress
  47. IPY = 2
  48. except ImportError:
  49. IPY = 0
  50. IProgress = None
  51. HBox = object
  52. try:
  53. from IPython.display import display # , clear_output
  54. except ImportError:
  55. pass
  56. __author__ = {"github.com/": ["lrq3000", "casperdcl", "alexanderkuk"]}
  57. __all__ = ['tqdm_notebook', 'tnrange', 'tqdm', 'trange']
  58. WARN_NOIPYW = ("IProgress not found. Please update jupyter and ipywidgets."
  59. " See https://ipywidgets.readthedocs.io/en/stable"
  60. "/user_install.html")
  61. class TqdmHBox(HBox):
  62. """`ipywidgets.HBox` with a pretty representation"""
  63. def _json_(self, pretty=None):
  64. pbar = getattr(self, 'pbar', None)
  65. if pbar is None:
  66. return {}
  67. d = pbar.format_dict
  68. if pretty is not None:
  69. d["ascii"] = not pretty
  70. return d
  71. def __repr__(self, pretty=False):
  72. pbar = getattr(self, 'pbar', None)
  73. if pbar is None:
  74. return super(TqdmHBox, self).__repr__()
  75. return pbar.format_meter(**self._json_(pretty))
  76. def _repr_pretty_(self, pp, *_, **__):
  77. pp.text(self.__repr__(True))
  78. class tqdm_notebook(std_tqdm):
  79. """
  80. Experimental IPython/Jupyter Notebook widget using tqdm!
  81. """
  82. @staticmethod
  83. def status_printer(_, total=None, desc=None, ncols=None):
  84. """
  85. Manage the printing of an IPython/Jupyter Notebook progress bar widget.
  86. """
  87. # Fallback to text bar if there's no total
  88. # DEPRECATED: replaced with an 'info' style bar
  89. # if not total:
  90. # return super(tqdm_notebook, tqdm_notebook).status_printer(file)
  91. # fp = file
  92. # Prepare IPython progress bar
  93. if IProgress is None: # #187 #451 #558 #872
  94. raise ImportError(WARN_NOIPYW)
  95. if total:
  96. pbar = IProgress(min=0, max=total)
  97. else: # No total? Show info style bar with no progress tqdm status
  98. pbar = IProgress(min=0, max=1)
  99. pbar.value = 1
  100. pbar.bar_style = 'info'
  101. if ncols is None:
  102. pbar.layout.width = "20px"
  103. ltext = HTML()
  104. rtext = HTML()
  105. if desc:
  106. ltext.value = desc
  107. container = TqdmHBox(children=[ltext, pbar, rtext])
  108. # Prepare layout
  109. if ncols is not None: # use default style of ipywidgets
  110. # ncols could be 100, "100px", "100%"
  111. ncols = str(ncols) # ipywidgets only accepts string
  112. try:
  113. if int(ncols) > 0: # isnumeric and positive
  114. ncols += 'px'
  115. except ValueError:
  116. pass
  117. pbar.layout.flex = '2'
  118. container.layout.width = ncols
  119. container.layout.display = 'inline-flex'
  120. container.layout.flex_flow = 'row wrap'
  121. return container
  122. def display(self, msg=None, pos=None,
  123. # additional signals
  124. close=False, bar_style=None, check_delay=True):
  125. # Note: contrary to native tqdm, msg='' does NOT clear bar
  126. # goal is to keep all infos if error happens so user knows
  127. # at which iteration the loop failed.
  128. # Clear previous output (really necessary?)
  129. # clear_output(wait=1)
  130. if not msg and not close:
  131. d = self.format_dict
  132. # remove {bar}
  133. d['bar_format'] = (d['bar_format'] or "{l_bar}<bar/>{r_bar}").replace(
  134. "{bar}", "<bar/>")
  135. msg = self.format_meter(**d)
  136. ltext, pbar, rtext = self.container.children
  137. pbar.value = self.n
  138. if msg:
  139. # html escape special characters (like '&')
  140. if '<bar/>' in msg:
  141. left, right = map(escape, re.split(r'\|?<bar/>\|?', msg, maxsplit=1))
  142. else:
  143. left, right = '', escape(msg)
  144. # Update description
  145. ltext.value = left
  146. # never clear the bar (signal: msg='')
  147. if right:
  148. rtext.value = right
  149. # Change bar style
  150. if bar_style:
  151. # Hack-ish way to avoid the danger bar_style being overridden by
  152. # success because the bar gets closed after the error...
  153. if pbar.bar_style != 'danger' or bar_style != 'success':
  154. pbar.bar_style = bar_style
  155. # Special signal to close the bar
  156. if close and pbar.bar_style != 'danger': # hide only if no error
  157. try:
  158. self.container.close()
  159. except AttributeError:
  160. self.container.visible = False
  161. self.container.layout.visibility = 'hidden' # IPYW>=8
  162. if check_delay and self.delay > 0 and not self.displayed:
  163. display(self.container)
  164. self.displayed = True
  165. @property
  166. def colour(self):
  167. if hasattr(self, 'container'):
  168. return self.container.children[-2].style.bar_color
  169. @colour.setter
  170. def colour(self, bar_color):
  171. if hasattr(self, 'container'):
  172. self.container.children[-2].style.bar_color = bar_color
  173. def __init__(self, *args, **kwargs):
  174. """
  175. Supports the usual `tqdm.tqdm` parameters as well as those listed below.
  176. Parameters
  177. ----------
  178. display : Whether to call `display(self.container)` immediately
  179. [default: True].
  180. """
  181. kwargs = kwargs.copy()
  182. # Setup default output
  183. file_kwarg = kwargs.get('file', sys.stderr)
  184. if file_kwarg is sys.stderr or file_kwarg is None:
  185. kwargs['file'] = sys.stdout # avoid the red block in IPython
  186. # Initialize parent class + avoid printing by using gui=True
  187. kwargs['gui'] = True
  188. # convert disable = None to False
  189. kwargs['disable'] = bool(kwargs.get('disable', False))
  190. colour = kwargs.pop('colour', None)
  191. display_here = kwargs.pop('display', True)
  192. super(tqdm_notebook, self).__init__(*args, **kwargs)
  193. if self.disable or not kwargs['gui']:
  194. self.disp = lambda *_, **__: None
  195. return
  196. # Get bar width
  197. self.ncols = '100%' if self.dynamic_ncols else kwargs.get("ncols", None)
  198. # Replace with IPython progress bar display (with correct total)
  199. unit_scale = 1 if self.unit_scale is True else self.unit_scale or 1
  200. total = self.total * unit_scale if self.total else self.total
  201. self.container = self.status_printer(self.fp, total, self.desc, self.ncols)
  202. self.container.pbar = proxy(self)
  203. self.displayed = False
  204. if display_here and self.delay <= 0:
  205. display(self.container)
  206. self.displayed = True
  207. self.disp = self.display
  208. self.colour = colour
  209. # Print initial bar state
  210. if not self.disable:
  211. self.display(check_delay=False)
  212. def __iter__(self):
  213. try:
  214. it = super(tqdm_notebook, self).__iter__()
  215. for obj in it:
  216. # return super(tqdm...) will not catch exception
  217. yield obj
  218. # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt
  219. except: # NOQA
  220. self.disp(bar_style='danger')
  221. raise
  222. # NB: don't `finally: close()`
  223. # since this could be a shared bar which the user will `reset()`
  224. def update(self, n=1):
  225. try:
  226. return super(tqdm_notebook, self).update(n=n)
  227. # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt
  228. except: # NOQA
  229. # cannot catch KeyboardInterrupt when using manual tqdm
  230. # as the interrupt will most likely happen on another statement
  231. self.disp(bar_style='danger')
  232. raise
  233. # NB: don't `finally: close()`
  234. # since this could be a shared bar which the user will `reset()`
  235. def close(self):
  236. if self.disable:
  237. return
  238. super(tqdm_notebook, self).close()
  239. # Try to detect if there was an error or KeyboardInterrupt
  240. # in manual mode: if n < total, things probably got wrong
  241. if self.total and self.n < self.total:
  242. self.disp(bar_style='danger', check_delay=False)
  243. else:
  244. if self.leave:
  245. self.disp(bar_style='success', check_delay=False)
  246. else:
  247. self.disp(close=True, check_delay=False)
  248. def clear(self, *_, **__):
  249. pass
  250. def reset(self, total=None):
  251. """
  252. Resets to 0 iterations for repeated use.
  253. Consider combining with `leave=True`.
  254. Parameters
  255. ----------
  256. total : int or float, optional. Total to use for the new bar.
  257. """
  258. if self.disable:
  259. return super(tqdm_notebook, self).reset(total=total)
  260. _, pbar, _ = self.container.children
  261. pbar.bar_style = ''
  262. if total is not None:
  263. pbar.max = total
  264. if not self.total and self.ncols is None: # no longer unknown total
  265. pbar.layout.width = None # reset width
  266. return super(tqdm_notebook, self).reset(total=total)
  267. def tnrange(*args, **kwargs):
  268. """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`."""
  269. return tqdm_notebook(range(*args), **kwargs)
  270. # Aliases
  271. tqdm = tqdm_notebook
  272. trange = tnrange