Sam Hooke

NiceGUI with async classes

These notes build upon the class based NiceGUI app written about here, and modify it to be async. It may help to read those notes first.

Why use async for NiceGUI? §

The NiceGUI FAQ has some good answers.

Async init for Python classes §

Typically the init for a Python class look as follows:

class MyClass:
    def __init__(self):
        await self.do_something()  # WRONG: Cannot await because __init__ is not async

    async def do_something(self):
        pass

my_obj = MyClass()

However, if there are async methods that need to be called during init, we cannot place them in the __init__ method because it is not async.

Instead, we can use the factory pattern to add an async create class method:

class MyClass:
    @classmethod
    async def create(cls):
        self = cls()
        await self.do_something()
        return self

    async def do_something(self):
        pass

my_obj = await MyClass.create()

Then it is permissable to use await inside the create method.

Using async classes with NiceGUI §

Applying the above to our async class, we can do the following:

import click
from nicegui import ui

# Very basic example GUI in a class.
class MyGUI:
    @classmethod
    async def create(cls):
        self = cls()
        ui.label("Hello, world!")
        return self


# Explicit index page.
@ui.page("/")
async def index():
    await MyGUI.create()


# Wrapper around the call to `ui.run`.
def my_run(port: int, reload: bool):
    ui.run(port=port, reload=reload, title="My GUI")


# Entrypoint for when GUI is launched by the CLI.
# e.g.: poetry run my-cli
@click.command()
@click.option(
    "--port",
    default=8081,
    help="Port for GUI",
)
def main(port):
    print("GUI launched by CLI")
    my_run(port=port, reload=False)


# Entrypoint for when GUI is launched by the CLI.
# e.g.: python my_app/my_cli.py
if __name__ in {"__main__", "__mp_main__"}:
    if __name__ == "__mp_main__":
        print("GUI launched by auto-reload")

    my_run(port=8081, reload=True)

This enables us to use async methods within the create method of MyGUI, e.g.:

class MyGUI:
    @classmethod
    async def create(cls):
        self = cls()
        await self.hello_world()
        return self

    async def hello_world(self):
        ui.label("Hello, world!")