telepot icon indicating copy to clipboard operation
telepot copied to clipboard

Timeout context manager error using sanic and telepot same time

Open xen opened this issue 7 years ago • 3 comments

I'm trying to create a web app that communicates with Telegram. And trying to use Sanic web framework with Telepot. Both are asyncio based. Now I'm getting a very weird error.

This is my code:

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)

    # here we fall
    await app.bot.sendMessage(
        "@test",
        "Wao! {}".format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),)
    )

if __name__ == "__main__":
    app.run(
        debug=True
    )

The error that I'm getting is:

[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

I'm not sure that it is the issue with the telepot. But probably you have an idea how to deal with it.

xen avatar Jan 19 '18 11:01 xen

As a temporary fix I made this workaround:

import asyncio

import aiohttp
import async_timeout
import telepot.aio
from telepot.aio.api import (
    _proxy, _parse, _compose_timeout, _compose_data, _methodurl
)
from telepot.exception import TelegramError


class Bot(telepot.aio.Bot):

    async def request(self, req, **user_kw):
        fn, args, kwargs, timeout, cleanup = self._transform(req, **user_kw)

        if _proxy:
            kwargs['proxy'] = _proxy[0]
            if len(_proxy) > 1:
                kwargs['proxy_auth'] = aiohttp.BasicAuth(*_proxy[1])

        try:
            if timeout is None:
                async with fn(*args, **kwargs) as r:
                    return await _parse(r)
            else:
                try:
                    with async_timeout.timeout(timeout):
                        async with fn(*args, **kwargs) as r:
                            return await _parse(r)

                except asyncio.TimeoutError:
                    raise TelegramError('Response timeout', 504, {})

        except aiohttp.ClientConnectionError:
            raise TelegramError('Connection Error', 400, {})

        finally:
            if cleanup:
                cleanup()  # e.g. closing one-time session

    async def _api_request(self, method, params=None, files=None, **kwargs):
        return await self.request((self._token, method, params, files), **kwargs)

    def _transform(self, req, **user_kw):
        timeout = _compose_timeout(req, **user_kw)
        data = _compose_data(req, **user_kw)
        url = _methodurl(req, **user_kw)
        session = aiohttp.ClientSession(
            connector=aiohttp.TCPConnector(limit=1, force_close=True),
            loop=self.loop)

        cleanup = session.close

        kwargs = {'data': data}
        kwargs.update(user_kw)

        return session.post, (url,), kwargs, timeout, cleanup

Now Bot doesn't lose loop. I'm not sure that it should be fixed in the code of the telepot, but if you want to make it working inside other asyncio frameworks I may have a reason to make loop linked to the calls made by the aiohttp.

xen avatar Jan 20 '18 15:01 xen

@xen Could you share a diff?

$ git diff > issue359

And then drop the file into the comment area or edit the issue.

das7pad avatar Jan 20 '18 18:01 das7pad

File attach didn't work well. So I made the pull request #362. I don't know how to attach it to this issue.

xen avatar Jan 21 '18 22:01 xen