Skip to content

Commit

Permalink
Merge pull request #100 from rstudio/ModuleSessionFixes
Browse files Browse the repository at this point in the history
  • Loading branch information
wch committed Jun 30, 2022
2 parents 5a05b18 + a5397ae commit 1f9ad5d
Show file tree
Hide file tree
Showing 28 changed files with 480 additions and 411 deletions.
3 changes: 2 additions & 1 deletion docs/source/index.rst
Expand Up @@ -303,7 +303,8 @@ Control application complexity by namespacing UI and server code.
:toctree: reference/
:template: class.rst

Module
module_ui
module_server


Type hints
Expand Down
55 changes: 41 additions & 14 deletions examples/moduleapp/app.py
@@ -1,23 +1,23 @@
from typing import Callable
from shiny import *


# ============================================================
# Counter module
# ============================================================
def counter_ui(
ns: Callable[[str], str], label: str = "Increment counter"
) -> ui.TagChildArg:
@module.ui
def counter_ui(label: str = "Increment counter") -> ui.TagChildArg:
return ui.div(
{"style": "border: 1px solid #ccc; border-radius: 5px; margin: 5px 0;"},
ui.h2("This is " + label),
ui.input_action_button(id=ns("button"), label=label),
ui.output_text_verbatim(id=ns("out")),
ui.input_action_button(id="button", label=label),
ui.output_text_verbatim(id="out"),
)


def counter_server(input: Inputs, output: Outputs, session: Session):
count: reactive.Value[int] = reactive.Value(0)
@module.server
def counter_server(
input: Inputs, output: Outputs, session: Session, starting_value: int = 0
):
count: reactive.Value[int] = reactive.Value(starting_value)

@reactive.Effect
@event(input.button)
Expand All @@ -30,21 +30,48 @@ def out() -> str:
return f"Click count is {count()}"


counter_module = Module(counter_ui, counter_server)
# ============================================================
# Counter Wrapper module -- shows that counter still works
# the same way when wrapped in a dynamic UI
# ============================================================
@module.ui
def counter_wrapper_ui() -> ui.TagChildArg:
return ui.output_ui("dynamic_counter")


@module.server
def counter_wrapper_server(
input: Inputs, output: Outputs, session: Session, label: str = "Increment counter"
):
@output()
@render.ui()
def dynamic_counter():
return counter_ui("counter", label)

counter_server("counter")


# =============================================================================
# App that uses module
# =============================================================================
app_ui = ui.page_fluid(
counter_module.ui("counter1", "Counter 1"),
counter_module.ui("counter2", "Counter 2"),
counter_ui("counter1", "Counter 1"),
counter_wrapper_ui("counter2_wrapper"),
ui.output_ui("counter3_ui"),
)


def server(input: Inputs, output: Outputs, session: Session):
counter_module.server("counter1")
counter_module.server("counter2")
counter_server("counter1")
counter_wrapper_server("counter2_wrapper", "Counter 2")

@output()
@render.ui()
def counter3_ui():
counter_server("counter3")
return counter_ui("counter3", "Counter 3")

counter_server("counter")


app = App(app_ui, server)
2 changes: 1 addition & 1 deletion pyrightconfig.json
@@ -1,5 +1,5 @@
{
"ignore": ["shiny/examples", "examples", "build", "dist", "typings"],
"ignore": ["shiny/examples", "examples", "build", "dist", "typings", "sandbox"],
"typeCheckingMode": "strict",
"reportImportCycles": "none",
"reportUnusedFunction": "none",
Expand Down
5 changes: 3 additions & 2 deletions shiny/__init__.py
Expand Up @@ -13,10 +13,11 @@
# Private submodules that have some user-facing functionality
from ._app import App
from ._decorators import event
from ._modules import Module
from ._validation import req
from ._deprecated import *

from . import module

if _is_pyodide:
# In pyodide, avoid importing _main because it imports packages that aren't
# available.
Expand All @@ -43,7 +44,7 @@
# _main.py
"run_app",
# _modules.py
"Module",
"module",
# _session.py
"Session",
"Inputs",
Expand Down
20 changes: 13 additions & 7 deletions shiny/_connection.py
Expand Up @@ -34,25 +34,31 @@ def __init__(self):
# make those more configurable if we need to customize the HTTPConnection (like
# "scheme", "path", and "query_string").
self._http_conn = HTTPConnection(scope={"type": "websocket", "headers": {}})
self._queue: asyncio.Queue[str] = asyncio.Queue()

async def send(self, message: str) -> None:
pass

# I should say I’m not 100% that the receive method can be a no-op for our testing
# purposes. It might need to be asyncio.sleep(0), and/or it might need an external
# way to yield until we tell the connection to continue, so that the run loop can
# continue.
async def receive(self) -> str:
# Sleep forever
await asyncio.Event().wait()
raise RuntimeError("make the type checker happy")
msg = await self._queue.get()
if msg == "":
raise ConnectionClosed()
return msg

async def close(self, code: int, reason: Optional[str]) -> None:
pass

def get_http_conn(self) -> HTTPConnection:
return self._http_conn

def cause_receive(self, message: str) -> None:
"""Call from tests to simulate the other side sending a message"""
self._queue.put_nowait(message)

def cause_disconnect(self) -> None:
"""Call from tests to simulate the other side disconnecting"""
self.cause_receive("")


class StarletteConnection(Connection):
def __init__(self, conn: starlette.websockets.WebSocket):
Expand Down
195 changes: 0 additions & 195 deletions shiny/_modules.py

This file was deleted.

0 comments on commit 1f9ad5d

Please sign in to comment.