Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/anyio/lowlevel.py: 49%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

86 statements  

1from __future__ import annotations 

2 

3__all__ = ( 

4 "EventLoopToken", 

5 "RunvarToken", 

6 "RunVar", 

7 "checkpoint", 

8 "checkpoint_if_cancelled", 

9 "cancel_shielded_checkpoint", 

10 "current_token", 

11) 

12 

13import enum 

14from dataclasses import dataclass 

15from types import TracebackType 

16from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, final, overload 

17from weakref import WeakKeyDictionary 

18 

19from ._core._eventloop import get_async_backend 

20 

21if TYPE_CHECKING: 

22 from .abc import AsyncBackend 

23 

24T = TypeVar("T") 

25D = TypeVar("D") 

26 

27 

28async def checkpoint() -> None: 

29 """ 

30 Check for cancellation and allow the scheduler to switch to another task. 

31 

32 Equivalent to (but more efficient than):: 

33 

34 await checkpoint_if_cancelled() 

35 await cancel_shielded_checkpoint() 

36 

37 .. versionadded:: 3.0 

38 

39 """ 

40 await get_async_backend().checkpoint() 

41 

42 

43async def checkpoint_if_cancelled() -> None: 

44 """ 

45 Enter a checkpoint if the enclosing cancel scope has been cancelled. 

46 

47 This does not allow the scheduler to switch to a different task. 

48 

49 .. versionadded:: 3.0 

50 

51 """ 

52 await get_async_backend().checkpoint_if_cancelled() 

53 

54 

55async def cancel_shielded_checkpoint() -> None: 

56 """ 

57 Allow the scheduler to switch to another task but without checking for cancellation. 

58 

59 Equivalent to (but potentially more efficient than):: 

60 

61 with CancelScope(shield=True): 

62 await checkpoint() 

63 

64 .. versionadded:: 3.0 

65 

66 """ 

67 await get_async_backend().cancel_shielded_checkpoint() 

68 

69 

70@final 

71@dataclass(frozen=True, repr=False) 

72class EventLoopToken: 

73 """ 

74 An opaque object that holds a reference to an event loop. 

75 

76 .. versionadded:: 4.11.0 

77 """ 

78 

79 backend_class: type[AsyncBackend] 

80 native_token: object 

81 

82 

83def current_token() -> EventLoopToken: 

84 """ 

85 Return a token object that can be used to call code in the current event loop from 

86 another thread. 

87 

88 :raises NoEventLoopError: if no supported asynchronous event loop is running in the 

89 current thread 

90 

91 .. versionadded:: 4.11.0 

92 

93 """ 

94 backend_class = get_async_backend() 

95 raw_token = backend_class.current_token() 

96 return EventLoopToken(backend_class, raw_token) 

97 

98 

99_run_vars: WeakKeyDictionary[object, dict[RunVar[Any], Any]] = WeakKeyDictionary() 

100 

101 

102class _NoValueSet(enum.Enum): 

103 NO_VALUE_SET = enum.auto() 

104 

105 

106class RunvarToken(Generic[T]): 

107 """ 

108 A token that can be used to restore a :class:`RunVar` to its previous value. 

109 

110 Returned by :meth:`RunVar.set`. Can be used as a context manager to automatically 

111 reset the variable on exit, or passed directly to :meth:`RunVar.reset`. 

112 """ 

113 

114 __slots__ = "_var", "_value", "_redeemed" 

115 

116 def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]): 

117 self._var = var 

118 self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value 

119 self._redeemed = False 

120 

121 def __enter__(self) -> RunvarToken[T]: 

122 return self 

123 

124 def __exit__( 

125 self, 

126 exc_type: type[BaseException] | None, 

127 exc_val: BaseException | None, 

128 exc_tb: TracebackType | None, 

129 ) -> None: 

130 self._var.reset(self) 

131 

132 

133class RunVar(Generic[T]): 

134 """ 

135 Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop. 

136 

137 Can be used as a context manager, Just like :class:`~contextvars.ContextVar`, that 

138 will reset the variable to its previous value when the context block is exited. 

139 """ 

140 

141 __slots__ = "_name", "_default" 

142 

143 NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET 

144 

145 def __init__( 

146 self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET 

147 ): 

148 self._name = name 

149 self._default = default 

150 

151 @property 

152 def _current_vars(self) -> dict[RunVar[T], T]: 

153 native_token = current_token().native_token 

154 try: 

155 return _run_vars[native_token] 

156 except KeyError: 

157 run_vars = _run_vars[native_token] = {} 

158 return run_vars 

159 

160 @overload 

161 def get(self, default: D) -> T | D: ... 

162 

163 @overload 

164 def get(self) -> T: ... 

165 

166 def get( 

167 self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET 

168 ) -> T | D: 

169 """ 

170 Return the current value of this run variable. 

171 

172 :param default: a fallback value to return if no value has been set 

173 :return: the current value, the provided default, or the variable's own default 

174 :raises LookupError: if no value is set and no default is available 

175 

176 """ 

177 try: 

178 return self._current_vars[self] 

179 except KeyError: 

180 if default is not RunVar.NO_VALUE_SET: 

181 return default 

182 elif self._default is not RunVar.NO_VALUE_SET: 

183 return self._default 

184 

185 raise LookupError( 

186 f'Run variable "{self._name}" has no value and no default set' 

187 ) 

188 

189 def set(self, value: T) -> RunvarToken[T]: 

190 """ 

191 Set the value of this run variable for the current event loop. 

192 

193 :param value: the new value 

194 :return: a token that can be used to restore the previous value 

195 

196 """ 

197 current_vars = self._current_vars 

198 token = RunvarToken(self, current_vars.get(self, RunVar.NO_VALUE_SET)) 

199 current_vars[self] = value 

200 return token 

201 

202 def reset(self, token: RunvarToken[T]) -> None: 

203 """ 

204 Restore this run variable to the value it held before the matching :meth:`set`. 

205 

206 :param token: the token returned by :meth:`set` 

207 :raises ValueError: if the token belongs to a different :class:`RunVar` or the token 

208 has already been used 

209 

210 """ 

211 if token._var is not self: 

212 raise ValueError("This token does not belong to this RunVar") 

213 

214 if token._redeemed: 

215 raise ValueError("This token has already been used") 

216 

217 if token._value is _NoValueSet.NO_VALUE_SET: 

218 try: 

219 del self._current_vars[self] 

220 except KeyError: 

221 pass 

222 else: 

223 self._current_vars[self] = token._value 

224 

225 token._redeemed = True 

226 

227 def __repr__(self) -> str: 

228 return f"<RunVar name={self._name!r}>"