diff --git a/docker/data/storage/notebooks/DEMO.md b/docker/data/storage/notebooks/DEMO.md index 9db7ef902..db0c59b92 100644 --- a/docker/data/storage/notebooks/DEMO.md +++ b/docker/data/storage/notebooks/DEMO.md @@ -226,7 +226,7 @@ def hist_demo(source, column): x=column, nbins=bin_count, ), - {bin_count, hist_range, source, column}, + [bin_count, hist_range, source, column], ) return [ @@ -271,7 +271,7 @@ def order_table(): ), [], ) - t = ui.use_memo(lambda: blink_to_append_only(blink_table), {blink_table}) + t = ui.use_memo(lambda: blink_to_append_only(blink_table), [blink_table]) def submit_order(order_sym, order_size, side): publisher.add( @@ -437,18 +437,18 @@ def multiwave(): f"y_tan={amplitude}*Math.tan({frequency}*x+{phase})", ] ), - {amplitude, frequency, phase}, + [amplitude, frequency, phase], ) p_sin = use_memo( - lambda: Figure().plot_xy(series_name="Sine", t=t, x="x", y="y_sin").show(), {t} + lambda: Figure().plot_xy(series_name="Sine", t=t, x="x", y="y_sin").show(), [t] ) p_cos = use_memo( lambda: Figure().plot_xy(series_name="Cosine", t=t, x="x", y="y_cos").show(), - {t}, + [t], ) p_tan = use_memo( lambda: Figure().plot_xy(series_name="Tangent", t=t, x="x", y="y_tan").show(), - {t}, + [t], ) return ui.column( diff --git a/plugins/ui/src/deephaven/ui/hooks/use_callback.py b/plugins/ui/src/deephaven/ui/hooks/use_callback.py index 7496e1205..a5fedf436 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_callback.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_callback.py @@ -3,9 +3,10 @@ from typing import Callable, Any, Sequence from .use_ref import use_ref, Ref +from ..types import Dependencies -def use_callback(func: Callable, dependencies: set[Any] | Sequence[Any]) -> Callable: +def use_callback(func: Callable, dependencies: Dependencies) -> Callable: """ Create a stable handle for a callback function. The callback will only be recreated if the dependencies change. diff --git a/plugins/ui/src/deephaven/ui/hooks/use_effect.py b/plugins/ui/src/deephaven/ui/hooks/use_effect.py index 713c3324e..e11e05fa6 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_effect.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_effect.py @@ -4,9 +4,10 @@ from .use_ref import use_ref, Ref from deephaven.liveness_scope import LivenessScope from .._internal import get_context +from ..types import Dependencies -def use_effect(func: Callable[[], Any], dependencies: set[Any] | Sequence[Any]): +def use_effect(func: Callable[[], Any], dependencies: Dependencies): """ Call a function when the dependencies change. Optionally return a cleanup function to be called when dependencies change again or component is unmounted. diff --git a/plugins/ui/src/deephaven/ui/hooks/use_execution_context.py b/plugins/ui/src/deephaven/ui/hooks/use_execution_context.py index 93f1c9045..2cfc808a1 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_execution_context.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_execution_context.py @@ -35,6 +35,6 @@ def use_execution_context( Returns: A callable that will take any callable and invoke it within the current exec context """ - exec_ctx = use_memo(lambda: exec_ctx if exec_ctx else get_exec_ctx(), {exec_ctx}) - exec_fn = use_memo(lambda: partial(func_with_ctx, exec_ctx), {exec_ctx}) + exec_ctx = use_memo(lambda: exec_ctx if exec_ctx else get_exec_ctx(), [exec_ctx]) + exec_fn = use_memo(lambda: partial(func_with_ctx, exec_ctx), [exec_ctx]) return exec_fn diff --git a/plugins/ui/src/deephaven/ui/hooks/use_liveness_scope.py b/plugins/ui/src/deephaven/ui/hooks/use_liveness_scope.py index 1cce94bdd..87acd3958 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_liveness_scope.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_liveness_scope.py @@ -5,9 +5,10 @@ from .use_ref import use_ref, Ref from typing import Callable from deephaven.liveness_scope import LivenessScope +from ..types import Dependencies -def use_liveness_scope(func: Callable, dependencies: set) -> Callable: +def use_liveness_scope(func: Callable, dependencies: Dependencies) -> Callable: """ Wraps a Callable in a liveness scope, and ensures that when invoked, if that callable causes the component to render, the scope will be retained by that render pass. It is diff --git a/plugins/ui/src/deephaven/ui/hooks/use_memo.py b/plugins/ui/src/deephaven/ui/hooks/use_memo.py index 39b384449..406714539 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_memo.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_memo.py @@ -4,11 +4,12 @@ from .._internal import ValueWithLiveness, get_context from typing import Any, Callable, TypeVar, cast, Union, Sequence from deephaven.liveness_scope import LivenessScope +from ..types import Dependencies T = TypeVar("T") -def use_memo(func: Callable[[], T], dependencies: set[Any] | Sequence[Any]) -> T: +def use_memo(func: Callable[[], T], dependencies: Dependencies) -> T: """ Memoize the result of a function call. The function will only be called again if the dependencies change. @@ -19,6 +20,11 @@ def use_memo(func: Callable[[], T], dependencies: set[Any] | Sequence[Any]) -> T Returns: The memoized result of the function call. """ + if not isinstance(dependencies, (list, tuple)): + raise TypeError( + f"dependencies must be a list or tuple, got {type(dependencies)}" + ) + deps_ref: Ref[set[Any] | Sequence[Any] | None] = use_ref(None) value_ref: Ref[ValueWithLiveness[T | None]] = use_ref( ValueWithLiveness(value=cast(Union[T, None], None), liveness_scope=None) diff --git a/plugins/ui/src/deephaven/ui/hooks/use_table_data.py b/plugins/ui/src/deephaven/ui/hooks/use_table_data.py index 985002e3f..3f2714612 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_table_data.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_table_data.py @@ -143,7 +143,7 @@ def use_table_data( table_updated = lambda: _set_new_data(table, sentinel, set_data, set_is_sentinel) ui.use_table_listener( - table, partial(_on_update, ctx, table_updated, executor_name), set() + table, partial(_on_update, ctx, table_updated, executor_name), [] ) return transform(data, is_sentinel) diff --git a/plugins/ui/src/deephaven/ui/hooks/use_table_listener.py b/plugins/ui/src/deephaven/ui/hooks/use_table_listener.py index da19b87a4..a7eef4ad5 100644 --- a/plugins/ui/src/deephaven/ui/hooks/use_table_listener.py +++ b/plugins/ui/src/deephaven/ui/hooks/use_table_listener.py @@ -8,7 +8,7 @@ from deephaven.execution_context import get_exec_ctx, ExecutionContext from .use_effect import use_effect -from ..types import LockType +from ..types import LockType, Dependencies def listener_with_ctx( @@ -67,7 +67,7 @@ def wrap_listener( def use_table_listener( table: Table, listener: Callable[[TableUpdate, bool], None] | TableListener, - dependencies: set[Any] | Sequence[Any], + dependencies: Dependencies, description: str | None = None, do_replay: bool = False, replay_lock: LockType = "shared", @@ -109,5 +109,5 @@ def start_listener() -> Callable[[], None]: use_effect( start_listener, - {table, listener, description, do_replay, replay_lock} | set(dependencies), + [table, listener, description, do_replay, replay_lock] + list(dependencies), ) diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index ac84792bf..7962f207d 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -105,3 +105,4 @@ class RowDataValue(CellData): # Stringable is a type that is naturally convertible to a string Stringable = Union[str, int, float, bool] Key = Stringable +Dependencies = Union[Tuple[Any], List[Any]] diff --git a/plugins/ui/test/deephaven/ui/test_hooks.py b/plugins/ui/test/deephaven/ui/test_hooks.py index 1a9fcfb8c..dce874bb1 100644 --- a/plugins/ui/test/deephaven/ui/test_hooks.py +++ b/plugins/ui/test/deephaven/ui/test_hooks.py @@ -108,7 +108,7 @@ def test_memo(self): from deephaven.ui.hooks import use_memo def _test_memo(fn=lambda: "foo", a=1, b=2): - return use_memo(fn, {a, b}) + return use_memo(fn, [a, b]) # Initial render render_result = render_hook(_test_memo) @@ -140,6 +140,12 @@ def _test_memo(fn=lambda: "foo", a=1, b=2): self.assertEqual(result, "biz") self.assertEqual(mock.call_count, 1) + def _test_memo_set(fn=lambda: "foo"): + return use_memo(fn, {}) + + # passing in a non-list/tuple for dependencies should raise a TypeError + self.assertRaises(TypeError, render_hook, _test_memo_set) + def verify_table_updated(self, table_writer, table, update): from deephaven.ui.hooks import use_table_listener from deephaven.table_listener import TableUpdate @@ -541,7 +547,7 @@ def start_thread(): thread.start() thread.join() - use_memo(start_thread, set()) + use_memo(start_thread, []) render_hook(_test_execution_context) @@ -564,7 +570,7 @@ def start_thread(): thread = threading.Thread(target=thread_func) thread.start() - use_memo(start_thread, set()) + use_memo(start_thread, []) render_hook(_test_execution_context) @@ -595,7 +601,7 @@ def _test_reused_tables(): a, set_a = use_state(lambda: table.where("X=1")) # When "a" changes, recompute table - don't return or otherwise track this table w.r.t. liveness - replace_a = use_liveness_scope(lambda: set_a(table.where("X=2")), set()) + replace_a = use_liveness_scope(lambda: set_a(table.where("X=2")), []) return a.size @@ -664,7 +670,7 @@ def helper(): now = dh_now() return table.where("Timestamp > now").last_by(by=["X"]) - local_rows = use_memo(helper, {a}) + local_rows = use_memo(helper, [a]) return local_rows.size