From 96c9abc8307fa79dceb6b44d6f0f3a235dc52477 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Mon, 7 Apr 2025 13:28:03 -0700 Subject: [PATCH 1/3] add bookmarking to df - part 1 --- shiny/render/_data_frame.py | 53 ++++++++++++ .../bookmark/dataframe/app-express.py | 81 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py diff --git a/shiny/render/_data_frame.py b/shiny/render/_data_frame.py index b70534c1c..a9c776556 100644 --- a/shiny/render/_data_frame.py +++ b/shiny/render/_data_frame.py @@ -562,6 +562,8 @@ def _reset_reactives(self) -> None: self._updated_data.unset() def _init_reactives(self) -> None: + from ..bookmark._restore_state import RestoreState + from ..bookmark._save_state import BookmarkState # Init self._value = reactive.Value(None) @@ -588,6 +590,57 @@ async def _(): # It currently is, as `@reactive.event()` is being used await self._attempt_update_cell_style() + def bookmark_cell_patch_id() -> str: + return f"{self.output_id}--cell_patch_map" + + def bookmark_data_id() -> str: + return f"{self.output_id}--data" + + @self._get_session().bookmark.on_bookmark + def _(state: BookmarkState): + cell_patch_map = self._cell_patch_map() + if cell_patch_map: + cell_patch_id_val = bookmark_cell_patch_id() + if cell_patch_id_val in state.values: + raise RuntimeError( + f"Bookmark state already contains a value for {cell_patch_id_val}. Please use a different ID." + ) + state.values[cell_patch_id_val] = cell_patch_map + + updated_data = self._updated_data() + if updated_data: + # TODO-barret-render.data_frame; Handle restoring updated data + # Related: Restoring Chat UI: https://github.com/posit-dev/py-shiny/issues/1952 + raise RuntimeError( + "Bookmarking an updated data frame is not supported." + ) + + @self._get_session().bookmark.on_restore + def _(state: RestoreState): + if state.input[f"{self.output_id}_cell_selection"]: + self.update_cell_selection( + state.input[f"{self.output_id}_cell_selection"] + ) + if state.input[f"{self.output_id}_column_sort"]: + self.update_sort(state.input[f"{self.output_id}_column_sort"]) + if state.input[f"{self.output_id}_column_filter"]: + self.update_filter(state.input[f"{self.output_id}_column_filter"]) + if state.input[f"{self.output_id}_cell_selection"]: + self.update_cell_selection( + state.input[f"{self.output_id}_cell_selection"] + ) + if state.input[f"{self.output_id}_data_view_rows"]: + + cell_patch_map = state.values.get(bookmark_cell_patch_id(), None) + if cell_patch_map: + self._cell_patch_map.set(cell_patch_map) + self._updated_data.set(self._nw_data_to_original_type(self._nw_data())) + + updated_data = state.values.get(bookmark_data_id(), None) + if updated_data: + # TODO-barret-render.data_frame; Handle restoring updated data + raise RuntimeError("Restoring an updated data frame is not supported.") + def _get_session(self) -> Session: if self._session is None: raise RuntimeError( diff --git a/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py b/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py new file mode 100644 index 000000000..c99f6cc83 --- /dev/null +++ b/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py @@ -0,0 +1,81 @@ +from palmerpenguins import load_penguins +from shiny import reactive +from shiny.express import app_opts, input, render, session, ui + +penguins = load_penguins() + +app_opts(bookmark_store="url") + + +ui.h2("Palmer Penguins") + +ui.h5("Current selection: ", {"class": "pt-2"}) + + +@render.code +def _(): + return penguins_df.cell_selection()["rows"] + + +ui.br() + + +@render.data_frame +def penguins_df(): + return render.DataGrid(penguins, selection_mode="rows") + + +@render.text +def penguins_df_text(): + return f"Selection mode: rows - Selected: {penguins_df.cell_selection()['rows']}" + + +ui.br() + + +@render.data_frame +def penguins_row_df(): + return render.DataGrid(penguins, selection_mode="row") + + +@render.text +def penguins_row_df_text(): + return f"Selection mode: row - Selected: {penguins_row_df.cell_selection()['rows']}" + + +ui.br() + + +@render.data_frame +def penguins_filter_df(): + return render.DataGrid(penguins, filters=True) + + +@render.text +def penguins_filter_df_text(): + return f"Filters enabled - Selected: {penguins_filter_df.cell_selection()['rows']}" + + +ui.br() + + +@render.data_frame +def penguins_editable_df(): + return render.DataGrid(penguins, editable=True) + + +@render.text +def penguins_editable_df_text(): + return f"Editable grid - Selected: {penguins_editable_df.cell_selection()['rows']}" + + +ui.br() + +ui.input_bookmark_button() + +ui.br() + + +@session.bookmark.on_bookmarked +async def _(url: str): + await session.bookmark.update_query_string(url) From 6b012796957e72b69624df7fb1c59a6e9e12c0b2 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Mon, 7 Apr 2025 18:14:02 -0400 Subject: [PATCH 2/3] Small updates --- shiny/render/_data_frame.py | 34 +++++++++---------- .../bookmark/dataframe/app-express.py | 17 +++++----- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/shiny/render/_data_frame.py b/shiny/render/_data_frame.py index a9c776556..4b590224a 100644 --- a/shiny/render/_data_frame.py +++ b/shiny/render/_data_frame.py @@ -607,29 +607,27 @@ def _(state: BookmarkState): ) state.values[cell_patch_id_val] = cell_patch_map - updated_data = self._updated_data() - if updated_data: + if self._updated_data.is_set(): # TODO-barret-render.data_frame; Handle restoring updated data # Related: Restoring Chat UI: https://github.com/posit-dev/py-shiny/issues/1952 raise RuntimeError( - "Bookmarking an updated data frame is not supported." + "Bookmarking a manually set data frame is not supported." ) - @self._get_session().bookmark.on_restore - def _(state: RestoreState): - if state.input[f"{self.output_id}_cell_selection"]: - self.update_cell_selection( - state.input[f"{self.output_id}_cell_selection"] - ) - if state.input[f"{self.output_id}_column_sort"]: - self.update_sort(state.input[f"{self.output_id}_column_sort"]) - if state.input[f"{self.output_id}_column_filter"]: - self.update_filter(state.input[f"{self.output_id}_column_filter"]) - if state.input[f"{self.output_id}_cell_selection"]: - self.update_cell_selection( - state.input[f"{self.output_id}_cell_selection"] - ) - if state.input[f"{self.output_id}_data_view_rows"]: + @self._get_session().bookmark.on_restored + async def _(state: RestoreState): + print("Available inputs:", list(state.input.keys())) + cell_selection_key = f"{self.output_id}_cell_selection" + column_sort_key = f"{self.output_id}_column_sort" + column_filter_key = f"{self.output_id}_column_filter" + if cell_selection_key in state.input: + print("setting cell selection", state.input[cell_selection_key]) + await self.update_cell_selection(state.input[cell_selection_key]) + print("done setting cell selection") + if column_sort_key in state.input: + await self.update_sort(state.input[column_sort_key]) + if column_filter_key in state.input: + await self.update_filter(state.input[column_filter_key]) cell_patch_map = state.values.get(bookmark_cell_patch_id(), None) if cell_patch_map: diff --git a/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py b/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py index c99f6cc83..b08f214c4 100644 --- a/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py +++ b/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py @@ -1,4 +1,5 @@ from palmerpenguins import load_penguins + from shiny import reactive from shiny.express import app_opts, input, render, session, ui @@ -6,6 +7,13 @@ app_opts(bookmark_store="url") +ui.input_bookmark_button() + + +@session.bookmark.on_bookmarked +async def _(url: str): + await session.bookmark.update_query_string(url) + ui.h2("Palmer Penguins") @@ -70,12 +78,3 @@ def penguins_editable_df_text(): ui.br() - -ui.input_bookmark_button() - -ui.br() - - -@session.bookmark.on_bookmarked -async def _(url: str): - await session.bookmark.update_query_string(url) From 83b31f66b27a910ab9ee2ba9ad7f650460cca93a Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Mon, 7 Apr 2025 22:42:14 -0400 Subject: [PATCH 3/3] temp add traceback code --- shiny/bookmark/_bookmark.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shiny/bookmark/_bookmark.py b/shiny/bookmark/_bookmark.py index 28abdb139..933024256 100644 --- a/shiny/bookmark/_bookmark.py +++ b/shiny/bookmark/_bookmark.py @@ -558,6 +558,13 @@ async def do_bookmark(self) -> None: await self.show_bookmark_url_modal(full_url) except Exception as e: + # TODO: REMOVE TEMP CODE w/ TRACEBACKS + import sys + import traceback + + # Starting in Python 3.10 this could be traceback.print_exception(e) + traceback.print_exception(*sys.exc_info()) + msg = f"Error bookmarking state: {e}" from ..ui._notification import notification_show