Skip to content

Commit 464394d

Browse files
committed
Add and test for module support within client data
1 parent 5040860 commit 464394d

File tree

3 files changed

+48
-34
lines changed

3 files changed

+48
-34
lines changed

shiny/session/_session.py

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,7 @@ def __init__(self, root_session: Session, ns: ResolvedId) -> None:
12201220
ns=ns,
12211221
outputs=root_session.output._outputs,
12221222
)
1223+
self.clientdata = ClientData(self)
12231224
self._outbound_message_queues = root_session._outbound_message_queues
12241225
self._downloads = root_session._downloads
12251226

@@ -1509,7 +1510,7 @@ class ClientData:
15091510

15101511
def __init__(self, session: Session) -> None:
15111512
self._session: Session = session
1512-
self._current_renderer: Renderer[Any] | None = None
1513+
self._current_output_name: ResolvedId | None = None
15131514

15141515
def url_hash(self) -> str:
15151516
"""
@@ -1559,7 +1560,7 @@ def pixelratio(self) -> float:
15591560
"""
15601561
return cast(int, self._read_input("pixelratio"))
15611562

1562-
def output_height(self, id: Optional[str] = None) -> float | None:
1563+
def output_height(self, id: Optional[Id] = None) -> float | None:
15631564
"""
15641565
Reactively read the height of an output.
15651566
@@ -1576,7 +1577,7 @@ def output_height(self, id: Optional[str] = None) -> float | None:
15761577
"""
15771578
return cast(float, self._read_output(id, "height"))
15781579

1579-
def output_width(self, id: Optional[str] = None) -> float | None:
1580+
def output_width(self, id: Optional[Id] = None) -> float | None:
15801581
"""
15811582
Reactively read the width of an output.
15821583
@@ -1593,7 +1594,7 @@ def output_width(self, id: Optional[str] = None) -> float | None:
15931594
"""
15941595
return cast(float, self._read_output(id, "width"))
15951596

1596-
def output_hidden(self, id: Optional[str] = None) -> bool | None:
1597+
def output_hidden(self, id: Optional[Id] = None) -> bool | None:
15971598
"""
15981599
Reactively read whether an output is hidden.
15991600
@@ -1609,7 +1610,7 @@ def output_hidden(self, id: Optional[str] = None) -> bool | None:
16091610
"""
16101611
return cast(bool, self._read_output(id, "hidden"))
16111612

1612-
def output_bg_color(self, id: Optional[str] = None) -> str | None:
1613+
def output_bg_color(self, id: Optional[Id] = None) -> str | None:
16131614
"""
16141615
Reactively read the background color of an output.
16151616
@@ -1626,7 +1627,7 @@ def output_bg_color(self, id: Optional[str] = None) -> str | None:
16261627
"""
16271628
return cast(str, self._read_output(id, "bg"))
16281629

1629-
def output_fg_color(self, id: Optional[str] = None) -> str | None:
1630+
def output_fg_color(self, id: Optional[Id] = None) -> str | None:
16301631
"""
16311632
Reactively read the foreground color of an output.
16321633
@@ -1643,7 +1644,7 @@ def output_fg_color(self, id: Optional[str] = None) -> str | None:
16431644
"""
16441645
return cast(str, self._read_output(id, "fg"))
16451646

1646-
def output_accent_color(self, id: Optional[str] = None) -> str | None:
1647+
def output_accent_color(self, id: Optional[Id] = None) -> str | None:
16471648
"""
16481649
Reactively read the accent color of an output.
16491650
@@ -1660,7 +1661,7 @@ def output_accent_color(self, id: Optional[str] = None) -> str | None:
16601661
"""
16611662
return cast(str, self._read_output(id, "accent"))
16621663

1663-
def output_font(self, id: Optional[str] = None) -> str | None:
1664+
def output_font(self, id: Optional[Id] = None) -> str | None:
16641665
"""
16651666
Reactively read the font(s) of an output.
16661667
@@ -1681,56 +1682,50 @@ def _read_input(self, key: str) -> str:
16811682
self._check_current_context(key)
16821683

16831684
id = ResolvedId(f".clientdata_{key}")
1684-
if id not in self._session.input:
1685+
if id not in self._session.root_scope().input:
16851686
raise ValueError(
16861687
f"ClientData value '{key}' not found. Please report this issue."
16871688
)
16881689

1689-
return self._session.input[id]()
1690+
return self._session.root_scope().input[id]()
16901691

1691-
def _read_output(self, id: str | None, key: str) -> str | None:
1692+
def _read_output(self, id: Id | None, key: str) -> str | None:
16921693
self._check_current_context(f"output_{key}")
16931694

1694-
if id is None and self._current_renderer is not None:
1695-
id = self._current_renderer.output_id
1695+
# No `id` provided support
1696+
if id is None and self._current_output_name is not None:
1697+
id = self._current_output_name
16961698

16971699
if id is None:
16981700
raise ValueError(
16991701
"session.clientdata.output_*() requires an id when not called within "
17001702
"an output renderer."
17011703
)
17021704

1705+
# Module support
1706+
if not isinstance(id, ResolvedId):
1707+
id = self._session.ns(id)
1708+
17031709
input_id = ResolvedId(f".clientdata_output_{id}_{key}")
1704-
if input_id in self._session.input:
1705-
return self._session.input[input_id]()
1710+
if input_id in self._session.root_scope().input:
1711+
return self._session.root_scope().input[input_id]()
17061712
else:
17071713
return None
17081714

17091715
@contextlib.contextmanager
1710-
def _renderer_ctx(self, renderer: Renderer[Any]) -> Generator[None, None, None]:
1716+
def _output_name_ctx(self, output_name: ResolvedId) -> Generator[None, None, None]:
17111717
"""
1712-
Context manager to temporarily set the current renderer.
1718+
Context manager to temporarily set the output name.
17131719
17141720
This is used to allow `session.clientdata.output_*()` methods to access the
1715-
current renderer's output id without needing to pass it explicitly.
1716-
1717-
Parameters
1718-
----------
1719-
renderer
1720-
The renderer to set as the current renderer.
1721-
1722-
Yields
1723-
------
1724-
None
1725-
The context manager does not return any value, but temporarily sets the
1726-
current renderer to the provided renderer.
1721+
current output name without needing to pass it explicitly.
17271722
"""
1728-
old_renderer = self._current_renderer
1723+
old_output_name = self._current_output_name
17291724
try:
1730-
self._current_renderer = renderer
1725+
self._current_output_name = output_name
17311726
yield
17321727
finally:
1733-
self._current_renderer = old_renderer
1728+
self._current_output_name = old_output_name
17341729

17351730
@staticmethod
17361731
def _check_current_context(key: str) -> None:
@@ -1836,7 +1831,7 @@ async def output_obs():
18361831
)
18371832

18381833
try:
1839-
with session.clientdata._renderer_ctx(renderer):
1834+
with session.clientdata._output_name_ctx(output_name):
18401835
# Call the app's renderer function
18411836
value = await renderer.render()
18421837

tests/playwright/shiny/session/current_output_info/app.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
1-
from shiny import App, Inputs, Outputs, Session, render, ui
1+
from shiny import App, Inputs, Outputs, Session, module, render, ui
2+
3+
4+
@module.ui
5+
def mod_ui():
6+
return ui.output_text("info2").add_class("shiny-report-theme")
7+
8+
9+
@module.server
10+
def mod_server(input: Inputs, output: Outputs, session: Session):
11+
@render.text
12+
def info2():
13+
bg_color = session.clientdata.output_bg_color()
14+
return f"BG color: {bg_color}"
15+
216

317
app_ui = ui.page_fluid(
418
ui.input_dark_mode(mode="light", id="dark_mode"),
519
ui.output_text("text1"),
620
ui.output_text("text2"),
721
ui.output_text("info").add_class("shiny-report-theme"),
22+
mod_ui("mod1"),
823
)
924

1025

1126
def server(input: Inputs, output: Outputs, session: Session):
27+
mod_server("mod1")
1228

1329
@render.text
1430
def info():

tests/playwright/shiny/session/current_output_info/test_current_output_info.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ def test_current_output_info(page: Page, local_app: ShinyAppProc) -> None:
1010

1111
# Check that we can get background color from clientdata
1212
info = controller.OutputText(page, "info")
13+
mod_info2 = controller.OutputText(page, "mod1-info2")
1314
info.expect_value("BG color: rgb(255, 255, 255)")
15+
mod_info2.expect_value("BG color: rgb(255, 255, 255)")
1416

1517
# Click the dark mode button to change the background color
1618
dark_mode = controller.InputDarkMode(page, "dark_mode")
@@ -20,3 +22,4 @@ def test_current_output_info(page: Page, local_app: ShinyAppProc) -> None:
2022

2123
# Check that the background color has changed
2224
info.expect_value("BG color: rgb(29, 31, 33)")
25+
mod_info2.expect_value("BG color: rgb(29, 31, 33)")

0 commit comments

Comments
 (0)