-
Notifications
You must be signed in to change notification settings - Fork 60
/
_utils.py
146 lines (113 loc) · 4 KB
/
_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
__all__ = ("get_current_session", "session_context", "require_active_session")
import sys
from contextlib import contextmanager
from contextvars import ContextVar, Token
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, TypeVar, Union
if TYPE_CHECKING:
from ._session import Session
from .._namespaces import namespace_context
if sys.version_info >= (3, 8):
from typing import TypedDict
else:
from typing_extensions import TypedDict
class RenderedDeps(TypedDict):
deps: List[Dict[str, Any]]
html: str
# ==============================================================================
# Context manager for current session (AKA current reactive domain)
# ==============================================================================
_current_session: ContextVar[Optional["Session"]] = ContextVar(
"current_session", default=None
)
def get_current_session() -> Optional["Session"]:
"""
Get the current user session.
Returns
-------
The current session if one is active, otherwise ``None``.
Note
----
Shiny apps should not need to call this function directly. Instead, it's intended to
be used by Shiny developing who wish to create new functions that should only be
called from within an active Shiny session.
See Also
-------
~require_active_session
"""
return _current_session.get()
@contextmanager
def session_context(session: Optional["Session"]):
"""
Context manager for current session.
Parameters
----------
session
A :class:`~shiny.Session` instance. If not provided, it is inferred via
:func:`~shiny.session.get_current_session`.
"""
token: Token[Union[Session, None]] = _current_session.set(session)
try:
with namespace_context(session.ns if session else None):
yield
finally:
_current_session.reset(token)
def require_active_session(session: Optional["Session"]) -> "Session":
"""
Raise an exception if no Shiny session is currently active.
Parameters
----------
session
A :class:`~shiny.Session` instance. If not provided, it is inferred via
:func:`~shiny.session.get_current_session`.
Returns
-------
The session.
Note
----
Shiny apps should not need to call this function directly. Instead, it's intended to
be used by Shiny developing who wish to create new functions that should only be
called from within an active Shiny session.
Raises
------
ValueError
If session is not active.
See Also
-------
~get_current_session
"""
if session is None:
session = get_current_session()
if session is None:
import inspect
call_stack = inspect.stack()
if len(call_stack) > 1:
caller = call_stack[1]
else:
# Uncommon case: this function is called from the top-level, so the caller
# is just require_active_session.
caller = call_stack[0]
calling_fn_name = caller.function
if calling_fn_name == "__init__":
# If the caller is __init__, then we're most likely in the initialization of
# an object. This will get the class name.
calling_fn_name = caller.frame.f_locals["self"].__class__.__name__
raise RuntimeError(
f"{calling_fn_name}() must be called from within an active Shiny session."
)
return session
# Ideally I'd love not to limit the types for T, but if I don't, the type checker has
# trouble figuring out what `T` is supposed to be when run_thunk is actually used. For
# now, just keep expanding the possible types, as needed.
T = TypeVar("T", str, int)
def read_thunk(thunk: Union[Callable[[], T], T]) -> T:
if callable(thunk):
return thunk()
else:
return thunk
def read_thunk_opt(thunk: Optional[Union[Callable[[], T], T]]) -> Optional[T]:
if thunk is None:
return None
elif callable(thunk):
return thunk()
else:
return thunk