Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: How to wait (in a blocking way) for messages from widget frontend? #3039

Open
anant-k-singh opened this issue Dec 17, 2020 · 6 comments
Labels
Custom Widget Issues related to using ipywidgets as a framework for custom widgets

Comments

@anant-k-singh
Copy link

anant-k-singh commented Dec 17, 2020

I have created a custom widget whose frontend renders a plot (using a NPM library). That library has an API that extracts the underlying data of the plot.

class My_Custom_Widget(widgets.DOMWidget):
    ...
    def wait_for_change(self, value):
        future = Future()
        # Callback for visual data
        def get_value(change):
            future.set_result(change.new)
            self.unobserve(get_value, value)

        self.observe(get_value, value)
        return future

    async def extract_data_async(self, visual_name):
        # "extract_data_request" is part of widget model (a traitlet)
        # Changing this model initiates the JS logic on frontend, data will be returned back by updating another model variable "visual_data"
        self.extract_data_request = {
            'visualName': visual_name
        }
        res = await self.wait_for_change('visual_data_response')
        return res

In the Jupyter notebook, I run the following:

extracted_data = await my_widget.extract_data_async(visual_name='vis_id#4')

I want to use this extracted data for ML purposes.

Current behavior: The kernel is blocked at line res = await self.wait_for_change(... forever since the get_value() never executes due to kernel being blocked, kinda deadlock :(

According to ipywidget docs,

You may want to pause your Python code to wait for some user interaction with a widget from the frontend. Typically this would be hard to do since running Python code blocks any widget messages from the frontend until the Python code is done.

Since the kernel is blocked (due to await), it doesn't execute the get_value() method hence future is never resolved/completed.

Please suggest an alternate approach.
An approach where kernel is not blocked by the data is received in the extracted_data variable after some time would also be helpful

Thanks in advance!

@ianhi
Copy link
Contributor

ianhi commented Dec 18, 2020

I think this is a tricky problem that has not yet been fully solved. There was some good discussion here: https://github.com/martinRenou/ipycanvas/issues/77

Two people who maybe are interested are @martinRenou and @jtpio

@ianhi
Copy link
Contributor

ianhi commented Dec 18, 2020

imjoy is an alternative framework on which to build widgets and has implemented https://github.com/imjoy-team/imjoy-rpc which allows for what you want. So also cc. @oeway

I think the relevant lines of how imjoy-rpc does it are these: https://github.com/imjoy-team/imjoy-rpc/blob/30835b1e36d3868ea80a8e2cb3339b9e3048a230/python/imjoy_rpc/rpc.py#L324

It would be really nice to have something like that available through ipywidgets as well.

@oeway
Copy link

oeway commented Dec 18, 2020

@ianhi Thanks for tagging me here and advertise ImJoy!

@anant-k-singh To clarify, we had the same issue as you describe here, and I think there isn't a good solution yet. As I understand, this limitation is come form the ipykernel, you can see it also here: ipython/ipykernel#65. There is a work around by caching the jupyter command with https://github.com/kafonek/ipython_blocking, but I think it won't solve the issue you have. I was told that there is an async kernel might solve this, but I haven't figure it out yet. For now, what we did is to avoid using top-level await.

BTW, I saw you also posted another issue here. If that's the same task you are trying to solve, you might want to try imjoy-jupyter-extension indeed. It is a complementary solution for making interactive widgets. Instead of using a data binding model, we provide an RPC interface which allows you call your JS function in python and vice versa. Here are two examples that works in Jupyter notebooks with the imjoy-jupyter-extension: Kaibu in VueJS and vizarr in react (click the "Open in Binder" badge to run the demo). Briefly, assuming you have a web app in react/vuejs and you want to use it in jupyter notebook, what you do is to load the imjoy-rpc js library into your app, then export some js functions (e.g. process_in_js()), that's it. To use your web app in a notebook as a plugin, you can call imjoy api(api.createWindow(src="URL_OF_YOUR_APP")) to to instantiate an iframe window for your web app, then you can just call the js function as if they are defined in python. If you pass a callback function from python to js, then you can call that function also from your frontend. To learn more, a more complete tutorial is here.

@jasongrout
Copy link
Member

Here is a possibly helpful snippet to try to get an unblocked await: https://gist.github.com/minrk/feaf2022bf43d1a94e03ceaf9a4ef355

@ianhi
Copy link
Contributor

ianhi commented Feb 26, 2021

@jasongrout how do you mean by unblocked? Does that mean it doesn't prevent the message processing?


Also in re-searching about this I came across #2417 and with some more clicking to https://gitter.im/jupyter-widgets/Lobby?at=5e86fe9381a582042e972b4d which looks like it may be the best way to solve awaiting a message from the frontend?

@ianhi ianhi added the Custom Widget Issues related to using ipywidgets as a framework for custom widgets label Apr 1, 2021
@davidbrochart
Copy link
Member

I was told that there is an async kernel might solve this, but I haven't figure it out yet.

akernel is a new Python asynchronous kernel I'm working on. It is still very experimental, but it allows to write the example in the documentation without creating a task, i.e. you can await at the top-level and it won't prevent receiving the value change:

for i in range(10):
    print('did work %s'%i)
    x = await wait_for_change(slider, 'value')
    print('async function continued with value %s'%x)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Custom Widget Issues related to using ipywidgets as a framework for custom widgets
Projects
None yet
Development

No branches or pull requests

5 participants