widgetti / solara

A Pure Python, React-style Framework for Scaling Your Jupyter and Web Apps
https://solara.dev
MIT License
1.62k stars 105 forks source link

dynamic forms in solara #512

Open tharwan opened 2 months ago

tharwan commented 2 months ago

We would like to display dynamic forms in our solara frontend based on pydantic models / json schemas.

There are multiple IPyWidgets out there that provide this functionality namely:

However both currently do not seem to be compatible with solara. Mainly because they do not directly provide a widget but rather create and manage exisiting widgets. Therefore they do not have the option to use .element for initialisation.

Do you have any hints on how to best cover this use case? Should the packages provide a proper widget? Would it be sufficient to be able to swap the widget classes for the corresponding solara ones? e.g. sl.InputText for IPyWidgets.Text Is there a good way to mix "traditional" IPyWidgets with reactive ones?

swelborn commented 2 months ago

Unsure if this helps, but I added a very basic implementation of forms to this effect here:

https://github.com/swelborn/tomopyui/blob/b3306ff2e31dd2ee34614c77187de1f98ef9df8d/tomopyui/frontend/components/metadata/metadata.py

It uses pydantic to do this, and pydantic models can be autogenerated from json schema (https://docs.pydantic.dev/latest/integrations/datamodel_code_generator/). It's in a very WIP PR to that repo.

maartenbreddels commented 2 months ago

@swelborn very interesting it reminder me of code that I saw from @Jhsmit Maybe it makes sense to have a form+pydantic example at the solara website?

@Jhsmit do you also have a link to your version?

maartenbreddels commented 2 months ago

On the topic of mixing classic widgets with components, this might be a good example on how to add a classic widget in a component (in this case ipyautoui cc @jgunstone):

import solara
from pydantic import BaseModel, Field
from ipyautoui import AutoUi

class LineGraph(BaseModel):
    """parameters to define a simple `y=m*x + c` line graph"""
    title: str = Field(default='line equation', description='add chart title')
    m: float = Field(default=2, description='gradient')
    c: float = Field(default=5, ge=0, le=10, description='intercept')
    x_range: tuple[int, int] = Field(
        default=(0,5), ge=0, le=50, description='x-range for chart')
    y_range: tuple[int, int] = Field(
        default=(0,5), ge=0, le=50, description='y-range for chart')

@solara.component
def Page():
    # important to use a widget component, not a function component
    container = solara.v.Html(tag="div")#, children=[])
    # this will reset the children (possibly a bug in reacton)
    # container = solara.Column()
    def add_classic_widget():
        # generate your normal widget
        ui = AutoUi(schema=LineGraph)
        # add it to the generated widget by solara/reacton
        container_widget = solara.get_widget(container)
        container_widget.children = (ui,)

    solara.use_effect(add_classic_widget, dependencies=[])
    return container

One worry is that this does not close the AutoUi widget, and might result in a (temporary) memory leak. It's temporary because all widgets belonging to a virtual kernel will be closed once the virtual kernel will be closed (on browser/page close or kernel cull timeout - default 24h https://solara.dev/docs/understanding/solara-server )

Does it make sense to have this example at https://solara.dev/docs/howto/ipywidget-libraries ?

Jhsmit commented 2 months ago

the one i made is here: https://github.com/Jhsmit/awesome-solara/blob/master/examples/input_form.py

havent used it in a while though

swelborn commented 2 months ago

nice @Jhsmit

one note for people reading this: code in @Jhsmit is that use of fields and .dict() is deprecated in newer versions of pydantic:

The getter for property "__fields__" is deprecated
  The `__fields__` attribute is deprecated, use `model_fields` instead.
The method "dict" in class "BaseModel" is deprecated
  The `dict` method is deprecated; use `model_dump` instead.
Jhsmit commented 2 months ago

thanks @swelborn, i might update it did you try it? does it work otherwise?

jgunstone commented 2 months ago

@maartenbreddels - thanks for the mention - If possible I'd like ipyautoui to be better supported in solara. https://github.com/maxfordham/ipyautoui/issues/289

as an almost exclusive rule in ipyautoui widgets are instantiated as below. traits are observed to change widget behaviour

class MyWidget(w.VBox):
    my_trait = tr.Unicode()
    def __init__(self, **kwargs):
        # pre-init code
        super().__init__(**kwargs)
        # post-init code

I had an initial quick look at your code-gen tool... my understanding is that it reads the traits and converts them to typed kwargs ? so my hope is with a bit of work I could make it work... I had an initial stab: https://github.com/maxfordham/ipyautoui/pull/291

a v simple widget worked fine (SaveButtonBar).... but the more complex ones didn't...

you got any hot tips?

tharwan commented 2 months ago

Does it make sense to have this example at https://solara.dev/docs/howto/ipywidget-libraries ?

having recently tried to prototype something with solara this would help tremendously, but for sure needs to have some context of when to apply it.

maartenbreddels commented 2 months ago

I think either ipyautoui is completely using reacton/solara, it we should just advice to use the above example. I have some ideas how we can make it easier to use widgets in a component directly (With a proper memory cleanup phase), but for now the above will suffice. I'll try to add it to the docs soon!

swelborn commented 2 months ago

@Jhsmit Sorry I didn't give it a go, I just pulled into vscode and noticed the deprecation warnings (which show as the function crossed through inline).