ez-a-sync 0.32.29__cp310-cp310-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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-win32.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
@@ -0,0 +1,597 @@
1
+ # cython: boundscheck=False
2
+ """
3
+ This module provides priority-based semaphore implementations. These semaphores allow
4
+ waiters to be assigned priorities, ensuring that higher priority waiters are
5
+ processed before lower priority ones.
6
+ """
7
+
8
+ import asyncio
9
+ import collections
10
+ import heapq
11
+ from logging import getLogger
12
+ from typing import List, Literal, Optional, Protocol, Set, Type, TypeVar
13
+
14
+ from cpython.unicode cimport PyUnicode_CompareWithASCIIString
15
+
16
+ from a_sync.primitives.locks.semaphore cimport Semaphore
17
+
18
+
19
+ # cdef collections
20
+ cdef object deque = collections.deque
21
+ del collections
22
+
23
+ # cdef heapq
24
+ cdef object heappush = heapq.heappush
25
+ cdef object heappop = heapq.heappop
26
+ del heapq
27
+
28
+ # cdef logging
29
+ cdef public object logger = getLogger(__name__)
30
+ cdef object c_logger = logger
31
+ cdef object DEBUG = 10
32
+ cdef object _logger_log = logger._log
33
+ cdef object _logger_is_enabled = logger.isEnabledFor
34
+ del getLogger
35
+
36
+ class Priority(Protocol):
37
+ def __lt__(self, other) -> bool: ...
38
+
39
+
40
+ PT = TypeVar("PT", bound=Priority)
41
+
42
+ CM = TypeVar("CM", bound="_AbstractPrioritySemaphoreContextManager[Priority]")
43
+
44
+
45
+ cdef class _AbstractPrioritySemaphore(Semaphore):
46
+ """
47
+ A semaphore that allows prioritization of waiters.
48
+
49
+ This semaphore manages waiters with associated priorities, ensuring that waiters with higher
50
+ priorities are processed before those with lower priorities. Subclasses must define the
51
+ `_top_priority` attribute to specify the default top priority behavior.
52
+
53
+ The `_context_manager_class` attribute should specify the class used for managing semaphore contexts.
54
+
55
+ See Also:
56
+ :class:`PrioritySemaphore` for an implementation using numeric priorities.
57
+ """
58
+
59
+ def __cinit__(self) -> None:
60
+ self._context_managers = {}
61
+ """A dictionary mapping priorities to their context managers."""
62
+
63
+ self._Semaphore__waiters = []
64
+ """A heap queue of context managers, sorted by priority."""
65
+
66
+ # NOTE: This should (hopefully) be temporary
67
+ self._potential_lost_waiters = set()
68
+ """A list of futures representing waiters that might have been lost."""
69
+
70
+ def __init__(
71
+ self,
72
+ context_manager_class: Type[_AbstractPrioritySemaphoreContextManager],
73
+ top_priority: object,
74
+ value: int = 1,
75
+ *,
76
+ name: Optional[str] = None,
77
+ ) -> None:
78
+ """Initializes the priority semaphore.
79
+
80
+ Args:
81
+ value: The initial capacity of the semaphore.
82
+ name: An optional name for the semaphore, used for debugging.
83
+
84
+ Examples:
85
+ >>> semaphore = _AbstractPrioritySemaphore(5, name="test_semaphore")
86
+ """
87
+ # context manager class is some temporary hacky shit, just ignore this
88
+ Semaphore.__init__(self, value, name=name)
89
+
90
+ self._capacity = value
91
+ """The initial capacity of the semaphore."""
92
+
93
+ self._top_priority = top_priority
94
+ self._top_priority_manager = context_manager_class(
95
+ self, top_priority, name=self.name
96
+ )
97
+ self._context_manager_class = context_manager_class
98
+
99
+ def __repr__(self) -> str:
100
+ """Returns a string representation of the semaphore."""
101
+ return f"<{self.__class__.__name__} name={self.name} capacity={self._capacity} value={self._Semaphore__value} waiters={self._count_waiters()}>"
102
+
103
+ async def __aenter__(self) -> None:
104
+ """Enters the semaphore context, acquiring it with the top priority.
105
+
106
+ This method is part of the asynchronous context management protocol.
107
+
108
+ Examples:
109
+ >>> semaphore = _AbstractPrioritySemaphore(5)
110
+ >>> async with semaphore:
111
+ ... await do_stuff()
112
+ """
113
+ await self._top_priority_manager.acquire()
114
+
115
+ async def __aexit__(self, *_) -> None:
116
+ """Exits the semaphore context, releasing it with the top priority.
117
+
118
+ This method is part of the asynchronous context management protocol.
119
+
120
+ Examples:
121
+ >>> semaphore = _AbstractPrioritySemaphore(5)
122
+ >>> async with semaphore:
123
+ ... await do_stuff()
124
+ """
125
+ self._top_priority_manager.release()
126
+
127
+ cpdef object acquire(self):
128
+ """Acquires the semaphore with the top priority.
129
+
130
+ This method overrides :meth:`Semaphore.acquire` to handle priority-based logic.
131
+
132
+ Examples:
133
+ >>> semaphore = _AbstractPrioritySemaphore(5)
134
+ >>> await semaphore.acquire()
135
+ """
136
+ return self._top_priority_manager.acquire()
137
+
138
+ def __getitem__(
139
+ self, priority: Optional[PT]
140
+ ) -> "_AbstractPrioritySemaphoreContextManager[PT]":
141
+ """Gets the context manager for a given priority.
142
+
143
+ Args:
144
+ priority: The priority for which to get the context manager. If None, uses the top priority.
145
+
146
+ Returns:
147
+ The context manager associated with the given priority.
148
+
149
+ Examples:
150
+ >>> semaphore = _AbstractPrioritySemaphore(5)
151
+ >>> context_manager = semaphore[priority]
152
+ """
153
+ return self.c_getitem(priority)
154
+
155
+ cdef _AbstractPrioritySemaphoreContextManager c_getitem(self, object priority):
156
+ cdef _AbstractPrioritySemaphoreContextManager context_manager
157
+ cdef dict[object, _AbstractPrioritySemaphoreContextManager] context_managers
158
+
159
+ if priority is None or priority == self._top_priority:
160
+ return self._top_priority_manager
161
+
162
+ context_managers = self._context_managers
163
+ context_manager = context_managers.get(priority)
164
+ if context_manager is None:
165
+ context_manager = self._context_manager_class(
166
+ self, priority, name=self.name
167
+ )
168
+ heappush(self._Semaphore__waiters, context_manager)
169
+ context_managers[priority] = context_manager
170
+ return context_manager
171
+
172
+ cpdef bint locked(self):
173
+ """Checks if the semaphore is locked.
174
+
175
+ Returns:
176
+ True if the semaphore cannot be acquired immediately, False otherwise.
177
+
178
+ Examples:
179
+ >>> semaphore = _AbstractPrioritySemaphore(5)
180
+ >>> semaphore.locked()
181
+ """
182
+ cdef list waiters
183
+ if self._Semaphore__value == 0:
184
+ return True
185
+ for cm in self._context_managers.values():
186
+ waiters = (<Semaphore>cm).__waiters
187
+ for waiter in waiters:
188
+ if _is_not_cancelled(waiter):
189
+ return True
190
+ return False
191
+
192
+ cdef dict[object, Py_ssize_t] _count_waiters(self):
193
+ """Counts the number of waiters for each priority.
194
+
195
+ Returns:
196
+ A dictionary mapping each priority to the number of waiters.
197
+
198
+ Examples:
199
+ >>> semaphore = _AbstractPrioritySemaphore(5)
200
+ >>> semaphore._count_waiters()
201
+ """
202
+ cdef _AbstractPrioritySemaphoreContextManager manager
203
+ cdef list[_AbstractPrioritySemaphoreContextManager] waiters = self._Semaphore__waiters
204
+ return {manager._priority: len(manager._Semaphore__waiters) for manager in sorted(waiters)}
205
+
206
+ cpdef void _wake_up_next(self):
207
+ """Wakes up the next waiter in line.
208
+
209
+ This method handles the waking of waiters based on priority. It includes an emergency
210
+ procedure to handle potential lost waiters, ensuring that no waiter is left indefinitely
211
+ waiting.
212
+
213
+ The emergency procedure is a temporary measure to address potential issues with lost waiters.
214
+
215
+ Examples:
216
+ >>> semaphore = _AbstractPrioritySemaphore(5)
217
+ >>> semaphore._wake_up_next()
218
+ """
219
+ cdef _AbstractPrioritySemaphoreContextManager manager
220
+ cdef object manager_waiters, get_next
221
+ cdef Py_ssize_t start_len, end_len
222
+ cdef set potential_lost_waiters
223
+ cdef bint woke_up, debug_logs
224
+
225
+ manager = self._top_priority_manager
226
+ manager_waiters = manager._Semaphore__waiters
227
+ potential_lost_waiters = self._potential_lost_waiters
228
+ debug_logs = _logger_is_enabled(DEBUG)
229
+ if manager_waiters:
230
+ get_next = manager_waiters.popleft
231
+ while manager_waiters:
232
+ waiter = get_next()
233
+ potential_lost_waiters.discard(waiter)
234
+ if _is_not_done(waiter):
235
+ waiter.set_result(None)
236
+ if debug_logs:
237
+ log_debug("woke up %s", (waiter, ))
238
+ return
239
+
240
+ while manager_waiters:
241
+ waiter = get_next()
242
+ potential_lost_waiters.discard(waiter)
243
+ if _is_not_done(waiter):
244
+ waiter.set_result(None)
245
+ if debug_logs:
246
+ log_debug("woke up %s", (waiter, ))
247
+ return
248
+
249
+ cdef list self_waiters = self._Semaphore__waiters
250
+ while self_waiters:
251
+ manager = heappop(self_waiters)
252
+ if len(manager) == 0:
253
+ # There are no more waiters, get rid of the empty manager
254
+ if debug_logs:
255
+ log_debug(
256
+ "manager %s has no more waiters, popping from %s",
257
+ (manager._repr_no_parent_(), self),
258
+ )
259
+ self._context_managers.pop(manager._priority)
260
+ continue
261
+
262
+ woke_up = False
263
+ start_len = len(manager)
264
+
265
+ manager_waiters = manager._Semaphore__waiters
266
+ if debug_logs:
267
+ log_debug("waking up next for %s", (manager._repr_no_parent_(), ))
268
+ if not manager_waiters:
269
+ log_debug("not manager._Semaphore__waiters", ())
270
+
271
+ if manager_waiters:
272
+ get_next = manager_waiters.popleft
273
+ waiter = get_next()
274
+ potential_lost_waiters.discard(waiter)
275
+ if _is_not_done(waiter):
276
+ waiter.set_result(None)
277
+ woke_up = True
278
+ if debug_logs:
279
+ log_debug("woke up %s", (waiter, ))
280
+ break
281
+
282
+ if not woke_up:
283
+ while manager_waiters:
284
+ waiter = get_next()
285
+ potential_lost_waiters.discard(waiter)
286
+ if _is_not_done(waiter):
287
+ waiter.set_result(None)
288
+ woke_up = True
289
+ if debug_logs:
290
+ log_debug("woke up %s", (waiter, ))
291
+ break
292
+
293
+ if not woke_up:
294
+ self._context_managers.pop(manager._priority)
295
+ continue
296
+
297
+ end_len = len(manager)
298
+
299
+ assert start_len > end_len, f"start {start_len} end {end_len}"
300
+
301
+ if end_len:
302
+ # There are still waiters, put the manager back
303
+ heappush(self_waiters, manager)
304
+ else:
305
+ # There are no more waiters, get rid of the empty manager
306
+ self._context_managers.pop(manager._priority)
307
+ return
308
+
309
+ # emergency procedure (hopefully temporary):
310
+ if not debug_logs:
311
+ while potential_lost_waiters:
312
+ waiter = potential_lost_waiters.pop()
313
+ if _is_not_done(waiter):
314
+ waiter.set_result(None)
315
+ return
316
+ return
317
+
318
+ while potential_lost_waiters:
319
+ waiter = potential_lost_waiters.pop()
320
+ log_debug("we found a lost waiter %s", (waiter, ))
321
+ if _is_not_done(waiter):
322
+ waiter.set_result(None)
323
+ log_debug("woke up lost waiter %s", (waiter, ))
324
+ return
325
+
326
+ log_debug("%s has no waiters to wake", (self, ))
327
+
328
+
329
+ cdef class _AbstractPrioritySemaphoreContextManager(Semaphore):
330
+ """
331
+ A context manager for priority semaphore waiters.
332
+
333
+ This context manager is associated with a specific priority and handles
334
+ the acquisition and release of the semaphore for waiters with that priority.
335
+ """
336
+
337
+ def __init__(
338
+ self,
339
+ parent: _AbstractPrioritySemaphore,
340
+ priority: PT,
341
+ name: Optional[str] = None,
342
+ ) -> None:
343
+ """Initializes the context manager for a specific priority.
344
+
345
+ Args:
346
+ parent: The parent semaphore.
347
+ priority: The priority associated with this context manager.
348
+ name: An optional name for the context manager, used for debugging.
349
+
350
+ Examples:
351
+ >>> parent_semaphore = _AbstractPrioritySemaphore(5)
352
+ >>> context_manager = _AbstractPrioritySemaphoreContextManager(parent_semaphore, priority=1)
353
+ """
354
+
355
+ Semaphore.__init__(self, 0, name=name)
356
+
357
+ self._parent = parent
358
+ """The parent semaphore."""
359
+
360
+ self._priority = priority
361
+ """The priority associated with this context manager."""
362
+
363
+ def __repr__(self) -> str:
364
+ """Returns a string representation of the context manager."""
365
+ return f"<{self.__class__.__name__} parent={self._parent} {self._priority_name}={self._priority} waiters={len(self)}>"
366
+
367
+ cpdef str _repr_no_parent_(self):
368
+ """Returns a string representation of the context manager without the parent."""
369
+ return f"<{self.__class__.__name__} parent_name={self._parent.name} {self._priority_name}={self._priority} waiters={len(self)}>"
370
+
371
+ def __richcmp__(self, _AbstractPrioritySemaphoreContextManager other, int op) -> bint:
372
+ """Rich comparison special method. Compares this context manager with another based on priority.
373
+
374
+ Args:
375
+ other: The other context manager to compare with.
376
+ op: The operation being performed. One of:
377
+ 0 -> Py_LT (a < b)
378
+ 1 -> Py_LE (a <= b)
379
+ 2 -> Py_EQ (a == b)
380
+ 3 -> Py_NE (a != b)
381
+ 4 -> Py_GT (a > b)
382
+ 5 -> Py_GE (a >= b)
383
+
384
+ Returns:
385
+ The boolean result of the comparison.
386
+
387
+ Raises:
388
+ TypeError: If the other object is not an instance of :class:`~_AbstractPrioritySemaphoreContextManager`.
389
+
390
+ Examples:
391
+ >>> cm1 = _AbstractPrioritySemaphoreContextManager(parent, priority=1)
392
+ >>> cm2 = _AbstractPrioritySemaphoreContextManager(parent, priority=2)
393
+ >>> cm1 < cm2
394
+ """
395
+ if op == 0: # Py_LT
396
+ return self._priority < other._priority
397
+ elif op == 1: # Py_LE
398
+ return self._priority <= other._priority
399
+ elif op == 2: # Py_EQ
400
+ return self is other
401
+ elif op == 3: # Py_NE
402
+ return self is not other
403
+ elif op == 4: # Py_GT
404
+ return self._priority > other._priority
405
+ elif op == 5: # Py_GE
406
+ return self._priority >= other._priority
407
+ return NotImplemented
408
+
409
+ cpdef object acquire(self):
410
+ """Acquires the semaphore for this context manager.
411
+
412
+ If the internal counter is larger than zero on entry,
413
+ decrement it by one and return True immediately. If it is
414
+ zero on entry, block, waiting until some other coroutine has
415
+ called release() to make it larger than 0, and then return
416
+ True.
417
+
418
+ This method overrides :meth:`Semaphore.acquire` to handle priority-based logic.
419
+
420
+ Examples:
421
+ >>> context_manager = _AbstractPrioritySemaphoreContextManager(parent, priority=1)
422
+ >>> await context_manager.acquire()
423
+ """
424
+ if self._parent._Semaphore__value <= 0:
425
+ self._c_ensure_debug_daemon((),{})
426
+ return self.__acquire()
427
+
428
+ async def __acquire(self) -> Literal[True]:
429
+ cdef object fut
430
+ cdef _AbstractPrioritySemaphore parent = self._parent
431
+ while parent._Semaphore__value <= 0:
432
+ if self._Semaphore__waiters is None:
433
+ self._Semaphore__waiters = deque()
434
+ fut = self._c_get_loop().create_future()
435
+ self._Semaphore__waiters.append(fut)
436
+ parent._potential_lost_waiters.add(fut)
437
+ try:
438
+ await fut
439
+ except:
440
+ # See the similar code in Queue.get.
441
+ fut.cancel()
442
+ if parent._Semaphore__value > 0 and _is_not_cancelled(fut):
443
+ parent._wake_up_next()
444
+ raise
445
+ parent._Semaphore__value -= 1
446
+ return True
447
+
448
+ cpdef void release(self):
449
+ """Releases the semaphore for this context manager.
450
+
451
+ This method overrides :meth:`Semaphore.release` to handle priority-based logic.
452
+
453
+ Examples:
454
+ >>> context_manager = _AbstractPrioritySemaphoreContextManager(parent, priority=1)
455
+ >>> context_manager.release()
456
+ """
457
+ self._parent.release()
458
+
459
+
460
+ cdef inline bint _is_not_done(fut: asyncio.Future):
461
+ return PyUnicode_CompareWithASCIIString(fut._state, b"PENDING") == 0
462
+
463
+ cdef inline bint _is_not_cancelled(fut: asyncio.Future):
464
+ return PyUnicode_CompareWithASCIIString(fut._state, b"CANCELLED") != 0
465
+
466
+
467
+ del asyncio
468
+
469
+
470
+ cdef class _PrioritySemaphoreContextManager(_AbstractPrioritySemaphoreContextManager):
471
+ """Context manager for numeric priority semaphores."""
472
+
473
+ def __cinit__(self):
474
+ self._priority_name = "priority"
475
+ # Semaphore.__cinit__(self)
476
+ self._Semaphore__waiters = deque()
477
+ self._decorated: Set[str] = set()
478
+
479
+ def __richcmp__(self, _PrioritySemaphoreContextManager other, int op) -> bint:
480
+ """Rich comparison special method. Compares this context manager with another based on priority.
481
+
482
+ Args:
483
+ other: The other context manager to compare with.
484
+ op: The operation being performed. One of:
485
+ 0 -> Py_LT (a < b)
486
+ 1 -> Py_LE (a <= b)
487
+ 2 -> Py_EQ (a == b)
488
+ 3 -> Py_NE (a != b)
489
+ 4 -> Py_GT (a > b)
490
+ 5 -> Py_GE (a >= b)
491
+
492
+ Returns:
493
+ The boolean result of the comparison.
494
+
495
+ Raises:
496
+ TypeError: If the other object is not an instance of :class:`~_PrioritySemaphoreContextManager`.
497
+
498
+ Examples:
499
+ >>> cm1 = _AbstractPrioritySemaphoreContextManager(parent, priority=1)
500
+ >>> cm2 = _AbstractPrioritySemaphoreContextManager(parent, priority=2)
501
+ >>> cm1 < cm2
502
+ """
503
+ if op == 0: # Py_LT
504
+ return <int>self._priority < <int>other._priority
505
+ elif op == 1: # Py_LE
506
+ return <int>self._priority <= <int>other._priority
507
+ elif op == 2: # Py_EQ
508
+ return self is other
509
+ elif op == 3: # Py_NE
510
+ return self is not other
511
+ elif op == 4: # Py_GT
512
+ return <int>self._priority > <int>other._priority
513
+ elif op == 5: # Py_GE
514
+ return <int>self._priority >= <int>other._priority
515
+ return NotImplemented
516
+
517
+ cdef class PrioritySemaphore(_AbstractPrioritySemaphore):
518
+ """Semaphore that uses numeric priorities for waiters.
519
+
520
+ This class extends :class:`_AbstractPrioritySemaphore` and provides a concrete implementation
521
+ using numeric priorities. The `_context_manager_class` is set to :class:`_PrioritySemaphoreContextManager`,
522
+ and the `_top_priority` is set to -1, which is the highest priority.
523
+
524
+ Examples:
525
+ The primary way to use this semaphore is by specifying a priority.
526
+
527
+ >>> priority_semaphore = PrioritySemaphore(10)
528
+ >>> async with priority_semaphore[priority]:
529
+ ... await do_stuff()
530
+
531
+ You can also enter and exit this semaphore without specifying a priority, and it will use the top priority by default:
532
+
533
+ >>> priority_semaphore = PrioritySemaphore(10)
534
+ >>> async with priority_semaphore:
535
+ ... await do_stuff()
536
+
537
+ See Also:
538
+ :class:`_AbstractPrioritySemaphore` for the base class implementation.
539
+ """
540
+
541
+ def __cinit__(self):
542
+ # _AbstractPrioritySemaphore.__cinit__(self)
543
+
544
+ self._context_managers = {}
545
+ """A dictionary mapping priorities to their context managers."""
546
+
547
+ self._Semaphore__waiters = []
548
+ """A heap queue of context managers, sorted by priority."""
549
+
550
+ # NOTE: This should (hopefully) be temporary
551
+ self._potential_lost_waiters = set()
552
+ """A list of futures representing waiters that might have been lost."""
553
+
554
+ def __init__(
555
+ self,
556
+ value: int = 1,
557
+ *,
558
+ name: Optional[str] = None,
559
+ ) -> None:
560
+ # context manager class is some temporary hacky shit, just ignore this
561
+ _AbstractPrioritySemaphore.__init__(self, _PrioritySemaphoreContextManager, -1, value, name=name)
562
+
563
+ def __getitem__(
564
+ self, priority: Optional[PT]
565
+ ) -> "_PrioritySemaphoreContextManager[PT]":
566
+ """Gets the context manager for a given priority.
567
+
568
+ Args:
569
+ priority: The priority for which to get the context manager. If None, uses the top priority.
570
+
571
+ Returns:
572
+ The context manager associated with the given priority.
573
+
574
+ Examples:
575
+ >>> semaphore = _AbstractPrioritySemaphore(5)
576
+ >>> context_manager = semaphore[priority]
577
+ """
578
+ return self.c_getitem(priority or -1)
579
+
580
+ cdef _PrioritySemaphoreContextManager c_getitem(self, object priority):
581
+ if self._Semaphore__value is None:
582
+ raise ValueError(self._Semaphore__value)
583
+
584
+ cdef dict[int, _PrioritySemaphoreContextManager] context_managers
585
+ cdef _PrioritySemaphoreContextManager context_manager
586
+
587
+ context_managers = self._context_managers
588
+ context_manager = context_managers.get(priority)
589
+ if context_manager is None:
590
+ context_manager = _PrioritySemaphoreContextManager(self, <int>priority, name=self.name)
591
+ heappush(self._Semaphore__waiters, context_manager)
592
+ context_managers[<int>priority] = context_manager
593
+ return context_manager
594
+
595
+
596
+ cdef void log_debug(str message, tuple args):
597
+ _logger_log(DEBUG, message, args)