From 2757b1d979391b7dca5d78018ef2bafab107ec23 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 17 Jun 2022 17:27:26 -0500 Subject: [PATCH] Give ResolvedId a __call__ method --- pyrightconfig.json | 2 +- shiny/_modules.py | 10 +++++----- shiny/_namespaces.py | 35 ++++++++++++++++++----------------- shiny/session/_session.py | 18 ++++++------------ shiny/session/_utils.py | 4 +++- tests/test_modules.py | 28 ++++++++++++++-------------- tests/test_poll.py | 3 +++ 7 files changed, 50 insertions(+), 50 deletions(-) diff --git a/pyrightconfig.json b/pyrightconfig.json index 8d5ddb884..75e2b64bd 100644 --- a/pyrightconfig.json +++ b/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", diff --git a/shiny/_modules.py b/shiny/_modules.py index ccfa352e3..fe2693efe 100644 --- a/shiny/_modules.py +++ b/shiny/_modules.py @@ -8,7 +8,7 @@ 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") @@ -16,8 +16,8 @@ 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 @@ -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) diff --git a/shiny/_namespaces.py b/shiny/_namespaces.py index 418a19150..5525f9027 100644 --- a/shiny/_namespaces.py +++ b/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) diff --git a/shiny/session/_session.py b/shiny/session/_session.py index 062c8a853..ad0591049 100644 --- a/shiny/session/_session.py +++ b/shiny/session/_session.py @@ -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 @@ -131,6 +131,8 @@ class Session(object, metaclass=SessionMeta): for type checking reasons). """ + ns = Root + # ========================================================================== # Initialization # ========================================================================== @@ -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) @@ -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 # ====================================================================================== diff --git a/shiny/session/_utils.py b/shiny/session/_utils.py index 1e2372040..e60bb51f3 100644 --- a/shiny/session/_utils.py +++ b/shiny/session/_utils.py @@ -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 @@ -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) diff --git a/tests/test_modules.py b/tests/test_modules.py index e5af66454..0cf16753a 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -11,7 +11,7 @@ @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")), @@ -19,8 +19,8 @@ def mod_inner() -> TagList: @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: @@ -28,10 +28,10 @@ def get_id(x: TagList, child_idx: int = 0) -> str: 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" @@ -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() @@ -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(): @@ -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()) @@ -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"] diff --git a/tests/test_poll.py b/tests/test_poll.py index 13382eb97..01c6b3249 100644 --- a/tests/test_poll.py +++ b/tests/test_poll.py @@ -12,6 +12,7 @@ from shiny import * from shiny import _utils from shiny.reactive import * +from shiny._namespaces import Root from .mocktime import MockTime @@ -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