From a65bfb95bf0f103fe34d0d913159e6aadd42b2d4 Mon Sep 17 00:00:00 2001 From: Simon Roy Date: Tue, 16 Jun 2026 01:21:06 -0400 Subject: [PATCH 1/4] Add get_spaces, get_space, and get_rooms --- matrix/bot.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ matrix/space.py | 5 ++++ 2 files changed, 68 insertions(+) create mode 100644 matrix/space.py diff --git a/matrix/bot.py b/matrix/bot.py index 51d876a..f5516f2 100644 --- a/matrix/bot.py +++ b/matrix/bot.py @@ -8,6 +8,7 @@ from nio import AsyncClient, Event, MatrixRoom from .room import Room +from .space import Space from .group import Group from .config import Config from .context import Context @@ -92,6 +93,7 @@ def get_room(self, room_id: str) -> Room | None: ```python room = bot.get_room("!abc123:matrix.org") + if room: print(room.name) ``` @@ -100,6 +102,67 @@ def get_room(self, room_id: str) -> Room | None: return Room(matrix_room=matrix_room, client=self.client) return None + def get_rooms(self) -> list[Room]: + """Retrieve a list of all rooms the bot is aware of. + + This method returns a list of `Room` objects for all rooms currently + known to the client. This includes both regular rooms and spaces. + + ## Example + + ```python + rooms = bot.get_rooms() + + for room in rooms: + print(room.name) + ``` + """ + return [ + Room(matrix_room=matrix_room, client=self.client) + for matrix_room in self.client.rooms.values() + ] + + def get_space(self, space_id: str) -> Space | None: + """Retrieve a `Space` instance by its Matrix room ID. + + Returns the `Space` object corresponding to `space_id` if it exists in + the client's known rooms and is a space. Returns `None` otherwise. + + ## Example + + ```python + space = bot.get_space("!space123:matrix.org") + + if space: + print(space.name) + ``` + """ + if ( + matrix_room := self.client.rooms.get(space_id) + ) and matrix_room.room_type == "m.space": + return Space(matrix_room=matrix_room, client=self.client) + return None + + def get_spaces(self) -> list[Space]: + """Retrieve a list of all spaces the bot is aware of. + + This method returns a list of `Space` objects for all rooms currently + known to the client that are identified as spaces. + + ## Example + + ```python + spaces = bot.get_spaces() + for space in spaces: + print(space.name) + ``` + """ + return [ + Space(matrix_room=matrix_room, client=self.client) + for matrix_room in self.client.rooms.values() + if matrix_room.room_type == "m.space" + ] + def load_extension(self, extension: Extension) -> None: self.log.debug(f"Loading extension: '{extension.name}'") diff --git a/matrix/space.py b/matrix/space.py new file mode 100644 index 0000000..b22d28a --- /dev/null +++ b/matrix/space.py @@ -0,0 +1,5 @@ +from .room import Room + + +class Space(Room): + pass From 83acc8d8d07b9ea75e29af23e648c45c76f5179a Mon Sep 17 00:00:00 2001 From: Simon Roy Date: Tue, 16 Jun 2026 01:33:57 -0400 Subject: [PATCH 2/4] add tests for get_room, get_rooms, get_space and get_spaces --- tests/test_bot.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/tests/test_bot.py b/tests/test_bot.py index 39e19eb..66d69e1 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -3,7 +3,7 @@ from unittest.mock import AsyncMock, MagicMock, patch from nio import MatrixRoom, RoomMessageText -from matrix.bot import Bot, Config, Extension, Room +from matrix.bot import Bot, Config, Extension, Room, Space from matrix.errors import ( CheckError, CommandNotFoundError, @@ -67,6 +67,76 @@ def event(): ) +@pytest.fixture +def space_room(): + space = MatrixRoom(room_id="!space:id", own_user_id="grace") + space.name = "Test Space" + space.room_type = "m.space" + return space + + +def test_get_room__with_known_room_id__expect_room(bot, room): + bot._client.rooms = {room.room_id: room} + + result = bot.get_room(room.room_id) + + assert isinstance(result, Room) + assert result.matrix_room is room + + +def test_get_room__with_unknown_room_id__expect_none(bot): + bot._client.rooms = {} + + assert bot.get_room("!missing:id") is None + + +def test_get_rooms__expect_all_known_rooms(bot, room, space_room): + bot._client.rooms = {room.room_id: room, space_room.room_id: space_room} + + rooms = bot.get_rooms() + + assert len(rooms) == 2 + assert {r.matrix_room for r in rooms} == {room, space_room} + assert all(isinstance(r, Room) for r in rooms) + + +def test_get_space__with_space_room_id__expect_space(bot, space_room): + bot._client.rooms = {space_room.room_id: space_room} + + result = bot.get_space(space_room.room_id) + + assert isinstance(result, Space) + assert result.matrix_room is space_room + + +def test_get_space__with_non_space_room_id__expect_none(bot, room): + bot._client.rooms = {room.room_id: room} + + assert bot.get_space(room.room_id) is None + + +def test_get_space__with_unknown_room_id__expect_none(bot): + bot._client.rooms = {} + + assert bot.get_space("!missing:id") is None + + +def test_get_spaces__with_mixed_rooms__expect_only_spaces(bot, room, space_room): + bot._client.rooms = {room.room_id: room, space_room.room_id: space_room} + + spaces = bot.get_spaces() + + assert len(spaces) == 1 + assert isinstance(spaces[0], Space) + assert spaces[0].matrix_room is space_room + + +def test_get_spaces__with_no_spaces__expect_empty_list(bot, room): + bot._client.rooms = {room.room_id: room} + + assert bot.get_spaces() == [] + + def test_bot_init_with_config(): bot = Bot() bot._load_config(Config(username="grace", password="grace1234")) From 24d110d2eb4ec4bad7daf9a8c08597324a3eaa19 Mon Sep 17 00:00:00 2001 From: Simon Roy Date: Tue, 16 Jun 2026 01:42:56 -0400 Subject: [PATCH 3/4] Made get_room and get_rooms return Room or Space depending on the room_type --- matrix/bot.py | 32 ++++++++++++++++---------------- tests/test_bot.py | 16 +++++++++++++--- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/matrix/bot.py b/matrix/bot.py index f5516f2..edf9a2b 100644 --- a/matrix/bot.py +++ b/matrix/bot.py @@ -88,6 +88,7 @@ def get_room(self, room_id: str) -> Room | None: Returns the `Room` object corresponding to `room_id` if it exists in the client's known rooms. Returns `None` if the room cannot be found. + If the room is a space, a `Space` instance is returned instead. ## Example @@ -99,14 +100,16 @@ def get_room(self, room_id: str) -> Room | None: ``` """ if matrix_room := self.client.rooms.get(room_id): - return Room(matrix_room=matrix_room, client=self.client) + room_cls = Space if matrix_room.room_type == "m.space" else Room + return room_cls(matrix_room=matrix_room, client=self.client) return None def get_rooms(self) -> list[Room]: """Retrieve a list of all rooms the bot is aware of. This method returns a list of `Room` objects for all rooms currently - known to the client. This includes both regular rooms and spaces. + known to the client. This includes both regular rooms and spaces; + spaces are returned as `Space` instances. ## Example @@ -117,10 +120,13 @@ def get_rooms(self) -> list[Room]: print(room.name) ``` """ - return [ - Room(matrix_room=matrix_room, client=self.client) - for matrix_room in self.client.rooms.values() - ] + rooms = [] + + for matrix_room in self.client.rooms.values(): + room_cls = Space if matrix_room.room_type == "m.space" else Room + rooms.append(room_cls(matrix_room=matrix_room, client=self.client)) + + return rooms def get_space(self, space_id: str) -> Space | None: """Retrieve a `Space` instance by its Matrix room ID. @@ -137,11 +143,8 @@ def get_space(self, space_id: str) -> Space | None: print(space.name) ``` """ - if ( - matrix_room := self.client.rooms.get(space_id) - ) and matrix_room.room_type == "m.space": - return Space(matrix_room=matrix_room, client=self.client) - return None + room = self.get_room(space_id) + return room if isinstance(room, Space) else None def get_spaces(self) -> list[Space]: """Retrieve a list of all spaces the bot is aware of. @@ -153,15 +156,12 @@ def get_spaces(self) -> list[Space]: ```python spaces = bot.get_spaces() + for space in spaces: print(space.name) ``` """ - return [ - Space(matrix_room=matrix_room, client=self.client) - for matrix_room in self.client.rooms.values() - if matrix_room.room_type == "m.space" - ] + return [room for room in self.get_rooms() if isinstance(room, Space)] def load_extension(self, extension: Extension) -> None: self.log.debug(f"Loading extension: '{extension.name}'") diff --git a/tests/test_bot.py b/tests/test_bot.py index 66d69e1..bf0b8e6 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -80,10 +80,19 @@ def test_get_room__with_known_room_id__expect_room(bot, room): result = bot.get_room(room.room_id) - assert isinstance(result, Room) + assert type(result) is Room assert result.matrix_room is room +def test_get_room__with_space_room_id__expect_space(bot, space_room): + bot._client.rooms = {space_room.room_id: space_room} + + result = bot.get_room(space_room.room_id) + + assert isinstance(result, Space) + assert result.matrix_room is space_room + + def test_get_room__with_unknown_room_id__expect_none(bot): bot._client.rooms = {} @@ -94,10 +103,11 @@ def test_get_rooms__expect_all_known_rooms(bot, room, space_room): bot._client.rooms = {room.room_id: room, space_room.room_id: space_room} rooms = bot.get_rooms() + by_id = {r.room_id: r for r in rooms} assert len(rooms) == 2 - assert {r.matrix_room for r in rooms} == {room, space_room} - assert all(isinstance(r, Room) for r in rooms) + assert type(by_id[room.room_id]) is Room + assert isinstance(by_id[space_room.room_id], Space) def test_get_space__with_space_room_id__expect_space(bot, space_room): From 3a95a2bd582a522443fa3693af14ea0a3bf19d5b Mon Sep 17 00:00:00 2001 From: penguinboi Date: Tue, 16 Jun 2026 21:24:01 -0400 Subject: [PATCH 4/4] Added doc --- docs/docs/reference/space.md | 15 +++++++++++++++ docs/mkdocs.yml | 1 + 2 files changed, 16 insertions(+) create mode 100644 docs/docs/reference/space.md diff --git a/docs/docs/reference/space.md b/docs/docs/reference/space.md new file mode 100644 index 0000000..37920be --- /dev/null +++ b/docs/docs/reference/space.md @@ -0,0 +1,15 @@ +# Space + +`Space` extends `Room` to represent a Matrix Space. It is returned by `Bot.get_space()` and `Bot.get_spaces()` instead of a plain `Room` whenever the room type is `m.space`. + +```python +from matrix import Bot + +bot = Bot() + +space = bot.get_space("!space123:matrix.org") +if space: + print(space.name) +``` + +::: matrix.space.Space diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e3efa30..ee15897 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -109,6 +109,7 @@ nav: - Protocols: reference/protocols.md - Registry: reference/registry.md - Room: reference/room.md + - Space: reference/space.md - Scheduler: reference/scheduler.md - Types: reference/types.md - Examples: