283 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			283 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
# From https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py
 | 
						|
# Licensed under the Apache License (Apache-2.0)
 | 
						|
 | 
						|
import asyncio
 | 
						|
import enum
 | 
						|
import sys
 | 
						|
import warnings
 | 
						|
from types import TracebackType
 | 
						|
from typing import Optional, Type
 | 
						|
 | 
						|
 | 
						|
if sys.version_info >= (3, 11):
 | 
						|
    from typing import final
 | 
						|
else:
 | 
						|
    # From https://github.com/python/typing_extensions/blob/main/src/typing_extensions.py
 | 
						|
    # Licensed under the Python Software Foundation License (PSF-2.0)
 | 
						|
 | 
						|
    # @final exists in 3.8+, but we backport it for all versions
 | 
						|
    # before 3.11 to keep support for the __final__ attribute.
 | 
						|
    # See https://bugs.python.org/issue46342
 | 
						|
    def final(f):
 | 
						|
        """This decorator can be used to indicate to type checkers that
 | 
						|
        the decorated method cannot be overridden, and decorated class
 | 
						|
        cannot be subclassed. For example:
 | 
						|
 | 
						|
            class Base:
 | 
						|
                @final
 | 
						|
                def done(self) -> None:
 | 
						|
                    ...
 | 
						|
            class Sub(Base):
 | 
						|
                def done(self) -> None:  # Error reported by type checker
 | 
						|
                    ...
 | 
						|
            @final
 | 
						|
            class Leaf:
 | 
						|
                ...
 | 
						|
            class Other(Leaf):  # Error reported by type checker
 | 
						|
                ...
 | 
						|
 | 
						|
        There is no runtime checking of these properties. The decorator
 | 
						|
        sets the ``__final__`` attribute to ``True`` on the decorated object
 | 
						|
        to allow runtime introspection.
 | 
						|
        """
 | 
						|
        try:
 | 
						|
            f.__final__ = True
 | 
						|
        except (AttributeError, TypeError):
 | 
						|
            # Skip the attribute silently if it is not writable.
 | 
						|
            # AttributeError happens if the object has __slots__ or a
 | 
						|
            # read-only property, TypeError if it's a builtin class.
 | 
						|
            pass
 | 
						|
        return f
 | 
						|
 | 
						|
    # End https://github.com/python/typing_extensions/blob/main/src/typing_extensions.py
 | 
						|
 | 
						|
 | 
						|
if sys.version_info >= (3, 11):
 | 
						|
 | 
						|
    def _uncancel_task(task: "asyncio.Task[object]") -> None:
 | 
						|
        task.uncancel()
 | 
						|
 | 
						|
else:
 | 
						|
 | 
						|
    def _uncancel_task(task: "asyncio.Task[object]") -> None:
 | 
						|
        pass
 | 
						|
 | 
						|
 | 
						|
__version__ = "4.0.3"
 | 
						|
 | 
						|
 | 
						|
__all__ = ("timeout", "timeout_at", "Timeout")
 | 
						|
 | 
						|
 | 
						|
def timeout(delay: Optional[float]) -> "Timeout":
 | 
						|
    """timeout context manager.
 | 
						|
 | 
						|
    Useful in cases when you want to apply timeout logic around block
 | 
						|
    of code or in cases when asyncio.wait_for is not suitable. For example:
 | 
						|
 | 
						|
    >>> async with timeout(0.001):
 | 
						|
    ...     async with aiohttp.get('https://github.com') as r:
 | 
						|
    ...         await r.text()
 | 
						|
 | 
						|
 | 
						|
    delay - value in seconds or None to disable timeout logic
 | 
						|
    """
 | 
						|
    loop = asyncio.get_running_loop()
 | 
						|
    if delay is not None:
 | 
						|
        deadline = loop.time() + delay  # type: Optional[float]
 | 
						|
    else:
 | 
						|
        deadline = None
 | 
						|
    return Timeout(deadline, loop)
 | 
						|
 | 
						|
 | 
						|
def timeout_at(deadline: Optional[float]) -> "Timeout":
 | 
						|
    """Schedule the timeout at absolute time.
 | 
						|
 | 
						|
    deadline argument points on the time in the same clock system
 | 
						|
    as loop.time().
 | 
						|
 | 
						|
    Please note: it is not POSIX time but a time with
 | 
						|
    undefined starting base, e.g. the time of the system power on.
 | 
						|
 | 
						|
    >>> async with timeout_at(loop.time() + 10):
 | 
						|
    ...     async with aiohttp.get('https://github.com') as r:
 | 
						|
    ...         await r.text()
 | 
						|
 | 
						|
 | 
						|
    """
 | 
						|
    loop = asyncio.get_running_loop()
 | 
						|
    return Timeout(deadline, loop)
 | 
						|
 | 
						|
 | 
						|
class _State(enum.Enum):
 | 
						|
    INIT = "INIT"
 | 
						|
    ENTER = "ENTER"
 | 
						|
    TIMEOUT = "TIMEOUT"
 | 
						|
    EXIT = "EXIT"
 | 
						|
 | 
						|
 | 
						|
@final
 | 
						|
class Timeout:
 | 
						|
    # Internal class, please don't instantiate it directly
 | 
						|
    # Use timeout() and timeout_at() public factories instead.
 | 
						|
    #
 | 
						|
    # Implementation note: `async with timeout()` is preferred
 | 
						|
    # over `with timeout()`.
 | 
						|
    # While technically the Timeout class implementation
 | 
						|
    # doesn't need to be async at all,
 | 
						|
    # the `async with` statement explicitly points that
 | 
						|
    # the context manager should be used from async function context.
 | 
						|
    #
 | 
						|
    # This design allows to avoid many silly misusages.
 | 
						|
    #
 | 
						|
    # TimeoutError is raised immediately when scheduled
 | 
						|
    # if the deadline is passed.
 | 
						|
    # The purpose is to time out as soon as possible
 | 
						|
    # without waiting for the next await expression.
 | 
						|
 | 
						|
    __slots__ = ("_deadline", "_loop", "_state", "_timeout_handler", "_task")
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self, deadline: Optional[float], loop: asyncio.AbstractEventLoop
 | 
						|
    ) -> None:
 | 
						|
        self._loop = loop
 | 
						|
        self._state = _State.INIT
 | 
						|
 | 
						|
        self._task: Optional["asyncio.Task[object]"] = None
 | 
						|
        self._timeout_handler = None  # type: Optional[asyncio.Handle]
 | 
						|
        if deadline is None:
 | 
						|
            self._deadline = None  # type: Optional[float]
 | 
						|
        else:
 | 
						|
            self.update(deadline)
 | 
						|
 | 
						|
    def __enter__(self) -> "Timeout":
 | 
						|
        warnings.warn(
 | 
						|
            "with timeout() is deprecated, use async with timeout() instead",
 | 
						|
            DeprecationWarning,
 | 
						|
            stacklevel=2,
 | 
						|
        )
 | 
						|
        self._do_enter()
 | 
						|
        return self
 | 
						|
 | 
						|
    def __exit__(
 | 
						|
        self,
 | 
						|
        exc_type: Optional[Type[BaseException]],
 | 
						|
        exc_val: Optional[BaseException],
 | 
						|
        exc_tb: Optional[TracebackType],
 | 
						|
    ) -> Optional[bool]:
 | 
						|
        self._do_exit(exc_type)
 | 
						|
        return None
 | 
						|
 | 
						|
    async def __aenter__(self) -> "Timeout":
 | 
						|
        self._do_enter()
 | 
						|
        return self
 | 
						|
 | 
						|
    async def __aexit__(
 | 
						|
        self,
 | 
						|
        exc_type: Optional[Type[BaseException]],
 | 
						|
        exc_val: Optional[BaseException],
 | 
						|
        exc_tb: Optional[TracebackType],
 | 
						|
    ) -> Optional[bool]:
 | 
						|
        self._do_exit(exc_type)
 | 
						|
        return None
 | 
						|
 | 
						|
    @property
 | 
						|
    def expired(self) -> bool:
 | 
						|
        """Is timeout expired during execution?"""
 | 
						|
        return self._state == _State.TIMEOUT
 | 
						|
 | 
						|
    @property
 | 
						|
    def deadline(self) -> Optional[float]:
 | 
						|
        return self._deadline
 | 
						|
 | 
						|
    def reject(self) -> None:
 | 
						|
        """Reject scheduled timeout if any."""
 | 
						|
        # cancel is maybe better name but
 | 
						|
        # task.cancel() raises CancelledError in asyncio world.
 | 
						|
        if self._state not in (_State.INIT, _State.ENTER):
 | 
						|
            raise RuntimeError(f"invalid state {self._state.value}")
 | 
						|
        self._reject()
 | 
						|
 | 
						|
    def _reject(self) -> None:
 | 
						|
        self._task = None
 | 
						|
        if self._timeout_handler is not None:
 | 
						|
            self._timeout_handler.cancel()
 | 
						|
            self._timeout_handler = None
 | 
						|
 | 
						|
    def shift(self, delay: float) -> None:
 | 
						|
        """Advance timeout on delay seconds.
 | 
						|
 | 
						|
        The delay can be negative.
 | 
						|
 | 
						|
        Raise RuntimeError if shift is called when deadline is not scheduled
 | 
						|
        """
 | 
						|
        deadline = self._deadline
 | 
						|
        if deadline is None:
 | 
						|
            raise RuntimeError("cannot shift timeout if deadline is not scheduled")
 | 
						|
        self.update(deadline + delay)
 | 
						|
 | 
						|
    def update(self, deadline: float) -> None:
 | 
						|
        """Set deadline to absolute value.
 | 
						|
 | 
						|
        deadline argument points on the time in the same clock system
 | 
						|
        as loop.time().
 | 
						|
 | 
						|
        If new deadline is in the past the timeout is raised immediately.
 | 
						|
 | 
						|
        Please note: it is not POSIX time but a time with
 | 
						|
        undefined starting base, e.g. the time of the system power on.
 | 
						|
        """
 | 
						|
        if self._state == _State.EXIT:
 | 
						|
            raise RuntimeError("cannot reschedule after exit from context manager")
 | 
						|
        if self._state == _State.TIMEOUT:
 | 
						|
            raise RuntimeError("cannot reschedule expired timeout")
 | 
						|
        if self._timeout_handler is not None:
 | 
						|
            self._timeout_handler.cancel()
 | 
						|
        self._deadline = deadline
 | 
						|
        if self._state != _State.INIT:
 | 
						|
            self._reschedule()
 | 
						|
 | 
						|
    def _reschedule(self) -> None:
 | 
						|
        assert self._state == _State.ENTER
 | 
						|
        deadline = self._deadline
 | 
						|
        if deadline is None:
 | 
						|
            return
 | 
						|
 | 
						|
        now = self._loop.time()
 | 
						|
        if self._timeout_handler is not None:
 | 
						|
            self._timeout_handler.cancel()
 | 
						|
 | 
						|
        self._task = asyncio.current_task()
 | 
						|
        if deadline <= now:
 | 
						|
            self._timeout_handler = self._loop.call_soon(self._on_timeout)
 | 
						|
        else:
 | 
						|
            self._timeout_handler = self._loop.call_at(deadline, self._on_timeout)
 | 
						|
 | 
						|
    def _do_enter(self) -> None:
 | 
						|
        if self._state != _State.INIT:
 | 
						|
            raise RuntimeError(f"invalid state {self._state.value}")
 | 
						|
        self._state = _State.ENTER
 | 
						|
        self._reschedule()
 | 
						|
 | 
						|
    def _do_exit(self, exc_type: Optional[Type[BaseException]]) -> None:
 | 
						|
        if exc_type is asyncio.CancelledError and self._state == _State.TIMEOUT:
 | 
						|
            assert self._task is not None
 | 
						|
            _uncancel_task(self._task)
 | 
						|
            self._timeout_handler = None
 | 
						|
            self._task = None
 | 
						|
            raise asyncio.TimeoutError
 | 
						|
        # timeout has not expired
 | 
						|
        self._state = _State.EXIT
 | 
						|
        self._reject()
 | 
						|
        return None
 | 
						|
 | 
						|
    def _on_timeout(self) -> None:
 | 
						|
        assert self._task is not None
 | 
						|
        self._task.cancel()
 | 
						|
        self._state = _State.TIMEOUT
 | 
						|
        # drop the reference early
 | 
						|
        self._timeout_handler = None
 | 
						|
 | 
						|
 | 
						|
# End https://github.com/aio-libs/async-timeout/blob/master/async_timeout/__init__.py
 |