-
-
Notifications
You must be signed in to change notification settings - Fork 594
FAQs
Check this FAQ, seek inside the GitHub discussions, Reddit, StackOverflow or connect with other developers on our Discord server. Because NiceGUI is build on FastAPI in the backend and Vue3 + Quasar in the frontend, you can also solve a lot of problems/questions by looking into their documentation.
When starting new questions or discussions, please put effort in formulating your post. Quite a number of people will read and think about your message. Make it worth their time.
If you encounter a bug or other issue with NiceGUI, the best way to report it is by opening a new issue on our GitHub repository.
When you're asking for help, it is important to provide a minimal executable example (MRE), if possible. Here is why:
-
By providing an MRE, you increase the chances of getting a quick and accurate response to your question. People are more likely to help when they can easily reproduce the problem and test their solutions.
-
It also helps you understand your problem better by isolating the issue and removing any unnecessary code. This can lead to a better understanding of the problem and potential solutions.
When creating an MRE, make sure to remove any code that is not directly related to the issue, while keeping the example complete and runnable. This includes imports, functions, and data. Keep the example as simple as possible while still demonstrating the problem.
Write your question/suggestion/info in your main language and translate it with an online tool like Google Translate, ChatGPT or similar. The output is often surprisingly good.
See the question about long running functions below.
NiceGUI (and the underlying FastAPI) are async frameworks. That means, no io-bound or cpu-bound tasks should be directly executed on the main thread but rather be "awaited". Otherwise they block execution on the main loop. See https://fastapi.tiangolo.com/async/ for a good in-depth explanation.
There are great libraries like aiofiles (for writing files async) and httpx (for async requests) or NiceGUIs generic run.io_bound
to do io-bound work (eg. if waiting for data).
Cpu-bound tasks need to be run in another process which can be achieved with NiceGUI's run.cpu_bound
(eg. if having something compute-heavy).
See our examples ffmepg, opencv webcam, search-as-you-type, progress and the script executor which could be helpful.
Keep in mind that the run.cpu_bound
tasks should not access class properties. As explained in https://github.com/zauberzeug/nicegui/discussions/2221#discussioncomment-7920864, run.cpu_bound
needs to execute the function in a separate process. For this it needs to transfer the whole state of the passed function to the process (which is done with pickle). It is encouraged to create static methods (or free functions) which get all the data as simple parameters (eg. no class/ui logic) and return the result (instead of writing it in class properties or global variables).
If a page builder decorated with @ui.page
is not marked as async
, FastAPI will run it in a background thread. That means you can very-well use io-blocking code like file reading, database access or requests in these functions without pausing the main thread. Just be aware that a later refactoring to make the page builder async will break your code. Thats why we advise against relying on this pattern as much as possible.
To see warnings about any code which blocks the main thread for more than 50 ms you can use the following code snippet:
def startup():
loop = asyncio.get_running_loop()
loop.set_debug(True)
loop.slow_callback_duration = 0.05
app.on_startup(startup)
Do not use this in production because it will significantly slow down execution.
See below.
You are probably experiencing Python's "late binding".
Try
for i in [1, 2, 3]:
ui.button(i, on_click=lambda: ui.label(i))
vs.
for i in [1, 2, 3]:
ui.button(i, on_click=lambda i=i: ui.label(i))
The i=i
captures the i
within the lambda statement.
When the lambda is eventually evaluated, it would use the current value of i
(which is now 3).
But with i=i
the label is created using a local copy.
You are probably using reload=True
which runs the main code once and then spawns a subprocess which is killed and relaunched on file change. To avoid evaluation of your code in the first "init" you have several options:
-
Use
ui.run(reload=False)
. Of course, you loose the handy auto-reload feature. But for production this might be the way to go. -
Use some kind of main guard:
if __name__ == '__mp_main__': print("Some message") ui.label("test") ui.run()
This avoids evaluating the code in the
"__main__"
process and restricts it to the child process"__mp_main__"
. -
Use a page decorator:
@ui.page('/') def main(): print("Some message") ui.label("test")
This evaluates the UI only when accessed. But if you have an expensive initialization to do once when starting the script, this might not be the way to go. Page decorators also change the visibility, since it generates a new page per client, so the state is not shared anymore.
-
Move expensive initialization into a startup callback:
def startup(): print("Some message") app.on_startup(startup) ui.label("test")
This way
startup
is only evaluated once in the child process, since the app doesn't start in the main process (unlessreload=False
).
In NiceGUI elements are placed wherever they are created. That allows you to quickly create and comprehend nested layout structures. If you create new elements inside an event handler, NiceGUI will place them in the parent container of the element which fired the event.
To pick some other place in the UI, you can enter its context explicitly. For example, if you have a button outside of a custom element to alter its state:
class LabeledCard(ui.card):
def __init__(self) -> None:
super().__init__()
with self:
ui.label('This is the LabeledCard')
def add_label(self) -> None:
with self: # Make sure to use the context of the card
ui.label('This is a label')
card = LabeledCard()
ui.button('Add label to card', on_click=card.add_label)
Binding is for automatically updating individual UI elements when certain values change. This won't add or remove element, but simply update them with new attributes. The refreshable
decorator wraps a function that will replace a whole container when refresh
is called. This might be more convenient to implement and allows for more complex dependencies (e.g. setting certain style or classes depending on some model state). But you have to be aware of the fact that a whole container element is replaced, which causes more network traffic. And client state, like the cursor position in an input element or the state of a dropdown menu, might get lost.
It cannot be achieved with the ui.upload
element because it uses the internal file picker dialog of the browser. For security reasons the browser does not provide the full path of the selected files. See #283 and #269 for more details.
But we have build an example to show a custom local file browser which acts on the filesystem of the running app (e.g. picking files from the server, not the user machine running the browser). If you are in native mode you can also use the system file picker which provides the paths:
from nicegui import app, ui
async def choose_file():
files = await app.native.main_window.create_file_dialog(allow_multiple=True)
for file in files:
ui.notify(file)
ui.button('choose file', on_click=choose_file)
ui.run(native=True)
In its simplest form NiceGUI elements are all shared between clients through the so called "auto-index client".
If you want private pages, you should use page-builder functions marked with the @ui.page
decorator.
You can use both but need to look out for subtile incompatibilities. For example when defining breakpoints. NiceGUI uses Vue3 with Quasar as a web framework because it has tons of greatly customizable UI elements and a huge community. On top of that, we decided early on that Tailwind adds quite a lot of nice styling features which would be a bit more difficult to achieve with Quasar alone.
The problem could be that for example "green-400" is a Tailwind color and not from the Quasar color palette.
The details, why the Tailwind color works in light mode, are a bit more complicated.
But Quasar basically assumes the color should be #fff
.
As the QToggle documentation states, the "color" prop needs to be a name from the Quasar Color Palette.
NiceGUI has a default gap applied so the children's size plus gaps are more than 100%.
Therefore the flex layout automatically wraps elements onto the next "line".
You can fix it by either set the no-wrap
or gap-0
classes.
See this question on StackOverflow and this discussion for more information.
app.storage.user
and app.storage.general
are saved in json files in the .nicegui
folder of your current working dir.
That means, if you deploy the app to a cloud server, the data will be stored there.
NiceGUI does not provide a central data server.
app.storage.browser
is an encrypted session cookie which can also hold small amounts of key/value data.
This data is only stored in the browser cookie and available in memory to the Python code. The browser data expires after 30 days after not accessing.
How to avoid the "reloading because handshake failed" javascript error message which results in a full page reload?
It might be that you are running Gunicorn or another load balancer with multiple workers. In that case the websocket connection may not be delivered to the same instance which created the html page. To avoid that you need switch to another more sophisticated load balancer like Traefic, Nginx or HAProxy which support sticky sessions. A simpler alternative is to just use a single worker and embrace async/await where an event loop ensures high speed concurrency. See the feature request about multiple worker support for details.
Thanks to the auto-index page, NiceGUI is very easy to start with. It also helps when trying or sharing short code snippets and simplifies example code. But as correctly stated on Discord, in 99% of all more complex apps, pages should rather be generated for each user individually using the @ui.page
decorator.
It might be that you are using Python's multiprocessing
module in a pyinstaller --onefile ...
compiled executable in Windows. This link explains the details, but if you use multiprocessing.freeze_support()
at the top of your main guard, things should work as you'd expect them to.