telegrinder 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of telegrinder might be problematic. Click here for more details.
- telegrinder/__init__.py +144 -144
- telegrinder/api/__init__.py +8 -8
- telegrinder/api/api.py +93 -93
- telegrinder/api/error.py +16 -16
- telegrinder/api/response.py +20 -20
- telegrinder/api/token.py +36 -36
- telegrinder/bot/__init__.py +66 -66
- telegrinder/bot/bot.py +76 -76
- telegrinder/bot/cute_types/__init__.py +11 -11
- telegrinder/bot/cute_types/base.py +258 -234
- telegrinder/bot/cute_types/callback_query.py +382 -382
- telegrinder/bot/cute_types/chat_join_request.py +61 -61
- telegrinder/bot/cute_types/chat_member_updated.py +160 -160
- telegrinder/bot/cute_types/inline_query.py +53 -53
- telegrinder/bot/cute_types/message.py +2631 -2631
- telegrinder/bot/cute_types/update.py +75 -75
- telegrinder/bot/cute_types/utils.py +92 -92
- telegrinder/bot/dispatch/__init__.py +55 -55
- telegrinder/bot/dispatch/abc.py +77 -77
- telegrinder/bot/dispatch/context.py +92 -92
- telegrinder/bot/dispatch/dispatch.py +202 -201
- telegrinder/bot/dispatch/handler/__init__.py +13 -13
- telegrinder/bot/dispatch/handler/abc.py +24 -24
- telegrinder/bot/dispatch/handler/audio_reply.py +44 -44
- telegrinder/bot/dispatch/handler/base.py +57 -57
- telegrinder/bot/dispatch/handler/document_reply.py +44 -44
- telegrinder/bot/dispatch/handler/func.py +128 -123
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -43
- telegrinder/bot/dispatch/handler/message_reply.py +36 -36
- telegrinder/bot/dispatch/handler/photo_reply.py +44 -44
- telegrinder/bot/dispatch/handler/sticker_reply.py +37 -37
- telegrinder/bot/dispatch/handler/video_reply.py +44 -44
- telegrinder/bot/dispatch/middleware/__init__.py +3 -3
- telegrinder/bot/dispatch/middleware/abc.py +16 -16
- telegrinder/bot/dispatch/process.py +132 -132
- telegrinder/bot/dispatch/return_manager/__init__.py +13 -13
- telegrinder/bot/dispatch/return_manager/abc.py +108 -108
- telegrinder/bot/dispatch/return_manager/callback_query.py +20 -20
- telegrinder/bot/dispatch/return_manager/inline_query.py +15 -15
- telegrinder/bot/dispatch/return_manager/message.py +36 -36
- telegrinder/bot/dispatch/view/__init__.py +13 -13
- telegrinder/bot/dispatch/view/abc.py +41 -41
- telegrinder/bot/dispatch/view/base.py +200 -211
- telegrinder/bot/dispatch/view/box.py +129 -129
- telegrinder/bot/dispatch/view/callback_query.py +17 -17
- telegrinder/bot/dispatch/view/chat_join_request.py +16 -16
- telegrinder/bot/dispatch/view/chat_member.py +39 -39
- telegrinder/bot/dispatch/view/inline_query.py +17 -17
- telegrinder/bot/dispatch/view/message.py +44 -44
- telegrinder/bot/dispatch/view/raw.py +114 -118
- telegrinder/bot/dispatch/waiter_machine/__init__.py +17 -17
- telegrinder/bot/dispatch/waiter_machine/actions.py +13 -13
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +8 -8
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +57 -57
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +53 -53
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +19 -19
- telegrinder/bot/dispatch/waiter_machine/machine.py +168 -170
- telegrinder/bot/dispatch/waiter_machine/middleware.py +89 -89
- telegrinder/bot/dispatch/waiter_machine/short_state.py +65 -65
- telegrinder/bot/polling/__init__.py +4 -4
- telegrinder/bot/polling/abc.py +25 -25
- telegrinder/bot/polling/polling.py +131 -131
- telegrinder/bot/rules/__init__.py +62 -62
- telegrinder/bot/rules/abc.py +238 -238
- telegrinder/bot/rules/adapter/__init__.py +9 -9
- telegrinder/bot/rules/adapter/abc.py +29 -29
- telegrinder/bot/rules/adapter/errors.py +5 -5
- telegrinder/bot/rules/adapter/event.py +76 -76
- telegrinder/bot/rules/adapter/node.py +48 -48
- telegrinder/bot/rules/adapter/raw_update.py +30 -30
- telegrinder/bot/rules/callback_data.py +171 -171
- telegrinder/bot/rules/chat_join.py +48 -48
- telegrinder/bot/rules/command.py +126 -126
- telegrinder/bot/rules/enum_text.py +36 -36
- telegrinder/bot/rules/func.py +26 -26
- telegrinder/bot/rules/fuzzy.py +24 -24
- telegrinder/bot/rules/inline.py +60 -60
- telegrinder/bot/rules/integer.py +20 -20
- telegrinder/bot/rules/is_from.py +146 -146
- telegrinder/bot/rules/markup.py +43 -43
- telegrinder/bot/rules/mention.py +14 -14
- telegrinder/bot/rules/message.py +17 -17
- telegrinder/bot/rules/message_entities.py +35 -35
- telegrinder/bot/rules/node.py +27 -27
- telegrinder/bot/rules/regex.py +37 -37
- telegrinder/bot/rules/rule_enum.py +72 -72
- telegrinder/bot/rules/start.py +42 -42
- telegrinder/bot/rules/state.py +37 -37
- telegrinder/bot/rules/text.py +33 -33
- telegrinder/bot/rules/update.py +15 -15
- telegrinder/bot/scenario/__init__.py +5 -5
- telegrinder/bot/scenario/abc.py +19 -19
- telegrinder/bot/scenario/checkbox.py +167 -147
- telegrinder/bot/scenario/choice.py +46 -44
- telegrinder/client/__init__.py +4 -4
- telegrinder/client/abc.py +75 -75
- telegrinder/client/aiohttp.py +130 -130
- telegrinder/model.py +244 -244
- telegrinder/modules.py +237 -237
- telegrinder/msgspec_json.py +14 -14
- telegrinder/msgspec_utils.py +410 -410
- telegrinder/node/__init__.py +20 -20
- telegrinder/node/attachment.py +92 -92
- telegrinder/node/base.py +143 -144
- telegrinder/node/callback_query.py +14 -14
- telegrinder/node/command.py +33 -33
- telegrinder/node/composer.py +196 -184
- telegrinder/node/container.py +27 -27
- telegrinder/node/event.py +71 -73
- telegrinder/node/me.py +16 -16
- telegrinder/node/message.py +14 -14
- telegrinder/node/polymorphic.py +48 -52
- telegrinder/node/rule.py +76 -76
- telegrinder/node/scope.py +38 -38
- telegrinder/node/source.py +71 -71
- telegrinder/node/text.py +21 -21
- telegrinder/node/tools/__init__.py +3 -3
- telegrinder/node/tools/generator.py +40 -40
- telegrinder/node/update.py +15 -15
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +74 -74
- telegrinder/tools/buttons.py +79 -79
- telegrinder/tools/error_handler/__init__.py +7 -7
- telegrinder/tools/error_handler/abc.py +33 -33
- telegrinder/tools/error_handler/error.py +9 -9
- telegrinder/tools/error_handler/error_handler.py +193 -193
- telegrinder/tools/formatting/__init__.py +46 -46
- telegrinder/tools/formatting/html.py +308 -308
- telegrinder/tools/formatting/links.py +33 -33
- telegrinder/tools/formatting/spec_html_formats.py +111 -111
- telegrinder/tools/functional.py +12 -12
- telegrinder/tools/global_context/__init__.py +7 -7
- telegrinder/tools/global_context/abc.py +63 -63
- telegrinder/tools/global_context/global_context.py +412 -412
- telegrinder/tools/global_context/telegrinder_ctx.py +27 -27
- telegrinder/tools/i18n/__init__.py +12 -12
- telegrinder/tools/i18n/abc.py +32 -32
- telegrinder/tools/i18n/middleware/__init__.py +3 -3
- telegrinder/tools/i18n/middleware/abc.py +25 -25
- telegrinder/tools/i18n/simple.py +43 -43
- telegrinder/tools/kb_set/__init__.py +4 -4
- telegrinder/tools/kb_set/base.py +15 -15
- telegrinder/tools/kb_set/yaml.py +63 -63
- telegrinder/tools/keyboard.py +128 -128
- telegrinder/tools/limited_dict.py +37 -37
- telegrinder/tools/loop_wrapper/__init__.py +4 -4
- telegrinder/tools/loop_wrapper/abc.py +15 -15
- telegrinder/tools/loop_wrapper/loop_wrapper.py +216 -216
- telegrinder/tools/magic.py +168 -168
- telegrinder/tools/parse_mode.py +6 -6
- telegrinder/tools/state_storage/__init__.py +4 -4
- telegrinder/tools/state_storage/abc.py +35 -35
- telegrinder/tools/state_storage/memory.py +25 -25
- telegrinder/types/__init__.py +6 -6
- telegrinder/types/enums.py +672 -672
- telegrinder/types/methods.py +4633 -4633
- telegrinder/types/objects.py +6317 -6317
- telegrinder/verification_utils.py +32 -32
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/LICENSE +22 -22
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/METADATA +1 -1
- telegrinder-0.3.2.dist-info/RECORD +164 -0
- telegrinder-0.3.1.dist-info/RECORD +0 -164
- {telegrinder-0.3.1.dist-info → telegrinder-0.3.2.dist-info}/WHEEL +0 -0
|
@@ -1,413 +1,413 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
from copy import deepcopy
|
|
3
|
-
from functools import wraps
|
|
4
|
-
|
|
5
|
-
import typing_extensions as typing
|
|
6
|
-
from fntypes.co import Error, Nothing, Ok, Option, Result, Some
|
|
7
|
-
|
|
8
|
-
from telegrinder.modules import logger
|
|
9
|
-
from telegrinder.msgspec_utils import msgspec_convert
|
|
10
|
-
|
|
11
|
-
from .abc import ABCGlobalContext, CtxVar, CtxVariable, GlobalCtxVar
|
|
12
|
-
|
|
13
|
-
T = typing.TypeVar("T")
|
|
14
|
-
F = typing.TypeVar("F", bound=typing.Callable)
|
|
15
|
-
CtxValueT = typing.TypeVar("CtxValueT", default=typing.Any)
|
|
16
|
-
|
|
17
|
-
if typing.TYPE_CHECKING:
|
|
18
|
-
_: typing.TypeAlias = None
|
|
19
|
-
else:
|
|
20
|
-
_ = lambda: None
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def type_check(value: object, value_type: type[T]) -> typing.TypeGuard[T]:
|
|
24
|
-
if value_type in (typing.Any, object):
|
|
25
|
-
return True
|
|
26
|
-
match msgspec_convert(value, value_type):
|
|
27
|
-
case Ok(v):
|
|
28
|
-
return type(value) is type(v)
|
|
29
|
-
case Error(_):
|
|
30
|
-
return False
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def is_dunder(name: str) -> bool:
|
|
34
|
-
return name.startswith("__") and name.endswith("__")
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def get_orig_class(obj: T) -> type[T]:
|
|
38
|
-
return getattr(obj, "__orig_class__", obj.__class__)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def root_protection(func: F) -> F:
|
|
42
|
-
if func.__name__ not in ("__setattr__", "__getattr__", "__delattr__"):
|
|
43
|
-
raise RuntimeError(
|
|
44
|
-
"You cannot decorate a {!r} function with this decorator, only "
|
|
45
|
-
"'__setattr__', __getattr__', '__delattr__' methods.".format(
|
|
46
|
-
func.__name__,
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
@wraps(func)
|
|
51
|
-
def wrapper(self: "GlobalContext", name: str, /, *args) -> typing.Any:
|
|
52
|
-
if self.is_root_attribute(name) and name in (self.__dict__ | self.__class__.__dict__):
|
|
53
|
-
root_attr = self.get_root_attribute(name).unwrap()
|
|
54
|
-
if all((not root_attr.can_be_rewritten, not root_attr.can_be_read)):
|
|
55
|
-
raise AttributeError(f"Unable to set, get, delete root attribute {name!r}.")
|
|
56
|
-
if func.__name__ == "__setattr__" and not root_attr.can_be_rewritten:
|
|
57
|
-
raise AttributeError(f"Unable to set root attribute {name!r}.")
|
|
58
|
-
if func.__name__ == "__getattr__" and not root_attr.can_be_read:
|
|
59
|
-
raise AttributeError(f"Unable to get root attribute {name!r}.")
|
|
60
|
-
if func.__name__ == "__delattr__":
|
|
61
|
-
raise AttributeError(f"Unable to delete root attribute {name!r}.")
|
|
62
|
-
|
|
63
|
-
return func(self, name, *args) # type: ignore
|
|
64
|
-
|
|
65
|
-
return wrapper # type: ignore
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def ctx_var(value: T, *, const: bool = False) -> T:
|
|
69
|
-
"""Example:
|
|
70
|
-
```
|
|
71
|
-
class MyCtx(GlobalContext):
|
|
72
|
-
name: typing.Final[str]
|
|
73
|
-
URL: typing.Final = ctx_var("https://google.com", const=True)
|
|
74
|
-
|
|
75
|
-
ctx = MyCtx(name=ctx_var("Alex", const=True))
|
|
76
|
-
ctx.URL #: 'https://google.com'
|
|
77
|
-
ctx.URL = '...' #: type checking error & exception 'TypeError'
|
|
78
|
-
```
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
return typing.cast(T, CtxVar(value, const=const))
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@dataclasses.dataclass(frozen=True, eq=False, slots=True)
|
|
85
|
-
class RootAttr:
|
|
86
|
-
name: str
|
|
87
|
-
can_be_read: bool = dataclasses.field(default=True, kw_only=True)
|
|
88
|
-
can_be_rewritten: bool = dataclasses.field(default=False, kw_only=True)
|
|
89
|
-
|
|
90
|
-
def __eq__(self, __value: str) -> bool:
|
|
91
|
-
return self.name == __value
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
@dataclasses.dataclass(repr=False, frozen=True, slots=True)
|
|
95
|
-
class Storage:
|
|
96
|
-
_storage: dict[str, "GlobalContext"] = dataclasses.field(
|
|
97
|
-
default_factory=lambda: {},
|
|
98
|
-
init=False,
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
def __repr__(self) -> str:
|
|
102
|
-
return "<ContextStorage: %s>" % ", ".join("ctx @" + repr(x) for x in self._storage)
|
|
103
|
-
|
|
104
|
-
@property
|
|
105
|
-
def storage(self) -> dict[str, "GlobalContext"]:
|
|
106
|
-
return self._storage.copy()
|
|
107
|
-
|
|
108
|
-
def set(self, name: str, ctx: "GlobalContext") -> None:
|
|
109
|
-
self._storage.setdefault(name, ctx)
|
|
110
|
-
|
|
111
|
-
def get(self, ctx_name: str) -> Option["GlobalContext"]:
|
|
112
|
-
ctx = self._storage.get(ctx_name)
|
|
113
|
-
return Some(ctx) if ctx is not None else Nothing()
|
|
114
|
-
|
|
115
|
-
def delete(self, ctx_name: str) -> None:
|
|
116
|
-
assert (
|
|
117
|
-
self._storage.pop(ctx_name, None) is not None
|
|
118
|
-
), f"Context {ctx_name!r} is not defined in storage."
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@typing.dataclass_transform(
|
|
122
|
-
kw_only_default=True,
|
|
123
|
-
order_default=True,
|
|
124
|
-
field_specifiers=(ctx_var,),
|
|
125
|
-
)
|
|
126
|
-
class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]):
|
|
127
|
-
"""GlobalContext.
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
ctx = GlobalContext()
|
|
131
|
-
ctx["client"] = Client()
|
|
132
|
-
ctx.address = CtxVar("128.0.0.7:8888", const=True)
|
|
133
|
-
|
|
134
|
-
def request():
|
|
135
|
-
data = {"user": "root_user", "password": "secret_password"}
|
|
136
|
-
ctx.client.request(ctx.address + "/login", data)
|
|
137
|
-
"""
|
|
138
|
-
|
|
139
|
-
__ctx_name__: str | None
|
|
140
|
-
__storage__: typing.ClassVar[Storage] = Storage()
|
|
141
|
-
__root_attributes__: typing.ClassVar[tuple[RootAttr, ...]] = (
|
|
142
|
-
RootAttr("__ctx_name__"),
|
|
143
|
-
RootAttr("__root_attributes__"),
|
|
144
|
-
RootAttr("__storage__"),
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
def __new__(
|
|
148
|
-
cls,
|
|
149
|
-
ctx_name: str | None = None,
|
|
150
|
-
/,
|
|
151
|
-
**variables: typing.Any | CtxVar[CtxValueT],
|
|
152
|
-
) -> typing.Self:
|
|
153
|
-
"""Create or get from storage a new `GlobalContext` object."""
|
|
154
|
-
|
|
155
|
-
if not issubclass(GlobalContext, cls):
|
|
156
|
-
defaults = {}
|
|
157
|
-
for name in cls.__annotations__:
|
|
158
|
-
if name in cls.__dict__ and name not in cls.__root_attributes__:
|
|
159
|
-
defaults[name] = getattr(cls, name)
|
|
160
|
-
delattr(cls, name)
|
|
161
|
-
if isinstance(defaults[name], CtxVar) and defaults[name].const:
|
|
162
|
-
variables.pop(name, None)
|
|
163
|
-
|
|
164
|
-
variables = defaults | variables
|
|
165
|
-
|
|
166
|
-
ctx_name = getattr(cls, "__ctx_name__", ctx_name)
|
|
167
|
-
if ctx_name is None:
|
|
168
|
-
ctx = dict.__new__(cls)
|
|
169
|
-
elif ctx_name in cls.__storage__.storage:
|
|
170
|
-
ctx = cls.__storage__.get(ctx_name).unwrap()
|
|
171
|
-
else:
|
|
172
|
-
ctx = dict.__new__(cls, ctx_name)
|
|
173
|
-
cls.__storage__.set(ctx_name, ctx)
|
|
174
|
-
|
|
175
|
-
ctx.set_context_variables(variables)
|
|
176
|
-
return ctx # type: ignore
|
|
177
|
-
|
|
178
|
-
def __init__(
|
|
179
|
-
self,
|
|
180
|
-
ctx_name: str | None = None,
|
|
181
|
-
/,
|
|
182
|
-
**variables: CtxValueT | CtxVariable[CtxValueT],
|
|
183
|
-
):
|
|
184
|
-
"""Initialization of `GlobalContext` with passed variables."""
|
|
185
|
-
|
|
186
|
-
if not hasattr(self, "__ctx_name__"):
|
|
187
|
-
self.__ctx_name__ = ctx_name
|
|
188
|
-
|
|
189
|
-
if variables and not self:
|
|
190
|
-
self.set_context_variables(variables)
|
|
191
|
-
|
|
192
|
-
def __repr__(self) -> str:
|
|
193
|
-
return "<{} -> ({})>".format(
|
|
194
|
-
f"{self.__class__.__name__}@{self.ctx_name!r}",
|
|
195
|
-
", ".join(repr(var) for var in self),
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
def __eq__(self, __value: "GlobalContext") -> bool:
|
|
199
|
-
"""Returns True if the names of context stores
|
|
200
|
-
that use self and __value instances are equivalent."""
|
|
201
|
-
|
|
202
|
-
return isinstance(__value, GlobalContext) and self.__ctx_name__ == __value.__ctx_name__
|
|
203
|
-
|
|
204
|
-
def __setitem__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
205
|
-
if is_dunder(__name):
|
|
206
|
-
raise NameError("Cannot set a context variable with dunder name.")
|
|
207
|
-
var = self.get(__name)
|
|
208
|
-
if var and var.unwrap().const:
|
|
209
|
-
raise TypeError(f"Unable to set variable {__name!r}, because it's a constant.")
|
|
210
|
-
dict.__setitem__(self, __name, GlobalCtxVar.collect(__name, __value))
|
|
211
|
-
|
|
212
|
-
def __getitem__(self, __name: str) -> CtxValueT:
|
|
213
|
-
return self.get(__name).unwrap().value
|
|
214
|
-
|
|
215
|
-
def __delitem__(self, __name: str):
|
|
216
|
-
var = self.get(__name).unwrap()
|
|
217
|
-
if var.const:
|
|
218
|
-
raise TypeError(f"Unable to delete variable {__name!r}, because it's a constant.")
|
|
219
|
-
dict.__delitem__(self, __name)
|
|
220
|
-
|
|
221
|
-
@root_protection
|
|
222
|
-
def __setattr__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
223
|
-
"""Setting a context variable."""
|
|
224
|
-
|
|
225
|
-
if is_dunder(__name):
|
|
226
|
-
return object.__setattr__(self, __name, __value)
|
|
227
|
-
self.__setitem__(__name, __value)
|
|
228
|
-
|
|
229
|
-
@root_protection
|
|
230
|
-
def __getattr__(self, __name: str) -> CtxValueT:
|
|
231
|
-
"""Getting a context variable."""
|
|
232
|
-
|
|
233
|
-
if is_dunder(__name):
|
|
234
|
-
return object.__getattribute__(self, __name)
|
|
235
|
-
return self.__getitem__(__name)
|
|
236
|
-
|
|
237
|
-
@root_protection
|
|
238
|
-
def __delattr__(self, __name: str) -> None:
|
|
239
|
-
"""Removing a context variable."""
|
|
240
|
-
|
|
241
|
-
if is_dunder(__name):
|
|
242
|
-
return object.__delattr__(self, __name)
|
|
243
|
-
self.__delitem__(__name)
|
|
244
|
-
|
|
245
|
-
@property
|
|
246
|
-
def ctx_name(self) -> str:
|
|
247
|
-
"""Context name."""
|
|
248
|
-
|
|
249
|
-
return self.__ctx_name__ or "<Unnamed ctx at %#x>" % id(self)
|
|
250
|
-
|
|
251
|
-
@classmethod
|
|
252
|
-
def is_root_attribute(cls, name: str) -> bool:
|
|
253
|
-
"""Returns True if exists root attribute
|
|
254
|
-
otherwise False."""
|
|
255
|
-
|
|
256
|
-
return name in cls.__root_attributes__
|
|
257
|
-
|
|
258
|
-
def set_context_variables(
|
|
259
|
-
self, variables: typing.Mapping[str, CtxValueT | CtxVariable[CtxValueT]]
|
|
260
|
-
) -> None:
|
|
261
|
-
"""Set context variables from mapping."""
|
|
262
|
-
|
|
263
|
-
for name, var in variables.items():
|
|
264
|
-
self[name] = var
|
|
265
|
-
|
|
266
|
-
def get_root_attribute(self, name: str) -> Option[RootAttr]:
|
|
267
|
-
"""Get root attribute by name."""
|
|
268
|
-
|
|
269
|
-
if self.is_root_attribute(name):
|
|
270
|
-
for rattr in self.__root_attributes__:
|
|
271
|
-
if rattr.name == name:
|
|
272
|
-
return Some(rattr)
|
|
273
|
-
return Nothing()
|
|
274
|
-
|
|
275
|
-
def items(self) -> list[tuple[str, GlobalCtxVar[CtxValueT]]]:
|
|
276
|
-
"""Return context variables as set-like items."""
|
|
277
|
-
|
|
278
|
-
return list(dict.items(self))
|
|
279
|
-
|
|
280
|
-
def keys(self) -> list[str]:
|
|
281
|
-
"""Returns context variable names as keys."""
|
|
282
|
-
|
|
283
|
-
return list(dict.keys(self))
|
|
284
|
-
|
|
285
|
-
def values(self) -> list[GlobalCtxVar[CtxValueT]]:
|
|
286
|
-
"""Returns context variables as values."""
|
|
287
|
-
|
|
288
|
-
return list(dict.values(self))
|
|
289
|
-
|
|
290
|
-
def update(self, other: typing.Self) -> None:
|
|
291
|
-
"""Update context."""
|
|
292
|
-
|
|
293
|
-
dict.update(dict(other.items()))
|
|
294
|
-
|
|
295
|
-
def copy(self) -> typing.Self:
|
|
296
|
-
"""Copy context. Returns copied context without ctx_name."""
|
|
297
|
-
|
|
298
|
-
return self.__class__(**self.dict())
|
|
299
|
-
|
|
300
|
-
def dict(self) -> dict[str, GlobalCtxVar[CtxValueT]]:
|
|
301
|
-
"""Returns context as dict."""
|
|
302
|
-
|
|
303
|
-
return {name: deepcopy(var) for name, var in self.items()}
|
|
304
|
-
|
|
305
|
-
@typing.overload
|
|
306
|
-
def pop(self, var_name: str) -> Option[GlobalCtxVar[CtxValueT]]: ...
|
|
307
|
-
|
|
308
|
-
@typing.overload
|
|
309
|
-
def pop(
|
|
310
|
-
self,
|
|
311
|
-
var_name: str,
|
|
312
|
-
var_value_type: type[T],
|
|
313
|
-
) -> Option[GlobalCtxVar[T]]: ...
|
|
314
|
-
|
|
315
|
-
def pop(self, var_name: str, var_value_type=object): # type: ignore
|
|
316
|
-
"""Pop context variable by name."""
|
|
317
|
-
|
|
318
|
-
val = self.get(var_name, var_value_type) # type: ignore
|
|
319
|
-
if val:
|
|
320
|
-
del self[var_name]
|
|
321
|
-
return val
|
|
322
|
-
return Nothing()
|
|
323
|
-
|
|
324
|
-
@typing.overload
|
|
325
|
-
def get(self, var_name: str) -> Option[GlobalCtxVar[CtxValueT]]: ...
|
|
326
|
-
|
|
327
|
-
@typing.overload
|
|
328
|
-
def get(
|
|
329
|
-
self,
|
|
330
|
-
var_name: str,
|
|
331
|
-
var_value_type: type[T],
|
|
332
|
-
) -> Option[GlobalCtxVar[T]]: ...
|
|
333
|
-
|
|
334
|
-
def get(self, var_name, var_value_type=object): # type: ignore
|
|
335
|
-
"""Get context variable by name."""
|
|
336
|
-
|
|
337
|
-
var_value_type = typing.Any if var_value_type is object else var_value_type
|
|
338
|
-
generic_types = typing.get_args(get_orig_class(self))
|
|
339
|
-
if generic_types and var_value_type is object:
|
|
340
|
-
var_value_type = generic_types[0]
|
|
341
|
-
var = dict.get(self, var_name)
|
|
342
|
-
if var is None:
|
|
343
|
-
return Nothing()
|
|
344
|
-
assert type_check(
|
|
345
|
-
var.value, var_value_type
|
|
346
|
-
), "Context variable value type of {!r} does not correspond to the expected type {!r}.".format(
|
|
347
|
-
type(var.value).__name__,
|
|
348
|
-
(
|
|
349
|
-
getattr(var_value_type, "__name__")
|
|
350
|
-
if isinstance(var_value_type, type)
|
|
351
|
-
else repr(var_value_type)
|
|
352
|
-
),
|
|
353
|
-
)
|
|
354
|
-
return Some(var)
|
|
355
|
-
|
|
356
|
-
@typing.overload
|
|
357
|
-
def get_value(self, var_name: str) -> Option[CtxValueT]: ...
|
|
358
|
-
|
|
359
|
-
@typing.overload
|
|
360
|
-
def get_value(
|
|
361
|
-
self,
|
|
362
|
-
var_name: str,
|
|
363
|
-
var_value_type: type[T],
|
|
364
|
-
) -> Option[T]: ...
|
|
365
|
-
|
|
366
|
-
def get_value(self, var_name, var_value_type=object): # type: ignore
|
|
367
|
-
"""Get context variable value by name."""
|
|
368
|
-
|
|
369
|
-
return self.get(var_name, var_value_type).map(lambda var: var.value)
|
|
370
|
-
|
|
371
|
-
def rename(self, old_var_name: str, new_var_name: str) -> Result[_, str]:
|
|
372
|
-
"""Rename context variable."""
|
|
373
|
-
|
|
374
|
-
var = self.get(old_var_name).unwrap()
|
|
375
|
-
if var.const:
|
|
376
|
-
return Error(f"Unable to rename variable {old_var_name!r}, " "because it's a constant.")
|
|
377
|
-
del self[old_var_name]
|
|
378
|
-
self[new_var_name] = var.value
|
|
379
|
-
return Ok(_())
|
|
380
|
-
|
|
381
|
-
def clear(self, *, include_consts: bool = False) -> None:
|
|
382
|
-
"""Clear context. If `include_consts = True`,
|
|
383
|
-
then the context is completely cleared."""
|
|
384
|
-
|
|
385
|
-
if not self:
|
|
386
|
-
return
|
|
387
|
-
if include_consts:
|
|
388
|
-
logger.warning(
|
|
389
|
-
"Constants from the global context {!r} have been cleaned up!",
|
|
390
|
-
self.ctx_name + " at %#x" % id(self),
|
|
391
|
-
)
|
|
392
|
-
return dict.clear(self)
|
|
393
|
-
|
|
394
|
-
for name, var in self.dict().items():
|
|
395
|
-
if not var.const:
|
|
396
|
-
del self[name]
|
|
397
|
-
|
|
398
|
-
def delete_ctx(self) -> Result[_, str]:
|
|
399
|
-
"""Delete context by `ctx_name`."""
|
|
400
|
-
|
|
401
|
-
if not self.__ctx_name__:
|
|
402
|
-
return Error("Cannot delete unnamed context.")
|
|
403
|
-
ctx = self.__storage__.get(self.ctx_name).unwrap()
|
|
404
|
-
dict.clear(ctx)
|
|
405
|
-
self.__storage__.delete(self.ctx_name)
|
|
406
|
-
logger.warning(f"Global context {self.ctx_name!r} has been deleted!")
|
|
407
|
-
return Ok(_())
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
__all__ = (
|
|
1
|
+
import dataclasses
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from functools import wraps
|
|
4
|
+
|
|
5
|
+
import typing_extensions as typing
|
|
6
|
+
from fntypes.co import Error, Nothing, Ok, Option, Result, Some
|
|
7
|
+
|
|
8
|
+
from telegrinder.modules import logger
|
|
9
|
+
from telegrinder.msgspec_utils import msgspec_convert
|
|
10
|
+
|
|
11
|
+
from .abc import ABCGlobalContext, CtxVar, CtxVariable, GlobalCtxVar
|
|
12
|
+
|
|
13
|
+
T = typing.TypeVar("T")
|
|
14
|
+
F = typing.TypeVar("F", bound=typing.Callable)
|
|
15
|
+
CtxValueT = typing.TypeVar("CtxValueT", default=typing.Any)
|
|
16
|
+
|
|
17
|
+
if typing.TYPE_CHECKING:
|
|
18
|
+
_: typing.TypeAlias = None
|
|
19
|
+
else:
|
|
20
|
+
_ = lambda: None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def type_check(value: object, value_type: type[T]) -> typing.TypeGuard[T]:
|
|
24
|
+
if value_type in (typing.Any, object):
|
|
25
|
+
return True
|
|
26
|
+
match msgspec_convert(value, value_type):
|
|
27
|
+
case Ok(v):
|
|
28
|
+
return type(value) is type(v)
|
|
29
|
+
case Error(_):
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_dunder(name: str) -> bool:
|
|
34
|
+
return name.startswith("__") and name.endswith("__")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_orig_class(obj: T) -> type[T]:
|
|
38
|
+
return getattr(obj, "__orig_class__", obj.__class__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def root_protection(func: F) -> F:
|
|
42
|
+
if func.__name__ not in ("__setattr__", "__getattr__", "__delattr__"):
|
|
43
|
+
raise RuntimeError(
|
|
44
|
+
"You cannot decorate a {!r} function with this decorator, only "
|
|
45
|
+
"'__setattr__', __getattr__', '__delattr__' methods.".format(
|
|
46
|
+
func.__name__,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@wraps(func)
|
|
51
|
+
def wrapper(self: "GlobalContext", name: str, /, *args) -> typing.Any:
|
|
52
|
+
if self.is_root_attribute(name) and name in (self.__dict__ | self.__class__.__dict__):
|
|
53
|
+
root_attr = self.get_root_attribute(name).unwrap()
|
|
54
|
+
if all((not root_attr.can_be_rewritten, not root_attr.can_be_read)):
|
|
55
|
+
raise AttributeError(f"Unable to set, get, delete root attribute {name!r}.")
|
|
56
|
+
if func.__name__ == "__setattr__" and not root_attr.can_be_rewritten:
|
|
57
|
+
raise AttributeError(f"Unable to set root attribute {name!r}.")
|
|
58
|
+
if func.__name__ == "__getattr__" and not root_attr.can_be_read:
|
|
59
|
+
raise AttributeError(f"Unable to get root attribute {name!r}.")
|
|
60
|
+
if func.__name__ == "__delattr__":
|
|
61
|
+
raise AttributeError(f"Unable to delete root attribute {name!r}.")
|
|
62
|
+
|
|
63
|
+
return func(self, name, *args) # type: ignore
|
|
64
|
+
|
|
65
|
+
return wrapper # type: ignore
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def ctx_var(value: T, *, const: bool = False) -> T:
|
|
69
|
+
"""Example:
|
|
70
|
+
```
|
|
71
|
+
class MyCtx(GlobalContext):
|
|
72
|
+
name: typing.Final[str]
|
|
73
|
+
URL: typing.Final = ctx_var("https://google.com", const=True)
|
|
74
|
+
|
|
75
|
+
ctx = MyCtx(name=ctx_var("Alex", const=True))
|
|
76
|
+
ctx.URL #: 'https://google.com'
|
|
77
|
+
ctx.URL = '...' #: type checking error & exception 'TypeError'
|
|
78
|
+
```
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
return typing.cast(T, CtxVar(value, const=const))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclasses.dataclass(frozen=True, eq=False, slots=True)
|
|
85
|
+
class RootAttr:
|
|
86
|
+
name: str
|
|
87
|
+
can_be_read: bool = dataclasses.field(default=True, kw_only=True)
|
|
88
|
+
can_be_rewritten: bool = dataclasses.field(default=False, kw_only=True)
|
|
89
|
+
|
|
90
|
+
def __eq__(self, __value: str) -> bool:
|
|
91
|
+
return self.name == __value
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclasses.dataclass(repr=False, frozen=True, slots=True)
|
|
95
|
+
class Storage:
|
|
96
|
+
_storage: dict[str, "GlobalContext"] = dataclasses.field(
|
|
97
|
+
default_factory=lambda: {},
|
|
98
|
+
init=False,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def __repr__(self) -> str:
|
|
102
|
+
return "<ContextStorage: %s>" % ", ".join("ctx @" + repr(x) for x in self._storage)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def storage(self) -> dict[str, "GlobalContext"]:
|
|
106
|
+
return self._storage.copy()
|
|
107
|
+
|
|
108
|
+
def set(self, name: str, ctx: "GlobalContext") -> None:
|
|
109
|
+
self._storage.setdefault(name, ctx)
|
|
110
|
+
|
|
111
|
+
def get(self, ctx_name: str) -> Option["GlobalContext"]:
|
|
112
|
+
ctx = self._storage.get(ctx_name)
|
|
113
|
+
return Some(ctx) if ctx is not None else Nothing()
|
|
114
|
+
|
|
115
|
+
def delete(self, ctx_name: str) -> None:
|
|
116
|
+
assert (
|
|
117
|
+
self._storage.pop(ctx_name, None) is not None
|
|
118
|
+
), f"Context {ctx_name!r} is not defined in storage."
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@typing.dataclass_transform(
|
|
122
|
+
kw_only_default=True,
|
|
123
|
+
order_default=True,
|
|
124
|
+
field_specifiers=(ctx_var,),
|
|
125
|
+
)
|
|
126
|
+
class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]):
|
|
127
|
+
"""GlobalContext.
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
ctx = GlobalContext()
|
|
131
|
+
ctx["client"] = Client()
|
|
132
|
+
ctx.address = CtxVar("128.0.0.7:8888", const=True)
|
|
133
|
+
|
|
134
|
+
def request():
|
|
135
|
+
data = {"user": "root_user", "password": "secret_password"}
|
|
136
|
+
ctx.client.request(ctx.address + "/login", data)
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
__ctx_name__: str | None
|
|
140
|
+
__storage__: typing.ClassVar[Storage] = Storage()
|
|
141
|
+
__root_attributes__: typing.ClassVar[tuple[RootAttr, ...]] = (
|
|
142
|
+
RootAttr("__ctx_name__"),
|
|
143
|
+
RootAttr("__root_attributes__"),
|
|
144
|
+
RootAttr("__storage__"),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def __new__(
|
|
148
|
+
cls,
|
|
149
|
+
ctx_name: str | None = None,
|
|
150
|
+
/,
|
|
151
|
+
**variables: typing.Any | CtxVar[CtxValueT],
|
|
152
|
+
) -> typing.Self:
|
|
153
|
+
"""Create or get from storage a new `GlobalContext` object."""
|
|
154
|
+
|
|
155
|
+
if not issubclass(GlobalContext, cls):
|
|
156
|
+
defaults = {}
|
|
157
|
+
for name in cls.__annotations__:
|
|
158
|
+
if name in cls.__dict__ and name not in cls.__root_attributes__:
|
|
159
|
+
defaults[name] = getattr(cls, name)
|
|
160
|
+
delattr(cls, name)
|
|
161
|
+
if isinstance(defaults[name], CtxVar) and defaults[name].const:
|
|
162
|
+
variables.pop(name, None)
|
|
163
|
+
|
|
164
|
+
variables = defaults | variables
|
|
165
|
+
|
|
166
|
+
ctx_name = getattr(cls, "__ctx_name__", ctx_name)
|
|
167
|
+
if ctx_name is None:
|
|
168
|
+
ctx = dict.__new__(cls)
|
|
169
|
+
elif ctx_name in cls.__storage__.storage:
|
|
170
|
+
ctx = cls.__storage__.get(ctx_name).unwrap()
|
|
171
|
+
else:
|
|
172
|
+
ctx = dict.__new__(cls, ctx_name)
|
|
173
|
+
cls.__storage__.set(ctx_name, ctx)
|
|
174
|
+
|
|
175
|
+
ctx.set_context_variables(variables)
|
|
176
|
+
return ctx # type: ignore
|
|
177
|
+
|
|
178
|
+
def __init__(
|
|
179
|
+
self,
|
|
180
|
+
ctx_name: str | None = None,
|
|
181
|
+
/,
|
|
182
|
+
**variables: CtxValueT | CtxVariable[CtxValueT],
|
|
183
|
+
):
|
|
184
|
+
"""Initialization of `GlobalContext` with passed variables."""
|
|
185
|
+
|
|
186
|
+
if not hasattr(self, "__ctx_name__"):
|
|
187
|
+
self.__ctx_name__ = ctx_name
|
|
188
|
+
|
|
189
|
+
if variables and not self:
|
|
190
|
+
self.set_context_variables(variables)
|
|
191
|
+
|
|
192
|
+
def __repr__(self) -> str:
|
|
193
|
+
return "<{} -> ({})>".format(
|
|
194
|
+
f"{self.__class__.__name__}@{self.ctx_name!r}",
|
|
195
|
+
", ".join(repr(var) for var in self),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def __eq__(self, __value: "GlobalContext") -> bool:
|
|
199
|
+
"""Returns True if the names of context stores
|
|
200
|
+
that use self and __value instances are equivalent."""
|
|
201
|
+
|
|
202
|
+
return isinstance(__value, GlobalContext) and self.__ctx_name__ == __value.__ctx_name__
|
|
203
|
+
|
|
204
|
+
def __setitem__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
205
|
+
if is_dunder(__name):
|
|
206
|
+
raise NameError("Cannot set a context variable with dunder name.")
|
|
207
|
+
var = self.get(__name)
|
|
208
|
+
if var and var.unwrap().const:
|
|
209
|
+
raise TypeError(f"Unable to set variable {__name!r}, because it's a constant.")
|
|
210
|
+
dict.__setitem__(self, __name, GlobalCtxVar.collect(__name, __value))
|
|
211
|
+
|
|
212
|
+
def __getitem__(self, __name: str) -> CtxValueT:
|
|
213
|
+
return self.get(__name).unwrap().value
|
|
214
|
+
|
|
215
|
+
def __delitem__(self, __name: str):
|
|
216
|
+
var = self.get(__name).unwrap()
|
|
217
|
+
if var.const:
|
|
218
|
+
raise TypeError(f"Unable to delete variable {__name!r}, because it's a constant.")
|
|
219
|
+
dict.__delitem__(self, __name)
|
|
220
|
+
|
|
221
|
+
@root_protection
|
|
222
|
+
def __setattr__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
|
|
223
|
+
"""Setting a context variable."""
|
|
224
|
+
|
|
225
|
+
if is_dunder(__name):
|
|
226
|
+
return object.__setattr__(self, __name, __value)
|
|
227
|
+
self.__setitem__(__name, __value)
|
|
228
|
+
|
|
229
|
+
@root_protection
|
|
230
|
+
def __getattr__(self, __name: str) -> CtxValueT:
|
|
231
|
+
"""Getting a context variable."""
|
|
232
|
+
|
|
233
|
+
if is_dunder(__name):
|
|
234
|
+
return object.__getattribute__(self, __name)
|
|
235
|
+
return self.__getitem__(__name)
|
|
236
|
+
|
|
237
|
+
@root_protection
|
|
238
|
+
def __delattr__(self, __name: str) -> None:
|
|
239
|
+
"""Removing a context variable."""
|
|
240
|
+
|
|
241
|
+
if is_dunder(__name):
|
|
242
|
+
return object.__delattr__(self, __name)
|
|
243
|
+
self.__delitem__(__name)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def ctx_name(self) -> str:
|
|
247
|
+
"""Context name."""
|
|
248
|
+
|
|
249
|
+
return self.__ctx_name__ or "<Unnamed ctx at %#x>" % id(self)
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def is_root_attribute(cls, name: str) -> bool:
|
|
253
|
+
"""Returns True if exists root attribute
|
|
254
|
+
otherwise False."""
|
|
255
|
+
|
|
256
|
+
return name in cls.__root_attributes__
|
|
257
|
+
|
|
258
|
+
def set_context_variables(
|
|
259
|
+
self, variables: typing.Mapping[str, CtxValueT | CtxVariable[CtxValueT]]
|
|
260
|
+
) -> None:
|
|
261
|
+
"""Set context variables from mapping."""
|
|
262
|
+
|
|
263
|
+
for name, var in variables.items():
|
|
264
|
+
self[name] = var
|
|
265
|
+
|
|
266
|
+
def get_root_attribute(self, name: str) -> Option[RootAttr]:
|
|
267
|
+
"""Get root attribute by name."""
|
|
268
|
+
|
|
269
|
+
if self.is_root_attribute(name):
|
|
270
|
+
for rattr in self.__root_attributes__:
|
|
271
|
+
if rattr.name == name:
|
|
272
|
+
return Some(rattr)
|
|
273
|
+
return Nothing()
|
|
274
|
+
|
|
275
|
+
def items(self) -> list[tuple[str, GlobalCtxVar[CtxValueT]]]:
|
|
276
|
+
"""Return context variables as set-like items."""
|
|
277
|
+
|
|
278
|
+
return list(dict.items(self))
|
|
279
|
+
|
|
280
|
+
def keys(self) -> list[str]:
|
|
281
|
+
"""Returns context variable names as keys."""
|
|
282
|
+
|
|
283
|
+
return list(dict.keys(self))
|
|
284
|
+
|
|
285
|
+
def values(self) -> list[GlobalCtxVar[CtxValueT]]:
|
|
286
|
+
"""Returns context variables as values."""
|
|
287
|
+
|
|
288
|
+
return list(dict.values(self))
|
|
289
|
+
|
|
290
|
+
def update(self, other: typing.Self) -> None:
|
|
291
|
+
"""Update context."""
|
|
292
|
+
|
|
293
|
+
dict.update(dict(other.items()))
|
|
294
|
+
|
|
295
|
+
def copy(self) -> typing.Self:
|
|
296
|
+
"""Copy context. Returns copied context without ctx_name."""
|
|
297
|
+
|
|
298
|
+
return self.__class__(**self.dict())
|
|
299
|
+
|
|
300
|
+
def dict(self) -> dict[str, GlobalCtxVar[CtxValueT]]:
|
|
301
|
+
"""Returns context as dict."""
|
|
302
|
+
|
|
303
|
+
return {name: deepcopy(var) for name, var in self.items()}
|
|
304
|
+
|
|
305
|
+
@typing.overload
|
|
306
|
+
def pop(self, var_name: str) -> Option[GlobalCtxVar[CtxValueT]]: ...
|
|
307
|
+
|
|
308
|
+
@typing.overload
|
|
309
|
+
def pop(
|
|
310
|
+
self,
|
|
311
|
+
var_name: str,
|
|
312
|
+
var_value_type: type[T],
|
|
313
|
+
) -> Option[GlobalCtxVar[T]]: ...
|
|
314
|
+
|
|
315
|
+
def pop(self, var_name: str, var_value_type=object): # type: ignore
|
|
316
|
+
"""Pop context variable by name."""
|
|
317
|
+
|
|
318
|
+
val = self.get(var_name, var_value_type) # type: ignore
|
|
319
|
+
if val:
|
|
320
|
+
del self[var_name]
|
|
321
|
+
return val
|
|
322
|
+
return Nothing()
|
|
323
|
+
|
|
324
|
+
@typing.overload
|
|
325
|
+
def get(self, var_name: str) -> Option[GlobalCtxVar[CtxValueT]]: ...
|
|
326
|
+
|
|
327
|
+
@typing.overload
|
|
328
|
+
def get(
|
|
329
|
+
self,
|
|
330
|
+
var_name: str,
|
|
331
|
+
var_value_type: type[T],
|
|
332
|
+
) -> Option[GlobalCtxVar[T]]: ...
|
|
333
|
+
|
|
334
|
+
def get(self, var_name, var_value_type=object): # type: ignore
|
|
335
|
+
"""Get context variable by name."""
|
|
336
|
+
|
|
337
|
+
var_value_type = typing.Any if var_value_type is object else var_value_type
|
|
338
|
+
generic_types = typing.get_args(get_orig_class(self))
|
|
339
|
+
if generic_types and var_value_type is object:
|
|
340
|
+
var_value_type = generic_types[0]
|
|
341
|
+
var = dict.get(self, var_name)
|
|
342
|
+
if var is None:
|
|
343
|
+
return Nothing()
|
|
344
|
+
assert type_check(
|
|
345
|
+
var.value, var_value_type
|
|
346
|
+
), "Context variable value type of {!r} does not correspond to the expected type {!r}.".format(
|
|
347
|
+
type(var.value).__name__,
|
|
348
|
+
(
|
|
349
|
+
getattr(var_value_type, "__name__")
|
|
350
|
+
if isinstance(var_value_type, type)
|
|
351
|
+
else repr(var_value_type)
|
|
352
|
+
),
|
|
353
|
+
)
|
|
354
|
+
return Some(var)
|
|
355
|
+
|
|
356
|
+
@typing.overload
|
|
357
|
+
def get_value(self, var_name: str) -> Option[CtxValueT]: ...
|
|
358
|
+
|
|
359
|
+
@typing.overload
|
|
360
|
+
def get_value(
|
|
361
|
+
self,
|
|
362
|
+
var_name: str,
|
|
363
|
+
var_value_type: type[T],
|
|
364
|
+
) -> Option[T]: ...
|
|
365
|
+
|
|
366
|
+
def get_value(self, var_name, var_value_type=object): # type: ignore
|
|
367
|
+
"""Get context variable value by name."""
|
|
368
|
+
|
|
369
|
+
return self.get(var_name, var_value_type).map(lambda var: var.value)
|
|
370
|
+
|
|
371
|
+
def rename(self, old_var_name: str, new_var_name: str) -> Result[_, str]:
|
|
372
|
+
"""Rename context variable."""
|
|
373
|
+
|
|
374
|
+
var = self.get(old_var_name).unwrap()
|
|
375
|
+
if var.const:
|
|
376
|
+
return Error(f"Unable to rename variable {old_var_name!r}, " "because it's a constant.")
|
|
377
|
+
del self[old_var_name]
|
|
378
|
+
self[new_var_name] = var.value
|
|
379
|
+
return Ok(_())
|
|
380
|
+
|
|
381
|
+
def clear(self, *, include_consts: bool = False) -> None:
|
|
382
|
+
"""Clear context. If `include_consts = True`,
|
|
383
|
+
then the context is completely cleared."""
|
|
384
|
+
|
|
385
|
+
if not self:
|
|
386
|
+
return
|
|
387
|
+
if include_consts:
|
|
388
|
+
logger.warning(
|
|
389
|
+
"Constants from the global context {!r} have been cleaned up!",
|
|
390
|
+
self.ctx_name + " at %#x" % id(self),
|
|
391
|
+
)
|
|
392
|
+
return dict.clear(self)
|
|
393
|
+
|
|
394
|
+
for name, var in self.dict().items():
|
|
395
|
+
if not var.const:
|
|
396
|
+
del self[name]
|
|
397
|
+
|
|
398
|
+
def delete_ctx(self) -> Result[_, str]:
|
|
399
|
+
"""Delete context by `ctx_name`."""
|
|
400
|
+
|
|
401
|
+
if not self.__ctx_name__:
|
|
402
|
+
return Error("Cannot delete unnamed context.")
|
|
403
|
+
ctx = self.__storage__.get(self.ctx_name).unwrap()
|
|
404
|
+
dict.clear(ctx)
|
|
405
|
+
self.__storage__.delete(self.ctx_name)
|
|
406
|
+
logger.warning(f"Global context {self.ctx_name!r} has been deleted!")
|
|
407
|
+
return Ok(_())
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
__all__ = (
|
|
411
411
|
"ABCGlobalContext",
|
|
412
412
|
"CtxVar",
|
|
413
413
|
"CtxVariable",
|
|
@@ -419,5 +419,5 @@ __all__ = (
|
|
|
419
419
|
"get_orig_class",
|
|
420
420
|
"is_dunder",
|
|
421
421
|
"root_protection",
|
|
422
|
-
"type_check",
|
|
423
|
-
)
|
|
422
|
+
"type_check",
|
|
423
|
+
)
|