Skip to content

Commit c3fcac0

Browse files
committed
Merge branch 'main' into add-bookmarking-input-components
2 parents 9a3027c + e9c682b commit c3fcac0

File tree

10 files changed

+329
-530
lines changed

10 files changed

+329
-530
lines changed

shiny/bookmark/_bookmark.py

Lines changed: 236 additions & 400 deletions
Large diffs are not rendered by default.

shiny/bookmark/_save_state.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ def __init__(
4343
self.dir = None # This will be set by external functions.
4444
self.values = {}
4545

46-
self._always_exclude: list[str] = []
47-
4846
async def _call_on_save(self):
4947
# Allow user-supplied save function to do things like add state$values, or
5048
# save data to state dir.

shiny/express/_stub_session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def on_ended(
6666

6767
def make_scope(self, id: Id) -> Session:
6868
ns = self.ns(id)
69-
return SessionProxy(parent=self, ns=ns)
69+
return SessionProxy(root_session=self, ns=ns)
7070

7171
def root_scope(self) -> ExpressStubSession:
7272
return self

shiny/session/_session.py

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,7 @@ def _process_ui(self, ui: TagChild) -> RenderedDeps:
11651165

11661166
def make_scope(self, id: Id) -> Session:
11671167
ns = self.ns(id)
1168-
return SessionProxy(parent=self, ns=ns)
1168+
return SessionProxy(root_session=self, ns=ns)
11691169

11701170
def root_scope(self) -> AppSession:
11711171
return self
@@ -1200,73 +1200,70 @@ class UpdateProgressMessage(TypedDict):
12001200

12011201

12021202
class SessionProxy(Session):
1203-
def __init__(self, parent: Session, ns: ResolvedId) -> None:
1203+
def __init__(self, root_session: Session, ns: ResolvedId) -> None:
12041204
super().__init__()
12051205

1206-
self._parent = parent
1207-
self.app = parent.app
1208-
self.id = parent.id
1206+
self._root_session = root_session
1207+
self.app = root_session.app
1208+
self.id = root_session.id
12091209
self.ns = ns
1210-
self.input = Inputs(values=parent.input._map, ns=ns)
1210+
self.input = Inputs(values=root_session.input._map, ns=ns)
12111211
self.output = Outputs(
12121212
self,
12131213
ns=ns,
1214-
outputs=parent.output._outputs,
1214+
outputs=root_session.output._outputs,
12151215
)
1216-
self._outbound_message_queues = parent._outbound_message_queues
1217-
self._downloads = parent._downloads
1216+
self._outbound_message_queues = root_session._outbound_message_queues
1217+
self._downloads = root_session._downloads
12181218

12191219
self.bookmark = BookmarkProxy(self)
12201220

12211221
def _is_hidden(self, name: str) -> bool:
1222-
return self._parent._is_hidden(name)
1222+
return self._root_session._is_hidden(name)
12231223

12241224
def on_ended(
12251225
self,
12261226
fn: Callable[[], None] | Callable[[], Awaitable[None]],
12271227
) -> Callable[[], None]:
1228-
return self._parent.on_ended(fn)
1228+
return self._root_session.on_ended(fn)
12291229

12301230
def is_stub_session(self) -> bool:
1231-
return self._parent.is_stub_session()
1231+
return self._root_session.is_stub_session()
12321232

12331233
async def close(self, code: int = 1001) -> None:
1234-
await self._parent.close(code)
1234+
await self._root_session.close(code)
12351235

12361236
def make_scope(self, id: str) -> Session:
1237-
return self._parent.make_scope(self.ns(id))
1237+
return self._root_session.make_scope(self.ns(id))
12381238

12391239
def root_scope(self) -> Session:
1240-
res = self
1241-
while isinstance(res, SessionProxy):
1242-
res = res._parent
1243-
return res
1240+
return self._root_session
12441241

12451242
def _process_ui(self, ui: TagChild) -> RenderedDeps:
1246-
return self._parent._process_ui(ui)
1243+
return self._root_session._process_ui(ui)
12471244

12481245
def send_input_message(self, id: str, message: dict[str, object]) -> None:
1249-
self._parent.send_input_message(self.ns(id), message)
1246+
self._root_session.send_input_message(self.ns(id), message)
12501247

12511248
def _send_insert_ui(
12521249
self, selector: str, multiple: bool, where: str, content: RenderedDeps
12531250
) -> None:
1254-
self._parent._send_insert_ui(selector, multiple, where, content)
1251+
self._root_session._send_insert_ui(selector, multiple, where, content)
12551252

12561253
def _send_remove_ui(self, selector: str, multiple: bool) -> None:
1257-
self._parent._send_remove_ui(selector, multiple)
1254+
self._root_session._send_remove_ui(selector, multiple)
12581255

12591256
def _send_progress(self, type: str, message: object) -> None:
1260-
self._parent._send_progress(type, message) # pyright: ignore
1257+
self._root_session._send_progress(type, message) # pyright: ignore
12611258

12621259
async def send_custom_message(self, type: str, message: dict[str, object]) -> None:
1263-
await self._parent.send_custom_message(type, message)
1260+
await self._root_session.send_custom_message(type, message)
12641261

12651262
def _increment_busy_count(self) -> None:
1266-
self._parent._increment_busy_count()
1263+
self._root_session._increment_busy_count()
12671264

12681265
def _decrement_busy_count(self) -> None:
1269-
self._parent._decrement_busy_count()
1266+
self._root_session._decrement_busy_count()
12701267

12711268
def set_message_handler(
12721269
self,
@@ -1283,7 +1280,7 @@ def set_message_handler(
12831280
if _handler_session is None:
12841281
_handler_session = self
12851282

1286-
return self._parent.set_message_handler(
1283+
return self._root_session.set_message_handler(
12871284
self.ns(name),
12881285
handler,
12891286
_handler_session=_handler_session,
@@ -1294,26 +1291,26 @@ def on_flush(
12941291
fn: Callable[[], None] | Callable[[], Awaitable[None]],
12951292
once: bool = True,
12961293
) -> Callable[[], None]:
1297-
return self._parent.on_flush(fn, once)
1294+
return self._root_session.on_flush(fn, once)
12981295

12991296
async def _send_message(self, message: dict[str, object]) -> None:
1300-
await self._parent._send_message(message)
1297+
await self._root_session._send_message(message)
13011298

13021299
def _send_message_sync(self, message: dict[str, object]) -> None:
1303-
self._parent._send_message_sync(message)
1300+
self._root_session._send_message_sync(message)
13041301

13051302
def on_flushed(
13061303
self,
13071304
fn: Callable[[], None] | Callable[[], Awaitable[None]],
13081305
once: bool = True,
13091306
) -> Callable[[], None]:
1310-
return self._parent.on_flushed(fn, once)
1307+
return self._root_session.on_flushed(fn, once)
13111308

13121309
def dynamic_route(self, name: str, handler: DynamicRouteHandler) -> str:
1313-
return self._parent.dynamic_route(self.ns(name), handler)
1310+
return self._root_session.dynamic_route(self.ns(name), handler)
13141311

13151312
async def _unhandled_error(self, e: Exception) -> None:
1316-
await self._parent._unhandled_error(e)
1313+
await self._root_session._unhandled_error(e)
13171314

13181315
def download(
13191316
self,
@@ -1324,7 +1321,7 @@ def download(
13241321
) -> Callable[[DownloadHandler], None]:
13251322
def wrapper(fn: DownloadHandler):
13261323
id_ = self.ns(id or fn.__name__)
1327-
return self._parent.download(
1324+
return self._root_session.download(
13281325
id=id_,
13291326
filename=filename,
13301327
media_type=media_type,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from starlette.requests import Request
2+
3+
from shiny import App, Inputs, Outputs, Session, reactive, ui
4+
5+
6+
def app_ui(request: Request):
7+
return ui.page_fluid(
8+
ui.input_radio_buttons("letter", "Choose a letter", choices=["A", "B", "C"]),
9+
)
10+
11+
12+
def server(input: Inputs, ouput: Outputs, session: Session):
13+
14+
@reactive.effect
15+
@reactive.event(input.letter, ignore_init=True)
16+
async def _():
17+
await session.bookmark()
18+
19+
20+
app = App(app_ui, server, bookmark_store="url")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import re
2+
3+
from playwright.sync_api import Page, expect
4+
5+
from shiny.playwright.controller import InputRadioButtons
6+
from shiny.run import ShinyAppProc
7+
8+
9+
def test_bookmark_modules(page: Page, local_app: ShinyAppProc):
10+
11+
page.goto(local_app.url)
12+
13+
letter = InputRadioButtons(page, "letter")
14+
letter.expect_selected("A")
15+
letter.set("C")
16+
17+
expect(page.locator("div.modal-body > textarea")).to_have_value(
18+
re.compile(r"letter=%22C%22")
19+
)
20+
21+
assert "?" not in page.url

tests/playwright/shiny/bookmark/modules/app-core-recursive.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def mod_btn(idx: int = 1):
2929
width="200px",
3030
),
3131
ui.hr(),
32-
mod_btn(f"sub{idx}", idx - 1) if idx > 0 else None,
32+
mod_btn(f"sub{idx - 1}", idx - 1) if idx > 0 else None,
3333
)
3434

3535

@@ -55,7 +55,6 @@ def value():
5555
@reactive.effect
5656
@reactive.event(input.btn1, input.btn2, input.dyn1, input.dyn2, ignore_init=True)
5757
async def _():
58-
# print("app-Bookmarking!")
5958
await session.bookmark()
6059

6160
session.bookmark.exclude.append("btn2")
@@ -68,7 +67,6 @@ def _(state: BookmarkState) -> None:
6867

6968
@session.bookmark.on_restore
7069
def _(restore_state: RestoreState) -> None:
71-
# print("app-Restore state:", restore_state.values)
7270

7371
if "btn2" in restore_state.values:
7472

@@ -79,14 +77,16 @@ def _(restore_state: RestoreState) -> None:
7977
ui.update_radio_buttons("dyn2", selected=restore_state.values["dyn2"])
8078

8179
if idx > 0:
82-
btn_server(f"sub{idx}", idx - 1)
80+
btn_server(f"sub{idx - 1}", idx - 1)
81+
else:
82+
# Attempt to call on_bookmarked at the very end of the proxy chain
83+
session.bookmark.on_bookmarked(session.bookmark.update_query_string)
8384

8485

85-
k = 2
86+
k = 4
8687

8788

8889
def app_ui(request: Request) -> ui.Tag:
89-
# print("app-Making UI")
9090
return ui.page_fixed(
9191
ui.output_code("bookmark_store"),
9292
"Click Buttons to update bookmark",
@@ -103,23 +103,6 @@ def server(input: Inputs, output: Outputs, session: Session):
103103
def bookmark_store():
104104
return f"{session.bookmark.store}"
105105

106-
@session.bookmark.on_bookmark
107-
async def on_bookmark(state: BookmarkState) -> None:
108-
print(
109-
"app-On Bookmark",
110-
"\nInputs: ",
111-
await state.input._serialize(exclude=state.exclude, state_dir=None),
112-
"\nValues: ",
113-
state.values,
114-
"\n\n",
115-
)
116-
# session.bookmark.update_query_string()
117-
118-
pass
119-
120-
session.bookmark.on_bookmarked(session.bookmark.update_query_string)
121-
# session.bookmark.on_bookmarked(session.bookmark.show_modal)
122-
123106

124107
SHINY_BOOKMARK_STORE: Literal["url", "server"] = os.getenv(
125108
"SHINY_BOOKMARK_STORE", "url"

tests/playwright/shiny/bookmark/modules/app-core.py

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ def mod_btn(idx: int):
3030
ui.output_ui("ui_html"),
3131
ui.output_code("value"),
3232
width="200px",
33-
# fill=True,
34-
# fillable=True,
35-
# height="75px",
3633
),
3734
ui.hr(),
3835
)
@@ -60,7 +57,6 @@ def value():
6057
@reactive.effect
6158
@reactive.event(input.btn1, input.btn2, input.dyn1, input.dyn2, ignore_init=True)
6259
async def _():
63-
# print("app-Bookmarking!")
6460
await session.bookmark()
6561

6662
session.bookmark.exclude.append("btn2")
@@ -73,7 +69,6 @@ def _(state: BookmarkState) -> None:
7369

7470
@session.bookmark.on_restore
7571
def _(restore_state: RestoreState) -> None:
76-
# print("app-Restore state:", restore_state.values)
7772

7873
if "btn2" in restore_state.values:
7974

@@ -84,53 +79,29 @@ def _(restore_state: RestoreState) -> None:
8479
ui.update_radio_buttons("dyn2", selected=restore_state.values["dyn2"])
8580

8681

87-
k = 2
82+
k = 4
8883

8984

9085
def app_ui(request: Request) -> ui.Tag:
91-
# print("app-Making UI")
9286
return ui.page_fixed(
9387
ui.output_code("bookmark_store"),
9488
"Click Button to update bookmark",
95-
# ui.input_action_button("btn", "Button"),
9689
*[mod_btn(f"mod{i}", i) for i in reversed(range(k))],
97-
# ui.input_radio_buttons("btn", "Button", choices=["a", "b", "c"], selected="a"),
98-
# ui.output_code("code"),
99-
# ui.input_bookmark_button(),
10090
)
10191

10292

10393
# Needs access to the restore context to the dynamic UI
10494
def server(input: Inputs, output: Outputs, session: Session):
10595

96+
session.bookmark.on_bookmarked(session.bookmark.update_query_string)
97+
10698
@render.code
10799
def bookmark_store():
108100
return f"{session.bookmark.store}"
109101

110102
for i in reversed(range(k)):
111103
btn_server(f"mod{i}", i)
112104

113-
@session.bookmark.on_bookmark
114-
async def on_bookmark(state: BookmarkState) -> None:
115-
# print(
116-
# "app-On Bookmark",
117-
# "\nInputs: ",
118-
# await state.input._serialize(exclude=state.exclude, state_dir=None),
119-
# "\nValues: ",
120-
# state.values,
121-
# "\n\n",
122-
# )
123-
# session.bookmark.update_query_string()
124-
125-
pass
126-
127-
session.bookmark.on_bookmarked(session.bookmark.update_query_string)
128-
# session.bookmark.on_bookmarked(session.bookmark.show_modal)
129-
130-
# @render.code
131-
# def code():
132-
# return f"{input.btn()}"
133-
134105

135106
SHINY_BOOKMARK_STORE: Literal["url", "server"] = os.getenv(
136107
"SHINY_BOOKMARK_STORE", "url"

0 commit comments

Comments
 (0)