telegrinder 1.0.0rc1__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.
- telegrinder/__init__.py +258 -0
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +15 -0
- telegrinder/api/api.py +175 -0
- telegrinder/api/error.py +50 -0
- telegrinder/api/response.py +23 -0
- telegrinder/api/token.py +30 -0
- telegrinder/api/validators.py +30 -0
- telegrinder/bot/__init__.py +144 -0
- telegrinder/bot/bot.py +70 -0
- telegrinder/bot/cute_types/__init__.py +41 -0
- telegrinder/bot/cute_types/base.py +228 -0
- telegrinder/bot/cute_types/base.pyi +49 -0
- telegrinder/bot/cute_types/business_connection.py +9 -0
- telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
- telegrinder/bot/cute_types/callback_query.py +248 -0
- telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
- telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
- telegrinder/bot/cute_types/chat_join_request.py +59 -0
- telegrinder/bot/cute_types/chat_member_updated.py +158 -0
- telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
- telegrinder/bot/cute_types/inline_query.py +41 -0
- telegrinder/bot/cute_types/message.py +2809 -0
- telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
- telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
- telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
- telegrinder/bot/cute_types/poll.py +9 -0
- telegrinder/bot/cute_types/poll_answer.py +9 -0
- telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
- telegrinder/bot/cute_types/shipping_query.py +11 -0
- telegrinder/bot/cute_types/update.py +209 -0
- telegrinder/bot/cute_types/utils.py +141 -0
- telegrinder/bot/dispatch/__init__.py +99 -0
- telegrinder/bot/dispatch/abc.py +74 -0
- telegrinder/bot/dispatch/action.py +99 -0
- telegrinder/bot/dispatch/context.py +162 -0
- telegrinder/bot/dispatch/dispatch.py +362 -0
- telegrinder/bot/dispatch/handler/__init__.py +23 -0
- telegrinder/bot/dispatch/handler/abc.py +25 -0
- telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
- telegrinder/bot/dispatch/handler/base.py +34 -0
- telegrinder/bot/dispatch/handler/document_reply.py +43 -0
- telegrinder/bot/dispatch/handler/func.py +73 -0
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +35 -0
- telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
- telegrinder/bot/dispatch/handler/video_reply.py +43 -0
- telegrinder/bot/dispatch/middleware/__init__.py +13 -0
- telegrinder/bot/dispatch/middleware/abc.py +112 -0
- telegrinder/bot/dispatch/middleware/box.py +32 -0
- telegrinder/bot/dispatch/middleware/filter.py +88 -0
- telegrinder/bot/dispatch/middleware/media_group.py +69 -0
- telegrinder/bot/dispatch/process.py +93 -0
- telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
- telegrinder/bot/dispatch/return_manager/abc.py +107 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +34 -0
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/utils.py +20 -0
- telegrinder/bot/dispatch/router/__init__.py +4 -0
- telegrinder/bot/dispatch/router/abc.py +15 -0
- telegrinder/bot/dispatch/router/base.py +154 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -0
- telegrinder/bot/dispatch/view/abc.py +15 -0
- telegrinder/bot/dispatch/view/base.py +226 -0
- telegrinder/bot/dispatch/view/box.py +207 -0
- telegrinder/bot/dispatch/view/media_group.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
- telegrinder/bot/polling/__init__.py +4 -0
- telegrinder/bot/polling/abc.py +25 -0
- telegrinder/bot/polling/error_handler.py +93 -0
- telegrinder/bot/polling/polling.py +167 -0
- telegrinder/bot/polling/utils.py +12 -0
- telegrinder/bot/rules/__init__.py +166 -0
- telegrinder/bot/rules/abc.py +150 -0
- telegrinder/bot/rules/button.py +20 -0
- telegrinder/bot/rules/callback_data.py +109 -0
- telegrinder/bot/rules/chat_join.py +28 -0
- telegrinder/bot/rules/chat_member_updated.py +145 -0
- telegrinder/bot/rules/command.py +137 -0
- telegrinder/bot/rules/enum_text.py +29 -0
- telegrinder/bot/rules/func.py +21 -0
- telegrinder/bot/rules/fuzzy.py +21 -0
- telegrinder/bot/rules/inline.py +45 -0
- telegrinder/bot/rules/integer.py +19 -0
- telegrinder/bot/rules/is_from.py +213 -0
- telegrinder/bot/rules/logic.py +22 -0
- telegrinder/bot/rules/magic.py +60 -0
- telegrinder/bot/rules/markup.py +51 -0
- telegrinder/bot/rules/media.py +13 -0
- telegrinder/bot/rules/mention.py +15 -0
- telegrinder/bot/rules/message_entities.py +37 -0
- telegrinder/bot/rules/node.py +43 -0
- telegrinder/bot/rules/payload.py +89 -0
- telegrinder/bot/rules/payment_invoice.py +14 -0
- telegrinder/bot/rules/regex.py +34 -0
- telegrinder/bot/rules/rule_enum.py +71 -0
- telegrinder/bot/rules/start.py +73 -0
- telegrinder/bot/rules/state.py +35 -0
- telegrinder/bot/rules/text.py +27 -0
- telegrinder/bot/rules/update.py +14 -0
- telegrinder/bot/scenario/__init__.py +5 -0
- telegrinder/bot/scenario/abc.py +16 -0
- telegrinder/bot/scenario/checkbox.py +183 -0
- telegrinder/bot/scenario/choice.py +44 -0
- telegrinder/client/__init__.py +11 -0
- telegrinder/client/abc.py +136 -0
- telegrinder/client/form_data.py +34 -0
- telegrinder/client/rnet.py +198 -0
- telegrinder/model.py +133 -0
- telegrinder/model.pyi +57 -0
- telegrinder/modules.py +1081 -0
- telegrinder/msgspec_utils/__init__.py +42 -0
- telegrinder/msgspec_utils/abc.py +16 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +61 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +388 -0
- telegrinder/msgspec_utils/encoder.py +204 -0
- telegrinder/msgspec_utils/json.py +15 -0
- telegrinder/msgspec_utils/tools.py +80 -0
- telegrinder/node/__init__.py +80 -0
- telegrinder/node/compose.py +193 -0
- telegrinder/node/nodes/__init__.py +96 -0
- telegrinder/node/nodes/attachment.py +169 -0
- telegrinder/node/nodes/callback_query.py +25 -0
- telegrinder/node/nodes/channel.py +97 -0
- telegrinder/node/nodes/command.py +33 -0
- telegrinder/node/nodes/error.py +43 -0
- telegrinder/node/nodes/event.py +70 -0
- telegrinder/node/nodes/file.py +39 -0
- telegrinder/node/nodes/global_node.py +66 -0
- telegrinder/node/nodes/i18n.py +110 -0
- telegrinder/node/nodes/me.py +26 -0
- telegrinder/node/nodes/message_entities.py +15 -0
- telegrinder/node/nodes/payload.py +84 -0
- telegrinder/node/nodes/reply_message.py +14 -0
- telegrinder/node/nodes/source.py +172 -0
- telegrinder/node/nodes/state_mutator.py +71 -0
- telegrinder/node/nodes/text.py +62 -0
- telegrinder/node/scope.py +88 -0
- telegrinder/node/utils.py +38 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -0
- telegrinder/tools/__init__.py +183 -0
- telegrinder/tools/aio.py +147 -0
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +85 -0
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/deep_links/links.py +468 -0
- telegrinder/tools/formatting/deep_links/parsing.py +88 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html.py +241 -0
- telegrinder/tools/fullname.py +82 -0
- telegrinder/tools/global_context/__init__.py +13 -0
- telegrinder/tools/global_context/abc.py +63 -0
- telegrinder/tools/global_context/builtin_context.py +45 -0
- telegrinder/tools/global_context/global_context.py +614 -0
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard/__init__.py +6 -0
- telegrinder/tools/keyboard/abc.py +84 -0
- telegrinder/tools/keyboard/base.py +108 -0
- telegrinder/tools/keyboard/button.py +181 -0
- telegrinder/tools/keyboard/data.py +31 -0
- telegrinder/tools/keyboard/keyboard.py +160 -0
- telegrinder/tools/keyboard/utils.py +95 -0
- telegrinder/tools/lifespan.py +188 -0
- telegrinder/tools/limited_dict.py +35 -0
- telegrinder/tools/loop_wrapper.py +271 -0
- telegrinder/tools/magic/__init__.py +29 -0
- telegrinder/tools/magic/annotations.py +172 -0
- telegrinder/tools/magic/descriptors.py +57 -0
- telegrinder/tools/magic/function.py +254 -0
- telegrinder/tools/magic/inspect.py +16 -0
- telegrinder/tools/magic/shortcut.py +107 -0
- telegrinder/tools/member_descriptor_proxy.py +95 -0
- telegrinder/tools/parse_mode.py +12 -0
- telegrinder/tools/serialization/__init__.py +5 -0
- telegrinder/tools/serialization/abc.py +34 -0
- telegrinder/tools/serialization/json_ser.py +60 -0
- telegrinder/tools/serialization/msgpack_ser.py +197 -0
- telegrinder/tools/serialization/utils.py +18 -0
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_mutator/__init__.py +4 -0
- telegrinder/tools/state_mutator/mutation.py +85 -0
- telegrinder/tools/state_storage/__init__.py +4 -0
- telegrinder/tools/state_storage/abc.py +38 -0
- telegrinder/tools/state_storage/memory.py +27 -0
- telegrinder/tools/strings.py +22 -0
- telegrinder/types/__init__.py +323 -0
- telegrinder/types/enums.py +754 -0
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +6143 -0
- telegrinder/types/methods_utils.py +66 -0
- telegrinder/types/objects.py +8184 -0
- telegrinder/types/webapp.py +129 -0
- telegrinder/verification_utils.py +35 -0
- telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
- telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
- telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
- telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import dataclasses
|
|
3
|
+
import datetime
|
|
4
|
+
import typing
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
|
|
7
|
+
from telegrinder.modules import logger
|
|
8
|
+
from telegrinder.tools.aio import run_task
|
|
9
|
+
from telegrinder.tools.fullname import fullname
|
|
10
|
+
|
|
11
|
+
type CoroutineTask[T] = typing.Coroutine[typing.Any, typing.Any, T]
|
|
12
|
+
type CoroutineFunc[**P, T] = typing.Callable[P, CoroutineTask[T]]
|
|
13
|
+
type Task[**P, T] = CoroutineFunc[P, T] | CoroutineTask[T] | DelayedTask[P]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def to_coroutine_task[T](task: Task[..., T], /) -> CoroutineTask[T]:
|
|
17
|
+
if asyncio.iscoroutinefunction(task) or isinstance(task, DelayedTask):
|
|
18
|
+
task = task()
|
|
19
|
+
elif not asyncio.iscoroutine(task):
|
|
20
|
+
raise TypeError("Task should be coroutine or coroutine function.")
|
|
21
|
+
return task
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclasses.dataclass
|
|
25
|
+
class DelayedTask[**P]:
|
|
26
|
+
_cancelled: bool = dataclasses.field(default=False, init=False, repr=False)
|
|
27
|
+
_event: asyncio.Event = dataclasses.field(default_factory=asyncio.Event, init=False, repr=False)
|
|
28
|
+
_timer: asyncio.TimerHandle | None = dataclasses.field(default=None, init=False, repr=False)
|
|
29
|
+
|
|
30
|
+
function: typing.Callable[P, CoroutineTask[typing.Any]]
|
|
31
|
+
seconds: float | datetime.timedelta
|
|
32
|
+
repeat: bool = dataclasses.field(default=False, kw_only=True)
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
self.function.cancel = self.cancel
|
|
36
|
+
|
|
37
|
+
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None:
|
|
38
|
+
stopped = False
|
|
39
|
+
|
|
40
|
+
while not stopped and not self.is_cancelled:
|
|
41
|
+
self.start_timer()
|
|
42
|
+
await self._event.wait()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
await self.function(*args, **kwargs)
|
|
46
|
+
except Exception:
|
|
47
|
+
await logger.aexception(
|
|
48
|
+
"Delayed task `{}` failed with exception, traceback message below:",
|
|
49
|
+
fullname(self.function),
|
|
50
|
+
)
|
|
51
|
+
finally:
|
|
52
|
+
self._event.clear()
|
|
53
|
+
self._timer = None
|
|
54
|
+
|
|
55
|
+
if not self.repeat:
|
|
56
|
+
stopped = True
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def is_cancelled(self) -> bool:
|
|
60
|
+
return self._cancelled
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def delay(self) -> float:
|
|
64
|
+
return float(self.seconds) if isinstance(self.seconds, int | float) else self.seconds.total_seconds()
|
|
65
|
+
|
|
66
|
+
def start_timer(self) -> None:
|
|
67
|
+
if self._timer is None:
|
|
68
|
+
self._timer = asyncio.get_running_loop().call_later(
|
|
69
|
+
self.delay,
|
|
70
|
+
callback=lambda: (self._event.set() if not self._event.is_set() else None),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def cancel(self) -> bool:
|
|
74
|
+
if self._cancelled:
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
self._cancelled = True
|
|
78
|
+
|
|
79
|
+
if self._timer is None or self._timer.cancelled():
|
|
80
|
+
self._timer = None
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
self._timer.cancel()
|
|
84
|
+
self._timer = None
|
|
85
|
+
|
|
86
|
+
for future in self._event._waiters:
|
|
87
|
+
if not future.cancelled():
|
|
88
|
+
future.cancel()
|
|
89
|
+
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclasses.dataclass(kw_only=True, slots=True, repr=False)
|
|
94
|
+
class Lifespan:
|
|
95
|
+
_started: bool = dataclasses.field(default=False, init=False)
|
|
96
|
+
_lifespan_context: typing.AsyncContextManager[typing.Any] | None = dataclasses.field(default=None, init=False)
|
|
97
|
+
lifespan_function: typing.Callable[[], typing.AsyncContextManager[typing.Any]] | None = dataclasses.field(
|
|
98
|
+
default=None
|
|
99
|
+
)
|
|
100
|
+
startup_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
101
|
+
shutdown_tasks: list[CoroutineTask[typing.Any]] = dataclasses.field(default_factory=lambda: [])
|
|
102
|
+
|
|
103
|
+
def __repr__(self) -> str:
|
|
104
|
+
return "<{}: started={}>".format(fullname(self), self._started)
|
|
105
|
+
|
|
106
|
+
def __add__(self, other: object, /) -> typing.Self:
|
|
107
|
+
if not isinstance(other, self.__class__):
|
|
108
|
+
return NotImplemented
|
|
109
|
+
|
|
110
|
+
return self.__class__(
|
|
111
|
+
startup_tasks=self.startup_tasks + other.startup_tasks,
|
|
112
|
+
shutdown_tasks=self.shutdown_tasks + other.shutdown_tasks,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def __iadd__(self, other: object, /) -> typing.Self:
|
|
116
|
+
if not isinstance(other, self.__class__):
|
|
117
|
+
return NotImplemented
|
|
118
|
+
|
|
119
|
+
self.startup_tasks.extend(other.startup_tasks)
|
|
120
|
+
self.shutdown_tasks.extend(other.shutdown_tasks)
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
async def __aenter__(self) -> None:
|
|
124
|
+
await self._start()
|
|
125
|
+
|
|
126
|
+
async def __aexit__(
|
|
127
|
+
self,
|
|
128
|
+
exc_type: typing.Any | None,
|
|
129
|
+
exc_value: typing.Any | None,
|
|
130
|
+
exc_tb: typing.Any | None,
|
|
131
|
+
) -> None:
|
|
132
|
+
await self._shutdown(exc_type, exc_value, exc_tb)
|
|
133
|
+
|
|
134
|
+
def __call__[Function: typing.Callable[[], typing.AsyncGenerator[typing.Any, None]]](
|
|
135
|
+
self,
|
|
136
|
+
func: Function,
|
|
137
|
+
/,
|
|
138
|
+
) -> Function:
|
|
139
|
+
self.lifespan_function = asynccontextmanager(func)
|
|
140
|
+
return func
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def started(self) -> bool:
|
|
144
|
+
return self._started
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
async def _run_tasks(tasks: list[CoroutineTask[typing.Any]], /) -> None:
|
|
148
|
+
while tasks:
|
|
149
|
+
await tasks.pop(0)
|
|
150
|
+
|
|
151
|
+
async def _start(self) -> None:
|
|
152
|
+
if not self._started:
|
|
153
|
+
await logger.adebug("Running lifespan startup tasks")
|
|
154
|
+
self._started = True
|
|
155
|
+
|
|
156
|
+
if self.lifespan_function is not None:
|
|
157
|
+
self._lifespan_context = self.lifespan_function()
|
|
158
|
+
await self._lifespan_context.__aenter__()
|
|
159
|
+
|
|
160
|
+
await self._run_tasks(self.startup_tasks)
|
|
161
|
+
|
|
162
|
+
async def _shutdown(self, *suppress_args: typing.Any) -> None:
|
|
163
|
+
if self._started:
|
|
164
|
+
await logger.adebug("Running lifespan shutdown tasks")
|
|
165
|
+
self._started = False
|
|
166
|
+
|
|
167
|
+
if self._lifespan_context is not None:
|
|
168
|
+
await self._lifespan_context.__aexit__(*suppress_args)
|
|
169
|
+
self._lifespan_context = None
|
|
170
|
+
|
|
171
|
+
await self._run_tasks(self.shutdown_tasks)
|
|
172
|
+
|
|
173
|
+
def start(self, loop: asyncio.AbstractEventLoop | None = None) -> None:
|
|
174
|
+
run_task(self._start(), loop=loop)
|
|
175
|
+
|
|
176
|
+
def shutdown(self, loop: asyncio.AbstractEventLoop | None = None) -> None:
|
|
177
|
+
run_task(self._shutdown(None, None, None), loop=loop)
|
|
178
|
+
|
|
179
|
+
def on_startup[**P, T](self, task: Task[P, T], /) -> Task[P, T]:
|
|
180
|
+
self.startup_tasks.append(to_coroutine_task(task))
|
|
181
|
+
return task
|
|
182
|
+
|
|
183
|
+
def on_shutdown[**P, T](self, task: Task[P, T], /) -> Task[P, T]:
|
|
184
|
+
self.shutdown_tasks.append(to_coroutine_task(task))
|
|
185
|
+
return task
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = ("CoroutineTask", "DelayedTask", "Lifespan", "run_task", "to_coroutine_task")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from collections import UserDict, deque
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LimitedDict[Key, Value](UserDict[Key, Value]):
|
|
5
|
+
def __init__(self, *, maxlimit: int = 1000) -> None:
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.maxlimit = maxlimit
|
|
8
|
+
self.queue: deque[Key] = deque(maxlen=maxlimit)
|
|
9
|
+
|
|
10
|
+
def set(self, key: Key, value: Value, /) -> Value | None:
|
|
11
|
+
"""Set item in the dictionary.
|
|
12
|
+
Returns a value that was deleted when the limit in the dictionary
|
|
13
|
+
was reached, otherwise None.
|
|
14
|
+
"""
|
|
15
|
+
deleted_item = None
|
|
16
|
+
|
|
17
|
+
if len(self.queue) >= self.maxlimit:
|
|
18
|
+
deleted_item = self.pop(self.queue.popleft(), None)
|
|
19
|
+
|
|
20
|
+
if key not in self.queue:
|
|
21
|
+
self.queue.append(key)
|
|
22
|
+
|
|
23
|
+
super().__setitem__(key, value)
|
|
24
|
+
return deleted_item
|
|
25
|
+
|
|
26
|
+
def __setitem__(self, key: Key, value: Value, /) -> None:
|
|
27
|
+
self.set(key, value)
|
|
28
|
+
|
|
29
|
+
def __delitem__(self, key: Key, /) -> None:
|
|
30
|
+
if key in self.queue:
|
|
31
|
+
self.queue.remove(key)
|
|
32
|
+
return super().__delitem__(key)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
__all__ = ("LimitedDict",)
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import datetime
|
|
4
|
+
import enum
|
|
5
|
+
import signal
|
|
6
|
+
import typing
|
|
7
|
+
from contextlib import suppress
|
|
8
|
+
|
|
9
|
+
from telegrinder.modules import logger
|
|
10
|
+
from telegrinder.tools.aio import TaskGroup, cancel_future, loop_is_running, run_task
|
|
11
|
+
from telegrinder.tools.final import Final
|
|
12
|
+
from telegrinder.tools.fullname import fullname
|
|
13
|
+
from telegrinder.tools.lifespan import (
|
|
14
|
+
CoroutineFunc,
|
|
15
|
+
CoroutineTask,
|
|
16
|
+
DelayedTask,
|
|
17
|
+
Lifespan,
|
|
18
|
+
Task,
|
|
19
|
+
to_coroutine_task,
|
|
20
|
+
)
|
|
21
|
+
from telegrinder.tools.singleton.singleton import Singleton
|
|
22
|
+
|
|
23
|
+
if typing.TYPE_CHECKING:
|
|
24
|
+
from contextvars import Context
|
|
25
|
+
|
|
26
|
+
type Tasks = set[asyncio.Task[typing.Any]]
|
|
27
|
+
type DelayedFunctionDecorator[**P, R] = typing.Callable[[typing.Callable[P, R]], DelayedFunction[P, R]]
|
|
28
|
+
|
|
29
|
+
class DelayedFunction[**P, R](typing.Protocol):
|
|
30
|
+
__name__: str
|
|
31
|
+
__delayed_task__: DelayedTask[typing.Callable[P, CoroutineTask[R]]]
|
|
32
|
+
|
|
33
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> CoroutineTask[R]: ...
|
|
34
|
+
|
|
35
|
+
def cancel(self) -> bool:
|
|
36
|
+
"""Cancel delayed task."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Timer(datetime.timedelta):
|
|
41
|
+
repeat = False
|
|
42
|
+
|
|
43
|
+
def __call__[**P, R](self, function: CoroutineFunc[P, R], /) -> DelayedFunction[P, R]:
|
|
44
|
+
loop_wrapper = LoopWrapper()
|
|
45
|
+
loop_wrapper.add_task(DelayedTask(function, seconds=self.total_seconds(), repeat=self.repeat))
|
|
46
|
+
return function # type: ignore
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Interval(Timer):
|
|
50
|
+
repeat = True
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@enum.unique
|
|
54
|
+
class LoopWrapperState(enum.Enum):
|
|
55
|
+
NOT_RUNNING = enum.auto()
|
|
56
|
+
RUNNING = enum.auto()
|
|
57
|
+
RUNNING_MANUALLY = enum.auto()
|
|
58
|
+
SHUTDOWN = enum.auto()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@typing.final
|
|
62
|
+
class LoopWrapper(Singleton, Final):
|
|
63
|
+
_loop: asyncio.AbstractEventLoop
|
|
64
|
+
_lifespan: Lifespan
|
|
65
|
+
_future_tasks: list[CoroutineTask[typing.Any]]
|
|
66
|
+
_state: LoopWrapperState
|
|
67
|
+
_all_tasks: set[asyncio.Task[typing.Any]]
|
|
68
|
+
_event_stop: asyncio.Event
|
|
69
|
+
_run_lw_task: asyncio.Handle
|
|
70
|
+
_is_attached_to_running_loop: bool
|
|
71
|
+
|
|
72
|
+
timer = Timer
|
|
73
|
+
interval = Interval
|
|
74
|
+
|
|
75
|
+
__slots__ = (
|
|
76
|
+
"_lifespan",
|
|
77
|
+
"_future_tasks",
|
|
78
|
+
"_state",
|
|
79
|
+
"_all_tasks",
|
|
80
|
+
"_loop",
|
|
81
|
+
"_event_stop",
|
|
82
|
+
"_run_lw_task",
|
|
83
|
+
"_is_attached_to_running_loop",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def __init__(self) -> None:
|
|
87
|
+
try:
|
|
88
|
+
self._loop = asyncio.get_event_loop()
|
|
89
|
+
except RuntimeError:
|
|
90
|
+
self._loop = asyncio.new_event_loop()
|
|
91
|
+
|
|
92
|
+
self._run_lw_task = self._run_lw_later()
|
|
93
|
+
self._lifespan = Lifespan()
|
|
94
|
+
self._event_stop = asyncio.Event()
|
|
95
|
+
self._state = LoopWrapperState.NOT_RUNNING
|
|
96
|
+
self._future_tasks = [self._waiter_stop()]
|
|
97
|
+
self._all_tasks = set()
|
|
98
|
+
self._is_attached_to_running_loop = False
|
|
99
|
+
|
|
100
|
+
signal.signal(signal.SIGTERM, lambda *_: self.stop())
|
|
101
|
+
|
|
102
|
+
def __repr__(self) -> str:
|
|
103
|
+
return "<{}: loop={!r}, lifespan={!r}>".format(
|
|
104
|
+
fullname(self) + (" (running)" if self.running else ""),
|
|
105
|
+
self._loop,
|
|
106
|
+
self._lifespan,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def __call__(self) -> asyncio.AbstractEventLoop:
|
|
110
|
+
return self._loop
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def lifespan(self) -> Lifespan:
|
|
114
|
+
return self._lifespan
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def loop(self) -> asyncio.AbstractEventLoop:
|
|
118
|
+
return self._loop
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def time(self) -> float:
|
|
122
|
+
return self._loop.time()
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def running(self) -> bool:
|
|
126
|
+
return self._state in {LoopWrapperState.RUNNING, LoopWrapperState.RUNNING_MANUALLY}
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def shutdown(self) -> bool:
|
|
130
|
+
return self._state is LoopWrapperState.SHUTDOWN
|
|
131
|
+
|
|
132
|
+
async def _run_async_event_loop(self) -> None:
|
|
133
|
+
if not self.running:
|
|
134
|
+
self._state = LoopWrapperState.RUNNING
|
|
135
|
+
async with self._async_wrap_loop():
|
|
136
|
+
await self._run()
|
|
137
|
+
|
|
138
|
+
async def _run(self) -> None:
|
|
139
|
+
await logger.adebug("Running loop wrapper")
|
|
140
|
+
|
|
141
|
+
while self._future_tasks:
|
|
142
|
+
self._create_task(self._future_tasks.pop(0))
|
|
143
|
+
|
|
144
|
+
while self.running and (tasks := self._get_all_tasks()):
|
|
145
|
+
tasks_results, _ = await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
|
|
146
|
+
for task_result in tasks_results:
|
|
147
|
+
try:
|
|
148
|
+
task_result.result()
|
|
149
|
+
except Exception:
|
|
150
|
+
await logger.aexception("Traceback message below:")
|
|
151
|
+
|
|
152
|
+
async def _cancel_tasks(self) -> None:
|
|
153
|
+
with suppress(asyncio.exceptions.CancelledError):
|
|
154
|
+
await cancel_future(asyncio.gather(*self._get_all_tasks(), return_exceptions=True))
|
|
155
|
+
|
|
156
|
+
@contextlib.asynccontextmanager
|
|
157
|
+
async def _async_wrap_loop(self) -> typing.AsyncGenerator[typing.Any, None]:
|
|
158
|
+
try:
|
|
159
|
+
try:
|
|
160
|
+
await self._lifespan._start()
|
|
161
|
+
finally:
|
|
162
|
+
yield
|
|
163
|
+
except asyncio.CancelledError:
|
|
164
|
+
await logger.adebug("Cancelling tasks...")
|
|
165
|
+
await self._cancel_tasks()
|
|
166
|
+
finally:
|
|
167
|
+
await self._shutdown()
|
|
168
|
+
|
|
169
|
+
async def _shutdown(self) -> None:
|
|
170
|
+
await self.lifespan._shutdown(None, None, None)
|
|
171
|
+
await logger.adebug("Shutting down loop wrapper")
|
|
172
|
+
self._state = LoopWrapperState.SHUTDOWN
|
|
173
|
+
|
|
174
|
+
async def _waiter_stop(self) -> None:
|
|
175
|
+
await self._event_stop.wait()
|
|
176
|
+
self._state = LoopWrapperState.SHUTDOWN
|
|
177
|
+
self._event_stop.clear()
|
|
178
|
+
await self._shutdown()
|
|
179
|
+
await self._cancel_tasks()
|
|
180
|
+
|
|
181
|
+
def _run_lw_later(self) -> asyncio.Handle:
|
|
182
|
+
return self._loop.call_soon_threadsafe(lambda l: l.create_task(self._run_async_event_loop()), self._loop)
|
|
183
|
+
|
|
184
|
+
def _create_task(
|
|
185
|
+
self,
|
|
186
|
+
coro: CoroutineTask[typing.Any],
|
|
187
|
+
/,
|
|
188
|
+
name: str | None = None,
|
|
189
|
+
context: Context | None = None,
|
|
190
|
+
) -> asyncio.Task[typing.Any]:
|
|
191
|
+
task = self._loop.create_task(coro, name=name, context=context)
|
|
192
|
+
self._all_tasks.add(task)
|
|
193
|
+
task.add_done_callback(self._all_tasks.discard)
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
return task
|
|
197
|
+
finally:
|
|
198
|
+
del task
|
|
199
|
+
|
|
200
|
+
def _get_all_tasks(self) -> Tasks:
|
|
201
|
+
"""Get a set of all tasks from the loop wrapper and event loop (`exclude the current task if any`)."""
|
|
202
|
+
|
|
203
|
+
return (self._all_tasks | asyncio.all_tasks(loop=self._loop)).symmetric_difference(
|
|
204
|
+
set() if (task := asyncio.current_task(self._loop)) is None else {task},
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def _close_loop(self) -> None:
|
|
208
|
+
if not self._loop.is_closed():
|
|
209
|
+
logger.debug("Closing event loop {!r}", self._loop)
|
|
210
|
+
self._loop.close()
|
|
211
|
+
|
|
212
|
+
@contextlib.contextmanager
|
|
213
|
+
def _wrap_loop(self, *, close_loop: bool = True) -> typing.Generator[typing.Any, None, None]:
|
|
214
|
+
try:
|
|
215
|
+
try:
|
|
216
|
+
self.lifespan.start(self._loop)
|
|
217
|
+
finally:
|
|
218
|
+
yield
|
|
219
|
+
except (KeyboardInterrupt, SystemExit) as e:
|
|
220
|
+
print(flush=True)
|
|
221
|
+
logger.info(f"Caught {e.__class__.__name__}, cancelling tasks")
|
|
222
|
+
run_task(self._cancel_tasks(), loop=self._loop)
|
|
223
|
+
|
|
224
|
+
if isinstance(e, SystemExit):
|
|
225
|
+
raise
|
|
226
|
+
finally:
|
|
227
|
+
run_task(self._shutdown(), loop=self._loop)
|
|
228
|
+
|
|
229
|
+
if close_loop:
|
|
230
|
+
self._close_loop()
|
|
231
|
+
|
|
232
|
+
def run(self, *, close_loop: bool = True) -> typing.NoReturn: # type: ignore
|
|
233
|
+
if self.running:
|
|
234
|
+
raise RuntimeError("Loop wrapper already running.")
|
|
235
|
+
|
|
236
|
+
self._state = LoopWrapperState.RUNNING_MANUALLY
|
|
237
|
+
with self._wrap_loop(close_loop=close_loop):
|
|
238
|
+
run_task(self._run(), loop=self._loop)
|
|
239
|
+
|
|
240
|
+
def stop(self) -> None:
|
|
241
|
+
if not self._event_stop.is_set():
|
|
242
|
+
self._event_stop.set()
|
|
243
|
+
|
|
244
|
+
def add_task(self, task: Task[..., typing.Any], /) -> None:
|
|
245
|
+
coro_task = to_coroutine_task(task)
|
|
246
|
+
|
|
247
|
+
if not self.running:
|
|
248
|
+
self._future_tasks.append(coro_task)
|
|
249
|
+
|
|
250
|
+
if self._is_attached_to_running_loop is False and loop_is_running():
|
|
251
|
+
self.attach_to_running_loop()
|
|
252
|
+
else:
|
|
253
|
+
self._create_task(coro_task)
|
|
254
|
+
|
|
255
|
+
def attach_to_running_loop(self) -> None:
|
|
256
|
+
if self.running:
|
|
257
|
+
raise RuntimeError("Cannot attach the running loop wrapper to the running loop.")
|
|
258
|
+
|
|
259
|
+
self._loop = asyncio.get_running_loop()
|
|
260
|
+
self._run_lw_task.cancel()
|
|
261
|
+
self._is_attached_to_running_loop = True
|
|
262
|
+
self._run_lw_task = self._run_lw_later()
|
|
263
|
+
|
|
264
|
+
def create_task_group[T](self) -> TaskGroup[typing.Any]:
|
|
265
|
+
loop = asyncio.get_running_loop()
|
|
266
|
+
if not self.running and self._loop is not loop:
|
|
267
|
+
self.attach_to_running_loop()
|
|
268
|
+
return TaskGroup(loop)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
__all__ = ("DelayedTask", "LoopWrapper", "to_coroutine_task")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from telegrinder.tools.magic.annotations import Annotations, get_generic_parameters
|
|
2
|
+
from telegrinder.tools.magic.descriptors import additional_property
|
|
3
|
+
from telegrinder.tools.magic.function import (
|
|
4
|
+
Bundle,
|
|
5
|
+
bundle,
|
|
6
|
+
get_default_args,
|
|
7
|
+
get_func_annotations,
|
|
8
|
+
get_func_parameters,
|
|
9
|
+
resolve_arg_names,
|
|
10
|
+
resolve_kwonly_arg_names,
|
|
11
|
+
resolve_posonly_arg_names,
|
|
12
|
+
)
|
|
13
|
+
from telegrinder.tools.magic.shortcut import Shortcut, shortcut
|
|
14
|
+
|
|
15
|
+
__all__ = (
|
|
16
|
+
"Annotations",
|
|
17
|
+
"Bundle",
|
|
18
|
+
"Shortcut",
|
|
19
|
+
"additional_property",
|
|
20
|
+
"bundle",
|
|
21
|
+
"get_default_args",
|
|
22
|
+
"get_func_annotations",
|
|
23
|
+
"get_func_parameters",
|
|
24
|
+
"get_generic_parameters",
|
|
25
|
+
"resolve_arg_names",
|
|
26
|
+
"resolve_kwonly_arg_names",
|
|
27
|
+
"resolve_posonly_arg_names",
|
|
28
|
+
"shortcut",
|
|
29
|
+
)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
import typing
|
|
5
|
+
from annotationlib import Format, ForwardRef, get_annotations
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from reprlib import recursive_repr
|
|
8
|
+
|
|
9
|
+
from kungfu.library.misc import from_optional
|
|
10
|
+
from kungfu.library.monad.option import NOTHING, Option, Some
|
|
11
|
+
|
|
12
|
+
from telegrinder.tools.global_context.global_context import GlobalContext, ctx_var
|
|
13
|
+
|
|
14
|
+
type TypeParameter = typing.Union[
|
|
15
|
+
typing.TypeVar,
|
|
16
|
+
typing.TypeVarTuple,
|
|
17
|
+
typing.ParamSpec,
|
|
18
|
+
]
|
|
19
|
+
type TypeParameters = tuple[TypeParameter, ...]
|
|
20
|
+
type SupportsAnnotations = type[typing.Any] | types.ModuleType | typing.Callable[..., typing.Any]
|
|
21
|
+
type AnnotationForm = typing.Any
|
|
22
|
+
|
|
23
|
+
_UNION_TYPES: typing.Final = frozenset((typing.Union, types.UnionType))
|
|
24
|
+
_CACHED_ANNOTATIONS: typing.Final = GlobalContext(
|
|
25
|
+
"cached_annotations",
|
|
26
|
+
thread_safe=True,
|
|
27
|
+
annotations=ctx_var(default_factory=dict, const=True),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _cache_annotations(obj: SupportsAnnotations, annotations: dict[str, AnnotationForm], /) -> None:
|
|
32
|
+
_CACHED_ANNOTATIONS.annotations[obj] = MappingAnnotations(annotations)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_cached_annotations(obj: SupportsAnnotations, /) -> MappingAnnotations | None:
|
|
36
|
+
return _CACHED_ANNOTATIONS.annotations.get(obj)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def is_union_type(obj: typing.Any, /) -> bool:
|
|
40
|
+
return typing.get_origin(obj) in _UNION_TYPES
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MappingAnnotations[T = AnnotationForm](dict[str, T]):
|
|
44
|
+
def __init__(self, annotations: typing.Mapping[str, AnnotationForm], /) -> None:
|
|
45
|
+
super().__init__(annotations)
|
|
46
|
+
self.return_type: Option[T] = from_optional(self.pop("return", None))
|
|
47
|
+
|
|
48
|
+
@recursive_repr()
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
return f"annotations={super().__repr__()}, return_type={self.return_type!r}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclasses.dataclass
|
|
54
|
+
class Annotations:
|
|
55
|
+
obj: SupportsAnnotations
|
|
56
|
+
|
|
57
|
+
@cached_property
|
|
58
|
+
def forward_ref_parameters(self) -> dict[str, typing.Any]:
|
|
59
|
+
parameters = dict[str, typing.Any](
|
|
60
|
+
is_argument=False,
|
|
61
|
+
is_class=False,
|
|
62
|
+
module=None,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if isinstance(self.obj, type):
|
|
66
|
+
parameters["is_class"] = True
|
|
67
|
+
parameters["module"] = (
|
|
68
|
+
sys.modules[module] if (module := getattr(self.obj, "__module__", None)) is not None else None
|
|
69
|
+
)
|
|
70
|
+
elif isinstance(self.obj, types.ModuleType):
|
|
71
|
+
parameters["module"] = self.obj
|
|
72
|
+
elif callable(self.obj):
|
|
73
|
+
parameters["is_argument"] = True
|
|
74
|
+
|
|
75
|
+
return parameters
|
|
76
|
+
|
|
77
|
+
@cached_property
|
|
78
|
+
def generic_parameters(self) -> Option[dict[TypeParameter, typing.Any]]:
|
|
79
|
+
return get_generic_parameters(self.obj)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_obj(cls, obj: typing.Any, /) -> typing.Self:
|
|
83
|
+
if not isinstance(obj, type | types.ModuleType | typing.Callable):
|
|
84
|
+
obj = type(obj)
|
|
85
|
+
|
|
86
|
+
return cls(obj)
|
|
87
|
+
|
|
88
|
+
@typing.overload
|
|
89
|
+
def get(
|
|
90
|
+
self,
|
|
91
|
+
*,
|
|
92
|
+
ignore_failed_evals: bool = True,
|
|
93
|
+
cache: bool = False,
|
|
94
|
+
) -> MappingAnnotations[typing.ForwardRef | AnnotationForm]: ...
|
|
95
|
+
|
|
96
|
+
@typing.overload
|
|
97
|
+
def get(
|
|
98
|
+
self,
|
|
99
|
+
*,
|
|
100
|
+
exclude_forward_refs: typing.Literal[True],
|
|
101
|
+
ignore_failed_evals: bool = True,
|
|
102
|
+
cache: bool = False,
|
|
103
|
+
) -> MappingAnnotations: ...
|
|
104
|
+
|
|
105
|
+
def get(
|
|
106
|
+
self,
|
|
107
|
+
*,
|
|
108
|
+
exclude_forward_refs: bool = False,
|
|
109
|
+
ignore_failed_evals: bool = True,
|
|
110
|
+
cache: bool = False,
|
|
111
|
+
) -> MappingAnnotations:
|
|
112
|
+
if (cached_annotations := _get_cached_annotations(self.obj)) is not None:
|
|
113
|
+
return cached_annotations
|
|
114
|
+
|
|
115
|
+
annotations = dict[str, typing.Any]()
|
|
116
|
+
for name, annotation in get_annotations(
|
|
117
|
+
obj=self.obj,
|
|
118
|
+
format=Format.FORWARDREF,
|
|
119
|
+
eval_str=False,
|
|
120
|
+
).items():
|
|
121
|
+
if isinstance(annotation, str):
|
|
122
|
+
annotation = ForwardRef(annotation, owner=self.obj, **self.forward_ref_parameters)
|
|
123
|
+
|
|
124
|
+
if not isinstance(annotation, ForwardRef):
|
|
125
|
+
annotations[name] = annotation
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
value = annotation.evaluate(format=Format.VALUE)
|
|
130
|
+
except NameError:
|
|
131
|
+
if not ignore_failed_evals:
|
|
132
|
+
raise
|
|
133
|
+
|
|
134
|
+
value = annotation
|
|
135
|
+
|
|
136
|
+
if isinstance(value, typing.ForwardRef) and exclude_forward_refs:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
annotations[name] = value
|
|
140
|
+
|
|
141
|
+
if cache:
|
|
142
|
+
_cache_annotations(self.obj, annotations)
|
|
143
|
+
|
|
144
|
+
return MappingAnnotations(annotations)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_generic_parameters(obj: typing.Any, /) -> Option[dict[TypeParameter, AnnotationForm]]:
|
|
148
|
+
origin_obj = typing.get_origin(obj)
|
|
149
|
+
args = typing.get_args(obj)
|
|
150
|
+
parameters: TypeParameters = getattr(origin_obj or obj, "__parameters__")
|
|
151
|
+
|
|
152
|
+
if not parameters:
|
|
153
|
+
return NOTHING
|
|
154
|
+
|
|
155
|
+
index = 0
|
|
156
|
+
generic_alias_args = dict[TypeParameter, typing.Any]()
|
|
157
|
+
|
|
158
|
+
for parameter in parameters:
|
|
159
|
+
if isinstance(parameter, typing.TypeVarTuple):
|
|
160
|
+
stop_index = len(args) - index
|
|
161
|
+
generic_alias_args[parameter] = args[index:stop_index]
|
|
162
|
+
index = stop_index
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
arg = args[index] if index < len(args) else None
|
|
166
|
+
generic_alias_args[parameter] = arg
|
|
167
|
+
index += 1
|
|
168
|
+
|
|
169
|
+
return Some(generic_alias_args)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
__all__ = ("Annotations", "get_generic_parameters")
|