Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,19 @@ jobs:
# run the suite against both the default renderer and the opt-in fast
# one (REACTON_FAST=1), so both stay green
reacton-fast: ["0", "1"]
# ipyvue/ipyvuetify 3 are the Vue 3 port. Pin the 1.x line by default and
# add one python version that runs against the 3.x line, so reacton keeps
# working with both.
ipyvue: ["<3"]
include:
- python-version: "3.12"
reacton-fast: "0"
ipyvue: ">=3"
- python-version: "3.12"
reacton-fast: "1"
ipyvue: ">=3"

name: unit-test (py${{ matrix.python-version }}, REACTON_FAST=${{ matrix.reacton-fast }})
name: unit-test${{ matrix.ipyvue == '>=3' && '-vue3' || '' }} (py${{ matrix.python-version }}, REACTON_FAST=${{ matrix.reacton-fast }})
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -39,7 +50,7 @@ jobs:
path: ./dist
- name: Install
run: |
pip install `echo dist/*.whl`[dev] "bqplot<0.13" "pandas<3" "ruff==0.8.3"
pip install `echo dist/*.whl`[dev] "bqplot<0.13" "pandas<3" "ruff==0.8.3" "ipyvue${{ matrix.ipyvue }}" "ipyvuetify${{ matrix.ipyvue }}"
- name: test
env:
REACTON_FAST: ${{ matrix.reacton-fast }}
Expand Down
24 changes: 22 additions & 2 deletions reacton/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@ def close_widget(widget: widgets.Widget):
logger.warning("Widget %r does not have a close method, possibly a close trait was added", widget)


def _is_shared_ipyvue_template(widget: widgets.Widget) -> bool:
"""Is this widget an ipyvue Template that is shared between VueTemplate instances?

ipyvue keeps a per-file registry of Template widgets (ipyvue/Template.py), so several
VueTemplate widgets can point at the same Template. Such a Template outlives the
VueTemplate that created it, and we must not close it as an orphan.
ipyvue 3 only puts a Template in that registry when there is a real comm, so an
unregistered Template belongs to a single VueTemplate and is a normal orphan.
"""
cls = widget.__class__
if cls.__name__ != "Template" or cls.__module__ != "ipyvue.Template":
return False
module = sys.modules.get(cls.__module__)
registry = getattr(module, "template_registry", None)
if registry is None:
# unknown ipyvue version: keep the old, conservative behaviour
return True
return any(template is widget for template in registry.values())


def _event_handler_exception_wrapper(f):
"""Wrap an event handler to catch exceptions and put them in a reacton context.

Expand Down Expand Up @@ -2166,7 +2186,7 @@ def reconsolidate_children():
if orphan_ids:
for orphan_widget in orphan_widgets:
# these are shared widgets
if orphan_widget.__class__.__name__ == "Template" and orphan_widget.__class__.__module__ == "ipyvue.Template":
if _is_shared_ipyvue_template(orphan_widget):
orphan_ids -= {orphan_widget.model_id}
if el.is_shared:
widget = self._shared_widgets[el]
Expand Down Expand Up @@ -2750,7 +2770,7 @@ def _reconsolidate(self, el: Element, default_key: str, parent_key: str):
orphan_widgets = set([_get_widgets_dict()[k] for k in orphan_ids])
for orphan_widget in orphan_widgets:
# these are shared between widgets
if orphan_widget.__class__.__name__ == "Template" and orphan_widget.__class__.__module__ == "ipyvue.Template":
if _is_shared_ipyvue_template(orphan_widget):
orphan_ids -= {orphan_widget.model_id}
widget = self._shared_widgets[el] if el.is_shared else context.widgets[key]
if widget.model_id not in self._orphans:
Expand Down
Loading