solara
solara copied to clipboard
dynamic forms in solara
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?
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 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?
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 ?
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
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.
thanks @swelborn, i might update it did you try it? does it work otherwise?
@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?
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.
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!
@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).