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