Skip to content

Added Jupyter console widget and Example#2353

Merged
j9ac9k merged 18 commits intopyqtgraph:masterfrom
jonmatthis:jupyter-console-widget
Sep 1, 2022
Merged

Added Jupyter console widget and Example#2353
j9ac9k merged 18 commits intopyqtgraph:masterfrom
jonmatthis:jupyter-console-widget

Conversation

@jonmatthis
Copy link
Copy Markdown
Contributor

I added a JupyterConsole widget and made an Example showing basic use

this was based on the implementation from yaqc-qtpy which I learned about in a conversation on the #pyqtgraph channel in the Python discord: https://discord.com/channels/267624335836053506/898139460821192724/991360299158470676

I did my best to follow the CONTRIBUTING.md guidelines, but I'm relatively new at this so I might have missed something

self.addTab(self.rich_jupyter_widget, "console")

def execute_command(self,command_string:str=None)->bool:
return self.rich_jupyter_widget.execute(command_string)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you may have mixed tabs and spaces here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's part of the code I got from yaqc so i guess I can blame them 😅

I formatted the files with black, so it should be good now

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jul 2, 2022

Hi @jonmatthis

Thanks so much for this PR. I always thought pyqtgraph's console example probably wasn't that great and we would better serve our users by showing how to better integrate jupyter consoles or the qtconsole.

A couple of small issues I see, seems like the formatting isn't quite right, I think you may have a tab instead of spaces in one place and such; you may want to runblack on the new-file.

Also in pyqtgraph/examples/utils.py you reference a file that doesn't exist, but in this PR you add the file to pyqtgraph/widgets/.... I'm not sure if you forgot to include the example file, or if the file in the widgets directory should be in the examples.

EDIT: while I have no problem adding type-hints, to the code-base (eventually want to do that anyway)

@ntjess
Copy link
Copy Markdown
Contributor

ntjess commented Jul 2, 2022

Nifty! I imagine updating the kernel's namespace would be a fairly common operation; would you consider adding a method similar to pushVariables from this SO answer? I would also support adding a namespace argument to the constructor for this reason, like the old ConsoleWidget

@jonmatthis
Copy link
Copy Markdown
Contributor Author

Whoops, I forgot to add the example file to git. I think I just did that?

I'll go through your other comments now, thanks!

… method

Also added method to input 'namespace' on init (like old Console)
@jonmatthis
Copy link
Copy Markdown
Contributor Author

Nifty! I imagine updating the kernel's namespace would be a fairly common operation; would you consider adding a method similar to pushVariables from this SO answer?

I agree that's very useful! I added that method and showed and sample usage in the example

I would also support adding a namespace argument to the constructor for this reason, like the old ConsoleWidget

I did this too, I'm not 100% clear on the concept of a 'namespace' so it's possible I missed something important :)

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jul 3, 2022

Hi @jonmatthis

Thanks for updating the PR. I'm reluctant to add code here to pyqtgraph/widgets, I think this is small enough, the entirety can live as an example.

In the discord chat, @pijyoi was kind enough to make a suggestion for what an example could look like:

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets
from qtconsole import inprocess

class ConsoleWidget(inprocess.QtInProcessRichJupyterWidget):
    def __init__(self):
        super().__init__()
        
        self.kernel_manager = inprocess.QtInProcessKernelManager()
        self.kernel_manager.start_kernel()
        self.kernel_client = self.kernel_manager.client()
        self.kernel_client.start_channels()

    def shutdown_kernel(self):
        self.kernel_client.stop_channels()
        self.kernel_manager.shutdown_kernel()

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        cwidget = QtWidgets.QWidget()
        vbox = QtWidgets.QVBoxLayout()
        cwidget.setLayout(vbox)
        self.plot = pg.PlotWidget()
        self.console = ConsoleWidget()
        vbox.addWidget(self.plot)
        vbox.addWidget(self.console)
        self.setCentralWidget(cwidget)

        app = QtWidgets.QApplication.instance()
        app.aboutToQuit.connect(self.console.shutdown_kernel)

        kernel = self.console.kernel_manager.kernel
        kernel.shell.push(dict(np=np, pw=self.plot))
        self.console.execute("whos")

if __name__ == '__main__':
    pg.mkQApp()
    main = MainWindow()
    main.show()
    pg.exec()

There is some president here for demonstrating functionality in the examples that isn't really a part of the library directly, specifically the GLPainter example comes to mind.

@jonmatthis
Copy link
Copy Markdown
Contributor Author

I agree with this! It demonstrates the method to get a jupyter console widget, while keeping responsibility for maintaining that core functionality of that widget in the hands of the qtconsole folks, which is more appropriate

@jonmatthis
Copy link
Copy Markdown
Contributor Author

A few suggested tweaks to that example to make it a bit more transparent to low-XP folks (like me😅).

It's mostly just changing the variable names to make it (even) more obvious which variable is what, and an example of how to execute a command after creating the Widget.

Other potential cool additions would be -

  • show sample inputs to plot something simple in that Plot window
  • Adding a very simple paramter_tree widget that shows the variables returned by whos
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets
from qtconsole import inprocess


class JupyterConsoleWidget(inprocess.QtInProcessRichJupyterWidget):
    def __init__(self):
        super().__init__()

        self.kernel_manager = inprocess.QtInProcessKernelManager()
        self.kernel_manager.start_kernel()
        self.kernel_client = self.kernel_manager.client()
        self.kernel_client.start_channels()

    def shutdown_kernel(self):
        self.kernel_client.stop_channels()
        self.kernel_manager.shutdown_kernel()


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        central_widget = QtWidgets.QWidget()
        vbox_widget = QtWidgets.QVBoxLayout()
        central_widget.setLayout(vbox_widget)
        self.plot_widget = pg.PlotWidget()
        self.console = JupyterConsoleWidget()
        vbox_widget.addWidget(self.plot_widget)
        vbox_widget.addWidget(self.console)
        self.setCentralWidget(central_widget)

        app = QtWidgets.QApplication.instance()
        app.aboutToQuit.connect(self.console.shutdown_kernel)

        kernel = self.console.kernel_manager.kernel
        kernel.shell.push(dict(np=np, pw=self.plot_widget))
        self.console.execute("whos")


if __name__ == '__main__':
    pg.mkQApp()
    main = MainWindow()
    main.show()
    main.console.execute("print(\"hello world :D \")")
    pg.exec()

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jul 4, 2022

image

Nice, in the PR there was an example of how to incorporate dark mode; can that be easily done?

@jonmatthis
Copy link
Copy Markdown
Contributor Author

sorry for the delay! I'll test this on my end later today, but I suspect that I'll be totally happy with this option :D

@jonmatthis
Copy link
Copy Markdown
Contributor Author

Ok, I think I removed my previously committed changes (but please double check I didn't miss anything)

To summarize changes:

Per the conversation above, I removed the standalong "JupyterConsoleWidget" in favor of a more fleshed out example that implements such a console based on the qtconsole project's examples laid out here: https://github.com/jupyter/qtconsole/tree/b4e08f763ef1334d3560d8dac1d7f9095859545a/examples

The new example creates a RichJupyterConsole Widget alongside a PlotWidget, and then shows an example of a way to plot simple sine/cosine waves by entering commands from the __main__ function of the example.

The widgets are implements as Docks so that Users can re-size the windows and close the PlotWidget if they want to.

The example was added to the list of examples in examples/utils.py

I tagged it as a 'recommended' example, but I'm totally happy to remove that if that doesn't feel appropriate.

Please let me know if there is anything else I can do to help move this forward :D

.gitignore Outdated

# jupyter notebooks
.ipynb_checkpoints
.venv No newline at end of file
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please insert new-line 👍🏻

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, done!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha no, a newline after the .venv :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooooh, that makes more sense 😅

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 7, 2022

Hi @jonmatthis

Thanks for updating the PR, sorry it's taken me a bit to get to this.

I added an inline comment about inserting a newline in .gitignore; the other thing is I think you need to remove pyqtgraph/examples/jupyterConsoleWidgetExample.py as that used the JupyterConsoleWidget class in pyqtgraph.widgets which is no longer present.

Also the dark mode is 🔥

@jonmatthis
Copy link
Copy Markdown
Contributor Author

Ok, done!

I knew I was forgetting something 😅


try:
from qtconsole import inprocess
except:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

specify the exception to catch as being ImportError here, not just a generic exception.

@jonmatthis
Copy link
Copy Markdown
Contributor Author

Ok, did the things 👍

try:
from qtconsole import inprocess
except:
except ImportError or NameError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the or operator does what you want it to.

class A(Exception): pass
class B(Exception): pass

try:
    raise B()
except A or B:
    print('caught')
except:
    print('oops')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, correct, what is needed here is `except (ImportError, NameError):

https://docs.python.org/3/tutorial/errors.html#handling-exceptions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

@pijyoi
Copy link
Copy Markdown
Contributor

pijyoi commented Aug 8, 2022

Do we want to add this example into conditionalExamples in https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/examples/test_examples.py#L68 in order to avoid a CI dependency on qtconsole?

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 8, 2022

Do we want to add this example into conditionalExamples in https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/examples/test_examples.py#L68 in order to avoid a CI dependency on qtconsole?

Completely forgot about conditionalExamples, thanks for the reminder @pijyoi this would certainly qualify.

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Aug 23, 2022

blerg, need to indicate to skip this example when qtconsole is not installed; sorry should have caught this earlier sorry about that!

Anyway, you need to add an entry here:

https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/examples/test_examples.py#L68-L77

You'll want to add something like

    "jupyter_console_example.py": exceptionCondition(
        importlib.util.find_spec("qtconsole") is not None,
        reason="No need to test with qtconsole not being installed"
    ),

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Sep 1, 2022

Hi @jonmatthis I hope you don't mind, I added that last bit to make the examples pass; I should have given you that feedback weeks ago, sorry about that. Anyway, this looks good to merge. Thanks for the PR!

@j9ac9k j9ac9k merged commit a097845 into pyqtgraph:master Sep 1, 2022
@jonmatthis
Copy link
Copy Markdown
Contributor Author

Hey cool!! This is my first PR into an open source project :D

Thank you for all your great work maintaining this incredible package, and for making this PR experience easy, fun, and educational <3

@jonmatthis jonmatthis deleted the jupyter-console-widget branch September 1, 2022 13:00
@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Sep 2, 2022

Congratulations on your first PR! Appreciate the sentiment, we certainly could have done better by giving you reviews/suggestions in a more timely manner, sorry about that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants