ez-a-sync 0.32.29__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ez-a-sync might be problematic. Click here for more details.

Files changed (177) hide show
  1. a_sync/ENVIRONMENT_VARIABLES.py +42 -0
  2. a_sync/__init__.pxd +2 -0
  3. a_sync/__init__.py +145 -0
  4. a_sync/_smart.c +22803 -0
  5. a_sync/_smart.cp310-win_amd64.pyd +0 -0
  6. a_sync/_smart.pxd +2 -0
  7. a_sync/_smart.pyi +202 -0
  8. a_sync/_smart.pyx +674 -0
  9. a_sync/_typing.py +258 -0
  10. a_sync/a_sync/__init__.py +60 -0
  11. a_sync/a_sync/_descriptor.c +20528 -0
  12. a_sync/a_sync/_descriptor.cp310-win_amd64.pyd +0 -0
  13. a_sync/a_sync/_descriptor.pyi +33 -0
  14. a_sync/a_sync/_descriptor.pyx +422 -0
  15. a_sync/a_sync/_flags.c +6074 -0
  16. a_sync/a_sync/_flags.cp310-win_amd64.pyd +0 -0
  17. a_sync/a_sync/_flags.pxd +3 -0
  18. a_sync/a_sync/_flags.pyx +92 -0
  19. a_sync/a_sync/_helpers.c +14521 -0
  20. a_sync/a_sync/_helpers.cp310-win_amd64.pyd +0 -0
  21. a_sync/a_sync/_helpers.pxd +3 -0
  22. a_sync/a_sync/_helpers.pyi +10 -0
  23. a_sync/a_sync/_helpers.pyx +167 -0
  24. a_sync/a_sync/_kwargs.c +12194 -0
  25. a_sync/a_sync/_kwargs.cp310-win_amd64.pyd +0 -0
  26. a_sync/a_sync/_kwargs.pxd +2 -0
  27. a_sync/a_sync/_kwargs.pyx +64 -0
  28. a_sync/a_sync/_meta.py +210 -0
  29. a_sync/a_sync/abstract.c +12411 -0
  30. a_sync/a_sync/abstract.cp310-win_amd64.pyd +0 -0
  31. a_sync/a_sync/abstract.pyi +141 -0
  32. a_sync/a_sync/abstract.pyx +221 -0
  33. a_sync/a_sync/base.c +14932 -0
  34. a_sync/a_sync/base.cp310-win_amd64.pyd +0 -0
  35. a_sync/a_sync/base.pyi +60 -0
  36. a_sync/a_sync/base.pyx +271 -0
  37. a_sync/a_sync/config.py +168 -0
  38. a_sync/a_sync/decorator.py +651 -0
  39. a_sync/a_sync/flags.c +5272 -0
  40. a_sync/a_sync/flags.cp310-win_amd64.pyd +0 -0
  41. a_sync/a_sync/flags.pxd +72 -0
  42. a_sync/a_sync/flags.pyi +74 -0
  43. a_sync/a_sync/flags.pyx +72 -0
  44. a_sync/a_sync/function.c +37846 -0
  45. a_sync/a_sync/function.cp310-win_amd64.pyd +0 -0
  46. a_sync/a_sync/function.pxd +28 -0
  47. a_sync/a_sync/function.pyi +571 -0
  48. a_sync/a_sync/function.pyx +1381 -0
  49. a_sync/a_sync/method.c +29774 -0
  50. a_sync/a_sync/method.cp310-win_amd64.pyd +0 -0
  51. a_sync/a_sync/method.pxd +9 -0
  52. a_sync/a_sync/method.pyi +525 -0
  53. a_sync/a_sync/method.pyx +1023 -0
  54. a_sync/a_sync/modifiers/__init__.pxd +1 -0
  55. a_sync/a_sync/modifiers/__init__.py +101 -0
  56. a_sync/a_sync/modifiers/cache/__init__.py +160 -0
  57. a_sync/a_sync/modifiers/cache/memory.py +165 -0
  58. a_sync/a_sync/modifiers/limiter.py +132 -0
  59. a_sync/a_sync/modifiers/manager.c +16149 -0
  60. a_sync/a_sync/modifiers/manager.cp310-win_amd64.pyd +0 -0
  61. a_sync/a_sync/modifiers/manager.pxd +5 -0
  62. a_sync/a_sync/modifiers/manager.pyi +219 -0
  63. a_sync/a_sync/modifiers/manager.pyx +299 -0
  64. a_sync/a_sync/modifiers/semaphores.py +173 -0
  65. a_sync/a_sync/property.c +27260 -0
  66. a_sync/a_sync/property.cp310-win_amd64.pyd +0 -0
  67. a_sync/a_sync/property.pyi +376 -0
  68. a_sync/a_sync/property.pyx +819 -0
  69. a_sync/a_sync/singleton.py +63 -0
  70. a_sync/aliases.py +3 -0
  71. a_sync/async_property/__init__.pxd +1 -0
  72. a_sync/async_property/__init__.py +1 -0
  73. a_sync/async_property/cached.c +20386 -0
  74. a_sync/async_property/cached.cp310-win_amd64.pyd +0 -0
  75. a_sync/async_property/cached.pxd +10 -0
  76. a_sync/async_property/cached.pyi +45 -0
  77. a_sync/async_property/cached.pyx +178 -0
  78. a_sync/async_property/proxy.c +34654 -0
  79. a_sync/async_property/proxy.cp310-win_amd64.pyd +0 -0
  80. a_sync/async_property/proxy.pxd +2 -0
  81. a_sync/async_property/proxy.pyi +124 -0
  82. a_sync/async_property/proxy.pyx +474 -0
  83. a_sync/asyncio/__init__.pxd +6 -0
  84. a_sync/asyncio/__init__.py +164 -0
  85. a_sync/asyncio/as_completed.c +18841 -0
  86. a_sync/asyncio/as_completed.cp310-win_amd64.pyd +0 -0
  87. a_sync/asyncio/as_completed.pxd +8 -0
  88. a_sync/asyncio/as_completed.pyi +109 -0
  89. a_sync/asyncio/as_completed.pyx +269 -0
  90. a_sync/asyncio/create_task.c +15902 -0
  91. a_sync/asyncio/create_task.cp310-win_amd64.pyd +0 -0
  92. a_sync/asyncio/create_task.pxd +2 -0
  93. a_sync/asyncio/create_task.pyi +51 -0
  94. a_sync/asyncio/create_task.pyx +271 -0
  95. a_sync/asyncio/gather.c +16679 -0
  96. a_sync/asyncio/gather.cp310-win_amd64.pyd +0 -0
  97. a_sync/asyncio/gather.pyi +107 -0
  98. a_sync/asyncio/gather.pyx +218 -0
  99. a_sync/asyncio/igather.c +12676 -0
  100. a_sync/asyncio/igather.cp310-win_amd64.pyd +0 -0
  101. a_sync/asyncio/igather.pxd +1 -0
  102. a_sync/asyncio/igather.pyi +7 -0
  103. a_sync/asyncio/igather.pyx +182 -0
  104. a_sync/asyncio/sleep.c +9593 -0
  105. a_sync/asyncio/sleep.cp310-win_amd64.pyd +0 -0
  106. a_sync/asyncio/sleep.pyi +14 -0
  107. a_sync/asyncio/sleep.pyx +49 -0
  108. a_sync/debugging.c +15362 -0
  109. a_sync/debugging.cp310-win_amd64.pyd +0 -0
  110. a_sync/debugging.pyi +76 -0
  111. a_sync/debugging.pyx +107 -0
  112. a_sync/exceptions.c +13312 -0
  113. a_sync/exceptions.cp310-win_amd64.pyd +0 -0
  114. a_sync/exceptions.pyi +376 -0
  115. a_sync/exceptions.pyx +446 -0
  116. a_sync/executor.py +619 -0
  117. a_sync/functools.c +12738 -0
  118. a_sync/functools.cp310-win_amd64.pyd +0 -0
  119. a_sync/functools.pxd +7 -0
  120. a_sync/functools.pyi +33 -0
  121. a_sync/functools.pyx +139 -0
  122. a_sync/future.py +1497 -0
  123. a_sync/iter.c +37271 -0
  124. a_sync/iter.cp310-win_amd64.pyd +0 -0
  125. a_sync/iter.pxd +11 -0
  126. a_sync/iter.pyi +370 -0
  127. a_sync/iter.pyx +981 -0
  128. a_sync/primitives/__init__.pxd +1 -0
  129. a_sync/primitives/__init__.py +53 -0
  130. a_sync/primitives/_debug.c +15757 -0
  131. a_sync/primitives/_debug.cp310-win_amd64.pyd +0 -0
  132. a_sync/primitives/_debug.pxd +12 -0
  133. a_sync/primitives/_debug.pyi +52 -0
  134. a_sync/primitives/_debug.pyx +223 -0
  135. a_sync/primitives/_loggable.c +11529 -0
  136. a_sync/primitives/_loggable.cp310-win_amd64.pyd +0 -0
  137. a_sync/primitives/_loggable.pxd +4 -0
  138. a_sync/primitives/_loggable.pyi +66 -0
  139. a_sync/primitives/_loggable.pyx +102 -0
  140. a_sync/primitives/locks/__init__.pxd +8 -0
  141. a_sync/primitives/locks/__init__.py +17 -0
  142. a_sync/primitives/locks/counter.c +17679 -0
  143. a_sync/primitives/locks/counter.cp310-win_amd64.pyd +0 -0
  144. a_sync/primitives/locks/counter.pxd +12 -0
  145. a_sync/primitives/locks/counter.pyi +151 -0
  146. a_sync/primitives/locks/counter.pyx +260 -0
  147. a_sync/primitives/locks/event.c +17063 -0
  148. a_sync/primitives/locks/event.cp310-win_amd64.pyd +0 -0
  149. a_sync/primitives/locks/event.pxd +22 -0
  150. a_sync/primitives/locks/event.pyi +43 -0
  151. a_sync/primitives/locks/event.pyx +185 -0
  152. a_sync/primitives/locks/prio_semaphore.c +25590 -0
  153. a_sync/primitives/locks/prio_semaphore.cp310-win_amd64.pyd +0 -0
  154. a_sync/primitives/locks/prio_semaphore.pxd +25 -0
  155. a_sync/primitives/locks/prio_semaphore.pyi +217 -0
  156. a_sync/primitives/locks/prio_semaphore.pyx +597 -0
  157. a_sync/primitives/locks/semaphore.c +26509 -0
  158. a_sync/primitives/locks/semaphore.cp310-win_amd64.pyd +0 -0
  159. a_sync/primitives/locks/semaphore.pxd +21 -0
  160. a_sync/primitives/locks/semaphore.pyi +197 -0
  161. a_sync/primitives/locks/semaphore.pyx +454 -0
  162. a_sync/primitives/queue.py +1022 -0
  163. a_sync/py.typed +0 -0
  164. a_sync/sphinx/__init__.py +3 -0
  165. a_sync/sphinx/ext.py +289 -0
  166. a_sync/task.py +932 -0
  167. a_sync/utils/__init__.py +105 -0
  168. a_sync/utils/iterators.py +297 -0
  169. a_sync/utils/repr.c +15799 -0
  170. a_sync/utils/repr.cp310-win_amd64.pyd +0 -0
  171. a_sync/utils/repr.pyi +2 -0
  172. a_sync/utils/repr.pyx +73 -0
  173. ez_a_sync-0.32.29.dist-info/METADATA +367 -0
  174. ez_a_sync-0.32.29.dist-info/RECORD +177 -0
  175. ez_a_sync-0.32.29.dist-info/WHEEL +5 -0
  176. ez_a_sync-0.32.29.dist-info/licenses/LICENSE.txt +17 -0
  177. ez_a_sync-0.32.29.dist-info/top_level.txt +1 -0
a_sync/_smart.pyx ADDED
@@ -0,0 +1,674 @@
1
+ """
2
+ This module defines smart future and task utilities for the a_sync library.
3
+ These utilities provide enhanced functionality for managing asynchronous tasks and futures,
4
+ including a custom task factory for creating :class:`~SmartTask` instances and a shielding mechanism
5
+ to protect tasks from cancellation.
6
+ """
7
+
8
+ import asyncio
9
+ import typing
10
+ import weakref
11
+ from logging import getLogger
12
+ from types import TracebackType
13
+ from typing import Awaitable, Generator, Optional, Set
14
+
15
+ cimport cython
16
+ from cpython.object cimport PyObject
17
+ from cpython.ref cimport Py_DECREF, Py_INCREF
18
+ from cpython.unicode cimport PyUnicode_CompareWithASCIIString
19
+ from cpython.version cimport PY_VERSION_HEX
20
+
21
+ from a_sync._typing import T
22
+
23
+ if typing.TYPE_CHECKING:
24
+ from a_sync import SmartProcessingQueue
25
+
26
+ cdef extern from "weakrefobject.h":
27
+ PyObject* PyWeakref_NewRef(PyObject*, PyObject*)
28
+ PyObject* PyWeakref_NewProxy(PyObject*, PyObject*)
29
+
30
+ cdef extern from "pythoncapi_compat.h":
31
+ int PyWeakref_GetRef(PyObject*, PyObject**)
32
+
33
+ # cdef asyncio
34
+ cdef object ensure_future = asyncio.ensure_future
35
+ cdef object get_event_loop = asyncio.get_event_loop
36
+ cdef object AbstractEventLoop = asyncio.AbstractEventLoop
37
+ cdef object Future = asyncio.Future
38
+ cdef object Task = asyncio.Task
39
+ cdef object CancelledError = asyncio.CancelledError
40
+ cdef object InvalidStateError = asyncio.InvalidStateError
41
+ cdef dict[object, object] _current_tasks = asyncio.tasks._current_tasks
42
+ cdef object _future_init = asyncio.Future.__init__
43
+ cdef object _get_loop = asyncio.futures._get_loop
44
+ cdef object _task_init = asyncio.Task.__init__
45
+
46
+ # cdef logging
47
+ cdef public object logger = getLogger(__name__)
48
+ cdef object DEBUG = 10
49
+ cdef bint _DEBUG_LOGS_ENABLED = logger.isEnabledFor(DEBUG)
50
+ cdef object _logger_log = logger._log
51
+ del getLogger
52
+
53
+ # cdef typing
54
+ cdef object Any = typing.Any
55
+ cdef object Generic = typing.Generic
56
+ cdef object Tuple = typing.Tuple
57
+ cdef object Union = typing.Union
58
+ del typing
59
+
60
+
61
+ cdef object Args = Tuple[Any]
62
+ cdef object Kwargs = Tuple[Tuple[str, Any]]
63
+ _Key = Tuple[Args, Kwargs]
64
+ cdef object Key = _Key
65
+
66
+
67
+ cdef Py_ssize_t ZERO = 0
68
+ cdef Py_ssize_t ONE = 1
69
+ cdef PyObject *NONE = <PyObject*>None
70
+
71
+
72
+ cdef void log_await(object arg):
73
+ _logger_log(DEBUG, "awaiting %s", (arg, ))
74
+
75
+
76
+ @cython.linetrace(False)
77
+ cdef Py_ssize_t count_waiters(fut: Union["SmartFuture", "SmartTask"]):
78
+ if _is_done(fut):
79
+ return ZERO
80
+ try:
81
+ waiters = fut._waiters
82
+ except AttributeError:
83
+ return ONE
84
+ cdef Py_ssize_t count = ZERO
85
+ for waiter in waiters:
86
+ count += count_waiters(waiter)
87
+ return count
88
+
89
+
90
+ cdef class WeakSet:
91
+ cdef readonly dict _refs
92
+ """Mapping from object ID to weak reference."""
93
+
94
+ cdef PyObject *__callback_ptr
95
+
96
+ def __cinit__(self):
97
+ self._refs = {}
98
+
99
+ def __init__(self):
100
+ cdef object gc_callback = self._gc_callback
101
+ self.__callback_ptr = <PyObject*>gc_callback
102
+ Py_INCREF(gc_callback)
103
+
104
+ def __dealloc__(self):
105
+ cdef PyObject *callback_ptr = self.__callback_ptr
106
+ if callback_ptr is not NULL:
107
+ Py_DECREF(<object>callback_ptr)
108
+
109
+ def __repr__(self):
110
+ # Use list comprehension syntax within the repr function for clarity
111
+ return f"WeakSet({', '.join(map(repr, self))})"
112
+
113
+ @cython.linetrace(False)
114
+ def __bool__(self) -> bool:
115
+ return bool(self._refs)
116
+
117
+ @cython.linetrace(False)
118
+ def __len__(self) -> int:
119
+ return len(self._refs)
120
+
121
+ @cython.linetrace(False)
122
+ def __contains__(self, item: Future) -> bool:
123
+ ref = self._refs.get(id(item))
124
+ return ref is not None and ref() is item
125
+
126
+ cdef void add(self, fut: Future):
127
+ # Keep a weak reference with a callback for when the item is collected
128
+ cdef PyObject *fut_ptr = <PyObject*>fut
129
+ cdef PyObject *weakref_ptr = PyWeakref_NewRef(fut_ptr, self.__callback_ptr)
130
+ if weakref_ptr == NULL:
131
+ raise MemoryError("Could not create ref")
132
+ self._refs[id(fut)] = <object>weakref_ptr
133
+
134
+ cdef void remove(self, fut: Future):
135
+ # Keep a weak reference with a callback for when the item is collected
136
+ try:
137
+ self._refs.pop(id(fut))
138
+ except KeyError:
139
+ raise KeyError(fut) from None
140
+
141
+ @cython.linetrace(False)
142
+ def __iter__(self):
143
+ cdef PyObject *obj_ptr = NULL
144
+ for ref in self._refs.values():
145
+ if PyWeakref_GetRef(<PyObject*>ref, &obj_ptr) == 1:
146
+ yield <object>obj_ptr
147
+
148
+ @cython.linetrace(False)
149
+ cdef void _gc_callback(self, fut: Future):
150
+ # Callback when a weakly-referenced object is garbage collected
151
+ self._refs.pop(id(fut), None) # Safely remove the item if it exists
152
+
153
+
154
+ @cython.linetrace(False)
155
+ cdef inline bint _is_done(fut: Future):
156
+ """Return True if the future is done.
157
+
158
+ Done means either that a result / exception are available, or that the
159
+ future was cancelled.
160
+ """
161
+ return PyUnicode_CompareWithASCIIString(fut._state, b"PENDING") != 0
162
+
163
+
164
+ @cython.linetrace(False)
165
+ cdef inline bint _is_not_done(fut: Future):
166
+ """Return False if the future is done.
167
+
168
+ Done means either that a result / exception are available, or that the
169
+ future was cancelled.
170
+ """
171
+ return PyUnicode_CompareWithASCIIString(fut._state, b"PENDING") == 0
172
+
173
+
174
+ @cython.linetrace(False)
175
+ cdef inline bint _is_cancelled(fut: Future):
176
+ """Return True if the future was cancelled."""
177
+ return PyUnicode_CompareWithASCIIString(fut._state, b"CANCELLED") == 0
178
+
179
+
180
+ @cython.linetrace(False)
181
+ cdef object _get_result(fut: Union["SmartFuture", "SmartTask"]):
182
+ """Return the result this future represents.
183
+
184
+ If the future has been cancelled, raises CancelledError. If the
185
+ future's result isn't yet available, raises InvalidStateError. If
186
+ the future is done and has an exception set, this exception is raised.
187
+ """
188
+ cdef str state = fut._state
189
+ if PyUnicode_CompareWithASCIIString(state, b"FINISHED") == 0:
190
+ exc = fut._exception
191
+ if exc is not None:
192
+ cached_traceback = fut.__traceback__
193
+ if cached_traceback is None:
194
+ fut._Future__log_traceback = False
195
+ cached_traceback = exc.__traceback__
196
+ fut.__traceback__ = cached_traceback
197
+ raise exc.with_traceback(cached_traceback) from exc.__cause__
198
+ return fut._result
199
+ if PyUnicode_CompareWithASCIIString(state, b"CANCELLED") == 0:
200
+ raise (
201
+ CancelledError()
202
+ if PY_VERSION_HEX < 0x03090000 # Python 3.9
203
+ else fut._make_cancelled_error()
204
+ )
205
+ raise InvalidStateError('Result is not ready.')
206
+
207
+ @cython.linetrace(False)
208
+ cdef object _get_exception(fut: Future):
209
+ """Return the exception that was set on this future.
210
+
211
+ The exception (or None if no exception was set) is returned only if
212
+ the future is done. If the future has been cancelled, raises
213
+ CancelledError. If the future isn't done yet, raises
214
+ InvalidStateError.
215
+ """
216
+ cdef str state = fut._state
217
+ if PyUnicode_CompareWithASCIIString(state, b"FINISHED") == 0:
218
+ fut._Future__log_traceback = False
219
+ return fut._exception
220
+ if PyUnicode_CompareWithASCIIString(state, b"CANCELLED") == 0:
221
+ raise (
222
+ CancelledError()
223
+ if PY_VERSION_HEX < 0x03090000 # Python 3.9
224
+ else fut._make_cancelled_error()
225
+ )
226
+ raise InvalidStateError('Exception is not set.')
227
+
228
+
229
+ class SmartFuture(Future, Generic[T]):
230
+ """
231
+ A smart future that tracks waiters and integrates with a smart processing queue.
232
+
233
+ Inherits from :class:`asyncio.Future`, providing additional functionality for tracking waiters and integrating with a smart processing queue.
234
+
235
+ Example:
236
+ Creating and awaiting a SmartFuture:
237
+
238
+ ```python
239
+ future = SmartFuture()
240
+ await future
241
+ ```
242
+
243
+ See Also:
244
+ - :class:`asyncio.Future`
245
+ """
246
+ _queue: Optional["SmartProcessingQueue[Any, Any, T]"] = None
247
+ _key: Optional[Key] = None
248
+
249
+ _waiters: "weakref.WeakSet[SmartTask[T]]"
250
+
251
+ __traceback__: Optional[TracebackType] = None
252
+
253
+ def __init__(
254
+ self,
255
+ *,
256
+ queue: Optional["SmartProcessingQueue[Any, Any, T]"] = None,
257
+ key: Optional[Key] = None,
258
+ loop: Optional[AbstractEventLoop] = None,
259
+ ) -> None:
260
+ """
261
+ Initialize the SmartFuture with an optional queue and key.
262
+
263
+ Args:
264
+ queue: Optional; a smart processing queue.
265
+ key: Optional; a key identifying the future.
266
+ loop: Optional; the event loop.
267
+
268
+ Example:
269
+ ```python
270
+ future = SmartFuture(queue=my_queue, key=my_key)
271
+ ```
272
+
273
+ See Also:
274
+ - :class:`SmartProcessingQueue`
275
+ """
276
+ cdef PyObject *queue_ptr
277
+ cdef PyObject *proxy_ptr
278
+ _future_init(self, loop=loop)
279
+ if queue:
280
+ queue_ptr = <PyObject*>queue
281
+ proxy_ptr = PyWeakref_NewProxy(queue_ptr, NONE)
282
+ if proxy_ptr == NULL:
283
+ raise MemoryError("Could not create proxy")
284
+ self._queue = <object>proxy_ptr
285
+ if key:
286
+ self._key = key
287
+ self._waiters = WeakSet()
288
+
289
+ def __repr__(self):
290
+ return f"<{type(self).__name__} key={self._key} waiters={count_waiters(self)} {<str>self._state}>"
291
+
292
+ def __lt__(self, other: "SmartFuture[T]") -> bint:
293
+ """
294
+ Compare the number of waiters to determine priority in a heap.
295
+ Lower values indicate higher priority, so more waiters means 'less than'.
296
+
297
+ Args:
298
+ other: Another SmartFuture to compare with.
299
+
300
+ Example:
301
+ ```python
302
+ future1 = SmartFuture()
303
+ future2 = SmartFuture()
304
+ print(future1 < future2)
305
+ ```
306
+
307
+ See Also:
308
+ - :meth:`num_waiters`
309
+ """
310
+ return count_waiters(self) > count_waiters(other)
311
+
312
+ def __await__(self) -> Generator[Any, None, T]:
313
+ """
314
+ Await the future, handling waiters and logging.
315
+
316
+ Yields:
317
+ The result of the future or task.
318
+
319
+ Raises:
320
+ RuntimeError: If await wasn't used with future.
321
+
322
+ Example:
323
+ Awaiting a SmartFuture:
324
+
325
+ ```python
326
+ future = SmartFuture()
327
+ result = await future
328
+ ```
329
+
330
+ Awaiting a SmartTask:
331
+
332
+ ```python
333
+ task = SmartTask(coro=my_coroutine())
334
+ result = await task
335
+ ```
336
+ """
337
+ if _is_done(self):
338
+ return _get_result(self) # May raise too.
339
+
340
+ self._asyncio_future_blocking = True
341
+ if task := current_task(self._loop):
342
+ (<WeakSet>self._waiters).add(task)
343
+ task.add_done_callback(
344
+ self._waiter_done_cleanup_callback # type: ignore [union-attr]
345
+ )
346
+
347
+ if _DEBUG_LOGS_ENABLED:
348
+ log_await(self)
349
+ yield self # This tells Task to wait for completion.
350
+ if _is_not_done(self):
351
+ raise RuntimeError("await wasn't used with future")
352
+
353
+ # remove the future from the associated queue, if any
354
+ if queue := self._queue:
355
+ queue._futs.pop(self._key, None)
356
+ return _get_result(self) # May raise too.
357
+
358
+ @cython.linetrace(False)
359
+ def _waiter_done_cleanup_callback(self, waiter: "SmartTask") -> None:
360
+ """
361
+ Callback to clean up waiters when a waiter task is done.
362
+
363
+ Removes the waiter from _waiters, and _queue._futs if applicable.
364
+
365
+ Args:
366
+ waiter: The waiter task to clean up.
367
+
368
+ Example:
369
+ Automatically called when a waiter task completes.
370
+ """
371
+ if _is_not_done(self):
372
+ (<WeakSet>self._waiters).remove(waiter)
373
+
374
+
375
+ cdef object _SmartFuture = SmartFuture
376
+
377
+
378
+ @cython.linetrace(False)
379
+ cdef inline object current_task(object loop):
380
+ return _current_tasks.get(loop)
381
+
382
+
383
+ @cython.linetrace(False)
384
+ cpdef inline object create_future(
385
+ queue: Optional["SmartProcessingQueue"] = None,
386
+ key: Optional[Key] = None,
387
+ loop: Optional[AbstractEventLoop] = None,
388
+ ):
389
+ """
390
+ Create a :class:`~SmartFuture` instance.
391
+
392
+ Args:
393
+ queue: Optional; a smart processing queue.
394
+ key: Optional; a key identifying the future.
395
+ loop: Optional; the event loop.
396
+
397
+ Returns:
398
+ A SmartFuture instance.
399
+
400
+ Example:
401
+ Creating a SmartFuture using the factory function:
402
+
403
+ ```python
404
+ future = create_future(queue=my_queue, key=my_key)
405
+ ```
406
+
407
+ See Also:
408
+ - :class:`SmartFuture`
409
+ """
410
+ return _SmartFuture(queue=queue, key=key, loop=loop or get_event_loop())
411
+
412
+
413
+ class SmartTask(Task, Generic[T]):
414
+ """
415
+ A smart task that tracks waiters and integrates with a smart processing queue.
416
+
417
+ Inherits from :class:`asyncio.Task`, providing additional functionality for tracking waiters.
418
+
419
+ Example:
420
+ Creating and awaiting a SmartTask:
421
+
422
+ ```python
423
+ task = SmartTask(coro=my_coroutine())
424
+ await task
425
+ ```
426
+
427
+ See Also:
428
+ - :class:`asyncio.Task`
429
+ """
430
+
431
+ _waiters: Set["Task[T]"]
432
+
433
+ __traceback__: Optional[TracebackType] = None
434
+
435
+ @cython.linetrace(False)
436
+ def __init__(
437
+ self,
438
+ coro: Awaitable[T],
439
+ *,
440
+ loop: Optional[AbstractEventLoop] = None,
441
+ name: Optional[str] = None,
442
+ ) -> None:
443
+ """
444
+ Initialize the SmartTask with a coroutine and optional event loop.
445
+
446
+ Args:
447
+ coro: The coroutine to run in the task.
448
+ loop: Optional; the event loop.
449
+ name: Optional; the name of the task.
450
+
451
+ Example:
452
+ ```python
453
+ task = SmartTask(coro=my_coroutine(), name="my_task")
454
+ ```
455
+
456
+ See Also:
457
+ - :func:`asyncio.create_task`
458
+ """
459
+ _task_init(self, coro, loop=loop, name=name)
460
+ self._waiters = set()
461
+
462
+ def __await__(self) -> Generator[Any, None, T]:
463
+ """
464
+ Await the task, handling waiters and logging.
465
+
466
+ Yields:
467
+ The result of the future or task.
468
+
469
+ Raises:
470
+ RuntimeError: If await wasn't used with future.
471
+
472
+ Example:
473
+ Awaiting a SmartFuture:
474
+
475
+ ```python
476
+ future = SmartFuture()
477
+ result = await future
478
+ ```
479
+
480
+ Awaiting a SmartTask:
481
+
482
+ ```python
483
+ task = SmartTask(coro=my_coroutine())
484
+ result = await task
485
+ ```
486
+ """
487
+ if _is_done(self):
488
+ return _get_result(self) # May raise too.
489
+
490
+ self._asyncio_future_blocking = True
491
+ if task := current_task(self._loop):
492
+ (<set>self._waiters).add(task)
493
+ task.add_done_callback(
494
+ self._waiter_done_cleanup_callback # type: ignore [union-attr]
495
+ )
496
+
497
+ if _DEBUG_LOGS_ENABLED:
498
+ log_await(self)
499
+ yield self # This tells Task to wait for completion.
500
+ if _is_not_done(self):
501
+ raise RuntimeError("await wasn't used with future")
502
+
503
+ # clear the waiters
504
+ self._waiters = set()
505
+ return _get_result(self) # May raise too.
506
+
507
+ @cython.linetrace(False)
508
+ def _waiter_done_cleanup_callback(self, waiter: "SmartTask") -> None:
509
+ """
510
+ Callback to clean up waiters when a waiter task is done.
511
+
512
+ Removes the waiter from _waiters, and _queue._futs if applicable.
513
+
514
+ Args:
515
+ waiter: The waiter task to clean up.
516
+
517
+ Example:
518
+ Automatically called when a waiter task completes.
519
+ """
520
+ if _is_not_done(self):
521
+ (<set>self._waiters).remove(waiter)
522
+
523
+
524
+ cdef object _SmartTask = SmartTask
525
+
526
+
527
+ @cython.linetrace(False)
528
+ cpdef object smart_task_factory(loop: AbstractEventLoop, coro: Awaitable[T]):
529
+ """
530
+ Task factory function that an event loop calls to create new tasks.
531
+
532
+ This factory function utilizes ez-a-sync's custom :class:`~SmartTask` implementation.
533
+
534
+ Args:
535
+ loop: The event loop.
536
+ coro: The coroutine to run in the task.
537
+
538
+ Returns:
539
+ A SmartTask instance running the provided coroutine.
540
+
541
+ Example:
542
+ Using the smart task factory to create a SmartTask:
543
+
544
+ ```python
545
+ loop = asyncio.get_event_loop()
546
+ task = smart_task_factory(loop, my_coroutine())
547
+ ```
548
+
549
+ See Also:
550
+ - :func:`set_smart_task_factory`
551
+ - :class:`SmartTask`
552
+ """
553
+ return _SmartTask(coro, loop=loop)
554
+
555
+
556
+ def set_smart_task_factory(loop: AbstractEventLoop = None) -> None:
557
+ """
558
+ Set the event loop's task factory to :func:`~smart_task_factory` so all tasks will be SmartTask instances.
559
+
560
+ Args:
561
+ loop: Optional; the event loop. If None, the current event loop is used.
562
+
563
+ Example:
564
+ Setting the smart task factory for the current event loop:
565
+
566
+ ```python
567
+ set_smart_task_factory()
568
+ ```
569
+
570
+ See Also:
571
+ - :func:`smart_task_factory`
572
+ """
573
+ if loop is None:
574
+ import a_sync.asyncio
575
+ loop = a_sync.asyncio.get_event_loop()
576
+ loop.set_task_factory(smart_task_factory)
577
+
578
+
579
+ cpdef object shield(arg: Awaitable[T]):
580
+ """
581
+ Wait for a future, shielding it from cancellation.
582
+
583
+ The statement
584
+
585
+ res = await shield(something())
586
+
587
+ is exactly equivalent to the statement
588
+
589
+ res = await something()
590
+
591
+ *except* that if the coroutine containing it is cancelled, the
592
+ task running in something() is not cancelled. From the POV of
593
+ something(), the cancellation did not happen. But its caller is
594
+ still cancelled, so the yield-from expression still raises
595
+ CancelledError. Note: If something() is cancelled by other means
596
+ this will still cancel shield().
597
+
598
+ If you want to completely ignore cancellation (not recommended)
599
+ you can combine shield() with a try/except clause, as follows:
600
+
601
+ try:
602
+ res = await shield(something())
603
+ except CancelledError:
604
+ res = None
605
+
606
+ Args:
607
+ arg: The awaitable to shield from cancellation.
608
+ loop: Optional; the event loop. Deprecated since Python 3.8.
609
+
610
+ Returns:
611
+ A :class:`SmartFuture` or :class:`asyncio.Future` instance.
612
+
613
+ Example:
614
+ Using shield to protect a coroutine from cancellation:
615
+
616
+ ```python
617
+ result = await shield(my_coroutine())
618
+ ```
619
+
620
+ See Also:
621
+ - :func:`asyncio.shield`
622
+ """
623
+ inner = ensure_future(arg)
624
+ if _is_done(inner):
625
+ # Shortcut.
626
+ return inner
627
+
628
+ loop = _get_loop(inner)
629
+ outer = _SmartFuture(loop=loop)
630
+
631
+ # special handling to connect SmartFutures to SmartTasks if enabled
632
+ if (waiters := getattr(inner, "_waiters", None)) is not None:
633
+ waiters.add(outer)
634
+
635
+ _inner_done_callback, _outer_done_callback = _get_done_callbacks(inner, outer)
636
+
637
+ inner.add_done_callback(_inner_done_callback)
638
+ outer.add_done_callback(_outer_done_callback)
639
+ return outer
640
+
641
+
642
+ cdef tuple _get_done_callbacks(inner: Task, outer: Future):
643
+
644
+ def _inner_done_callback(inner):
645
+ if _is_cancelled(outer):
646
+ if not _is_cancelled(inner):
647
+ # Mark inner's result as retrieved.
648
+ inner._Future__log_traceback = False
649
+ return
650
+
651
+ if _is_cancelled(inner):
652
+ outer.cancel()
653
+ else:
654
+ exc = _get_exception(inner)
655
+ if exc is not None:
656
+ outer.set_exception(exc)
657
+ else:
658
+ outer.set_result(inner._result)
659
+
660
+ def _outer_done_callback(outer):
661
+ if _is_not_done(inner):
662
+ inner.remove_done_callback(_inner_done_callback)
663
+
664
+ return _inner_done_callback, _outer_done_callback
665
+
666
+
667
+ __all__ = [
668
+ "create_future",
669
+ "shield",
670
+ "SmartFuture",
671
+ "SmartTask",
672
+ "smart_task_factory",
673
+ "set_smart_task_factory",
674
+ ]