pyroflow 0.1.6__tar.gz → 0.2.1__tar.gz
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.
- {pyroflow-0.1.6 → pyroflow-0.2.1}/PKG-INFO +12 -8
- {pyroflow-0.1.6 → pyroflow-0.2.1}/README.md +4 -4
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyproject.toml +17 -3
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/__init__.py +3 -0
- pyroflow-0.2.1/pyroflow/__meta__.py +1 -0
- pyroflow-0.2.1/pyroflow/enums.py +23 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/listener_coordinator/listener_coordinator.py +1 -8
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/models.py +8 -9
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/typings.py +1 -8
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinated/callback_query_coordinated.py +1 -1
- pyroflow-0.2.1/pyroflow/update_history/message_history.py +42 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/async_tools.py +62 -29
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/classes.py +1 -7
- pyroflow-0.2.1/pyroflow/utils/iter_tools.py +37 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/typings.py +16 -20
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/validate_tools.py +7 -6
- pyroflow-0.2.1/tests/__init__.py +0 -0
- pyroflow-0.2.1/tests/conftest.py +70 -0
- pyroflow-0.2.1/tests/test_compat_static.py +144 -0
- pyroflow-0.2.1/tests/test_coordinator.py +110 -0
- pyroflow-0.2.1/tests/test_enums.py +31 -0
- pyroflow-0.2.1/tests/test_history.py +143 -0
- pyroflow-0.2.1/tests/test_imports.py +100 -0
- pyroflow-0.2.1/tests/test_listener.py +136 -0
- pyroflow-0.2.1/tests/test_models.py +84 -0
- pyroflow-0.2.1/tests/test_patch_cls.py +118 -0
- pyroflow-0.2.1/tests/test_utils.py +149 -0
- pyroflow-0.1.6/pyroflow/__meta__.py +0 -1
- pyroflow-0.1.6/pyroflow/enums.py +0 -10
- pyroflow-0.1.6/pyroflow/update_history/message_history.py +0 -6
- pyroflow-0.1.6/pyroflow/utils/iter_tools.py +0 -32
- {pyroflow-0.1.6 → pyroflow-0.2.1}/LICENSE +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/client.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/dispatcher.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/errors.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/listener_coordinator/__init__.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/listener_coordinator/memory_listener_coordinator.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/listener_coordinator/redis_listener_coordinator.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/types.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinated/__init__.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinated/message_coordinated.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinated/update_coordinated.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinator/__init__.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinator/memory_update_coordinator.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinator/redis_update_coordinator.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinator/update_coordinator.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history/__init__.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history/callback_query_history.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history/update_history.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history_store/__init__.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history_store/memory_update_history_store.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history_store/update_history_store.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_listener/__init__.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_listener/callback_query_listener.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_listener/message_listener.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_listener/update_listener.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/__init__.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/enums.py +0 -0
- {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/misc_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyroflow
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Conversation-oriented Pyrogram extension with per-update listeners and multi-session coordination
|
|
5
5
|
Project-URL: Homepage, https://github.com/eeeob/pyroflow
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/eeeob/pyroflow/issues
|
|
@@ -13,7 +13,6 @@ Classifier: Framework :: AsyncIO
|
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -22,16 +21,21 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
22
21
|
Classifier: Topic :: Communications :: Chat
|
|
23
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
23
|
Classifier: Typing :: Typed
|
|
25
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.10
|
|
26
25
|
Requires-Dist: cachetools
|
|
27
26
|
Requires-Dist: kurigram>=2.2.0
|
|
28
27
|
Requires-Dist: sortedcontainers==2.4.0
|
|
29
|
-
Requires-Dist: typing-extensions>=4.6.0; python_version < '3.
|
|
28
|
+
Requires-Dist: typing-extensions>=4.6.0; python_version < '3.13'
|
|
30
29
|
Provides-Extra: dev
|
|
31
30
|
Requires-Dist: hatch<=1.16.5; extra == 'dev'
|
|
32
31
|
Requires-Dist: twine<=6.2.0; extra == 'dev'
|
|
33
32
|
Provides-Extra: redis
|
|
34
33
|
Requires-Dist: redis>=4.2.0; extra == 'redis'
|
|
34
|
+
Provides-Extra: test
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
37
|
+
Requires-Dist: redis>=4.2.0; extra == 'test'
|
|
38
|
+
Requires-Dist: tomli; (python_version < '3.11') and extra == 'test'
|
|
35
39
|
Description-Content-Type: text/markdown
|
|
36
40
|
|
|
37
41
|
# pyroflow
|
|
@@ -72,7 +76,7 @@ pyroflow solves all three problems:
|
|
|
72
76
|
|
|
73
77
|
## Installation
|
|
74
78
|
|
|
75
|
-
**Minimum requirements:** Python 3.
|
|
79
|
+
**Minimum requirements:** Python 3.10+
|
|
76
80
|
|
|
77
81
|
```
|
|
78
82
|
pip install pyroflow # core
|
|
@@ -233,10 +237,10 @@ client.run()
|
|
|
233
237
|
A `UpdateHistory` records which handlers ran successfully for each update. This enables features like `back` buttons that replay or inspect previous processing steps.
|
|
234
238
|
|
|
235
239
|
```python
|
|
236
|
-
from pyroflow import Client,
|
|
240
|
+
from pyroflow import Client, CallbackQueryHistory
|
|
237
241
|
|
|
238
242
|
client = Client("my_session")
|
|
239
|
-
client.register_history(
|
|
243
|
+
client.register_history(CallbackQueryHistory())
|
|
240
244
|
|
|
241
245
|
client.run()
|
|
242
246
|
```
|
|
@@ -248,7 +252,7 @@ They can also be removed at runtime:
|
|
|
248
252
|
```python
|
|
249
253
|
await client.unregister_listener(Message)
|
|
250
254
|
await client.unregister_coordinated(Message)
|
|
251
|
-
await client.unregister_history(
|
|
255
|
+
await client.unregister_history(CallbackQueryHistory)
|
|
252
256
|
```
|
|
253
257
|
|
|
254
258
|
---
|
|
@@ -36,7 +36,7 @@ pyroflow solves all three problems:
|
|
|
36
36
|
|
|
37
37
|
## Installation
|
|
38
38
|
|
|
39
|
-
**Minimum requirements:** Python 3.
|
|
39
|
+
**Minimum requirements:** Python 3.10+
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
pip install pyroflow # core
|
|
@@ -197,10 +197,10 @@ client.run()
|
|
|
197
197
|
A `UpdateHistory` records which handlers ran successfully for each update. This enables features like `back` buttons that replay or inspect previous processing steps.
|
|
198
198
|
|
|
199
199
|
```python
|
|
200
|
-
from pyroflow import Client,
|
|
200
|
+
from pyroflow import Client, CallbackQueryHistory
|
|
201
201
|
|
|
202
202
|
client = Client("my_session")
|
|
203
|
-
client.register_history(
|
|
203
|
+
client.register_history(CallbackQueryHistory())
|
|
204
204
|
|
|
205
205
|
client.run()
|
|
206
206
|
```
|
|
@@ -212,7 +212,7 @@ They can also be removed at runtime:
|
|
|
212
212
|
```python
|
|
213
213
|
await client.unregister_listener(Message)
|
|
214
214
|
await client.unregister_coordinated(Message)
|
|
215
|
-
await client.unregister_history(
|
|
215
|
+
await client.unregister_history(CallbackQueryHistory)
|
|
216
216
|
```
|
|
217
217
|
|
|
218
218
|
---
|
|
@@ -7,7 +7,7 @@ build-backend = "hatchling.build"
|
|
|
7
7
|
name = "pyroflow"
|
|
8
8
|
dynamic = ["version"]
|
|
9
9
|
description = "Conversation-oriented Pyrogram extension with per-update listeners and multi-session coordination"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
authors = [{ name = "Abdullah", email = "aldheeb01@gmail.com" }]
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
license = { text = "MIT" }
|
|
@@ -27,7 +27,6 @@ classifiers = [
|
|
|
27
27
|
"Intended Audience :: Developers",
|
|
28
28
|
"License :: OSI Approved :: MIT License",
|
|
29
29
|
"Programming Language :: Python :: 3",
|
|
30
|
-
"Programming Language :: Python :: 3.9",
|
|
31
30
|
"Programming Language :: Python :: 3.10",
|
|
32
31
|
"Programming Language :: Python :: 3.11",
|
|
33
32
|
"Programming Language :: Python :: 3.12",
|
|
@@ -43,7 +42,7 @@ dependencies = [
|
|
|
43
42
|
"cachetools",
|
|
44
43
|
"sortedcontainers==2.4.0",
|
|
45
44
|
"kurigram>=2.2.0",
|
|
46
|
-
"typing_extensions>=4.6.0; python_version < '3.
|
|
45
|
+
"typing_extensions>=4.6.0; python_version < '3.13'",
|
|
47
46
|
]
|
|
48
47
|
|
|
49
48
|
|
|
@@ -62,6 +61,13 @@ redis = [
|
|
|
62
61
|
"redis>=4.2.0",
|
|
63
62
|
]
|
|
64
63
|
|
|
64
|
+
test = [
|
|
65
|
+
"pytest>=7.0",
|
|
66
|
+
"pytest-asyncio>=0.23",
|
|
67
|
+
"redis>=4.2.0",
|
|
68
|
+
"tomli; python_version < '3.11'",
|
|
69
|
+
]
|
|
70
|
+
|
|
65
71
|
|
|
66
72
|
[tool.hatch.build.targets.sdist]
|
|
67
73
|
exclude = [
|
|
@@ -78,3 +84,11 @@ allow-direct-references = true
|
|
|
78
84
|
[tool.hatch.build.targets.wheel]
|
|
79
85
|
ignore-vcs = true
|
|
80
86
|
packages = ["pyroflow"]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
[tool.pytest.ini_options]
|
|
90
|
+
asyncio_mode = "auto"
|
|
91
|
+
testpaths = ["tests"]
|
|
92
|
+
filterwarnings = [
|
|
93
|
+
"ignore::DeprecationWarning",
|
|
94
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
if sys.version_info >= (3, 11):
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
else:
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
class StrEnum(str, Enum):
|
|
9
|
+
def _generate_next_value_(name, *args):
|
|
10
|
+
return name.lower()
|
|
11
|
+
def __str__(self):
|
|
12
|
+
return str(self.value)
|
|
13
|
+
|
|
14
|
+
from enum import IntEnum, auto
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UpdateLockState(IntEnum):
|
|
18
|
+
PROCESSING = 0
|
|
19
|
+
HANDLED = 1
|
|
20
|
+
|
|
21
|
+
class DuplicatePolicy(StrEnum):
|
|
22
|
+
REJECT = auto()
|
|
23
|
+
REPLACE = auto()
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Optional, Union, Tuple, TYPE_CHECKING
|
|
2
|
+
from typing import Optional, Union, Tuple, TypeAlias, TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
from ..utils.typings import AsyncLockProto
|
|
6
6
|
from ..typings import ListenerCoordinatorIdT
|
|
7
7
|
from ..models import ListenerKey
|
|
8
8
|
|
|
9
|
-
import sys
|
|
10
|
-
|
|
11
|
-
if sys.version_info >= (3, 10):
|
|
12
|
-
from typing import TypeAlias
|
|
13
|
-
else:
|
|
14
|
-
from typing_extensions import TypeAlias
|
|
15
|
-
|
|
16
9
|
if TYPE_CHECKING:
|
|
17
10
|
from ..update_listener import UpdateListener
|
|
18
11
|
|
|
@@ -8,7 +8,6 @@ import asyncio
|
|
|
8
8
|
import time
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
@dataclass(frozen=True, slots=True, eq=False)
|
|
13
12
|
class ListenerKey:
|
|
14
13
|
chat_id: int
|
|
@@ -18,11 +17,11 @@ class ListenerKey:
|
|
|
18
17
|
@property
|
|
19
18
|
def to_tuple(self) -> Tuple[int, Optional[int], Optional[int]]:
|
|
20
19
|
return (
|
|
21
|
-
self.chat_id,
|
|
22
|
-
self.user_id,
|
|
20
|
+
self.chat_id,
|
|
21
|
+
self.user_id,
|
|
23
22
|
self.message_id
|
|
24
23
|
)
|
|
25
|
-
|
|
24
|
+
|
|
26
25
|
|
|
27
26
|
def sub_keys(self, min_dep: int = 1) -> Generator["ListenerKey", None, None]:
|
|
28
27
|
keys = self.to_tuple
|
|
@@ -32,26 +31,26 @@ class ListenerKey:
|
|
|
32
31
|
yield ListenerKey(
|
|
33
32
|
*keys[:i]
|
|
34
33
|
)
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
def __hash__(self):
|
|
37
36
|
return hash(self.to_tuple)
|
|
38
|
-
|
|
37
|
+
|
|
39
38
|
def __eq__(self, value):
|
|
40
39
|
return isinstance(value, ListenerKey) and self.to_tuple == value.to_tuple
|
|
41
40
|
|
|
42
41
|
@dataclass(slots=True)
|
|
43
42
|
class ListenerModel:
|
|
44
43
|
key: ListenerKey
|
|
45
|
-
meta: Optional[JsonValueT] = None
|
|
44
|
+
meta: Optional[JsonValueT] = None
|
|
46
45
|
future: asyncio.Future = field(default_factory=asyncio.Future)
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
def resolve(self, update) -> None:
|
|
50
49
|
self.future.set_result(update)
|
|
51
|
-
|
|
50
|
+
|
|
52
51
|
def set_exc(self, exc: Exception) -> None:
|
|
53
52
|
self.future.set_exception(exc)
|
|
54
|
-
|
|
53
|
+
|
|
55
54
|
def done(self) -> bool:
|
|
56
55
|
return self.future.done()
|
|
57
56
|
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
from typing import Union, Tuple
|
|
2
|
-
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
if sys.version_info >= (3, 10):
|
|
6
|
-
from typing import TypeAlias
|
|
7
|
-
else:
|
|
8
|
-
from typing_extensions import TypeAlias
|
|
1
|
+
from typing import Union, Tuple, TypeAlias
|
|
9
2
|
|
|
10
3
|
UpdateCoordinatorKeyT: TypeAlias = Tuple[Union[int, str], ...]
|
|
11
4
|
UpdateHistoryKeyT = UpdateCoordinatorKeyT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..types import Message
|
|
4
|
+
from .update_history import UpdateHistory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MessageHistory(UpdateHistory[Message]):
|
|
8
|
+
__update_type__ = Message
|
|
9
|
+
|
|
10
|
+
if not TYPE_CHECKING:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
store_factory = None,
|
|
14
|
+
is_recordable_func = None,
|
|
15
|
+
extract_key_func = None,
|
|
16
|
+
extract_data_func = None,
|
|
17
|
+
**kw
|
|
18
|
+
):
|
|
19
|
+
|
|
20
|
+
if is_recordable_func is None:
|
|
21
|
+
is_recordable_func = self._is_recordable
|
|
22
|
+
|
|
23
|
+
if extract_key_func is None:
|
|
24
|
+
extract_key_func = self._extract_key
|
|
25
|
+
|
|
26
|
+
if extract_data_func is None:
|
|
27
|
+
extract_data_func = self._extract_data
|
|
28
|
+
|
|
29
|
+
super().__init__(store_factory, is_recordable_func, extract_key_func, extract_data_func, **kw)
|
|
30
|
+
|
|
31
|
+
async def _is_recordable(self, update):
|
|
32
|
+
return update.chat is not None
|
|
33
|
+
|
|
34
|
+
async def _extract_key(self, update):
|
|
35
|
+
return (
|
|
36
|
+
update.chat.id,
|
|
37
|
+
getattr(update.from_user, "id", None),
|
|
38
|
+
update.id,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async def _extract_data(self, update):
|
|
42
|
+
return update.text
|
|
@@ -3,18 +3,41 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
3
3
|
|
|
4
4
|
from .typings import NestedContainer, MaybeAwaitable, _True, _False, _P, _T
|
|
5
5
|
from .validate_tools import is_exception, iscoroutinefunction_wrapped
|
|
6
|
-
from .iter_tools import
|
|
6
|
+
from .iter_tools import iter_flat_cont
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import functools
|
|
10
10
|
import logging
|
|
11
11
|
import contextvars
|
|
12
12
|
import inspect
|
|
13
|
+
import traceback
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
log = logging.getLogger(__file__)
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def _log_exc(
|
|
20
|
+
header: Optional[str],
|
|
21
|
+
caller_stack: List[traceback.FrameSummary],
|
|
22
|
+
exc: BaseException,
|
|
23
|
+
index: Optional[int] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
exc_trace = traceback.format_exception(type(exc), exc, exc.__traceback__)
|
|
26
|
+
caller_trace = traceback.format_list(caller_stack)
|
|
27
|
+
|
|
28
|
+
if header is None:
|
|
29
|
+
header = ""
|
|
30
|
+
|
|
31
|
+
if index is not None:
|
|
32
|
+
header = f"{header} [awaitable index={index}]" if header else f"[awaitable index={index}]"
|
|
33
|
+
|
|
34
|
+
log.error(
|
|
35
|
+
"%s\n%s",
|
|
36
|
+
header,
|
|
37
|
+
"".join(caller_trace) + "".join(exc_trace)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
18
41
|
@overload
|
|
19
42
|
async def to_thread(
|
|
20
43
|
func: Callable[_P, _T],
|
|
@@ -37,25 +60,26 @@ async def to_thread(
|
|
|
37
60
|
func,
|
|
38
61
|
*args,
|
|
39
62
|
executor = None,
|
|
40
|
-
log_exc = True,
|
|
41
|
-
return_exc = False,
|
|
63
|
+
log_exc = True,
|
|
64
|
+
return_exc = False,
|
|
42
65
|
**kwargs,
|
|
43
66
|
):
|
|
44
|
-
|
|
67
|
+
caller_stack = traceback.extract_stack()[:-1]
|
|
68
|
+
|
|
45
69
|
loop = asyncio.get_running_loop()
|
|
46
70
|
ctx = contextvars.copy_context()
|
|
47
71
|
func = functools.partial(ctx.run, func, *args, **kwargs)
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
|
|
73
|
+
|
|
50
74
|
try:
|
|
51
75
|
return await loop.run_in_executor(executor, func)
|
|
52
76
|
except Exception as e:
|
|
53
77
|
if log_exc:
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
_log_exc("error in to_thread", caller_stack, e)
|
|
79
|
+
|
|
56
80
|
if return_exc:
|
|
57
81
|
return e
|
|
58
|
-
|
|
82
|
+
|
|
59
83
|
raise
|
|
60
84
|
|
|
61
85
|
|
|
@@ -77,12 +101,14 @@ async def gather_helper(
|
|
|
77
101
|
log_exc = True,
|
|
78
102
|
):
|
|
79
103
|
|
|
80
|
-
|
|
104
|
+
caller_stack = traceback.extract_stack()[:-1]
|
|
81
105
|
|
|
82
|
-
|
|
83
|
-
|
|
106
|
+
results = await asyncio.gather(*iter_flat_cont(coros), return_exceptions=return_exc)
|
|
107
|
+
|
|
108
|
+
if log_exc and return_exc:
|
|
109
|
+
for i, r in enumerate(results):
|
|
84
110
|
if is_exception(r):
|
|
85
|
-
|
|
111
|
+
_log_exc("error in gather_helper", caller_stack, r, index=i)
|
|
86
112
|
|
|
87
113
|
return results
|
|
88
114
|
|
|
@@ -130,15 +156,25 @@ async def safe_await(
|
|
|
130
156
|
log_exc = True,
|
|
131
157
|
):
|
|
132
158
|
|
|
159
|
+
caller_stack = traceback.extract_stack()[:-1]
|
|
160
|
+
|
|
133
161
|
results = []
|
|
162
|
+
is_multi = False
|
|
134
163
|
|
|
135
|
-
for coro in
|
|
164
|
+
for i, coro in enumerate(iter_flat_cont(coros)):
|
|
165
|
+
if not is_multi and i > 0:
|
|
166
|
+
is_multi = True
|
|
136
167
|
try:
|
|
137
168
|
result = await coro
|
|
138
169
|
except Exception as e:
|
|
139
170
|
if log_exc:
|
|
140
|
-
|
|
141
|
-
|
|
171
|
+
_log_exc(
|
|
172
|
+
"error in safe_await",
|
|
173
|
+
caller_stack,
|
|
174
|
+
e,
|
|
175
|
+
index=i if is_multi else None,
|
|
176
|
+
)
|
|
177
|
+
|
|
142
178
|
result = e
|
|
143
179
|
|
|
144
180
|
if is_exception(result):
|
|
@@ -172,21 +208,21 @@ async def maybe_awaitable(
|
|
|
172
208
|
async def maybe_awaitable(
|
|
173
209
|
awaitable_or_callable,
|
|
174
210
|
*args,
|
|
175
|
-
executor = None,
|
|
176
|
-
return_exc = False,
|
|
211
|
+
executor = None,
|
|
212
|
+
return_exc = False,
|
|
177
213
|
log_exc = True,
|
|
178
214
|
**kwargs,
|
|
179
215
|
):
|
|
180
|
-
|
|
216
|
+
|
|
181
217
|
if inspect.isawaitable(awaitable_or_callable):
|
|
182
218
|
if args or kwargs:
|
|
183
219
|
raise TypeError(
|
|
184
220
|
"Cannot pass args/kwargs to an already-created awaitable"
|
|
185
221
|
)
|
|
186
222
|
return await safe_await(
|
|
187
|
-
awaitable_or_callable,
|
|
188
|
-
return_exc=return_exc,
|
|
189
|
-
log_exc=log_exc,
|
|
223
|
+
awaitable_or_callable,
|
|
224
|
+
return_exc=return_exc,
|
|
225
|
+
log_exc=log_exc,
|
|
190
226
|
)
|
|
191
227
|
elif iscoroutinefunction_wrapped(awaitable_or_callable):
|
|
192
228
|
return await safe_await(
|
|
@@ -209,12 +245,9 @@ async def maybe_awaitable(
|
|
|
209
245
|
)
|
|
210
246
|
|
|
211
247
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
248
|
__all__ = [
|
|
216
|
-
"to_thread",
|
|
217
|
-
"gather_helper",
|
|
218
|
-
"safe_await",
|
|
249
|
+
"to_thread",
|
|
250
|
+
"gather_helper",
|
|
251
|
+
"safe_await",
|
|
219
252
|
"maybe_awaitable"
|
|
220
|
-
]
|
|
253
|
+
]
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
|
-
from typing import Generic, Type
|
|
2
|
+
from typing import Generic, Type, ClassVar
|
|
3
3
|
|
|
4
4
|
from pyrogram.types import Update as PyroUpdate
|
|
5
5
|
from .typings import UpdateType
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
-
import sys
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
if sys.version_info >= (3, 10):
|
|
12
|
-
from typing import ClassVar
|
|
13
|
-
else:
|
|
14
|
-
from typing_extensions import ClassVar
|
|
15
|
-
|
|
16
10
|
class AsyncioLock(asyncio.Lock):
|
|
17
11
|
async def release(self):
|
|
18
12
|
return super().release()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import List, Generator, Any, overload
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .typings import NestedContainer, _T
|
|
5
|
+
from .validate_tools import is_container
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@overload
|
|
11
|
+
def iter_flat_cont(*containers: None) -> Generator[Any, None, None]: ...
|
|
12
|
+
@overload
|
|
13
|
+
def iter_flat_cont(*containers: NestedContainer[None]) -> Generator[Any, None, None]: ...
|
|
14
|
+
@overload
|
|
15
|
+
def iter_flat_cont(*containers: NestedContainer[_T]) -> Generator[_T, None, None]: ...
|
|
16
|
+
def iter_flat_cont(*containers):
|
|
17
|
+
for item in containers:
|
|
18
|
+
if is_container(item):
|
|
19
|
+
yield from iter_flat_cont(*item)
|
|
20
|
+
elif item is not None:
|
|
21
|
+
yield item
|
|
22
|
+
|
|
23
|
+
@overload
|
|
24
|
+
def flat_cont(*containers: None) -> List: ...
|
|
25
|
+
@overload
|
|
26
|
+
def flat_cont(*containers: NestedContainer[None]) -> List: ...
|
|
27
|
+
@overload
|
|
28
|
+
def flat_cont(*containers: NestedContainer[_T]) -> List[_T]: ...
|
|
29
|
+
def flat_cont(*containers):
|
|
30
|
+
return list(iter_flat_cont(*containers))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = (
|
|
34
|
+
"iter_flat_cont",
|
|
35
|
+
"flat_cont",
|
|
36
|
+
|
|
37
|
+
)
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
from typing import (
|
|
2
|
-
Collection as TCollection, Generator, Union, Reversible,
|
|
3
|
-
Sequence, AbstractSet, Mapping, List, Any, Dict,
|
|
4
|
-
|
|
5
|
-
Literal, TYPE_CHECKING,
|
|
2
|
+
Collection as TCollection, Generator, Union, Reversible,
|
|
3
|
+
Sequence, AbstractSet, Mapping, List, Any, Dict,
|
|
4
|
+
Coroutine, Awaitable, TypeVar,
|
|
5
|
+
Literal, TYPE_CHECKING,
|
|
6
|
+
ParamSpec, TypeAlias, Protocol, Callable,
|
|
6
7
|
)
|
|
7
8
|
|
|
8
9
|
from enum import Enum
|
|
9
10
|
|
|
10
11
|
import sys
|
|
11
12
|
|
|
12
|
-
if sys.version_info >= (3, 10):
|
|
13
|
-
from typing import ParamSpec, TypeAlias, Protocol
|
|
14
|
-
else:
|
|
15
|
-
from typing_extensions import ParamSpec, TypeAlias, Protocol
|
|
16
|
-
|
|
17
13
|
if sys.version_info >= (3, 11):
|
|
18
14
|
from enum import EnumType
|
|
19
15
|
else:
|
|
@@ -74,15 +70,15 @@ UpdateType = TypeVar("UpdateType", bound=PyroUpdate)
|
|
|
74
70
|
|
|
75
71
|
|
|
76
72
|
__all__ = (
|
|
77
|
-
"Container",
|
|
78
|
-
"NestedContainer",
|
|
79
|
-
"NotContainer",
|
|
80
|
-
"MaybeAwaitable",
|
|
81
|
-
"Number",
|
|
82
|
-
"MaybeCoroutineCallable",
|
|
83
|
-
"AsyncLockProto",
|
|
84
|
-
"JsonValueT",
|
|
85
|
-
"UpdateType",
|
|
86
|
-
"_P", "_T", "_CT",
|
|
87
|
-
"_True", "_False",
|
|
73
|
+
"Container",
|
|
74
|
+
"NestedContainer",
|
|
75
|
+
"NotContainer",
|
|
76
|
+
"MaybeAwaitable",
|
|
77
|
+
"Number",
|
|
78
|
+
"MaybeCoroutineCallable",
|
|
79
|
+
"AsyncLockProto",
|
|
80
|
+
"JsonValueT",
|
|
81
|
+
"UpdateType",
|
|
82
|
+
"_P", "_T", "_CT",
|
|
83
|
+
"_True", "_False",
|
|
88
84
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Union
|
|
1
|
+
from typing import Any, Union, get_args
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
import inspect
|
|
@@ -6,20 +6,21 @@ import inspect
|
|
|
6
6
|
|
|
7
7
|
if sys.version_info >= (3, 13):
|
|
8
8
|
from typing import TypeIs
|
|
9
|
-
elif sys.version_info >= (3, 10):
|
|
10
|
-
from typing_extensions import TypeIs
|
|
11
9
|
else:
|
|
12
|
-
from typing_extensions import
|
|
10
|
+
from typing_extensions import TypeIs
|
|
13
11
|
|
|
14
12
|
from .typings import Container, NotContainer, _T
|
|
15
13
|
|
|
14
|
+
_CONTAINER_TYPES = get_args(Container)
|
|
15
|
+
_NOT_CONTAINER_TYPES = get_args(NotContainer)
|
|
16
|
+
|
|
16
17
|
def is_exception(obj: Any) -> TypeIs[BaseException]:
|
|
17
18
|
return isinstance(obj, BaseException)
|
|
18
19
|
|
|
19
20
|
def is_container(obj: Any) -> TypeIs[Union['Container[_T]', Any]]:
|
|
20
|
-
return isinstance(obj,
|
|
21
|
+
return isinstance(obj, _CONTAINER_TYPES) and not isinstance(obj, _NOT_CONTAINER_TYPES)
|
|
21
22
|
|
|
22
|
-
def iscoroutinefunction_wrapped(f):
|
|
23
|
+
def iscoroutinefunction_wrapped(f) -> bool:
|
|
23
24
|
is_coro = False
|
|
24
25
|
|
|
25
26
|
def _stop(func):
|
|
File without changes
|