marimo-team / marimo

A reactive notebook for Python — run reproducible experiments, execute as a script, deploy as an app, and version with git.
https://marimo.io
Apache License 2.0
6.82k stars 241 forks source link

Traitlets update from python not propagated to anywidget javascript #2043

Closed twitwi closed 2 months ago

twitwi commented 2 months ago

Describe the bug

I made an anywidget and while updates from js to js work, updates from python to js are not (while they are working in jupyter).

NB: besides, it is probably normal but if not "wrapped" with mo.ui.anywidget, updates from js also do not cause cell rerun in marimo neither (but such concept does not exist in jupyter).

Environment

(also tested with older versions)

{
  "marimo": "0.8.0",
  "OS": "Linux",
  "OS Version": "6.5.0-26-generic",
  "Processor": "x86_64",
  "Python Version": "3.10.12",
  "Binaries": {
    "Browser": "--",
    "Node": "v20.16.0"
  },
  "Requirements": {
    "click": "8.1.7",
    "importlib-resources": "5.12.0",
    "jedi": "0.19.1",
    "markdown": "3.6",
    "pymdown-extensions": "10.8.1",
    "pygments": "2.18.0",
    "tomlkit": "0.12.5",
    "uvicorn": "0.29.0",
    "starlette": "0.37.2",
    "websockets": "12.0",
    "typing-extensions": "4.12.0",
    "ruff": "0.0.292"
  }
}

Code to reproduce

import marimo as mo
import anywidget
import traitlets
### The anywidget example, with an additional traitlets 'msg' (but counter.count = 123  does not trigger js update either)
class CounterWidget(anywidget.AnyWidget):
    _esm = """
    function render({ model, el }) {
      const button = document.createElement("button");
      const update = () => {
          button.innerHTML = `${model.get("msg")} ${model.get("count")} (${Math.random().toFixed(3)})`;
      };
      button.addEventListener("click", () => {
        model.set("count", model.get("count") + 1);
        model.save_changes();
      });
      update();
      model.on("change:count", update);
      model.on("change:msg", update);
      el.classList.add("counter-widget");
      el.appendChild(button);
    }
    export default { render };
    """
    _css = """
    .counter-widget button { color: white; font-size: 1.75rem; background-color: #ea580c; padding: 0.5rem 1rem; border: none; border-radius: 0.25rem; }
    .counter-widget button:hover { background-color: #9a3412; }
    """
    count = traitlets.Int(0).tag(sync=True)
    msg = traitlets.Unicode('').tag(sync=True)

counter = CounterWidget(count=42, msg='count is')

# none of these 3 versions give an update when the next cell is run
#V1:
counter = mo.ui.anywidget(counter) # wrapping (NB: allows counter.value['count'] to work)
counter
#V2:
#mo.ui.anywidget(counter) # wrapping for display, not for the following setter
#V3:
#counter # not wrapping
counter.msg = 'Count:' # would expect to trigger button update
counter.value['msg'] = 'C is' # this one does not either
counter.value['count'] # (auto reruns if wrapped) (ok)
counter.count # (auto reruns too) (ok)
mscolnick commented 2 months ago

This is indeed a bug - I can look into this.

@manzt, whats the best approach to sync Py with Js? should i add my own observers to each traitlet? it seems like like widget.on_trait_change is deprecated.

manzt commented 2 months ago

@manzt, whats the best approach to sync Py with Js?

Hmm, I think I'm missing some context. I haven't look closely at the Python side of mo.ui.anywidget, but it looks like you are swapping in a custom comm, and assignment:

counter.msg = "count:"

Should trigger a publish_msg call since msg is sync=True

manzt commented 2 months ago

Happy to find a time to pair if that would help!

manzt commented 2 months ago

I wonder if this proxying is causing an issue with the underlying behavior of the getters/setters:

https://github.com/marimo-team/marimo/blob/beccc28ccada25923bcf897a80a8c1e028c51adc/marimo/_plugins/ui/_impl/from_anywidget.py#L156-L158

mscolnick commented 2 months ago

It is possible the proxying is causing the issues. My custom comm may be missing some implementation details too.

It would be great to pair with you, ill message you on Discord.

twitwi commented 2 months ago

:heart: