reacton 1.8.2__tar.gz → 1.8.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reacton-1.8.2 → reacton-1.8.3}/PKG-INFO +1 -1
- reacton-1.8.3/reacton/_version.py +1 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/core.py +84 -37
- {reacton-1.8.2 → reacton-1.8.3}/reacton/core_test.py +39 -12
- reacton-1.8.2/reacton/_version.py +0 -1
- {reacton-1.8.2 → reacton-1.8.3}/.gitignore +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/LICENSE +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/pyproject.toml +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/react_ipywidgets/__init__.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/react_ipywidgets/core.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/__init__.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/bqplot.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/deprecated/__init__.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/deprecated/ipyvuetify.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/find.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/find_test.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/generate.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/generate_test.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/ipycanvas.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/ipyvue.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/ipyvuetify.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/ipywidgets.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/logging.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/patch.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/patch_display.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/py.typed +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/rx.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/test.vue +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/test_rx.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/utils.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/utils_test.py +0 -0
- {reacton-1.8.2 → reacton-1.8.3}/reacton/work.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.8.3"
|
|
@@ -770,14 +770,42 @@ def get_widget(el: Element):
|
|
|
770
770
|
|
|
771
771
|
|
|
772
772
|
def use_state(initial: T, key: str = None, eq: Callable[[Any, Any], bool] = None) -> Tuple[T, Callable[[Union[T, Callable[[T], T]]], None]]:
|
|
773
|
-
"""Returns a (value, setter) tuple that is used to manage state in a component.
|
|
773
|
+
"""Returns a `(value, setter)` tuple that is used to manage state in a component.
|
|
774
774
|
|
|
775
775
|
This function can only be called from a component function.
|
|
776
776
|
|
|
777
|
-
The value returns the current state (which equals initial at the first render call).
|
|
778
|
-
|
|
777
|
+
The value returns the current state (which equals `initial` at the first render call). Or the value that was last set using the setter.
|
|
778
|
+
|
|
779
|
+
Note that the setter function can be used in two ways.
|
|
780
|
+
|
|
781
|
+
Directly setting the value:
|
|
782
|
+
|
|
783
|
+
```python
|
|
784
|
+
@reacton.component
|
|
785
|
+
def ButtonClick():
|
|
786
|
+
clicks, set_clicks = reacton.use_state(0)
|
|
787
|
+
def my_click_handler():
|
|
788
|
+
set_clicks(clicks+1)
|
|
789
|
+
button = w.Button(description=f"Clicked {clicks} times",
|
|
790
|
+
on_click=my_click_handler)
|
|
791
|
+
return button
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
Updating the value based on the previous/current value.
|
|
795
|
+
|
|
796
|
+
```python
|
|
797
|
+
@reacton.component
|
|
798
|
+
def ButtonClick():
|
|
799
|
+
clicks, set_clicks = reacton.use_state(0)
|
|
800
|
+
def my_click_handler():
|
|
801
|
+
set_clicks(lambda prev: prev+1)
|
|
802
|
+
button = w.Button(description=f"Clicked {clicks} times",
|
|
803
|
+
on_click=my_click_handler)
|
|
804
|
+
return button
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
The last one avoid issues with stale data, which means you have a reference to the value of an old render pass (not present in this simple example).
|
|
779
808
|
|
|
780
|
-
Subsequent
|
|
781
809
|
"""
|
|
782
810
|
rc = _get_render_context()
|
|
783
811
|
return rc.use_state(initial, key, eq)
|
|
@@ -839,7 +867,7 @@ def use_reducer(reduce: Callable[[T, U], T], initial_state: T) -> Tuple[T, Calla
|
|
|
839
867
|
return state, dispatch
|
|
840
868
|
|
|
841
869
|
|
|
842
|
-
def use_memo(f, dependencies=None, debug_name: str = None):
|
|
870
|
+
def use_memo(f: Callable[[], T], dependencies=None, debug_name: str = None) -> T:
|
|
843
871
|
if debug_name is None:
|
|
844
872
|
debug_name = f.__name__
|
|
845
873
|
rc = _get_render_context()
|
|
@@ -1217,15 +1245,15 @@ class _RenderContext:
|
|
|
1217
1245
|
memo = (value, dependencies)
|
|
1218
1246
|
self.context.memo.append(memo)
|
|
1219
1247
|
self.context.memo_index += 1
|
|
1220
|
-
logger.
|
|
1248
|
+
logger.debug("Initial memo = %r for index %r (debug-name: %r)", memo, self.context.memo_index - 1, name)
|
|
1221
1249
|
return value
|
|
1222
1250
|
else:
|
|
1223
1251
|
memo = self.context.memo[self.context.memo_index]
|
|
1224
1252
|
value, dependencies_previous = memo
|
|
1225
1253
|
if utils.equals(dependencies_previous, dependencies):
|
|
1226
|
-
logger.
|
|
1254
|
+
logger.debug("Got memo hit = %r for index %r (debug-name: %r)", memo, self.context.memo_index, name)
|
|
1227
1255
|
else:
|
|
1228
|
-
logger.
|
|
1256
|
+
logger.debug("Replace memo with = %r for index %r (debug-name: %r)", memo, self.context.memo_index, name)
|
|
1229
1257
|
value = f()
|
|
1230
1258
|
memo = (value, dependencies)
|
|
1231
1259
|
self.context.memo[self.context.memo_index] = memo
|
|
@@ -1243,11 +1271,11 @@ class _RenderContext:
|
|
|
1243
1271
|
self.context.state_metadata[key] = len(initial)
|
|
1244
1272
|
elif utils.isinstance_lazy(initial, "pandas.DataFrame"):
|
|
1245
1273
|
self.context.state_metadata[key] = utils.dataframe_fingerprint(initial)
|
|
1246
|
-
logger.
|
|
1274
|
+
logger.debug("Initial state = %r for key %r (%r)", initial, key, id(self.context))
|
|
1247
1275
|
return initial, self.make_setter(key, self.context, eq)
|
|
1248
1276
|
else:
|
|
1249
1277
|
state = self.context.state[key]
|
|
1250
|
-
logger.
|
|
1278
|
+
logger.debug("Got state = %r for key %r (%r)", state, key, id(self.context))
|
|
1251
1279
|
return state, self.make_setter(key, self.context, eq)
|
|
1252
1280
|
|
|
1253
1281
|
def make_setter(self, key, context: ComponentContext, eq: Callable[[Any, Any], bool] = None):
|
|
@@ -1336,12 +1364,12 @@ class _RenderContext:
|
|
|
1336
1364
|
if len(self.context.effects) <= self.context.effect_index:
|
|
1337
1365
|
self.context.effect_index += 1
|
|
1338
1366
|
self.context.effects.append(Effect(effect, dependencies))
|
|
1339
|
-
logger.
|
|
1367
|
+
logger.debug("Initial effect = %r for index %r (%r)", effect, self.context.effect_index - 1, dependencies)
|
|
1340
1368
|
else:
|
|
1341
1369
|
previous_effect = self.context.effects[self.context.effect_index]
|
|
1342
1370
|
# we always set it, even replacing it when we didn't execute it
|
|
1343
1371
|
# in the consolidation phase we decide what to do (e.g. skip it)
|
|
1344
|
-
logger.
|
|
1372
|
+
logger.debug("Setting next effect = %r for index %r (%r)", effect, self.context.effect_index, dependencies)
|
|
1345
1373
|
if previous_effect.executed:
|
|
1346
1374
|
# line up...
|
|
1347
1375
|
previous_effect.next = Effect(effect, dependencies)
|
|
@@ -1486,7 +1514,11 @@ class _RenderContext:
|
|
|
1486
1514
|
"make sure your root component always returns the same component type"
|
|
1487
1515
|
)
|
|
1488
1516
|
if container:
|
|
1489
|
-
|
|
1517
|
+
if widget is None:
|
|
1518
|
+
# Exception occurred, and we cannot render the widget
|
|
1519
|
+
container.children = []
|
|
1520
|
+
else:
|
|
1521
|
+
container.children = [widget]
|
|
1490
1522
|
|
|
1491
1523
|
if self.context_root.exceptions_children or self.context_root.exceptions_self:
|
|
1492
1524
|
# an exception bubbled up during reconsolidate
|
|
@@ -1844,7 +1876,7 @@ class _RenderContext:
|
|
|
1844
1876
|
# if we have a next, it means that effect itself is executed
|
|
1845
1877
|
# TODO: custom equals
|
|
1846
1878
|
if effect.next.dependencies is not None and utils.equals(effect.dependencies, effect.next.dependencies):
|
|
1847
|
-
logger.
|
|
1879
|
+
logger.debug("No need to add effect, dependencies are the same (%r %r)", effect.callable, effect.dependencies)
|
|
1848
1880
|
# not needed, just remove the reference
|
|
1849
1881
|
effect.next = None
|
|
1850
1882
|
else:
|
|
@@ -1928,34 +1960,48 @@ class _RenderContext:
|
|
|
1928
1960
|
raise RuntimeError(f"Element ({el}) was already in self._shared_widgets")
|
|
1929
1961
|
else:
|
|
1930
1962
|
logger.info("Creating new widget: %r %r", el, key)
|
|
1931
|
-
widget
|
|
1932
|
-
|
|
1963
|
+
widget = None
|
|
1964
|
+
if not context.exceptions_children:
|
|
1965
|
+
try:
|
|
1966
|
+
widget, orphan_ids = el._create_widget(kwargs)
|
|
1967
|
+
except BaseException as e:
|
|
1968
|
+
context.exceptions_self.append(e)
|
|
1969
|
+
self._rerender_needed_reasons.append(RerenderReason(reason="Exception ocurred during reconciliation (creating widget)"))
|
|
1970
|
+
self._rerender_needed = True
|
|
1933
1971
|
if el.is_shared:
|
|
1934
1972
|
self._shared_widgets[el] = widget
|
|
1935
1973
|
else:
|
|
1936
1974
|
context.widgets[key] = widget
|
|
1937
1975
|
elif el_prev is not None and el_prev.component == el.component:
|
|
1938
|
-
logger.
|
|
1976
|
+
logger.debug("Updating widget: %r → %r %r", el_prev, el, key)
|
|
1939
1977
|
assert el_prev is not None
|
|
1940
1978
|
# TODO: remove event listeners while doing so
|
|
1941
1979
|
# assign to _widgets[el] first, before errors can occur
|
|
1942
1980
|
kwargs = reconsolidate_children()
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1981
|
+
if not context.exceptions_children:
|
|
1982
|
+
try:
|
|
1983
|
+
el._update_widget(widget_previous, el_prev, kwargs)
|
|
1984
|
+
except BaseException as e:
|
|
1985
|
+
context.exceptions_self.append(e)
|
|
1986
|
+
self._rerender_needed_reasons.append(RerenderReason(reason="Exception ocurred during reconciliation (updating widget)"))
|
|
1987
|
+
self._rerender_needed = True
|
|
1949
1988
|
if el.is_shared:
|
|
1950
1989
|
self._shared_widgets[el] = widget_previous
|
|
1951
1990
|
else:
|
|
1952
1991
|
context.widgets[key] = widget_previous
|
|
1953
1992
|
else:
|
|
1954
1993
|
assert el_prev is not None, "widget_previous is not None, but el_prev is"
|
|
1955
|
-
logger.
|
|
1994
|
+
logger.debug("Replacing widget: %r → %r %r", el_prev, el, key)
|
|
1956
1995
|
self._remove_element(el_prev, key, parent_key=parent_key)
|
|
1957
1996
|
kwargs = reconsolidate_children()
|
|
1958
|
-
widget
|
|
1997
|
+
widget = None
|
|
1998
|
+
if not context.exceptions_children:
|
|
1999
|
+
try:
|
|
2000
|
+
widget, orphan_ids = el._create_widget(kwargs)
|
|
2001
|
+
except BaseException as e:
|
|
2002
|
+
context.exceptions_self.append(e)
|
|
2003
|
+
self._rerender_needed_reasons.append(RerenderReason(reason="Exception ocurred during reconciliation (updating widget)"))
|
|
2004
|
+
self._rerender_needed = True
|
|
1959
2005
|
if el.is_shared:
|
|
1960
2006
|
self._shared_widgets[el] = widget
|
|
1961
2007
|
else:
|
|
@@ -2042,7 +2088,7 @@ class _RenderContext:
|
|
|
2042
2088
|
assert key is not None
|
|
2043
2089
|
assert self.context is not None
|
|
2044
2090
|
context = self.context
|
|
2045
|
-
logger.
|
|
2091
|
+
logger.debug("Remove: (%s, %s) %r", parent_key, key, el)
|
|
2046
2092
|
|
|
2047
2093
|
if el.is_shared:
|
|
2048
2094
|
if el not in self._shared_elements:
|
|
@@ -2090,17 +2136,18 @@ class _RenderContext:
|
|
|
2090
2136
|
widget = self._shared_widgets[el]
|
|
2091
2137
|
else:
|
|
2092
2138
|
widget = context.widgets[key]
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2139
|
+
if widget is not None:
|
|
2140
|
+
assert widget.comm is not None
|
|
2141
|
+
assert widget.model_id in _get_widgets_dict()
|
|
2142
|
+
|
|
2143
|
+
for orphan in self._orphans.get(widget.model_id, set()):
|
|
2144
|
+
orphan_widget = _get_widgets_dict().get(orphan)
|
|
2145
|
+
if orphan_widget:
|
|
2146
|
+
close_widget(orphan_widget)
|
|
2147
|
+
if widget.model_id in self._orphans:
|
|
2148
|
+
del self._orphans[widget.model_id]
|
|
2149
|
+
el._cleanup_callbacks(widget)
|
|
2150
|
+
el._close_widget(widget)
|
|
2104
2151
|
if el.is_shared:
|
|
2105
2152
|
del self._shared_widgets[el]
|
|
2106
2153
|
else:
|
|
@@ -1913,10 +1913,8 @@ def test_recover_exception_in_cleanup_child(Container1, Container2):
|
|
|
1913
1913
|
with pytest.raises(Exception):
|
|
1914
1914
|
rc.render(Container1(children=[Container2()]))
|
|
1915
1915
|
|
|
1916
|
-
|
|
1916
|
+
with pytest.raises(Exception, match=".*fail.*"):
|
|
1917
1917
|
rc.close()
|
|
1918
|
-
except Exception as e:
|
|
1919
|
-
assert e.args[0] == "fail"
|
|
1920
1918
|
|
|
1921
1919
|
|
|
1922
1920
|
def test_recover_exception_in_cleanup():
|
|
@@ -1957,25 +1955,46 @@ def test_recover_exception_in_cleanup():
|
|
|
1957
1955
|
rc.close()
|
|
1958
1956
|
|
|
1959
1957
|
|
|
1960
|
-
def test_recover_exception_in_widget_update():
|
|
1958
|
+
def test_recover_exception_in_widget_update(Container1, Container2):
|
|
1961
1959
|
set_fail = None
|
|
1962
1960
|
|
|
1961
|
+
@react.component
|
|
1962
|
+
def Child(value):
|
|
1963
|
+
with v.Container() as main:
|
|
1964
|
+
v.Btn(children=[value])
|
|
1965
|
+
return main
|
|
1966
|
+
|
|
1963
1967
|
@react.component
|
|
1964
1968
|
def Test():
|
|
1965
1969
|
nonlocal set_fail
|
|
1966
1970
|
fail, set_fail = react.use_state(False)
|
|
1967
|
-
value =
|
|
1971
|
+
value = "ok"
|
|
1968
1972
|
if fail:
|
|
1969
|
-
value =
|
|
1970
|
-
|
|
1973
|
+
value = 1 # type: ignore
|
|
1974
|
+
if not fail:
|
|
1975
|
+
with Container1() as main:
|
|
1976
|
+
v.Btn(children=[value])
|
|
1977
|
+
else:
|
|
1978
|
+
with Container2() as main:
|
|
1979
|
+
Child(value=value)
|
|
1980
|
+
return main
|
|
1971
1981
|
|
|
1972
|
-
# with pytest.raises(Exception):
|
|
1973
1982
|
box, rc = react.render(Test())
|
|
1974
|
-
assert rc.find(
|
|
1983
|
+
assert rc.find(ipyvuetify.Btn)
|
|
1975
1984
|
assert set_fail is not None
|
|
1976
1985
|
set_fail(True)
|
|
1977
1986
|
assert not rc._is_rendering
|
|
1978
|
-
assert not rc.find(
|
|
1987
|
+
assert not rc.find(ipyvuetify.Btn)
|
|
1988
|
+
rc.close()
|
|
1989
|
+
|
|
1990
|
+
box, rc = react.render(Test(), handle_error=False)
|
|
1991
|
+
assert rc.find(ipyvuetify.Btn)
|
|
1992
|
+
assert set_fail is not None
|
|
1993
|
+
with pytest.raises(Exception, match=".*Could not create widget.*"):
|
|
1994
|
+
set_fail(True)
|
|
1995
|
+
set_fail(True)
|
|
1996
|
+
assert not rc._is_rendering
|
|
1997
|
+
rc.render(w.HTML(value="recover"))
|
|
1979
1998
|
rc.close()
|
|
1980
1999
|
|
|
1981
2000
|
|
|
@@ -3103,10 +3122,18 @@ def test_event_multiple():
|
|
|
3103
3122
|
rc.close()
|
|
3104
3123
|
|
|
3105
3124
|
|
|
3106
|
-
|
|
3125
|
+
@reacton.component
|
|
3126
|
+
def UseMemoComponent():
|
|
3127
|
+
rc = core.get_render_context()
|
|
3128
|
+
reacton.use_memo(lambda: [rc], [])
|
|
3129
|
+
return w.Button()
|
|
3130
|
+
|
|
3131
|
+
|
|
3132
|
+
@pytest.mark.parametrize("component", [w.Button, ButtonComponentFunction, UseMemoComponent])
|
|
3133
|
+
def test_memory_leak(component):
|
|
3107
3134
|
# we make sure that there is not reference to the context
|
|
3108
3135
|
# after we close it
|
|
3109
|
-
box, rc = react.render(
|
|
3136
|
+
box, rc = react.render(component(), handle_error=False)
|
|
3110
3137
|
rc.close()
|
|
3111
3138
|
# useful for debugging
|
|
3112
3139
|
# import objgraph
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.8.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|