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.
Files changed (59) hide show
  1. {pyroflow-0.1.6 → pyroflow-0.2.1}/PKG-INFO +12 -8
  2. {pyroflow-0.1.6 → pyroflow-0.2.1}/README.md +4 -4
  3. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyproject.toml +17 -3
  4. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/__init__.py +3 -0
  5. pyroflow-0.2.1/pyroflow/__meta__.py +1 -0
  6. pyroflow-0.2.1/pyroflow/enums.py +23 -0
  7. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/listener_coordinator/listener_coordinator.py +1 -8
  8. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/models.py +8 -9
  9. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/typings.py +1 -8
  10. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinated/callback_query_coordinated.py +1 -1
  11. pyroflow-0.2.1/pyroflow/update_history/message_history.py +42 -0
  12. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/async_tools.py +62 -29
  13. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/classes.py +1 -7
  14. pyroflow-0.2.1/pyroflow/utils/iter_tools.py +37 -0
  15. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/typings.py +16 -20
  16. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/validate_tools.py +7 -6
  17. pyroflow-0.2.1/tests/__init__.py +0 -0
  18. pyroflow-0.2.1/tests/conftest.py +70 -0
  19. pyroflow-0.2.1/tests/test_compat_static.py +144 -0
  20. pyroflow-0.2.1/tests/test_coordinator.py +110 -0
  21. pyroflow-0.2.1/tests/test_enums.py +31 -0
  22. pyroflow-0.2.1/tests/test_history.py +143 -0
  23. pyroflow-0.2.1/tests/test_imports.py +100 -0
  24. pyroflow-0.2.1/tests/test_listener.py +136 -0
  25. pyroflow-0.2.1/tests/test_models.py +84 -0
  26. pyroflow-0.2.1/tests/test_patch_cls.py +118 -0
  27. pyroflow-0.2.1/tests/test_utils.py +149 -0
  28. pyroflow-0.1.6/pyroflow/__meta__.py +0 -1
  29. pyroflow-0.1.6/pyroflow/enums.py +0 -10
  30. pyroflow-0.1.6/pyroflow/update_history/message_history.py +0 -6
  31. pyroflow-0.1.6/pyroflow/utils/iter_tools.py +0 -32
  32. {pyroflow-0.1.6 → pyroflow-0.2.1}/LICENSE +0 -0
  33. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/client.py +0 -0
  34. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/dispatcher.py +0 -0
  35. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/errors.py +0 -0
  36. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/listener_coordinator/__init__.py +0 -0
  37. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/listener_coordinator/memory_listener_coordinator.py +0 -0
  38. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/listener_coordinator/redis_listener_coordinator.py +0 -0
  39. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/types.py +0 -0
  40. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinated/__init__.py +0 -0
  41. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinated/message_coordinated.py +0 -0
  42. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinated/update_coordinated.py +0 -0
  43. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinator/__init__.py +0 -0
  44. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinator/memory_update_coordinator.py +0 -0
  45. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinator/redis_update_coordinator.py +0 -0
  46. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_coordinator/update_coordinator.py +0 -0
  47. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history/__init__.py +0 -0
  48. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history/callback_query_history.py +0 -0
  49. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history/update_history.py +0 -0
  50. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history_store/__init__.py +0 -0
  51. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history_store/memory_update_history_store.py +0 -0
  52. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_history_store/update_history_store.py +0 -0
  53. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_listener/__init__.py +0 -0
  54. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_listener/callback_query_listener.py +0 -0
  55. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_listener/message_listener.py +0 -0
  56. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/update_listener/update_listener.py +0 -0
  57. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/__init__.py +0 -0
  58. {pyroflow-0.1.6 → pyroflow-0.2.1}/pyroflow/utils/enums.py +0 -0
  59. {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.6
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.9
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.10'
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.9+
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, MessageHistory
240
+ from pyroflow import Client, CallbackQueryHistory
237
241
 
238
242
  client = Client("my_session")
239
- client.register_history(MessageHistory())
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(Message)
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.9+
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, MessageHistory
200
+ from pyroflow import Client, CallbackQueryHistory
201
201
 
202
202
  client = Client("my_session")
203
- client.register_history(MessageHistory())
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(Message)
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.9"
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.10'",
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
+ ]
@@ -1,3 +1,5 @@
1
+ from .__meta__ import __version__
2
+
1
3
  from .listener_coordinator import *
2
4
  from .update_coordinated import *
3
5
  from .update_coordinator import *
@@ -12,3 +14,4 @@ from .types import Message, CallbackQuery
12
14
 
13
15
 
14
16
 
17
+
@@ -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
@@ -28,4 +28,4 @@ class CallbackQueryCoordinated(UpdateCoordinated[CallbackQuery]):
28
28
  return (update.id, )
29
29
 
30
30
  async def _is_coordinatable(self, update):
31
- True
31
+ return True
@@ -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 flat_cont
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
- log.exception("error in to_thread")
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
- results = await asyncio.gather(*flat_cont(coros), return_exceptions=return_exc)
104
+ caller_stack = traceback.extract_stack()[:-1]
81
105
 
82
- if log_exc:
83
- for r in results:
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
- log.error("error in gather_helper", exc_info=r)
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 flat_cont(coros):
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
- log.exception("Error in safe_await")
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
- Callable, Coroutine, Awaitable, TypeVar,
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 TypeGuard as TypeIs
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, Container) and not isinstance(obj, NotContainer)
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