Skip to content
Feeding a clanker? Grab this page as raw .md

TangleFunction API#

TangleFunction introspects a function's type hints and defaults and renders it as a clean call expression such as train(lr=0.01, epochs=10, optimizer='adam'). Numbers drag horizontally to scrub (and click to type an exact value), Literal/Enum/bool arguments click to cycle through their known options, and strings are click-to-edit. The live arguments land in values, ready to splat into the function with fn(**tf.value["values"]).

Numbers are unbounded scrubbers by default. Give one a range or a step by annotating it with annotated_types constraints (Ge, Le, Gt, Lt, MultipleOf) — the same constraints pydantic uses, so pydantic.Field(ge=..., le=...) works too — or with a params= override.

See also: TangleLatex for the same drag gesture inside a LaTeX formula, and Tangle widgets for individual inline sliders and choices in plain prose.

Bases: AnyWidget

Render a typed Python function as an interactive call expression.

Each argument becomes editable in place: numbers drag horizontally to scrub, Literal/Enum/bool arguments click to cycle through their known options, and strings are click-to-edit. The live arguments are synced to the values dict, ready to splat into the function.

Examples:

import marimo as mo
from typing import Literal
from wigglystuff import TangleFunction

def train(lr: float = 0.01, epochs: int = 10,
          optimizer: Literal["adam", "sgd"] = "adam", shuffle: bool = True):
    ...

tf = mo.ui.anywidget(TangleFunction(train))
tf
# in another cell:
train(**tf.value["values"])

Numbers are unbounded scrubbers by default. Give one a range or a step by annotating it with annotated_types constraints (the same ones pydantic uses, so pydantic.Field(ge=..., le=...) works too):

```python
from typing import Annotated
from annotated_types import Ge, Le, MultipleOf

def generate(temperature: Annotated[float, Ge(0), Le(2), MultipleOf(0.05)] = 0.7):
    ...

mo.ui.anywidget(TangleFunction(generate))
```
Note

For Enum parameters the emitted value is the member's .value (so it stays JSON-serializable and matches the displayed label). Literal is the recommended way to expose a known set of options.

Create a TangleFunction widget.

Parameters:

Name Type Description Default
fn Callable[..., Any]

The function to render. Its parameters are introspected from type hints and defaults.

required
params Optional[Mapping[str, Mapping[str, Any]]]

Optional per-parameter overrides keyed by name. For numeric parameters: min_value, max_value, step, digits, pixels_per_step, and value. Any parameter may also be given options (a list) to force a click-cycle choice. These take precedence over annotated_types/pydantic.Field constraints read from the parameter's Annotated metadata.

None
theme str

Color theme: "auto", "light", or "dark".

'auto'
width int

Maximum width in pixels before the expression wraps.

560
**kwargs Any

Forwarded to anywidget.AnyWidget.

{}
Source code in wigglystuff/tangle_function.py
def __init__(
    self,
    fn: Callable[..., Any],
    params: Optional[Mapping[str, Mapping[str, Any]]] = None,
    *,
    theme: str = "auto",
    width: int = 560,
    **kwargs: Any,
) -> None:
    """Create a TangleFunction widget.

    Args:
        fn: The function to render. Its parameters are introspected from type
            hints and defaults.
        params: Optional per-parameter overrides keyed by name. For numeric
            parameters: ``min_value``, ``max_value``, ``step``, ``digits``,
            ``pixels_per_step``, and ``value``. Any parameter may also be
            given ``options`` (a list) to force a click-cycle choice. These
            take precedence over ``annotated_types``/``pydantic.Field``
            constraints read from the parameter's ``Annotated`` metadata.
        theme: Color theme: ``"auto"``, ``"light"``, or ``"dark"``.
        width: Maximum width in pixels before the expression wraps.
        **kwargs: Forwarded to ``anywidget.AnyWidget``.
    """
    if not callable(fn):
        raise ValueError("fn must be callable.")
    params = params or {}
    if not isinstance(params, Mapping):
        raise ValueError("params must be a mapping of parameter names to configs.")
    if theme not in {"auto", "light", "dark"}:
        raise ValueError("theme must be 'auto', 'light', or 'dark'.")

    try:
        sig = inspect.signature(fn)
    except (ValueError, TypeError) as exc:
        raise ValueError("fn must be an introspectable callable.") from exc
    try:
        hints = typing.get_type_hints(fn, include_extras=True)
    except Exception:
        hints = {}

    parameters: dict[str, dict[str, Any]] = {}
    param_order: list[str] = []
    for pname, param in sig.parameters.items():
        if pname == "self" or param.kind in _SKIP_KINDS:
            continue
        override = params.get(pname, {})
        if not isinstance(override, Mapping):
            raise ValueError(f"params[{pname!r}] must be a mapping.")
        hint = hints.get(pname, param.annotation)
        if hint is inspect.Parameter.empty:
            hint = None
        parameters[pname] = _classify(pname, param, hint, override)
        param_order.append(pname)

    unknown = sorted(set(params) - set(parameters))
    if unknown:
        raise ValueError(
            "params keys not found among the function's parameters: "
            + ", ".join(unknown)
        )

    values = {name: parameters[name]["value"] for name in param_order}
    super().__init__(
        fn_name=getattr(fn, "__name__", "f"),
        parameters=parameters,
        param_order=param_order,
        values=values,
        theme=theme,
        width=width,
        **kwargs,
    )
    self._fn = fn

Synced traitlets#

Traitlet Type Notes
fn_name str The function's name, shown before the opening parenthesis.
parameters dict Per-parameter render spec (kind, options/bounds/step/digits).
param_order list Parameter names in signature order.
values dict Live current value for each parameter; splat into the function.
theme str "auto", "light", or "dark".
width int Max width in pixels before the expression wraps one arg per line.
error str Validation/render error surfaced from the widget.