Skip to content

Commit

Permalink
Give ResolvedId a __call__ method
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsievert authored and jcheng5 committed Jun 24, 2022
1 parent 5f9dc9f commit 2757b1d
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 50 deletions.
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
10 changes: 5 additions & 5 deletions shiny/_modules.py
Expand Up @@ -8,16 +8,16 @@
else:
from typing import ParamSpec, Concatenate

from ._namespaces import namespaced_id, namespace_context, get_current_namespaces
from ._namespaces import namespaced_id, namespace_context, Id
from .session import Inputs, Outputs, Session, require_active_session, session_context

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


def module_ui(fn: Callable[P, R]) -> Callable[Concatenate[str, P], R]:
def wrapper(ns: str, *args: P.args, **kwargs: P.kwargs) -> R:
with namespace_context(get_current_namespaces() + [ns]):
def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R:
with namespace_context(id):
return fn(*args, **kwargs)

return wrapper
Expand All @@ -26,9 +26,9 @@ def wrapper(ns: str, *args: P.args, **kwargs: P.kwargs) -> R:
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:
def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R:
sess = require_active_session(None)
child_sess = sess.make_scope(ns)
child_sess = sess.make_scope(id)
with session_context(child_sess):
return fn(child_sess.input, child_sess.output, child_sess, *args, **kwargs)

Expand Down
35 changes: 18 additions & 17 deletions shiny/_namespaces.py
@@ -1,39 +1,40 @@
from contextlib import contextmanager
from contextvars import ContextVar, Token
from typing import Union, List
from typing import Union


class ResolvedId(str):
pass
def __call__(self, id: "Id") -> "ResolvedId":
if self == "" or isinstance(id, ResolvedId):
return ResolvedId(id)
else:
return ResolvedId(self + "_" + id)


Id = Union[str, ResolvedId]
Root = ResolvedId("")


def namespaced_id(id: Id) -> Id:
return namespaced_id_ns(id, get_current_namespaces())
Id = Union[str, ResolvedId]


def namespaced_id_ns(id: Id, namespaces: List[str] = []) -> Id:
if isinstance(id, ResolvedId) or len(namespaces) == 0:
return id
else:
return ResolvedId("_".join(namespaces) + "_" + id)
def namespaced_id(id: Id) -> ResolvedId:
return ResolvedId(get_current_namespace())(id)


def get_current_namespaces() -> List[str]:
return _current_namespaces.get()
def get_current_namespace() -> ResolvedId:
return _current_namespace.get()


_current_namespaces: ContextVar[List[str]] = ContextVar(
"current_namespaces", default=[]
_current_namespace: ContextVar[ResolvedId] = ContextVar(
"current_namespace", default=Root
)


@contextmanager
def namespace_context(namespaces: List[str]):
token: Token[List[str]] = _current_namespaces.set(namespaces)
def namespace_context(id: Union[Id, None]):
namespace = namespaced_id(id) if id else Root
token: Token[ResolvedId] = _current_namespace.set(namespace)
try:
yield
finally:
_current_namespaces.reset(token)
_current_namespace.reset(token)
18 changes: 6 additions & 12 deletions shiny/session/_session.py
Expand Up @@ -49,7 +49,7 @@
from .._fileupload import FileInfo, FileUploadManager
from ..http_staticfiles import FileResponse
from ..input_handler import input_handlers
from .._namespaces import namespaced_id_ns
from .._namespaces import ResolvedId, Id, Root
from ..reactive import Value, Effect, Effect_, isolate, flush
from ..reactive._core import lock
from ..types import SafeException, SilentCancelOutputException, SilentException
Expand Down Expand Up @@ -131,6 +131,8 @@ class Session(object, metaclass=SessionMeta):
for type checking reasons).
"""

ns = Root

# ==========================================================================
# Initialization
# ==========================================================================
Expand Down Expand Up @@ -731,17 +733,13 @@ def _process_ui(self, ui: TagChildArg) -> RenderedDeps:

return {"deps": deps, "html": res["html"]}

@staticmethod
def ns(id: str) -> str:
return id

def make_scope(self, id: str) -> "Session":
ns = create_ns_func(id)
def make_scope(self, id: Id) -> "Session":
ns = self.ns(id)
return SessionProxy(parent=self, ns=ns) # type: ignore


class SessionProxy:
def __init__(self, parent: Session, ns: Callable[[str], str]) -> None:
def __init__(self, parent: Session, ns: ResolvedId) -> None:
self._parent = parent
self.ns = ns
self.input = Inputs(values=parent.input._map, ns=ns)
Expand Down Expand Up @@ -774,10 +772,6 @@ def wrapper(fn: DownloadHandler):
return wrapper


def create_ns_func(namespace: str) -> Callable[[str], str]:
return lambda x: namespaced_id_ns(x, [namespace])


# ======================================================================================
# Inputs
# ======================================================================================
Expand Down
4 changes: 3 additions & 1 deletion shiny/session/_utils.py
Expand Up @@ -8,6 +8,7 @@
if TYPE_CHECKING:
from ._session import Session

from .._namespaces import namespace_context

if sys.version_info >= (3, 8):
from typing import TypedDict
Expand Down Expand Up @@ -62,7 +63,8 @@ def session_context(session: Optional["Session"]):
"""
token: Token[Union[Session, None]] = _current_session.set(session)
try:
yield
with namespace_context(session.ns if session else None):
yield
finally:
_current_session.reset(token)

Expand Down
28 changes: 14 additions & 14 deletions tests/test_modules.py
Expand Up @@ -11,27 +11,27 @@


@module_ui
def mod_inner() -> TagList:
def mod_inner_ui() -> TagList:
return TagList(
ui.input_action_button("button", label="inner"),
ui.output_text(namespaced_id("out")),
)


@module_ui
def mod_outer() -> TagList:
return TagList(mod_inner("inner"), ui.output_text("out2"))
def mod_outer_ui() -> TagList:
return TagList(mod_inner_ui("inner"), ui.output_text("out2"))


def get_id(x: TagList, child_idx: int = 0) -> str:
return cast(Tag, x[child_idx]).attrs["id"]


def test_module_ui():
x = mod_inner("inner")
x = mod_inner_ui("inner")
assert get_id(x, 0) == "inner_button"
assert get_id(x, 1) == "inner_out"
y = mod_outer("outer")
y = mod_outer_ui("outer")
assert get_id(y, 0) == "outer_inner_button"
assert get_id(y, 1) == "outer_inner_out"
assert get_id(y, 2) == "outer_out2"
Expand All @@ -42,7 +42,7 @@ def test_session_scoping():
sessions: Dict[str, Union[Session, None, str]] = {}

@module_server
def inner(input: Inputs, output: Outputs, session: Session):
def inner_server(input: Inputs, output: Outputs, session: Session):
@reactive.Calc
def out():
return get_current_session()
Expand All @@ -53,25 +53,25 @@ def _():
sessions["inner_current"] = get_current_session()
sessions["inner_calc_current"] = out()
sessions["inner_id"] = session.ns("foo")
sessions["inner_ui_id"] = get_id(mod_outer("outer"), 0)
sessions["inner_ui_id"] = get_id(mod_outer_ui("outer"), 0)

@module_server
def outer(input: Inputs, output: Outputs, session: Session):
def outer_server(input: Inputs, output: Outputs, session: Session):
@reactive.Calc
def out():
return get_current_session()

@reactive.Effect
def _():
inner("mod_inner")
inner_server("mod_inner")
sessions["outer"] = session
sessions["outer_current"] = get_current_session()
sessions["outer_calc_current"] = out()
sessions["outer_id"] = session.ns("foo")
sessions["outer_ui_id"] = get_id(mod_outer("outer"), 0)
sessions["outer_ui_id"] = get_id(mod_outer_ui("outer"), 0)

def server(input: Inputs, output: Outputs, session: Session):
outer("mod_outer")
outer_server("mod_outer")

@reactive.Calc
def out():
Expand All @@ -83,7 +83,7 @@ def _():
sessions["top_current"] = get_current_session()
sessions["top_calc_current"] = out()
sessions["top_id"] = session.ns("foo")
sessions["top_ui_id"] = get_id(mod_outer("outer"), 0)
sessions["top_ui_id"] = get_id(mod_outer_ui("outer"), 0)

App(ui.TagList(), server)._create_session(MockConnection())
run_coro_sync(reactive.flush())
Expand All @@ -92,13 +92,13 @@ def _():
assert sessions["inner_current"] is sessions["inner_calc_current"]
assert isinstance(sessions["inner_current"], Session)
assert sessions["inner_id"] == "mod_outer_mod_inner_foo"
assert sessions["inner_ui_id"] == "outer_inner_button"
assert sessions["inner_ui_id"] == "mod_outer_mod_inner_outer_inner_button"

assert sessions["outer"] is sessions["outer_current"]
assert sessions["outer_current"] is sessions["outer_calc_current"]
assert isinstance(sessions["outer_current"], Session)
assert sessions["outer_id"] == "mod_outer_foo"
assert sessions["outer_ui_id"] == "outer_inner_button"
assert sessions["outer_ui_id"] == "mod_outer_outer_inner_button"

assert sessions["top"] is sessions["top_current"]
assert sessions["top_current"] is sessions["top_calc_current"]
Expand Down
3 changes: 3 additions & 0 deletions tests/test_poll.py
Expand Up @@ -12,6 +12,7 @@
from shiny import *
from shiny import _utils
from shiny.reactive import *
from shiny._namespaces import Root

from .mocktime import MockTime

Expand All @@ -25,6 +26,8 @@ class OnEndedSessionCallbacks:
Eventually we should have a proper mock of Session, then we can retire this.
"""

ns = Root

def __init__(self):
self._on_ended_callbacks = _utils.Callbacks()
# Unfortunately we have to lie here and say we're a session. Obvously, any
Expand Down

0 comments on commit 2757b1d

Please sign in to comment.