Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp shiny modules #100

Merged
merged 13 commits into from Jun 30, 2022
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 @@
{
jcheng5 marked this conversation as resolved.
Show resolved Hide resolved
"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.