Skip to content

Commit df9f9bd

Browse files
committed
Clean up bookmarking classes to reduce abstract methods only required for a single class
1 parent 8b982af commit df9f9bd

File tree

4 files changed

+53
-142
lines changed

4 files changed

+53
-142
lines changed

shiny/bookmark/_bookmark.py

Lines changed: 49 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import warnings
44
from abc import ABC, abstractmethod
55
from pathlib import Path
6-
from typing import TYPE_CHECKING, Awaitable, Callable, Literal, NoReturn
6+
from typing import TYPE_CHECKING, Awaitable, Callable, Literal
77

88
from .._utils import AsyncCallbacks, CancelCallback, wrap_async
99
from ._button import BOOKMARK_ID
@@ -122,26 +122,6 @@ def _restore_context(self) -> RestoreContext | None:
122122
"""
123123
...
124124

125-
@abstractmethod
126-
def _set_restore_context(self, restore_context: RestoreContext):
127-
"""
128-
Set the session's RestoreContext object.
129-
130-
This should only be done within the `init` websocket message.
131-
"""
132-
...
133-
134-
def _get_bookmark_exclude(self) -> list[str]:
135-
"""
136-
Get the list of inputs excluded from being bookmarked.
137-
"""
138-
139-
scoped_excludes: list[str] = []
140-
for proxy_exclude_fn in self._on_get_exclude:
141-
scoped_excludes.extend(proxy_exclude_fn())
142-
# Remove duplicates
143-
return list(set([*self.exclude, *scoped_excludes]))
144-
145125
# # TODO: Barret - Implement this?!?
146126
# @abstractmethod
147127
# async def get_url(self) -> str:
@@ -156,16 +136,6 @@ def _get_bookmark_exclude(self) -> list[str]:
156136

157137
# await session.insert_ui(modal_with_url(url))
158138

159-
@abstractmethod
160-
def _create_effects(self) -> None:
161-
"""
162-
Create the effects for the bookmarking system.
163-
164-
This method should be called when the session is created after the initial inputs have been set.
165-
"""
166-
...
167-
168-
@abstractmethod
169139
def on_bookmark(
170140
self,
171141
callback: (
@@ -185,9 +155,8 @@ def on_bookmark(
185155
This method should accept a single argument, which is a
186156
:class:`~shiny.bookmark._bookmark.ShinySaveState` object.
187157
"""
188-
...
158+
return self._on_bookmark_callbacks.register(wrap_async(callback))
189159

190-
@abstractmethod
191160
def on_bookmarked(
192161
self,
193162
callback: Callable[[str], None] | Callable[[str], Awaitable[None]],
@@ -204,7 +173,33 @@ def on_bookmarked(
204173
The callback function to call when the session is bookmarked.
205174
This method should accept a single argument, the string representing the query parameter component of the URL.
206175
"""
207-
...
176+
return self._on_bookmarked_callbacks.register(wrap_async(callback))
177+
178+
def on_restore(
179+
self,
180+
callback: (
181+
Callable[[RestoreState], None] | Callable[[RestoreState], Awaitable[None]]
182+
),
183+
) -> CancelCallback:
184+
"""
185+
Registers a function that will be called just before restoring state.
186+
187+
This callback will be executed **before** the bookmark state is restored.
188+
"""
189+
return self._on_restore_callbacks.register(wrap_async(callback))
190+
191+
def on_restored(
192+
self,
193+
callback: (
194+
Callable[[RestoreState], None] | Callable[[RestoreState], Awaitable[None]]
195+
),
196+
) -> CancelCallback:
197+
"""
198+
Registers a function that will be called just after restoring state.
199+
200+
This callback will be executed **after** the bookmark state is restored.
201+
"""
202+
return self._on_restored_callbacks.register(wrap_async(callback))
208203

209204
@abstractmethod
210205
async def update_query_string(
@@ -237,34 +232,6 @@ async def do_bookmark(self) -> None:
237232
"""
238233
...
239234

240-
@abstractmethod
241-
def on_restore(
242-
self,
243-
callback: (
244-
Callable[[RestoreState], None] | Callable[[RestoreState], Awaitable[None]]
245-
),
246-
) -> CancelCallback:
247-
"""
248-
Registers a function that will be called just before restoring state.
249-
250-
This callback will be executed **before** the bookmark state is restored.
251-
"""
252-
...
253-
254-
@abstractmethod
255-
def on_restored(
256-
self,
257-
callback: (
258-
Callable[[RestoreState], None] | Callable[[RestoreState], Awaitable[None]]
259-
),
260-
) -> CancelCallback:
261-
"""
262-
Registers a function that will be called just after restoring state.
263-
264-
This callback will be executed **after** the bookmark state is restored.
265-
"""
266-
...
267-
268235

269236
class BookmarkApp(Bookmark):
270237
_session: AppSession
@@ -288,25 +255,19 @@ def __init__(self, session: AppSession):
288255
# Making this a read only property as app authors will not be able to change how the session is restored as the server function will run after the session has been restored.
289256
@property
290257
def store(self) -> BookmarkStore:
291-
"""
292-
App's bookmark store value
293-
294-
Possible values:
295-
* `"url"`: Save / reload the bookmark state in the URL.
296-
* `"server"`: Save / reload the bookmark state on the server.
297-
* `"disable"` (default): Bookmarking is diabled.
298-
"""
299258

300259
return self._session.app.bookmark_store
301260

302261
@property
303262
def _restore_context(self) -> RestoreContext | None:
304-
"""
305-
A read-only value of the session's RestoreContext object.
306-
"""
307263
return self._restore_context_value
308264

309265
def _set_restore_context(self, restore_context: RestoreContext):
266+
"""
267+
Set the session's RestoreContext object.
268+
269+
This should only be done within the `init` websocket message.
270+
"""
310271
self._restore_context_value = restore_context
311272

312273
def _create_effects(self) -> None:
@@ -448,6 +409,17 @@ async def update_query_string(
448409
}
449410
)
450411

412+
def _get_bookmark_exclude(self) -> list[str]:
413+
"""
414+
Get the list of inputs excluded from being bookmarked.
415+
"""
416+
417+
scoped_excludes: list[str] = []
418+
for proxy_exclude_fn in self._on_get_exclude:
419+
scoped_excludes.extend(proxy_exclude_fn())
420+
# Remove duplicates
421+
return list(set([*self.exclude, *scoped_excludes]))
422+
451423
async def do_bookmark(self) -> None:
452424

453425
if self.store == "disable":
@@ -552,9 +524,7 @@ def __init__(self, session_proxy: SessionProxy):
552524

553525
# The goal of this method is to save the scope's values. All namespaced inputs
554526
# will already exist within the `root_state`.
555-
@self._session._parent.bookmark.on_bookmark
556-
async def scoped_on_bookmark(root_state: BookmarkState) -> None:
557-
return await self._scoped_on_bookmark(root_state)
527+
self._session._parent.bookmark.on_bookmark(self._scoped_on_bookmark)
558528

559529
from ..session import session_context
560530

@@ -633,32 +603,6 @@ def store(self) -> BookmarkStore:
633603
def _restore_context(self) -> RestoreContext | None:
634604
return self._session._parent.bookmark._restore_context
635605

636-
def _set_restore_context(self, restore_context: RestoreContext) -> NoReturn:
637-
raise NotImplementedError(
638-
"The `RestoreContext` should only be set on the root session object."
639-
)
640-
641-
def _create_effects(self) -> NoReturn:
642-
raise NotImplementedError(
643-
"Please call `._create_effects()` from the root session only."
644-
)
645-
646-
def on_bookmark(
647-
self,
648-
callback: (
649-
Callable[[BookmarkState], None] | Callable[[BookmarkState], Awaitable[None]]
650-
),
651-
/,
652-
) -> CancelCallback:
653-
return self._on_bookmark_callbacks.register(wrap_async(callback))
654-
655-
def on_bookmarked(
656-
self,
657-
callback: Callable[[str], None] | Callable[[str], Awaitable[None]],
658-
/,
659-
) -> CancelCallback:
660-
return self._on_bookmarked_callbacks.register(wrap_async(callback))
661-
662606
async def update_query_string(
663607
self, query_string: str, mode: Literal["replace", "push"] = "replace"
664608
) -> None:
@@ -667,22 +611,6 @@ async def update_query_string(
667611
async def do_bookmark(self) -> None:
668612
await self._session._parent.bookmark.do_bookmark()
669613

670-
def on_restore(
671-
self,
672-
callback: (
673-
Callable[[RestoreState], None] | Callable[[RestoreState], Awaitable[None]]
674-
),
675-
) -> CancelCallback:
676-
return self._on_restore_callbacks.register(wrap_async(callback))
677-
678-
def on_restored(
679-
self,
680-
callback: (
681-
Callable[[RestoreState], None] | Callable[[RestoreState], Awaitable[None]]
682-
),
683-
) -> CancelCallback:
684-
return self._on_restored_callbacks.register(wrap_async(callback))
685-
686614

687615
class BookmarkExpressStub(Bookmark):
688616

@@ -692,24 +620,16 @@ def __init__(self, session: ExpressStubSession) -> None:
692620
from ..express._stub_session import ExpressStubSession
693621

694622
assert isinstance(session, ExpressStubSession)
695-
# self._session = session
696623

697624
@property
698625
def store(self) -> BookmarkStore:
699626
return "disable"
700627

701628
@property
702629
def _restore_context(self) -> RestoreContext | None:
630+
# no-op within ExpressStub
703631
return None
704632

705-
def _set_restore_context(self, restore_context: RestoreContext) -> None:
706-
return None
707-
708-
def _create_effects(self) -> NoReturn:
709-
raise NotImplementedError(
710-
"Please call `._create_effects()` only from a real session object"
711-
)
712-
713633
def on_bookmark(
714634
self,
715635
callback: (
@@ -729,9 +649,11 @@ def on_bookmarked(
729649
async def update_query_string(
730650
self, query_string: str, mode: Literal["replace", "push"] = "replace"
731651
) -> None:
652+
# no-op within ExpressStub
732653
return None
733654

734655
async def do_bookmark(self) -> None:
656+
# no-op within ExpressStub
735657
return None
736658

737659
def on_restore(

shiny/bookmark/_global.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,6 @@
1414
# During App initialization, the save_dir and restore_dir functions are conventionally set
1515
# to read-only on the App.
1616

17-
# The set methods below are used to set the save_dir and restore_dir locations for locations like Connect or SSP.
18-
# Ex:
19-
# ```python
20-
# @shiny.bookmark.set_global_save_dir_fn
21-
# def connect_save_shiny_bookmark(id: str) -> Path:
22-
# path = Path("connect") / id
23-
# path.mkdir(parents=True, exist_ok=True)
24-
# return path
25-
# @shiny.bookmark.set_global_restore_dir_fn
26-
# def connect_restore_shiny_bookmark(id: str) -> Path:
27-
# return Path("connect") / id
28-
# ```
29-
3017

3118
bookmark_save_dir: BookmarkSaveDirFn | None = None
3219
bookmark_restore_dir: BookmarkRestoreDirFn | None = None

shiny/session/_session.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,9 @@ def verify_state(expected_state: ConnectionState) -> None:
638638
verify_state(ConnectionState.Start)
639639

640640
# BOOKMARKS!
641+
if not isinstance(self.bookmark, BookmarkApp):
642+
raise RuntimeError("`.bookmark` must be a BookmarkApp")
643+
641644
if ".clientdata_url_search" in message_obj["data"]:
642645
self.bookmark._set_restore_context(
643646
await RestoreContext.from_query_string(

shiny/ui/_input_check_radio.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from htmltools import Tag, TagChild, css, div, span, tags
1212

1313
from .._docstring import add_example
14+
from ..bookmark import restore_input
1415
from ..module import resolve_id
1516
from ._html_deps_shinyverse import components_dependencies
1617
from ._utils import shiny_input_label
@@ -288,8 +289,6 @@ def input_radio_buttons(
288289
resolved_id = resolve_id(id)
289290
input_label = shiny_input_label(resolved_id, label)
290291

291-
from ..bookmark import restore_input
292-
293292
options = _generate_options(
294293
id=resolved_id,
295294
type="radio",

0 commit comments

Comments
 (0)