widgetti / reacton Goto Github PK
View Code? Open in Web Editor NEWA pure Python port of React for ipywidgets
Home Page: https://reacton.solara.dev/
License: MIT License
A pure Python port of React for ipywidgets
Home Page: https://reacton.solara.dev/
License: MIT License
Hi, I feel this project should really help me but I am struggling to get how it would be beneficial compared to a class type structure. Also, would there be a benefit to using Reacton within a class structure? I couldn't get that to work. I would very much appreciate your thoughts on the following button click examples below. In particular, how can Reacton work in classes? and would that structure typically be used in large Jupyter widget web-app projects?
import ipywidgets as widgets
clicks = 0 # issue 3
def on_click(button):
global clicks # issue 3
clicks += 1
button.description = f"Clicked {clicks} times"
button = widgets.Button(description="Clicked 0 times")
button.on_click(on_click) #
button
import reacton
@reacton.component
def ButtonClick():
# first render, this return 0, after that, the last argument
# of set_clicks
clicks, set_clicks = reacton.use_state(0)
def my_click_handler():
# trigger a new render with a new value for clicks
set_clicks(clicks+1)
button = w.Button(description=f"Clicked {clicks} times",
on_click=my_click_handler)
return button
ButtonClick()
The method I currently use for large projects.
import ipywidgets as widgets
class ButtonClick:
def __init__(self):
self.clicks = -1 # issue 3
self.button = widgets.Button()
self.button.on_click(self.on_click)
self.on_click(_)
def on_click(self,_):
self.clicks += 1
self.button.description = f"Clicked {self.clicks} times"
ButtonClick().button
Is this even needed? I'm not sure. It would be nice to incorporate Reacton in existing code within a class based structure.
class ButtonClick_r:
def __init__(self):
self.ButtonClick()
self.clicks= 0
self.button = w.Button(description=f"Clicked {self.clicks} times",
on_click=self.my_click_handler)
@reacton.component
def ButtonClick(self):
# first render, this return 0, after that, the last argument
# of set_clicks
self.clicks, set_clicks = reacton.use_state(0)
def my_click_handler(self):
# trigger a new render with a new value for clicks
set_clicks(self.clicks+1)
ButtonClick_r().button
Started exploring the project, no react knowledge, I like the pure python approach.
Just a minor thing:
In the getting-started.md the example says:
el = ButtonClick
display(el)
I guess this was meant "()":
el = ButtonClick()
display(el)
Thanks a ton.
Consider the following simple component:
import solara as sl
@sl.component
def Page():
initial = sl.use_reactive(1)
def make_invalid():
initial.set(-initial.value)
sl.Button("Toggle invalid", on_click=make_invalid)
if initial.value < 0:
sl.Markdown("Invalid value")
return
another_reactive = sl.use_reactive(0)
After clicking the button, we get the following error:
Traceback (most recent call last):
File "/Users/ntjess/miniconda3/envs/py312/lib/python3.12/site-packages/reacton/core.py", line 1751, in _render
raise RuntimeError(
RuntimeError: Previously render had 4 effects, this run 3 (in element/component: Page()/react.component(__main__.Page)). Are you using conditional hooks?
In this case, it is clear how to fix the issue. But if the condition to trigger conditional hooks rarely appears, debugging is especially difficult.
I wrote the following function which attempts to detect these cases at compile time:
DEFAULT_USE_FUNCTIONS = (
"use_state",
"use_reactive",
"use_thread",
"use_task",
"use_effect",
"use_memo",
)
def error_on_early_return(component: t.Callable, use_functions=DEFAULT_USE_FUNCTIONS):
nodes = list(ast.walk(ast.parse(inspect.getsource(component))))
earliest_return_node: ast.Return | None = None
latest_use_node: ast.expr | None = None
latest_use_node_id = ""
for node in nodes:
if isinstance(node, ast.Return):
if (
earliest_return_node is None
or node.lineno > earliest_return_node.lineno
):
earliest_return_node = node
elif isinstance(node, ast.Call):
func = node.func
if isinstance(func, ast.Call):
# Nested function, it will appear in another node later
continue
if isinstance(func, ast.Name):
id_ = func.id
elif isinstance(func, ast.Attribute):
id_ = func.attr
else:
raise ValueError(
f"Unexpected function node type: {func}, {func.lineno=}"
)
if id_ in use_functions and (
latest_use_node is None or node.lineno > latest_use_node.lineno
):
latest_use_node = node
latest_use_node_id = id_
if (
earliest_return_node
and latest_use_node
and earliest_return_node.lineno <= latest_use_node.lineno
):
raise ValueError(
f"{component}: `{latest_use_node_id}` found on line {latest_use_node.lineno} despite early"
f" return on line {earliest_return_node. lineno}"
)
Running this on the sample component provided a much more helpful error (again, at compile time instead of after a conditional toggle!):
ValueError: <function Page at 0x100ebe200>: `use_reactive` found on line 13 despite early return on line 12
I am curious what other cases should be detected and whether you think this function can be adopted into reacton or solara more generally.
use_*
inside an if
or for
blockreturn
before use_*
use_*
outside a render context (this would be a bit harder, but still possible by tracing ast.Call
usage)In my case, I wrapped solara.component
to call this function first and found a few other conditional hook bugs in my existing components.
When using an ipywidgets.Output widget in a reacton component, plots are not displayed in notebook. I've tested this with the ipympl and inline backends. It works fine when using ipywidgets directly, and used to work with react-ipywdgets 0.11.
Here's an example that shows both the working case (with ipywidgeds) and the non-working case (reacton).
import matplotlib.pyplot as plt
import reacton as react
import reacton.ipywidgets as w
import ipywidgets as widgets
#%matplotlib inline
%matplotlib ipympl
def plot(outputWidget):
with plt.ioff():
fig, ax = plt.subplots()
fruits = ['apple', 'blueberry', 'cherry', 'orange']
counts = [40, 100, 30, 55]
bar_labels = ['red', 'blue', '_red', 'orange']
bar_colors = ['tab:red', 'tab:blue', 'tab:red', 'tab:orange']
ax.bar(fruits, counts, label=bar_labels, color=bar_colors)
ax.set_ylabel('fruit supply')
ax.set_title('Fruit supply by kind and color')
ax.legend(title='Fruit color')
with outputWidget:
print("Displaying")
plt.show()
# Reacton Test Case
@react.component()
def Test():
render, setRender = react.use_state(False)
def displayReacton():
if render == False:
return
outputWidget = react.get_widget(output)
plot(outputWidget)
with w.VBox() as vbox:
w.Button(description="Render", on_click=lambda: setRender(True))
output = w.Output()
react.use_effect(displayReacton, [render])
return vbox
Test()
# Ipywidgets test case
outputWidget = widgets.Output()
button = widgets.Button(description="Render")
def displayIpywidgets(_):
plot(outputWidget)
button.on_click(displayIpywidgets)
widgets.VBox([
button,
outputWidget
])
Software Versions:
python 3.10.9
matplotlib-base 3.7.1
ipympl 0.9.3
reacton 1.2.2
I was trying to execute the calculator demo
https://reacton.solara.dev/en/latest/_output/lab/
and from import reacton
I got the following error.
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
/tmp/xpython_42/3272763547.py in <cell line: 1>()
----> 1 import reacton
2 import reacton.ipywidgets as w
3
4
5 @reacton.component
ModuleNotFoundError: No module named 'reacton'
Reacton does appear in this jupyterlite config file (I think)
https://github.com/widgetti/reacton/blob/master/docs/xeus-python-environment.yml
We could try to support this, or give a better error message.
I am trying to implement this very simple example with reacton.ipvuetify.VuetifyTemplate. The implementation if templates in reacton is a method instead of a class and I am unsure as to how you pass methods to the function (in this example the vue_btn_click
).
import traitlets
import ipyvuetify
class TestWidget(ipyvuetify.VuetifyTemplate):
template = traitlets.Unicode('''
<template>
<v-btn @click="btn_click">
Button
</v-btn>
</template>
''').tag(sync=True)
def vue_btn_click(self, data):
print(1)
test = TestWidget()
test
I got following warning when running pytest:
reacton\core.py:362: DeprecationWarning: Widget.widgets is deprecated.
before = set(widgets.Widget.widgets)
reacton\core.py:1936: DeprecationWarning: Widget.widgets is deprecated.
orphan_widgets = set([widgets.Widget.widgets[k] for k in orphan_ids])
All occurrences of referencing Widget.widgets throw DeprecationWarning.
In ipywidgets(8.0.6), it states:
# Because this is a static attribute, it will be accessed when initializing this class. In that case, since a user
# did not explicitly try to use this attribute, we do not want to throw a deprecation warning.
# So we check if the thing calling this static property is one of the known initialization functions in traitlets.
Anyway to avoid?
P.S.
What's the difference between render()
and render_fixed()
? I do see render add another container around elements. Any explanations about when to choose which?
Thanks a lot.
Like many other people, I want to write good-looking apps with React-style (Model-View-Update, The Elm Architecture) in 100% Python.
Congratulations on implementing Hooks in Python; I believe you’re the first person to do that.
https://maartenbreddels.medium.com/advance-your-ipywidget-app-development-with-reacton-6734a5607d69
Once a common library for rendering/reconciliation exists, the same procedure can be repeated for Qt
It sure would be nice if we could write PySide6 apps with Reacton. Here’s an issue for talking about that.
This project has already saved me a bunch of time building complex Jupyter UIs. I just wanted to say thanks!
Hi I am trying to create a DataTable with single row select as well as dropdown (Select) menus in one of the columns. My current issue is that changing the value of one dropdown changes the value of every dropdown (probably because they share a v_model). In the end I need to be able to pull the values of the dropdown out of the table.
import reacton.ipyvuetify as v
data = [
{"name": "John", "age": 15, "city": "New York"},
{"name": "Jane", "age": 23, "city": "London"},
{"name": "Peter", "age": 32, "city": "Paris"},
{"name": "Kate", "age": 21, "city": "Berlin"},
{"name": "Mary", "age": 29, "city": "Mumbai"},
]
v.DataTable(
headers=[
{"text": "Name", "value": "name"},
{"text": "Age", "value": "age"},
{"text": "City", "value": "city"},
{"text": "Selector", "value": "selector"},
],
items=data,
class_="elevation-1",
single_select=True,
show_select=True,
v_slots=[
{
"name": "item.selector",
"variable": "item",
"children": v.Select(
items=["Yes", "No"],
v_model="item.selector",
),
}
],
)
In some cases when changing the options of a SelectionRangeSlider, the options list is not updated. For this to occur, you must have both continuous_update set to True, and an index specified. I've seen similar behavior in react_ipywidgets 0.11 (ipywidgets 0.7) with the SelectMultiple widget. I believe it's some kind of race condition in the SelectionRangeSlider's JavaScript view, but I haven't been able to reproduce this issue with ipywidgets directly.
Test Case:
Click Update, and then try and adjust the slider range. The range remains 1-3, instead of updating to 1-10.
Set continuous_update to False, or remove the index, and the problem does not occur.
import reacton as react
import reacton.ipywidgets as w
@react.component()
def Test():
options, setOptions = react.use_state(['1', '2', '3'])
firstIndex = 0
lastIndex = max(0, len(options)-1)
def onClicked():
setOptions([str(i) for i in range(1, 11)])
print("what")
print(options)
with w.VBox() as vbox:
w.Button(description="Update", on_click=onClicked)
w.SelectionRangeSlider(
description="Options",
options=options,
index=(firstIndex, lastIndex),
continuous_update=True
)
return vbox
Test()
Software Versions:
python 3.10.9
ipywidgets 8.0.4
reacton 1.2.2
The example from https://github.com/widgetti/reacton#markdown-component-example does not work with myst_parser>=0.18
since the to_html
function has been removed from the python api (see https://github.com/executablebooks/MyST-Parser/blob/28725fceb8e1e117cb247b06a267f82c501ce527/CHANGELOG.md#breaking-changes)
One solution:
@reacton.component
def Markdown(md: str):
from markdown_it.renderer import RendererHTML
from myst_parser.config.main import MdParserConfig
from myst_parser.parsers.mdit import create_md_parser
md_parser = create_md_parser(MdParserConfig(), RendererHTML)
html = md_parser.render(md)
return w.HTML(value=html)
BR, Alex
The DatetimePicker ist not currently covered by https://github.com/widgetti/reacton/blob/master/reacton/ipywidgets.py
First congrats on this work, really amazing to see this. Have you looked at integrating ipydatagrid? What would that take?
Hello!
I'm not sure if this issue should be opened in reacton or ipyvuetify repository.
When I try to use Vuetify transition groups, I get an error in browser console saying:
[Vue warn]: <transition-group> children must be keyed: <v-dialog>
However I used a key method on reacton elements inside the group:
with rv.FadeTransition(group=True):
if figures.raw_measurements:
MplFigure(*figures.raw_measurements).key("raw_measurements")
if figures.field_accuracy:
MplFigure(*figures.field_accuracy).key("field_accuracy")
if figures.calibration_moment:
MplFigure(*figures.calibration_moment).key("calibration_moment")
(MplFigure here is a @reacton.component
with rv.Dialog
as the root element.)
Should the same keys be used by Vue in browser?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.