matrix-python 1.4.7a0__tar.gz → 1.4.9a0__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.
- matrix_python-1.4.9a0/LICENSE +21 -0
- matrix_python-1.4.9a0/PKG-INFO +128 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/README.md +2 -1
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/_version.py +3 -3
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/extension.py +17 -9
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/protocols.py +4 -0
- matrix_python-1.4.9a0/matrix/py.typed +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/registry.py +74 -11
- matrix_python-1.4.9a0/matrix_python.egg-info/PKG-INFO +128 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix_python.egg-info/SOURCES.txt +1 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/pyproject.toml +3 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_extension.py +11 -4
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_registry.py +63 -1
- matrix_python-1.4.7a0/LICENSE +0 -674
- matrix_python-1.4.7a0/PKG-INFO +0 -781
- matrix_python-1.4.7a0/matrix_python.egg-info/PKG-INFO +0 -781
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/dependabot.yml +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/CODEOWNERS +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/codeql.yml +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/publish.yml +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/scorecard.yml +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/tests.yml +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.gitignore +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/CODE_OF_CONDUCT.md +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/CONTRIBUTING.md +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/README.md +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/checks.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/config.yaml +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/cooldown.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/error_handling.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/extension.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/ping.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/reaction.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/scheduler.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/__init__.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/bot.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/checks.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/command.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/config.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/content.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/context.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/errors.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/group.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/help/__init__.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/help/help_command.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/help/pagination.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/message.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/room.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/scheduler.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/types.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix_python.egg-info/dependency_links.txt +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix_python.egg-info/requires.txt +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix_python.egg-info/top_level.txt +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/mypy.ini +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/setup.cfg +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/help/test_default_help_command.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/help/test_help_command.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/help/test_pagination.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_bot.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_command.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_config.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_context.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_group.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_message.py +0 -0
- {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_room.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Sebastián Ramírez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matrix-python
|
|
3
|
+
Version: 1.4.9a0
|
|
4
|
+
Summary: An easy-to-use Matrix bot framework designed for quick development and minimal setup
|
|
5
|
+
Author: Simon Roy, Chris Dedman Rollet
|
|
6
|
+
Maintainer-email: Code Society Lab <admin@codesociety.xyz>
|
|
7
|
+
License: The MIT License (MIT)
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2021 Sebastián Ramírez
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in
|
|
19
|
+
all copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
27
|
+
THE SOFTWARE.
|
|
28
|
+
Project-URL: Homepage, https://codesociety.xyz
|
|
29
|
+
Project-URL: Source, https://github.com/Code-Society-Lab/matrixpy
|
|
30
|
+
Project-URL: Issues, https://github.com/Code-Society-Lab/matrixpy/issues
|
|
31
|
+
Requires-Python: >=3.10
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
Requires-Dist: matrix-nio==0.25.2
|
|
34
|
+
Requires-Dist: logger
|
|
35
|
+
Requires-Dist: PyYAML==6.0.3
|
|
36
|
+
Requires-Dist: markdown==3.10.2
|
|
37
|
+
Requires-Dist: APScheduler==3.11.2
|
|
38
|
+
Requires-Dist: envyaml==1.10.211231
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: pytest==9.0.3; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-asyncio==1.3.0; extra == "dev"
|
|
42
|
+
Requires-Dist: black==26.3.1; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy==1.20.0; extra == "dev"
|
|
44
|
+
Requires-Dist: types-PyYAML==6.0.12.20260408; extra == "dev"
|
|
45
|
+
Requires-Dist: types-Markdown==3.10.2.20260408; extra == "dev"
|
|
46
|
+
|
|
47
|
+
<div align="center">
|
|
48
|
+
<em>A simple, developer-friendly library to create powerful <a href="https://matrix.org">Matrix</a> bots.</em>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<img alt="image" src="https://github.com/user-attachments/assets/d9140a9e-27fa-44e4-a5ca-87ee7bbf868f" />
|
|
52
|
+
|
|
53
|
+
<hr />
|
|
54
|
+
|
|
55
|
+
[](https://github.com/Code-Society-Lab/matrixpy/wiki)
|
|
56
|
+
[](https://discord.gg/code-society-823178343943897088)
|
|
57
|
+
[](https://matrix.to/#/%23codesociety:matrix.org )
|
|
58
|
+
[](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/tests.yml)
|
|
59
|
+
[](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/codeql.yml)
|
|
60
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/Code-Society-Lab/matrixpy)
|
|
61
|
+
|
|
62
|
+
Matrix.py is a lightweight and intuitive Python library to build bots on
|
|
63
|
+
the [Matrix protocol](https://matrix.org). It provides a clean,
|
|
64
|
+
decorator-based API similar to popular event-driven frameworks, allowing
|
|
65
|
+
developers to focus on behavior rather than boilerplate.
|
|
66
|
+
|
|
67
|
+
#### Key Features
|
|
68
|
+
|
|
69
|
+
- Minimal setup, easy to extend
|
|
70
|
+
- Event-driven API using async/await
|
|
71
|
+
- Clean command registration
|
|
72
|
+
- Automatic event handler registration
|
|
73
|
+
- Built on [matrix-nio](https://github.com/matrix-nio/matrix-nio)
|
|
74
|
+
|
|
75
|
+
# Quickstart
|
|
76
|
+
|
|
77
|
+
**Requirements**
|
|
78
|
+
|
|
79
|
+
- Python 3.10+
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
pip install matrix-python
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If you plan on contributing to matrix.py, we recommend to install the development libraries:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
pip install -e .[dev]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
*Note*: It is recommended to use
|
|
92
|
+
a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)
|
|
93
|
+
when installing python packages.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from matrix import Bot, Context
|
|
97
|
+
|
|
98
|
+
bot = Bot()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@bot.command("ping")
|
|
102
|
+
async def ping(ctx: Context):
|
|
103
|
+
await ctx.reply("Pong!")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
bot.start(config="config.yml")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
[Documentation](https://github.com/Code-Society-Lab/matrixpy/wiki) - [Examples](https://github.com/Code-Society-Lab/matrixpy/tree/main/examples)
|
|
110
|
+
|
|
111
|
+
# Contributing
|
|
112
|
+
|
|
113
|
+
We welcome everyone to contribute!
|
|
114
|
+
|
|
115
|
+
Whether it's fixing bugs, suggesting features, or improving the docs - every bit helps.
|
|
116
|
+
|
|
117
|
+
- Submit an issue
|
|
118
|
+
- Open a pull request
|
|
119
|
+
- Or just hop into our [Matrix](https://matrix.to/#/%23codesociety:matrix.org)
|
|
120
|
+
or [Discord](https://discord.gg/code-society-823178343943897088) server and say hi!
|
|
121
|
+
|
|
122
|
+
If you intend to contribute, please read the [CONTRIBUTING.md](./CONTRIBUTING.md) first. Additionally, **every
|
|
123
|
+
contributor** is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
|
|
124
|
+
|
|
125
|
+
# License
|
|
126
|
+
|
|
127
|
+
This project is licensed under the terms
|
|
128
|
+
of [MIT license](https://github.com/Code-Society-Lab/matrixpy/blob/main/LICENSE).
|
|
@@ -78,4 +78,5 @@ contributor** is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
|
|
|
78
78
|
|
|
79
79
|
# License
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
This project is licensed under the terms
|
|
82
|
+
of [MIT license](https://github.com/Code-Society-Lab/matrixpy/blob/main/LICENSE).
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.4.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 4,
|
|
21
|
+
__version__ = version = '1.4.9a0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 4, 9, 'a0')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g5f8d673e3'
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Callable
|
|
3
|
+
from typing import Callable
|
|
4
4
|
|
|
5
5
|
from matrix.protocols import BotLike
|
|
6
6
|
from matrix.registry import Registry
|
|
7
|
+
from matrix.config import Config
|
|
7
8
|
from matrix.room import Room
|
|
8
9
|
|
|
9
10
|
logger = logging.getLogger(__name__)
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class Extension(Registry):
|
|
13
|
-
def __init__(self, name: str, prefix:
|
|
14
|
+
def __init__(self, name: str, prefix: str | None = None) -> None:
|
|
14
15
|
super().__init__(name, prefix=prefix)
|
|
15
16
|
|
|
16
|
-
self.
|
|
17
|
-
self._on_load:
|
|
18
|
-
self._on_unload:
|
|
17
|
+
self._bot: BotLike | None = None
|
|
18
|
+
self._on_load: Callable | None = None
|
|
19
|
+
self._on_unload: Callable | None = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def bot(self) -> BotLike:
|
|
23
|
+
assert self._bot, "Extension is not loaded"
|
|
24
|
+
return self._bot
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def config(self) -> Config:
|
|
28
|
+
return self.bot.config
|
|
19
29
|
|
|
20
30
|
def get_room(self, room_id: str) -> Room | None:
|
|
21
31
|
"""Retrieve a `Room` instance by its Matrix room ID.
|
|
@@ -31,12 +41,10 @@ class Extension(Registry):
|
|
|
31
41
|
print(room.name)
|
|
32
42
|
```
|
|
33
43
|
"""
|
|
34
|
-
if self.bot is None:
|
|
35
|
-
raise RuntimeError("Extension is not loaded")
|
|
36
44
|
return self.bot.get_room(room_id)
|
|
37
45
|
|
|
38
46
|
def load(self, bot: BotLike) -> None:
|
|
39
|
-
self.
|
|
47
|
+
self._bot = bot
|
|
40
48
|
|
|
41
49
|
if self._on_load:
|
|
42
50
|
self._on_load()
|
|
@@ -59,7 +67,7 @@ class Extension(Registry):
|
|
|
59
67
|
return func
|
|
60
68
|
|
|
61
69
|
def unload(self) -> None:
|
|
62
|
-
self.
|
|
70
|
+
self._bot = None
|
|
63
71
|
|
|
64
72
|
if self._on_unload:
|
|
65
73
|
self._on_unload()
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
from typing import Protocol
|
|
2
2
|
|
|
3
|
+
from matrix.config import Config
|
|
3
4
|
from matrix.room import Room
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class BotLike(Protocol):
|
|
7
8
|
prefix: str | None
|
|
8
9
|
|
|
10
|
+
@property
|
|
11
|
+
def config(self) -> Config: ...
|
|
12
|
+
|
|
9
13
|
def get_room(self, room_id: str) -> Room | None: ...
|
|
File without changes
|
|
@@ -2,7 +2,20 @@ import inspect
|
|
|
2
2
|
import logging
|
|
3
3
|
|
|
4
4
|
from collections import defaultdict
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import (
|
|
6
|
+
TypeVar,
|
|
7
|
+
Any,
|
|
8
|
+
Callable,
|
|
9
|
+
Coroutine,
|
|
10
|
+
Literal,
|
|
11
|
+
Optional,
|
|
12
|
+
Type,
|
|
13
|
+
Union,
|
|
14
|
+
Dict,
|
|
15
|
+
List,
|
|
16
|
+
cast,
|
|
17
|
+
overload,
|
|
18
|
+
)
|
|
6
19
|
|
|
7
20
|
from nio import (
|
|
8
21
|
Event,
|
|
@@ -25,6 +38,8 @@ GroupCallable = Callable[[Callable[..., Coroutine[Any, Any, Any]]], Group]
|
|
|
25
38
|
ErrorCallback = Callable[[Exception], Coroutine]
|
|
26
39
|
CommandErrorCallback = Callable[[Context, Exception], Coroutine[Any, Any, Any]]
|
|
27
40
|
|
|
41
|
+
F = TypeVar("F", ErrorCallback, CommandErrorCallback)
|
|
42
|
+
|
|
28
43
|
|
|
29
44
|
class Registry:
|
|
30
45
|
"""
|
|
@@ -355,43 +370,91 @@ class Registry:
|
|
|
355
370
|
|
|
356
371
|
return wrapper
|
|
357
372
|
|
|
373
|
+
@overload
|
|
358
374
|
def error(
|
|
359
|
-
self,
|
|
360
|
-
|
|
375
|
+
self,
|
|
376
|
+
exception: Optional[type[Exception]] = None,
|
|
377
|
+
*,
|
|
378
|
+
context: Literal[True],
|
|
379
|
+
) -> Callable[[CommandErrorCallback], CommandErrorCallback]: ...
|
|
380
|
+
|
|
381
|
+
@overload
|
|
382
|
+
def error(
|
|
383
|
+
self,
|
|
384
|
+
exception: Optional[type[Exception]] = None,
|
|
385
|
+
*,
|
|
386
|
+
context: Literal[False] = ...,
|
|
387
|
+
) -> Callable[[ErrorCallback], ErrorCallback]: ...
|
|
388
|
+
|
|
389
|
+
def error(
|
|
390
|
+
self,
|
|
391
|
+
exception: Optional[type[Exception]] = None,
|
|
392
|
+
*,
|
|
393
|
+
context: bool = False,
|
|
394
|
+
) -> Union[
|
|
395
|
+
Callable[[ErrorCallback], ErrorCallback],
|
|
396
|
+
Callable[[CommandErrorCallback], CommandErrorCallback],
|
|
397
|
+
]:
|
|
361
398
|
"""Decorator to register an error handler.
|
|
362
399
|
|
|
363
400
|
If an exception type is provided, the handler is only invoked for
|
|
364
401
|
that specific exception. If omitted, the handler acts as a generic
|
|
365
402
|
fallback for any unhandled error.
|
|
366
403
|
|
|
404
|
+
Set ``context=True`` to receive the command context alongside the error,
|
|
405
|
+
useful for command-specific errors where you want to reply to the user.
|
|
406
|
+
|
|
367
407
|
## Example
|
|
368
408
|
|
|
369
409
|
```python
|
|
370
410
|
@bot.error(ValueError)
|
|
371
411
|
async def on_value_error(error):
|
|
372
|
-
|
|
412
|
+
pass
|
|
373
413
|
|
|
374
414
|
@bot.error()
|
|
375
415
|
async def on_any_error(error):
|
|
376
|
-
|
|
416
|
+
pass
|
|
417
|
+
|
|
418
|
+
@bot.error(CommandNotFoundError, context=True)
|
|
419
|
+
async def on_command_not_found(ctx, error):
|
|
420
|
+
await ctx.reply("Command not found!")
|
|
377
421
|
```
|
|
378
422
|
"""
|
|
379
423
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
def wrapper(func: ErrorCallback) -> ErrorCallback:
|
|
424
|
+
def wrapper(
|
|
425
|
+
func: F,
|
|
426
|
+
) -> F:
|
|
384
427
|
if not inspect.iscoroutinefunction(func):
|
|
385
428
|
raise TypeError("Error handlers must be coroutines")
|
|
386
429
|
|
|
387
|
-
|
|
430
|
+
if context:
|
|
431
|
+
self._register_command_error(
|
|
432
|
+
cast(CommandErrorCallback, func), exception
|
|
433
|
+
)
|
|
434
|
+
else:
|
|
435
|
+
self._register_error(cast(ErrorCallback, func), exception)
|
|
388
436
|
|
|
389
437
|
logger.debug(
|
|
390
438
|
"registered error handler '%s' on %s",
|
|
391
439
|
func.__name__,
|
|
392
440
|
type(self).__name__,
|
|
393
441
|
)
|
|
394
|
-
|
|
395
442
|
return func
|
|
396
443
|
|
|
397
444
|
return wrapper
|
|
445
|
+
|
|
446
|
+
def _register_error(
|
|
447
|
+
self, func: ErrorCallback, exception: Optional[type[Exception]] = None
|
|
448
|
+
) -> None:
|
|
449
|
+
if not exception:
|
|
450
|
+
exception = Exception
|
|
451
|
+
self._error_handlers[exception] = func
|
|
452
|
+
|
|
453
|
+
def _register_command_error(
|
|
454
|
+
self,
|
|
455
|
+
func: CommandErrorCallback,
|
|
456
|
+
exception: Optional[type[Exception]] = None,
|
|
457
|
+
) -> None:
|
|
458
|
+
if not exception:
|
|
459
|
+
exception = Exception
|
|
460
|
+
self._command_error_handlers[exception] = func
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: matrix-python
|
|
3
|
+
Version: 1.4.9a0
|
|
4
|
+
Summary: An easy-to-use Matrix bot framework designed for quick development and minimal setup
|
|
5
|
+
Author: Simon Roy, Chris Dedman Rollet
|
|
6
|
+
Maintainer-email: Code Society Lab <admin@codesociety.xyz>
|
|
7
|
+
License: The MIT License (MIT)
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2021 Sebastián Ramírez
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in
|
|
19
|
+
all copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
27
|
+
THE SOFTWARE.
|
|
28
|
+
Project-URL: Homepage, https://codesociety.xyz
|
|
29
|
+
Project-URL: Source, https://github.com/Code-Society-Lab/matrixpy
|
|
30
|
+
Project-URL: Issues, https://github.com/Code-Society-Lab/matrixpy/issues
|
|
31
|
+
Requires-Python: >=3.10
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
Requires-Dist: matrix-nio==0.25.2
|
|
34
|
+
Requires-Dist: logger
|
|
35
|
+
Requires-Dist: PyYAML==6.0.3
|
|
36
|
+
Requires-Dist: markdown==3.10.2
|
|
37
|
+
Requires-Dist: APScheduler==3.11.2
|
|
38
|
+
Requires-Dist: envyaml==1.10.211231
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: pytest==9.0.3; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-asyncio==1.3.0; extra == "dev"
|
|
42
|
+
Requires-Dist: black==26.3.1; extra == "dev"
|
|
43
|
+
Requires-Dist: mypy==1.20.0; extra == "dev"
|
|
44
|
+
Requires-Dist: types-PyYAML==6.0.12.20260408; extra == "dev"
|
|
45
|
+
Requires-Dist: types-Markdown==3.10.2.20260408; extra == "dev"
|
|
46
|
+
|
|
47
|
+
<div align="center">
|
|
48
|
+
<em>A simple, developer-friendly library to create powerful <a href="https://matrix.org">Matrix</a> bots.</em>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<img alt="image" src="https://github.com/user-attachments/assets/d9140a9e-27fa-44e4-a5ca-87ee7bbf868f" />
|
|
52
|
+
|
|
53
|
+
<hr />
|
|
54
|
+
|
|
55
|
+
[](https://github.com/Code-Society-Lab/matrixpy/wiki)
|
|
56
|
+
[](https://discord.gg/code-society-823178343943897088)
|
|
57
|
+
[](https://matrix.to/#/%23codesociety:matrix.org )
|
|
58
|
+
[](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/tests.yml)
|
|
59
|
+
[](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/codeql.yml)
|
|
60
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/Code-Society-Lab/matrixpy)
|
|
61
|
+
|
|
62
|
+
Matrix.py is a lightweight and intuitive Python library to build bots on
|
|
63
|
+
the [Matrix protocol](https://matrix.org). It provides a clean,
|
|
64
|
+
decorator-based API similar to popular event-driven frameworks, allowing
|
|
65
|
+
developers to focus on behavior rather than boilerplate.
|
|
66
|
+
|
|
67
|
+
#### Key Features
|
|
68
|
+
|
|
69
|
+
- Minimal setup, easy to extend
|
|
70
|
+
- Event-driven API using async/await
|
|
71
|
+
- Clean command registration
|
|
72
|
+
- Automatic event handler registration
|
|
73
|
+
- Built on [matrix-nio](https://github.com/matrix-nio/matrix-nio)
|
|
74
|
+
|
|
75
|
+
# Quickstart
|
|
76
|
+
|
|
77
|
+
**Requirements**
|
|
78
|
+
|
|
79
|
+
- Python 3.10+
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
pip install matrix-python
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If you plan on contributing to matrix.py, we recommend to install the development libraries:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
pip install -e .[dev]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
*Note*: It is recommended to use
|
|
92
|
+
a [virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)
|
|
93
|
+
when installing python packages.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from matrix import Bot, Context
|
|
97
|
+
|
|
98
|
+
bot = Bot()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@bot.command("ping")
|
|
102
|
+
async def ping(ctx: Context):
|
|
103
|
+
await ctx.reply("Pong!")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
bot.start(config="config.yml")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
[Documentation](https://github.com/Code-Society-Lab/matrixpy/wiki) - [Examples](https://github.com/Code-Society-Lab/matrixpy/tree/main/examples)
|
|
110
|
+
|
|
111
|
+
# Contributing
|
|
112
|
+
|
|
113
|
+
We welcome everyone to contribute!
|
|
114
|
+
|
|
115
|
+
Whether it's fixing bugs, suggesting features, or improving the docs - every bit helps.
|
|
116
|
+
|
|
117
|
+
- Submit an issue
|
|
118
|
+
- Open a pull request
|
|
119
|
+
- Or just hop into our [Matrix](https://matrix.to/#/%23codesociety:matrix.org)
|
|
120
|
+
or [Discord](https://discord.gg/code-society-823178343943897088) server and say hi!
|
|
121
|
+
|
|
122
|
+
If you intend to contribute, please read the [CONTRIBUTING.md](./CONTRIBUTING.md) first. Additionally, **every
|
|
123
|
+
contributor** is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
|
|
124
|
+
|
|
125
|
+
# License
|
|
126
|
+
|
|
127
|
+
This project is licensed under the terms
|
|
128
|
+
of [MIT license](https://github.com/Code-Society-Lab/matrixpy/blob/main/LICENSE).
|
|
@@ -3,6 +3,7 @@ import pytest
|
|
|
3
3
|
from unittest.mock import MagicMock
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
+
from matrix.config import Config
|
|
6
7
|
from matrix.extension import Extension
|
|
7
8
|
from matrix.room import Room
|
|
8
9
|
|
|
@@ -10,6 +11,10 @@ from matrix.room import Room
|
|
|
10
11
|
class MockBot:
|
|
11
12
|
prefix: str = "!"
|
|
12
13
|
|
|
14
|
+
@property
|
|
15
|
+
def config(self) -> Config:
|
|
16
|
+
return MagicMock(spec=Config)
|
|
17
|
+
|
|
13
18
|
def __init__(self, room: Optional[Room] = None) -> None:
|
|
14
19
|
self.get_room = MagicMock(return_value=room or MagicMock(spec=Room))
|
|
15
20
|
|
|
@@ -41,7 +46,8 @@ def test_init_with_name_only__expect_prefix_is_none():
|
|
|
41
46
|
|
|
42
47
|
|
|
43
48
|
def test_init__expect_bot_is_none(extension: Extension):
|
|
44
|
-
|
|
49
|
+
with pytest.raises(AssertionError):
|
|
50
|
+
_ = extension.bot
|
|
45
51
|
|
|
46
52
|
|
|
47
53
|
def test_init__expect_on_load_is_none(extension: Extension):
|
|
@@ -190,7 +196,8 @@ def test_unload__expect_bot_cleared(extension: Extension, bot: MockBot):
|
|
|
190
196
|
extension.load(bot)
|
|
191
197
|
extension.unload()
|
|
192
198
|
|
|
193
|
-
|
|
199
|
+
with pytest.raises(AssertionError):
|
|
200
|
+
_ = extension.bot
|
|
194
201
|
|
|
195
202
|
|
|
196
203
|
def test_unload_with_registered_handler__expect_handler_called(
|
|
@@ -217,7 +224,7 @@ def test_unload_with_no_handler__expect_no_error(extension: Extension, bot: Mock
|
|
|
217
224
|
|
|
218
225
|
|
|
219
226
|
def test_get_room_before_load__expect_runtime_error(extension: Extension):
|
|
220
|
-
with pytest.raises(
|
|
227
|
+
with pytest.raises(AssertionError):
|
|
221
228
|
extension.get_room("!room:example.com")
|
|
222
229
|
|
|
223
230
|
|
|
@@ -241,5 +248,5 @@ def test_get_room_after_unload__expect_runtime_error(
|
|
|
241
248
|
extension.load(bot)
|
|
242
249
|
extension.unload()
|
|
243
250
|
|
|
244
|
-
with pytest.raises(
|
|
251
|
+
with pytest.raises(AssertionError):
|
|
245
252
|
extension.get_room("!room:example.com")
|
|
@@ -5,7 +5,7 @@ from nio import RoomMessageText, RoomMemberEvent, TypingNoticeEvent, ReactionEve
|
|
|
5
5
|
from matrix.registry import Registry
|
|
6
6
|
from matrix.command import Command
|
|
7
7
|
from matrix.group import Group
|
|
8
|
-
from matrix.errors import AlreadyRegisteredError
|
|
8
|
+
from matrix.errors import AlreadyRegisteredError, CommandNotFoundError, CheckError
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@pytest.fixture
|
|
@@ -334,6 +334,68 @@ def test_register_error_handler_overwrites_previous_handler__expect_latest_handl
|
|
|
334
334
|
assert registry._error_handlers[ValueError] is second_handler
|
|
335
335
|
|
|
336
336
|
|
|
337
|
+
def test_register_command_error_handler_with_exception_type__expect_handler_in_dict(
|
|
338
|
+
registry: Registry,
|
|
339
|
+
):
|
|
340
|
+
@registry.error(CommandNotFoundError, context=True)
|
|
341
|
+
async def on_command_not_found(ctx, error):
|
|
342
|
+
pass
|
|
343
|
+
|
|
344
|
+
assert (
|
|
345
|
+
registry._command_error_handlers[CommandNotFoundError] is on_command_not_found
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def test_register_command_error_handler_with_non_coroutine__expect_type_error(
|
|
350
|
+
registry: Registry,
|
|
351
|
+
):
|
|
352
|
+
with pytest.raises(TypeError):
|
|
353
|
+
|
|
354
|
+
@registry.error(CommandNotFoundError, context=True)
|
|
355
|
+
def sync_handler(ctx, error):
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def test_register_multiple_command_error_handlers__expect_all_in_dict(
|
|
360
|
+
registry: Registry,
|
|
361
|
+
):
|
|
362
|
+
@registry.error(CommandNotFoundError, context=True)
|
|
363
|
+
async def on_command_not_found(ctx, error):
|
|
364
|
+
pass
|
|
365
|
+
|
|
366
|
+
@registry.error(CheckError, context=True)
|
|
367
|
+
async def on_check_error(ctx, error):
|
|
368
|
+
pass
|
|
369
|
+
|
|
370
|
+
assert CommandNotFoundError in registry._command_error_handlers
|
|
371
|
+
assert CheckError in registry._command_error_handlers
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_register_command_error_handler_overwrites_previous__expect_latest_handler(
|
|
375
|
+
registry: Registry,
|
|
376
|
+
):
|
|
377
|
+
@registry.error(CommandNotFoundError, context=True)
|
|
378
|
+
async def first_handler(ctx, error):
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
@registry.error(CommandNotFoundError, context=True)
|
|
382
|
+
async def second_handler(ctx, error):
|
|
383
|
+
pass
|
|
384
|
+
|
|
385
|
+
assert registry._command_error_handlers[CommandNotFoundError] is second_handler
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def test_register_error_with_context_false__expect_handler_in_error_handlers(
|
|
389
|
+
registry: Registry,
|
|
390
|
+
):
|
|
391
|
+
@registry.error(ValueError, context=False)
|
|
392
|
+
async def on_value_error(error):
|
|
393
|
+
pass
|
|
394
|
+
|
|
395
|
+
assert registry._error_handlers[ValueError] is on_value_error
|
|
396
|
+
assert ValueError not in registry._command_error_handlers
|
|
397
|
+
|
|
398
|
+
|
|
337
399
|
def test_commands_property_with_empty_registry__expect_empty_dict(registry: Registry):
|
|
338
400
|
assert registry.commands == {}
|
|
339
401
|
|