Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions shiny/express/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .. import render
from . import ui
from ._is_express import is_express_app
from ._module import module
from ._output import ( # noqa: F401
output_args, # pyright: ignore[reportUnusedImport]
suspend_display, # pyright: ignore[reportUnusedImport] - Deprecated
Expand All @@ -29,6 +30,7 @@
"wrap_express_app",
"ui",
"expressify",
"module",
)

# Add types to help type checkers
Expand Down
10 changes: 7 additions & 3 deletions shiny/express/_mock_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import textwrap
from typing import TYPE_CHECKING, Awaitable, Callable, cast

from .._namespaces import Root
from .._namespaces import Id, ResolvedId, Root
from ..session import Inputs, Outputs, Session

if TYPE_CHECKING:
Expand All @@ -21,8 +21,8 @@ class ExpressMockSession:
the `app_opts()` function.
"""

def __init__(self):
self.ns = Root
def __init__(self, ns: ResolvedId = Root):
self.ns = ns
self.input = Inputs({})
self.output = Outputs(cast(Session, self), self.ns, outputs={})

Expand All @@ -39,6 +39,10 @@ def on_ended(
) -> Callable[[], None]:
return lambda: None

def make_scope(self, id: Id) -> Session:
ns = self.ns(id)
return cast(Session, ExpressMockSession(ns))

def __getattr__(self, name: str):
raise AttributeError(
textwrap.dedent(
Expand Down
35 changes: 35 additions & 0 deletions shiny/express/_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import functools
from typing import Callable, Concatenate, ParamSpec, TypeVar

from ..module import Id
from ..session._session import Inputs, Outputs, Session
from ..session._utils import require_active_session, session_context
from .expressify_decorator import expressify

T = TypeVar("T")
P = ParamSpec("P")
R = TypeVar("R")

__all__ = ("module",)


def module(
fn: Callable[Concatenate[Inputs, Outputs, Session, P], R]
) -> Callable[Concatenate[Id, P], R]:
fn = expressify(fn)

@functools.wraps(fn)
def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R:
parent_session = require_active_session(None)
module_session = parent_session.make_scope(id)

with session_context(module_session):
return fn(
module_session.input,
module_session.output,
module_session,
*args,
**kwargs
)

return wrapper
29 changes: 24 additions & 5 deletions shiny/render/renderer/_renderer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
from __future__ import annotations

from typing import Any, Awaitable, Callable, Generic, Optional, TypeVar, Union, cast
from typing import (
TYPE_CHECKING,
Any,
Awaitable,
Callable,
Generic,
Optional,
TypeVar,
Union,
cast,
)

from htmltools import MetadataNode, Tag, TagList

Expand All @@ -9,6 +19,9 @@
from ..._utils import is_async_callable, wrap_async
from ...types import Jsonifiable

if TYPE_CHECKING:
from ...session import Session

# TODO-barret-docs: Double check docs are rendererd
# Missing first paragraph from some classes: Example: TransformerMetadata.
# No init method for TransformerParams. This is because the `DocClass` object does not
Expand Down Expand Up @@ -204,6 +217,7 @@ def __init__(
super().__init__()

self._auto_registered: bool = False
self._session: Session | None = None

# Must be done last
if callable(_fn):
Expand Down Expand Up @@ -278,10 +292,13 @@ def tagify(self) -> DefaultUIFnResult:
return rendered_ui

def _render_auto_output_ui(self) -> DefaultUIFnResultOrNone:
return self.auto_output_ui(
# Pass the `@output_args(foo="bar")` kwargs through to the auto_output_ui function.
**self._auto_output_ui_kwargs,
)
from ...session import session_context

with session_context(self._session):
return self.auto_output_ui(
# Pass the `@output_args(foo="bar")` kwargs through to the auto_output_ui function.
**self._auto_output_ui_kwargs,
)

# ######
# Auto registering output
Expand All @@ -297,6 +314,7 @@ def _on_register(self) -> None:
ns_name = session.output._ns(self.__name__)
session.output.remove(ns_name)
self._auto_registered = False
self._session = session

def _auto_register(self) -> None:
"""
Expand All @@ -317,6 +335,7 @@ def _auto_register(self) -> None:
# We mark the fact that we're auto-registered so that, if an explicit
# registration now occurs, we can undo this auto-registration.
self._auto_registered = True
self._session = s


# Not inheriting from `WrapAsync[[], IT]` as python 3.8 needs typing extensions that
Expand Down
2 changes: 1 addition & 1 deletion shiny/session/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def session_context(session: Optional[Session]):
"""
token: Token[Session | None] = _current_session.set(session)
try:
with namespace_context(session.ns if session else None):
with namespace_context(session.ns if session is not None else None):
yield
finally:
_current_session.reset(token)
Expand Down