Help trying to port my widget to work in Solara
I am the creator of Buckaroo a full featured dataframe viewer that wraps ag-grid. I am trying to figure out how to expose buckaroo as a component for Solara apps.
A couple of points:
- Buckaroo extends ipywidgets.DOMWidget
- Buckaroo generally functions as its own miniapp configured with optionally user provided mutation functions, users don't wire in any events themselves.
- Inside the frontend of Buckaroo there is the core
DFViewercomponent that takes a serialized dataframe and Buckaroo styling config. There is also extra UI that swaps clientside styling configs, and sets properties on the parent widget that communicates back with the python widget (which transform function to apply, some other things)
It might be easier and more straightforward to integrate just the DFViewer frontend component. I don't have this as a separate DOMWidget, but I could work on it. This would probably be easier for solara users since it is less opinionated than the full BuckarooWidget.
Where I'm getting stuck
I am having trouble understanding the wrapping stage. I'm a bit lost as to how to make Buckaroo work there. I looked at the other examples (IPYWidgets, BQPlot, IPYVeutify).
The codegen in particular is confusing. what is the generated code accomplishing?
Are you inserting actual python code into an existing file in site-packages? are you only using it to run mypy on the generated code? are you using it to get completions in an IDE?
Ahhh after looking at the code in my sitepackages, I see that you are indeed writing to the file.
Documenting this would help.
The code of
class ButtonElement(reacton.core.Element):
def _add_widget_event_listener(self, widget: widgets.Widget, name: str, callback: Callable):
if name == "on_click":
callback_exception_safe = _event_handler_exception_wrapper(callback)
def on_click(change):
callback_exception_safe()
key = (widget.model_id, name, callback)
self._callback_wrappers[key] = on_click
widget.on_click(on_click)
else:
super()._add_widget_event_listener(widget, name, callback)
def _remove_widget_event_listener(self, widget: widgets.Widget, name: str, callback: Callable):
if name == "on_click":
key = (widget.model_id, name, callback)
on_click = self._callback_wrappers[key]
del self._callback_wrappers[key]
widget.on_click(on_click, remove=True)
else:
super()._remove_widget_event_listener(widget, name, callback)
if __name__ == "__main__":
from . import generate
class CodeGen(generate.CodeGen):
element_classes = {ipywidgets.Button: ButtonElement}
def get_extra_argument(self, cls):
return {ipywidgets.Button: [("on_click", None, typing.Callable[[], Any])]}.get(cls, [])
current_module = __import__(__name__)
CodeGen([widgets, ipywidgets.widgets.widget_description, ipywidgets.widgets.widget_int]).generate(__file__)
when run through CodeGen becomes
def _Button(
button_style: str = "",
description: str = "",
disabled: bool = False,
icon: str = "",
layout: Union[Dict[str, Any], Element[ipywidgets.widgets.widget_layout.Layout]] = {},
style: Union[Dict[str, Any], Element[ipywidgets.widgets.widget_button.ButtonStyle]] = {},
tooltip: str = "",
on_button_style: typing.Callable[[str], Any] = None,
on_description: typing.Callable[[str], Any] = None,
on_disabled: typing.Callable[[bool], Any] = None,
on_icon: typing.Callable[[str], Any] = None,
on_layout: typing.Callable[[Union[Dict[str, Any], Element[ipywidgets.widgets.widget_layout.Layout]]], Any] = None,
on_style: typing.Callable[[Union[Dict[str, Any], Element[ipywidgets.widgets.widget_button.ButtonStyle]]], Any] = None,
on_tooltip: typing.Callable[[str], Any] = None,
on_click: typing.Callable[[], typing.Any] = None,
) -> Element[ipywidgets.widgets.widget_button.Button]:
"""Button widget.
This widget has an `on_click` method that allows you to listen for the
user clicking on the button. The click event itself is stateless.
Parameters
----------
description: str
description displayed next to the button
tooltip: str
tooltip caption of the toggle button
icon: str
font-awesome icon name
disabled: bool
whether user interaction is enabled
:param button_style: Use a predefined styling for the button.
:param description: Button label.
:param disabled: Enable or disable user changes.
:param icon: Font-awesome icon name, without the 'fa-' prefix.
:param tooltip: Tooltip caption of the button.
"""
...
@implements(_Button)
def Button(**kwargs):
if isinstance(kwargs.get("layout"), dict):
kwargs["layout"] = Layout(**kwargs["layout"])
if isinstance(kwargs.get("style"), dict):
kwargs["style"] = ButtonStyle(**kwargs["style"])
widget_cls = ipywidgets.widgets.widget_button.Button
comp = reacton.core.ComponentWidget(widget=widget_cls)
return ButtonElement(comp, kwargs=kwargs)
del _Button
Ok, I got this to work.
My own codegen
import reacton
from buckaroo.buckaroo_widget import BuckarooWidget
def reacton_buckaroo(**kwargs):
widget_cls = BuckarooWidget
comp = reacton.core.ComponentWidget(widget=widget_cls)
return reacton.core.Element(comp, kwargs=kwargs)
then invoking it in a simple solara app
import pandas as pd
import solara
df = pd.DataFrame({'a':[10,20]})
@solara.component
def Page():
bw = reacton_buckaroo(df=df)
Page()