From 3f5c51e8056c4fb1de970fcecb05d1a3ccdd4541 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 22 Feb 2020 00:59:10 +0200 Subject: [PATCH] Add webhook feed method to Dispatcher --- aiogram/api/methods/base.py | 6 + aiogram/dispatcher/dispatcher.py | 96 +++++++++++- poetry.lock | 183 ++++++++--------------- tests/test_dispatcher/test_dispatcher.py | 86 ++++++++++- 4 files changed, 239 insertions(+), 132 deletions(-) diff --git a/aiogram/api/methods/base.py b/aiogram/api/methods/base.py index d1a7078e..c1077701 100644 --- a/aiogram/api/methods/base.py +++ b/aiogram/api/methods/base.py @@ -24,6 +24,12 @@ class Request(BaseModel): class Config(BaseConfig): arbitrary_types_allowed = True + def render_webhook_request(self): + return { + "method": self.method, + **{key: value for key, value in self.data.items() if value is not None}, + } + class Response(ResponseParameters, GenericModel, Generic[T]): ok: bool diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index e4ed4dc7..d45d623a 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -1,6 +1,8 @@ import asyncio -from asyncio import Lock -from typing import Any, AsyncGenerator, Dict, Optional +import contextvars +import warnings +from asyncio import CancelledError, Future, Lock +from typing import Any, AsyncGenerator, Dict, Optional, Union from .. import loggers from ..api.client.bot import Bot @@ -94,7 +96,7 @@ class Dispatcher(Router): update_id = update.update_id + 1 @classmethod - async def _silent_call_request(cls, result: TelegramMethod) -> None: + async def _silent_call_request(cls, bot: Bot, result: TelegramMethod) -> None: """ Simulate answer into WebHook @@ -102,7 +104,7 @@ class Dispatcher(Router): :return: """ try: - await result + await bot(result) except TelegramAPIError as e: # In due to WebHook mechanism doesn't allows to get response for # requests called in answer to WebHook request. @@ -111,13 +113,13 @@ class Dispatcher(Router): loggers.dispatcher.error("Failed to make answer: %s: %s", e.__class__.__name__, e) async def process_update( - self, update: Update, bot: Bot, call_answer: bool = True, **kwargs: Any + self, bot: Bot, update: Update, call_answer: bool = True, **kwargs: Any ) -> bool: """ Propagate update to event listeners - :param update: instance of Update :param bot: instance of Bot + :param update: instance of Update :param call_answer: need to execute response as Telegram method (like answer into webhook) :param kwargs: contextual data for middlewares, filters and handlers :return: status @@ -125,7 +127,7 @@ class Dispatcher(Router): try: async for result in self.feed_update(bot, update, **kwargs): if call_answer and isinstance(result, TelegramMethod): - await self._silent_call_request(result) + await self._silent_call_request(bot=bot, result=result) return True except Exception as e: @@ -149,7 +151,85 @@ class Dispatcher(Router): :return: """ async for update in self._listen_updates(bot): - await self.process_update(update=update, bot=bot, **kwargs) + await self.process_update(bot=bot, update=update, **kwargs) + + async def _feed_webhook_update(self, bot: Bot, update: Update, **kwargs: Any) -> Any: + """ + The same with `Dispatcher.process_update()` but returns real response instead of bool + """ + try: + async for result in self.feed_update(bot, update, **kwargs): + return result + + except Exception as e: + loggers.dispatcher.exception( + "Cause exception while process update id=%d by bot id=%d\n%s: %s", + update.update_id, + bot.id, + e.__class__.__name__, + e, + ) + raise + + async def feed_webhook_update( + self, bot: Bot, update: Union[Update, Dict[str, Any]], _timeout: int = 55, **kwargs + ) -> Optional[Dict[str, Any]]: + if not isinstance(update, Update): # Allow to use raw updates + update = Update(**update) + + ctx = contextvars.copy_context() + loop = asyncio.get_running_loop() + waiter = loop.create_future() + + def release_waiter(*args: Any): + if not waiter.done(): + waiter.set_result(None) + + timeout_handle = loop.call_later(_timeout, release_waiter) + + process_updates: Future = asyncio.ensure_future( + self._feed_webhook_update(bot=bot, update=update, **kwargs) + ) + process_updates.add_done_callback(release_waiter, context=ctx) + + def process_response(task: asyncio.Task): + warnings.warn( + f"Detected slow response into webhook.\n" + f"Telegram is waiting for response only first 60 seconds and then re-send update.\n" + f"For preventing this situation response into webhook returned immediately " + f"and handler is moved to background and still processing update.", + RuntimeWarning, + ) + try: + result = task.result() + except Exception as e: + raise e + if isinstance(result, TelegramMethod): + asyncio.ensure_future(self._silent_call_request(bot=bot, result=result)) + + try: + try: + await waiter + except CancelledError: # pragma: nocover + process_updates.remove_done_callback(release_waiter) + process_updates.cancel() + raise + + if process_updates.done(): + # TODO: handle exceptions + response: Any = process_updates.result() + if isinstance(response, TelegramMethod): + request = response.build_request() + return request.render_webhook_request() + + else: + process_updates.remove_done_callback(release_waiter) + process_updates.add_done_callback(process_response, context=ctx) + + finally: + timeout_handle.cancel() + + return None async def start_polling(self, *bots: Bot, **kwargs: Any) -> None: """ diff --git a/poetry.lock b/poetry.lock index 5a70b057..f1088435 100644 --- a/poetry.lock +++ b/poetry.lock @@ -219,21 +219,13 @@ flake8 = ">=3.3.0" jinja2 = ">=2.9.0" pygments = ">=2.2.0" -[[package]] -category = "dev" -description = "An HTML Minifier" -name = "htmlmin" -optional = false -python-versions = "*" -version = "0.1.12" - [[package]] category = "main" description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8" +version = "2.9" [[package]] category = "dev" @@ -334,14 +326,6 @@ MarkupSafe = ">=0.23" [package.extras] i18n = ["Babel (>=0.8)"] -[[package]] -category = "dev" -description = "JavaScript minifier." -name = "jsmin" -optional = false -python-versions = "*" -version = "2.2.2" - [[package]] category = "dev" description = "Python LiveReload is an awesome tool for web developers" @@ -373,8 +357,8 @@ category = "dev" description = "Python implementation of Markdown." name = "markdown" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "3.1.1" +python-versions = ">=3.5" +version = "3.2.1" [package.dependencies] setuptools = ">=36" @@ -439,27 +423,13 @@ description = "A Material Design theme for MkDocs" name = "mkdocs-material" optional = false python-versions = "*" -version = "4.6.0" +version = "4.6.3" [package.dependencies] -Pygments = ">=2.2" -markdown = "<3.2" -mkdocs = ">=1" -mkdocs-minify-plugin = ">=0.2" -pymdown-extensions = ">=6.2,<6.3" - -[[package]] -category = "dev" -description = "An MkDocs plugin to minify HTML and/or JS files prior to being written to disk" -name = "mkdocs-minify-plugin" -optional = false -python-versions = ">=2.7" -version = "0.2.1" - -[package.dependencies] -htmlmin = ">=0.1.4" -jsmin = ">=2.2.2" -mkdocs = ">=1.0.4" +Pygments = ">=2.4" +markdown = ">=3.2" +mkdocs = ">=1.0" +pymdown-extensions = ">=6.3" [[package]] category = "dev" @@ -475,7 +445,7 @@ description = "multidict implementation" name = "multidict" optional = false python-versions = ">=3.5" -version = "4.7.4" +version = "4.7.5" [[package]] category = "dev" @@ -519,7 +489,7 @@ description = "A Python Parser" name = "parso" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.6.0" +version = "0.6.1" [package.extras] testing = ["docopt", "pytest (>=3.0.7)"] @@ -532,14 +502,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.7.0" -[[package]] -category = "dev" -description = "Backport of PEP 562." -name = "pep562" -optional = false -python-versions = "*" -version = "1.0" - [[package]] category = "dev" description = "Pexpect allows easy control of interactive console applications." @@ -647,11 +609,10 @@ description = "Extension pack for Python Markdown." name = "pymdown-extensions" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "6.2.1" +version = "6.3" [package.dependencies] -Markdown = ">=3.0.1" -pep562 = "*" +Markdown = ">=3.2" [[package]] category = "dev" @@ -796,7 +757,7 @@ description = "Alternative regular expression module, to replace re." name = "regex" optional = false python-versions = "*" -version = "2020.1.8" +version = "2020.2.20" [[package]] category = "dev" @@ -890,11 +851,11 @@ marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_versi name = "zipp" optional = false python-versions = ">=3.6" -version = "2.1.0" +version = "3.0.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["jaraco.itertools"] +testing = ["jaraco.itertools", "func-timeout"] [extras] fast = ["uvloop"] @@ -1026,12 +987,9 @@ flake8-html = [ {file = "flake8-html-0.4.0.tar.gz", hash = "sha256:44bec37f142e97c4a5b2cf10efe24ed253617a9736878851a594d4763011e742"}, {file = "flake8_html-0.4.0-py2.py3-none-any.whl", hash = "sha256:f372cd599ba9a374943eaa75a9cce30408cf4c0cc2251bc5194e6b0d3fc2bc3a"}, ] -htmlmin = [ - {file = "htmlmin-0.1.12.tar.gz", hash = "sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"}, -] idna = [ - {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, - {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, + {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, + {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, ] importlib-metadata = [ {file = "importlib_metadata-1.5.0-py2.py3-none-any.whl", hash = "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"}, @@ -1057,9 +1015,6 @@ jinja2 = [ {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, ] -jsmin = [ - {file = "jsmin-2.2.2.tar.gz", hash = "sha256:b6df99b2cd1c75d9d342e4335b535789b8da9107ec748212706ef7bbe5c2553b"}, -] livereload = [ {file = "livereload-2.6.1-py2.py3-none-any.whl", hash = "sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b"}, {file = "livereload-2.6.1.tar.gz", hash = "sha256:89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"}, @@ -1094,8 +1049,8 @@ lxml = [ {file = "lxml-4.5.0.tar.gz", hash = "sha256:8620ce80f50d023d414183bf90cc2576c2837b88e00bea3f33ad2630133bbb60"}, ] markdown = [ - {file = "Markdown-3.1.1-py2.py3-none-any.whl", hash = "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"}, - {file = "Markdown-3.1.1.tar.gz", hash = "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a"}, + {file = "Markdown-3.2.1-py2.py3-none-any.whl", hash = "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d"}, + {file = "Markdown-3.2.1.tar.gz", hash = "sha256:90fee683eeabe1a92e149f7ba74e5ccdc81cd397bd6c516d93a8da0ef90b6902"}, ] markdown-include = [ {file = "markdown-include-0.5.1.tar.gz", hash = "sha256:72a45461b589489a088753893bc95c5fa5909936186485f4ed55caa57d10250f"}, @@ -1142,35 +1097,31 @@ mkdocs = [ {file = "mkdocs-1.0.4.tar.gz", hash = "sha256:17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939"}, ] mkdocs-material = [ - {file = "mkdocs-material-4.6.0.tar.gz", hash = "sha256:b21aa2645ccb11442ea381c92d187bbc94127f50702c0d28c3fc0152fa7b29da"}, - {file = "mkdocs_material-4.6.0-py2.py3-none-any.whl", hash = "sha256:89a8e2527ca8426c40f2213ce53513f73f54d0a32b36aef33fde6849d294e9ec"}, -] -mkdocs-minify-plugin = [ - {file = "mkdocs-minify-plugin-0.2.1.tar.gz", hash = "sha256:3000a5069dd0f42f56a8aaf7fd5ea1222c67487949617e39585d6b6434b074b6"}, - {file = "mkdocs_minify_plugin-0.2.1-py2-none-any.whl", hash = "sha256:d54fdd5be6843dd29fd7af2f7fdd20a9eb4db46f1f6bed914e03b2f58d2d488e"}, + {file = "mkdocs-material-4.6.3.tar.gz", hash = "sha256:1d486635b03f5a2ec87325842f7b10c7ae7daa0eef76b185572eece6a6ea212c"}, + {file = "mkdocs_material-4.6.3-py2.py3-none-any.whl", hash = "sha256:7f3afa0a09c07d0b89a6a9755fdb00513aee8f0cec3538bb903325c80f66f444"}, ] more-itertools = [ {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, ] multidict = [ - {file = "multidict-4.7.4-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:93166e0f5379cf6cd29746989f8a594fa7204dcae2e9335ddba39c870a287e1c"}, - {file = "multidict-4.7.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a8ed33e8f9b67e3b592c56567135bb42e7e0e97417a4b6a771e60898dfd5182b"}, - {file = "multidict-4.7.4-cp35-cp35m-win32.whl", hash = "sha256:a38baa3046cce174a07a59952c9f876ae8875ef3559709639c17fdf21f7b30dd"}, - {file = "multidict-4.7.4-cp35-cp35m-win_amd64.whl", hash = "sha256:9a7b115ee0b9b92d10ebc246811d8f55d0c57e82dbb6a26b23c9a9a6ad40ce0c"}, - {file = "multidict-4.7.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:dcfed56aa085b89d644af17442cdc2debaa73388feba4b8026446d168ca8dad7"}, - {file = "multidict-4.7.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f29b885e4903bd57a7789f09fe9d60b6475a6c1a4c0eca874d8558f00f9d4b51"}, - {file = "multidict-4.7.4-cp36-cp36m-win32.whl", hash = "sha256:13f3ebdb5693944f52faa7b2065b751cb7e578b8dd0a5bb8e4ab05ad0188b85e"}, - {file = "multidict-4.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4fba5204d32d5c52439f88437d33ad14b5f228e25072a192453f658bddfe45a7"}, - {file = "multidict-4.7.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a6d219f49821f4b2c85c6d426346a5d84dab6daa6f85ca3da6c00ed05b54022d"}, - {file = "multidict-4.7.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:63810343ea07f5cd86ba66ab66706243a6f5af075eea50c01e39b4ad6bc3c57a"}, - {file = "multidict-4.7.4-cp37-cp37m-win32.whl", hash = "sha256:26502cefa86d79b86752e96639352c7247846515c864d7c2eb85d036752b643c"}, - {file = "multidict-4.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5eee66f882ab35674944dfa0d28b57fa51e160b4dce0ce19e47f495fdae70703"}, - {file = "multidict-4.7.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:527124ef435f39a37b279653ad0238ff606b58328ca7989a6df372fd75d7fe26"}, - {file = "multidict-4.7.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:83c6ddf0add57c6b8a7de0bc7e2d656be3eefeff7c922af9a9aae7e49f225625"}, - {file = "multidict-4.7.4-cp38-cp38-win32.whl", hash = "sha256:6bd10adf9f0d6a98ccc792ab6f83d18674775986ba9bacd376b643fe35633357"}, - {file = "multidict-4.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:5414f388ffd78c57e77bd253cf829373721f450613de53dc85a08e34d806e8eb"}, - {file = "multidict-4.7.4.tar.gz", hash = "sha256:d7d428488c67b09b26928950a395e41cc72bb9c3d5abfe9f0521940ee4f796d4"}, + {file = "multidict-4.7.5-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3"}, + {file = "multidict-4.7.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35"}, + {file = "multidict-4.7.5-cp35-cp35m-win32.whl", hash = "sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1"}, + {file = "multidict-4.7.5-cp35-cp35m-win_amd64.whl", hash = "sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd"}, + {file = "multidict-4.7.5-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20"}, + {file = "multidict-4.7.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136"}, + {file = "multidict-4.7.5-cp36-cp36m-win32.whl", hash = "sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e"}, + {file = "multidict-4.7.5-cp36-cp36m-win_amd64.whl", hash = "sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78"}, + {file = "multidict-4.7.5-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8"}, + {file = "multidict-4.7.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab"}, + {file = "multidict-4.7.5-cp37-cp37m-win32.whl", hash = "sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928"}, + {file = "multidict-4.7.5-cp37-cp37m-win_amd64.whl", hash = "sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1"}, + {file = "multidict-4.7.5-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4"}, + {file = "multidict-4.7.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2"}, + {file = "multidict-4.7.5-cp38-cp38-win32.whl", hash = "sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5"}, + {file = "multidict-4.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969"}, + {file = "multidict-4.7.5.tar.gz", hash = "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e"}, ] mypy = [ {file = "mypy-0.761-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6"}, @@ -1197,17 +1148,13 @@ packaging = [ {file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"}, ] parso = [ - {file = "parso-0.6.0-py2.py3-none-any.whl", hash = "sha256:1376bdc8cb81377ca481976933773295218a2df47d3e1182ba76d372b1acb128"}, - {file = "parso-0.6.0.tar.gz", hash = "sha256:597f36de5102a8db05ffdf7ecdc761838b86565a4a111604c6e78beaedf1b045"}, + {file = "parso-0.6.1-py2.py3-none-any.whl", hash = "sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095"}, + {file = "parso-0.6.1.tar.gz", hash = "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57"}, ] pathspec = [ {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"}, {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"}, ] -pep562 = [ - {file = "pep562-1.0-py2.py3-none-any.whl", hash = "sha256:d2a48b178ebf5f8dd31709cc26a19808ef794561fa2fe50ea01ea2bad4d667ef"}, - {file = "pep562-1.0.tar.gz", hash = "sha256:58cb1cc9ee63d93e62b4905a50357618d526d289919814bea1f0da8f53b79395"}, -] pexpect = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, @@ -1261,8 +1208,8 @@ pygments = [ {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, ] pymdown-extensions = [ - {file = "pymdown-extensions-6.2.1.tar.gz", hash = "sha256:3bbe6048275f8a0d13a0fe44e0ea201e67268aa7bb40c2544eef16abbf168f7b"}, - {file = "pymdown_extensions-6.2.1-py2.py3-none-any.whl", hash = "sha256:dce5e17b93be0572322b7d06c9a13c13a9d98694d6468277911d50ca87d26f29"}, + {file = "pymdown-extensions-6.3.tar.gz", hash = "sha256:cb879686a586b22292899771f5e5bc3382808e92aa938f71b550ecdea709419f"}, + {file = "pymdown_extensions-6.3-py2.py3-none-any.whl", hash = "sha256:66fae2683c7a1dac53184f7de57f51f8dad73f9ead2f453e94e85096cb811335"}, ] pyparsing = [ {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, @@ -1314,27 +1261,27 @@ pyyaml = [ {file = "PyYAML-5.3.tar.gz", hash = "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"}, ] regex = [ - {file = "regex-2020.1.8-cp27-cp27m-win32.whl", hash = "sha256:4e8f02d3d72ca94efc8396f8036c0d3bcc812aefc28ec70f35bb888c74a25161"}, - {file = "regex-2020.1.8-cp27-cp27m-win_amd64.whl", hash = "sha256:e6c02171d62ed6972ca8631f6f34fa3281d51db8b326ee397b9c83093a6b7242"}, - {file = "regex-2020.1.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4eae742636aec40cf7ab98171ab9400393360b97e8f9da67b1867a9ee0889b26"}, - {file = "regex-2020.1.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bd25bb7980917e4e70ccccd7e3b5740614f1c408a642c245019cff9d7d1b6149"}, - {file = "regex-2020.1.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3e77409b678b21a056415da3a56abfd7c3ad03da71f3051bbcdb68cf44d3c34d"}, - {file = "regex-2020.1.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:07b39bf943d3d2fe63d46281d8504f8df0ff3fe4c57e13d1656737950e53e525"}, - {file = "regex-2020.1.8-cp36-cp36m-win32.whl", hash = "sha256:23e2c2c0ff50f44877f64780b815b8fd2e003cda9ce817a7fd00dea5600c84a0"}, - {file = "regex-2020.1.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27429b8d74ba683484a06b260b7bb00f312e7c757792628ea251afdbf1434003"}, - {file = "regex-2020.1.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0e182d2f097ea8549a249040922fa2b92ae28be4be4895933e369a525ba36576"}, - {file = "regex-2020.1.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e3cd21cc2840ca67de0bbe4071f79f031c81418deb544ceda93ad75ca1ee9f7b"}, - {file = "regex-2020.1.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ecc6de77df3ef68fee966bb8cb4e067e84d4d1f397d0ef6fce46913663540d77"}, - {file = "regex-2020.1.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:26ff99c980f53b3191d8931b199b29d6787c059f2e029b2b0c694343b1708c35"}, - {file = "regex-2020.1.8-cp37-cp37m-win32.whl", hash = "sha256:7bcd322935377abcc79bfe5b63c44abd0b29387f267791d566bbb566edfdd146"}, - {file = "regex-2020.1.8-cp37-cp37m-win_amd64.whl", hash = "sha256:10671601ee06cf4dc1bc0b4805309040bb34c9af423c12c379c83d7895622bb5"}, - {file = "regex-2020.1.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:98b8ed7bb2155e2cbb8b76f627b2fd12cf4b22ab6e14873e8641f266e0fb6d8f"}, - {file = "regex-2020.1.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a6ba91b94427cd49cd27764679024b14a96874e0dc638ae6bdd4b1a3ce97be1"}, - {file = "regex-2020.1.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:6a6ae17bf8f2d82d1e8858a47757ce389b880083c4ff2498dba17c56e6c103b9"}, - {file = "regex-2020.1.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:0932941cdfb3afcbc26cc3bcf7c3f3d73d5a9b9c56955d432dbf8bbc147d4c5b"}, - {file = "regex-2020.1.8-cp38-cp38-win32.whl", hash = "sha256:d58e4606da2a41659c84baeb3cfa2e4c87a74cec89a1e7c56bee4b956f9d7461"}, - {file = "regex-2020.1.8-cp38-cp38-win_amd64.whl", hash = "sha256:e7c7661f7276507bce416eaae22040fd91ca471b5b33c13f8ff21137ed6f248c"}, - {file = "regex-2020.1.8.tar.gz", hash = "sha256:d0f424328f9822b0323b3b6f2e4b9c90960b24743d220763c7f07071e0778351"}, + {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"}, + {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"}, + {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"}, + {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"}, + {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"}, + {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"}, + {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"}, + {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"}, + {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"}, ] six = [ {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, @@ -1421,6 +1368,6 @@ yarl = [ {file = "yarl-1.4.2.tar.gz", hash = "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b"}, ] zipp = [ - {file = "zipp-2.1.0-py3-none-any.whl", hash = "sha256:ccc94ed0909b58ffe34430ea5451f07bc0c76467d7081619a454bf5c98b89e28"}, - {file = "zipp-2.1.0.tar.gz", hash = "sha256:feae2f18633c32fc71f2de629bfb3bd3c9325cd4419642b1f1da42ee488d9b98"}, + {file = "zipp-3.0.0-py3-none-any.whl", hash = "sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2"}, + {file = "zipp-3.0.0.tar.gz", hash = "sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a"}, ] diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py index 5e210c08..3220f5c8 100644 --- a/tests/test_dispatcher/test_dispatcher.py +++ b/tests/test_dispatcher/test_dispatcher.py @@ -1,8 +1,9 @@ +import asyncio import datetime import time +import warnings import pytest - from aiogram import Bot from aiogram.api.methods import GetMe, GetUpdates, SendMessage from aiogram.api.types import Chat, Message, Update, User @@ -16,6 +17,29 @@ except ImportError: from unittest.mock import AsyncMock as CoroutineMock, patch # type: ignore +async def simple_message_handler(message: Message): + await asyncio.sleep(1.5) + return message.answer("ok") + + +async def invalid_message_handler(message: Message): + await asyncio.sleep(1.5) + raise Exception(42) + + +RAW_UPDATE = { + "update_id": 42, + "message": { + "message_id": 42, + "date": 1582324717, + "text": "test", + "chat": {"id": 42, "type": "private"}, + "from": {"id": 42, "is_bot": False, "first_name": "Test"}, + }, +} +UPDATE = Update(**RAW_UPDATE) + + class TestDispatcher: def test_parent_router(self): dp = Dispatcher() @@ -102,7 +126,7 @@ class TestDispatcher: async def test_silent_call_request(self, bot: MockedBot, caplog): dispatcher = Dispatcher() bot.add_result_for(SendMessage, ok=False, error_code=400, description="Kaboom") - await dispatcher._silent_call_request(SendMessage(chat_id=42, text="test")) + await dispatcher._silent_call_request(bot, SendMessage(chat_id=42, text="test")) log_records = [rec.message for rec in caplog.records] assert len(log_records) == 1 assert "Failed to make answer" in log_records[0] @@ -111,7 +135,7 @@ class TestDispatcher: async def test_process_update_empty(self, bot: MockedBot): dispatcher = Dispatcher() - assert not await dispatcher.process_update(Update(update_id=42), bot=bot) + assert not await dispatcher.process_update(bot=bot, update=Update(update_id=42)) @pytest.mark.asyncio async def test_process_update_handled(self, bot: MockedBot): @@ -121,7 +145,7 @@ class TestDispatcher: async def update_handler(update: Update): pass - assert await dispatcher.process_update(Update(update_id=42), bot=bot) + assert await dispatcher.process_update(bot=bot, update=Update(update_id=42)) @pytest.mark.asyncio async def test_process_update_call_request(self, bot: MockedBot): @@ -135,7 +159,7 @@ class TestDispatcher: "aiogram.dispatcher.dispatcher.Dispatcher._silent_call_request", new_callable=CoroutineMock, ) as mocked_silent_call_request: - assert await dispatcher.process_update(Update(update_id=42), bot=bot) + assert await dispatcher.process_update(bot=bot, update=Update(update_id=42)) mocked_silent_call_request.assert_awaited_once() @pytest.mark.asyncio @@ -146,7 +170,7 @@ class TestDispatcher: async def update_handler(update: Update): raise Exception("Kaboom!") - assert await dispatcher.process_update(Update(update_id=42), bot=bot) + assert await dispatcher.process_update(bot=bot, update=Update(update_id=42)) log_records = [rec.message for rec in caplog.records] assert len(log_records) == 1 assert "Cause exception while process update" in log_records[0] @@ -200,3 +224,53 @@ class TestDispatcher: ) as patched_start_polling: dispatcher.run_polling(bot) patched_start_polling.assert_awaited_once() + + @pytest.mark.asyncio + async def test_feed_webhook_update_fast_process(self, bot: MockedBot): + dispatcher = Dispatcher() + dispatcher.message_handler.register(simple_message_handler) + + response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=2) + assert isinstance(response, dict) + assert response["method"] == "sendMessage" + assert response["text"] == "ok" + + # @pytest.mark.asyncio + # async def test_feed_webhook_update_fast_process_error(self, bot: MockedBot): + # dispatcher = Dispatcher() + # dispatcher.message_handler.register(invalid_message_handler) + # + # response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=2) + # assert isinstance(response, dict) + # assert response["method"] == "sendMessage" + # assert response["text"] == "ok" + + @pytest.mark.asyncio + async def test_feed_webhook_update_slow_process(self, bot: MockedBot, recwarn): + warnings.simplefilter("always") + + dispatcher = Dispatcher() + dispatcher.message_handler.register(simple_message_handler) + + with patch( + "aiogram.dispatcher.dispatcher.Dispatcher._silent_call_request", + new_callable=CoroutineMock, + ) as mocked_silent_call_request: + response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=1) + assert response is None + await asyncio.sleep(1) + assert mocked_silent_call_request.awaited() + + @pytest.mark.asyncio + async def test_feed_webhook_update_fast_process_error(self, bot: MockedBot, caplog): + warnings.simplefilter("always") + + dispatcher = Dispatcher() + dispatcher.message_handler.register(invalid_message_handler) + + response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=1) + assert response is None + await asyncio.sleep(1) + + log_records = [rec.message for rec in caplog.records] + assert "Cause exception while process update" in log_records[0]