/paco/timeout.py

https://github.com/h2non/paco · Python · 95 lines · 38 code · 10 blank · 47 comment · 8 complexity · 438e3265f0e09f5c75560a2399cd8032 MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. import asyncio
  3. from .decorator import decorate
  4. @decorate
  5. def timeout(coro, timeout=None, loop=None):
  6. """
  7. Wraps a given coroutine function, that when executed, if it takes more
  8. than the given timeout in seconds to execute, it will be canceled and
  9. raise an `asyncio.TimeoutError`.
  10. This function is equivalent to Python standard
  11. `asyncio.wait_for()` function.
  12. This function can be used as decorator.
  13. Arguments:
  14. coro (coroutinefunction|coroutine): coroutine to wrap.
  15. timeout (int|float): max wait timeout in seconds.
  16. loop (asyncio.BaseEventLoop): optional event loop to use.
  17. Raises:
  18. TypeError: if coro argument is not a coroutine function.
  19. Returns:
  20. coroutinefunction: wrapper coroutine function.
  21. Usage::
  22. await paco.timeout(coro, timeout=10)
  23. """
  24. @asyncio.coroutine
  25. def _timeout(coro):
  26. return (yield from asyncio.wait_for(coro, timeout, loop=loop))
  27. @asyncio.coroutine
  28. def wrapper(*args, **kw):
  29. return (yield from _timeout(coro(*args, **kw)))
  30. return _timeout(coro) if asyncio.iscoroutine(coro) else wrapper
  31. class TimeoutLimit(object):
  32. """
  33. Timeout limit context manager.
  34. Useful in cases when you want to apply timeout logic around block
  35. of code or in cases when asyncio.wait_for is not suitable.
  36. Originally based on: https://github.com/aio-libs/async-timeout
  37. Arguments:
  38. timeout (int): value in seconds or None to disable timeout logic.
  39. loop (asyncio.BaseEventLoop): asyncio compatible event loop.
  40. Usage::
  41. with paco.TimeoutLimit(0.1):
  42. await paco.wait(task1, task2)
  43. """
  44. def __init__(self, timeout, loop=None):
  45. self._timeout = timeout
  46. self._loop = loop or asyncio.get_event_loop()
  47. self._task = None
  48. self._cancelled = False
  49. self._cancel_handler = None
  50. def __enter__(self):
  51. self._task = asyncio.Task.current_task(loop=self._loop)
  52. if self._task is None:
  53. raise RuntimeError('paco: timeout context manager should '
  54. 'be used inside a task')
  55. if self._timeout is not None:
  56. self._cancel_handler = self._loop.call_later(
  57. self._timeout, self.cancel)
  58. return self
  59. def __exit__(self, exc_type, exc_val, exc_tb):
  60. if exc_type is asyncio.CancelledError and self._cancelled:
  61. self._cancel_handler = None
  62. self._task = None
  63. raise asyncio.TimeoutError from None
  64. if self._timeout is not None:
  65. self._cancel_handler.cancel()
  66. self._cancel_handler = None
  67. self._task = None
  68. def cancel(self):
  69. """
  70. Cancels current task running task in the context manager.
  71. """
  72. self._cancelled = self._task.cancel()