Custom Jupyter widgets made easy with modern web technologies and seamless platform integration
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Decorator for converting regular classes into widgets with ES module and CSS support, providing a more functional approach to widget creation.
def widget(
*,
esm: str | Path,
css: str | Path | None = None,
**kwargs
):
"""
Decorator to register a widget class as a mimebundle.
Parameters:
esm (str | Path): The path or contents of an ES Module for the widget
css (str | Path | None): The path or contents of a CSS file for the widget
**kwargs: Additional keyword arguments passed to the widget
Returns:
Callable: A decorator that registers the widget class as a mimebundle
Usage:
@widget(esm="./widget.js", css="./widget.css")
class MyWidget:
pass
"""Combines dataclass, psygnal events, and widget functionality into a single decorator for reactive widget creation.
def dataclass(
cls=None,
*,
esm: str | Path,
css: str | Path | None = None,
**dataclass_kwargs
):
"""
Turns class into a dataclass, makes it evented, and registers it as a widget.
Parameters:
cls (type | None): The class to decorate (when used without parentheses)
esm (str | Path): The path or contents of an ES Module for the widget
css (str | Path | None): The path or contents of a CSS file for the widget
**dataclass_kwargs: Additional keyword arguments passed to dataclass decorator
Returns:
type: The evented dataclass with widget capabilities
Usage:
@dataclass(esm="./counter.js")
class Counter:
value: int = 0
"""Advanced descriptor for managing widget representations and communication channels with fine-grained control over synchronization and display behavior.
class MimeBundleDescriptor:
"""
Descriptor that builds a ReprMimeBundle when accessed on an instance.
Manages communication channels between Python models and JavaScript views
with support for bidirectional state synchronization and event handling.
"""
def __init__(
self,
*,
follow_changes: bool = True,
autodetect_observer: bool = True,
no_view: bool = False,
**extra_state
):
"""
Initialize the descriptor with synchronization options.
Parameters:
follow_changes (bool): Enable bidirectional state synchronization
autodetect_observer (bool): Automatically detect observer patterns (psygnal, traitlets)
no_view (bool): Create DOM-less widget (comm only, no display)
**extra_state: Additional state to send to JavaScript view
"""from anywidget.experimental import widget
import psygnal
@widget(esm="./my-widget.js", css="./my-widget.css")
@psygnal.evented
class ReactiveWidget:
def __init__(self):
self.value = 0
self.label = "Counter"
def increment(self):
self.value += 1
# Usage
widget_instance = ReactiveWidget()
widget_instance # Displays in Jupyterfrom anywidget.experimental import dataclass
@dataclass(esm="""
function render({ model, el }) {
let updateDisplay = () => {
el.innerHTML = `
<div>
<h3>${model.get("title")}</h3>
<p>Value: ${model.get("value")}</p>
<p>Status: ${model.get("status")}</p>
</div>
`;
};
model.on("change", updateDisplay);
updateDisplay();
}
export default { render };
""")
class StatusWidget:
title: str = "Status Dashboard"
value: int = 0
status: str = "Ready"
# Create reactive widget
dashboard = StatusWidget(title="My Dashboard", value=42)
dashboard.value = 100 # Automatically updates displayfrom anywidget.experimental import MimeBundleDescriptor
import dataclasses
@dataclasses.dataclass
class CustomModel:
name: str = "Default"
value: float = 0.0
active: bool = True
# Custom descriptor with specific behavior
_repr_mimebundle_ = MimeBundleDescriptor(
follow_changes=True,
autodetect_observer=False, # Manual control
no_view=False,
_esm="""
function render({ model, el }) {
let update = () => {
el.innerHTML = `
<div class="custom-model">
<h4>${model.get("name")}</h4>
<div>Value: ${model.get("value")}</div>
<div>Active: ${model.get("active")}</div>
</div>
`;
};
model.on("change", update);
update();
}
export default { render };
""",
_css="""
.custom-model {
border: 2px solid #333;
padding: 10px;
border-radius: 5px;
font-family: monospace;
}
"""
)
model = CustomModel(name="Test Model", value=3.14159)
model # Displays with custom representationfrom anywidget.experimental import MimeBundleDescriptor
import threading
import time
class BackgroundProcessor:
def __init__(self):
self.status = "idle"
self.progress = 0
self.results = []
# No visual display, just communication
_repr_mimebundle_ = MimeBundleDescriptor(
no_view=True, # DOM-less widget
_esm="""
// Background communication handler
function render({ model, el }) {
// No DOM elements, just event handling
model.on("change:status", () => {
console.log("Status:", model.get("status"));
});
}
export default { render };
"""
)
def start_processing(self):
"""Start background processing"""
def process():
self.status = "running"
for i in range(101):
self.progress = i
time.sleep(0.1)
self.status = "completed"
threading.Thread(target=process, daemon=True).start()
processor = BackgroundProcessor()
processor # Creates comm channel but no display
processor.start_processing()Install with Tessl CLI
npx tessl i tessl/pypi-anywidget