solara icon indicating copy to clipboard operation
solara copied to clipboard

dynamic forms in solara

Open tharwan opened this issue 1 year ago • 10 comments

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:

  • https://github.com/maxfordham/ipyautoui
  • https://github.com/ssciwr/ipywidgets-jsonschema

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?

tharwan avatar Feb 21 '24 07:02 tharwan

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.

swelborn avatar Feb 23 '24 18:02 swelborn

@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 avatar Feb 27 '24 14:02 maartenbreddels

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 ?

maartenbreddels avatar Feb 27 '24 14:02 maartenbreddels

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

Jhsmit avatar Feb 27 '24 14:02 Jhsmit

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.

swelborn avatar Feb 27 '24 15:02 swelborn

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

Jhsmit avatar Feb 27 '24 15:02 Jhsmit

@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?

jgunstone avatar Feb 27 '24 17:02 jgunstone

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.

tharwan avatar Feb 28 '24 06:02 tharwan

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!

maartenbreddels avatar Feb 28 '24 08:02 maartenbreddels

@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).

swelborn avatar Feb 28 '24 15:02 swelborn