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 +13 -2
- matrix/_error_handler.py +14 -0
- matrix/_version.py +9 -19
- matrix/api.py +111 -0
- matrix/bot.py +299 -364
- matrix/checks.py +105 -4
- matrix/command.py +50 -51
- matrix/component.py +109 -0
- matrix/config.py +108 -44
- matrix/content.py +15 -0
- matrix/context.py +6 -5
- matrix/errors.py +23 -6
- matrix/extension.py +90 -0
- matrix/group.py +61 -7
- matrix/help/help_command.py +62 -72
- matrix/help/pagination.py +4 -22
- matrix/member.py +217 -0
- matrix/message.py +202 -38
- matrix/protocols.py +13 -0
- matrix/py.typed +0 -0
- matrix/registry.py +460 -0
- matrix/room.py +175 -48
- matrix/scheduler.py +13 -10
- matrix/space.py +45 -0
- matrix/types.py +15 -0
- matrix_python-1.0.1.dist-info/METADATA +153 -0
- matrix_python-1.0.1.dist-info/RECORD +30 -0
- {matrix_python-1.0.0b0.dist-info → matrix_python-1.0.1.dist-info}/WHEEL +1 -1
- matrix_python-1.0.0b0.dist-info/METADATA +0 -773
- matrix_python-1.0.0b0.dist-info/RECORD +0 -21
- {matrix_python-1.0.0b0.dist-info → matrix_python-1.0.1.dist-info}/top_level.txt +0 -0
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
|
]
|
matrix/_error_handler.py
ADDED
|
@@ -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
|
|
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__:
|
|
27
|
-
version_tuple:
|
|
28
|
-
commit_id:
|
|
29
|
-
__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.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 1)
|
|
33
23
|
|
|
34
|
-
__commit_id__ = commit_id =
|
|
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])
|