ez-a-sync 0.33.4__cp313-cp313-musllinux_1_2_i686.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.
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 +22830 -0
  5. a_sync/_smart.cpython-313-i386-linux-musl.so +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 +20537 -0
  12. a_sync/a_sync/_descriptor.cpython-313-i386-linux-musl.so +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 +6082 -0
  16. a_sync/a_sync/_flags.cpython-313-i386-linux-musl.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 +14529 -0
  20. a_sync/a_sync/_helpers.cpython-313-i386-linux-musl.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 +12202 -0
  25. a_sync/a_sync/_kwargs.cpython-313-i386-linux-musl.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 +12420 -0
  30. a_sync/a_sync/abstract.cpython-313-i386-linux-musl.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 +14940 -0
  34. a_sync/a_sync/base.cpython-313-i386-linux-musl.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 +651 -0
  39. a_sync/a_sync/flags.c +5272 -0
  40. a_sync/a_sync/flags.cpython-313-i386-linux-musl.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 +37856 -0
  45. a_sync/a_sync/function.cpython-313-i386-linux-musl.so +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 +29662 -0
  50. a_sync/a_sync/method.cpython-313-i386-linux-musl.so +0 -0
  51. a_sync/a_sync/method.pxd +9 -0
  52. a_sync/a_sync/method.pyi +523 -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 +16157 -0
  60. a_sync/a_sync/modifiers/manager.cpython-313-i386-linux-musl.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 +299 -0
  64. a_sync/a_sync/modifiers/semaphores.py +173 -0
  65. a_sync/a_sync/property.c +27268 -0
  66. a_sync/a_sync/property.cpython-313-i386-linux-musl.so +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 +20397 -0
  74. a_sync/async_property/cached.cpython-313-i386-linux-musl.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 +34662 -0
  79. a_sync/async_property/proxy.cpython-313-i386-linux-musl.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 +18849 -0
  86. a_sync/asyncio/as_completed.cpython-313-i386-linux-musl.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 +15912 -0
  91. a_sync/asyncio/create_task.cpython-313-i386-linux-musl.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 +271 -0
  95. a_sync/asyncio/gather.c +16687 -0
  96. a_sync/asyncio/gather.cpython-313-i386-linux-musl.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 +13080 -0
  100. a_sync/asyncio/igather.cpython-313-i386-linux-musl.so +0 -0
  101. a_sync/asyncio/igather.pxd +1 -0
  102. a_sync/asyncio/igather.pyi +8 -0
  103. a_sync/asyncio/igather.pyx +183 -0
  104. a_sync/asyncio/sleep.c +9601 -0
  105. a_sync/asyncio/sleep.cpython-313-i386-linux-musl.so +0 -0
  106. a_sync/asyncio/sleep.pyi +14 -0
  107. a_sync/asyncio/sleep.pyx +49 -0
  108. a_sync/debugging.c +15370 -0
  109. a_sync/debugging.cpython-313-i386-linux-musl.so +0 -0
  110. a_sync/debugging.pyi +76 -0
  111. a_sync/debugging.pyx +107 -0
  112. a_sync/exceptions.c +13320 -0
  113. a_sync/exceptions.cpython-313-i386-linux-musl.so +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 +12746 -0
  118. a_sync/functools.cpython-313-i386-linux-musl.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 +37279 -0
  124. a_sync/iter.cpython-313-i386-linux-musl.so +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 +15765 -0
  131. a_sync/primitives/_debug.cpython-313-i386-linux-musl.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 +11538 -0
  136. a_sync/primitives/_loggable.cpython-313-i386-linux-musl.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 +17938 -0
  143. a_sync/primitives/locks/counter.cpython-313-i386-linux-musl.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 +267 -0
  147. a_sync/primitives/locks/event.c +17072 -0
  148. a_sync/primitives/locks/event.cpython-313-i386-linux-musl.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 +185 -0
  152. a_sync/primitives/locks/prio_semaphore.c +25635 -0
  153. a_sync/primitives/locks/prio_semaphore.cpython-313-i386-linux-musl.so +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 +26553 -0
  158. a_sync/primitives/locks/semaphore.cpython-313-i386-linux-musl.so +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 +1026 -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 +934 -0
  167. a_sync/utils/__init__.py +105 -0
  168. a_sync/utils/iterators.py +297 -0
  169. a_sync/utils/repr.c +15866 -0
  170. a_sync/utils/repr.cpython-313-i386-linux-musl.so +0 -0
  171. a_sync/utils/repr.pyi +2 -0
  172. a_sync/utils/repr.pyx +73 -0
  173. ez_a_sync-0.33.4.dist-info/METADATA +368 -0
  174. ez_a_sync-0.33.4.dist-info/RECORD +177 -0
  175. ez_a_sync-0.33.4.dist-info/WHEEL +5 -0
  176. ez_a_sync-0.33.4.dist-info/licenses/LICENSE.txt +17 -0
  177. ez_a_sync-0.33.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1026 @@
1
+ """
2
+ This module provides various queue implementations for managing asynchronous tasks.
3
+ It includes standard FIFO queues, priority queues, and processing queues with enhanced functionality.
4
+
5
+ Classes:
6
+ Queue: A generic asynchronous queue that extends the functionality of `asyncio.Queue`.
7
+ ProcessingQueue: A queue designed for processing tasks asynchronously with multiple workers.
8
+ PriorityProcessingQueue: A priority-based processing queue where tasks are processed based on priority.
9
+ SmartProcessingQueue: A processing queue that executes jobs with the most waiters first, supporting dynamic priorities.
10
+
11
+ See Also:
12
+ `asyncio.Queue`: The base class for asynchronous FIFO queues.
13
+ `asyncio.PriorityQueue`: The base class for priority queues.
14
+ """
15
+
16
+ import asyncio
17
+ import sys
18
+ from asyncio import AbstractEventLoop, Future, InvalidStateError, QueueEmpty, Task, get_event_loop
19
+ from asyncio.events import _get_running_loop
20
+ from functools import wraps
21
+ from heapq import heappop, heappush, heappushpop
22
+ from logging import getLogger
23
+ from weakref import WeakValueDictionary, proxy, ref
24
+
25
+ from a_sync._smart import SmartFuture, create_future
26
+ from a_sync._smart import _Key as _SmartKey
27
+ from a_sync._typing import *
28
+ from a_sync.asyncio import create_task, igather
29
+ from a_sync.functools import cached_property_unsafe
30
+
31
+ logger = getLogger(__name__)
32
+ log_debug = logger.debug
33
+
34
+ if sys.version_info < (3, 9):
35
+
36
+ class _Queue(asyncio.Queue, Generic[T]):
37
+ __slots__ = (
38
+ "_queue",
39
+ "_maxsize",
40
+ "_loop",
41
+ "_getters",
42
+ "_putters",
43
+ "_unfinished_tasks",
44
+ "_finished",
45
+ )
46
+
47
+ else:
48
+
49
+ class _Queue(asyncio.Queue[T]):
50
+ __slots__ = (
51
+ "_queue",
52
+ "_maxsize",
53
+ "_getters",
54
+ "_putters",
55
+ "_unfinished_tasks",
56
+ "_finished",
57
+ )
58
+
59
+
60
+ class Queue(_Queue[T]):
61
+ """
62
+ A generic asynchronous queue that extends the functionality of `asyncio.Queue`.
63
+
64
+ This implementation supports retrieving multiple items at once and handling
65
+ task processing in both FIFO and LIFO order. It provides enhanced type hinting
66
+ support and additional methods for bulk operations.
67
+
68
+ Inherits from:
69
+ - :class:`~asyncio.Queue`
70
+
71
+ Example:
72
+ >>> queue = Queue()
73
+ >>> await queue.put(item='task1')
74
+ >>> await queue.put(item='task2')
75
+ >>> result = await queue.get()
76
+ >>> print(result)
77
+ task1
78
+ >>> all_tasks = await queue.get_all()
79
+ >>> print(all_tasks)
80
+ ['task2']
81
+ """
82
+
83
+ def __bool__(self) -> Literal[True]:
84
+ """A Queue will always exist, even without items."""
85
+ return True
86
+
87
+ def __len__(self) -> int:
88
+ """Returns the number of items currently in the queue."""
89
+ return len(self._queue)
90
+
91
+ async def get(self) -> T:
92
+ """
93
+ Asynchronously retrieves and removes the next item from the queue.
94
+
95
+ If the queue is empty, this method will block until an item is available.
96
+
97
+ Example:
98
+ >>> result = await queue.get()
99
+ >>> print(result)
100
+ """
101
+ return await _Queue.get(self)
102
+
103
+ def get_nowait(self) -> T:
104
+ """
105
+ Retrieves and removes the next item from the queue without blocking.
106
+
107
+ This method does not wait for an item to be available and will raise
108
+ an exception if the queue is empty.
109
+
110
+ Raises:
111
+ :exc:`~asyncio.QueueEmpty`: If the queue is empty.
112
+
113
+ Example:
114
+ >>> result = queue.get_nowait()
115
+ >>> print(result)
116
+ """
117
+ return _Queue.get_nowait(self)
118
+
119
+ async def put(self, item: T) -> None:
120
+ """
121
+ Asynchronously adds an item to the queue.
122
+
123
+ If the queue is full, this method will block until space is available.
124
+
125
+ Args:
126
+ item: The item to add to the queue.
127
+
128
+ Example:
129
+ >>> await queue.put(item='task')
130
+ """
131
+ await _Queue.put(self, item)
132
+
133
+ def put_nowait(self, item: T) -> None:
134
+ """
135
+ Adds an item to the queue without blocking.
136
+
137
+ This method does not wait for space to be available and will raise
138
+ an exception if the queue is full.
139
+
140
+ Args:
141
+ item: The item to add to the queue.
142
+
143
+ Raises:
144
+ :exc:`~asyncio.QueueFull`: If the queue is full.
145
+
146
+ Example:
147
+ >>> queue.put_nowait(item='task')
148
+ """
149
+ return _Queue.put_nowait(self, item)
150
+
151
+ async def get_all(self) -> List[T]:
152
+ """
153
+ Asynchronously retrieves and removes all available items from the queue.
154
+
155
+ If the queue is empty, this method will wait until at least one item
156
+ is available before returning.
157
+
158
+ Example:
159
+ >>> tasks = await queue.get_all()
160
+ >>> print(tasks)
161
+ """
162
+ try:
163
+ return self.get_all_nowait()
164
+ except QueueEmpty:
165
+ return [await self.get()]
166
+
167
+ def get_all_nowait(self) -> List[T]:
168
+ """
169
+ Retrieves and removes all available items from the queue without waiting.
170
+
171
+ This method does not wait for items to be available and will raise
172
+ an exception if the queue is empty.
173
+
174
+ Raises:
175
+ :exc:`~asyncio.QueueEmpty`: If the queue is empty.
176
+
177
+ Example:
178
+ >>> tasks = queue.get_all_nowait()
179
+ >>> print(tasks)
180
+ """
181
+ get_nowait = self.get_nowait
182
+ values: List[T] = []
183
+ append = values.append
184
+
185
+ while True:
186
+ try:
187
+ append(get_nowait())
188
+ except QueueEmpty as e:
189
+ if not values:
190
+ raise QueueEmpty from e
191
+ return values
192
+
193
+ async def get_multi(self, i: int, can_return_less: bool = False) -> List[T]:
194
+ """
195
+ Asynchronously retrieves up to `i` items from the queue.
196
+
197
+ Args:
198
+ i: The number of items to retrieve.
199
+ can_return_less: If True, may return fewer than `i` items if queue is emptied.
200
+
201
+ Raises:
202
+ :exc:`~asyncio.QueueEmpty`: If no items are available and fewer items cannot be returned.
203
+
204
+ Example:
205
+ >>> tasks = await queue.get_multi(i=2, can_return_less=True)
206
+ >>> print(tasks)
207
+ """
208
+ _validate_args(i, can_return_less)
209
+ get_next = self.get
210
+ get_multi = self.get_multi_nowait
211
+
212
+ items = []
213
+ extend = items.extend
214
+ while len(items) < i and not can_return_less:
215
+ try:
216
+ extend(get_multi(i - len(items), can_return_less=True))
217
+ except QueueEmpty:
218
+ items = [await get_next()]
219
+ return items
220
+
221
+ def get_multi_nowait(self, i: int, can_return_less: bool = False) -> List[T]:
222
+ """
223
+ Retrieves up to `i` items from the queue without waiting.
224
+
225
+ Args:
226
+ i: The number of items to retrieve.
227
+ can_return_less: If True, may return fewer than `i` items if queue is emptied.
228
+
229
+ Raises:
230
+ :exc:`~asyncio.QueueEmpty`: If no items are available and fewer items cannot be returned.
231
+
232
+ Example:
233
+ >>> tasks = queue.get_multi_nowait(i=3, can_return_less=True)
234
+ >>> print(tasks)
235
+ """
236
+ _validate_args(i, can_return_less)
237
+
238
+ get_nowait = self.get_nowait
239
+
240
+ items = []
241
+ append = items.append
242
+ for _ in range(i):
243
+ try:
244
+ append(get_nowait())
245
+ except QueueEmpty:
246
+ if items and can_return_less:
247
+ return items
248
+ # put these back in the queue since we didn't return them
249
+ put_nowait = self.put_nowait
250
+ for value in items:
251
+ put_nowait(value)
252
+ raise QueueEmpty from None
253
+ return items
254
+
255
+
256
+ def log_broken(func: Callable[[Any], NoReturn]) -> Callable[[Any], NoReturn]:
257
+ @wraps(func)
258
+ async def __worker_exc_wrap(self: "ProcessingQueue"):
259
+ try:
260
+ return await func(self)
261
+ except Exception as e:
262
+ logger.error("%s is broken!!!", self)
263
+ logger.exception(e)
264
+ raise
265
+
266
+ return __worker_exc_wrap
267
+
268
+
269
+ _init = asyncio.Queue.__init__
270
+ _put_nowait = asyncio.Queue.put_nowait
271
+ _loop_kwarg_deprecated = sys.version_info >= (3, 10)
272
+
273
+
274
+ class ProcessingQueue(_Queue[Tuple[P, "Future[V]"]], Generic[P, V]):
275
+ """
276
+ A queue designed for processing tasks asynchronously with multiple workers.
277
+
278
+ Each item in the queue is processed by a worker, and tasks can return results
279
+ via asynchronous futures. This queue is ideal for scenarios where tasks need
280
+ to be processed concurrently with a fixed number of workers.
281
+
282
+ Example:
283
+ >>> async def process_task(data): return data.upper()
284
+ >>> queue = ProcessingQueue(func=process_task, num_workers=5)
285
+ >>> fut = await queue.put(item='task')
286
+ >>> print(await fut)
287
+ TASK
288
+ """
289
+
290
+ _closed: bool = False
291
+ """Indicates whether the queue is closed."""
292
+
293
+ __slots__ = "func", "num_workers"
294
+
295
+ def __init__(
296
+ self,
297
+ func: Callable[P, Awaitable[V]],
298
+ num_workers: int,
299
+ *,
300
+ return_data: bool = True,
301
+ name: str = "",
302
+ loop: Optional[AbstractEventLoop] = None,
303
+ ) -> None:
304
+ """
305
+ Initializes a processing queue with the given worker function and worker count.
306
+
307
+ Args:
308
+ func: The task function to process.
309
+ num_workers: Number of workers to process tasks.
310
+ return_data: Whether tasks should return data via futures. Defaults to True.
311
+ name: Name of the queue. Defaults to an empty string.
312
+ loop: Optional event loop for the queue.
313
+
314
+ Example:
315
+ >>> queue = ProcessingQueue(func=my_task_func, num_workers=3, name='myqueue')
316
+ """
317
+ if not _loop_kwarg_deprecated:
318
+ _init(self, loop=loop)
319
+ elif loop:
320
+ raise NotImplementedError(
321
+ f"You cannot pass a value for `loop` in python {sys.version_info}"
322
+ )
323
+ else:
324
+ _init(self)
325
+
326
+ self.func = func
327
+ """The function that each worker will process."""
328
+
329
+ self.num_workers = num_workers
330
+ """The number of worker tasks for processing."""
331
+
332
+ self._name = name
333
+ """Optional name for the queue."""
334
+
335
+ self._no_futs = not return_data
336
+ """Indicates whether tasks will return data via futures."""
337
+
338
+ # NOTE: asyncio defines both this and __str__
339
+ def __repr__(self) -> str:
340
+ """
341
+ Provides a detailed string representation of the queue.
342
+
343
+ Example:
344
+ >>> print(queue)
345
+ """
346
+ repr_string = f"<{type(self).__name__} at {hex(id(self))}"
347
+ if self._name:
348
+ repr_string += f" name={self._name}"
349
+ repr_string += f" func={self.func} num_workers={self.num_workers}"
350
+ if self._unfinished_tasks:
351
+ repr_string += f" pending={self._unfinished_tasks}"
352
+ return f"{repr_string}>"
353
+
354
+ # NOTE: asyncio defines both this and __repr__
355
+ def __str__(self) -> str:
356
+ """
357
+ Provides a string representation of the queue.
358
+
359
+ Example:
360
+ >>> print(queue)
361
+ """
362
+ repr_string = f"<{type(self).__name__}"
363
+ if self._name:
364
+ repr_string += f" name={self._name}"
365
+ repr_string += f" func={self.func} num_workers={self.num_workers}"
366
+ if self._unfinished_tasks:
367
+ repr_string += f" pending={self._unfinished_tasks}"
368
+ return f"{repr_string}>"
369
+
370
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> "Future[V]":
371
+ """
372
+ Submits a task to the queue.
373
+
374
+ Example:
375
+ >>> fut = queue(*args, **kwargs)
376
+ >>> print(fut)
377
+ """
378
+ return self.put_nowait(*args, **kwargs)
379
+
380
+ def __del__(self) -> None:
381
+ """
382
+ Handles the deletion of the queue, ensuring tasks are handled.
383
+ """
384
+ if self._closed:
385
+ return
386
+ if self._unfinished_tasks > 0:
387
+ context = {
388
+ "message": f"{self} was destroyed but has work pending!",
389
+ }
390
+ if loop := _get_running_loop():
391
+ loop.call_exception_handler(context)
392
+
393
+ @property
394
+ def name(self) -> str:
395
+ """
396
+ Returns the name of the queue, or its representation.
397
+
398
+ Example:
399
+ >>> print(queue.name)
400
+ """
401
+ return self._name or repr(self)
402
+
403
+ def close(self) -> None:
404
+ """
405
+ Closes the queue, preventing further task submissions.
406
+
407
+ Example:
408
+ >>> queue.close()
409
+ """
410
+ self._closed = True
411
+
412
+ async def put(self, *args: P.args, **kwargs: P.kwargs) -> "Future[V]":
413
+ # sourcery skip: use-contextlib-suppress
414
+ """
415
+ Asynchronously submits a task to the queue.
416
+
417
+ Args:
418
+ args: Positional arguments for the task.
419
+ kwargs: Keyword arguments for the task.
420
+
421
+ Returns:
422
+ The future result of the task.
423
+
424
+ Example:
425
+ >>> fut = await queue.put(item='task')
426
+ >>> print(await fut)
427
+ """
428
+ while self.full():
429
+ putter = self._get_loop().create_future()
430
+ self._putters.append(putter)
431
+ try:
432
+ await putter
433
+ except:
434
+ putter.cancel() # Just in case putter is not done yet.
435
+ try:
436
+ # Clean self._putters from canceled putters.
437
+ self._putters.remove(putter)
438
+ except ValueError:
439
+ # The putter could be removed from self._putters by a
440
+ # previous get_nowait call.
441
+ pass
442
+ if not self.full() and not putter.cancelled():
443
+ # We were woken up by get_nowait(), but can't take
444
+ # the call. Wake up the next in line.
445
+ self._wakeup_next(self._putters)
446
+ raise
447
+ return self.put_nowait(*args, **kwargs)
448
+
449
+ def put_nowait(self, *args: P.args, **kwargs: P.kwargs) -> "Future[V]":
450
+ """
451
+ Immediately submits a task to the queue without waiting.
452
+
453
+ Args:
454
+ args: Positional arguments for the task.
455
+ kwargs: Keyword arguments for the task.
456
+
457
+ Returns:
458
+ The future result of the task.
459
+
460
+ Example:
461
+ >>> fut = queue.put_nowait(item='task')
462
+ >>> print(await fut)
463
+ """
464
+ self._ensure_workers()
465
+ if self._no_futs:
466
+ return _put_nowait(self, (args, kwargs))
467
+ fut = Future(loop=self._workers._loop)
468
+ _put_nowait(self, (args, kwargs, proxy(fut)))
469
+ return fut
470
+
471
+ def _ensure_workers(self) -> None:
472
+ """Ensures that the worker tasks are running."""
473
+ if self._closed:
474
+ raise RuntimeError(f"{type(self).__name__} is closed: ", self) from None
475
+ if self._workers.done():
476
+ worker_subtasks: List["Task[NoReturn]"] = self._workers._workers
477
+ for worker in worker_subtasks:
478
+ if worker.done(): # its only done if its broken
479
+ exc = worker.exception()
480
+ # re-raise with clean traceback
481
+ raise exc.with_traceback(exc.__traceback__) from exc.__cause__
482
+ # this should never be reached, but just in case
483
+ exc = self._workers.exception()
484
+ # re-raise with clean traceback
485
+ raise exc.with_traceback(exc.__traceback__) from exc.__cause__
486
+
487
+ @cached_property_unsafe
488
+ def _workers(self) -> "Task[NoReturn]":
489
+ """Creates and manages the worker tasks for the queue."""
490
+ log_debug("starting worker task for %s", self)
491
+ name = str(self.name)
492
+ workers = tuple(
493
+ create_task(
494
+ coro=self._worker_coro(),
495
+ name=f"{name} [Task-{i}]",
496
+ log_destroy_pending=False,
497
+ )
498
+ for i in range(self.num_workers)
499
+ )
500
+ task = create_task(
501
+ igather(workers),
502
+ name=f"{name} worker main Task",
503
+ log_destroy_pending=False,
504
+ )
505
+ task._workers = workers
506
+ return task
507
+
508
+ @log_broken
509
+ async def _worker_coro(self) -> NoReturn:
510
+ """
511
+ The coroutine executed by worker tasks to process the queue.
512
+ """
513
+ get_next_job = self.get
514
+ func = self.func
515
+ task_done = self.task_done
516
+
517
+ args: P.args
518
+ kwargs: P.kwargs
519
+ if self._no_futs:
520
+ while True:
521
+ try:
522
+ args, kwargs = await get_next_job()
523
+ await func(*args, **kwargs)
524
+ except Exception as e:
525
+ logger.error("%s in worker for %s!", type(e).__name__, self)
526
+ logger.exception(e)
527
+ task_done()
528
+ else:
529
+ fut: Future[V]
530
+ while True:
531
+ try:
532
+ args, kwargs, fut = await get_next_job()
533
+ except RuntimeError as e:
534
+ if _check_loop_is_closed(self, e):
535
+ return
536
+ raise
537
+
538
+ if fut is None or fut.cancelled():
539
+ # the weakref was already cleaned up, we don't need to process this item
540
+ task_done()
541
+ continue
542
+
543
+ # TODO: implement some callback to handle cancellation
544
+
545
+ try:
546
+ result = await func(*args, **kwargs)
547
+ except Exception as e:
548
+ try:
549
+ fut.set_exception(e)
550
+ except InvalidStateError:
551
+ _log_invalid_state_err("exception", func, fut, e)
552
+ else:
553
+ try:
554
+ fut.set_result(result)
555
+ except InvalidStateError:
556
+ _log_invalid_state_err("result", func, fut, result)
557
+ task_done()
558
+
559
+
560
+ def _validate_args(i: int, can_return_less: bool) -> None:
561
+ """
562
+ Validates the arguments for methods that retrieve multiple items from the queue.
563
+
564
+ Args:
565
+ i: The number of items to retrieve.
566
+ can_return_less: Whether the method is allowed to return fewer than `i` items.
567
+
568
+ Raises:
569
+ :exc:`~TypeError`: If `i` is not an integer or `can_return_less` is not a boolean.
570
+ :exc:`~ValueError`: If `i` is not greater than 1.
571
+
572
+ Example:
573
+ >>> _validate_args(i=2, can_return_less=False)
574
+ """
575
+ if not isinstance(i, int):
576
+ raise TypeError(f"`i` must be an integer greater than 1. You passed {i}")
577
+ if not isinstance(can_return_less, bool):
578
+ raise TypeError(f"`can_return_less` must be boolean. You passed {can_return_less}")
579
+ if i <= 1:
580
+ raise ValueError(f"`i` must be an integer greater than 1. You passed {i}")
581
+
582
+
583
+ class _SmartFutureRef(ref, Generic[T]):
584
+ """
585
+ Weak reference for :class:`~SmartFuture` objects used in priority queues.
586
+
587
+ See Also:
588
+ :class:`~SmartFuture`
589
+ """
590
+
591
+ def __lt__(self, other: "_SmartFutureRef[T]") -> bool:
592
+ """
593
+ Compares two weak references to SmartFuture objects for ordering.
594
+
595
+ This comparison is used in priority queues to determine the order of processing. A SmartFuture
596
+ reference is considered less than another if it has more waiters or if it has been garbage collected.
597
+
598
+ Args:
599
+ other: The other SmartFuture reference to compare with.
600
+
601
+ Returns:
602
+ bool: True if this reference is less than the other, False otherwise.
603
+
604
+ Example:
605
+ >>> ref1 = _SmartFutureRef(fut1)
606
+ >>> ref2 = _SmartFutureRef(fut2)
607
+ >>> print(ref1 < ref2)
608
+ """
609
+ strong_self = self()
610
+ if strong_self is None:
611
+ return True
612
+ strong_other = other()
613
+ if strong_other is None:
614
+ return False
615
+ return strong_self < strong_other
616
+
617
+
618
+ class _PriorityQueueMixin(Generic[T]):
619
+ """
620
+ Mixin for creating priority queue functionality with support for custom comparison.
621
+
622
+ See Also:
623
+ :class:`~asyncio.PriorityQueue`
624
+ """
625
+
626
+ def _init(self, maxsize):
627
+ """
628
+ Initializes the priority queue.
629
+
630
+ Example:
631
+ >>> queue._init(maxsize=10)
632
+ """
633
+ self._queue: List[T] = []
634
+
635
+ def _put(self, item, heappush=heappush):
636
+ """
637
+ Adds an item to the priority queue based on its priority.
638
+
639
+ Example:
640
+ >>> queue._put(item='task')
641
+ """
642
+ heappush(self._queue, item)
643
+
644
+ def _get(self, heappop=heappop):
645
+ """
646
+ Retrieves the highest priority item from the queue.
647
+
648
+ Example:
649
+ >>> task = queue._get()
650
+ >>> print(task)
651
+ """
652
+ return heappop(self._queue)
653
+
654
+
655
+ class PriorityProcessingQueue(_PriorityQueueMixin[T], ProcessingQueue[T, V]):
656
+ """
657
+ A priority-based processing queue where tasks are processed based on priority.
658
+
659
+ This queue allows tasks to be added with a specified priority, ensuring that
660
+ higher priority tasks are processed before lower priority ones. It is ideal
661
+ for scenarios where task prioritization is crucial.
662
+
663
+ Example:
664
+ >>> async def process_task(data): return data.upper()
665
+ >>> queue = PriorityProcessingQueue(func=process_task, num_workers=5)
666
+ >>> fut = await queue.put(priority=1, item='task')
667
+ >>> print(await fut)
668
+ TASK
669
+
670
+ See Also:
671
+ :class:`~ProcessingQueue`
672
+ """
673
+
674
+ async def put(self, priority: Any, *args: P.args, **kwargs: P.kwargs) -> "Future[V]":
675
+ # sourcery skip: use-contextlib-suppress
676
+ """
677
+ Asynchronously adds a task with priority to the queue.
678
+
679
+ Args:
680
+ priority: The priority of the task.
681
+ args: Positional arguments for the task.
682
+ kwargs: Keyword arguments for the task.
683
+
684
+ Returns:
685
+ The future representing the result of the task.
686
+
687
+ Example:
688
+ >>> fut = await queue.put(priority=1, item='task')
689
+ >>> print(await fut)
690
+ """
691
+ while self.full():
692
+ putter = self._get_loop().create_future()
693
+ self._putters.append(putter)
694
+ try:
695
+ await putter
696
+ except:
697
+ putter.cancel() # Just in case putter is not done yet.
698
+ try:
699
+ # Clean self._putters from canceled putters.
700
+ self._putters.remove(putter)
701
+ except ValueError:
702
+ # The putter could be removed from self._putters by a
703
+ # previous get_nowait call.
704
+ pass
705
+ if not self.full() and not putter.cancelled():
706
+ # We were woken up by get_nowait(), but can't take
707
+ # the call. Wake up the next in line.
708
+ self._wakeup_next(self._putters)
709
+ raise
710
+
711
+ return self.put_nowait(priority, *args, **kwargs)
712
+
713
+ def put_nowait(self, priority: Any, *args: P.args, **kwargs: P.kwargs) -> "Future[V]":
714
+ """
715
+ Immediately adds a task with priority to the queue without waiting.
716
+
717
+ Args:
718
+ priority: The priority of the task.
719
+ args: Positional arguments for the task.
720
+ kwargs: Keyword arguments for the task.
721
+
722
+ Returns:
723
+ The future representing the result of the task.
724
+
725
+ Example:
726
+ >>> fut = queue.put_nowait(priority=1, item='task')
727
+ >>> print(await fut)
728
+ """
729
+ self._ensure_workers()
730
+ fut = Future(loop=self._workers._loop)
731
+ _Queue.put_nowait(self, (priority, args, kwargs, fut))
732
+ return fut
733
+
734
+ def _get(self, heappop=heappop):
735
+ """
736
+ Retrieves the highest priority task from the queue.
737
+
738
+ Returns:
739
+ The priority, task arguments, keyword arguments, and future of the task.
740
+
741
+ Example:
742
+ >>> task = queue._get()
743
+ >>> print(task)
744
+ """
745
+ # For readability, what we're really doing is this:
746
+ # priority, args, kwargs, fut = heappop(self._queue)
747
+ # return args, kwargs, fut
748
+ return heappop(self._queue)[1:]
749
+
750
+
751
+ class _VariablePriorityQueueMixin(_PriorityQueueMixin[T]):
752
+ """
753
+ Mixin for priority queues where task priorities can be updated dynamically.
754
+
755
+ See Also:
756
+ :class:`~_PriorityQueueMixin`
757
+ """
758
+
759
+ def _get(self, heappop=heappop):
760
+ """
761
+ Resorts the priority queue to consider any changes in priorities and retrieves the task with the highest updated priority.
762
+
763
+ Args:
764
+ heappop: Function to pop the highest priority task.
765
+
766
+ Returns:
767
+ The highest priority task in the queue.
768
+
769
+ Example:
770
+ >>> task = queue._get()
771
+ >>> print(task)
772
+ """
773
+ # NOTE: Since waiter priorities can change, heappop might not return the job with the
774
+ # most waiters if `self._queue` is not currently in order, but we can use `heappushpop`,
775
+ # to ensure we get the job with the most waiters.
776
+ return heappushpop(queue := self._queue, heappop(queue))
777
+
778
+ def _get_key(self, *args, **kwargs) -> _SmartKey:
779
+ """
780
+ Generates a unique key for task identification based on arguments.
781
+
782
+ Args:
783
+ args: Positional arguments for the task.
784
+ kwargs: Keyword arguments for the task.
785
+
786
+ Returns:
787
+ The generated key for the task.
788
+
789
+ Example:
790
+ >>> key = queue._get_key(*args, **kwargs)
791
+ >>> print(key)
792
+ """
793
+ return args, tuple(sorted(kwargs.items()))
794
+
795
+
796
+ class VariablePriorityQueue(_VariablePriorityQueueMixin[T], asyncio.PriorityQueue):
797
+ """
798
+ A :class:`~asyncio.PriorityQueue` subclass that allows priorities to be updated (or computed) on the fly.
799
+
800
+ This queue supports dynamic priority updates, making it suitable for tasks
801
+ where priorities may change over time. It ensures that tasks are processed
802
+ based on the most current priority.
803
+
804
+ Example:
805
+ >>> queue = VariablePriorityQueue()
806
+ >>> queue.put_nowait((1, 'task1'))
807
+ >>> queue.put_nowait((2, 'task2'))
808
+ >>> task = queue.get_nowait()
809
+ >>> print(task)
810
+
811
+ See Also:
812
+ :class:`~asyncio.PriorityQueue`
813
+ """
814
+
815
+
816
+ class SmartProcessingQueue(_VariablePriorityQueueMixin[T], ProcessingQueue[Concatenate[T, P], V]):
817
+ """
818
+ A processing queue that will execute jobs with the most waiters first, supporting dynamic priorities.
819
+
820
+ This queue is designed to handle tasks with dynamic priorities, ensuring that
821
+ tasks with the most waiters are prioritized. It is ideal for scenarios where
822
+ task execution order is influenced by the number of waiters.
823
+
824
+ Example:
825
+ >>> async def process_task(data): return data.upper()
826
+ >>> queue = SmartProcessingQueue(func=process_task, num_workers=5)
827
+ >>> fut = await queue.put(item='task')
828
+ >>> print(await fut)
829
+ TASK
830
+
831
+ See Also:
832
+ :class:`~ProcessingQueue`
833
+ """
834
+
835
+ _no_futs = False
836
+ """Whether smart futures are used."""
837
+
838
+ _futs: "WeakValueDictionary[_SmartKey[T], SmartFuture[T]]"
839
+ """
840
+ Weak reference dictionary for managing smart futures.
841
+ """
842
+
843
+ def __init__(
844
+ self,
845
+ func: Callable[Concatenate[T, P], Awaitable[V]],
846
+ num_workers: int,
847
+ *,
848
+ name: str = "",
849
+ loop: Optional[AbstractEventLoop] = None,
850
+ ) -> None:
851
+ """
852
+ Initializes a smart processing queue with the given worker function.
853
+
854
+ Args:
855
+ func: The worker function.
856
+ num_workers: Number of worker tasks.
857
+ name: Optional name for the queue.
858
+ loop: Optional event loop.
859
+
860
+ Example:
861
+ >>> queue = SmartProcessingQueue(func=my_task_func, num_workers=3, name='smart_queue')
862
+ """
863
+ if not name:
864
+ unwrapped = func
865
+ while callable(getattr(unwrapped, "__wrapped__", None)):
866
+ unwrapped = unwrapped.__wrapped__
867
+ name = f"{unwrapped.__module__}.{unwrapped.__qualname__}"
868
+ ProcessingQueue.__init__(self, func, num_workers, return_data=True, name=name, loop=loop)
869
+ self._futs = WeakValueDictionary()
870
+
871
+ async def put(self, *args: P.args, **kwargs: P.kwargs) -> SmartFuture[V]:
872
+ # sourcery skip: use-contextlib-suppress
873
+ """
874
+ Asynchronously adds a task with smart future handling to the queue.
875
+
876
+ Args:
877
+ args: Positional arguments for the task.
878
+ kwargs: Keyword arguments for the task.
879
+
880
+ Returns:
881
+ The future representing the task's result.
882
+
883
+ Example:
884
+ >>> fut = await queue.put(item='task')
885
+ >>> print(await fut)
886
+ """
887
+ while self.full():
888
+ putter = self._loop.create_future()
889
+ self._putters.append(putter)
890
+ try:
891
+ await putter
892
+ except:
893
+ putter.cancel() # Just in case putter is not done yet.
894
+ try:
895
+ # Clean self._putters from canceled putters.
896
+ self._putters.remove(putter)
897
+ except ValueError:
898
+ # The putter could be removed from self._putters by a
899
+ # previous get_nowait call.
900
+ pass
901
+ if not self.full() and not putter.cancelled():
902
+ # We were woken up by get_nowait(), but can't take
903
+ # the call. Wake up the next in line.
904
+ self._wakeup_next(self._putters)
905
+ raise
906
+ return self.put_nowait(*args, **kwargs)
907
+
908
+ def put_nowait(self, *args: P.args, **kwargs: P.kwargs) -> SmartFuture[V]:
909
+ """
910
+ Immediately adds a task with smart future handling to the queue without waiting.
911
+
912
+ Args:
913
+ args: Positional arguments for the task.
914
+ kwargs: Keyword arguments for the task.
915
+
916
+ Returns:
917
+ The future representing the task's result.
918
+
919
+ Example:
920
+ >>> fut = queue.put_nowait(item='task')
921
+ >>> print(await fut)
922
+ """
923
+ self._ensure_workers()
924
+ key = self._get_key(*args, **kwargs)
925
+ if fut := self._futs.get(key, None):
926
+ return fut
927
+ fut = SmartFuture(queue=self, key=key, loop=self._loop)
928
+ self._futs[key] = fut
929
+ Queue.put_nowait(self, (_SmartFutureRef(fut), args, kwargs))
930
+ return fut
931
+
932
+ def _get(self):
933
+ """
934
+ Retrieves the task with the highest priority from the queue.
935
+
936
+ Returns:
937
+ The priority, task arguments, keyword arguments, and future of the task.
938
+
939
+ Example:
940
+ >>> task = queue._get()
941
+ >>> print(task)
942
+ """
943
+ fut, args, kwargs = _VariablePriorityQueueMixin._get(self)
944
+ return args, kwargs, fut()
945
+
946
+ async def _worker_coro(self) -> NoReturn:
947
+ """
948
+ Worker coroutine responsible for processing tasks in the queue.
949
+
950
+ Retrieves tasks, executes them, and sets the results or exceptions for the futures.
951
+
952
+ Raises:
953
+ Any: Exceptions raised during task processing are logged.
954
+
955
+ Example:
956
+ >>> await queue._worker_coro()
957
+ """
958
+ get_next_job = self.get
959
+ func = self.func
960
+ task_done = self.task_done
961
+ log = log_debug
962
+
963
+ args: P.args
964
+ kwargs: P.kwargs
965
+ fut: SmartFuture[V]
966
+ try:
967
+ while True:
968
+ try:
969
+ args, kwargs, fut = await get_next_job()
970
+ except RuntimeError as e:
971
+ if _check_loop_is_closed(self, e):
972
+ return
973
+ raise
974
+
975
+ if fut is None or fut.cancelled():
976
+ # the weakref was already cleaned up, we don't need to process this item
977
+ task_done()
978
+ continue
979
+
980
+ log("processing %s", fut)
981
+
982
+ # TODO: implement some callback to handle cancellation
983
+
984
+ try:
985
+ result = await func(*args, **kwargs)
986
+ except Exception as e:
987
+ log("%s: %s", type(e).__name__, e)
988
+ try:
989
+ fut.set_exception(e)
990
+ except InvalidStateError:
991
+ _log_invalid_state_err("exception", func, fut, e)
992
+ else:
993
+ try:
994
+ fut.set_result(result)
995
+ except InvalidStateError:
996
+ _log_invalid_state_err("result", func, fut, result)
997
+ task_done()
998
+
999
+ except Exception as e:
1000
+ logger.error("%s is broken!!!", self)
1001
+ logger.exception(e)
1002
+ raise
1003
+
1004
+
1005
+ def _log_invalid_state_err(
1006
+ typ: Literal["result", "exception"], func: Callable, fut: Future, value: Any
1007
+ ) -> None:
1008
+ logger.error(
1009
+ "cannot set %s for %s %s: %s",
1010
+ typ,
1011
+ func.__name__,
1012
+ fut,
1013
+ value,
1014
+ )
1015
+
1016
+
1017
+ def _check_loop_is_closed(queue: Queue, e: Exception) -> bool:
1018
+ if "Event loop is closed" not in str(e):
1019
+ return False
1020
+ if queue._unfinished_tasks:
1021
+ logger.error(
1022
+ "Event loop is closed. Closing %s with %s unfinished tasks",
1023
+ queue,
1024
+ queue._unfinished_tasks,
1025
+ )
1026
+ return True