Skip to content

Commit

Permalink
Remove the need to have a separate namespace_context() by always usin…
Browse files Browse the repository at this point in the history
…g the session's namespace
  • Loading branch information
cpsievert committed Mar 7, 2022
1 parent 43f73f9 commit 26e1063
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 111 deletions.
77 changes: 11 additions & 66 deletions shiny/_namespaces.py
@@ -1,37 +1,9 @@
__all__ = ("namespace_context", "namespaced_id")
# TODO: make this available under the shiny.modules API
__all__ = ("namespaced_id",)

from contextlib import contextmanager
from contextvars import ContextVar, Token
import functools
from typing import Union, Optional

from typing import Awaitable, Union, Optional, TypeVar, Callable, cast

from shiny.types import MISSING, MISSING_TYPE

from ._utils import is_async_callable

_current_namespace: ContextVar[Optional[str]] = ContextVar(
"current_namespace", default=None
)


def get_current_namespace() -> Optional[str]:
"""
Get the current namespace.
"""
return _current_namespace.get()


@contextmanager
def namespace_context(ns: str):
"""
Set a namespace for the duration of the context.
"""
token: Token[Union[str, None]] = _current_namespace.set(ns)
try:
yield
finally:
_current_namespace.reset(token)
from .types import MISSING, MISSING_TYPE


def namespaced_id(id: str, ns: Union[str, MISSING_TYPE, None] = MISSING) -> str:
Expand All @@ -41,12 +13,7 @@ def namespaced_id(id: str, ns: Union[str, MISSING_TYPE, None] = MISSING) -> str:
Parameters
----------
id
The ID to namespace.
Warning
-------
This is only provided so that htmltools can use it to namespace ids within a
``Module()`` UI function.
The ID to namespace..
"""
if isinstance(ns, MISSING_TYPE):
ns = get_current_namespace()
Expand All @@ -57,33 +24,11 @@ def namespaced_id(id: str, ns: Union[str, MISSING_TYPE, None] = MISSING) -> str:
return ns + "_" + id


T = TypeVar("T")
Fn = Union[Callable[..., T], Callable[..., Awaitable[T]]]


def add_namespace(fn: Fn[T], ns: str) -> Fn[T]:
"""
Modify a function to use the **current** namespace when it executes.
Note
----
This is useful for marking user supplied functions to use the **current** namespace
when they execute **later on**.
"""

if is_async_callable(fn):

@functools.wraps(fn)
async def wrapper(*args: object, **kwargs: object) -> T:
with namespace_context(ns):
return await fn(*args, **kwargs)
def get_current_namespace() -> Optional[str]:
from .session import get_current_session

session = get_current_session()
if session is None:
return None
else:
fn = cast(Callable[..., T], fn)

@functools.wraps(fn)
def wrapper(*args: object, **kwargs: object) -> T:
with namespace_context(ns):
return fn(*args, **kwargs)

return wrapper
return session._ns
27 changes: 15 additions & 12 deletions shiny/modules.py
@@ -1,11 +1,11 @@
__all__ = ("Module",)
__all__ = ("Module", "namespaced_id")

from typing import Any, Callable, Optional

from htmltools import TagChildArg

from ._docstring import add_example
from ._namespaces import namespace_context, namespaced_id, get_current_namespace
from ._namespaces import namespaced_id
from .session import Inputs, Outputs, Session, require_active_session, session_context


Expand Down Expand Up @@ -43,8 +43,8 @@ class ModuleOutputs(Outputs):
"""

def __init__(self, ns: str, parent_outputs: Outputs):
self._parent = parent_outputs
self._ns = namespaced_id(ns, parent_outputs._ns) # Support nested modules
self._parent = parent_outputs

def __getattr__(self, attr: str) -> Any:
return getattr(self._parent, attr)
Expand All @@ -64,15 +64,23 @@ class ModuleSession(Session):
"""

def __init__(self, ns: str, parent_session: Session):
self._parent = parent_session
self._ns = namespaced_id(ns, parent_session._ns) # Support nested modules
self._parent = parent_session
self.input = ModuleInputs(ns, parent_session.input)
self.output = ModuleOutputs(ns, parent_session.output)

def __getattr__(self, attr: str) -> Any:
return getattr(self._parent, attr)


class MockModuleSession(ModuleSession):
def __init__(self, ns: str):
self._ns = ns
self._parent = None
self.input = Inputs()
self.output = Outputs(self)


@add_example()
class Module:
"""
Expand Down Expand Up @@ -108,9 +116,9 @@ def ui(self, ns: str, *args: Any, **kwargs: Any) -> TagChildArg:
Additional keyword arguments to pass to the module's UI definition.
"""

# Support nested modules by adding this namespace to any current namespace.
ns_full = namespaced_id(ns, get_current_namespace())
with namespace_context(ns_full):
# Create a fake session so that namespaced_id() knows
# what the relevant namespace is
with session_context(MockModuleSession(ns)):
return self._ui(*args, **kwargs)

def server(
Expand All @@ -133,11 +141,6 @@ def server(
"""

mod_sess = ModuleSession(ns, require_active_session(session))
# N.B. we don't need a `with namespace_context()` here, because when
# reactive primitive like Calc(), Effect(), and RenderFunction() are created,
# they use the session._ns field to add a `with namespace_context()` to the
# the user's function (it has to be done this way since the user functions
# are executed after the server function is executed).
with session_context(mod_sess):
return self._server(
mod_sess.input, mod_sess.output, mod_sess, *args, **kwargs
Expand Down
36 changes: 13 additions & 23 deletions shiny/reactive/_reactives.py
Expand Up @@ -18,7 +18,6 @@

from ._core import Context, Dependents, ReactiveWarning
from .._docstring import add_example
from .._namespaces import add_namespace
from .. import _utils
from ..types import MISSING, MISSING_TYPE, SilentException

Expand Down Expand Up @@ -213,6 +212,12 @@ def __init__(
self.__name__ = fn.__name__
self.__doc__ = fn.__doc__

# The CalcAsync subclass will pass in an async function, but it tells the
# static type checker that it's synchronous. wrap_async() is smart -- if is
# passed an async function, it will not change it.
self._fn: CalcFunctionAsync[T] = _utils.wrap_async(fn)
self._is_async: bool = _utils.is_async_callable(fn)

self._dependents: Dependents = Dependents()
self._invalidated: bool = True
self._running: bool = False
Expand All @@ -232,17 +237,6 @@ def __init__(
session = get_current_session()
self._session = session

# If created within a ModuleSession(), add the namespace_context to the user's
# func so that any UI generated by it will have namespaced ids.
if session is not None and session._ns is not None:
fn = add_namespace(fn, session._ns)

# The CalcAsync subclass will pass in an async function, but it tells the
# static type checker that it's synchronous. wrap_async() is smart -- if is
# passed an async function, it will not change it.
self._fn: CalcFunctionAsync[T] = _utils.wrap_async(fn)
self._is_async: bool = _utils.is_async_callable(fn)

# Use lists to hold (optional) value and error, instead of Optional[T],
# because it makes typing more straightforward. For example if
# .get_value() simply returned self._value, self._value had type
Expand Down Expand Up @@ -426,6 +420,13 @@ def __init__(
self.__name__ = fn.__name__
self.__doc__ = fn.__doc__

# The EffectAsync subclass will pass in an async function, but it tells the
# static type checker that it's synchronous. wrap_async() is smart -- if is
# passed an async function, it will not change it.
self._fn: EffectFunctionAsync = _utils.wrap_async(fn)
# This indicates whether the user's effect function (before wrapping) is async.
self._is_async: bool = _utils.is_async_callable(fn)

self._priority: int = priority
self._suspended = suspended
self._on_resume: Callable[[], None] = lambda: None
Expand All @@ -449,17 +450,6 @@ def __init__(

if self._session is not None:
self._session.on_ended(self._on_session_ended_cb)
# If created within a ModuleSession(), add the namespace_context to the
# user's func so that any UI generated by it will have namespaced ids.
if self._session._ns is not None:
fn = add_namespace(fn, self._session._ns)

# The EffectAsync subclass will pass in an async function, but it tells the
# static type checker that it's synchronous. wrap_async() is smart -- if is
# passed an async function, it will not change it.
self._fn: EffectFunctionAsync = _utils.wrap_async(fn)
# This indicates whether the user's effect function (before wrapping) is async.
self._is_async: bool = _utils.is_async_callable(fn)

# Defer the first running of this until flushReact is called
self._create_context().invalidate()
Expand Down
12 changes: 2 additions & 10 deletions shiny/session/_session.py
Expand Up @@ -50,7 +50,7 @@
from .._fileupload import FileInfo, FileUploadManager
from ..http_staticfiles import FileResponse
from ..input_handler import input_handlers
from .._namespaces import namespaced_id, add_namespace
from .._namespaces import namespaced_id
from ..reactive import Value, Effect, Effect_, isolate, flush
from ..reactive._core import lock
from ..types import SafeException, SilentCancelOutputException, SilentException
Expand Down Expand Up @@ -442,7 +442,7 @@ def send_input_message(self, id: str, message: Dict[str, object]) -> None:
message
The message to send.
"""
msg: Dict[str, object] = {"id": self.ns(id), "message": message}
msg: Dict[str, object] = {"id": namespaced_id(id, self._ns), "message": message}
self._outbound_message_queues["input_messages"].append(msg)
self._request_flush()

Expand Down Expand Up @@ -654,9 +654,6 @@ def _process_ui(self, ui: TagChildArg) -> RenderedDeps:

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

def ns(self, id: str) -> str:
return namespaced_id(id, self._ns)


# ======================================================================================
# Inputs
Expand Down Expand Up @@ -751,11 +748,6 @@ def set_fn(fn: render.RenderFunction) -> None:
# Get the (possibly namespaced) output id
fn_name = namespaced_id(name or fn.__name__, self._ns)

# If created within a ModuleSession(), add the namespace_context to the
# user's func so that any UI generated by it will have namespaced ids.
if self._ns is not None:
fn.__call__ = add_namespace(fn.__call__, self._ns)

# fn is either a regular function or a RenderFunction object. If
# it's the latter, we can give it a bit of metadata, which can be
# used by the
Expand Down

0 comments on commit 26e1063

Please sign in to comment.