from contextlib import contextmanager import trio def move_on_at(deadline): """Use as a context manager to create a cancel scope with the given absolute deadline. Args: deadline (float): The deadline. """ return trio.CancelScope(deadline=deadline) def move_on_after(seconds): """Use as a context manager to create a cancel scope whose deadline is set to now + *seconds*. Args: seconds (float): The timeout. Raises: ValueError: if timeout is less than zero. """ if seconds < 0: raise ValueError("timeout must be non-negative") return move_on_at(trio.current_time() + seconds) async def sleep_forever(): """Pause execution of the current task forever (or until cancelled). Equivalent to calling ``await sleep(math.inf)``. """ await trio.lowlevel.wait_task_rescheduled(lambda _: trio.lowlevel.Abort.SUCCEEDED) async def sleep_until(deadline): """Pause execution of the current task until the given time. The difference between :func:`sleep` and :func:`sleep_until` is that the former takes a relative time and the latter takes an absolute time according to Trio's internal clock (as returned by :func:`current_time`). Args: deadline (float): The time at which we should wake up again. May be in the past, in which case this function executes a checkpoint but does not block. """ with move_on_at(deadline): await sleep_forever() async def sleep(seconds): """Pause execution of the current task for the given number of seconds. Args: seconds (float): The number of seconds to sleep. May be zero to insert a checkpoint without actually blocking. Raises: ValueError: if *seconds* is negative. """ if seconds < 0: raise ValueError("duration must be non-negative") if seconds == 0: await trio.lowlevel.checkpoint() else: await sleep_until(trio.current_time() + seconds) class TooSlowError(Exception): """Raised by :func:`fail_after` and :func:`fail_at` if the timeout expires. """ @contextmanager def fail_at(deadline): """Creates a cancel scope with the given deadline, and raises an error if it is actually cancelled. This function and :func:`move_on_at` are similar in that both create a cancel scope with a given absolute deadline, and if the deadline expires then both will cause :exc:`Cancelled` to be raised within the scope. The difference is that when the :exc:`Cancelled` exception reaches :func:`move_on_at`, it's caught and discarded. When it reaches :func:`fail_at`, then it's caught and :exc:`TooSlowError` is raised in its place. Raises: TooSlowError: if a :exc:`Cancelled` exception is raised in this scope and caught by the context manager. """ with move_on_at(deadline) as scope: yield scope if scope.cancelled_caught: raise TooSlowError def fail_after(seconds): """Creates a cancel scope with the given timeout, and raises an error if it is actually cancelled. This function and :func:`move_on_after` are similar in that both create a cancel scope with a given timeout, and if the timeout expires then both will cause :exc:`Cancelled` to be raised within the scope. The difference is that when the :exc:`Cancelled` exception reaches :func:`move_on_after`, it's caught and discarded. When it reaches :func:`fail_after`, then it's caught and :exc:`TooSlowError` is raised in its place. Raises: TooSlowError: if a :exc:`Cancelled` exception is raised in this scope and caught by the context manager. ValueError: if *seconds* is less than zero. """ if seconds < 0: raise ValueError("timeout must be non-negative") return fail_at(trio.current_time() + seconds)