From 2bb5344b1bdba7d5750d9c7e110f52397351a2f0 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Mon, 27 Nov 2023 14:48:38 -0600 Subject: [PATCH 1/6] Add lowercase reactive.value, calc, effect --- shiny/reactive/_reactives.py | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/shiny/reactive/_reactives.py b/shiny/reactive/_reactives.py index 630a6d093..3e6e73819 100644 --- a/shiny/reactive/_reactives.py +++ b/shiny/reactive/_reactives.py @@ -2,7 +2,18 @@ from __future__ import annotations -__all__ = ("Value", "Calc", "Calc_", "CalcAsync_", "Effect", "Effect_", "event") +__all__ = ( + "value", + "Value", + "calc", + "Calc", + "Calc_", + "CalcAsync_", + "effect", + "Effect", + "Effect_", + "event", +) import functools import traceback @@ -35,7 +46,7 @@ # Value # ============================================================================== @add_example() -class Value(Generic[T]): +class value(Generic[T]): """ Create a reactive value. @@ -204,6 +215,9 @@ def freeze(self) -> None: self._value = MISSING +# Alias for backward compatibility +Value = value + # ============================================================================== # Calc # ============================================================================== @@ -343,12 +357,12 @@ async def __call__(self) -> T: # pyright: ignore[reportIncompatibleMethodOverri @overload -def Calc(fn: CalcFunctionAsync[T]) -> CalcAsync_[T]: +def calc(fn: CalcFunctionAsync[T]) -> CalcAsync_[T]: ... @overload -def Calc(fn: CalcFunction[T]) -> Calc_[T]: +def calc(fn: CalcFunction[T]) -> Calc_[T]: ... @@ -373,14 +387,14 @@ def Calc(fn: CalcFunction[T]) -> Calc_[T]: # __call__ method) or CalcAsync object (which has an async __call__ method), and it # works out. @overload -def Calc( +def calc( *, session: "MISSING_TYPE | Session | None" = MISSING ) -> Callable[[CalcFunction[T]], Calc_[T]]: ... @add_example() -def Calc( +def calc( fn: Optional[CalcFunction[T] | CalcFunctionAsync[T]] = None, *, session: "MISSING_TYPE | Session | None" = MISSING, @@ -434,6 +448,9 @@ def create_calc(fn: CalcFunction[T] | CalcFunctionAsync[T]) -> Calc_[T]: return create_calc(fn) +# Alias for backward compatibility +Calc = calc + # ============================================================================== # Effect # ============================================================================== @@ -630,12 +647,12 @@ def _on_session_ended_cb(self) -> None: @overload -def Effect(fn: EffectFunction | EffectFunctionAsync) -> Effect_: +def effect(fn: EffectFunction | EffectFunctionAsync) -> Effect_: ... @overload -def Effect( +def effect( *, suspended: bool = False, priority: int = 0, @@ -645,7 +662,7 @@ def Effect( @add_example() -def Effect( +def effect( fn: Optional[EffectFunction | EffectFunctionAsync] = None, *, suspended: bool = False, @@ -706,6 +723,9 @@ def create_effect(fn: EffectFunction | EffectFunctionAsync) -> Effect_: return create_effect(fn) +Effect = effect + + # ============================================================================== # event decorator # ============================================================================== From 791ceb32a69bfec98bfb5733958c42017c6501f7 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Mon, 27 Nov 2023 15:08:27 -0600 Subject: [PATCH 2/6] Change internal usage to reactive.value, calc, effect --- shiny/reactive/__init__.py | 6 + shiny/reactive/_poll.py | 10 +- shiny/session/_session.py | 22 +- tests/deploys/apps/plotly_app/app.py | 4 +- tests/e2e/TODO/sidebar/app.py | 8 +- tests/e2e/TODO/update_popover/app.py | 6 +- tests/e2e/async/app.py | 2 +- .../0648-update-slider-datetime-value/app.py | 2 +- tests/e2e/bugs/0696-resolve-id/app.py | 8 +- tests/e2e/inputs/input_file/app.py | 2 +- tests/e2e/navs/navset_hidden/app.py | 2 +- tests/e2e/session/flush/app.py | 16 +- tests/e2e/ui/accordion/app.py | 14 +- tests/e2e/ui/popover/app.py | 6 +- tests/e2e/ui/tooltip/app.py | 6 +- tests/pytest/test_modules.py | 12 +- tests/pytest/test_poll.py | 10 +- tests/pytest/test_reactives.py | 238 +++++++++--------- tests/pytest/test_shinysession.py | 4 +- 19 files changed, 192 insertions(+), 186 deletions(-) diff --git a/shiny/reactive/__init__.py b/shiny/reactive/__init__.py index 1de7c5bac..b76543a5e 100644 --- a/shiny/reactive/__init__.py +++ b/shiny/reactive/__init__.py @@ -8,10 +8,13 @@ ) from ._poll import poll, file_reader from ._reactives import ( # noqa: F401 + value, Value, + calc, Calc, Calc_, # pyright: ignore[reportUnusedImport] CalcAsync_, # pyright: ignore[reportUnusedImport] + effect, Effect, Effect_, # pyright: ignore[reportUnusedImport] event, @@ -26,8 +29,11 @@ "on_flushed", "poll", "file_reader", + "value", "Value", + "calc", "Calc", + "effect", "Effect", "event", ) diff --git a/shiny/reactive/_poll.py b/shiny/reactive/_poll.py index 053e17d5f..e091a4786 100644 --- a/shiny/reactive/_poll.py +++ b/shiny/reactive/_poll.py @@ -96,10 +96,10 @@ def poll( """ with reactive.isolate(): - last_value: reactive.Value[Any] = reactive.Value(poll_func()) - last_error: reactive.Value[Optional[Exception]] = reactive.Value(None) + last_value: reactive.value[Any] = reactive.value(poll_func()) + last_error: reactive.value[Optional[Exception]] = reactive.value(None) - @reactive.Effect(priority=priority, session=session) + @reactive.effect(priority=priority, session=session) async def _(): try: if _utils.is_async_callable(poll_func): @@ -159,7 +159,7 @@ async def _(): def wrapper(fn: Callable[[], T]) -> Callable[[], T]: if _utils.is_async_callable(fn): - @reactive.Calc(session=session) + @reactive.calc(session=session) @functools.wraps(fn) async def result_async() -> T: # If an error occurred, raise it @@ -182,7 +182,7 @@ async def result_async() -> T: else: - @reactive.Calc(session=session) + @reactive.calc(session=session) @functools.wraps(fn) def result_sync() -> T: # If an error occurred, raise it diff --git a/shiny/session/_session.py b/shiny/session/_session.py index 359dd6ce4..b38972cb7 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -37,7 +37,7 @@ if TYPE_CHECKING: from .._app import App -from .. import _utils, render +from .. import _utils, reactive, render from .._connection import Connection, ConnectionClosed from .._docstring import add_example from .._fileupload import FileInfo, FileUploadManager @@ -46,7 +46,7 @@ from .._utils import wrap_async from ..http_staticfiles import FileResponse from ..input_handler import input_handlers -from ..reactive import Effect, Effect_, Value, flush, isolate +from ..reactive import Effect_, effect, flush, isolate, value from ..reactive._core import lock, on_flushed from ..render.transformer import OutputRenderer from ..types import SafeException, SilentCancelOutputException, SilentException @@ -345,7 +345,7 @@ def _is_hidden(self, name: str) -> bool: # id; make that explicit by wrapping it in ResolvedId, otherwise self.input # will throw an id validation error. hidden_value_obj = cast( - Value[bool], self.input[ResolvedId(f".clientdata_output_{name}_hidden")] + value[bool], self.input[ResolvedId(f".clientdata_output_{name}_hidden")] ) if not hidden_value_obj.is_set(): return True @@ -898,24 +898,24 @@ class Inputs: """ def __init__( - self, values: dict[str, Value[Any]], ns: Callable[[str], str] = Root + self, values: dict[str, value[Any]], ns: Callable[[str], str] = Root ) -> None: self._map = values self._ns = ns - def __setitem__(self, key: str, value: Value[Any]) -> None: - if not isinstance(value, Value): + def __setitem__(self, key: str, value: value[Any]) -> None: + if not isinstance(value, reactive.value): raise TypeError("`value` must be a reactive.Value object.") self._map[self._ns(key)] = value - def __getitem__(self, key: str) -> Value[Any]: + def __getitem__(self, key: str) -> value[Any]: key = self._ns(key) # Auto-populate key if accessed but not yet set. Needed to take reactive # dependencies on input values that haven't been received from client # yet. if key not in self._map: - self._map[key] = Value[Any](read_only=True) + self._map[key] = value[Any](read_only=True) return self._map[key] @@ -923,14 +923,14 @@ def __delitem__(self, key: str) -> None: del self._map[self._ns(key)] # Allow access of values as attributes. - def __setattr__(self, attr: str, value: Value[Any]) -> None: + def __setattr__(self, attr: str, value: value[Any]) -> None: if attr in ("_map", "_ns"): super().__setattr__(attr, value) return self.__setitem__(attr, value) - def __getattr__(self, attr: str) -> Value[Any]: + def __getattr__(self, attr: str) -> value[Any]: if attr in ("_map", "_ns"): return object.__getattribute__(self, attr) return self.__getitem__(attr) @@ -1008,7 +1008,7 @@ def set_renderer(renderer_fn: OutputRenderer[OT]) -> OutputRenderer[OT]: self._suspend_when_hidden[output_name] = suspend_when_hidden - @Effect( + @effect( suspended=suspend_when_hidden and self._session._is_hidden(output_name), priority=priority, ) diff --git a/tests/deploys/apps/plotly_app/app.py b/tests/deploys/apps/plotly_app/app.py index cf780ae39..ea775b1cf 100644 --- a/tests/deploys/apps/plotly_app/app.py +++ b/tests/deploys/apps/plotly_app/app.py @@ -61,7 +61,7 @@ def summary_data(): height="100%", ) - @reactive.Calc + @reactive.calc def filtered_df(): selected_idx = list(req(input.summary_data_selected_rows())) countries = summary_df["country"][selected_idx] @@ -116,7 +116,7 @@ def synchronize_size(output_id): def wrapper(func): input = session.get_current_session().input - @reactive.Effect + @reactive.effect def size_updater(): func( input[f".clientdata_output_{output_id}_width"](), diff --git a/tests/e2e/TODO/sidebar/app.py b/tests/e2e/TODO/sidebar/app.py index 8e71a34ce..387b1ca80 100644 --- a/tests/e2e/TODO/sidebar/app.py +++ b/tests/e2e/TODO/sidebar/app.py @@ -62,24 +62,24 @@ def server(input: Inputs, output: Outputs, session: Session) -> None: def ui_content(): return f"Hello, {input.adjective()} {input.animal()}!" - @reactive.Effect + @reactive.effect @reactive.event(input.open_all) def _(): ui.update_sidebar("sidebar_inner", show=True) ui.update_sidebar("sidebar_outer", show=True) - @reactive.Effect + @reactive.effect @reactive.event(input.close_all) def _(): ui.update_sidebar("sidebar_inner", show=False) ui.update_sidebar("sidebar_outer", show=False) - @reactive.Effect + @reactive.effect @reactive.event(input.toggle_inner) def _(): ui.update_sidebar("sidebar_inner", show=not input.sidebar_inner()) - @reactive.Effect + @reactive.effect @reactive.event(input.toggle_outer) def _(): ui.update_sidebar("sidebar_outer", show=not input.sidebar_outer()) diff --git a/tests/e2e/TODO/update_popover/app.py b/tests/e2e/TODO/update_popover/app.py index 55b6d1c2b..639987639 100644 --- a/tests/e2e/TODO/update_popover/app.py +++ b/tests/e2e/TODO/update_popover/app.py @@ -14,12 +14,12 @@ def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect + @reactive.effect def _(): # Immediately display popover ui.update_popover("popover_id", show=True) - @reactive.Effect + @reactive.effect @reactive.event(input.btn_update) def _(): content = ( @@ -33,7 +33,7 @@ def _(): # show=True ) - @reactive.Effect + @reactive.effect def _(): req(input.btn_w_popover()) ui.notification_show("Button clicked!", duration=3, type="message") diff --git a/tests/e2e/async/app.py b/tests/e2e/async/app.py index 324291f29..0f1dcf63f 100644 --- a/tests/e2e/async/app.py +++ b/tests/e2e/async/app.py @@ -31,7 +31,7 @@ async def hash_output(): content = await hash_result() return content - @reactive.Calc() + @reactive.calc() async def hash_result() -> str: with ui.Progress() as p: p.set(message="Calculating...") diff --git a/tests/e2e/bugs/0648-update-slider-datetime-value/app.py b/tests/e2e/bugs/0648-update-slider-datetime-value/app.py index 15f5e62bd..ec5343723 100644 --- a/tests/e2e/bugs/0648-update-slider-datetime-value/app.py +++ b/tests/e2e/bugs/0648-update-slider-datetime-value/app.py @@ -51,7 +51,7 @@ def txt(): else: return input.times() - @reactive.Effect + @reactive.effect @reactive.event(input.reset) def reset_time(): ui.update_slider("times", min=min, max=max, value=value) diff --git a/tests/e2e/bugs/0696-resolve-id/app.py b/tests/e2e/bugs/0696-resolve-id/app.py index f4d6e4fd7..0365ae5b7 100644 --- a/tests/e2e/bugs/0696-resolve-id/app.py +++ b/tests/e2e/bugs/0696-resolve-id/app.py @@ -217,7 +217,7 @@ def mod_x_server( ): session_dict[session.ns] = session - @reactive.Calc + @reactive.calc def n(): return int(letters.index(input.input_radio_buttons())) @@ -394,7 +394,7 @@ def server(input: Inputs, output: Outputs, session: Session): ui.update_popover("explanation", show=True) # On button clicks, hide the explanation popover - @reactive.Effect(suspended=True) + @reactive.effect(suspended=True) def _(): input.reset() input.update_global() @@ -482,7 +482,7 @@ def update_session( for _input_key, session_key in module_keys: offsets[session_key] = 0 - @reactive.Effect + @reactive.effect def _(): # trigger reactivity input.reset() @@ -492,7 +492,7 @@ def _(): update_session(session_dict[session_key], count=0) def update_session_effect(*, input_key: str, session_key: str) -> None: - @reactive.Effect + @reactive.effect @reactive.event(input[input_key]) def _(): update_session( diff --git a/tests/e2e/inputs/input_file/app.py b/tests/e2e/inputs/input_file/app.py index 225f2bde8..eea183e86 100644 --- a/tests/e2e/inputs/input_file/app.py +++ b/tests/e2e/inputs/input_file/app.py @@ -20,7 +20,7 @@ def server(input: Inputs, output: Outputs, session: Session): - @reactive.Calc + @reactive.calc def parsed_file(): file: typing.Union[typing.List["FileInfo"], None] = input.file1() if file is None: diff --git a/tests/e2e/navs/navset_hidden/app.py b/tests/e2e/navs/navset_hidden/app.py index 09ff8d859..82771e41e 100644 --- a/tests/e2e/navs/navset_hidden/app.py +++ b/tests/e2e/navs/navset_hidden/app.py @@ -20,7 +20,7 @@ def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect + @reactive.effect @reactive.event(input.controller) def _(): ui.update_navs("hidden_tabs", selected="panel" + str(input.controller())) diff --git a/tests/e2e/session/flush/app.py b/tests/e2e/session/flush/app.py index 8cc62011a..9d9f4830e 100644 --- a/tests/e2e/session/flush/app.py +++ b/tests/e2e/session/flush/app.py @@ -81,12 +81,12 @@ async def _(): session.on_ended(on_ended_async("session ended - async - test3")) session.on_ended(on_ended_sync("session ended - sync - test4")) - all_vals: reactive.Value[tuple[str, ...]] = reactive.Value(()) - flush_vals: reactive.Value[tuple[str, ...]] = reactive.Value(()) - flushed_vals: reactive.Value[tuple[str, ...]] = reactive.Value(()) + all_vals: reactive.value[tuple[str, ...]] = reactive.value(()) + flush_vals: reactive.value[tuple[str, ...]] = reactive.value(()) + flushed_vals: reactive.value[tuple[str, ...]] = reactive.value(()) def call_a( - vals: reactive.Value[tuple[str, ...]], + vals: reactive.value[tuple[str, ...]], suffix: str, ): def _(): @@ -97,7 +97,7 @@ def _(): return _ def call_b( - vals: reactive.Value[tuple[str, ...]], + vals: reactive.value[tuple[str, ...]], suffix: str, ): async def _(): @@ -112,7 +112,7 @@ async def _(): return _ def call_c( - vals: reactive.Value[tuple[str, ...]], + vals: reactive.value[tuple[str, ...]], suffix: str, ): def _(): @@ -125,11 +125,11 @@ def _(): # Continuously trigger the reactive graph to ensure that the flush / flushed # callbacks are called. If this Effect is not called, then the click counter will # always be one higher than the flush/flushed values displayed. - @reactive.Effect + @reactive.effect def _(): reactive.invalidate_later(0.25) - @reactive.Effect + @reactive.effect @reactive.event(input.btn) def _(): btn_count = input.btn() diff --git a/tests/e2e/ui/accordion/app.py b/tests/e2e/ui/accordion/app.py index ff07c5d8f..ddea80b5d 100644 --- a/tests/e2e/ui/accordion/app.py +++ b/tests/e2e/ui/accordion/app.py @@ -29,14 +29,14 @@ def make_panel(letter: str) -> ui.AccordionPanel: def server(input: Inputs, output: Outputs, session: Session) -> None: - @reactive.Calc + @reactive.calc def acc() -> list[str]: acc_val: list[str] | None = input.acc() if acc_val is None: acc_val = [] return acc_val - @reactive.Effect + @reactive.effect def _(): req(input.toggle_b()) @@ -46,12 +46,12 @@ def _(): else: ui.update_accordion_panel("acc", "Section B", show=True) - @reactive.Effect + @reactive.effect def _(): req(input.open_all()) ui.update_accordion("acc", show=True) - @reactive.Effect + @reactive.effect def _(): req(input.close_all()) ui.update_accordion("acc", show=False) @@ -60,7 +60,7 @@ def _(): has_alternate = True has_updates = False - @reactive.Effect + @reactive.effect def _(): req(input.alternate()) @@ -79,7 +79,7 @@ def _(): ui.update_accordion("acc", show=sections) has_alternate = not has_alternate - @reactive.Effect + @reactive.effect def _(): req(input.toggle_efg()) @@ -93,7 +93,7 @@ def _(): has_efg = not has_efg - @reactive.Effect + @reactive.effect def _(): req(input.toggle_updates()) diff --git a/tests/e2e/ui/popover/app.py b/tests/e2e/ui/popover/app.py index 45ea2191b..c7add2786 100644 --- a/tests/e2e/ui/popover/app.py +++ b/tests/e2e/ui/popover/app.py @@ -15,19 +15,19 @@ def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect + @reactive.effect def _(): req(input.btn_show()) ui.update_popover("popover_id", show=True) - @reactive.Effect + @reactive.effect def _(): req(input.btn_close()) ui.update_popover("popover_id", show=False) - @reactive.Effect + @reactive.effect def _(): req(input.btn_w_popover()) ui.notification_show("Button clicked!", duration=3, type="message") diff --git a/tests/e2e/ui/tooltip/app.py b/tests/e2e/ui/tooltip/app.py index fcf6cb208..375d32eb4 100644 --- a/tests/e2e/ui/tooltip/app.py +++ b/tests/e2e/ui/tooltip/app.py @@ -15,19 +15,19 @@ def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect + @reactive.effect def _(): req(input.btn_show()) ui.update_tooltip("tooltip_id", show=True) - @reactive.Effect + @reactive.effect def _(): req(input.btn_close()) ui.update_tooltip("tooltip_id", show=False) - @reactive.Effect + @reactive.effect def _(): req(input.btn_w_tooltip()) ui.notification_show("Button clicked!", duration=3, type="message") diff --git a/tests/pytest/test_modules.py b/tests/pytest/test_modules.py index 5c9e6e2cc..1b91fc7d7 100644 --- a/tests/pytest/test_modules.py +++ b/tests/pytest/test_modules.py @@ -45,11 +45,11 @@ async def test_session_scoping(): @module.server def inner_server(input: Inputs, output: Outputs, session: Session): - @reactive.Calc + @reactive.calc def out(): return get_current_session() - @reactive.Effect + @reactive.effect def _(): sessions["inner"] = session sessions["inner_current"] = get_current_session() @@ -59,11 +59,11 @@ def _(): @module.server def outer_server(input: Inputs, output: Outputs, session: Session): - @reactive.Calc + @reactive.calc def out(): return get_current_session() - @reactive.Effect + @reactive.effect def _(): inner_server("mod_inner") sessions["outer"] = session @@ -75,11 +75,11 @@ def _(): def server(input: Inputs, output: Outputs, session: Session): outer_server("mod_outer") - @reactive.Calc + @reactive.calc def out(): return get_current_session() - @reactive.Effect + @reactive.effect def _(): sessions["top"] = session sessions["top_current"] = get_current_session() diff --git a/tests/pytest/test_poll.py b/tests/pytest/test_poll.py index 4630669b4..cee0d9d0b 100644 --- a/tests/pytest/test_poll.py +++ b/tests/pytest/test_poll.py @@ -11,7 +11,7 @@ from shiny import Session, _utils, session from shiny._namespaces import Root -from shiny.reactive import Effect, Value, file_reader, flush, isolate, poll +from shiny.reactive import effect, file_reader, flush, isolate, poll, value from .mocktime import MockTime @@ -57,9 +57,9 @@ async def test_poll(): async with OnEndedSessionCallbacks(): poll_invocations = 0 poll_return1 = 0 # A non-reactive component of the return value - poll_return2 = Value(0) # A reactive component of the return value + poll_return2 = value(0) # A reactive component of the return value value_invocations = 0 - value_dep = Value(0) + value_dep = value(0) def poll_func(): nonlocal poll_invocations @@ -83,7 +83,7 @@ def value_func(): # @poll returns a lazy Calc, so value hasn't been invoked yet. assert (poll_invocations, value_invocations) == (2, 0) - @Effect() + @effect() def _(): value_func() @@ -157,7 +157,7 @@ def bad_poll() -> Any: invocations = 0 - @Effect() + @effect() def _(): nonlocal invocations invocations += 1 diff --git a/tests/pytest/test_reactives.py b/tests/pytest/test_reactives.py index e57c056d1..b1ddf2a99 100644 --- a/tests/pytest/test_reactives.py +++ b/tests/pytest/test_reactives.py @@ -7,7 +7,7 @@ from shiny import App, render, req, ui from shiny._connection import MockConnection -from shiny.reactive import Calc, Effect, Value, event, flush, invalidate_later, isolate +from shiny.reactive import calc, effect, event, flush, invalidate_later, isolate, value from shiny.reactive._core import ReactiveWarning from shiny.types import ActionButtonValue, SilentException @@ -21,19 +21,19 @@ async def test_flush_runs_newly_invalidated(): flush. """ - v1 = Value(1) - v2 = Value(2) + v1 = value(1) + v2 = value(2) v2_result = None # In practice, on the first flush, Effects run in the order that they were created. # Our test checks that o2 runs _after_ o1. - @Effect() + @effect() def o2(): nonlocal v2_result v2_result = v2() - @Effect() + @effect() def o1(): v2.set(v1()) @@ -50,19 +50,19 @@ async def test_flush_runs_newly_invalidated_async(): flush. (Same as previous test, but async.) """ - v1 = Value(1) - v2 = Value(2) + v1 = value(1) + v2 = value(2) v2_result = None # In practice, on the first flush, Effects run in the order that they were # created. Our test checks that o2 runs _after_ o1. - @Effect() + @effect() async def o2(): nonlocal v2_result v2_result = v2() - @Effect() + @effect() async def o1(): v2.set(v1()) @@ -77,9 +77,9 @@ async def o1(): # ====================================================================== @pytest.mark.asyncio async def test_reactive_value_same_no_invalidate(): - v = Value(1) + v = value(1) - @Effect() + @effect() def o(): v() @@ -96,7 +96,7 @@ def o(): # ====================================================================== @pytest.mark.asyncio async def test_reactive_value_unset(): - v = Value[int]() + v = value[int]() with isolate(): assert v.is_set() is False @@ -105,7 +105,7 @@ async def test_reactive_value_unset(): val: int = 0 - @Effect() + @effect() def o(): nonlocal val val = v() @@ -136,10 +136,10 @@ def o(): # ====================================================================== @pytest.mark.asyncio async def test_reactive_value_is_set(): - v = Value[int]() + v = value[int]() v_is_set: bool = False - @Effect() + @effect() def o(): nonlocal v_is_set v_is_set = v.is_set() @@ -179,16 +179,16 @@ def o(): # ====================================================================== @pytest.mark.asyncio async def test_recursive_calc(): - v = Value(5) + v = value(5) - @Calc() + @calc() def r(): if v() == 0: return 0 v.set(v() - 1) r() - @Effect() + @effect() def o(): r() @@ -201,16 +201,16 @@ def o(): @pytest.mark.asyncio async def test_recursive_async_calc(): - v = Value(5) + v = value(5) - @Calc() + @calc() async def r() -> int: if v() == 0: return 0 v.set(v() - 1) return await r() - @Effect() + @effect() async def o(): await r() @@ -228,12 +228,12 @@ async def o(): @pytest.mark.asyncio async def test_async_sequential(): - x: Value[int] = Value(1) + x: value[int] = value(1) results: list[int] = [] exec_order: list[str] = [] async def react_chain(n: int): - @Calc() + @calc() async def r(): nonlocal exec_order exec_order.append(f"r{n}-1") @@ -241,7 +241,7 @@ async def r(): exec_order.append(f"r{n}-2") return x() + 10 - @Effect() + @effect() async def _(): nonlocal exec_order exec_order.append(f"o{n}-1") @@ -277,9 +277,9 @@ async def _(): @pytest.mark.asyncio async def test_isolate_basic_without_context(): # isolate() works with calc and Value; allows executing without a reactive context. - v = Value(1) + v = value(1) - @Calc() + @calc() def r(): return v() + 10 @@ -296,16 +296,16 @@ def get_r(): @pytest.mark.asyncio async def test_isolate_prevents_dependency(): - v = Value(1) + v = value(1) - @Calc() + @calc() def r(): return v() + 10 - v_dep = Value(1) # Use this only for invalidating the effect + v_dep = value(1) # Use this only for invalidating the effect o_val = None - @Effect() + @effect() def o(): nonlocal o_val v_dep() @@ -344,9 +344,9 @@ async def f(): async def test_isolate_async_basic_without_context(): # async isolate works with calc and Value; allows executing without a reactive # context. - v = Value(1) + v = value(1) - @Calc() + @calc() async def r(): return v() + 10 @@ -360,16 +360,16 @@ async def get_r(): @pytest.mark.asyncio async def test_isolate_async_prevents_dependency(): - v = Value(1) + v = value(1) - @Calc() + @calc() async def r(): return v() + 10 - v_dep = Value(1) # Use this only for invalidating the effect + v_dep = value(1) # Use this only for invalidating the effect o_val = None - @Effect() + @effect() async def o(): nonlocal o_val v_dep() @@ -397,22 +397,22 @@ async def o(): # ====================================================================== @pytest.mark.asyncio async def test_effect_priority(): - v = Value(1) + v = value(1) results: list[int] = [] - @Effect(priority=1) + @effect(priority=1) def o1(): nonlocal results v() results.append(1) - @Effect(priority=2) + @effect(priority=2) def o2(): nonlocal results v() results.append(2) - @Effect(priority=1) + @effect(priority=1) def o3(): nonlocal results v() @@ -423,7 +423,7 @@ def o3(): # Add another effect with priority 2. Only this one will run (until we # invalidate others by changing v). - @Effect(priority=2) + @effect(priority=2) def o4(): nonlocal results v() @@ -448,22 +448,22 @@ def o4(): # Same as previous, but with async @pytest.mark.asyncio async def test_async_effect_priority(): - v = Value(1) + v = value(1) results: list[int] = [] - @Effect(priority=1) + @effect(priority=1) async def o1(): nonlocal results v() results.append(1) - @Effect(priority=2) + @effect(priority=2) async def o2(): nonlocal results v() results.append(2) - @Effect(priority=1) + @effect(priority=1) async def o3(): nonlocal results v() @@ -474,7 +474,7 @@ async def o3(): # Add another effect with priority 2. Only this one will run (until we # invalidate others by changing v). - @Effect(priority=2) + @effect(priority=2) async def o4(): nonlocal results v() @@ -501,10 +501,10 @@ async def o4(): # ====================================================================== @pytest.mark.asyncio async def test_effect_destroy(): - v = Value(1) + v = value(1) results: list[int] = [] - @Effect() + @effect() def o1(): nonlocal results v() @@ -519,10 +519,10 @@ def o1(): assert results == [1] # Same as above, but destroy before running first time - v = Value(1) + v = value(1) results: list[int] = [] - @Effect() + @effect() def o2(): nonlocal results v() @@ -540,17 +540,17 @@ def o2(): async def test_error_handling(): vals: List[str] = [] - @Effect() + @effect() def _(): vals.append("o1") - @Effect() + @effect() def _(): vals.append("o2-1") raise Exception("Error here!") vals.append("o2-2") - @Effect() + @effect() def _(): vals.append("o3") @@ -562,18 +562,18 @@ def _(): vals: List[str] = [] - @Calc() + @calc() def r(): vals.append("r") raise Exception("Error here!") - @Effect() + @effect() def _(): vals.append("o1-1") r() vals.append("o1-2") - @Effect() + @effect() def _(): vals.append("o2") @@ -587,21 +587,21 @@ def _(): async def test_calc_error_rethrow(): # Make sure calcs re-throw errors. vals: List[str] = [] - v = Value(1) + v = value(1) - @Calc() + @calc() def r(): vals.append("r") raise Exception("Error here!") - @Effect() + @effect() def _(): v() vals.append("o1-1") r() vals.append("o1-2") - @Effect() + @effect() def _(): v() vals.append("o2-2") @@ -624,11 +624,11 @@ def _(): # For https://github.com/posit-dev/py-shiny/issues/26 @pytest.mark.asyncio async def test_dependent_invalidation(): - trigger = Value(0) - v = Value(0) + trigger = value(0) + v = value(0) error_occurred = False - @Effect() + @effect() def _(): trigger() @@ -641,11 +641,11 @@ def _(): nonlocal error_occurred error_occurred = True - @Effect() + @effect() def _(): r() - @Calc() + @calc() def r(): return v() @@ -667,7 +667,7 @@ def r(): async def test_req(): n_times = 0 - @Effect() + @effect() def _(): req(False) nonlocal n_times @@ -676,7 +676,7 @@ def _(): await flush() assert n_times == 0 - @Effect() + @effect() def _(): req(True) nonlocal n_times @@ -685,14 +685,14 @@ def _(): await flush() assert n_times == 1 - @Calc() + @calc() def r(): req(False) return 1 val = None - @Effect() + @effect() def _(): nonlocal val val = r() @@ -700,12 +700,12 @@ def _(): await flush() assert val is None - @Calc() + @calc() def r2(): req(True) return 1 - @Effect() + @effect() def _(): nonlocal val val = r2() @@ -719,7 +719,7 @@ async def test_invalidate_later(): mock_time = MockTime() with mock_time(): - @Effect() + @effect() def obs1(): invalidate_later(1) @@ -749,9 +749,9 @@ def obs1(): async def test_invalidate_later_invalidation(): mock_time = MockTime() with mock_time(): - rv = Value(0) + rv = value(0) - @Effect() + @effect() def obs1(): if rv() == 0: invalidate_later(1) @@ -803,7 +803,7 @@ async def test_event_decorator(): n_times = 0 # By default, runs every time that event expression is _not_ None (ignore_none=True) - @Effect() + @effect() @event(lambda: None, lambda: ActionButtonValue(0)) def _(): nonlocal n_times @@ -813,7 +813,7 @@ def _(): assert n_times == 0 # Unless ignore_none=False - @Effect() + @effect() @event(lambda: None, lambda: ActionButtonValue(0), ignore_none=False) def _(): nonlocal n_times @@ -823,7 +823,7 @@ def _(): assert n_times == 1 # Or if one of the args is not None - @Effect() + @effect() @event(lambda: None, lambda: ActionButtonValue(0), lambda: True) def _(): nonlocal n_times @@ -833,9 +833,9 @@ def _(): assert n_times == 2 # Is invalidated properly by reactive values - v = Value(1) + v = value(1) - @Effect() + @effect() @event(v) def _(): nonlocal n_times @@ -853,9 +853,9 @@ def _(): assert n_times == 4 # Doesn't run on init - v = Value(1) + v = value(1) - @Effect() + @effect() @event(v, ignore_init=True) def _(): nonlocal n_times @@ -869,10 +869,10 @@ def _(): assert n_times == 5 # Isolates properly - v = Value(1) - v2 = Value(1) + v = value(1) + v2 = value(1) - @Effect() + @effect() @event(v) def _(): nonlocal n_times @@ -886,14 +886,14 @@ def _(): assert n_times == 6 # works with @calc() - v2 = Value(1) + v2 = value(1) - @Calc() + @calc() @event(lambda: v2(), ignore_init=True) def r2b(): return 1 - @Effect() + @effect() def _(): nonlocal n_times n_times += r2b() @@ -914,7 +914,7 @@ async def test_event_async_decorator(): n_times = 0 # By default, runs every time that event expression is _not_ None (ignore_none=True) - @Effect() + @effect() @event(lambda: None, lambda: ActionButtonValue(0)) async def _(): nonlocal n_times @@ -924,7 +924,7 @@ async def _(): assert n_times == 0 # Unless ignore_none=False - @Effect() + @effect() @event(lambda: None, lambda: ActionButtonValue(0), ignore_none=False) async def _(): nonlocal n_times @@ -934,7 +934,7 @@ async def _(): assert n_times == 1 # Or if one of the args is not None - @Effect() + @effect() @event(lambda: None, lambda: ActionButtonValue(0), lambda: True) async def _(): nonlocal n_times @@ -944,9 +944,9 @@ async def _(): assert n_times == 2 # Is invalidated properly by reactive values - v = Value(1) + v = value(1) - @Effect() + @effect() @event(v) async def _(): nonlocal n_times @@ -964,9 +964,9 @@ async def _(): assert n_times == 4 # Doesn't run on init - v = Value(1) + v = value(1) - @Effect() + @effect() @event(v, ignore_init=True) async def _(): nonlocal n_times @@ -980,10 +980,10 @@ async def _(): assert n_times == 5 # Isolates properly - v = Value(1) - v2 = Value(1) + v = value(1) + v2 = value(1) - @Effect() + @effect() @event(v) async def _(): nonlocal n_times @@ -997,20 +997,20 @@ async def _(): assert n_times == 6 # works with @calc() - v2 = Value(1) + v2 = value(1) - @Calc() + @calc() async def r_a(): await asyncio.sleep(0) # Make sure the async function yields control return 1 - @Calc() + @calc() @event(lambda: v2(), r_a, ignore_init=True) async def r2b(): await asyncio.sleep(0) # Make sure the async function yields control return 1 - @Effect() + @effect() async def _(): nonlocal n_times await asyncio.sleep(0) @@ -1030,9 +1030,9 @@ async def _(): @pytest.mark.asyncio async def test_event_silent_exception(): n_times = 0 - x = Value[bool]() + x = value[bool]() - @Effect() + @effect() @event(x) def _(): nonlocal n_times @@ -1060,14 +1060,14 @@ def _(): @pytest.mark.asyncio async def test_event_silent_exception_async(): n_times = 0 - x = Value[bool]() + x = value[bool]() async def req_fn() -> int: await asyncio.sleep(0) x() return 1234 - @Effect() + @effect() @event(req_fn) async def _(): await asyncio.sleep(0) @@ -1109,14 +1109,14 @@ async def _(): # Should complain that @event() can't take the result of @Effect (which returns # None). @event(lambda: 1) # type: ignore - @Effect() + @effect() async def _(): ... with pytest.raises(TypeError): # Should complain that @event must be applied before @Calc. @event(lambda: 1) - @Calc() + @calc() async def _(): ... @@ -1141,12 +1141,12 @@ async def _(): async def _(): ... - @Effect() + @effect() @event(lambda: 1) async def _(): ... - @Calc() + @calc() @event(lambda: 1) async def _(): ... @@ -1191,14 +1191,14 @@ def _(): with pytest.raises(TypeError): # Should complain about @Calc @output # type: ignore - @Calc + @calc def _(): ... with pytest.raises(TypeError): # Should complain about @Effet @output # type: ignore - @Effect + @effect def _(): ... @@ -1219,13 +1219,13 @@ def _(): # ------------------------------------------------------------ @pytest.mark.asyncio async def test_effect_pausing(): - a = Value(float(1)) + a = value(float(1)) - @Calc() + @calc() def funcA(): return a() - @Effect() + @effect() def obsB(): funcA() @@ -1301,13 +1301,13 @@ def _(): # ------------------------------------------------------------ @pytest.mark.asyncio async def test_effect_async_pausing(): - a = Value(float(1)) + a = value(float(1)) - @Calc() + @calc() async def funcA(): return a() - @Effect() + @effect() async def obsB(): await funcA() @@ -1380,9 +1380,9 @@ def _(): @pytest.mark.asyncio async def test_observer_async_suspended_resumed_observers_run_at_most_once(): - a = Value(1) + a = value(1) - @Effect() + @effect() async def obs(): print(a()) diff --git a/tests/pytest/test_shinysession.py b/tests/pytest/test_shinysession.py index 8ef2149a9..18017ba44 100644 --- a/tests/pytest/test_shinysession.py +++ b/tests/pytest/test_shinysession.py @@ -3,7 +3,7 @@ import pytest from shiny import ui -from shiny.reactive import Effect, flush, isolate +from shiny.reactive import effect, flush, isolate from shiny.session import Inputs from shiny.types import SilentException @@ -55,7 +55,7 @@ async def test_input_nonexistent_deps(): input = Inputs({}) result = None - @Effect() + @effect() def o1(): nonlocal result result = "x" in input From 3cbb33a6936bd307fed945b541343df1e030cf2b Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Mon, 27 Nov 2023 15:18:19 -0600 Subject: [PATCH 3/6] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10f448d11..4baf6cbf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### New features +* Closed #814: The classes `reactive.Value`, `reactive.Calc`, and `reactive.Effect` have been changed to have lowercase names: `reactive.value`, `reactive.calc`, and `reactive.effect`. The old capitalized names are now aliases to the new lowercase names, so existing code will continue to work. The examples have not been changed yet, but will be changed in a future release. (#822) + ### Bug fixes * Fix support for `shiny.ui.accordion(multiple=)` (#799). From 957ae6d1236c282527b4686f089bdef8bbe2ce6a Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Mon, 27 Nov 2023 15:18:38 -0600 Subject: [PATCH 4/6] Add lowercase functions to docs --- docs/_quartodoc.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/_quartodoc.yml b/docs/_quartodoc.yml index 98f38e7af..cc1dd3353 100644 --- a/docs/_quartodoc.yml +++ b/docs/_quartodoc.yml @@ -186,6 +186,9 @@ quartodoc: - title: Reactive programming desc: "" contents: + - reactive.calc + - reactive.effect + - reactive.value - reactive.Calc - reactive.Effect - reactive.Value From 28746007904720c921f4153918ef9071cd0cecae Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 29 Nov 2023 14:46:05 -0600 Subject: [PATCH 5/6] Change class name back to Value --- shiny/reactive/_poll.py | 4 +- shiny/reactive/_reactives.py | 5 +-- shiny/session/_session.py | 18 ++++---- tests/e2e/session/flush/app.py | 12 ++--- tests/pytest/test_poll.py | 6 +-- tests/pytest/test_reactives.py | 80 +++++++++++++++++----------------- 6 files changed, 62 insertions(+), 63 deletions(-) diff --git a/shiny/reactive/_poll.py b/shiny/reactive/_poll.py index e091a4786..479757ae5 100644 --- a/shiny/reactive/_poll.py +++ b/shiny/reactive/_poll.py @@ -96,8 +96,8 @@ def poll( """ with reactive.isolate(): - last_value: reactive.value[Any] = reactive.value(poll_func()) - last_error: reactive.value[Optional[Exception]] = reactive.value(None) + last_value: reactive.Value[Any] = reactive.Value(poll_func()) + last_error: reactive.Value[Optional[Exception]] = reactive.Value(None) @reactive.effect(priority=priority, session=session) async def _(): diff --git a/shiny/reactive/_reactives.py b/shiny/reactive/_reactives.py index 3e6e73819..fd724b130 100644 --- a/shiny/reactive/_reactives.py +++ b/shiny/reactive/_reactives.py @@ -46,7 +46,7 @@ # Value # ============================================================================== @add_example() -class value(Generic[T]): +class Value(Generic[T]): """ Create a reactive value. @@ -215,8 +215,7 @@ def freeze(self) -> None: self._value = MISSING -# Alias for backward compatibility -Value = value +value = Value # ============================================================================== # Calc diff --git a/shiny/session/_session.py b/shiny/session/_session.py index b38972cb7..b46eec44c 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -46,7 +46,7 @@ from .._utils import wrap_async from ..http_staticfiles import FileResponse from ..input_handler import input_handlers -from ..reactive import Effect_, effect, flush, isolate, value +from ..reactive import Effect_, Value, effect, flush, isolate from ..reactive._core import lock, on_flushed from ..render.transformer import OutputRenderer from ..types import SafeException, SilentCancelOutputException, SilentException @@ -345,7 +345,7 @@ def _is_hidden(self, name: str) -> bool: # id; make that explicit by wrapping it in ResolvedId, otherwise self.input # will throw an id validation error. hidden_value_obj = cast( - value[bool], self.input[ResolvedId(f".clientdata_output_{name}_hidden")] + Value[bool], self.input[ResolvedId(f".clientdata_output_{name}_hidden")] ) if not hidden_value_obj.is_set(): return True @@ -898,24 +898,24 @@ class Inputs: """ def __init__( - self, values: dict[str, value[Any]], ns: Callable[[str], str] = Root + self, values: dict[str, Value[Any]], ns: Callable[[str], str] = Root ) -> None: self._map = values self._ns = ns - def __setitem__(self, key: str, value: value[Any]) -> None: - if not isinstance(value, reactive.value): + def __setitem__(self, key: str, value: Value[Any]) -> None: + if not isinstance(value, reactive.Value): raise TypeError("`value` must be a reactive.Value object.") self._map[self._ns(key)] = value - def __getitem__(self, key: str) -> value[Any]: + def __getitem__(self, key: str) -> Value[Any]: key = self._ns(key) # Auto-populate key if accessed but not yet set. Needed to take reactive # dependencies on input values that haven't been received from client # yet. if key not in self._map: - self._map[key] = value[Any](read_only=True) + self._map[key] = Value[Any](read_only=True) return self._map[key] @@ -923,14 +923,14 @@ def __delitem__(self, key: str) -> None: del self._map[self._ns(key)] # Allow access of values as attributes. - def __setattr__(self, attr: str, value: value[Any]) -> None: + def __setattr__(self, attr: str, value: Value[Any]) -> None: if attr in ("_map", "_ns"): super().__setattr__(attr, value) return self.__setitem__(attr, value) - def __getattr__(self, attr: str) -> value[Any]: + def __getattr__(self, attr: str) -> Value[Any]: if attr in ("_map", "_ns"): return object.__getattribute__(self, attr) return self.__getitem__(attr) diff --git a/tests/e2e/session/flush/app.py b/tests/e2e/session/flush/app.py index 9d9f4830e..38e6643c9 100644 --- a/tests/e2e/session/flush/app.py +++ b/tests/e2e/session/flush/app.py @@ -81,12 +81,12 @@ async def _(): session.on_ended(on_ended_async("session ended - async - test3")) session.on_ended(on_ended_sync("session ended - sync - test4")) - all_vals: reactive.value[tuple[str, ...]] = reactive.value(()) - flush_vals: reactive.value[tuple[str, ...]] = reactive.value(()) - flushed_vals: reactive.value[tuple[str, ...]] = reactive.value(()) + all_vals: reactive.Value[tuple[str, ...]] = reactive.Value(()) + flush_vals: reactive.Value[tuple[str, ...]] = reactive.Value(()) + flushed_vals: reactive.Value[tuple[str, ...]] = reactive.Value(()) def call_a( - vals: reactive.value[tuple[str, ...]], + vals: reactive.Value[tuple[str, ...]], suffix: str, ): def _(): @@ -97,7 +97,7 @@ def _(): return _ def call_b( - vals: reactive.value[tuple[str, ...]], + vals: reactive.Value[tuple[str, ...]], suffix: str, ): async def _(): @@ -112,7 +112,7 @@ async def _(): return _ def call_c( - vals: reactive.value[tuple[str, ...]], + vals: reactive.Value[tuple[str, ...]], suffix: str, ): def _(): diff --git a/tests/pytest/test_poll.py b/tests/pytest/test_poll.py index cee0d9d0b..46b5b298c 100644 --- a/tests/pytest/test_poll.py +++ b/tests/pytest/test_poll.py @@ -11,7 +11,7 @@ from shiny import Session, _utils, session from shiny._namespaces import Root -from shiny.reactive import effect, file_reader, flush, isolate, poll, value +from shiny.reactive import Value, effect, file_reader, flush, isolate, poll from .mocktime import MockTime @@ -57,9 +57,9 @@ async def test_poll(): async with OnEndedSessionCallbacks(): poll_invocations = 0 poll_return1 = 0 # A non-reactive component of the return value - poll_return2 = value(0) # A reactive component of the return value + poll_return2 = Value(0) # A reactive component of the return value value_invocations = 0 - value_dep = value(0) + value_dep = Value(0) def poll_func(): nonlocal poll_invocations diff --git a/tests/pytest/test_reactives.py b/tests/pytest/test_reactives.py index b1ddf2a99..45eb1eed3 100644 --- a/tests/pytest/test_reactives.py +++ b/tests/pytest/test_reactives.py @@ -7,7 +7,7 @@ from shiny import App, render, req, ui from shiny._connection import MockConnection -from shiny.reactive import calc, effect, event, flush, invalidate_later, isolate, value +from shiny.reactive import Value, calc, effect, event, flush, invalidate_later, isolate from shiny.reactive._core import ReactiveWarning from shiny.types import ActionButtonValue, SilentException @@ -21,8 +21,8 @@ async def test_flush_runs_newly_invalidated(): flush. """ - v1 = value(1) - v2 = value(2) + v1 = Value(1) + v2 = Value(2) v2_result = None @@ -50,8 +50,8 @@ async def test_flush_runs_newly_invalidated_async(): flush. (Same as previous test, but async.) """ - v1 = value(1) - v2 = value(2) + v1 = Value(1) + v2 = Value(2) v2_result = None @@ -77,7 +77,7 @@ async def o1(): # ====================================================================== @pytest.mark.asyncio async def test_reactive_value_same_no_invalidate(): - v = value(1) + v = Value(1) @effect() def o(): @@ -96,7 +96,7 @@ def o(): # ====================================================================== @pytest.mark.asyncio async def test_reactive_value_unset(): - v = value[int]() + v = Value[int]() with isolate(): assert v.is_set() is False @@ -136,7 +136,7 @@ def o(): # ====================================================================== @pytest.mark.asyncio async def test_reactive_value_is_set(): - v = value[int]() + v = Value[int]() v_is_set: bool = False @effect() @@ -179,7 +179,7 @@ def o(): # ====================================================================== @pytest.mark.asyncio async def test_recursive_calc(): - v = value(5) + v = Value(5) @calc() def r(): @@ -201,7 +201,7 @@ def o(): @pytest.mark.asyncio async def test_recursive_async_calc(): - v = value(5) + v = Value(5) @calc() async def r() -> int: @@ -228,7 +228,7 @@ async def o(): @pytest.mark.asyncio async def test_async_sequential(): - x: value[int] = value(1) + x: Value[int] = Value(1) results: list[int] = [] exec_order: list[str] = [] @@ -277,7 +277,7 @@ async def _(): @pytest.mark.asyncio async def test_isolate_basic_without_context(): # isolate() works with calc and Value; allows executing without a reactive context. - v = value(1) + v = Value(1) @calc() def r(): @@ -296,13 +296,13 @@ def get_r(): @pytest.mark.asyncio async def test_isolate_prevents_dependency(): - v = value(1) + v = Value(1) @calc() def r(): return v() + 10 - v_dep = value(1) # Use this only for invalidating the effect + v_dep = Value(1) # Use this only for invalidating the effect o_val = None @effect() @@ -344,7 +344,7 @@ async def f(): async def test_isolate_async_basic_without_context(): # async isolate works with calc and Value; allows executing without a reactive # context. - v = value(1) + v = Value(1) @calc() async def r(): @@ -360,13 +360,13 @@ async def get_r(): @pytest.mark.asyncio async def test_isolate_async_prevents_dependency(): - v = value(1) + v = Value(1) @calc() async def r(): return v() + 10 - v_dep = value(1) # Use this only for invalidating the effect + v_dep = Value(1) # Use this only for invalidating the effect o_val = None @effect() @@ -397,7 +397,7 @@ async def o(): # ====================================================================== @pytest.mark.asyncio async def test_effect_priority(): - v = value(1) + v = Value(1) results: list[int] = [] @effect(priority=1) @@ -448,7 +448,7 @@ def o4(): # Same as previous, but with async @pytest.mark.asyncio async def test_async_effect_priority(): - v = value(1) + v = Value(1) results: list[int] = [] @effect(priority=1) @@ -501,7 +501,7 @@ async def o4(): # ====================================================================== @pytest.mark.asyncio async def test_effect_destroy(): - v = value(1) + v = Value(1) results: list[int] = [] @effect() @@ -519,7 +519,7 @@ def o1(): assert results == [1] # Same as above, but destroy before running first time - v = value(1) + v = Value(1) results: list[int] = [] @effect() @@ -587,7 +587,7 @@ def _(): async def test_calc_error_rethrow(): # Make sure calcs re-throw errors. vals: List[str] = [] - v = value(1) + v = Value(1) @calc() def r(): @@ -624,8 +624,8 @@ def _(): # For https://github.com/posit-dev/py-shiny/issues/26 @pytest.mark.asyncio async def test_dependent_invalidation(): - trigger = value(0) - v = value(0) + trigger = Value(0) + v = Value(0) error_occurred = False @effect() @@ -749,7 +749,7 @@ def obs1(): async def test_invalidate_later_invalidation(): mock_time = MockTime() with mock_time(): - rv = value(0) + rv = Value(0) @effect() def obs1(): @@ -833,7 +833,7 @@ def _(): assert n_times == 2 # Is invalidated properly by reactive values - v = value(1) + v = Value(1) @effect() @event(v) @@ -853,7 +853,7 @@ def _(): assert n_times == 4 # Doesn't run on init - v = value(1) + v = Value(1) @effect() @event(v, ignore_init=True) @@ -869,8 +869,8 @@ def _(): assert n_times == 5 # Isolates properly - v = value(1) - v2 = value(1) + v = Value(1) + v2 = Value(1) @effect() @event(v) @@ -886,7 +886,7 @@ def _(): assert n_times == 6 # works with @calc() - v2 = value(1) + v2 = Value(1) @calc() @event(lambda: v2(), ignore_init=True) @@ -944,7 +944,7 @@ async def _(): assert n_times == 2 # Is invalidated properly by reactive values - v = value(1) + v = Value(1) @effect() @event(v) @@ -964,7 +964,7 @@ async def _(): assert n_times == 4 # Doesn't run on init - v = value(1) + v = Value(1) @effect() @event(v, ignore_init=True) @@ -980,8 +980,8 @@ async def _(): assert n_times == 5 # Isolates properly - v = value(1) - v2 = value(1) + v = Value(1) + v2 = Value(1) @effect() @event(v) @@ -997,7 +997,7 @@ async def _(): assert n_times == 6 # works with @calc() - v2 = value(1) + v2 = Value(1) @calc() async def r_a(): @@ -1030,7 +1030,7 @@ async def _(): @pytest.mark.asyncio async def test_event_silent_exception(): n_times = 0 - x = value[bool]() + x = Value[bool]() @effect() @event(x) @@ -1060,7 +1060,7 @@ def _(): @pytest.mark.asyncio async def test_event_silent_exception_async(): n_times = 0 - x = value[bool]() + x = Value[bool]() async def req_fn() -> int: await asyncio.sleep(0) @@ -1219,7 +1219,7 @@ def _(): # ------------------------------------------------------------ @pytest.mark.asyncio async def test_effect_pausing(): - a = value(float(1)) + a = Value(float(1)) @calc() def funcA(): @@ -1301,7 +1301,7 @@ def _(): # ------------------------------------------------------------ @pytest.mark.asyncio async def test_effect_async_pausing(): - a = value(float(1)) + a = Value(float(1)) @calc() async def funcA(): @@ -1380,7 +1380,7 @@ def _(): @pytest.mark.asyncio async def test_observer_async_suspended_resumed_observers_run_at_most_once(): - a = value(1) + a = Value(1) @effect() async def obs(): From b51398b1efdee2df0e2baed540e0bb8e619176d0 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 29 Nov 2023 14:49:01 -0600 Subject: [PATCH 6/6] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4baf6cbf0..8a88912cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### New features -* Closed #814: The classes `reactive.Value`, `reactive.Calc`, and `reactive.Effect` have been changed to have lowercase names: `reactive.value`, `reactive.calc`, and `reactive.effect`. The old capitalized names are now aliases to the new lowercase names, so existing code will continue to work. The examples have not been changed yet, but will be changed in a future release. (#822) +* Closed #814: The functions `reactive.Calc` and `reactive.Effect` have been changed to have lowercase names: `reactive.calc`, and `reactive.effect`. The old capitalized names are now aliases to the new lowercase names, so existing code will continue to work. Similarly, the class `reactive.Value` has a new alias, `reactive.value`, but in this case, since the original was a class, it keeps the original capitalized name as the primary name. The examples have not been changed yet, but will be changed in a future release. (#822) ### Bug fixes