diff --git a/setup.cfg b/setup.cfg index 81cf1ce0f..2e4b31848 100644 --- a/setup.cfg +++ b/setup.cfg @@ -93,6 +93,7 @@ test = folium palmerpenguins faicons + ridgeplot dev = black>=24.0 flake8>=6.0.0 diff --git a/shiny/express/_stub_session.py b/shiny/express/_stub_session.py index 19460cc10..7133bb915 100644 --- a/shiny/express/_stub_session.py +++ b/shiny/express/_stub_session.py @@ -119,6 +119,12 @@ async def _send_message(self, message: dict[str, object]) -> None: def _send_message_sync(self, message: dict[str, object]) -> None: return + def _increment_busy_count(self) -> None: + return + + def _decrement_busy_count(self) -> None: + return + def on_flush( self, fn: Callable[[], None] | Callable[[], Awaitable[None]], diff --git a/shiny/reactive/_reactives.py b/shiny/reactive/_reactives.py index a9147d91f..1503b42b2 100644 --- a/shiny/reactive/_reactives.py +++ b/shiny/reactive/_reactives.py @@ -547,7 +547,7 @@ def on_invalidate_cb() -> None: def _continue() -> None: ctx.add_pending_flush(self._priority) if self._session: - self._session._send_message_sync({"busy": "busy"}) + self._session._increment_busy_count() if self._suspended: self._on_resume = _continue @@ -558,7 +558,7 @@ async def on_flush_cb() -> None: if not self._destroyed: await self._run() if self._session: - self._session._send_message_sync({"busy": "idle"}) + self._session._decrement_busy_count() ctx.on_invalidate(on_invalidate_cb) ctx.on_flush(on_flush_cb) diff --git a/shiny/session/_session.py b/shiny/session/_session.py index 4be3962df..ff3880d76 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -471,6 +471,12 @@ def set_message_handler( namespaced when used with a session proxy. """ + @abstractmethod + def _increment_busy_count(self) -> None: ... + + @abstractmethod + def _decrement_busy_count(self) -> None: ... + # ====================================================================================== # AppSession @@ -493,6 +499,7 @@ def __init__( self.id: str = id self._conn: Connection = conn self._debug: bool = debug + self._busy_count: int = 0 self._message_handlers: dict[ str, tuple[Callable[..., Awaitable[Jsonifiable]], Session], @@ -785,11 +792,11 @@ async def uploadEnd(job_id: str, input_id: str) -> None: async def _handle_request( self, request: Request, action: str, subpath: Optional[str] ) -> ASGIApp: - self._send_message_sync({"busy": "busy"}) + self._increment_busy_count() try: return await self._handle_request_impl(request, action, subpath) finally: - self._send_message_sync({"busy": "idle"}) + self._decrement_busy_count() async def _handle_request_impl( self, request: Request, action: str, subpath: Optional[str] @@ -1011,6 +1018,16 @@ async def _flush(self) -> None: with session_context(self): await self._flushed_callbacks.invoke() + def _increment_busy_count(self) -> None: + self._busy_count += 1 + if self._busy_count == 1: + self._send_message_sync({"busy": "busy"}) + + def _decrement_busy_count(self) -> None: + self._busy_count -= 1 + if self._busy_count == 0: + self._send_message_sync({"busy": "idle"}) + # ========================================================================== # On session ended # ========================================================================== @@ -1191,6 +1208,12 @@ def _send_progress(self, type: str, message: object) -> None: async def send_custom_message(self, type: str, message: dict[str, object]) -> None: await self._parent.send_custom_message(type, message) + def _increment_busy_count(self) -> None: + self._parent._increment_busy_count() + + def _decrement_busy_count(self) -> None: + self._parent._decrement_busy_count() + def set_message_handler( self, name: str, diff --git a/tests/playwright/examples/example_apps.py b/tests/playwright/examples/example_apps.py index 296c64f31..a5f0b54f0 100644 --- a/tests/playwright/examples/example_apps.py +++ b/tests/playwright/examples/example_apps.py @@ -33,7 +33,7 @@ def get_apps(path: str) -> typing.List[str]: return app_paths -app_idle_wait = {"duration": 300, "timeout": 5 * 1000} +app_idle_wait = {"duration": 300, "timeout": 30 * 1000} app_hard_wait: typing.Dict[str, int] = { "examples/brownian": 250, "examples/ui-func": 250, diff --git a/tests/pytest/test_poll.py b/tests/pytest/test_poll.py index 46b5b298c..1b876fae8 100644 --- a/tests/pytest/test_poll.py +++ b/tests/pytest/test_poll.py @@ -39,6 +39,12 @@ def on_ended(self, fn: Callable[[], None]) -> Callable[[], None]: def _send_message_sync(self, message: Dict[str, object]) -> None: pass + def _increment_busy_count(self) -> None: + pass + + def _decrement_busy_count(self) -> None: + pass + async def __aenter__(self): self._session_context.__enter__()