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
Show file tree
Hide file tree
Changes from all commits
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
33 changes: 33 additions & 0 deletions doc/how_to/dataclass/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Interfacing with ipywidgets and Pydantic Models

Panel components and APIs are primarily built on [Param](https://param.holoviz.org/), a data-class-like library that adds parameter validation and event listeners to your objects.

When working with other frameworks like ipywidgets or Pydantic, which also provide dataclass-like objects, we offer various utilities to create Parameterized objects, `rx` expressions, and more for seamless integration with Panel.

::::{grid} 2 3 3 5
:gutter: 1 1 1 2

:::{grid-item-card} {octicon}`book;2.5em;sd-mr-1 sd-animate-grow50` Interact with ipywidgets
:link: ipywidget
:link-type: doc

How to interact with ipywidgets via familiar APIs like watch, bind, depends, and rx.
:::

:::{grid-item-card} {octicon}`triangle-up;2.5em;sd-mr-1 sd-animate-grow50` Interact with Pydantic models
:link: pydantic
:link-type: doc

How to interact with Pydantic Models via familiar APIs like watch, bind, depends, and rx.
:::

::::

```{toctree}
:titlesonly:
:hidden:
:maxdepth: 2

ipywidget
pydantic
```
185 changes: 185 additions & 0 deletions doc/how_to/dataclass/ipywidget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Interact with ipywidgets

This how-to guide demonstrates how to easily enable interaction with [ipywidgets](https://ipywidgets.readthedocs.io/en/stable/) using familiar APIs from Panel and [Param](https://param.holoviz.org/), such as `watch`, `bind`, `depends`, and `rx`.

## Overview

The `pn.dataclass` module provides functions to synchronize the *traits* of an ipywidget with a Parameterized object, a Panel widget, or an `rx` value. It also provides a `ModelViewer` class for creating a Layoutable Viewer that wraps an ipywidget Widget class or instance.

### Terminology

- **Traitlets**: A library for creating dataclass like models (`HasTraits`) with observable fields (called *traits*).
- **ipywidgets**: A library for creating interactive widgets for notebooks. Its base class `Widget` derives from `HasTraits`.
- **widget**: Refers to ipywidgets `Widget` classes or instances unless otherwise stated.
- **model**: Refers to Traitlets `HasTraits` and ipywidgets `Widget` classes or instances.
- **names**: Refers to the names of the traits/parameters to synchronize. Can be an iterable or a dictionary mapping from trait names to parameter names.

### Classes

- **`ModelParameterized`**: An abstract Parameterized base class for wrapping a Traitlets `HasTraits` class or instance.
- **`ModelViewer`**: An abstract base class for creating a Layoutable Viewer that wraps an ipywidget Widget class or instance.

### Functions

- **`to_rx`**: Creates `rx` values from traits of a model, each synced to a trait of the model.
- **`sync_with_parameterized`**: Syncs the traits of a model with the parameters of a Parameterized object.
- **`sync_with_widget`**: Syncs a trait of the model with the value of a Panel widget.
- **`sync_with_rx`**: Syncs a single trait of a model with an `rx` value.

All synchronization is bidirectional. Only top-level traits/parameters are synchronized, not nested ones.

## Synchronize Traits of an ipywidget with Panel Widgets

Use `sync_with_widget` to synchronize a trait of an ipywidget with the `value` parameter of a Panel widget.

```{pyodide}
import panel as pn
import ipyleaflet as ipyl

pn.extension("ipywidgets")

leaflet_map = ipyl.Map()

zoom_widget = pn.widgets.FloatSlider(value=4.0, start=1.0, end=24.0, name="Zoom")
zoom_control_widget = pn.widgets.Checkbox(name="Show Zoom Control")

pn.dataclass.sync_with_widget(leaflet_map, zoom_widget, name="zoom")
pn.dataclass.sync_with_widget(leaflet_map, zoom_control_widget, name="zoom_control")
pn.Column(leaflet_map, zoom_widget, zoom_control_widget).servable()
```

## Synchronize an ipywidget with a Parameterized Object

Use `sync_with_parameterized` to synchronize an ipywidget with a Parameterized object.

```{pyodide}
import panel as pn
import ipyleaflet as ipyl
import param

pn.extension("ipywidgets")

leaflet_map = ipyl.Map()

class Map(param.Parameterized):
center = param.List(default=[52.204793, 360.121558])
zoom = param.Number(default=4, bounds=(0, 24), step=1)

parameterized = Map()

pn.dataclass.sync_with_parameterized(
model=leaflet_map, parameterized=parameterized
)
pn.Column(leaflet_map, parameterized.param.zoom, parameterized.param.center).servable()
```

The `sync_with_parameterized` function synchronizes the shared traits/parameters `center` and `zoom` between the `leaflet_map` and `parameterized` objects.

To specify a subset of traits/parameters to synchronize, use the `names` argument:

```python
pn.dataclass.sync_with_parameterized(
model=leaflet_map, parameterized=parameterized, names=("center",)
)
```

The `names` argument can also be a dictionary mapping trait names to parameter names:

```{pyodide}
import panel as pn
import ipyleaflet as ipyl
import param

pn.extension("ipywidgets")

leaflet_map = ipyl.Map()

class Map(param.Parameterized):
zoom_level = param.Number(default=4, bounds=(0, 24), step=1)

parameterized = Map()

pn.dataclass.sync_with_parameterized(
model=leaflet_map, parameterized=parameterized, names={"zoom": "zoom_level"}
)
pn.Column(leaflet_map, parameterized.param.zoom_level).servable()
```

## Create a Viewer from an ipywidget Instance

To create a `Viewer` object from a ipywidget instance, use the `ModelViewer` class:

```{pyodide}
import panel as pn
import ipyleaflet as ipyl

pn.extension("ipywidgets")

leaflet_map = ipyl.Map(zoom=4)

viewer = pn.dataclass.ModelViewer(model=leaflet_map, sizing_mode="stretch_both")
pn.Row(pn.Column(viewer.param, scroll=True), viewer, height=400).servable()
```

Check out the parameters to the left of the map, there you will find all the traits of the `leaflet_map` instance. Try changing some.

To specify a subset of traits/parameters to synchronize, use the `names` argument:

```python
viewer = pn.dataclass.ModelViewer(
model=leaflet_map, names=("center",), sizing_mode="stretch_both"
)
```

The `names` argument can also be a dictionary mapping trait names to parameter names.

## Create a Viewer from an ipywidget Class

To create a `Viewer` class from an ipywidget class, use the `ModelViewer` class:

```{pyodide}
import panel as pn
import ipyleaflet as ipyl
import param

pn.extension("ipywidgets")

class MapViewer(pn.dataclass.ModelViewer):
_model_class = ipyl.Map
_model_names = ["center", "zoom"]

zoom = param.Number(4, bounds=(0, 24), step=1)

viewer = MapViewer(sizing_mode="stretch_both")

pn.Row(pn.Column(viewer.param, scroll=True), viewer, height=400).servable()
```

The `_model_names` attribute is an optional iterable or dictionary. It specifies which traits to synchronize to which parameters.

## Create Reactive Values from the Traits of an ipywidget

Use `to_rx` to create reactive values from the traits of an ipywidget.

```{pyodide}
import panel as pn
import ipyleaflet as ipyl

pn.extension("ipywidgets")

leaflet_map = ipyl.Map(zoom=4)
zoom, zoom_control = pn.dataclass.to_rx(
leaflet_map, "zoom", "zoom_control"
)

pn.Column(
leaflet_map,
zoom.rx.pipe(
lambda x: f"**Value**: {x}, **Zoom Control**: " + zoom_control.rx.pipe(str)
),
).servable()
```

## References

- [IPyWidget Pane Reference](../../reference/panes/IPyWidget.ipynb)
Loading
Loading