Skip to content

[WIP] feat(bookmarking): Add bookmarking support for data frames #1959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions shiny/bookmark/_bookmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
51 changes: 51 additions & 0 deletions shiny/render/_data_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -588,6 +590,55 @@ 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

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 a manually set data frame is not supported."
)

@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:
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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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.input_bookmark_button()


@session.bookmark.on_bookmarked
async def _(url: str):
await session.bookmark.update_query_string(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()
Loading