NiceGUI with Click, Poetry, auto-reload and classes
All notes in this series:
- NiceGUI: Always show main scrollbar
- NiceGUI: Show a confirmation popup
- NiceGUI: File upload and download
- FastAPI: Pretty print JSON
- NiceGUI with Click, Poetry, auto-reload and classes
- NiceGUI: tkinter error when updating pyplot
- NiceGUI: Bind visibility to arbitrary value
- NiceGUI: Change threshold for binding propagation warning
- NiceGUI with async classes
It’s very easy to get up and running with NiceGUI, and the development cycle is fast because it has an auto-reload feature, where the web page automatically updates whenever you save a change to your project’s Python code.
However, almost all the official NiceGUI examples show UI code being written at the top-level, outside of a class, and outside of a main guard.
For example, following is the ui.button
example code:
I like the auto-reload feature for development, but want to wrap up my GUI code in a class. Additionally, I want to provide command line options using Click, and expose the GUI application as a Poetry script. It turns out doing all of these together is a little fiddly.
Solution §
These notes cover how to do all of the following:
- Use the NiceGUI auto-reload feature for development.
- Use Poetry to provide a script for users to run the GUI.
- Use Click to allow specifying options when launching the GUI.
- Wrap your NiceGUI code in a class rather than having it at the top-level.
- And, as an optional bonus step: configure an explicit index page.
Auto-reload and Poetry §
The auto-reload feature is enabled by default, but can be set explicitly with ui.run(reload=True)
. However, auto-reload only works if the script is called directly, e.g.:
If you call it indirectly, e.g. via Poetry…
…then auto-reload will not work, and you will get this warning:
For this problem, the best solution I have found is to have two entry points to your GUI:
- An
if __name__ in {"__main__", "__mp_main__"}:
entry point for calling directly. (We’ll get to the details of this line next). - A
def main():
entry point for calling it via Poetry.
The first entry point uses ui.run(reload=True)
, and the second uses ui.run(reload=False)
. This prevents getting any warning when launching via Poetry
Avoid double run during auto-reload §
You may have been wondering why this is the first entry point:
The reason is that with auto-reload, NiceGUI uses two processes1:
- The main process (
__main__
) starts the server. - The child process (
__mp_main__
) is restarted each time the code is changed.
Each process evalutes the script. So any top-level code (e.g. code outside a class, function or main guard) will be executed whenever the script is evaluated.2
There are several solutions1, but since I want to put my GUI code in a class, my preferred way is to use a main guard as follows:
This way the GUI code (contained in the MyGUI
class) only gets created by the child process. So we avoid the “double run”. However the ui.run
function gets called each time, which is necessary for the main and child process to work.
Click §
The second entry point can be further extended with Click to allow passing in options. For example, adding a --port
option to specify which port the GUI runs on:
I haven’t dug into the details, but in my experience I found that Click did not work auto-reload. Attempting to do so caused NiceGUI to hang when launching. Hence why I only use Click with the Poetry entry point, which has reload=False
.
Putting it all together §
Following is a full example of using NiceGUI, Click and Poetry scripts:
To enable using poetry run my-cli
to launch the GUI, we must add this to our pyproject.toml
:
Now you can do the following:
- As a user:
- Use
poetry run my-cli
to launch the GUI. - Use
poetry run my-cli --port=8090
to specify the port.
- Use
- As a developer:
- Use
poetry run python my_app/my_cli.py
to launch the GUI with auto-reload enabled.
- Use
Bonus: explicit index page §
If you’ve got this far, you might be interested in this next step. It isn’t necessary, but can help simplify the code a little.
Almost all the NiceGUI documentation uses an implicit index page, whereby UI elements defined outside of a @ui.page
decorator are part of the auto-index page at /
.
By instead using an explicit index page, we can remove the need to instantiate MyGUI()
in two separate places. Though beware this also means data within the index page is “private to the user and not shared with others”, so if you are relying on that feature to allow multiple users to share the same index page, using an explicit index page will break that functionality.
I prefer the explicit index page, especially for more complex GUIs, since it makes it clearer how the pages are defined, especially once you start adding more than one page.
Credit to falkoschindler for both explaining the double run behaviour and providing multiple solutions. ↩︎ ↩︎
This minimal example demonstrates the issue clearly. ↩︎
All notes in this series:
- NiceGUI: Always show main scrollbar
- NiceGUI: Show a confirmation popup
- NiceGUI: File upload and download
- FastAPI: Pretty print JSON
- NiceGUI with Click, Poetry, auto-reload and classes
- NiceGUI: tkinter error when updating pyplot
- NiceGUI: Bind visibility to arbitrary value
- NiceGUI: Change threshold for binding propagation warning
- NiceGUI with async classes