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