matrix-python 1.0.0b0__py3-none-any.whl → 1.0.1__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.
matrix/__init__.py CHANGED
@@ -8,21 +8,32 @@ except PackageNotFoundError:
8
8
  from matrix._version import version as __version__
9
9
 
10
10
  from .bot import Bot
11
- from .group import Group
11
+ from .group import Group, group
12
12
  from .config import Config
13
13
  from .context import Context
14
14
  from .command import Command
15
15
  from .help import HelpCommand
16
- from .checks import cooldown
16
+ from .checks import cooldown, is_admin, is_moderator
17
17
  from .room import Room
18
+ from .space import Space
19
+ from .message import Message
20
+ from .extension import Extension
21
+ from .component import Table
18
22
 
19
23
  __all__ = [
20
24
  "Bot",
21
25
  "Group",
26
+ "group",
22
27
  "Config",
23
28
  "Command",
24
29
  "Context",
25
30
  "HelpCommand",
26
31
  "cooldown",
32
+ "is_admin",
33
+ "is_moderator",
27
34
  "Room",
35
+ "Space",
36
+ "Message",
37
+ "Extension",
38
+ "Table",
28
39
  ]
@@ -0,0 +1,14 @@
1
+ import inspect
2
+ from typing import Callable, Dict, Optional, TypeVar
3
+
4
+ F = TypeVar("F", bound=Callable[..., object])
5
+
6
+
7
+ def resolve_error_handler(
8
+ handlers: Dict[type[Exception], F], error: Exception
9
+ ) -> Optional[F]:
10
+ """Look up the handler registered for the error's type or nearest base class."""
11
+ for cls in inspect.getmro(type(error)):
12
+ if cls in handlers:
13
+ return handlers[cls]
14
+ return None
matrix/_version.py CHANGED
@@ -1,5 +1,6 @@
1
- # file generated by setuptools-scm
1
+ # file generated by vcs-versioning
2
2
  # don't change, don't track in version control
3
+ from __future__ import annotations
3
4
 
4
5
  __all__ = [
5
6
  "__version__",
@@ -10,25 +11,14 @@ __all__ = [
10
11
  "commit_id",
11
12
  ]
12
13
 
13
- TYPE_CHECKING = False
14
- if TYPE_CHECKING:
15
- from typing import Tuple
16
- from typing import Union
17
-
18
- VERSION_TUPLE = Tuple[Union[int, str], ...]
19
- COMMIT_ID = Union[str, None]
20
- else:
21
- VERSION_TUPLE = object
22
- COMMIT_ID = object
23
-
24
14
  version: str
25
15
  __version__: str
26
- __version_tuple__: VERSION_TUPLE
27
- version_tuple: VERSION_TUPLE
28
- commit_id: COMMIT_ID
29
- __commit_id__: COMMIT_ID
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
30
20
 
31
- __version__ = version = '1.0.0b0'
32
- __version_tuple__ = version_tuple = (1, 0, 0, 'b0')
21
+ __version__ = version = '1.0.1'
22
+ __version_tuple__ = version_tuple = (1, 0, 1)
33
23
 
34
- __commit_id__ = commit_id = None
24
+ __commit_id__ = commit_id = 'g8e6ddd04d'
matrix/api.py ADDED
@@ -0,0 +1,111 @@
1
+ import asyncio
2
+ from typing import Awaitable, Callable, Iterable, TypeVar
3
+
4
+ from nio import ErrorResponse, Response
5
+
6
+ from matrix.errors import MatrixApiError
7
+
8
+ T = TypeVar("T", bound=Response)
9
+ R = TypeVar("R")
10
+
11
+
12
+ async def matrix_call(coro: Awaitable[T], /, *, error_message: str) -> T:
13
+ """Await `coro`, translating any failure into a `MatrixApiError`.
14
+
15
+ matrix-nio's `AsyncClient` methods don't raise on API-level errors; they
16
+ return an `ErrorResponse` instead of raising. This wraps a single call so
17
+ both transport-level exceptions and nio `ErrorResponse` results become a
18
+ `MatrixApiError` carrying `error_message`. When the `ErrorResponse` is a
19
+ rate limit with a `retry_after_ms`, that value is carried through
20
+ unchanged on `MatrixApiError.retry_after_ms`.
21
+
22
+ ## Example
23
+
24
+ ```python
25
+ response = await matrix_call(
26
+ self.client.room_kick(room_id=self.room_id, user_id=user_id),
27
+ error_message="Failed to kick user",
28
+ )
29
+ ```
30
+ """
31
+ try:
32
+ response = await coro
33
+ except Exception as e:
34
+ raise MatrixApiError(f"{error_message}: {e}") from e
35
+
36
+ if isinstance(response, ErrorResponse):
37
+ raise MatrixApiError(
38
+ f"{error_message}: {response}", retry_after_ms=response.retry_after_ms
39
+ )
40
+
41
+ return response
42
+
43
+
44
+ async def with_retry(
45
+ func: Callable[[], Awaitable[R]],
46
+ /,
47
+ *,
48
+ retries: int = 3,
49
+ base_delay: float = 1.0,
50
+ ) -> R:
51
+ """Call `func`, retrying on `MatrixApiError` with backoff.
52
+
53
+ Retries up to `retries` times after the initial attempt (so up to
54
+ `retries + 1` total calls). If the raised `MatrixApiError` carries a
55
+ `retry_after_ms` (from the Matrix server), that wait is used exactly
56
+ (converted to seconds); otherwise falls back to exponential delay
57
+ (`base_delay * 2 ** attempt`). Only `MatrixApiError` triggers a retry;
58
+ any other exception propagates.
59
+
60
+ ## Example
61
+
62
+ ```python
63
+ message = await with_retry(lambda: room.send(content, notice=notice))
64
+ ```
65
+ """
66
+ for attempt in range(retries + 1):
67
+ try:
68
+ return await func()
69
+ except MatrixApiError as e:
70
+ if attempt == retries:
71
+ raise
72
+
73
+ delay = base_delay * 2**attempt
74
+ if e.retry_after_ms is not None:
75
+ delay = e.retry_after_ms / 1000
76
+
77
+ await asyncio.sleep(delay)
78
+
79
+ raise AssertionError("unreachable")
80
+
81
+
82
+ async def bounded_gather(
83
+ coros: Iterable[Awaitable[R]],
84
+ /,
85
+ *,
86
+ max_concurrent: int = 8,
87
+ ) -> list[R]:
88
+ """Run `coros` concurrently, limited to `max_concurrent` at a time.
89
+
90
+ Behaves like `asyncio.gather`, but never runs more than `max_concurrent`
91
+ awaitables at once. Useful for fanning out many API calls (e.g.
92
+ broadcasting to many rooms) without overwhelming a shared rate limit by
93
+ firing every request at once. The first exception raised by any
94
+ coroutine cancels the rest and propagates immediately, same as
95
+ `asyncio.gather`'s default behavior.
96
+
97
+ ## Example
98
+
99
+ ```python
100
+ messages = await bounded_gather(
101
+ (room.send(content) for room in rooms), max_concurrent=8
102
+ )
103
+ ```
104
+ """
105
+ semaphore = asyncio.Semaphore(max_concurrent)
106
+
107
+ async def _run(coro: Awaitable[R]) -> R:
108
+ async with semaphore:
109
+ return await coro
110
+
111
+ return await asyncio.gather(*[_run(coro) for coro in coros])