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.
Files changed (65) hide show
  1. matrix_python-1.4.9a0/LICENSE +21 -0
  2. matrix_python-1.4.9a0/PKG-INFO +128 -0
  3. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/README.md +2 -1
  4. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/_version.py +3 -3
  5. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/extension.py +17 -9
  6. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/protocols.py +4 -0
  7. matrix_python-1.4.9a0/matrix/py.typed +0 -0
  8. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/registry.py +74 -11
  9. matrix_python-1.4.9a0/matrix_python.egg-info/PKG-INFO +128 -0
  10. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix_python.egg-info/SOURCES.txt +1 -0
  11. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/pyproject.toml +3 -0
  12. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_extension.py +11 -4
  13. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_registry.py +63 -1
  14. matrix_python-1.4.7a0/LICENSE +0 -674
  15. matrix_python-1.4.7a0/PKG-INFO +0 -781
  16. matrix_python-1.4.7a0/matrix_python.egg-info/PKG-INFO +0 -781
  17. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/dependabot.yml +0 -0
  18. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/CODEOWNERS +0 -0
  19. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/codeql.yml +0 -0
  20. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/publish.yml +0 -0
  21. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/scorecard.yml +0 -0
  22. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.github/workflows/tests.yml +0 -0
  23. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/.gitignore +0 -0
  24. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/CODE_OF_CONDUCT.md +0 -0
  25. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/CONTRIBUTING.md +0 -0
  26. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/README.md +0 -0
  27. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/checks.py +0 -0
  28. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/config.yaml +0 -0
  29. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/cooldown.py +0 -0
  30. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/error_handling.py +0 -0
  31. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/extension.py +0 -0
  32. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/ping.py +0 -0
  33. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/reaction.py +0 -0
  34. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/examples/scheduler.py +0 -0
  35. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/__init__.py +0 -0
  36. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/bot.py +0 -0
  37. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/checks.py +0 -0
  38. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/command.py +0 -0
  39. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/config.py +0 -0
  40. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/content.py +0 -0
  41. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/context.py +0 -0
  42. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/errors.py +0 -0
  43. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/group.py +0 -0
  44. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/help/__init__.py +0 -0
  45. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/help/help_command.py +0 -0
  46. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/help/pagination.py +0 -0
  47. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/message.py +0 -0
  48. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/room.py +0 -0
  49. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/scheduler.py +0 -0
  50. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix/types.py +0 -0
  51. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix_python.egg-info/dependency_links.txt +0 -0
  52. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix_python.egg-info/requires.txt +0 -0
  53. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/matrix_python.egg-info/top_level.txt +0 -0
  54. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/mypy.ini +0 -0
  55. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/setup.cfg +0 -0
  56. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/help/test_default_help_command.py +0 -0
  57. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/help/test_help_command.py +0 -0
  58. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/help/test_pagination.py +0 -0
  59. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_bot.py +0 -0
  60. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_command.py +0 -0
  61. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_config.py +0 -0
  62. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_context.py +0 -0
  63. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_group.py +0 -0
  64. {matrix_python-1.4.7a0 → matrix_python-1.4.9a0}/tests/test_message.py +0 -0
  65. {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
+ [![Static Badge](https://img.shields.io/badge/%F0%9F%93%9A-Documentation-%235c5c5c)](https://github.com/Code-Society-Lab/matrixpy/wiki)
56
+ [![Join Discord](https://discordapp.com/api/guilds/823178343943897088/widget.png?style=shield)](https://discord.gg/code-society-823178343943897088)
57
+ [![Join Matrix](https://img.shields.io/matrix/codesociety%3Amatrix.org?logo=matrix&label=%20&labelColor=%23202020&color=%23202020)](https://matrix.to/#/%23codesociety:matrix.org )
58
+ [![Tests](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/tests.yml/badge.svg)](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/tests.yml)
59
+ [![CodeQL Advanced](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/codeql.yml/badge.svg)](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/codeql.yml)
60
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Code-Society-Lab/matrixpy/badge)](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
- matrix.py is released under [GPL-3.0](https://opensource.org/license/gpl-3-0)
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.7a0'
22
- __version_tuple__ = version_tuple = (1, 4, 7, 'a0')
21
+ __version__ = version = '1.4.9a0'
22
+ __version_tuple__ = version_tuple = (1, 4, 9, 'a0')
23
23
 
24
- __commit_id__ = commit_id = 'gdeb79d4c2'
24
+ __commit_id__ = commit_id = 'g5f8d673e3'
@@ -1,21 +1,31 @@
1
1
  import inspect
2
2
  import logging
3
- from typing import Callable, Optional
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: Optional[str] = None) -> None:
14
+ def __init__(self, name: str, prefix: str | None = None) -> None:
14
15
  super().__init__(name, prefix=prefix)
15
16
 
16
- self.bot: Optional[BotLike] = None
17
- self._on_load: Optional[Callable] = None
18
- self._on_unload: Optional[Callable] = None
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.bot = bot
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.bot = None
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 Any, Callable, Coroutine, Optional, Type, Union, Dict, List
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, exception: Optional[type[Exception]] = None
360
- ) -> Callable[[ErrorCallback], ErrorCallback]:
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
- await room.send(f"Bad value: {error}")
412
+ pass
373
413
 
374
414
  @bot.error()
375
415
  async def on_any_error(error):
376
- await room.send(f"Something went wrong: {error}")
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
- if not exception:
381
- exception = Exception
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
- self._error_handlers[exception] = func
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
+ [![Static Badge](https://img.shields.io/badge/%F0%9F%93%9A-Documentation-%235c5c5c)](https://github.com/Code-Society-Lab/matrixpy/wiki)
56
+ [![Join Discord](https://discordapp.com/api/guilds/823178343943897088/widget.png?style=shield)](https://discord.gg/code-society-823178343943897088)
57
+ [![Join Matrix](https://img.shields.io/matrix/codesociety%3Amatrix.org?logo=matrix&label=%20&labelColor=%23202020&color=%23202020)](https://matrix.to/#/%23codesociety:matrix.org )
58
+ [![Tests](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/tests.yml/badge.svg)](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/tests.yml)
59
+ [![CodeQL Advanced](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/codeql.yml/badge.svg)](https://github.com/Code-Society-Lab/matrixpy/actions/workflows/codeql.yml)
60
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Code-Society-Lab/matrixpy/badge)](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).
@@ -33,6 +33,7 @@ matrix/extension.py
33
33
  matrix/group.py
34
34
  matrix/message.py
35
35
  matrix/protocols.py
36
+ matrix/py.typed
36
37
  matrix/registry.py
37
38
  matrix/room.py
38
39
  matrix/scheduler.py
@@ -44,6 +44,9 @@ dev = [
44
44
  packages = ["matrix"]
45
45
  license-files = []
46
46
 
47
+ [tool.setuptools.package-data]
48
+ matrix = ["py.typed"]
49
+
47
50
  [tool.setuptools_scm]
48
51
  write_to = "matrix/_version.py"
49
52
 
@@ -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
- assert extension.bot is None
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
- assert extension.bot is None
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(RuntimeError, match="Extension is not loaded"):
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(RuntimeError, match="Extension is not loaded"):
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