diff --git a/shiny/reactive/_poll.py b/shiny/reactive/_poll.py index 413125a8b..2e8446bae 100644 --- a/shiny/reactive/_poll.py +++ b/shiny/reactive/_poll.py @@ -7,7 +7,7 @@ from .. import _utils, reactive from .._docstring import add_example -from ..types import MISSING, MISSING_TYPE +from ..types import MISSING, MISSING_TYPE, SilentException if TYPE_CHECKING: from ..session import Session @@ -99,7 +99,9 @@ def poll( with reactive.isolate(): last_value: reactive.Value[Any] = reactive.Value(poll_func()) - last_error: reactive.Value[Optional[Exception]] = reactive.Value(None) + last_error: reactive.Value[Optional[Exception | MISSING_TYPE]] = reactive.Value( + None + ) @reactive.effect(priority=priority, session=session) async def _(): @@ -166,7 +168,9 @@ def wrapper(fn: Callable[[], T]) -> Callable[[], T]: async def result_async() -> T: # If an error occurred, raise it err = last_error.get() - if err is not None: + if isinstance(err, MISSING_TYPE): + raise SilentException + elif err is not None: raise err # Take dependency on polling result @@ -189,7 +193,9 @@ async def result_async() -> T: def result_sync() -> T: # If an error occurred, raise it err = last_error.get() - if err is not None: + if isinstance(err, MISSING_TYPE): + raise SilentException + elif err is not None: raise err # Take dependency on polling result diff --git a/shiny/reactive/_reactives.py b/shiny/reactive/_reactives.py index 54c855579..5503cf46c 100644 --- a/shiny/reactive/_reactives.py +++ b/shiny/reactive/_reactives.py @@ -116,10 +116,10 @@ def __init__( self._value_dependents: Dependents = Dependents() self._is_set_dependents: Dependents = Dependents() - def __call__(self) -> T: + def __call__(self) -> T | MISSING_TYPE: return self.get() - def get(self) -> T: + def get(self) -> T | MISSING_TYPE: """ Read the reactive value. @@ -138,9 +138,6 @@ def get(self) -> T: self._value_dependents.register() - if isinstance(self._value, MISSING_TYPE): - raise SilentException - return self._value def set(self, value: T) -> bool: diff --git a/shiny/session/_session.py b/shiny/session/_session.py index f2618aaa3..7cd823f7c 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -50,7 +50,12 @@ from ..reactive import Effect_, Value, effect, flush, isolate from ..reactive._core import lock, on_flushed from ..render.renderer import Jsonifiable, RendererBase, RendererBaseT -from ..types import SafeException, SilentCancelOutputException, SilentException +from ..types import ( + MISSING_TYPE, + SafeException, + SilentCancelOutputException, + SilentException, +) from ._utils import RenderedDeps, read_thunk_opt, session_context @@ -344,13 +349,15 @@ def _is_hidden(self, name: str) -> bool: # The .clientdata_output_{name}_hidden string is already a fully namespaced # 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")] - ) + hidden_value_obj = self.input[ + ResolvedId(f".clientdata_output_{name}_hidden") + ] + if isinstance(hidden_value_obj, MISSING_TYPE): + return True if not hidden_value_obj.is_set(): return True - return hidden_value_obj() + return cast(bool, hidden_value_obj()) # ========================================================================== # Message handlers diff --git a/shiny/types.py b/shiny/types.py index fbdedbf89..8b86b710e 100644 --- a/shiny/types.py +++ b/shiny/types.py @@ -23,9 +23,45 @@ from matplotlib.figure import Figure +class MISSING_SILENT_EXCEPTION: + def __getattribute__(self, _: str) -> None: + raise SilentException + + def __getitem__(self, _: str) -> None: + raise SilentException + + def __setitem__(self, _: Any, __: Any) -> None: + raise SilentException + + def __add__(self, _: Any) -> None: + raise SilentException + + def __sub__(self, _: Any) -> None: + raise SilentException + + def __mul__(self, _: Any) -> None: + raise SilentException + + def __div__(self, _: Any) -> None: + raise SilentException + + # Sentinel value - indicates a missing value in a function call. -class MISSING_TYPE: - pass +class MISSING_TYPE(MISSING_SILENT_EXCEPTION): + def __eq__(self, _: Any) -> bool: + return False + + def __bool__(self) -> bool: + return False + + def __len__(self) -> int: + return 0 + + def __repr__(self) -> str: + return "" + + def __str__(self) -> str: + return "" MISSING: MISSING_TYPE = MISSING_TYPE()