同时使用Sanic与Telepot时出现Timeout上下文管理器错误
问题:Sanic + Telepot.aio 启动时触发 RuntimeError 错误
我正在开发一个和Telegram通信的Web应用,尝试结合基于asyncio的Sanic框架和Telepot库,但启动时出现了异常。以下是我的代码:
import datetime import telepot.aio from sanic import Sanic app = Sanic(__name__, load_env=False) app.config.LOGO = '' @app.listener('before_server_start') async def server_init(app, loop): app.bot = telepot.aio.Bot('anything', loop=loop) # 此处出现错误 await app.bot.sendMessage( "@test", "Wao! {}".format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),) ) if __name__ == "__main__": app.run( debug=True )
错误信息:
[2018-01-18 22:41:43 +0200] [10996] [ERROR] Experienced exception while trying to serve Traceback (most recent call last): File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/sanic/app.py", line 646, in run serve(**server_settings) File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/sanic/server.py", line 588, in serve trigger_events(before_start, loop) File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/sanic/server.py", line 496, in trigger_events loop.run_until_complete(result) File "uvloop/loop.pyx", line 1364, in uvloop.loop.Loop.run_until_complete File "/home/mk/Dev/project/sanic-telepot.py", line 14, in server_init "Wao! {}".format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),) File "/usr/lib/python3.6/asyncio/coroutines.py", line 109, in __next__ return self.gen.send(None) File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/telepot/aio/__init__.py", line 100, in sendMessage return await self._api_request('sendMessage', _rectify(p)) File "/usr/lib/python3.6/asyncio/coroutines.py", line 109, in __next__ return self.gen.send(None) File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/telepot/aio/__init__.py", line 78, in _api_request return await api.request((self._token, method, params, files), **kwargs) File "/usr/lib/python3.6/asyncio/coroutines.py", line 109, in __next__ return self.gen.send(None) File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/telepot/aio/api.py", line 139, in request async with fn(*args, **kwargs) as r: File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/aiohttp/client.py", line 690, in __aenter__ self._resp = yield from self._coro File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/aiohttp/client.py", line 221, in _request with timer: File "/home/mk/Dev/project/venv/lib/python3.6/site-packages/aiohttp/helpers.py", line 712, in __enter__ raise RuntimeError('Timeout context manager should be used ' RuntimeError: Timeout context manager should be used inside a task
Telepot内部依赖aiohttp进行HTTP请求,用aiohttp.web实现类似功能时代码能正常运行,而且用同样方式连接的Redis、数据库等其他依赖都工作正常,请问该怎么解决这个问题?
解决方案
这个问题的核心原因是:Sanic默认使用uvloop作为事件循环,在before_server_start监听器里直接await Telepot的API调用时,这个协程是被loop.run_until_complete直接执行的,并没有被包装成一个正式的Task对象。而aiohttp的超时上下文管理器要求必须运行在Task内部,所以就触发了这个RuntimeError。
解决方法很简单,把发送消息的协程包装成一个Task再执行,具体修改你的server_init函数:
@app.listener('before_server_start') async def server_init(app, loop): app.bot = telepot.aio.Bot('你的真实Telegram Bot Token', loop=loop) # 用loop.create_task把发送消息的操作包装成Task loop.create_task( app.bot.sendMessage( "@test", # 注意:建议使用数字ID代替用户名,避免找不到聊天对象 "Wao! {}".format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) ) )
为什么这样能解决问题?
loop.create_task()会把你的协程包装成一个Task对象,这个Task会被事件循环调度执行,此时aiohttp的超时上下文管理器就能识别到有效的Task上下文,不会再抛出错误。
另外补充两个注意点:
- 请把代码里的
'anything'替换成你从BotFather那里获取的真实有效的Bot Token,否则API调用还是会失败。 - 发送消息的目标
@test如果是用户名,可能存在无法找到的情况,建议改用聊天的数字ID(可以通过Telegram的一些Bot工具获取,比如@userinfobot),这样更可靠。
内容的提问来源于stack exchange,提问作者Mikhail Kashkin
相关产品推荐
相关产品推荐

