Skip to content

Commit ec95cdb

Browse files
committed
When serializing inputs for bookmark, pass in single object for future param expansion
1 parent c5f8a48 commit ec95cdb

File tree

3 files changed

+66
-28
lines changed

3 files changed

+66
-28
lines changed

shiny/bookmark/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from ._global import set_global_restore_dir_fn, set_global_save_dir_fn
99
from ._restore_state import RestoreContext, RestoreState, restore_input
1010
from ._save_state import BookmarkState
11+
from ._serializers import Unserializable, serializer_unserializable, SerializeInputInfo
1112

1213
__all__ = (
1314
# _bookmark
@@ -26,4 +27,8 @@
2627
"restore_input",
2728
# _save_state
2829
"BookmarkState",
30+
# _serializers
31+
"SerializeInputInfo",
32+
"Unserializable",
33+
"serializer_unserializable",
2934
)

shiny/bookmark/_serializers.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,49 @@
66

77
from typing_extensions import TypeIs
88

9+
T = TypeVar("T")
10+
911

1012
class Unserializable: ...
1113

1214

13-
T = TypeVar("T")
15+
class SerializeInputInfo:
16+
value: Any
17+
state_dir: Path | None
18+
19+
def __init__(self, *, value: Any, state_dir: Path | None):
20+
self.value = value
21+
self.state_dir = state_dir
1422

1523

1624
def is_unserializable(x: Any) -> TypeIs[Unserializable]:
1725
return isinstance(x, Unserializable)
1826

1927

20-
async def serializer_unserializable(
21-
value: Any = None, state_dir: Path | None = None
22-
) -> Unserializable:
28+
async def serializer_unserializable(info: SerializeInputInfo) -> Unserializable:
2329
return Unserializable()
2430

2531

26-
async def serializer_default(value: T, state_dir: Path | None) -> T:
27-
return value
32+
async def serializer_default(info: SerializeInputInfo) -> Any:
33+
return info.value
2834

2935

3036
# TODO: Barret - Integrate
31-
def serializer_file_input(
32-
value: Any,
33-
state_dir: Path | None,
34-
) -> Any | Unserializable:
35-
if state_dir is None:
37+
def serializer_file_input(info: SerializeInputInfo) -> Any | Unserializable:
38+
if info.state_dir is None:
3639
return Unserializable()
3740

3841
# TODO: Barret - Double check this logic!
3942

4043
# `value` is a data frame. When persisting files, we need to copy the file to
4144
# the persistent dir and then strip the original path before saving.
42-
datapath = Path(value["datapath"])
43-
new_paths = state_dir / datapath.name
45+
datapath = Path(info.value["datapath"])
46+
new_paths = info.state_dir / datapath.name
4447

4548
if new_paths.exists():
4649
new_paths.unlink()
4750
copyfile(datapath, new_paths)
4851

49-
value["datapath"] = new_paths.name
52+
info.value["datapath"] = new_paths.name
5053

51-
return value
54+
return info.value

shiny/session/_session.py

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from .._namespaces import Id, Root
4444
from .._typing_extensions import NotRequired, TypedDict
4545
from .._utils import wrap_async
46-
from ..bookmark import BookmarkApp, BookmarkProxy
46+
from ..bookmark import BookmarkApp, BookmarkProxy, SerializeInputInfo
4747
from ..bookmark._button import BOOKMARK_ID
4848
from ..bookmark._restore_state import RestoreContext
4949
from ..http_staticfiles import FileResponse
@@ -1354,7 +1354,7 @@ class Inputs:
13541354
_serializers: dict[
13551355
str,
13561356
Callable[
1357-
[Any, Path | None],
1357+
[SerializeInputInfo],
13581358
Awaitable[Any | Unserializable],
13591359
],
13601360
]
@@ -1424,14 +1424,8 @@ def set_serializer(
14241424
self,
14251425
id: str,
14261426
fn: (
1427-
Callable[
1428-
[Any, Path | None],
1429-
Awaitable[Any | Unserializable],
1430-
]
1431-
| Callable[
1432-
[Any, Path | None],
1433-
Any | Unserializable,
1434-
]
1427+
Callable[[SerializeInputInfo], Awaitable[Any | Unserializable]]
1428+
| Callable[[SerializeInputInfo], Any | Unserializable]
14351429
),
14361430
) -> None:
14371431
"""
@@ -1442,9 +1436,44 @@ def set_serializer(
14421436
id
14431437
The ID of the input value.
14441438
fn
1445-
A function that takes the input value and returns a modified value. The
1446-
returned value will be used for test snapshots and bookmarking.
1439+
A function that takes the input value information: an object containing
1440+
`value` and `state_dir`. The function should return a JSON serializable
1441+
value. During execution, the function may save information to disk using the
1442+
`.state_dir` attribute. The returned value will be used for bookmarking.
1443+
1444+
Examples
1445+
--------
1446+
1447+
When setting an input, you can add a suffix to the name: e.g. `"x:shiny.date"`
1448+
or `"y:shiny.password"`. This will cause the name to go through the appropriate
1449+
input handler, e.g. `"shiny.date"`.
1450+
1451+
A good use for this implementation is to set the bookmark serializer for this
1452+
object. An example for inputs handled by `"shiny.password"` is given below where
1453+
the bookmark serializer is set to always ignore this value.
1454+
1455+
```python
1456+
@shiny.input_handlers.add("shiny.password")
1457+
def _(value: str, name: ResolvedId, session: Session) -> str:
1458+
# Never bookmark passwords
1459+
session.input.set_serializer(name, serializer_unserializable)
1460+
1461+
return value
1462+
```
1463+
1464+
```python
1465+
def serializer_capitalize(info: SerializeInputInfo) -> str:
1466+
return info.value.upper()
1467+
1468+
@shiny.input_handlers.add("capitalize")
1469+
def _(value: str, name: ResolvedId, session: Session) -> str:
1470+
# Capitalize the value before bookmarking
1471+
session.input.set_serializer(name, serializer_capitalize)
1472+
1473+
return value
1474+
```
14471475
"""
1476+
# test snapshots and
14481477
self._serializers[id] = wrap_async(fn)
14491478

14501479
async def _serialize(
@@ -1476,7 +1505,8 @@ async def _serialize(
14761505

14771506
# Possibly apply custom serialization given the input id
14781507
serializer = self._serializers.get(key, serializer_default)
1479-
serialized_value = await serializer(val, state_dir)
1508+
serializer_info = SerializeInputInfo(value=val, state_dir=state_dir)
1509+
serialized_value = await serializer(serializer_info)
14801510

14811511
# Filter out any values that were marked as unserializable.
14821512
if isinstance(serialized_value, Unserializable):

0 commit comments

Comments
 (0)