From da1bc2a06167bd9ebde11cb3d495e7d7d418c87e Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Mon, 12 Feb 2024 15:52:59 -0400 Subject: [PATCH 01/11] Resolve Flake8 error 704 multiple statements on one line (def) --- shiny/render/_dataframe.py | 2 +- shiny/session/_session.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/shiny/render/_dataframe.py b/shiny/render/_dataframe.py index a24996c77..b8a6e7537 100644 --- a/shiny/render/_dataframe.py +++ b/shiny/render/_dataframe.py @@ -18,7 +18,7 @@ class AbstractTabularData(abc.ABC): @abc.abstractmethod def to_payload(self) -> Jsonifiable: - ... + pass @add_example(ex_dir="../api-examples/data_frame") diff --git a/shiny/session/_session.py b/shiny/session/_session.py index d10e749fa..02d9d8cc3 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -608,22 +608,26 @@ def _send_remove_ui(self, selector: str, multiple: bool) -> None: @overload def _send_progress( self, type: Literal["binding"], message: BindingProgressMessage - ) -> None: ... + ) -> None: + pass @overload def _send_progress( self, type: Literal["open"], message: OpenProgressMessage - ) -> None: ... + ) -> None: + pass @overload def _send_progress( self, type: Literal["close"], message: CloseProgressMessage - ) -> None: ... + ) -> None: + pass @overload def _send_progress( self, type: Literal["update"], message: UpdateProgressMessage - ) -> None: ... + ) -> None: + pass def _send_progress(self, type: str, message: object) -> None: msg: dict[str, object] = {"progress": {"type": type, "message": message}} @@ -1033,7 +1037,8 @@ def __init__( self._suspend_when_hidden = suspend_when_hidden @overload - def __call__(self, renderer: RendererT) -> RendererT: ... + def __call__(self, renderer: RendererT) -> RendererT: + pass @overload def __call__( @@ -1042,7 +1047,8 @@ def __call__( id: Optional[str] = None, suspend_when_hidden: bool = True, priority: int = 0, - ) -> Callable[[RendererT], RendererT]: ... + ) -> Callable[[RendererT], RendererT]: + pass def __call__( self, From e9ae02f3eb216cd9b5a66165ed41a4008ecd6fb8 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Mon, 12 Feb 2024 15:53:29 -0400 Subject: [PATCH 02/11] Add selected_rows method to dataframe class --- examples/dataframe/app.py | 6 ++--- shiny/api-examples/data_frame/app-core.py | 4 +-- shiny/api-examples/data_frame/app-express.py | 7 +++--- shiny/render/_dataframe.py | 25 ++++++++++++++++--- shiny/session/_session.py | 6 ++--- tests/playwright/deploys/plotly/app.py | 2 +- .../shiny/bugs/0676-row-selection/app.py | 4 +-- 7 files changed, 37 insertions(+), 17 deletions(-) diff --git a/examples/dataframe/app.py b/examples/dataframe/app.py index b53be4f78..fc1554beb 100644 --- a/examples/dataframe/app.py +++ b/examples/dataframe/app.py @@ -93,12 +93,12 @@ def handle_edit(): @render.text def detail(): if ( - input.grid_selected_rows() is not None - and len(input.grid_selected_rows()) > 0 + grid.input_selected_rows() is not None + and len(grid.input_selected_rows()) > 0 ): # "split", "records", "index", "columns", "values", "table" - return df().iloc[list(input.grid_selected_rows())] + return df().iloc[list(grid.input_selected_rows())] app = App(app_ui, server) diff --git a/shiny/api-examples/data_frame/app-core.py b/shiny/api-examples/data_frame/app-core.py index b2b0d1d76..f039d0be4 100644 --- a/shiny/api-examples/data_frame/app-core.py +++ b/shiny/api-examples/data_frame/app-core.py @@ -44,9 +44,9 @@ def summary_data(): @reactive.calc def filtered_df(): - # input.summary_data_selected_rows() is a tuple, so we must convert it to list, + # summary_data.selected_rows() is a tuple, so we must convert it to list, # as that's what Pandas requires for indexing. - selected_idx = list(req(input.summary_data_selected_rows())) + selected_idx = list(req(summary_data.input_selected_rows())) countries = summary_df.iloc[selected_idx]["country"] # Filter data for selected countries return df[df["country"].isin(countries)] diff --git a/shiny/api-examples/data_frame/app-express.py b/shiny/api-examples/data_frame/app-express.py index 638590cb8..e269ae0f5 100644 --- a/shiny/api-examples/data_frame/app-express.py +++ b/shiny/api-examples/data_frame/app-express.py @@ -3,7 +3,7 @@ from shinywidgets import render_widget from shiny import reactive, req -from shiny.express import input, render, ui +from shiny.express import render, ui # Load the Gapminder dataset df = px.data.gapminder() @@ -66,9 +66,10 @@ def country_detail_percap(): @reactive.calc def filtered_df(): - # input.summary_data_selected_rows() is a tuple, so we must convert it to list, + # summary_data.input_selected_rows() is a tuple, so we must convert it to list, # as that's what Pandas requires for indexing. - selected_idx = list(req(input.summary_data_selected_rows())) + + selected_idx = list(req(summary_data.input_selected_rows())) countries = summary_df.iloc[selected_idx]["country"] # Filter data for selected countries return df[df["country"].isin(countries)] diff --git a/shiny/render/_dataframe.py b/shiny/render/_dataframe.py index b8a6e7537..7b8ec2be1 100644 --- a/shiny/render/_dataframe.py +++ b/shiny/render/_dataframe.py @@ -2,12 +2,22 @@ import abc import json -from typing import TYPE_CHECKING, Any, Literal, Protocol, Union, cast, runtime_checkable +from typing import ( + TYPE_CHECKING, + Any, + Literal, + Optional, + Protocol, + Union, + cast, + runtime_checkable, +) from htmltools import Tag from .. import ui from .._docstring import add_example, no_example +from ..session._utils import require_active_session from ._dataframe_unsafe import serialize_numpy_dtypes from .renderer import Jsonifiable, Renderer @@ -238,7 +248,7 @@ class data_frame(Renderer[DataFrameResult]): Row selection ------------- When using the row selection feature, you can access the selected rows by using the - `input._selected_rows()` function, where `` is the `id` of the + `.input_selected_rows()` method, where `` is the `id` of the :func:`~shiny.ui.output_data_frame`. The value returned will be `None` if no rows are selected, or a tuple of integers representing the indices of the selected rows. To filter a pandas data frame down to the selected rows, use @@ -271,12 +281,21 @@ async def transform(self, value: DataFrameResult) -> Jsonifiable: ) return value.to_payload() + def input_selected_rows(self) -> Optional[tuple[int]]: + """ + When `row_selection_mode` is set to "single" or "multiple" this will return + a tuple of integers representing the rows selected by a user. + """ + + active_session = require_active_session(None) + return active_session.input[self.output_id + "_selected_rows"]() + @runtime_checkable class PandasCompatible(Protocol): # Signature doesn't matter, runtime_checkable won't look at it anyway def to_pandas(self) -> object: - ... + pass def cast_to_pandas(x: object, error_message_begin: str) -> object: diff --git a/shiny/session/_session.py b/shiny/session/_session.py index 02d9d8cc3..9a2a26753 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -214,9 +214,9 @@ def __init__( self._outbound_message_queues = OutBoundMessageQueues() - self._message_handlers: dict[str, Callable[..., Awaitable[object]]] = ( - self._create_message_handlers() - ) + self._message_handlers: dict[ + str, Callable[..., Awaitable[object]] + ] = self._create_message_handlers() self._file_upload_manager: FileUploadManager = FileUploadManager() self._on_ended_callbacks = _utils.AsyncCallbacks() self._has_run_session_end_tasks: bool = False diff --git a/tests/playwright/deploys/plotly/app.py b/tests/playwright/deploys/plotly/app.py index 5f93ea463..32702a644 100644 --- a/tests/playwright/deploys/plotly/app.py +++ b/tests/playwright/deploys/plotly/app.py @@ -63,7 +63,7 @@ def summary_data(): def filtered_df(): # input.summary_data_selected_rows() is a tuple, so we must convert it to list, # as that's what Pandas requires for indexing. - selected_idx = list(req(input.summary_data_selected_rows())) + selected_idx = list(req(summary_data.input_selected_rows())) countries = summary_df.iloc[selected_idx]["country"] # Filter data for selected countries return df[df["country"].isin(countries)] diff --git a/tests/playwright/shiny/bugs/0676-row-selection/app.py b/tests/playwright/shiny/bugs/0676-row-selection/app.py index ab6c11255..7dc9440a2 100644 --- a/tests/playwright/shiny/bugs/0676-row-selection/app.py +++ b/tests/playwright/shiny/bugs/0676-row-selection/app.py @@ -34,8 +34,8 @@ def grid(): @render.table def detail(): if ( - input.grid_selected_rows() is not None - and len(input.grid_selected_rows()) > 0 + grid.input_selected_rows() is not None + and len(grid.input_selected_rows()) > 0 ): # "split", "records", "index", "columns", "values", "table" return df.iloc[list(input.grid_selected_rows())] From 7f4d563e20096a7600031e168cc6130dde13c4bd Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Tue, 13 Feb 2024 09:30:03 -0400 Subject: [PATCH 03/11] Apply suggestions from code review Co-authored-by: Barret Schloerke --- shiny/api-examples/data_frame/app-core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shiny/api-examples/data_frame/app-core.py b/shiny/api-examples/data_frame/app-core.py index f039d0be4..d7e762ec4 100644 --- a/shiny/api-examples/data_frame/app-core.py +++ b/shiny/api-examples/data_frame/app-core.py @@ -44,6 +44,8 @@ def summary_data(): @reactive.calc def filtered_df(): + req(summary_data.input_selected_rows()) + # summary_data.selected_rows() is a tuple, so we must convert it to list, # as that's what Pandas requires for indexing. selected_idx = list(req(summary_data.input_selected_rows())) From f8d2b9cd242cd9e21d15b4a711c1937d72875618 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Tue, 13 Feb 2024 09:30:13 -0400 Subject: [PATCH 04/11] Apply suggestions from code review Co-authored-by: Barret Schloerke --- shiny/render/_dataframe.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shiny/render/_dataframe.py b/shiny/render/_dataframe.py index 7b8ec2be1..334e915c0 100644 --- a/shiny/render/_dataframe.py +++ b/shiny/render/_dataframe.py @@ -248,6 +248,7 @@ class data_frame(Renderer[DataFrameResult]): Row selection ------------- When using the row selection feature, you can access the selected rows by using the + `.input_selected_rows()` method, where `` is the render function name that corresponds with the `id=` used in :func:`~shiny.ui.outout_data_frame`. Internally, this method retrieves the selected row value from session's `input._selected_rows()` value. The value returned will be `None` if no rows `.input_selected_rows()` method, where `` is the `id` of the :func:`~shiny.ui.output_data_frame`. The value returned will be `None` if no rows are selected, or a tuple of integers representing the indices of the selected rows. From 2de5cfc386ba0ea0e076edec0443cc2dc226aa15 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Tue, 13 Feb 2024 09:30:25 -0400 Subject: [PATCH 05/11] Apply suggestions from code review Co-authored-by: Barret Schloerke --- shiny/render/_dataframe.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/shiny/render/_dataframe.py b/shiny/render/_dataframe.py index 334e915c0..50e0e392a 100644 --- a/shiny/render/_dataframe.py +++ b/shiny/render/_dataframe.py @@ -249,8 +249,6 @@ class data_frame(Renderer[DataFrameResult]): ------------- When using the row selection feature, you can access the selected rows by using the `.input_selected_rows()` method, where `` is the render function name that corresponds with the `id=` used in :func:`~shiny.ui.outout_data_frame`. Internally, this method retrieves the selected row value from session's `input._selected_rows()` value. The value returned will be `None` if no rows - `.input_selected_rows()` method, where `` is the `id` of the - :func:`~shiny.ui.output_data_frame`. The value returned will be `None` if no rows are selected, or a tuple of integers representing the indices of the selected rows. To filter a pandas data frame down to the selected rows, use `df.iloc[list(input._selected_rows())]`. From 487a60a9db79ab1bdb07eb1f5b89e343496217c5 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Tue, 13 Feb 2024 09:30:33 -0400 Subject: [PATCH 06/11] Apply suggestions from code review Co-authored-by: Barret Schloerke --- shiny/session/_session.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shiny/session/_session.py b/shiny/session/_session.py index 9a2a26753..c8647345e 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -215,7 +215,8 @@ def __init__( self._outbound_message_queues = OutBoundMessageQueues() self._message_handlers: dict[ - str, Callable[..., Awaitable[object]] + str, + Callable[..., Awaitable[object]], ] = self._create_message_handlers() self._file_upload_manager: FileUploadManager = FileUploadManager() self._on_ended_callbacks = _utils.AsyncCallbacks() From 6ecfc99b01d4dc6a44ed048c2fa632d5a1a86445 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Tue, 13 Feb 2024 09:30:42 -0400 Subject: [PATCH 07/11] Apply suggestions from code review Co-authored-by: Barret Schloerke --- tests/playwright/deploys/plotly/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/playwright/deploys/plotly/app.py b/tests/playwright/deploys/plotly/app.py index 32702a644..ef2bd94de 100644 --- a/tests/playwright/deploys/plotly/app.py +++ b/tests/playwright/deploys/plotly/app.py @@ -61,6 +61,8 @@ def summary_data(): @reactive.Calc def filtered_df(): + req(summary_data.input_selected_rows()) + # input.summary_data_selected_rows() is a tuple, so we must convert it to list, # as that's what Pandas requires for indexing. selected_idx = list(req(summary_data.input_selected_rows())) From cd070267bc7727b0a91fb96cd514c862d0ae0097 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Tue, 13 Feb 2024 09:30:51 -0400 Subject: [PATCH 08/11] Apply suggestions from code review Co-authored-by: Barret Schloerke --- tests/playwright/deploys/plotly/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/playwright/deploys/plotly/app.py b/tests/playwright/deploys/plotly/app.py index ef2bd94de..f3b8b78e5 100644 --- a/tests/playwright/deploys/plotly/app.py +++ b/tests/playwright/deploys/plotly/app.py @@ -65,7 +65,7 @@ def filtered_df(): # input.summary_data_selected_rows() is a tuple, so we must convert it to list, # as that's what Pandas requires for indexing. - selected_idx = list(req(summary_data.input_selected_rows())) + selected_idx = list(summary_data.input_selected_rows()) countries = summary_df.iloc[selected_idx]["country"] # Filter data for selected countries return df[df["country"].isin(countries)] From 4bba1f2dda95caa4d58dd136104f2fc7c965e4b5 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Tue, 13 Feb 2024 09:32:50 -0400 Subject: [PATCH 09/11] Fix Flake8 issue --- tests/playwright/shiny/bugs/0676-row-selection/app.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/playwright/shiny/bugs/0676-row-selection/app.py b/tests/playwright/shiny/bugs/0676-row-selection/app.py index 7dc9440a2..91fbebc97 100644 --- a/tests/playwright/shiny/bugs/0676-row-selection/app.py +++ b/tests/playwright/shiny/bugs/0676-row-selection/app.py @@ -33,12 +33,9 @@ def grid(): @render.table def detail(): - if ( - grid.input_selected_rows() is not None - and len(grid.input_selected_rows()) > 0 - ): - # "split", "records", "index", "columns", "values", "table" - return df.iloc[list(input.grid_selected_rows())] + selected_rows = grid.input_selected_rows() or () + if len(selected_rows) > 0: + return df.iloc[list(selected_rows)] @render.text def debug(): From 9d98231598feb4acd405943e2cf37c7119223667 Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Wed, 14 Feb 2024 13:30:46 -0400 Subject: [PATCH 10/11] Apply suggestions from code review Co-authored-by: Barret Schloerke --- examples/dataframe/app.py | 8 +++----- shiny/api-examples/data_frame/app-core.py | 2 +- shiny/api-examples/data_frame/app-express.py | 4 +++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/dataframe/app.py b/examples/dataframe/app.py index fc1554beb..18db9f227 100644 --- a/examples/dataframe/app.py +++ b/examples/dataframe/app.py @@ -92,12 +92,10 @@ def handle_edit(): @render.text def detail(): - if ( - grid.input_selected_rows() is not None - and len(grid.input_selected_rows()) > 0 - ): + selected_rows = grid.input_selected_rows() or () + if len(selected_rows) > 0: # "split", "records", "index", "columns", "values", "table" - + return df.iloc[list(selected_rows)] return df().iloc[list(grid.input_selected_rows())] diff --git a/shiny/api-examples/data_frame/app-core.py b/shiny/api-examples/data_frame/app-core.py index d7e762ec4..83843ed57 100644 --- a/shiny/api-examples/data_frame/app-core.py +++ b/shiny/api-examples/data_frame/app-core.py @@ -48,7 +48,7 @@ def filtered_df(): # summary_data.selected_rows() is a tuple, so we must convert it to list, # as that's what Pandas requires for indexing. - selected_idx = list(req(summary_data.input_selected_rows())) + selected_idx = list(summary_data.input_selected_rows()) countries = summary_df.iloc[selected_idx]["country"] # Filter data for selected countries return df[df["country"].isin(countries)] diff --git a/shiny/api-examples/data_frame/app-express.py b/shiny/api-examples/data_frame/app-express.py index e269ae0f5..18064e7ee 100644 --- a/shiny/api-examples/data_frame/app-express.py +++ b/shiny/api-examples/data_frame/app-express.py @@ -66,10 +66,12 @@ def country_detail_percap(): @reactive.calc def filtered_df(): + req(summary_data.input_selected_rows()) + # summary_data.input_selected_rows() is a tuple, so we must convert it to list, # as that's what Pandas requires for indexing. - selected_idx = list(req(summary_data.input_selected_rows())) + selected_idx = list(summary_data.input_selected_rows()) countries = summary_df.iloc[selected_idx]["country"] # Filter data for selected countries return df[df["country"].isin(countries)] From 831677f11368b686c500b37e1dea666332b07cbf Mon Sep 17 00:00:00 2001 From: Gordon Shotwell Date: Wed, 14 Feb 2024 13:31:56 -0400 Subject: [PATCH 11/11] Further code review comments --- examples/dataframe/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/dataframe/app.py b/examples/dataframe/app.py index 18db9f227..09b694827 100644 --- a/examples/dataframe/app.py +++ b/examples/dataframe/app.py @@ -95,7 +95,6 @@ def detail(): selected_rows = grid.input_selected_rows() or () if len(selected_rows) > 0: # "split", "records", "index", "columns", "values", "table" - return df.iloc[list(selected_rows)] return df().iloc[list(grid.input_selected_rows())]