Skip to content

Commit

Permalink
Add ipywatch
Browse files Browse the repository at this point in the history
  • Loading branch information
itepifanio committed Feb 19, 2024
1 parent 607c7c5 commit 6427777
Show file tree
Hide file tree
Showing 11 changed files with 435 additions and 68 deletions.
8 changes: 0 additions & 8 deletions debugbar/_modidx.py

This file was deleted.

7 changes: 0 additions & 7 deletions debugbar/core.py

This file was deleted.

File renamed without changes.
34 changes: 34 additions & 0 deletions ipywatch/_modidx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Autogenerated by nbdev

d = { 'settings': { 'branch': 'main',
'doc_baseurl': '/ipywatch',
'doc_host': 'https://itepifanio.github.io',
'git_url': 'https://github.com/itepifanio/ipywatch',
'lib_path': 'ipywatch'},
'syms': { 'ipywatch.history': { 'ipywatch.history.WidgetStateHistory': ('widget_history.html#widgetstatehistory', 'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.__delitem__': ( 'widget_history.html#widgetstatehistory.__delitem__',
'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.__getitem__': ( 'widget_history.html#widgetstatehistory.__getitem__',
'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.__init__': ( 'widget_history.html#widgetstatehistory.__init__',
'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.__iter__': ( 'widget_history.html#widgetstatehistory.__iter__',
'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.__len__': ( 'widget_history.html#widgetstatehistory.__len__',
'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.__setitem__': ( 'widget_history.html#widgetstatehistory.__setitem__',
'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.get_current_state': ( 'widget_history.html#widgetstatehistory.get_current_state',
'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.get_state_history': ( 'widget_history.html#widgetstatehistory.get_state_history',
'ipywatch/history.py'),
'ipywatch.history.WidgetStateHistory.set_state': ( 'widget_history.html#widgetstatehistory.set_state',
'ipywatch/history.py')},
'ipywatch.ipywatch': { 'ipywatch.ipywatch.Ipywatch': ('ipywatch.html#ipywatch', 'ipywatch/ipywatch.py'),
'ipywatch.ipywatch.Ipywatch.__init__': ('ipywatch.html#ipywatch.__init__', 'ipywatch/ipywatch.py'),
'ipywatch.ipywatch.Ipywatch._on_state_change': ( 'ipywatch.html#ipywatch._on_state_change',
'ipywatch/ipywatch.py'),
'ipywatch.ipywatch.WidgetStateHistoryListener': ( 'ipywatch.html#widgetstatehistorylistener',
'ipywatch/ipywatch.py'),
'ipywatch.ipywatch.WidgetStateHistoryListener.__init__': ( 'ipywatch.html#widgetstatehistorylistener.__init__',
'ipywatch/ipywatch.py')}}}
50 changes: 50 additions & 0 deletions ipywatch/history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_widget_history.ipynb.

# %% auto 0
__all__ = ['WidgetStateHistory']

# %% ../nbs/02_widget_history.ipynb 3
from collections import deque
from typing import Any, Deque, Dict, Iterator

# %% ../nbs/02_widget_history.ipynb 4
class WidgetStateHistory:
def __init__(self, history_size: int = 5):
self.history_size = history_size
self._widget_states: Dict[str, Any] = {}

def set_state(self, comm_id: str, state: Any):
if comm_id not in self._widget_states:
self._widget_states[comm_id] = deque(maxlen=self.history_size)

self._widget_states[comm_id].append(state)

def get_current_state(self, comm_id: str):
if comm_id in self._widget_states and self._widget_states[comm_id]:
return self._widget_states[comm_id][-1]

raise KeyError(f"No state found for widget comm_id: {comm_id}")

def get_state_history(self, comm_id: str) -> Any:
if comm_id in self._widget_states:
return self._widget_states[comm_id]

raise KeyError(f"No history found for widget comm_id: {comm_id}")

def __setitem__(self, comm_id: str, state: Any):
self.set_state(comm_id, state)

def __getitem__(self, comm_id: str):
return self.get_current_state(comm_id)

def __delitem__(self, comm_id: str):
if comm_id in self._widget_states:
del self._widget_states[comm_id]
else:
raise KeyError(f"Comm ID {comm_id} not found")

def __iter__(self) -> Iterator[str]:
return iter(self._widget_states)

def __len__(self) -> int:
return len(self._widget_states)
67 changes: 67 additions & 0 deletions ipywatch/ipywatch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_ipywatch.ipynb.

# %% auto 0
__all__ = ['WidgetStateHistoryListener', 'Ipywatch']

# %% ../nbs/03_ipywatch.ipynb 3
from typing import Any, Callable, List

import ipywidgets
from ipykernel.comm import Comm
from ipywidgets import HBox, VBox, Label, Output, Button, Text, Tab, Layout

from ipywatch.history import WidgetStateHistory

# %% ../nbs/03_ipywatch.ipynb 4
class WidgetStateHistoryListener:
def __init__(
self,
history_size: int = 5,
on_state_change: Callable[[ipywidgets.Widget, Any], None]=None
):
self.history_size = history_size
self.history = WidgetStateHistory(history_size)
self.on_state_change = on_state_change

_original_send = Comm.send

def _patched_send(comm, data=None, metadata=None, buffers=None):
comm_id = comm.comm_id

widget = ipywidgets.Widget.widgets.get(comm_id)

self.history.set_state(comm_id, data)

if self.on_state_change:
self.on_state_change(widget, data)

_original_send(comm, data, metadata, buffers)

Comm.send = _patched_send

# %% ../nbs/03_ipywatch.ipynb 5
class Ipywatch(HBox):
def __init__(self, width: str = '100%', height: str = '400px', history_size: int = 5, **kwargs):
self.updating = False # Flag to prevent recursion

self.listener = WidgetStateHistoryListener(history_size=history_size)

self.messages = Output(layout=dict(width='100%', height='400px'))

self.listener.on_state_change = self._on_state_change

super().__init__(
children=[self.messages],
layout=Layout(width=width, height=height)
)

def _on_state_change(self, widget, state):
if self.updating:
return

self.updating = True

widget_type = type(widget).__name__ if widget else "Unknown"
self.messages.append_stdout(f"Event emitted by {widget_type}: {state}\n")

self.updating = False
1 change: 0 additions & 1 deletion nbs/00_comm.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"#| hide\n",
"import solara\n",
"import ipywidgets as widgets\n",
"\n",
"from ipykernel.comm import Comm"
]
},
Expand Down
63 changes: 15 additions & 48 deletions nbs/01_reacton.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
"from reacton.core import _RenderContext, ComponentContext, Element"
]
},
{
"cell_type": "markdown",
"id": "e72c2232-af05-40ca-8250-b147590d7b75",
"metadata": {},
"source": [
"Adding a `pre_render` hook to reacton codebase we're able to track each state change at component level. "
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -35,7 +43,10 @@
"_original_render = _RenderContext.render\n",
"\n",
"def pre_render(self, element: Element, container: widgets.Widget = None):\n",
" print(f'pre_render::{self.state_get()} \\n\\n{element.component}\\n\\n{container if container is None else container.model_id}')\n",
" print(f'state::{self.state_get()}\\n')\n",
" print(f'element: {element.component} --- {element.component.name}, {element.component.value_name}, {element.component.widget}\\n')\n",
" if container is not None:\n",
" print(f'model_id::{container.model_id}')\n",
"\n",
"def _patched_render(self, element: Element, container: widgets.Widget = None):\n",
" pre_render(self, element, container)\n",
Expand All @@ -45,58 +56,14 @@
"_RenderContext.render = _patched_render"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c8fab2f-37d2-4c41-a0fa-c0a41ec4249e",
"metadata": {},
"outputs": [],
"source": [
"\"\"\"\n",
"_original_state_get = _RenderContext.state_get\n",
"_original_state_set = _RenderContext.state_set\n",
"\n",
"def _patched_state_get(self, context: Optional[ComponentContext] = None):\n",
" state = context.state if context is not None else context\n",
" print(f'_patched_state_get::{state}')\n",
"\n",
" _original_state_get(self, context)\n",
"\n",
"def _patched_state_set(self, context: ComponentContext, state):\n",
" print(f'_patched_state_set::{state}')\n",
"\n",
" _original_state_set(self, context, state)\n",
"\n",
"_RenderContext.state_get = _patched_state_get\n",
"_RenderContext.state_set = _patched_state_set\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"id": "9d112073-92ce-4571-9070-8dd51a89beb1",
"metadata": {},
"source": [
"The following example was adapted from [reacton test suite](https://github.com/widgetti/reacton/blob/3eee8f7681d5aad8d56c284f0c76fb7e55f8d917/reacton/core_test.py#L1982). Patching `state_get` and `state_set` didn't seems to track the widget changes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5f290cc7-2e9b-4a07-963e-33290c663c8c",
"metadata": {},
"outputs": [],
"source": [
"@react.component\n",
"def Test():\n",
" value, set_value = react.use_state(0)\n",
" return react_widgets.IntSlider(value=value)\n",
"## Testing\n",
"\n",
"slider, rc = react.render_fixed(Test())\n",
"state = rc.state_get()\n",
"box = widgets.VBox()\n",
"hbox, rc = react.render(Test(), box, initial_state=state, handle_error=False)\n",
"slider"
"The following cells displays ipywidgets an solara example of monitoring state changes. Interact with the following widgets to intercept its state changes."
]
},
{
Expand All @@ -107,7 +74,7 @@
"outputs": [],
"source": [
"int_value = solara.reactive(0)\n",
"solara.SliderInt(\"Another Test Slider:\", value=int_value, min=0, max=10)"
"slider = solara.SliderInt(\"Another Test Slider:\", value=int_value, min=0, max=10)"
]
}
],
Expand Down
119 changes: 119 additions & 0 deletions nbs/02_widget_history.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "c6bc2197-18a4-4b3c-9124-9e8eb3eccbdb",
"metadata": {},
"outputs": [],
"source": [
"#| default_exp history"
]
},
{
"cell_type": "markdown",
"id": "06df2b84-4f6b-4933-bedb-971fea8d447e",
"metadata": {},
"source": [
"# Widget history"
]
},
{
"cell_type": "markdown",
"id": "fa1e8874-8cfa-4d6c-8af6-75e7f4b71141",
"metadata": {},
"source": [
"Store a fixed number of stored states in-memory"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c43c72ad-2594-4fa7-a905-b14db392c09e",
"metadata": {},
"outputs": [],
"source": [
"#| exporti\n",
"from collections import deque\n",
"from typing import Any, Deque, Dict, Iterator"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8ae746d4-240e-4ed7-9681-76b7a72af0bf",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class WidgetStateHistory:\n",
" def __init__(self, history_size: int = 5):\n",
" self.history_size = history_size\n",
" self._widget_states: Dict[str, Any] = {}\n",
"\n",
" def set_state(self, comm_id: str, state: Any):\n",
" if comm_id not in self._widget_states:\n",
" self._widget_states[comm_id] = deque(maxlen=self.history_size)\n",
"\n",
" self._widget_states[comm_id].append(state)\n",
"\n",
" def get_current_state(self, comm_id: str):\n",
" if comm_id in self._widget_states and self._widget_states[comm_id]:\n",
" return self._widget_states[comm_id][-1]\n",
"\n",
" raise KeyError(f\"No state found for widget comm_id: {comm_id}\")\n",
"\n",
" def get_state_history(self, comm_id: str) -> Any:\n",
" if comm_id in self._widget_states:\n",
" return self._widget_states[comm_id]\n",
"\n",
" raise KeyError(f\"No history found for widget comm_id: {comm_id}\")\n",
"\n",
" def __setitem__(self, comm_id: str, state: Any):\n",
" self.set_state(comm_id, state)\n",
"\n",
" def __getitem__(self, comm_id: str):\n",
" return self.get_current_state(comm_id)\n",
"\n",
" def __delitem__(self, comm_id: str):\n",
" if comm_id in self._widget_states:\n",
" del self._widget_states[comm_id]\n",
" else:\n",
" raise KeyError(f\"Comm ID {comm_id} not found\")\n",
"\n",
" def __iter__(self) -> Iterator[str]:\n",
" return iter(self._widget_states)\n",
"\n",
" def __len__(self) -> int:\n",
" return len(self._widget_states)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "323932aa-7412-4c70-9a1f-a3b313ad5139",
"metadata": {},
"outputs": [],
"source": [
"widget_states = WidgetStateHistory(history_size=5)\n",
"\n",
"widget_states[\"widget_1\"] = {\"value\": 10}\n",
"widget_states[\"widget_1\"] = {\"value\": 20}\n",
"assert len(widget_states) == 1\n",
"assert widget_states['widget_1'] == {\"value\": 20}\n",
"\n",
"del widget_states[\"widget_1\"] \n",
"assert len(widget_states) == 0"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "python3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading

0 comments on commit 6427777

Please sign in to comment.