From e368d661accad2a4d1303274eb073723bf8d347a Mon Sep 17 00:00:00 2001 From: Jean-Marie Mineau Date: Sun, 3 Oct 2021 16:36:36 +0200 Subject: [PATCH] surrender to the async devil and use blackmagic to initialize asyncronously an object --- src/matrix_bot/async_utils.py | 17 +++++++++++++++ src/matrix_bot/client.py | 41 ++++++++++++++++++++++++++++++----- tests/test.py | 13 ++++++++--- 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 src/matrix_bot/async_utils.py diff --git a/src/matrix_bot/async_utils.py b/src/matrix_bot/async_utils.py new file mode 100644 index 0000000..b821214 --- /dev/null +++ b/src/matrix_bot/async_utils.py @@ -0,0 +1,17 @@ +""" +Some utilities to help with async stuff. +""" + +class Aobject(object): + """ + Inheriting this class allows you to define an async __init__. + So you can create objects by doing something like `await MyClass(params)` + Copied from https://newbedev.com/how-to-set-class-attribute-with-await-in-init + """ + async def __new__(cls, *a, **kw): + instance = super().__new__(cls) + await instance.__init__(*a, **kw) + return instance + + async def __init__(self): + pass diff --git a/src/matrix_bot/client.py b/src/matrix_bot/client.py index fefa5f7..c4a5b0b 100644 --- a/src/matrix_bot/client.py +++ b/src/matrix_bot/client.py @@ -10,13 +10,14 @@ from typing import ( NoReturn, Union ) +from .async_utils import Aobject from .utils import ( Room, RoomAlias, RoomId ) -class Client: +class Client(Aobject): """ Connect to the matrix server and handle interactions with the server. @@ -24,6 +25,8 @@ class Client: allowed_rooms: dict of the rooms where the bot is allowed to connect, indexed by id (the name starting with '!'). If set to None, the bot connect to all room where it is invited. + + /!\ The client is initialized asyncronously: `client = await Client(...)` """ __client: nio.AsyncClient @@ -31,7 +34,7 @@ class Client: __rooms_by_id: dict[RoomId, Room] allowed_rooms: Optional[dict[RoomId, Room]] - def __init__( + async def __init__( self, username: str, homeserver: str, @@ -47,23 +50,22 @@ class Client: (given by room id (expl: '!xxx:matrix.org') of room alias (expl: '#xxx:matrix.org')) """ - loop = asyncio.get_event_loop() self.__client = nio.AsyncClient( homeserver, username ) self.__rooms_by_aliases = {} self.__rooms_by_id = {} - resp = loop.run_until_complete(self.__client.login(password)) + resp = await self.__client.login(password) if isinstance(resp, nio.responses.LoginError): raise RuntimeError(f"Fail to connect: {resp.message}") - # Where is the async map when you need it? + # TODO: Where is the async map when you need it? self.allowed_rooms = None if allowed_rooms_names: self.allowed_rooms = {} for room_name in allowed_rooms_names: - room = loop.run_until_complete(self.resolve_room(room_name)) + room = await self.resolve_room(room_name) self.allowed_rooms[room.id] = room # room uniqueness is handled by self.resolve_room @@ -107,3 +109,30 @@ class Client: self.__rooms_by_aliases[room_name] = room return room + async def sync( + self, + sync_delta:int=30 + )->NoReturn: + """ + Sync with the server every sync_delta seconds. + sync_delta: the time in sec between each sync. + """ + while True: + sync_resp = await self.__client.sync(sync_delta*1000) + if isinstance(sync_resp, nio.responses.SyncError): + print(f"Error while syncronizing: {sync_resp.message}") # TODO: use proper logging + continue + print(sync_resp) + + async def run( + self, + sync_delta:int=30 + )->NoReturn: + """ + Run the bot: sync with the server and execute callbacks. + """ + await asyncio.gather( + self.sync(sync_delta=sync_delta) + ) + + diff --git a/tests/test.py b/tests/test.py index 7222d4c..1aea0f0 100644 --- a/tests/test.py +++ b/tests/test.py @@ -2,18 +2,25 @@ Not really tests, because I don't know how to testt without a whole matrix server. """ +import asyncio import os from dotenv import load_dotenv from matrix_bot.client import Client from getpass import getpass -if __name__ == "__main__": +async def main(): load_dotenv() - client = Client( + client = await Client( os.environ["MUSER"], os.environ["HOMESERVER"], os.environ["PASSWD"], os.environ["ROOMS"].split(",") ) - print(client.allowed_rooms) +# print(client.allowed_rooms) + await client.run() + + + +if __name__ == "__main__": + asyncio.run(main())