Skip to content

Commit e38e21b

Browse files
authored
Merge branch 'main' into datagrid-empty-column
2 parents 6f05721 + 7851978 commit e38e21b

File tree

24 files changed

+660
-39
lines changed

24 files changed

+660
-39
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## New features
1111

12-
* Both `ui.Chat()` and `ui.MarkdownStream()` now support arbirary Shiny UI elements inside of messages. This allows for gathering input from the user (e.g., `ui.input_select()`), displaying of rich output (e.g., `render.DataGrid()`), and more. (#1868)
12+
* Added support for bookmarking Shiny applications. Bookmarking allows users to save the current state of an application and return to it later. This feature is available in both Shiny Core and Shiny Express. (#1870, #1915, #1919, #1920, #1922, #1934, #1938, #1945, #1955)
13+
* To enable bookmarking in Express mode, set `shiny.express.app_opts(bookmark_store=)` during the app's initial construction.
14+
* To enable bookmarking in Core mode, set `shiny.App(bookmark_store=)` when constructing the `app` object.
15+
16+
* Added a new `.enable_bookmarking(client)` method to `ui.Chat()`. This method will attach bookmark hooks to save and restore the chat's messages and client state. (#1951, #1954)
17+
18+
* Both `ui.Chat()` and `ui.MarkdownStream()` now support the inclusion of Shiny UI elements inside of messages. This allows for gathering input from the user (e.g., `ui.input_select()`), displaying of rich output (e.g., `render.DataGrid()`), and more. (#1868)
1319

1420
* Added a new `.message_stream_context()` method to `ui.Chat()`. This context manager is a useful alternative to `.append_message_stream()` when you want to: (1) Nest a stream within another and/or
1521
(2) Overwrite/replace streaming content. (#1906)

pyproject.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ dev = [
120120
"langsmith<0.3",
121121
"openai",
122122
"ollama",
123+
"chatlas>=0.6.1",
123124
"tokenizers",
124125
"aiohttp",
125126
"beautifulsoup4",
@@ -134,9 +135,6 @@ doc = [
134135
"griffe>=1.3.2",
135136
]
136137

137-
[tool.uv.sources]
138-
# https://github.com/encode/uvicorn/pull/2602
139-
uvicorn = { git = "https://github.com/schloerke/uvicorn", branch = "reload-exclude-abs-path" }
140138

141139
[project.urls]
142140
Homepage = "https://github.com/posit-dev/py-shiny"

shiny/_app.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
from ._error import ErrorMiddleware
3131
from ._shinyenv import is_pyodide
3232
from ._utils import guess_mime_type, is_async_callable, sort_keys_length
33-
from .bookmark import _global as bookmark_global_state
3433
from .bookmark._global import as_bookmark_dir_fn
3534
from .bookmark._restore_state import RestoreContext, restore_context
3635
from .bookmark._types import (
@@ -42,6 +41,7 @@
4241
from .html_dependencies import jquery_deps, require_deps, shiny_deps
4342
from .http_staticfiles import FileResponse, StaticFiles
4443
from .session._session import AppSession, Inputs, Outputs, Session, session_context
44+
from .types import MISSING, MISSING_TYPE
4545

4646
T = TypeVar("T")
4747

@@ -115,8 +115,8 @@ def server(input: Inputs, output: Outputs, session: Session):
115115
ui: RenderedHTML | Callable[[Request], Tag | TagList]
116116
server: Callable[[Inputs, Outputs, Session], None]
117117

118-
_bookmark_save_dir_fn: BookmarkSaveDirFn | None
119-
_bookmark_restore_dir_fn: BookmarkRestoreDirFn | None
118+
_bookmark_save_dir_fn: BookmarkSaveDirFn | None | MISSING_TYPE
119+
_bookmark_restore_dir_fn: BookmarkRestoreDirFn | None | MISSING_TYPE
120120
_bookmark_store: BookmarkStore
121121

122122
def __init__(
@@ -498,8 +498,8 @@ def _render_page_from_file(self, file: Path, lib_prefix: str) -> RenderedHTML:
498498
# ==========================================================================
499499

500500
def _init_bookmarking(self, *, bookmark_store: BookmarkStore, ui: Any) -> None:
501-
self._bookmark_save_dir_fn = bookmark_global_state.bookmark_save_dir
502-
self._bookmark_restore_dir_fn = bookmark_global_state.bookmark_restore_dir
501+
self._bookmark_save_dir_fn = MISSING
502+
self._bookmark_restore_dir_fn = MISSING
503503
self._bookmark_store = bookmark_store
504504

505505
if bookmark_store != "disable" and not callable(ui):

shiny/_main.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,32 @@ def run_app(
359359

360360
reload_args: ReloadArgs = {}
361361
if reload:
362+
if shiny_bookmarks_folder_name in reload_excludes:
363+
# Related: https://github.com/posit-dev/py-shiny/pull/1950
364+
#
365+
# Temp hack to work around uvicorn
366+
# https://github.com/encode/uvicorn/pull/2602 which will incorrectly reload
367+
# an app if any matching files in `RELOAD_INCLUDES_DEFAULT` (e.g. `*.png`)
368+
# are uploaded within `shiny_bookmarks` (an excluded relative directory).
369+
#
370+
# By extending `reload_excludes` to ignore everything under the bookmarks folder, we
371+
# can prevent the unexpected reload from happening for the root session. File
372+
# matches are performed via `pathlib.PurePath.match`, which is a right-match and
373+
# only supports `*` glob.
374+
#
375+
# Ignore up to five modules deep. This should cover most cases.
376+
#
377+
# Note: file uploads are always in the root session, so they are always
378+
# stored in the root bookmark dir of `shiny_bookmarks_folder_name / *`.
379+
reload_excludes = [
380+
*reload_excludes,
381+
str(Path(shiny_bookmarks_folder_name) / "*"),
382+
str(Path(shiny_bookmarks_folder_name) / "*" / "*"),
383+
str(Path(shiny_bookmarks_folder_name) / "*" / "*" / "*"),
384+
str(Path(shiny_bookmarks_folder_name) / "*" / "*" / "*" / "*"),
385+
str(Path(shiny_bookmarks_folder_name) / "*" / "*" / "*" / "*" / "*"),
386+
]
387+
362388
reload_args = {
363389
"reload": reload,
364390
# Adding `reload_includes` param while `reload=False` produces an warning

shiny/api-examples/notification_show/app-core.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ def server(input: Inputs, output: Outputs, session: Session):
1414
@reactive.effect
1515
@reactive.event(input.show)
1616
def _():
17-
nonlocal ids
1817
nonlocal n
1918
# Save the ID for removal later
2019
id = ui.notification_show("Message " + str(n), duration=None)
@@ -24,7 +23,6 @@ def _():
2423
@reactive.effect
2524
@reactive.event(input.remove)
2625
def _():
27-
nonlocal ids
2826
if ids:
2927
ui.notification_remove(ids.pop())
3028

shiny/api-examples/notification_show/app-express.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
@reactive.effect
1212
@reactive.event(input.show)
1313
def _():
14-
global ids
1514
global n
1615
# Save the ID for removal later
1716
id = ui.notification_show("Message " + str(n), duration=None)
@@ -22,6 +21,5 @@ def _():
2221
@reactive.effect
2322
@reactive.event(input.remove)
2423
def _():
25-
global ids
2624
if ids:
2725
ui.notification_remove(ids.pop())

shiny/bookmark/_bookmark.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,8 @@ async def _scoped_on_bookmark(self, root_state: BookmarkState) -> None:
610610
)
611611

612612
# Make subdir for scope
613-
# TODO: Barret; Is this for uploaded files?!?
613+
#
614+
# Folder only used by author callbacks. File uploads are handled by root session
614615
if root_state.dir is not None:
615616
scope_subpath = self._ns
616617
scoped_state.dir = Path(root_state.dir) / scope_subpath

shiny/bookmark/_global.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,44 @@
33
from typing import overload
44

55
from .._utils import wrap_async
6+
from ..types import MISSING_TYPE
67
from ._types import (
78
BookmarkDirFn,
89
BookmarkDirFnAsync,
910
BookmarkRestoreDirFn,
1011
BookmarkSaveDirFn,
1112
)
1213

13-
# WARNING! This file contains global state!
14+
# WARNING! This file contains global default values!
15+
16+
1417
# During App initialization, the save_dir and restore_dir functions are conventionally set
1518
# to read-only on the App.
19+
# If nothing is set on the `app` object, the global default bookmark functions are found and used during every save/restore.
20+
_default_bookmark_save_dir_fn: BookmarkSaveDirFn | None = None
21+
_default_bookmark_restore_dir_fn: BookmarkRestoreDirFn | None = None
22+
23+
24+
def get_bookmark_save_dir_fn(
25+
save_dir_fn: BookmarkSaveDirFn | None | MISSING_TYPE,
26+
) -> BookmarkSaveDirFn | None:
27+
if isinstance(save_dir_fn, MISSING_TYPE):
28+
# Allow for default bookmark function to be utilized after app initialization.
29+
# Sometimes the app is created before hooks are registered.
30+
return _default_bookmark_save_dir_fn
31+
else:
32+
return save_dir_fn
1633

1734

18-
bookmark_save_dir: BookmarkSaveDirFn | None = None
19-
bookmark_restore_dir: BookmarkRestoreDirFn | None = None
35+
def get_bookmark_restore_dir_fn(
36+
restore_dir_fn: BookmarkRestoreDirFn | None | MISSING_TYPE,
37+
) -> BookmarkRestoreDirFn | None:
38+
if isinstance(restore_dir_fn, MISSING_TYPE):
39+
# Allow for default bookmark function to be utilized after app initialization.
40+
# Sometimes the app is created before hooks are registered.
41+
return _default_bookmark_restore_dir_fn
42+
else:
43+
return restore_dir_fn
2044

2145

2246
@overload
@@ -74,9 +98,9 @@ def restore_bookmark_dir(id: str) -> Path:
7498
--------
7599
* `~shiny.bookmark.set_global_restore_dir_fn` : Set the global bookmark restore directory function
76100
"""
77-
global bookmark_save_dir
101+
global _default_bookmark_save_dir_fn
78102

79-
bookmark_save_dir = as_bookmark_dir_fn(fn)
103+
_default_bookmark_save_dir_fn = as_bookmark_dir_fn(fn)
80104
return fn
81105

82106

@@ -118,7 +142,7 @@ def restore_bookmark_dir(id: str) -> Path:
118142
--------
119143
* `~shiny.bookmark.set_global_save_dir_fn` : Set the global bookmark save directory function.
120144
"""
121-
global bookmark_restore_dir
145+
global _default_bookmark_restore_dir_fn
122146

123-
bookmark_restore_dir = as_bookmark_dir_fn(fn)
147+
_default_bookmark_restore_dir_fn = as_bookmark_dir_fn(fn)
124148
return fn

shiny/bookmark/_restore_state.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .._docstring import add_example
1111
from ..module import ResolvedId
1212
from ._bookmark_state import local_restore_dir
13+
from ._global import get_bookmark_restore_dir_fn
1314
from ._types import BookmarkRestoreDirFn
1415
from ._utils import from_json_file, from_json_str, in_shiny_server
1516

@@ -189,7 +190,9 @@ async def _load_state_qs(self, query_string: str, *, app: App) -> None:
189190

190191
id = id[0]
191192

192-
load_bookmark_fn: BookmarkRestoreDirFn | None = app._bookmark_restore_dir_fn
193+
load_bookmark_fn: BookmarkRestoreDirFn | None = get_bookmark_restore_dir_fn(
194+
app._bookmark_restore_dir_fn
195+
)
193196

194197
if load_bookmark_fn is None:
195198
if in_shiny_server():

shiny/bookmark/_save_state.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .._utils import private_random_id
88
from ..reactive import isolate
99
from ._bookmark_state import local_save_dir
10+
from ._global import get_bookmark_save_dir_fn
1011
from ._types import BookmarkSaveDirFn
1112
from ._utils import in_shiny_server, to_json_file, to_json_str
1213

@@ -66,7 +67,9 @@ async def _save_state(self, *, app: App) -> str:
6667
# to `self.dir`.
6768

6869
# This will be defined by the hosting environment if it supports bookmarking.
69-
save_bookmark_fn: BookmarkSaveDirFn | None = app._bookmark_save_dir_fn
70+
save_bookmark_fn: BookmarkSaveDirFn | None = get_bookmark_save_dir_fn(
71+
app._bookmark_save_dir_fn
72+
)
7073

7174
if save_bookmark_fn is None:
7275
if in_shiny_server():

0 commit comments

Comments
 (0)