|Documentation Status| |Binder|
Block Jupyter cell execution while interacting with widgets.
This library is for people familiar with ipywidgets
who want to solve the
following problem:
ipywidgets
[#] or similarYou want to implement a notebook like the one below
.. code-block:: python
ui = make_ui() display(ui) data = ui.wait_for_data()
do_things_with(data)
do_more_tings()
And you want to be able to execute Cells -> Run All
menu option and still get correct output.
This library assists in implementing your custom ui.wait_for_data()
poll loop.
If you have tried implementing such workflow in the past you'll know that it is
not that simple. If you haven't, see Technical Details
_ section below for an
explanation on why it's hard and how jupyter-ui-poll
solves it.
Quick, self contained example:
.. code-block:: python
import time from ipywidgets import Button from jupyter_ui_poll import ui_events
ui_done = False def on_click(btn): global ui_done ui_done = True btn.description = '👍'
btn = Button(description='Click Me') btn.on_click(on_click) display(btn)
with ui_events() as poll: while ui_done is False: poll(10) # React to UI events (up to 10 at a time) print('.', end='') time.sleep(0.1) print('done')
For a more detailed tutorial see Example notebook
, you can also run it
right now using awesome Binder
_ service.
This library requires Python 3.6 or greater.
.. code-block::
pip install jupyter-ui-poll
conda install -c conda-forge jupyter-ui-poll
Jupyter widgets (ipywidgets
) provide an excellent foundation to develop
interactive data investigation apps directly inside Jupyter notebook or Jupyter
lab environment. Jupyter is great at displaying data and ipywidgets
provide
a mechanism to get input from the user in a more convenient way than entering or
changing Python code inside a Jupyter cell. Developer can construct an
interactive user interface often used to parameterize information display or
other kinds of computation.
Interactivity is handled with callbacks, ipywidget
GUI is HTML based, user
actions, like clicking a button, trigger JavaScript events that are then
translated in to calls to Python code developer registered with the library. It
is a significantly different, asynchronous, paradigm than your basic Jupyter
notebook which operates in a straightforward blocking, linear fashion. It is not
possible to display a Modal UI that would block execution of other Jupyter cells
until needed information is supplied by the user.
jupyter-ui-poll
allows one to implement a "blocking GUI" inside a Jupyter
environment. It is a common requirement to query user for some non-trivial input
parameters that are easier to enter via GUI rather than code. User input happens
at the top of the notebook, then that data is used in cells below. While this is
possible to achieve directly with ipywidgets
it requires teaching the user
to enter all the needed data before moving on to execute the cells below. This
is bound to cause some confusion and also breaks Cells -> Run All
functionality.
An obvious solution is to keep running in a loop until all the needed data was entered by the user.
.. code-block:: python
display(app.make_ui()) while not app.have_all_the_data(): time.sleep(0.1)
A naive version of the code above does not work. This is because no widget
events are being processed while executing code inside a Jupyter cell. Callbacks
you have registered with the widget library won't get a chance to run and so
state of app.have_all_the_data()
won't ever change. "Execute code inside
Jupyter cell" is just another event being processed by the IPython kernel, and
only one event is executed at a time. One could ask IPython kernel to process
more events by calling kernel.do_one_iteration()
in the poll loop. This
kinda works, callbacks will be called as input is entered, but IPython will also
process "execute cell" events, so Cells -> Run All
scenario will still be
broken, as code in lower cells will be executed before the data it operates on
becomes available.
This library hooks into IPython internal machinery to selectively execute events in a polling fashion, delaying code cell execution events until after interactive part is over.
Basic idea was copied from ipython_blocking
[#]_ project:
execute_request
handler in IPython kernel temporarilykernel.do_one_iteration()
in a polling fashion until exit conditions are metexecute_request
.. [#] https://jupyter.org/ .. [#] https://github.com/jupyter-widgets/ipywidgets .. [#] https://github.com/kafonek/ipython_blocking
.. _Example notebook: notebooks/Examples.ipynb .. _run it: https://mybinder.org/v2/gh/kirill888/jupyter-ui-poll/develop?filepath=notebooks%2FExamples.ipynb .. _Binder: https://mybinder.org/
.. |Documentation Status| image:: https://readthedocs.org/projects/jupyter-ui-poll/badge/?version=latest :target: https://jupyter-ui-poll.readthedocs.io/en/latest/?badge=latest
.. |Binder| image:: https://mybinder.org/badge_logo.svg :target: https://mybinder.org/v2/gh/kirill888/jupyter-ui-poll/develop?filepath=notebooks%2FExamples.ipynb :alt: Run Examples in Binder