Skip to content

Commit 096c31b

Browse files
committed
Code feedback from joe/winston;
1 parent 8b87e09 commit 096c31b

File tree

2 files changed

+120
-120
lines changed

2 files changed

+120
-120
lines changed

shiny/render/_dataframe.py

Lines changed: 70 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,29 @@
3535
DataFrameResult = Union[None, "pd.DataFrame", "DataGrid", "DataTable"]
3636

3737

38-
class OnCellUpdateFn(Protocol):
38+
class CellUpdateFn(Protocol):
3939
async def __call__(
4040
self,
4141
*,
42-
row_index: int,
43-
column_index: int,
44-
value: str,
45-
prev: str,
42+
info: CellUpdateInfo,
43+
# TODO-barret; Remove `**kwargs`
4644
**kwargs: Any, # future proofing
4745
) -> Any: ...
48-
class OnCellUpdateParams(TypedDict):
46+
47+
48+
class CellUpdateInfo(TypedDict):
4949
row_index: int
5050
column_index: int
5151
value: str
5252
prev: str
5353

5454

55-
class OnCellsUpdateFn(Protocol):
55+
class CellsUpdateFn(Protocol):
5656
async def __call__(
5757
self,
5858
*,
59-
update_infos: list[OnCellUpdateParams],
59+
infos: list[CellUpdateInfo],
60+
# TODO-barret; Remove `**kwargs`
6061
**kwargs: Any, # future proofing
6162
) -> Any: ...
6263
@dataclass
@@ -403,8 +404,8 @@ class data_frame(Renderer[DataFrameResult]):
403404
_value: reactive.Value[DataFrameResult | None]
404405
# _data: reactive.Value[pd.DataFrame | None]
405406

406-
handle_cell_update: OnCellUpdateFn
407-
handle_cells_update: OnCellsUpdateFn
407+
handle_cell_update: CellUpdateFn
408+
handle_cells_update: CellsUpdateFn
408409

409410
cell_patches: reactive.Value[list[CellPatch]]
410411

@@ -455,6 +456,7 @@ def _init_reactives(self) -> None:
455456

456457
# Init
457458
self._value: reactive.Value[Union[DataFrameResult, None]] = reactive.Value(None)
459+
self.cell_patches: reactive.Value[list[CellPatch]] = reactive.Value([])
458460

459461
@reactive.calc
460462
def self_data() -> pd.DataFrame:
@@ -514,8 +516,6 @@ def self_data_selected() -> pd.DataFrame:
514516

515517
self.data_selected = self_data_selected
516518

517-
self.cell_patches: reactive.Value[list[CellPatch]] = reactive.Value([])
518-
519519
@reactive.calc
520520
def self_data_patched() -> pd.DataFrame:
521521
data = self.data()
@@ -537,44 +537,46 @@ def _get_session(self) -> Session:
537537
)
538538
return self._session
539539

540-
def on_cell_update(self, fn: OnCellUpdateFn) -> Self:
540+
# TODO-barret; `on` sounds like a registration. Maybe use `set` instead? `set_cell_update_fn`?
541+
def on_cell_update(self, fn: CellUpdateFn) -> Self:
541542
self.handle_cell_update = fn
542543
return self
543544

544-
def on_cells_update(self, fn: OnCellsUpdateFn) -> Self:
545+
def on_cells_update(self, fn: CellsUpdateFn) -> Self:
545546
self.handle_cells_update = fn
546547
return self
547548

548549
def _init_handlers(self) -> None:
549550
async def _on_cell_update_default(
550551
*,
551-
row_index: int,
552-
column_index: int,
553-
value: str,
554-
prev: str,
552+
info: CellUpdateInfo,
553+
# row_index: int,
554+
# column_index: int,
555+
# value: str,
556+
# prev: str,
555557
**kwargs: Any,
556558
) -> str:
557-
return value
559+
return info["value"]
558560

559561
async def _on_cells_update_default(
560562
*,
561-
update_infos: list[OnCellUpdateParams],
563+
infos: list[CellUpdateInfo],
562564
**kwargs: Any,
563565
):
564566
with reactive.isolate():
565567
formatted_values: list[Any] = []
566-
for update_info in update_infos:
567-
row_index = update_info["row_index"]
568-
column_index = update_info["column_index"]
569-
value = update_info["value"]
570-
prev = update_info["prev"]
571-
572-
formatted_value = await self.handle_cell_update(
573-
row_index=row_index,
574-
column_index=column_index,
575-
value=value,
576-
prev=prev,
577-
)
568+
for update_info in infos:
569+
# row_index = update_info["row_index"]
570+
# column_index = update_info["column_index"]
571+
# value = update_info["value"]
572+
# prev = update_info["prev"]
573+
574+
formatted_value = await self.handle_cell_update(info=update_info)
575+
# row_index=row_index,
576+
# column_index=column_index,
577+
# value=value,
578+
# prev=prev,
579+
# )
578580
# TODO-barret; check type here?
579581
# TODO-barret; The return value should be coerced by pandas to the correct type
580582
formatted_values.append(formatted_value)
@@ -587,43 +589,45 @@ async def _on_cells_update_default(
587589

588590
# To be called by session's outputRPC message handler on this data_frame
589591
# Do not change this method unless you update corresponding code in `/js/dataframe/`!!
590-
async def _handle_cells_update(self, update_infos: list[OnCellUpdateParams]):
591-
with session_context(self._get_session()):
592-
with reactive.isolate():
593-
# Make new array to trigger reactive update
594-
patches = [p for p in self.cell_patches()]
592+
# @outputRPC_handler
593+
async def _handle_cells_update(self, update_infos: list[CellUpdateInfo]):
594+
595+
# if len(self.cell_patches()) > 1:
596+
# raise RuntimeError("Barret testing!")
597+
598+
# Make new array to trigger reactive update
599+
patches = [p for p in self.cell_patches()]
595600

596-
# Call on_cells_update
597-
formatted_values = await self.handle_cells_update(
598-
update_infos=update_infos
601+
# Call user's on_cells_update method to retrieve formatted values
602+
# TODO-barret; REname `formatted` to `raw_values`
603+
formatted_values = await self.handle_cells_update(infos=update_infos)
604+
605+
if len(formatted_values) != len(update_infos):
606+
raise ValueError(
607+
f"The return value of {self.output_id}'s `handle_cells_update()` (typically set by `@{self.output_id}.on_cells_update`) must be a list of the same length as the input list of cell updates. Received {len(formatted_values)} items and expected {len(update_infos)}."
608+
)
609+
610+
# Add new patches
611+
for formatted_value, update_info in zip(formatted_values, update_infos):
612+
patches.append(
613+
CellPatch(
614+
row_index=update_info["row_index"],
615+
column_index=update_info["column_index"],
616+
value=formatted_value,
617+
prev=update_info["prev"],
599618
)
619+
)
600620

601-
if len(formatted_values) != len(update_infos):
602-
raise ValueError(
603-
f"The return value of {self.output_id}'s `handle_cells_update()` (typically set by `@{self.output_id}.on_cells_update`) must be a list of the same length as the input list of cell updates. Received {len(formatted_values)} items and expected {len(update_infos)}."
604-
)
605-
606-
# Add new patches
607-
for formatted_value, update_info in zip(formatted_values, update_infos):
608-
patches.append(
609-
CellPatch(
610-
row_index=update_info["row_index"],
611-
column_id=update_info["column_id"],
612-
value=formatted_value,
613-
prev=update_info["prev"],
614-
)
615-
)
616-
617-
# Remove duplicate patches
618-
patch_map: dict[tuple[int, str], CellPatch] = {}
619-
for patch in patches:
620-
patch_map[(patch.row_index, patch.column_id)] = patch
621-
patches = list(patch_map.values())
622-
623-
# Set new patches
624-
self.cell_patches.set(patches)
621+
# Remove duplicate patches
622+
patch_map: dict[tuple[int, int], CellPatch] = {}
623+
for patch in patches:
624+
patch_map[(patch.row_index, patch.column_index)] = patch
625+
patches = list(patch_map.values())
625626

626-
return formatted_values
627+
# Set new patches
628+
self.cell_patches.set(patches)
629+
630+
return formatted_values
627631

628632
def auto_output_ui(self) -> Tag:
629633
return ui.output_data_frame(id=self.output_id)
@@ -661,11 +665,10 @@ def _set_output_metadata(self, *, output_id: str) -> None:
661665
async def render(self) -> Jsonifiable:
662666
# Reset value
663667
self._value.set(None)
668+
self.cell_patches.set([])
664669

665670
value = await self.fn()
666671
if value is None:
667-
# Quit early
668-
self._value.set(None)
669672
return None
670673

671674
if not isinstance(value, AbstractTabularData):

tests/playwright/shiny/components/data_frame/edit/app.py

Lines changed: 50 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,71 @@
33
import pandas as pd
44
from palmerpenguins import load_penguins_raw # pyright: ignore[reportMissingTypeStubs]
55

6-
from shiny import App, Inputs, reactive, render, ui
6+
from shiny import App, Inputs, Outputs, Session, module, reactive, render, ui
7+
from shiny.render._dataframe import CellUpdateInfo
8+
9+
# TODO-barret; Make an example that uses a dataframe that then updates a higher level reactive, that causes the df to update... which causes the table to render completely
10+
# TODO-barret-future; When "updating" data, try to maintain the scroll, filter info when a new `df` is supplied;
11+
712

813
# Load the dataset
9-
df = reactive.value(load_penguins_raw())
14+
penguins = load_penguins_raw()
15+
16+
df1 = pd.DataFrame(data={"a": [1, 2]})
17+
df1.insert(1, "a", [3, 4], True) # pyright: ignore
18+
19+
df = penguins
20+
# df = df1
21+
22+
23+
# df = reactive.value(df1)df = reactive.value(load_penguins_raw())
24+
25+
MOD_ID = "testing"
26+
27+
28+
@module.ui
29+
def mod_ui():
30+
return ui.TagList(
31+
ui.card(
32+
ui.output_data_frame("summary_data"),
33+
height="400px",
34+
),
35+
)
1036

1137

1238
app_ui = ui.page_fillable(
1339
{"class": "p-3"},
1440
# ui.markdown(
1541
# "**Instructions**: Select one or more countries in the table below to see more information."
1642
# ),
17-
ui.card(
18-
ui.output_data_frame("summary_data"),
19-
height="400px",
20-
),
21-
ui.pre(id="barret"),
22-
ui.tags.script(
23-
"""
24-
// const window_console_log = console.log;
25-
// console.log = function() {
26-
// window_console_log.apply(console, arguments);
27-
// let txt = "log - " + Date.now() + ": ";
28-
// for (let i = 0; i < arguments.length; i++) {
29-
// if (i > 0) txt += ", ";
30-
// txt += arguments[i];
31-
// }
32-
// $("#barret").append(txt + "\\n");
33-
// };
34-
const window_console_trace = console.trace;
35-
console.trace = function() {
36-
window_console_trace.apply(console, arguments);
37-
let txt = "trace - " + Date.now() + ": ";
38-
for (let i = 0; i < arguments.length; i++) {
39-
if (i > 0) txt += ", ";
40-
txt += arguments[i];
41-
}
42-
$("#barret").append(txt + "\\n");
43-
};
44-
const window_console_error = console.error;
45-
console.error = function() {
46-
window_console_error.apply(console, arguments);
47-
let txt = "error - " + Date.now() + ": ";
48-
for (let i = 0; i < arguments.length; i++) {
49-
if (i > 0) txt += ", ";
50-
txt += arguments[i];
51-
}
52-
$("#barret").append(txt + "\\n");
53-
};
54-
// console.log("Hello, World!");
55-
// console.error("Hello, World!");
56-
"""
57-
),
43+
mod_ui(MOD_ID),
5844
)
5945

6046

61-
def server(input: Inputs):
47+
@module.server
48+
def mod_server(input: Inputs, output: Outputs, session: Session):
6249
@render.data_frame
6350
def summary_data():
6451
# return df
65-
return render.DataGrid(df(), mode="edit")
66-
# return render.DataTable(df, row_selection_mode="multiple")
52+
return render.DataGrid(df, mode="edit")
53+
return render.DataTable(df, mode="edit")
6754

6855
@summary_data.on_cell_update
6956
async def handle_edit(
7057
*,
71-
row_index: int,
72-
column_id: str,
73-
value: str,
74-
prev: str,
75-
**kwargs: Any,
58+
info: CellUpdateInfo,
59+
# info: int,
60+
# column_index: int,
61+
# value: str,
62+
# prev: str,
7663
):
77-
return "demo_" + value
64+
return "demo_" + info["value"]
65+
66+
@reactive.effect
67+
def _():
68+
summary_data.data()
69+
summary_data.data_patched()
70+
print(summary_data.data_patched(), summary_data.cell_patches())
7871

7972
# summary_data.data_patched() # ~ patched_df()
8073

@@ -152,4 +145,8 @@ async def handle_edit_simple(
152145
# return df_copy
153146

154147

155-
app = App(app_ui, server)
148+
def server(input: Inputs, output: Outputs, session: Session):
149+
mod_server(MOD_ID)
150+
151+
152+
app = App(app_ui, server, debug=False)

0 commit comments

Comments
 (0)