Format: text/x-python
⬇ Download File (4.1 KB)
   1  from __future__ import annotations
   2  
   3  import enum
   4  from dataclasses import dataclass
   5  from typing import Any, Generic, Literal, TypeVar, overload
   6  from weakref import WeakKeyDictionary
   7  
   8  from ._core._eventloop import get_async_backend
   9  
  10  T = TypeVar("T")
  11  D = TypeVar("D")
  12  
  13  
  14  async def checkpoint() -> None:
  15      """
  16      Check for cancellation and allow the scheduler to switch to another task.
  17  
  18      Equivalent to (but more efficient than)::
  19  
  20          await checkpoint_if_cancelled()
  21          await cancel_shielded_checkpoint()
  22  
  23  
  24      .. versionadded:: 3.0
  25  
  26      """
  27      await get_async_backend().checkpoint()
  28  
  29  
  30  async def checkpoint_if_cancelled() -> None:
  31      """
  32      Enter a checkpoint if the enclosing cancel scope has been cancelled.
  33  
  34      This does not allow the scheduler to switch to a different task.
  35  
  36      .. versionadded:: 3.0
  37  
  38      """
  39      await get_async_backend().checkpoint_if_cancelled()
  40  
  41  
  42  async def cancel_shielded_checkpoint() -> None:
  43      """
  44      Allow the scheduler to switch to another task but without checking for cancellation.
  45  
  46      Equivalent to (but potentially more efficient than)::
  47  
  48          with CancelScope(shield=True):
  49              await checkpoint()
  50  
  51  
  52      .. versionadded:: 3.0
  53  
  54      """
  55      await get_async_backend().cancel_shielded_checkpoint()
  56  
  57  
  58  def current_token() -> object:
  59      """
  60      Return a backend specific token object that can be used to get back to the event
  61      loop.
  62  
  63      """
  64      return get_async_backend().current_token()
  65  
  66  
  67  _run_vars: WeakKeyDictionary[Any, dict[str, Any]] = WeakKeyDictionary()
  68  _token_wrappers: dict[Any, _TokenWrapper] = {}
  69  
  70  
  71  @dataclass(frozen=True)
  72  class _TokenWrapper:
  73      __slots__ = "_token", "__weakref__"
  74      _token: object
  75  
  76  
  77  class _NoValueSet(enum.Enum):
  78      NO_VALUE_SET = enum.auto()
  79  
  80  
  81  class RunvarToken(Generic[T]):
  82      __slots__ = "_var", "_value", "_redeemed"
  83  
  84      def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]):
  85          self._var = var
  86          self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value
  87          self._redeemed = False
  88  
  89  
  90  class RunVar(Generic[T]):
  91      """
  92      Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop.
  93      """
  94  
  95      __slots__ = "_name", "_default"
  96  
  97      NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET
  98  
  99      _token_wrappers: set[_TokenWrapper] = set()
 100  
 101      def __init__(
 102          self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET
 103      ):
 104          self._name = name
 105          self._default = default
 106  
 107      @property
 108      def _current_vars(self) -> dict[str, T]:
 109          token = current_token()
 110          try:
 111              return _run_vars[token]
 112          except KeyError:
 113              run_vars = _run_vars[token] = {}
 114              return run_vars
 115  
 116      @overload
 117      def get(self, default: D) -> T | D: ...
 118  
 119      @overload
 120      def get(self) -> T: ...
 121  
 122      def get(
 123          self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET
 124      ) -> T | D:
 125          try:
 126              return self._current_vars[self._name]
 127          except KeyError:
 128              if default is not RunVar.NO_VALUE_SET:
 129                  return default
 130              elif self._default is not RunVar.NO_VALUE_SET:
 131                  return self._default
 132  
 133          raise LookupError(
 134              f'Run variable "{self._name}" has no value and no default set'
 135          )
 136  
 137      def set(self, value: T) -> RunvarToken[T]:
 138          current_vars = self._current_vars
 139          token = RunvarToken(self, current_vars.get(self._name, RunVar.NO_VALUE_SET))
 140          current_vars[self._name] = value
 141          return token
 142  
 143      def reset(self, token: RunvarToken[T]) -> None:
 144          if token._var is not self:
 145              raise ValueError("This token does not belong to this RunVar")
 146  
 147          if token._redeemed:
 148              raise ValueError("This token has already been used")
 149  
 150          if token._value is _NoValueSet.NO_VALUE_SET:
 151              try:
 152                  del self._current_vars[self._name]
 153              except KeyError:
 154                  pass
 155          else:
 156              self._current_vars[self._name] = token._value
 157  
 158          token._redeemed = True
 159  
 160      def __repr__(self) -> str:
 161          return f"<RunVar name={self._name!r}>"
 162