Format: text/x-python
⬇ Download File (3.5 KB)
   1  # This code is originally sourced from the aio-libs project "async_timeout",
   2  # under the Apache 2.0 license. You may see the original project at
   3  # https://github.com/aio-libs/async-timeout
   4  
   5  # It is vendored here to reduce chain-dependencies on this library, and
   6  # modified slightly to remove some features we don't use.
   7  
   8  
   9  import asyncio
  10  import warnings
  11  from types import TracebackType
  12  from typing import Any  # noqa
  13  from typing import Optional, Type
  14  
  15  
  16  class timeout:
  17      """timeout context manager.
  18  
  19      Useful in cases when you want to apply timeout logic around block
  20      of code or in cases when asyncio.wait_for is not suitable. For example:
  21  
  22      >>> with timeout(0.001):
  23      ...     async with aiohttp.get('https://github.com') as r:
  24      ...         await r.text()
  25  
  26  
  27      timeout - value in seconds or None to disable timeout logic
  28      loop - asyncio compatible event loop
  29      """
  30  
  31      def __init__(
  32          self,
  33          timeout: Optional[float],
  34          *,
  35          loop: Optional[asyncio.AbstractEventLoop] = None,
  36      ) -> None:
  37          self._timeout = timeout
  38          if loop is None:
  39              loop = asyncio.get_running_loop()
  40          else:
  41              warnings.warn(
  42                  """The loop argument to timeout() is deprecated.""", DeprecationWarning
  43              )
  44          self._loop = loop
  45          self._task = None  # type: Optional[asyncio.Task[Any]]
  46          self._cancelled = False
  47          self._cancel_handler = None  # type: Optional[asyncio.Handle]
  48          self._cancel_at = None  # type: Optional[float]
  49  
  50      def __enter__(self) -> "timeout":
  51          return self._do_enter()
  52  
  53      def __exit__(
  54          self,
  55          exc_type: Type[BaseException],
  56          exc_val: BaseException,
  57          exc_tb: TracebackType,
  58      ) -> Optional[bool]:
  59          self._do_exit(exc_type)
  60          return None
  61  
  62      async def __aenter__(self) -> "timeout":
  63          return self._do_enter()
  64  
  65      async def __aexit__(
  66          self,
  67          exc_type: Type[BaseException],
  68          exc_val: BaseException,
  69          exc_tb: TracebackType,
  70      ) -> None:
  71          self._do_exit(exc_type)
  72  
  73      @property
  74      def expired(self) -> bool:
  75          return self._cancelled
  76  
  77      @property
  78      def remaining(self) -> Optional[float]:
  79          if self._cancel_at is not None:
  80              return max(self._cancel_at - self._loop.time(), 0.0)
  81          else:
  82              return None
  83  
  84      def _do_enter(self) -> "timeout":
  85          # Support Tornado 5- without timeout
  86          # Details: https://github.com/python/asyncio/issues/392
  87          if self._timeout is None:
  88              return self
  89  
  90          self._task = asyncio.current_task(self._loop)
  91          if self._task is None:
  92              raise RuntimeError(
  93                  "Timeout context manager should be used " "inside a task"
  94              )
  95  
  96          if self._timeout <= 0:
  97              self._loop.call_soon(self._cancel_task)
  98              return self
  99  
 100          self._cancel_at = self._loop.time() + self._timeout
 101          self._cancel_handler = self._loop.call_at(self._cancel_at, self._cancel_task)
 102          return self
 103  
 104      def _do_exit(self, exc_type: Type[BaseException]) -> None:
 105          if exc_type is asyncio.CancelledError and self._cancelled:
 106              self._cancel_handler = None
 107              self._task = None
 108              raise asyncio.TimeoutError
 109          if self._timeout is not None and self._cancel_handler is not None:
 110              self._cancel_handler.cancel()
 111              self._cancel_handler = None
 112          self._task = None
 113          return None
 114  
 115      def _cancel_task(self) -> None:
 116          if self._task is not None:
 117              self._task.cancel()
 118              self._cancelled = True
 119