CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-anywidget

Custom Jupyter widgets made easy with modern web technologies and seamless platform integration

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

experimental.mddocs/

Experimental Features

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.

Capabilities

Widget Decorator

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
    """

Dataclass Widget Decorator

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
    """

MimeBundleDescriptor

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
        """

Usage Examples

Widget Decorator Pattern

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 Jupyter

Dataclass Widget Pattern

from 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 display

Custom MimeBundleDescriptor

from 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 representation

DOM-less Widget (Communication Only)

from 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

docs

core-widget.md

experimental.md

file-management.md

index.md

ipython-integration.md

tile.json