add Client.run_until()

This commit is contained in:
adbenitez
2022-12-11 03:31:29 -05:00
parent be63e18ebf
commit 2ebd3f54e6
3 changed files with 111 additions and 36 deletions

View File

@@ -1,6 +1,17 @@
"""Event loop implementations offering high level event handling/hooking."""
import inspect
import logging
from typing import Callable, Dict, Iterable, Optional, Set, Tuple, Type, Union
from typing import (
Callable,
Coroutine,
Dict,
Iterable,
Optional,
Set,
Tuple,
Type,
Union,
)
from deltachat_rpc_client.account import Account
@@ -56,6 +67,18 @@ class Client:
self.logger.debug("Account configured")
async def run_forever(self) -> None:
"""Process events forever."""
await self.run_until(lambda _: False)
async def run_until(
self, func: Callable[[AttrDict], Union[bool, Coroutine]]
) -> AttrDict:
"""Process events until the given callable evaluates to True.
The callable should accept an AttrDict object representing the
last processed event. The event is returned when the callable
evaluates to True.
"""
self.logger.debug("Listening to incoming events...")
if await self.is_configured():
await self.account.start_io()
@@ -68,6 +91,12 @@ class Client:
if event.type == EventType.INCOMING_MSG:
await self._process_messages()
stop = func(event)
if inspect.isawaitable(stop):
stop = await stop
if stop:
return event
async def _on_event(
self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent
) -> None:

View File

@@ -1,13 +1,11 @@
import json
import os
from typing import AsyncGenerator, List
from typing import AsyncGenerator, List, Optional
import aiohttp
import pytest_asyncio
from .account import Account
from .client import Bot
from .deltachat import DeltaChat
from . import Account, AttrDict, Bot, Client, DeltaChat, EventType, Message
from .rpc import Rpc
@@ -51,6 +49,46 @@ class ACFactory:
await account.start_io()
return accounts
async def send_message(
self,
to_account: Account,
from_account: Optional[Account] = None,
text: Optional[str] = None,
file: Optional[str] = None,
group: Optional[str] = None,
) -> Message:
if not from_account:
from_account = (await self.get_online_accounts(1))[0]
to_contact = await from_account.create_contact(
await to_account.get_config("addr")
)
if group:
to_chat = await from_account.create_group(group)
await to_chat.add_contact(to_contact)
else:
to_chat = await to_contact.create_chat()
return await to_chat.send_message(text=text, file=file)
async def process_message(
self,
to_client: Client,
from_account: Optional[Account] = None,
text: Optional[str] = None,
file: Optional[str] = None,
group: Optional[str] = None,
) -> AttrDict:
await self.send_message(
to_account=to_client.account,
from_account=from_account,
text=text,
file=file,
group=group,
)
event = await to_client.run_until(lambda e: e.type == EventType.INCOMING_MSG)
msg = await to_client.account.get_message_by_id(event.msg_id)
return await msg.get_snapshot()
@pytest_asyncio.fixture
async def rpc(tmp_path) -> AsyncGenerator:

View File

@@ -1,6 +1,8 @@
from unittest.mock import MagicMock
import pytest
from deltachat_rpc_client import AttrDict, EventType, events
from deltachat_rpc_client import EventType, events
from deltachat_rpc_client.rpc import JsonRpcError
@@ -216,40 +218,46 @@ async def test_message(acfactory) -> None:
@pytest.mark.asyncio
async def test_bot(acfactory) -> None:
async def callback(e):
res.append(e)
def track(key):
async def wrapper(e):
mock.hook(e[key])
res = []
return wrapper
mock = MagicMock()
user = (await acfactory.get_online_accounts(1))[0]
bot = await acfactory.new_configured_bot()
assert await bot.is_configured()
assert await bot.account.get_config("bot") == "1"
bot.add_hook(callback, events.RawEvent(EventType.INFO))
info_event = AttrDict(account=bot.account, type=EventType.INFO, msg="info")
warn_event = AttrDict(account=bot.account, type=EventType.WARNING, msg="warning")
await bot._on_event(info_event)
await bot._on_event(warn_event)
assert info_event in res
assert warn_event not in res
assert len(res) == 1
hook = track("msg_id"), events.RawEvent(EventType.INCOMING_MSG)
bot.add_hook(*hook)
event = await acfactory.process_message(
from_account=user, to_client=bot, text="Hello!"
)
mock.hook.assert_called_once_with(event.id)
bot.remove_hook(*hook)
res = []
bot.add_hook(callback, events.NewMessage(r"hello"))
bot.add_hook(callback, events.NewMessage(command="/help"))
snapshot1 = AttrDict(text="hello", command=None)
snapshot2 = AttrDict(text="hello, world", command=None)
snapshot3 = AttrDict(text="hey!", command=None)
for snapshot in [snapshot1, snapshot2, snapshot3]:
await bot._on_event(snapshot, events.NewMessage)
assert len(res) == 2
assert snapshot1 in res
assert snapshot2 in res
assert snapshot3 not in res
mock.hook.reset_mock()
hook = track("id"), events.NewMessage(r"hello")
bot.add_hook(*hook)
bot.add_hook(track("id"), events.NewMessage(command="/help"))
event = await acfactory.process_message(
from_account=user, to_client=bot, text="hello"
)
mock.hook.assert_called_with(event.id)
event = await acfactory.process_message(
from_account=user, to_client=bot, text="hello!"
)
mock.hook.assert_called_with(event.id)
await acfactory.process_message(from_account=user, to_client=bot, text="hey!")
assert len(mock.hook.mock_calls) == 2
bot.remove_hook(*hook)
res = []
bot.remove_hook(callback, events.NewMessage(r"hello"))
snapshot4 = AttrDict(command="/help")
await bot._on_event(snapshot, events.NewMessage)
await bot._on_event(snapshot4, events.NewMessage)
assert len(res) == 1
assert snapshot4 in res
mock.hook.reset_mock()
await acfactory.process_message(from_account=user, to_client=bot, text="hello")
event = await acfactory.process_message(
from_account=user, to_client=bot, text="/help"
)
mock.hook.assert_called_once_with(event.id)