Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

async_timeout.py 8.3KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. # From https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py
  2. # Licensed under the Apache License (Apache-2.0)
  3. import asyncio
  4. import enum
  5. import sys
  6. import warnings
  7. from types import TracebackType
  8. from typing import Optional, Type
  9. # From https://github.com/python/typing_extensions/blob/main/src/typing_extensions.py
  10. # Licensed under the Python Software Foundation License (PSF-2.0)
  11. if sys.version_info >= (3, 11):
  12. from typing import final
  13. else:
  14. # @final exists in 3.8+, but we backport it for all versions
  15. # before 3.11 to keep support for the __final__ attribute.
  16. # See https://bugs.python.org/issue46342
  17. def final(f):
  18. """This decorator can be used to indicate to type checkers that
  19. the decorated method cannot be overridden, and decorated class
  20. cannot be subclassed. For example:
  21. class Base:
  22. @final
  23. def done(self) -> None:
  24. ...
  25. class Sub(Base):
  26. def done(self) -> None: # Error reported by type checker
  27. ...
  28. @final
  29. class Leaf:
  30. ...
  31. class Other(Leaf): # Error reported by type checker
  32. ...
  33. There is no runtime checking of these properties. The decorator
  34. sets the ``__final__`` attribute to ``True`` on the decorated object
  35. to allow runtime introspection.
  36. """
  37. try:
  38. f.__final__ = True
  39. except (AttributeError, TypeError):
  40. # Skip the attribute silently if it is not writable.
  41. # AttributeError happens if the object has __slots__ or a
  42. # read-only property, TypeError if it's a builtin class.
  43. pass
  44. return f
  45. # End https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py
  46. __version__ = "4.0.2"
  47. __all__ = ("timeout", "timeout_at", "Timeout")
  48. def timeout(delay: Optional[float]) -> "Timeout":
  49. """timeout context manager.
  50. Useful in cases when you want to apply timeout logic around block
  51. of code or in cases when asyncio.wait_for is not suitable. For example:
  52. >>> async with timeout(0.001):
  53. ... async with aiohttp.get('https://github.com') as r:
  54. ... await r.text()
  55. delay - value in seconds or None to disable timeout logic
  56. """
  57. loop = asyncio.get_running_loop()
  58. if delay is not None:
  59. deadline = loop.time() + delay # type: Optional[float]
  60. else:
  61. deadline = None
  62. return Timeout(deadline, loop)
  63. def timeout_at(deadline: Optional[float]) -> "Timeout":
  64. """Schedule the timeout at absolute time.
  65. deadline argument points on the time in the same clock system
  66. as loop.time().
  67. Please note: it is not POSIX time but a time with
  68. undefined starting base, e.g. the time of the system power on.
  69. >>> async with timeout_at(loop.time() + 10):
  70. ... async with aiohttp.get('https://github.com') as r:
  71. ... await r.text()
  72. """
  73. loop = asyncio.get_running_loop()
  74. return Timeout(deadline, loop)
  75. class _State(enum.Enum):
  76. INIT = "INIT"
  77. ENTER = "ENTER"
  78. TIMEOUT = "TIMEOUT"
  79. EXIT = "EXIT"
  80. @final
  81. class Timeout:
  82. # Internal class, please don't instantiate it directly
  83. # Use timeout() and timeout_at() public factories instead.
  84. #
  85. # Implementation note: `async with timeout()` is preferred
  86. # over `with timeout()`.
  87. # While technically the Timeout class implementation
  88. # doesn't need to be async at all,
  89. # the `async with` statement explicitly points that
  90. # the context manager should be used from async function context.
  91. #
  92. # This design allows to avoid many silly misusages.
  93. #
  94. # TimeoutError is raised immediately when scheduled
  95. # if the deadline is passed.
  96. # The purpose is to time out as soon as possible
  97. # without waiting for the next await expression.
  98. __slots__ = ("_deadline", "_loop", "_state", "_timeout_handler")
  99. def __init__(
  100. self, deadline: Optional[float], loop: asyncio.AbstractEventLoop
  101. ) -> None:
  102. self._loop = loop
  103. self._state = _State.INIT
  104. self._timeout_handler = None # type: Optional[asyncio.Handle]
  105. if deadline is None:
  106. self._deadline = None # type: Optional[float]
  107. else:
  108. self.update(deadline)
  109. def __enter__(self) -> "Timeout":
  110. warnings.warn(
  111. "with timeout() is deprecated, use async with timeout() instead",
  112. DeprecationWarning,
  113. stacklevel=2,
  114. )
  115. self._do_enter()
  116. return self
  117. def __exit__(
  118. self,
  119. exc_type: Optional[Type[BaseException]],
  120. exc_val: Optional[BaseException],
  121. exc_tb: Optional[TracebackType],
  122. ) -> Optional[bool]:
  123. self._do_exit(exc_type)
  124. return None
  125. async def __aenter__(self) -> "Timeout":
  126. self._do_enter()
  127. return self
  128. async def __aexit__(
  129. self,
  130. exc_type: Optional[Type[BaseException]],
  131. exc_val: Optional[BaseException],
  132. exc_tb: Optional[TracebackType],
  133. ) -> Optional[bool]:
  134. self._do_exit(exc_type)
  135. return None
  136. @property
  137. def expired(self) -> bool:
  138. """Is timeout expired during execution?"""
  139. return self._state == _State.TIMEOUT
  140. @property
  141. def deadline(self) -> Optional[float]:
  142. return self._deadline
  143. def reject(self) -> None:
  144. """Reject scheduled timeout if any."""
  145. # cancel is maybe better name but
  146. # task.cancel() raises CancelledError in asyncio world.
  147. if self._state not in (_State.INIT, _State.ENTER):
  148. raise RuntimeError(f"invalid state {self._state.value}")
  149. self._reject()
  150. def _reject(self) -> None:
  151. if self._timeout_handler is not None:
  152. self._timeout_handler.cancel()
  153. self._timeout_handler = None
  154. def shift(self, delay: float) -> None:
  155. """Advance timeout on delay seconds.
  156. The delay can be negative.
  157. Raise RuntimeError if shift is called when deadline is not scheduled
  158. """
  159. deadline = self._deadline
  160. if deadline is None:
  161. raise RuntimeError("cannot shift timeout if deadline is not scheduled")
  162. self.update(deadline + delay)
  163. def update(self, deadline: float) -> None:
  164. """Set deadline to absolute value.
  165. deadline argument points on the time in the same clock system
  166. as loop.time().
  167. If new deadline is in the past the timeout is raised immediately.
  168. Please note: it is not POSIX time but a time with
  169. undefined starting base, e.g. the time of the system power on.
  170. """
  171. if self._state == _State.EXIT:
  172. raise RuntimeError("cannot reschedule after exit from context manager")
  173. if self._state == _State.TIMEOUT:
  174. raise RuntimeError("cannot reschedule expired timeout")
  175. if self._timeout_handler is not None:
  176. self._timeout_handler.cancel()
  177. self._deadline = deadline
  178. if self._state != _State.INIT:
  179. self._reschedule()
  180. def _reschedule(self) -> None:
  181. assert self._state == _State.ENTER
  182. deadline = self._deadline
  183. if deadline is None:
  184. return
  185. now = self._loop.time()
  186. if self._timeout_handler is not None:
  187. self._timeout_handler.cancel()
  188. task = asyncio.current_task()
  189. if deadline <= now:
  190. self._timeout_handler = self._loop.call_soon(self._on_timeout, task)
  191. else:
  192. self._timeout_handler = self._loop.call_at(deadline, self._on_timeout, task)
  193. def _do_enter(self) -> None:
  194. if self._state != _State.INIT:
  195. raise RuntimeError(f"invalid state {self._state.value}")
  196. self._state = _State.ENTER
  197. self._reschedule()
  198. def _do_exit(self, exc_type: Optional[Type[BaseException]]) -> None:
  199. if exc_type is asyncio.CancelledError and self._state == _State.TIMEOUT:
  200. self._timeout_handler = None
  201. raise asyncio.TimeoutError
  202. # timeout has not expired
  203. self._state = _State.EXIT
  204. self._reject()
  205. return None
  206. def _on_timeout(self, task: "asyncio.Task[None]") -> None:
  207. task.cancel()
  208. self._state = _State.TIMEOUT
  209. # drop the reference early
  210. self._timeout_handler = None
  211. # End https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py