Skip to content

Commit b0bf3db

Browse files
committed
Move BookmarkState class to sep file
1 parent 99ecada commit b0bf3db

File tree

3 files changed

+129
-127
lines changed

3 files changed

+129
-127
lines changed

shiny/bookmark/_bookmark_state.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import os
2+
from abc import ABC, abstractmethod
3+
from pathlib import Path
4+
5+
6+
class BookmarkState(ABC):
7+
"""
8+
Class for saving and restoring state to/from disk.
9+
"""
10+
11+
@abstractmethod
12+
async def save_dir(
13+
self,
14+
id: str,
15+
# write_files: Callable[[Path], Awaitable[None]],
16+
) -> Path:
17+
"""
18+
Construct directory for saving state.
19+
20+
Parameters
21+
----------
22+
id
23+
The unique identifier for the state.
24+
25+
Returns
26+
-------
27+
Path
28+
Directory location for saving state. This directory must exist.
29+
"""
30+
# write_files
31+
# A async function that writes the state to a serializable location. The method receives a path object and
32+
...
33+
34+
@abstractmethod
35+
async def load_dir(
36+
self,
37+
id: str,
38+
# read_files: Callable[[Path], Awaitable[None]],
39+
) -> Path:
40+
"""
41+
Construct directory for loading state.
42+
43+
Parameters
44+
----------
45+
id
46+
The unique identifier for the state.
47+
48+
Returns
49+
-------
50+
Path | None
51+
Directory location for loading state. If `None`, state loading will be ignored. If a `Path`, the directory must exist.
52+
"""
53+
...
54+
55+
56+
class BookmarkStateLocal(BookmarkState):
57+
"""
58+
Function wrappers for saving and restoring state to/from disk when running Shiny
59+
locally.
60+
"""
61+
62+
def _local_dir(self, id: str) -> Path:
63+
# Try to save/load from current working directory as we do not know where the
64+
# app file is located
65+
return Path(os.getcwd()) / "shiny_bookmarks" / id
66+
67+
async def save_dir(self, id: str) -> Path:
68+
state_dir = self._local_dir(id)
69+
if not state_dir.exists():
70+
state_dir.mkdir(parents=True)
71+
return state_dir
72+
73+
async def load_dir(self, id: str) -> Path:
74+
return self._local_dir(id)
75+
76+
# async def save(
77+
# self,
78+
# id: str,
79+
# write_files: Callable[[Path], Awaitable[None]],
80+
# ) -> None:
81+
# state_dir = self._local_dir(id)
82+
# if not state_dir.exists():
83+
# state_dir.mkdir(parents=True)
84+
85+
# await write_files(state_dir)
86+
87+
# async def load(
88+
# self,
89+
# id: str,
90+
# read_files: Callable[[Path], Awaitable[None]],
91+
# ) -> None:
92+
# await read_files(self._local_dir(id))
93+
# await read_files(self._local_dir(id))

shiny/bookmark/_save_state.py

Lines changed: 36 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,22 @@
11
# TODO: barret - Set / Load SaveState for Connect. Ex: Connect https://github.com/posit-dev/connect/blob/8de330aec6a61cf21e160b5081d08a1d3d7e8129/R/connect.R#L915
2+
# Might need to have independent save/load functions to register to avoid a class constructor
23

3-
import os
44
import pickle
5-
from abc import ABC, abstractmethod
65
from pathlib import Path
76
from typing import TYPE_CHECKING, Any, Awaitable, Callable
87
from urllib.parse import urlencode as urllib_urlencode
98

109
from .._utils import private_random_id
1110
from ..reactive import isolate
12-
from ._utils import is_hosted, to_json
11+
from ._bookmark_state import BookmarkState
12+
from ._utils import is_hosted, to_json_str
1313

1414
if TYPE_CHECKING:
1515
from .. import Inputs
1616
else:
1717
Inputs = Any
1818

1919

20-
class SaveState(ABC):
21-
"""
22-
Class for saving and restoring state to/from disk.
23-
"""
24-
25-
@abstractmethod
26-
async def save_dir(
27-
self,
28-
id: str,
29-
# write_files: Callable[[Path], Awaitable[None]],
30-
) -> Path:
31-
"""
32-
Construct directory for saving state.
33-
34-
Parameters
35-
----------
36-
id
37-
The unique identifier for the state.
38-
39-
Returns
40-
-------
41-
Path
42-
Directory location for saving state. This directory must exist.
43-
"""
44-
# write_files
45-
# A async function that writes the state to a serializable location. The method receives a path object and
46-
...
47-
48-
@abstractmethod
49-
async def load_dir(
50-
self,
51-
id: str,
52-
# read_files: Callable[[Path], Awaitable[None]],
53-
) -> Path:
54-
"""
55-
Construct directory for loading state.
56-
57-
Parameters
58-
----------
59-
id
60-
The unique identifier for the state.
61-
62-
Returns
63-
-------
64-
Path | None
65-
Directory location for loading state. If `None`, state loading will be ignored. If a `Path`, the directory must exist.
66-
"""
67-
...
68-
69-
70-
class SaveStateLocal(SaveState):
71-
"""
72-
Function wrappers for saving and restoring state to/from disk when running Shiny
73-
locally.
74-
"""
75-
76-
def _local_dir(self, id: str) -> Path:
77-
# Try to save/load from current working directory as we do not know where the
78-
# app file is located
79-
return Path(os.getcwd()) / "shiny_bookmarks" / id
80-
81-
async def save_dir(self, id: str) -> Path:
82-
state_dir = self._local_dir(id)
83-
if not state_dir.exists():
84-
state_dir.mkdir(parents=True)
85-
return state_dir
86-
87-
async def load_dir(self, id: str) -> Path:
88-
return self._local_dir(id)
89-
90-
# async def save(
91-
# self,
92-
# id: str,
93-
# write_files: Callable[[Path], Awaitable[None]],
94-
# ) -> None:
95-
# state_dir = self._local_dir(id)
96-
# if not state_dir.exists():
97-
# state_dir.mkdir(parents=True)
98-
99-
# await write_files(state_dir)
100-
101-
# async def load(
102-
# self,
103-
# id: str,
104-
# read_files: Callable[[Path], Awaitable[None]],
105-
# ) -> None:
106-
# await read_files(self._local_dir(id))
107-
# await read_files(self._local_dir(id))
108-
109-
110-
# #############################################################################
111-
112-
11320
class ShinySaveState:
11421
# session: ?
11522
# * Would get us access to inputs, possibly app dir, registered on save / load classes (?), exclude
@@ -163,37 +70,13 @@ async def _save_state(self) -> str:
16370
"""
16471
id = private_random_id(prefix="", bytes=8)
16572

166-
# TODO: barret move code to single call location
167-
# A function for saving the state object to disk, given a directory to save
168-
# to.
169-
async def save_state_to_dir(state_dir: Path) -> None:
170-
self.dir = state_dir
171-
172-
await self._call_on_save()
173-
174-
self._exclude_bookmark_value()
175-
176-
input_values_json = await self.input._serialize(
177-
exclude=self.exclude,
178-
state_dir=self.dir,
179-
)
180-
assert self.dir is not None
181-
with open(self.dir / "input.pickle", "wb") as f:
182-
pickle.dump(input_values_json, f)
183-
184-
if len(self.values) > 0:
185-
with open(self.dir / "values.pickle", "wb") as f:
186-
pickle.dump(self.values, f)
187-
188-
return
189-
19073
# Pass the saveState function to the save interface function, which will
19174
# invoke saveState after preparing the directory.
19275

19376
# TODO: FUTURE - Get the save interface from the session object?
19477
# Look for a save.interface function. This will be defined by the hosting
19578
# environment if it supports bookmarking.
196-
save_interface_loaded: SaveState | None = None
79+
save_interface_loaded: BookmarkState | None = None
19780

19881
if save_interface_loaded is None:
19982
if is_hosted():
@@ -203,15 +86,33 @@ async def save_state_to_dir(state_dir: Path) -> None:
20386
)
20487
else:
20588
# We're running Shiny locally.
206-
save_interface_loaded = SaveStateLocal()
89+
save_interface_loaded = BookmarkStateLocal()
20790

208-
if not isinstance(save_interface_loaded, SaveState):
91+
if not isinstance(save_interface_loaded, BookmarkState):
20992
raise TypeError(
210-
"The save interface retrieved must be an instance of `shiny.bookmark.SaveState`."
93+
"The save interface retrieved must be an instance of `shiny.bookmark.BookmarkStateLocal`."
21194
)
21295

21396
save_dir = Path(await save_interface_loaded.save_dir(id))
214-
await save_state_to_dir(save_dir)
97+
98+
# Save the state to disk.
99+
self.dir = save_dir
100+
await self._call_on_save()
101+
102+
self._exclude_bookmark_value()
103+
104+
input_values_json = await self.input._serialize(
105+
exclude=self.exclude,
106+
state_dir=self.dir,
107+
)
108+
assert self.dir is not None
109+
with open(self.dir / "input.pickle", "wb") as f:
110+
pickle.dump(input_values_json, f)
111+
112+
if len(self.values) > 0:
113+
with open(self.dir / "values.pickle", "wb") as f:
114+
pickle.dump(self.values, f)
115+
# End save to disk
215116

216117
# No need to encode URI component as it is only ascii characters.
217118
return f"_state_id_={id}"
@@ -243,7 +144,12 @@ async def _encode_state(self) -> str:
243144

244145
# If any input values are present, add them.
245146
if len(input_values_serialized) > 0:
246-
input_qs = urllib_urlencode(to_json(input_values_serialized))
147+
input_qs = urllib_urlencode(
148+
{
149+
key: to_json_str(value)
150+
for key, value in input_values_serialized.items()
151+
}
152+
)
247153

248154
qs_str_parts.append("_inputs_&")
249155
qs_str_parts.append(input_qs)
@@ -252,7 +158,10 @@ async def _encode_state(self) -> str:
252158
if len(qs_str_parts) > 0:
253159
qs_str_parts.append("&")
254160

255-
values_qs = urllib_urlencode(to_json(self.values))
161+
# print("\n\nself.values", self.values)
162+
values_qs = urllib_urlencode(
163+
{key: to_json_str(value) for key, value in self.values.items()}
164+
)
256165

257166
qs_str_parts.append("_values_&")
258167
qs_str_parts.append(values_qs)

shiny/bookmark/_shiny_save_state.py

Whitespace-only changes.

0 commit comments

Comments
 (0)