reacton 1.8.1__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.1 → reacton-1.8.3}/PKG-INFO +1 -1
- reacton-1.8.3/reacton/_version.py +1 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/core.py +84 -45
- {reacton-1.8.1 → reacton-1.8.3}/reacton/core_test.py +39 -12
- reacton-1.8.1/reacton/_version.py +0 -1
- {reacton-1.8.1 → reacton-1.8.3}/.gitignore +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/LICENSE +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/pyproject.toml +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/react_ipywidgets/__init__.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/react_ipywidgets/core.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/__init__.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/bqplot.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/deprecated/__init__.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/deprecated/ipyvuetify.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/find.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/find_test.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/generate.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/generate_test.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/ipycanvas.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/ipyvue.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/ipyvuetify.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/ipywidgets.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/logging.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/patch.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/patch_display.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/py.typed +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/rx.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/test.vue +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/test_rx.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/utils.py +0 -0
- {reacton-1.8.1 → reacton-1.8.3}/reacton/utils_test.py +0 -0
- {reacton-1.8.1 → 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):
|
|
@@ -1258,16 +1286,8 @@ class _RenderContext:
|
|
|
1258
1286
|
# I am not sure exactly why, but this will at least make
|
|
1259
1287
|
# it easier for Python to garbage collect, since it will
|
|
1260
1288
|
# avoid circular references
|
|
1261
|
-
context_ref = weakref.ref(context)
|
|
1262
|
-
self_ref = weakref.ref(self)
|
|
1263
|
-
del context
|
|
1264
|
-
del self
|
|
1265
1289
|
|
|
1266
1290
|
def set_(value):
|
|
1267
|
-
context = context_ref()
|
|
1268
|
-
assert context is not None
|
|
1269
|
-
self = self_ref()
|
|
1270
|
-
assert self is not None
|
|
1271
1291
|
if callable(value):
|
|
1272
1292
|
value = value(context.state[key])
|
|
1273
1293
|
logger.info("Set state = %r for key %r (previous value was %r) (%r)", value, key, context.state[key], id(self.context))
|
|
@@ -1344,12 +1364,12 @@ class _RenderContext:
|
|
|
1344
1364
|
if len(self.context.effects) <= self.context.effect_index:
|
|
1345
1365
|
self.context.effect_index += 1
|
|
1346
1366
|
self.context.effects.append(Effect(effect, dependencies))
|
|
1347
|
-
logger.
|
|
1367
|
+
logger.debug("Initial effect = %r for index %r (%r)", effect, self.context.effect_index - 1, dependencies)
|
|
1348
1368
|
else:
|
|
1349
1369
|
previous_effect = self.context.effects[self.context.effect_index]
|
|
1350
1370
|
# we always set it, even replacing it when we didn't execute it
|
|
1351
1371
|
# in the consolidation phase we decide what to do (e.g. skip it)
|
|
1352
|
-
logger.
|
|
1372
|
+
logger.debug("Setting next effect = %r for index %r (%r)", effect, self.context.effect_index, dependencies)
|
|
1353
1373
|
if previous_effect.executed:
|
|
1354
1374
|
# line up...
|
|
1355
1375
|
previous_effect.next = Effect(effect, dependencies)
|
|
@@ -1494,7 +1514,11 @@ class _RenderContext:
|
|
|
1494
1514
|
"make sure your root component always returns the same component type"
|
|
1495
1515
|
)
|
|
1496
1516
|
if container:
|
|
1497
|
-
|
|
1517
|
+
if widget is None:
|
|
1518
|
+
# Exception occurred, and we cannot render the widget
|
|
1519
|
+
container.children = []
|
|
1520
|
+
else:
|
|
1521
|
+
container.children = [widget]
|
|
1498
1522
|
|
|
1499
1523
|
if self.context_root.exceptions_children or self.context_root.exceptions_self:
|
|
1500
1524
|
# an exception bubbled up during reconsolidate
|
|
@@ -1852,7 +1876,7 @@ class _RenderContext:
|
|
|
1852
1876
|
# if we have a next, it means that effect itself is executed
|
|
1853
1877
|
# TODO: custom equals
|
|
1854
1878
|
if effect.next.dependencies is not None and utils.equals(effect.dependencies, effect.next.dependencies):
|
|
1855
|
-
logger.
|
|
1879
|
+
logger.debug("No need to add effect, dependencies are the same (%r %r)", effect.callable, effect.dependencies)
|
|
1856
1880
|
# not needed, just remove the reference
|
|
1857
1881
|
effect.next = None
|
|
1858
1882
|
else:
|
|
@@ -1936,34 +1960,48 @@ class _RenderContext:
|
|
|
1936
1960
|
raise RuntimeError(f"Element ({el}) was already in self._shared_widgets")
|
|
1937
1961
|
else:
|
|
1938
1962
|
logger.info("Creating new widget: %r %r", el, key)
|
|
1939
|
-
widget
|
|
1940
|
-
|
|
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
|
|
1941
1971
|
if el.is_shared:
|
|
1942
1972
|
self._shared_widgets[el] = widget
|
|
1943
1973
|
else:
|
|
1944
1974
|
context.widgets[key] = widget
|
|
1945
1975
|
elif el_prev is not None and el_prev.component == el.component:
|
|
1946
|
-
logger.
|
|
1976
|
+
logger.debug("Updating widget: %r → %r %r", el_prev, el, key)
|
|
1947
1977
|
assert el_prev is not None
|
|
1948
1978
|
# TODO: remove event listeners while doing so
|
|
1949
1979
|
# assign to _widgets[el] first, before errors can occur
|
|
1950
1980
|
kwargs = reconsolidate_children()
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
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
|
|
1957
1988
|
if el.is_shared:
|
|
1958
1989
|
self._shared_widgets[el] = widget_previous
|
|
1959
1990
|
else:
|
|
1960
1991
|
context.widgets[key] = widget_previous
|
|
1961
1992
|
else:
|
|
1962
1993
|
assert el_prev is not None, "widget_previous is not None, but el_prev is"
|
|
1963
|
-
logger.
|
|
1994
|
+
logger.debug("Replacing widget: %r → %r %r", el_prev, el, key)
|
|
1964
1995
|
self._remove_element(el_prev, key, parent_key=parent_key)
|
|
1965
1996
|
kwargs = reconsolidate_children()
|
|
1966
|
-
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
|
|
1967
2005
|
if el.is_shared:
|
|
1968
2006
|
self._shared_widgets[el] = widget
|
|
1969
2007
|
else:
|
|
@@ -2050,7 +2088,7 @@ class _RenderContext:
|
|
|
2050
2088
|
assert key is not None
|
|
2051
2089
|
assert self.context is not None
|
|
2052
2090
|
context = self.context
|
|
2053
|
-
logger.
|
|
2091
|
+
logger.debug("Remove: (%s, %s) %r", parent_key, key, el)
|
|
2054
2092
|
|
|
2055
2093
|
if el.is_shared:
|
|
2056
2094
|
if el not in self._shared_elements:
|
|
@@ -2098,17 +2136,18 @@ class _RenderContext:
|
|
|
2098
2136
|
widget = self._shared_widgets[el]
|
|
2099
2137
|
else:
|
|
2100
2138
|
widget = context.widgets[key]
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
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)
|
|
2112
2151
|
if el.is_shared:
|
|
2113
2152
|
del self._shared_widgets[el]
|
|
2114
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.1"
|
|
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
|