forked from posit-dev/py-shiny
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path_namespaces.py
106 lines (74 loc) · 2.6 KB
/
_namespaces.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
from __future__ import annotations
import re
from contextlib import contextmanager
from contextvars import ContextVar, Token
from typing import Generator, Pattern, Union, overload
class ResolvedId(str):
def __call__(self, id: Id) -> ResolvedId:
if isinstance(id, ResolvedId):
return id
validate_id(id)
if self == "":
return ResolvedId(id)
else:
return ResolvedId(self + "-" + id)
Root: ResolvedId = ResolvedId("")
Id = Union[str, ResolvedId]
def current_namespace() -> ResolvedId:
return _current_namespace.get() or _default_namespace
def resolve_id(id: Id) -> ResolvedId:
"""
Resolve an ID, possibly with a module namespace.
Parameters
----------
Args
id: An ID.
Returns
An ID (if in a module, this will contain a namespace prefix).
"""
curr_ns = current_namespace()
return curr_ns(id)
@overload
def resolve_id_or_none(id: None) -> None: ...
@overload
def resolve_id_or_none(id: Id) -> ResolvedId: ...
# Do not export this method from `shiny`. Let developers handle it themselves.
def resolve_id_or_none(id: Id | None) -> ResolvedId | None:
"""
Resolve an ID, possibly with a module namespace. With `None` support.
This method should only be used if `None` values are allowed. If not, use `resolve_id(id=)`.
Parameters
----------
Args
id: An ID or `None`.
Returns
If `id=None`, then `None`. Otherwise, an ID (if in a module, this will contain a namespace prefix).
"""
if id is None:
return None
return resolve_id(id)
# \w is a large set for unicode patterns, that's fine; we mostly want to avoid some
# special characters like space, comma, period, and especially dash
re_valid_id: Pattern[str] = re.compile("^\\.?\\w+$")
def validate_id(id: str) -> None:
if not isinstance(id, str):
raise ValueError("`id` must be a single string")
if id == "":
raise ValueError("`id` must be a non-empty string")
if not re_valid_id.match(id):
raise ValueError(
f"The string '{id}' is not a valid id; only letters, numbers, and "
"underscore are permitted"
)
_current_namespace: ContextVar[ResolvedId | None] = ContextVar(
"current_namespace", default=None
)
_default_namespace: ResolvedId = Root
@contextmanager
def namespace_context(id: Id | None) -> Generator[None, None, None]:
namespace = resolve_id(id) if id else Root
token: Token[ResolvedId | None] = _current_namespace.set(namespace)
try:
yield
finally:
_current_namespace.reset(token)