Skip to content

Stop selecting a GUI backend on import; let rendercanvas.auto decide - #1105

Open
hmaarrfk wants to merge 2 commits into
fastplotlib:mainfrom
hmaarrfk:lazy-auto-canvas
Open

hmaarrfk wants to merge 2 commits into
fastplotlib:mainfrom
hmaarrfk:lazy-auto-canvas

Conversation

@hmaarrfk

@hmaarrfk hmaarrfk commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Remove the loop selection code from fastplotlib allowing downstream applications to make the choice.

Claude

Motivation

fastplotlib is often used as a library inside a larger application, not as the application itself. That application decides which windowing system and event loop to use, or runs headless/offscreen and passes its own canvas and renderer to Figure.

Before this PR, import fastplotlib ran from rendercanvas.auto import RenderCanvas, loop at import time, through fastplotlib/utils/gui.py. It also tried to import a Qt library first, so that Qt would be preferred. As a result:

  • fastplotlib could not be imported at all without Qt, glfw or wx, even by applications that never use the auto canvas (rendercanvas.auto raises ImportError at module level).
  • Importing fastplotlib imported a Qt library and chose a GUI backend and loop before the host application had decided anything.
  • The only workaround was setting RENDERCANVAS_BACKEND before the first import, which leaks a process-wide setting into the host application.

How we got here

  1. First commit (c7f486b): kept the public API intact (fpl.loop, fastplotlib.utils.gui.GUI_BACKEND, IS_JUPYTER, QtWidgets, ...) and made the backend selection lazy through module-level __getattr__ in three modules.
  2. Review: @almarklein agreed fastplotlib is a library and the application should control loops, but pointed out the amount of module __getattr__ magic, and that the cleanest model is PyGfx's: the user creates the canvas and loop. @kushalkolar was in favor of removing the Jupyter GPU-adapter banner. The maintainers then indicated they are fine with a more drastic, API-changing version.
  3. Second commit (235313c): drops the lazy-proxy approach. rendercanvas.auto is imported only inside the functions that need it, and fastplotlib no longer makes GUI decisions on import.

Final state

  • fastplotlib/utils/gui.py is removed. With it go:
    • the Jupyter logo + GPU adapter banner;
    • the Qt priming (fastplotlib no longer prefers Qt; rendercanvas.auto decides, like in PyGfx);
    • the GUI_BACKEND, IS_JUPYTER and QtCore/QtGui/QtWidgets re-exports. None of them were used inside fastplotlib.
  • make_canvas_and_renderer imports rendercanvas.auto.RenderCanvas only when Figure gets canvas=None. BaseRenderCanvas is imported directly from rendercanvas.
  • NDWidget fetch scheduling (widgets/nd_widget/_index.py) imports rendercanvas.auto.loop inside _schedule_fetch.
  • fpl.loop and fastplotlib.utils.loop are removed, with no deprecation shim, so there is no module __getattr__ left anywhere. Scripts that call fpl.loop.run() now get an AttributeError and should switch to rendercanvas.auto.loop.
  • Examples and docs (159 examples, user guide, matplotlib migration guide, nd_widget guide, generated API page) now use:
    from rendercanvas.auto import loop
    ...
    loop.run()

Behavior changes to review

  • Default backend when several are installed: with no GUI lib imported yet, rendercanvas.auto tries glfw before Qt. Users who have both installed and relied on fastplotlib preferring Qt now get glfw. They can import their Qt lib first or set RENDERCANVAS_BACKEND=qt.
  • No banner in Jupyter. fpl.enumerate_adapters() / fpl.print_wgpu_report() remain for checking GPUs.
  • Removed names: fpl.loop, fastplotlib.utils.loop, fastplotlib.utils.gui (and everything in it).
  • The "WGPU could not enumerate any adapters" warning at import is unchanged and out of scope here.

Verification

  • With PySide6/PyQt6/PySide2/PyQt5/glfw/wx blocked from importing: import fastplotlib succeeds without importing rendercanvas.auto or any GUI lib. Figure(canvas=OffscreenRenderCanvas(...), renderer=WgpuRenderer(...)) and Figure(canvas="offscreen") show and draw, still without importing rendercanvas.auto. Figure() raises rendercanvas' ImportError, as expected.
  • With Qt available: no Qt lib and no rendercanvas.auto after import fastplotlib. fpl.loop no longer exists. fpl.Figure() creates a QRenderCanvas.
  • RENDERCANVAS_FORCE_OFFSCREEN=1 pytest tests (without tests/test_recording.py, which fails locally on a missing PyAV codec, same as on main): 1548 passed.
  • RENDERCANVAS_FORCE_OFFSCREEN=1 pytest examples: 195 passed, 21 failed. The same 21 fail before this change (missing optional deps such as sklearn in my env).

Resume this Claude session:

cd /home/mark/git/pygfx/fastplotlib
claude --resume ae74f971-2c13-46b0-a892-ebcf861a72ec

<details><summary>Claude's draft</summary>

Importing fastplotlib used to import `rendercanvas.auto` at module level
(via `fastplotlib.utils.gui`), which runs the GUI backend selection and
raises ImportError when no Qt, glfw or wx backend can be loaded. This
happened even for applications that pass their own `canvas` and
`renderer` to `Figure`.

The auto selection (Qt priming, `rendercanvas.auto` import, Jupyter
banner, Qt submodule imports) now runs on first access of
`RenderCanvas`, `loop`, `GUI_BACKEND`, `IS_JUPYTER` or `QtCore/QtGui/
QtWidgets` in `fastplotlib.utils.gui`, through a module `__getattr__`.
`fpl.loop` and `fastplotlib.utils.loop` are lazy in the same way. In
practice the selection now happens when a Figure is created with
`canvas=None`, or when `fpl.loop` is used.

Resume this Claude session:
```
cd /home/mark/git/pygfx/fastplotlib
claude --resume 079dc15c-fc70-4763-9f9b-515f8af4b56e
```
</details>
Comment thread fastplotlib/utils/__init__.py Outdated
@@ -1,5 +1,4 @@
# this MUST be imported as early as possible in fpl.__init__ before any other wgpu stuff
from .gui import loop
from . import gui

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.

Having this is a little "hostile" to people using different loops and event managers.

I'm hoping you can delay this?

@hmaarrfk

Copy link
Copy Markdown
Contributor Author

@kushalkolar before really going into the details of this change request, i'm hoping we can relax starting the loop as early as possible.

It seems a bit of an overreach when we think of fastplotlib as a "library" and not an application.

The choice of the loop is really an "application level decision".

LMK what you think

@kushalkolar

Copy link
Copy Markdown
Member

I think I like this overall.

@almarklein @clewis7 what do you think?

I'm leaning on removing the banner with available GPUs list, is anyone using even notebooks and looking at these things anymore these days 🤷 . It was inspired by the bokeh banner

@hmaarrfk

Copy link
Copy Markdown
Contributor Author

I saw this change list and it was a little large. Thx for considering alternatives.

@kushalkolar

Copy link
Copy Markdown
Member

I saw this change list and it was a little large. Thx for considering alternatives.

Is this you Mark or Claude 😂

@kushalkolar

Copy link
Copy Markdown
Member

Btw @hmaarrfk we should meet again when you're in NYC!

@almarklein

Copy link
Copy Markdown
Collaborator

I agree that fpl is a library and it would be good to allow the application code to have more control over loops etc. I think this PR tries to achieve that without breaking the API. But there's a lot of module-level __getatrr__'s, so maybe (over time) it could be moved to a new API with less of that magic? Not sure how hard that would be, or what it'd look like.

The 'cleanest' solution is probably to do what PyGfx does and let the user create the canvas and loop. But FPL tries to abstract some of that away, so not sure.

@hmaarrfk

Copy link
Copy Markdown
Contributor Author

Is this you Mark or Claude 😂

i mostly exclusively code in claude now.

In truth upon reviewing the changelist, I totally understood what it was trying to acheive, that is what almar said "not changing the API", and in python land everything is part of the API, so the use of __getattr__ is the only way.

If you are ok with changing a bit of the API, i think we can "avoid importing loop", until we absolutely need it in the functions that need it. And this would avoid the whole __getattr__ gymnastics.

@hmaarrfk hmaarrfk changed the title Resolve rendercanvas auto backend lazily Stop selecting a GUI backend on import; let rendercanvas.auto decide Oct 27, 2026
@kushalkolar

Copy link
Copy Markdown
Member

Looks like I forgot to put examples/diffs in .gitignore

<details><summary>Claude's draft</summary>

Following the review discussion, replace the lazy module `__getattr__`
approach with a simpler one: fastplotlib no longer makes any
application-level GUI decision at import time.

- Remove `fastplotlib/utils/gui.py`. With it go the Qt priming, the
  Jupyter adapter banner, and the `GUI_BACKEND`, `IS_JUPYTER` and
  `QtCore/QtGui/QtWidgets` re-exports, none of which were used
  internally.
- `make_canvas_and_renderer` and the NDWidget fetch scheduler import
  `rendercanvas.auto` inside the function, only when they need it.
  `BaseRenderCanvas` comes straight from `rendercanvas`.
- `fpl.loop` and `fastplotlib.utils.loop` are removed, with no
  deprecation shim.
- Examples and the user guide use `from rendercanvas.auto import loop`.

Resume this Claude session:
```
cd /home/mark/git/pygfx/fastplotlib
claude --resume ae74f971-2c13-46b0-a892-ebcf861a72ec
```
</details>
@hmaarrfk

Copy link
Copy Markdown
Contributor Author

Looks like I forgot to put examples/diffs in .gitignore

I figured that was the case, can you confirm that you were describing in fact just riping out this whole loop stuff

@kushalkolar

Copy link
Copy Markdown
Member

Looks like I forgot to put examples/diffs in .gitignore

I figured that was the case, can you confirm that you were describing in fact just riping out this whole loop stuff

I think it makes sense if people are using different loops as you said. I haven't encountered this though, curious where you've run into it.

@hmaarrfk

Copy link
Copy Markdown
Contributor Author

i guess i kinda said it right? We are building our application, and thus want tighter control of the event loop, so having things depend on import order is a little strange and frustrating to nail down.

We are evaluating different loops, and different optimizations between them, so like having this parameter to dance around is a little annoying, and we often work "offscreen".

@kushalkolar

Copy link
Copy Markdown
Member

i guess i kinda said it right? We are building our application, and thus want tighter control of the event loop, so having things depend on import order is a little strange and frustrating to nail down.

We are evaluating different loops, and different optimizations between them, so like having this parameter to dance around is a little annoying, and we often work "offscreen".

yea I think that makes sense!

@hmaarrfk
hmaarrfk marked this pull request as ready for review October 27, 2026 02:48
@hmaarrfk

Copy link
Copy Markdown
Contributor Author

ok marked as ready for review. thx

@kushalkolar

Copy link
Copy Markdown
Member

@FlynnOConnell @apasarkar once this is merged you'll need to import loop directly from rendercanvas instead of relying on fpl to do it, see the diff in the examples

@kushalkolar

Copy link
Copy Markdown
Member

Seems like fastplotlib/utils/__init__.py just needs some black and gtg. Docs build which is good, previews won't deploy from forks though which is the failure so don't worry about it.

@almarklein

Copy link
Copy Markdown
Collaborator

once this is merged you'll need to import loop directly from rendercanvas instead of relying on fpl to do it

We can temporarily add a small module-level __getattr__ in the fpl namespace, that loads the loop from rendercanvas and prints a warning that this usage is deprecated and that one should import it from rendercanvas instead. Then next release or so remove or replace with exception. That should make it much friendlier for unaware users how to migrate their code.

@hmaarrfk

Copy link
Copy Markdown
Contributor Author

Please feel free to push directly here, i'll have my AI not squash your changes.

@FlynnOConnell

FlynnOConnell commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

I'm leaning on removing the banner with available GPUs list

@kushalkolar It is helpful for some notebook users, it's been used in the last few weeks as a quick "hey this isn't right"

@clewis7

clewis7 commented Oct 27, 2026

Copy link
Copy Markdown
Member

I'm on the fence about removing the notebook banner.
It can be kind of clunky, but at times it is really helpful to see which GPU has been selected.

@FlynnOConnell

Copy link
Copy Markdown
Collaborator

Can we just print the parrot and selected device? Not all avail

This branch has not been deployed

No deployments
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.

5 participants