Skip to content

Commit

Permalink
Experiment with a decorator based API
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsievert committed May 20, 2022
1 parent 2a8a175 commit 8acca98
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 65 deletions.
3 changes: 2 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,8 @@ Control application complexity by namespacing UI and server code.
:toctree: reference/
:template: class.rst

Module
module_ui
module_server


Type hints
Expand Down
16 changes: 9 additions & 7 deletions examples/moduleapp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# ============================================================
# Counter module
# ============================================================
@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;"},
Expand All @@ -12,7 +13,8 @@ def counter_ui(label: str = "Increment counter") -> ui.TagChildArg:
)


def counter_server(input: Inputs, output: Outputs, session: Session):
@module_server
def counter_server(input: Inputs, output: Outputs, session: Session) -> int:
count: reactive.Value[int] = reactive.Value(0)

@reactive.Effect()
Expand All @@ -25,22 +27,22 @@ def _():
def out() -> str:
return f"Click count is {count()}"


counter_module = Module(counter_ui, counter_server)
return 1


# =============================================================================
# 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_ui("counter2", "Counter 2"),
)


def server(input: Inputs, output: Outputs, session: Session):
counter_module.server("counter1")
counter_module.server("counter2")
counter_server("counter1")
val = counter_server("counter2")
print(val)


app = App(app_ui, server)
5 changes: 3 additions & 2 deletions shiny/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# Private submodules that have some user-facing functionality
from ._app import App
from ._decorators import event
from ._modules import Module
from ._modules import module_ui, module_server
from ._validation import req

if _is_pyodide:
Expand Down Expand Up @@ -42,7 +42,8 @@
# _main.py
"run_app",
# _modules.py
"Module",
"module_ui",
"module_server",
# _render.py
"render_text",
"render_plot",
Expand Down
134 changes: 79 additions & 55 deletions shiny/_modules.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__all__ = ("Module", "namespaced_id")
__all__ = ("namespaced_id", "module_ui", "module_server")

from typing import Any, Callable, Optional
from typing import Any, Callable, Optional, TypeVar, ParamSpec, Concatenate

from htmltools import TagChildArg

Expand Down Expand Up @@ -78,59 +78,83 @@ def __init__(self, ns: str):
self._ns = ns


@add_example()
class Module:
"""
Modularize UI and server-side logic.
Parameters
----------
ui
The module's UI definition.
server
The module's server-side logic.
"""
P = ParamSpec("P")
R = TypeVar("R")


def __init__(
self,
ui: Callable[..., TagChildArg],
server: Callable[[ModuleInputs, ModuleOutputs, ModuleSession], None],
) -> None:
self._ui: Callable[..., TagChildArg] = ui
self._server: Callable[
[ModuleInputs, ModuleOutputs, ModuleSession], None
] = server

def ui(self, ns: str, *args: Any) -> TagChildArg:
"""
Render the module's UI.
Parameters
----------
namespace
A namespace for the module.
args
Additional arguments to pass to the module's UI definition.
"""

# Create a fake session so that namespaced_id() knows
# what the relevant namespace is
def module_ui(fn: Callable[P, R]) -> Callable[Concatenate[str, P], R]:
def wrapper(ns: str, *args: P.args, **kwargs: P.kwargs) -> R:
with session_context(MockModuleSession(ns)):
return self._ui(*args)

def server(self, ns: str, *, session: Optional[Session] = None) -> None:
"""
Invoke the module's server-side logic.
Parameters
----------
ns
A namespace for the module.
session
A :class:`~shiny.Session` instance. If not provided, it is inferred via
:func:`~shiny.session.get_current_session`.
"""

mod_sess = ModuleSession(ns, require_active_session(session))
return fn(*args, **kwargs)

return wrapper


def module_server(
fn: Callable[Concatenate[Inputs, Outputs, Session, P], R]
) -> Callable[Concatenate[str, P], R]:
def wrapper(ns: str, *args: P.args, **kwargs: P.kwargs) -> R:
mod_sess = ModuleSession(ns, require_active_session(None))
with session_context(mod_sess):
return self._server(mod_sess.input, mod_sess.output, mod_sess)
return fn(mod_sess.input, mod_sess.output, mod_sess, *args, **kwargs)

return wrapper


# @add_example()
# class Module:
# """
# Modularize UI and server-side logic.
#
# Parameters
# ----------
# ui
# The module's UI definition.
# server
# The module's server-side logic.
# """
#
# def __init__(
# self,
# ui: Callable[..., TagChildArg],
# server: Callable[[ModuleInputs, ModuleOutputs, ModuleSession], None],
# ) -> None:
# self._ui: Callable[..., TagChildArg] = ui
# self._server: Callable[
# [ModuleInputs, ModuleOutputs, ModuleSession], None
# ] = server
#
# def ui(self, ns: str, *args: Any) -> TagChildArg:
# """
# Render the module's UI.
#
# Parameters
# ----------
# namespace
# A namespace for the module.
# args
# Additional arguments to pass to the module's UI definition.
# """
#
# # Create a fake session so that namespaced_id() knows
# # what the relevant namespace is
# with session_context(MockModuleSession(ns)):
# return self._ui(*args)
#
# def server(self, ns: str, *, session: Optional[Session] = None) -> None:
# """
# Invoke the module's server-side logic.
#
# Parameters
# ----------
# ns
# A namespace for the module.
# session
# A :class:`~shiny.Session` instance. If not provided, it is inferred via
# :func:`~shiny.session.get_current_session`.
# """
#
# mod_sess = ModuleSession(ns, require_active_session(session))
# with session_context(mod_sess):
# return self._server(mod_sess.input, mod_sess.output, mod_sess)
#

0 comments on commit 8acca98

Please sign in to comment.