|
| 1 | +# Vendored from https://github.com/aio-libs/async-timeout |
| 2 | +# Copyright: 2016-2017 Andrew Svetlov |
| 3 | +# License: Apache 2.0 |
| 4 | + |
| 5 | +import asyncio |
| 6 | + |
| 7 | + |
| 8 | +__version__ = '2.0.0' |
| 9 | + |
| 10 | + |
| 11 | +class timeout: |
| 12 | + """timeout context manager. |
| 13 | +
|
| 14 | + Useful in cases when you want to apply timeout logic around block |
| 15 | + of code or in cases when asyncio.wait_for is not suitable. For example: |
| 16 | +
|
| 17 | + >>> async with timeout(0.001): |
| 18 | + ... async with aiohttp.get('https://github.com') as r: |
| 19 | + ... await r.text() |
| 20 | +
|
| 21 | +
|
| 22 | + timeout - value in seconds or None to disable timeout logic |
| 23 | + loop - asyncio compatible event loop |
| 24 | + """ |
| 25 | + def __init__(self, timeout, *, loop=None): |
| 26 | + self._timeout = timeout |
| 27 | + if loop is None: |
| 28 | + loop = asyncio.get_event_loop() |
| 29 | + self._loop = loop |
| 30 | + self._task = None |
| 31 | + self._cancelled = False |
| 32 | + self._cancel_handler = None |
| 33 | + self._cancel_at = None |
| 34 | + |
| 35 | + def __enter__(self): |
| 36 | + return self._do_enter() |
| 37 | + |
| 38 | + def __exit__(self, exc_type, exc_val, exc_tb): |
| 39 | + self._do_exit(exc_type) |
| 40 | + |
| 41 | + @asyncio.coroutine |
| 42 | + def __aenter__(self): |
| 43 | + return self._do_enter() |
| 44 | + |
| 45 | + @asyncio.coroutine |
| 46 | + def __aexit__(self, exc_type, exc_val, exc_tb): |
| 47 | + self._do_exit(exc_type) |
| 48 | + |
| 49 | + @property |
| 50 | + def expired(self): |
| 51 | + return self._cancelled |
| 52 | + |
| 53 | + @property |
| 54 | + def remaining(self): |
| 55 | + if self._cancel_at is not None: |
| 56 | + return max(self._cancel_at - self._loop.time(), 0.0) |
| 57 | + else: |
| 58 | + return None |
| 59 | + |
| 60 | + def _do_enter(self): |
| 61 | + # Support Tornado 5- without timeout |
| 62 | + # Details: https://github.com/python/asyncio/issues/392 |
| 63 | + if self._timeout is None: |
| 64 | + return self |
| 65 | + |
| 66 | + self._task = current_task(self._loop) |
| 67 | + if self._task is None: |
| 68 | + raise RuntimeError('Timeout context manager should be used ' |
| 69 | + 'inside a task') |
| 70 | + |
| 71 | + if self._timeout <= 0: |
| 72 | + self._loop.call_soon(self._cancel_task) |
| 73 | + return self |
| 74 | + |
| 75 | + self._cancel_at = self._loop.time() + self._timeout |
| 76 | + self._cancel_handler = self._loop.call_at( |
| 77 | + self._cancel_at, self._cancel_task) |
| 78 | + return self |
| 79 | + |
| 80 | + def _do_exit(self, exc_type): |
| 81 | + if exc_type is asyncio.CancelledError and self._cancelled: |
| 82 | + self._cancel_handler = None |
| 83 | + self._task = None |
| 84 | + raise asyncio.TimeoutError |
| 85 | + if self._timeout is not None and self._cancel_handler is not None: |
| 86 | + self._cancel_handler.cancel() |
| 87 | + self._cancel_handler = None |
| 88 | + self._task = None |
| 89 | + |
| 90 | + def _cancel_task(self): |
| 91 | + self._task.cancel() |
| 92 | + self._cancelled = True |
| 93 | + |
| 94 | + |
| 95 | +def current_task(loop): |
| 96 | + task = asyncio.Task.current_task(loop=loop) |
| 97 | + if task is None: |
| 98 | + if hasattr(loop, 'current_task'): |
| 99 | + task = loop.current_task() |
| 100 | + |
| 101 | + return task |
0 commit comments