Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it easy to use dataclass like models using familiar apis #6912

Open
wants to merge 65 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
cd58f50
improve ipywidgets reference guide
MarcSkovMadsen Jun 5, 2024
e75776a
improve language
MarcSkovMadsen Jun 5, 2024
19c9741
clean notebook
MarcSkovMadsen Jun 5, 2024
428ec92
fix reference links
MarcSkovMadsen Jun 5, 2024
86b5002
explain sizing better
MarcSkovMadsen Jun 5, 2024
71287e4
pre-commit
MarcSkovMadsen Jun 5, 2024
8b41a07
design spec including tests
MarcSkovMadsen Jun 11, 2024
f0a766d
support layout kwargs
MarcSkovMadsen Jun 11, 2024
1da8658
use panel
MarcSkovMadsen Jun 11, 2024
17a39e3
update design spec
MarcSkovMadsen Jun 11, 2024
4907090
implement rx
MarcSkovMadsen Jun 11, 2024
42b2d82
implement first version
MarcSkovMadsen Jun 11, 2024
00b5731
add to_viewer back
MarcSkovMadsen Jun 11, 2024
a8021a5
carve out sync_parameterized
MarcSkovMadsen Jun 11, 2024
1c18076
major refactor of ipywidget
MarcSkovMadsen Jun 12, 2024
09f6a1c
fix remaining todos
MarcSkovMadsen Jun 12, 2024
12583df
skip test if not ipywidgets available
MarcSkovMadsen Jun 13, 2024
883eb7b
carve out and add back in other PR
MarcSkovMadsen Jun 13, 2024
d80a506
Merge branch 'docs/ipywidgets-reference-update' into ipywidget_utility
MarcSkovMadsen Jun 13, 2024
56ed52e
update names and improve docstrings
MarcSkovMadsen Jun 13, 2024
c21c865
use model and names terminology
MarcSkovMadsen Jun 15, 2024
025659b
support wrapping model Classes
MarcSkovMadsen Jun 15, 2024
e649aeb
support WidgetViewer from class
MarcSkovMadsen Jun 15, 2024
83a5f93
add missing test
MarcSkovMadsen Jun 15, 2024
0a83798
make _names an attribute instead of parameter
MarcSkovMadsen Jun 15, 2024
771f986
simplify to model class
MarcSkovMadsen Jun 15, 2024
2f0f175
expose model
MarcSkovMadsen Jun 15, 2024
8471e49
clean up tests
MarcSkovMadsen Jun 15, 2024
36217c9
document. had to move to wrappers module
MarcSkovMadsen Jun 15, 2024
aa0eaa3
rename to observers
MarcSkovMadsen Jun 15, 2024
b1da06b
rename for more generality and alignment with observer pattern
MarcSkovMadsen Jun 15, 2024
7661402
first version of how-to guide
MarcSkovMadsen Jun 15, 2024
b2ad70b
docs review
MarcSkovMadsen Jun 15, 2024
88344a2
add comment
MarcSkovMadsen Jun 15, 2024
d1b5d9e
remove example files
MarcSkovMadsen Jun 15, 2024
56d0740
Merge branch 'main' of https://github.com/holoviz/panel into ipywidge…
MarcSkovMadsen Jun 15, 2024
bcd21a4
review feedback
MarcSkovMadsen Jun 15, 2024
3669877
docs review feedback
MarcSkovMadsen Jun 15, 2024
4a20c94
fix links
MarcSkovMadsen Jun 15, 2024
fdd25bf
refactor to dataclass namespace
MarcSkovMadsen Jun 16, 2024
6e1215d
rename _names to names and _model_names
MarcSkovMadsen Jun 16, 2024
b178ebf
refactor to support Pydantic too
MarcSkovMadsen Jun 16, 2024
289a125
update table
MarcSkovMadsen Jun 16, 2024
6f9c4f5
add ideas for observing pydantic
MarcSkovMadsen Jun 16, 2024
2ae5d19
Various cleanup
philippjfr Jun 17, 2024
a543719
Add pydantic to pixi deps
philippjfr Jun 17, 2024
287d240
Fix indexes
philippjfr Jun 17, 2024
08819f7
Fix index
philippjfr Jun 17, 2024
8baab7c
update names for consistency
MarcSkovMadsen Jun 17, 2024
60f7964
Align docstring
philippjfr Jun 17, 2024
a3c7b4e
Optimize and fix parameter syncing
philippjfr Jun 17, 2024
0fc8092
Rename create_ functions to to_
philippjfr Jun 18, 2024
6d8c2a3
Reorganize dataclass module
philippjfr Jun 18, 2024
11685c8
Convert parameter types
philippjfr Jun 18, 2024
fb2e77d
Merge branch 'main' into ipywidget_utility
philippjfr Jun 21, 2024
1aff765
Serialize datetime objects
philippjfr Jun 22, 2024
3bd2fad
Allow dev version in base_version
philippjfr Jun 22, 2024
bf17799
Merge remote-tracking branch 'origin/main' into ipywidget_utility
MarcSkovMadsen Jul 13, 2024
3cac03e
review feedback
MarcSkovMadsen Jul 14, 2024
ef1521f
fix
MarcSkovMadsen Jul 14, 2024
0a9902a
pydantic parameters + default value
MarcSkovMadsen Jul 14, 2024
11a896d
add missing pydantic parameters
MarcSkovMadsen Jul 15, 2024
f7ee165
fix tuple exception
MarcSkovMadsen Jul 15, 2024
fc76ad4
add ModelForm
MarcSkovMadsen Jul 15, 2024
d364cfe
refactor
MarcSkovMadsen Jul 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add to_viewer back
MarcSkovMadsen committed Jun 11, 2024
commit 00b5731386f9b459b0a12a096b6d661ae316773e
73 changes: 60 additions & 13 deletions panel/ipywidget.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@
from param import Parameterized
from param.reactive import bind

from .viewable import Viewer
from .pane import IPyWidget
from .viewable import Layoutable, Viewer

if TYPE_CHECKING:
try:
@@ -23,16 +24,19 @@ class HasTraits: # type: ignore
class HasTraits: # type: ignore
"""Mock class"""


def _is_custom_trait(name):
if name.startswith("_"):
return False
if name in {'comm', 'tabbable', 'keys', 'log', 'layout'}:
if name in {"comm", "tabbable", "keys", "log", "layout"}:
return False
return True


def _get_parameter_names(widget):
return [name for name in widget.traits() if _is_custom_trait(name)]


class IpyWidgetParameterized(Parameterized):
_widget = param.Parameter(allow_None=False)
_parameters = param.List(allow_None=False)
@@ -52,22 +56,27 @@ def __init__(self, **params):
setattr(self, parameter, getattr(widget, parameter))

# Observe widget parameters
def _handle_widget_change(change, widget=widget, parameter=parameter):
def _handle_widget_change(_, widget=widget, parameter=parameter):
setattr(self, parameter, getattr(widget, parameter))

widget.observe(_handle_widget_change, names=parameter)

# Bind to self parameters
def _handle_observer_change(value, widget=widget, parameter=parameter):
def _handle_observer_change(_, widget=widget, parameter=parameter):
setattr(widget, parameter, getattr(self, parameter))

bind(_handle_observer_change, value=self.param[parameter], watch=True)
bind(_handle_observer_change, self.param[parameter], watch=True)


_ipywidget_classes = {}


def to_parameterized(
widget: HasTraits, parameters: Iterable | None = None, bases: Parameterized|None=None
) -> Viewer:
widget: HasTraits,
parameters: Iterable | None = None,
bases: Parameterized | None = None,
**kwargs
) -> Parameterized:
"""Returns a Parameterized object with parameters synced to the ipywidget widget parameters

Args:
@@ -79,19 +88,57 @@ def to_parameterized(
if not parameters:
parameters = _get_parameter_names(widget)
if bases:
bases = (bases, IpyWidgetParameterized,)
bases = (
bases,
IpyWidgetParameterized,
)
else:
bases = (IpyWidgetParameterized,)
name = type(widget).__name__
key = (name, tuple(parameters), bases)
if name in _ipywidget_classes:
viewer = _ipywidget_classes[key]
parameterized = _ipywidget_classes[key]
else:
existing_params = set(IpyWidgetParameterized.param)
params = {name: param.Parameterized() for name in parameters if name not in existing_params}
viewer = param.parameterized_class(name, params=params, bases=bases)
_ipywidget_classes[key] = viewer
return viewer(_widget=widget, _parameters=parameters)
params = {
name: param.Parameterized()
for name in parameters
if name not in existing_params
}
parameterized = param.parameterized_class(name, params=params, bases=bases)
_ipywidget_classes[key] = parameterized
return parameterized(_widget=widget, _parameters=parameters, **kwargs)


class IpyWidgetViewer(Layoutable, Viewer):

def __init__(self, **params):
super().__init__(**params)

widget = self._widget
layout_params = {name: self.param[name] for name in Layoutable.param}

self._layout = IPyWidget(widget, **layout_params)

def __panel__(self):
return self._layout


def to_viewer(
widget: HasTraits,
parameters: Iterable | None = None,
bases: Parameterized | None = None,
**kwargs,
) -> Viewer:
"""Returns a Parameterized object with parameters synced to the ipywidget widget parameters

Args:
widget (HasTraits): The ipywidget to create the Viewer from.
parameters (Iterable | None): The parameters to add to the Parameterized and to sync.
If no parameters are specified all public parameters on the widget will be added
and synced.
"""
return to_parameterized(widget, parameters=parameters, bases=IpyWidgetViewer, **kwargs)


def sync_rx(element: HasTraits, name: str, target: param.rx):
30 changes: 26 additions & 4 deletions panel/tests/test_ipywidget.py
Original file line number Diff line number Diff line change
@@ -10,14 +10,16 @@
from ipywidgets import DOMWidget, register
from traitlets import Float, Int, Unicode

from panel.ipywidget import to_parameterized, to_rx
from panel.ipywidget import to_parameterized, to_rx, to_viewer
from panel.pane import IPyWidget
from panel.viewable import Layoutable, Viewer


@register
class ExampleIpyWidget(DOMWidget):
_view_name = Unicode('ExampleIpyWidgetView').tag(sync=True)
_view_module = Unicode('example_ipywidget').tag(sync=True)
_view_module_version = Unicode('0.1.0').tag(sync=True)
_view_name = Unicode("ExampleIpyWidgetView").tag(sync=True)
_view_module = Unicode("example_ipywidget").tag(sync=True)
_view_module_version = Unicode("0.1.0").tag(sync=True)

name = Unicode("Default Name").tag(description="A string trait")
age = Int(0).tag(description="An integer trait")
@@ -58,6 +60,7 @@ def test_to_parameterized_is_synced(widget):
assert viewer.age == widget.age
assert viewer.weight == widget.weight


def test_to_parameterized_parameter_list(widget):
viewer = to_parameterized(widget, parameters=["name", "age"])

@@ -79,6 +82,7 @@ def test_to_parameterized_parameter_list(widget):
assert viewer.name == widget.name
assert viewer.age == widget.age


def test_to_parameterized_bases(widget):

class ExampleParameterized(param.Parameterized):
@@ -95,6 +99,24 @@ class ExampleParameterized(param.Parameterized):
assert viewer.weight == widget.weight


def test_to_viewer(widget):
viewer = to_viewer(widget)

assert isinstance(viewer, Layoutable)
assert isinstance(viewer, Viewer)

component = viewer.__panel__()
assert isinstance(component, IPyWidget)

viewer.sizing_mode = "stretch_width"
assert viewer.sizing_mode == component.sizing_mode


def test_to_viewer_kwargs(widget):
viewer = to_viewer(widget, parameters=["name", "age"], sizing_mode="stretch_width")
assert viewer.__panel__().sizing_mode == "stretch_width"


def test_to_rx(widget):
age, weight, name = to_rx(widget, parameters=["age", "weight", "name"])
assert isinstance(name, param.reactive.rx)