0
# Experimental Features
1
2
Advanced decorator-based patterns and cutting-edge features for creating widgets using modern Python idioms. These experimental features provide alternative approaches to widget creation using dataclasses, event systems, and function-based patterns.
3
4
## Capabilities
5
6
### Widget Decorator
7
8
Decorator for converting regular classes into widgets with ES module and CSS support, providing a more functional approach to widget creation.
9
10
```python { .api }
11
def widget(
12
*,
13
esm: str | Path,
14
css: str | Path | None = None,
15
**kwargs
16
):
17
"""
18
Decorator to register a widget class as a mimebundle.
19
20
Parameters:
21
esm (str | Path): The path or contents of an ES Module for the widget
22
css (str | Path | None): The path or contents of a CSS file for the widget
23
**kwargs: Additional keyword arguments passed to the widget
24
25
Returns:
26
Callable: A decorator that registers the widget class as a mimebundle
27
28
Usage:
29
@widget(esm="./widget.js", css="./widget.css")
30
class MyWidget:
31
pass
32
"""
33
```
34
35
### Dataclass Widget Decorator
36
37
Combines dataclass, psygnal events, and widget functionality into a single decorator for reactive widget creation.
38
39
```python { .api }
40
def dataclass(
41
cls=None,
42
*,
43
esm: str | Path,
44
css: str | Path | None = None,
45
**dataclass_kwargs
46
):
47
"""
48
Turns class into a dataclass, makes it evented, and registers it as a widget.
49
50
Parameters:
51
cls (type | None): The class to decorate (when used without parentheses)
52
esm (str | Path): The path or contents of an ES Module for the widget
53
css (str | Path | None): The path or contents of a CSS file for the widget
54
**dataclass_kwargs: Additional keyword arguments passed to dataclass decorator
55
56
Returns:
57
type: The evented dataclass with widget capabilities
58
59
Usage:
60
@dataclass(esm="./counter.js")
61
class Counter:
62
value: int = 0
63
"""
64
```
65
66
67
### MimeBundleDescriptor
68
69
Advanced descriptor for managing widget representations and communication channels with fine-grained control over synchronization and display behavior.
70
71
```python { .api }
72
class MimeBundleDescriptor:
73
"""
74
Descriptor that builds a ReprMimeBundle when accessed on an instance.
75
76
Manages communication channels between Python models and JavaScript views
77
with support for bidirectional state synchronization and event handling.
78
"""
79
80
def __init__(
81
self,
82
*,
83
follow_changes: bool = True,
84
autodetect_observer: bool = True,
85
no_view: bool = False,
86
**extra_state
87
):
88
"""
89
Initialize the descriptor with synchronization options.
90
91
Parameters:
92
follow_changes (bool): Enable bidirectional state synchronization
93
autodetect_observer (bool): Automatically detect observer patterns (psygnal, traitlets)
94
no_view (bool): Create DOM-less widget (comm only, no display)
95
**extra_state: Additional state to send to JavaScript view
96
"""
97
```
98
99
100
## Usage Examples
101
102
### Widget Decorator Pattern
103
104
```python
105
from anywidget.experimental import widget
106
import psygnal
107
108
@widget(esm="./my-widget.js", css="./my-widget.css")
109
@psygnal.evented
110
class ReactiveWidget:
111
def __init__(self):
112
self.value = 0
113
self.label = "Counter"
114
115
def increment(self):
116
self.value += 1
117
118
# Usage
119
widget_instance = ReactiveWidget()
120
widget_instance # Displays in Jupyter
121
```
122
123
### Dataclass Widget Pattern
124
125
```python
126
from anywidget.experimental import dataclass
127
128
@dataclass(esm="""
129
function render({ model, el }) {
130
let updateDisplay = () => {
131
el.innerHTML = `
132
<div>
133
<h3>${model.get("title")}</h3>
134
<p>Value: ${model.get("value")}</p>
135
<p>Status: ${model.get("status")}</p>
136
</div>
137
`;
138
};
139
140
model.on("change", updateDisplay);
141
updateDisplay();
142
}
143
export default { render };
144
""")
145
class StatusWidget:
146
title: str = "Status Dashboard"
147
value: int = 0
148
status: str = "Ready"
149
150
# Create reactive widget
151
dashboard = StatusWidget(title="My Dashboard", value=42)
152
dashboard.value = 100 # Automatically updates display
153
```
154
155
156
### Custom MimeBundleDescriptor
157
158
```python
159
from anywidget.experimental import MimeBundleDescriptor
160
import dataclasses
161
162
@dataclasses.dataclass
163
class CustomModel:
164
name: str = "Default"
165
value: float = 0.0
166
active: bool = True
167
168
# Custom descriptor with specific behavior
169
_repr_mimebundle_ = MimeBundleDescriptor(
170
follow_changes=True,
171
autodetect_observer=False, # Manual control
172
no_view=False,
173
_esm="""
174
function render({ model, el }) {
175
let update = () => {
176
el.innerHTML = `
177
<div class="custom-model">
178
<h4>${model.get("name")}</h4>
179
<div>Value: ${model.get("value")}</div>
180
<div>Active: ${model.get("active")}</div>
181
</div>
182
`;
183
};
184
model.on("change", update);
185
update();
186
}
187
export default { render };
188
""",
189
_css="""
190
.custom-model {
191
border: 2px solid #333;
192
padding: 10px;
193
border-radius: 5px;
194
font-family: monospace;
195
}
196
"""
197
)
198
199
model = CustomModel(name="Test Model", value=3.14159)
200
model # Displays with custom representation
201
```
202
203
### DOM-less Widget (Communication Only)
204
205
```python
206
from anywidget.experimental import MimeBundleDescriptor
207
import threading
208
import time
209
210
class BackgroundProcessor:
211
def __init__(self):
212
self.status = "idle"
213
self.progress = 0
214
self.results = []
215
216
# No visual display, just communication
217
_repr_mimebundle_ = MimeBundleDescriptor(
218
no_view=True, # DOM-less widget
219
_esm="""
220
// Background communication handler
221
function render({ model, el }) {
222
// No DOM elements, just event handling
223
model.on("change:status", () => {
224
console.log("Status:", model.get("status"));
225
});
226
}
227
export default { render };
228
"""
229
)
230
231
def start_processing(self):
232
"""Start background processing"""
233
def process():
234
self.status = "running"
235
for i in range(101):
236
self.progress = i
237
time.sleep(0.1)
238
self.status = "completed"
239
240
threading.Thread(target=process, daemon=True).start()
241
242
processor = BackgroundProcessor()
243
processor # Creates comm channel but no display
244
processor.start_processing()
245
```