ez-a-sync 0.32.23__cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_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.

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 +22686 -0
  5. a_sync/_smart.cpython-39-i386-linux-gnu.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 +19749 -0
  12. a_sync/a_sync/_descriptor.cpython-39-i386-linux-gnu.so +0 -0
  13. a_sync/a_sync/_descriptor.pyi +34 -0
  14. a_sync/a_sync/_descriptor.pyx +422 -0
  15. a_sync/a_sync/_flags.c +5687 -0
  16. a_sync/a_sync/_flags.cpython-39-i386-linux-gnu.so +0 -0
  17. a_sync/a_sync/_flags.pxd +3 -0
  18. a_sync/a_sync/_flags.pyx +92 -0
  19. a_sync/a_sync/_helpers.c +13722 -0
  20. a_sync/a_sync/_helpers.cpython-39-i386-linux-gnu.so +0 -0
  21. a_sync/a_sync/_helpers.pxd +3 -0
  22. a_sync/a_sync/_helpers.pyi +10 -0
  23. a_sync/a_sync/_helpers.pyx +167 -0
  24. a_sync/a_sync/_kwargs.c +10672 -0
  25. a_sync/a_sync/_kwargs.cpython-39-i386-linux-gnu.so +0 -0
  26. a_sync/a_sync/_kwargs.pxd +2 -0
  27. a_sync/a_sync/_kwargs.pyx +64 -0
  28. a_sync/a_sync/_meta.py +210 -0
  29. a_sync/a_sync/abstract.c +11350 -0
  30. a_sync/a_sync/abstract.cpython-39-i386-linux-gnu.so +0 -0
  31. a_sync/a_sync/abstract.pyi +141 -0
  32. a_sync/a_sync/abstract.pyx +221 -0
  33. a_sync/a_sync/base.c +14066 -0
  34. a_sync/a_sync/base.cpython-39-i386-linux-gnu.so +0 -0
  35. a_sync/a_sync/base.pyi +60 -0
  36. a_sync/a_sync/base.pyx +271 -0
  37. a_sync/a_sync/config.py +168 -0
  38. a_sync/a_sync/decorator.py +651 -0
  39. a_sync/a_sync/flags.c +4471 -0
  40. a_sync/a_sync/flags.cpython-39-i386-linux-gnu.so +0 -0
  41. a_sync/a_sync/flags.pxd +72 -0
  42. a_sync/a_sync/flags.pyi +74 -0
  43. a_sync/a_sync/flags.pyx +72 -0
  44. a_sync/a_sync/function.c +38187 -0
  45. a_sync/a_sync/function.cpython-39-i386-linux-gnu.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 +1379 -0
  49. a_sync/a_sync/method.c +30587 -0
  50. a_sync/a_sync/method.cpython-39-i386-linux-gnu.so +0 -0
  51. a_sync/a_sync/method.pxd +9 -0
  52. a_sync/a_sync/method.pyi +524 -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 +14939 -0
  60. a_sync/a_sync/modifiers/manager.cpython-39-i386-linux-gnu.so +0 -0
  61. a_sync/a_sync/modifiers/manager.pxd +5 -0
  62. a_sync/a_sync/modifiers/manager.pyi +219 -0
  63. a_sync/a_sync/modifiers/manager.pyx +299 -0
  64. a_sync/a_sync/modifiers/semaphores.py +173 -0
  65. a_sync/a_sync/property.c +28130 -0
  66. a_sync/a_sync/property.cpython-39-i386-linux-gnu.so +0 -0
  67. a_sync/a_sync/property.pyi +376 -0
  68. a_sync/a_sync/property.pyx +820 -0
  69. a_sync/a_sync/singleton.py +63 -0
  70. a_sync/aliases.py +3 -0
  71. a_sync/async_property/__init__.pxd +1 -0
  72. a_sync/async_property/__init__.py +1 -0
  73. a_sync/async_property/cached.c +20542 -0
  74. a_sync/async_property/cached.cpython-39-i386-linux-gnu.so +0 -0
  75. a_sync/async_property/cached.pxd +10 -0
  76. a_sync/async_property/cached.pyi +45 -0
  77. a_sync/async_property/cached.pyx +178 -0
  78. a_sync/async_property/proxy.c +36410 -0
  79. a_sync/async_property/proxy.cpython-39-i386-linux-gnu.so +0 -0
  80. a_sync/async_property/proxy.pxd +2 -0
  81. a_sync/async_property/proxy.pyi +124 -0
  82. a_sync/async_property/proxy.pyx +474 -0
  83. a_sync/asyncio/__init__.pxd +6 -0
  84. a_sync/asyncio/__init__.py +164 -0
  85. a_sync/asyncio/as_completed.c +18198 -0
  86. a_sync/asyncio/as_completed.cpython-39-i386-linux-gnu.so +0 -0
  87. a_sync/asyncio/as_completed.pxd +8 -0
  88. a_sync/asyncio/as_completed.pyi +109 -0
  89. a_sync/asyncio/as_completed.pyx +269 -0
  90. a_sync/asyncio/create_task.c +15109 -0
  91. a_sync/asyncio/create_task.cpython-39-i386-linux-gnu.so +0 -0
  92. a_sync/asyncio/create_task.pxd +2 -0
  93. a_sync/asyncio/create_task.pyi +51 -0
  94. a_sync/asyncio/create_task.pyx +276 -0
  95. a_sync/asyncio/gather.c +15735 -0
  96. a_sync/asyncio/gather.cpython-39-i386-linux-gnu.so +0 -0
  97. a_sync/asyncio/gather.pyi +107 -0
  98. a_sync/asyncio/gather.pyx +218 -0
  99. a_sync/asyncio/igather.c +11984 -0
  100. a_sync/asyncio/igather.cpython-39-i386-linux-gnu.so +0 -0
  101. a_sync/asyncio/igather.pxd +1 -0
  102. a_sync/asyncio/igather.pyi +7 -0
  103. a_sync/asyncio/igather.pyx +182 -0
  104. a_sync/asyncio/sleep.c +8916 -0
  105. a_sync/asyncio/sleep.cpython-39-i386-linux-gnu.so +0 -0
  106. a_sync/asyncio/sleep.pyi +14 -0
  107. a_sync/asyncio/sleep.pyx +49 -0
  108. a_sync/debugging.c +15157 -0
  109. a_sync/debugging.cpython-39-i386-linux-gnu.so +0 -0
  110. a_sync/debugging.pyi +76 -0
  111. a_sync/debugging.pyx +107 -0
  112. a_sync/exceptions.c +13169 -0
  113. a_sync/exceptions.cpython-39-i386-linux-gnu.so +0 -0
  114. a_sync/exceptions.pyi +376 -0
  115. a_sync/exceptions.pyx +446 -0
  116. a_sync/executor.py +575 -0
  117. a_sync/functools.c +11489 -0
  118. a_sync/functools.cpython-39-i386-linux-gnu.so +0 -0
  119. a_sync/functools.pxd +7 -0
  120. a_sync/functools.pyi +33 -0
  121. a_sync/functools.pyx +139 -0
  122. a_sync/future.py +1497 -0
  123. a_sync/iter.c +37702 -0
  124. a_sync/iter.cpython-39-i386-linux-gnu.so +0 -0
  125. a_sync/iter.pxd +11 -0
  126. a_sync/iter.pyi +366 -0
  127. a_sync/iter.pyx +981 -0
  128. a_sync/primitives/__init__.pxd +1 -0
  129. a_sync/primitives/__init__.py +53 -0
  130. a_sync/primitives/_debug.c +14737 -0
  131. a_sync/primitives/_debug.cpython-39-i386-linux-gnu.so +0 -0
  132. a_sync/primitives/_debug.pxd +12 -0
  133. a_sync/primitives/_debug.pyi +52 -0
  134. a_sync/primitives/_debug.pyx +223 -0
  135. a_sync/primitives/_loggable.c +10569 -0
  136. a_sync/primitives/_loggable.cpython-39-i386-linux-gnu.so +0 -0
  137. a_sync/primitives/_loggable.pxd +4 -0
  138. a_sync/primitives/_loggable.pyi +66 -0
  139. a_sync/primitives/_loggable.pyx +102 -0
  140. a_sync/primitives/locks/__init__.pxd +8 -0
  141. a_sync/primitives/locks/__init__.py +17 -0
  142. a_sync/primitives/locks/counter.c +16972 -0
  143. a_sync/primitives/locks/counter.cpython-39-i386-linux-gnu.so +0 -0
  144. a_sync/primitives/locks/counter.pxd +12 -0
  145. a_sync/primitives/locks/counter.pyi +151 -0
  146. a_sync/primitives/locks/counter.pyx +260 -0
  147. a_sync/primitives/locks/event.c +16125 -0
  148. a_sync/primitives/locks/event.cpython-39-i386-linux-gnu.so +0 -0
  149. a_sync/primitives/locks/event.pxd +22 -0
  150. a_sync/primitives/locks/event.pyi +43 -0
  151. a_sync/primitives/locks/event.pyx +185 -0
  152. a_sync/primitives/locks/prio_semaphore.c +25204 -0
  153. a_sync/primitives/locks/prio_semaphore.cpython-39-i386-linux-gnu.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 +25813 -0
  158. a_sync/primitives/locks/semaphore.cpython-39-i386-linux-gnu.so +0 -0
  159. a_sync/primitives/locks/semaphore.pxd +21 -0
  160. a_sync/primitives/locks/semaphore.pyi +196 -0
  161. a_sync/primitives/locks/semaphore.pyx +454 -0
  162. a_sync/primitives/queue.py +1018 -0
  163. a_sync/py.typed +0 -0
  164. a_sync/sphinx/__init__.py +3 -0
  165. a_sync/sphinx/ext.py +289 -0
  166. a_sync/task.py +932 -0
  167. a_sync/utils/__init__.py +105 -0
  168. a_sync/utils/iterators.py +297 -0
  169. a_sync/utils/repr.c +14354 -0
  170. a_sync/utils/repr.cpython-39-i386-linux-gnu.so +0 -0
  171. a_sync/utils/repr.pyi +2 -0
  172. a_sync/utils/repr.pyx +73 -0
  173. ez_a_sync-0.32.23.dist-info/METADATA +367 -0
  174. ez_a_sync-0.32.23.dist-info/RECORD +177 -0
  175. ez_a_sync-0.32.23.dist-info/WHEEL +8 -0
  176. ez_a_sync-0.32.23.dist-info/licenses/LICENSE.txt +17 -0
  177. ez_a_sync-0.32.23.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1379 @@
1
+ import asyncio
2
+ import inspect
3
+ import sys
4
+ import typing
5
+ from logging import getLogger
6
+ from libc.stdint cimport uintptr_t
7
+
8
+ import async_lru
9
+ import async_property
10
+ from typing_extensions import Unpack
11
+
12
+ from a_sync._typing import AnyFn, AnyIterable, CoroFn, DefaultMode, MaybeCoro, ModifierKwargs, P, SyncFn, T
13
+ from a_sync.a_sync._kwargs cimport get_flag_name, is_sync
14
+ from a_sync.a_sync._helpers cimport _asyncify, _await
15
+ from a_sync.a_sync.flags cimport VIABLE_FLAGS
16
+ from a_sync.a_sync.modifiers cimport ModifierManager
17
+ from a_sync.functools cimport update_wrapper, wraps
18
+
19
+ if typing.TYPE_CHECKING:
20
+ from a_sync import TaskMapping
21
+ from a_sync.a_sync.method import (
22
+ ASyncBoundMethod,
23
+ ASyncBoundMethodAsyncDefault,
24
+ ASyncBoundMethodSyncDefault,
25
+ )
26
+
27
+ else:
28
+ # due to circ import issues we will populate this later
29
+ TaskMapping = None
30
+
31
+
32
+ ctypedef object object_id
33
+
34
+
35
+ # cdef asyncio
36
+ cdef object iscoroutinefunction = asyncio.iscoroutinefunction
37
+ del asyncio
38
+
39
+ # cdef inspect
40
+ cdef object getargspec
41
+ if sys.version_info < (3, 11):
42
+ getargspec = inspect.getargspec
43
+ else:
44
+ # getargspec was deprecated in python 3.11
45
+ getargspec = inspect.getfullargspec
46
+ cdef object isasyncgenfunction = inspect.isasyncgenfunction
47
+ cdef object isgeneratorfunction = inspect.isgeneratorfunction
48
+ del inspect
49
+
50
+ # cdef logging
51
+ cdef public object logger = getLogger(__name__)
52
+ cdef object _logger_debug = logger.debug
53
+
54
+ # cdef typing
55
+ cdef object TYPE_CHECKING = typing.TYPE_CHECKING
56
+ cdef object Any = typing.Any
57
+ cdef object Callable = typing.Callable
58
+ cdef object Coroutine = typing.Coroutine
59
+ cdef object Generic = typing.Generic
60
+ cdef object Literal = typing.Literal
61
+ cdef object Optional = typing.Optional
62
+ cdef object overload = typing.overload
63
+
64
+
65
+ # cdef async_lru
66
+ cdef object _LRUCacheWrapper = async_lru._LRUCacheWrapper
67
+
68
+ # cdef async_property
69
+ cdef object AsyncPropertyDescriptor = async_property.base.AsyncPropertyDescriptor
70
+ cdef object AsyncCachedPropertyDescriptor = async_property.cached.AsyncCachedPropertyDescriptor
71
+
72
+
73
+ cdef class _ModifiedMixin:
74
+ """
75
+ A mixin class for internal use that provides functionality for applying modifiers to functions.
76
+
77
+ This class is used as a base for :class:`~ASyncFunction` and its variants, such as
78
+ `ASyncFunctionAsyncDefault` and `ASyncFunctionSyncDefault`, to handle the application
79
+ of async and sync modifiers to functions. Modifiers can alter the behavior of functions,
80
+ such as converting sync functions to async, applying caching, or rate limiting.
81
+
82
+ See Also:
83
+ - :class:`~ASyncFunction`
84
+ - :class:`~ModifierManager`
85
+ """
86
+
87
+ @property
88
+ def default(self) -> DefaultMode:
89
+ """
90
+ Gets the default execution mode (sync, async, or None) for the function.
91
+
92
+ Returns:
93
+ The default execution mode.
94
+
95
+ See Also:
96
+ - :attr:`ModifierManager.default`
97
+ """
98
+ return self.get_default()
99
+
100
+ cdef inline object _asyncify(self, func: SyncFn[P, T]):
101
+ """
102
+ Converts a synchronous function to an asynchronous one and applies async modifiers.
103
+
104
+ Args:
105
+ func: The synchronous function to be converted.
106
+
107
+ Returns:
108
+ The asynchronous version of the function with applied modifiers.
109
+
110
+ See Also:
111
+ - :meth:`ModifierManager.apply_async_modifiers`
112
+ """
113
+ return self.modifiers.apply_async_modifiers(_asyncify(func, self.modifiers.executor))
114
+
115
+ cdef inline object get_await(self):
116
+ """
117
+ Applies sync modifiers to the _helpers._await function and caches it.
118
+
119
+ Returns:
120
+ The modified _await function.
121
+
122
+ See Also:
123
+ - :meth:`ModifierManager.apply_sync_modifiers`
124
+ """
125
+ awaiter = self.__await
126
+ if awaiter is None:
127
+ awaiter = self.modifiers.apply_sync_modifiers(_await)
128
+ self.__await = awaiter
129
+ return awaiter
130
+
131
+ cdef inline str get_default(self):
132
+ cdef str default = self.__default
133
+ if default is None:
134
+ default = self.modifiers.get_default()
135
+ self.__default = default
136
+ return default
137
+
138
+
139
+
140
+ cdef void _validate_wrapped_fn(fn: Callable):
141
+ """Ensures 'fn' is an appropriate function for wrapping with a_sync.
142
+
143
+ Args:
144
+ fn: The function to validate.
145
+
146
+ Raises:
147
+ TypeError: If the input is not callable.
148
+ RuntimeError: If the function has arguments with names that conflict with viable flags.
149
+
150
+ See Also:
151
+ - :func:`_check_not_genfunc`
152
+ """
153
+ typ = type(fn)
154
+ if typ is _function_type:
155
+ _check_not_genfunc_cached(fn)
156
+ _validate_argspec_cached(fn)
157
+ return
158
+ if issubclass(typ, (AsyncPropertyDescriptor, AsyncCachedPropertyDescriptor)):
159
+ return # These are always valid
160
+ elif issubclass(typ, _LRUCacheWrapper):
161
+ fn = fn.__wrapped__
162
+ if type(fn) is _function_type:
163
+ _check_not_genfunc_cached(fn)
164
+ _validate_argspec_cached(fn)
165
+ return
166
+ elif not callable(fn):
167
+ raise TypeError(f"Input is not callable. Unable to decorate {fn}")
168
+ _check_not_genfunc(fn)
169
+ _validate_argspec(fn)
170
+
171
+ cdef object _function_type = type(getLogger)
172
+
173
+ cdef set[object_id] _argspec_validated = set()
174
+
175
+ cdef inline void _validate_argspec_cached(fn: Callable):
176
+ cdef object_id fid = id(fn)
177
+ if fid not in _argspec_validated:
178
+ _validate_argspec(fn)
179
+ _argspec_validated.add(fid)
180
+
181
+ cdef inline void _validate_argspec(fn: Callable):
182
+ cdef tuple[str, ...] fn_args
183
+ cdef object fn_code
184
+
185
+ try:
186
+ fn_code = fn.__code__ # May fail for built-ins or special callables
187
+ fn_args = fn_code.co_varnames[:fn_code.co_argcount]
188
+ except:
189
+ try:
190
+ argspec = getargspec(fn)
191
+ except TypeError:
192
+ warn = logger.warning
193
+ warn(f"inspect.{getargspec.__name__} does not support {fn}")
194
+ warn("we will allow you to proceed but cannot guarantee things will work")
195
+ warn("hopefully you know what you're doing...")
196
+ return
197
+ else:
198
+ fn_args = argspec[0]
199
+
200
+ for flag in fn_args:
201
+ if flag in VIABLE_FLAGS:
202
+ raise RuntimeError(
203
+ f"{fn} must not have any arguments with the following names: {VIABLE_FLAGS}"
204
+ )
205
+
206
+
207
+ cdef class _ASyncFunction(_ModifiedMixin):
208
+ """
209
+ A callable wrapper object that can be executed both synchronously and asynchronously.
210
+
211
+ This class wraps a function or coroutine function, allowing it to be called in both
212
+ synchronous and asynchronous contexts. It provides a flexible interface for handling
213
+ different execution modes and applying various modifiers to the function's behavior.
214
+
215
+ The class supports various modifiers that can alter the behavior of the function,
216
+ such as caching, rate limiting, and execution in specific contexts (e.g., thread pools).
217
+
218
+ Note:
219
+ The logic for determining whether to execute the function synchronously or asynchronously
220
+ is handled by the `self.fn` property, which checks for flags in the `kwargs` and defers
221
+ to the default execution mode if no flags are specified.
222
+
223
+ Example:
224
+ async def my_coroutine(x: int) -> str:
225
+ return str(x)
226
+
227
+ func = ASyncFunction(my_coroutine)
228
+
229
+ # Synchronous call
230
+ result = func(5, sync=True) # returns "5"
231
+
232
+ # Asynchronous call
233
+ result = await func(5) # returns "5"
234
+
235
+ See Also:
236
+ - :class:`_ModifiedMixin`
237
+ - :class:`ModifierManager`
238
+ """
239
+
240
+ def __init__(
241
+ self,
242
+ fn: AnyFn[P, T],
243
+ _skip_validate: bint = False,
244
+ **modifiers: Unpack[ModifierKwargs],
245
+ ) -> None:
246
+ """
247
+ Initializes an ASyncFunction instance.
248
+
249
+ Args:
250
+ fn: The function to wrap.
251
+ _skip_validate: For internal use only. Skips validation of the wrapped function when its already been validated once before.
252
+ **modifiers: Keyword arguments for function modifiers.
253
+
254
+ See Also:
255
+ - :func:`_validate_wrapped_fn`
256
+ - :class:`ModifierManager`
257
+ """
258
+ if not _skip_validate:
259
+ _validate_wrapped_fn(fn)
260
+
261
+ self.modifiers = ModifierManager(modifiers)
262
+ """A :class:`~ModifierManager` instance managing function modifiers."""
263
+
264
+ self._fn = None
265
+ """The wrapped callable that will be called. This will be populated the first time it is needed."""
266
+
267
+ self.__wrapped__ = fn
268
+ """The original function that was wrapped."""
269
+
270
+ self.__sync_default_cached = False
271
+ self.__async_def_cached = False
272
+
273
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> MaybeCoro[T]:
274
+ """
275
+ Calls the wrapped function either synchronously or asynchronously.
276
+
277
+ This method determines whether to execute the wrapped function synchronously
278
+ or asynchronously based on the default mode and any provided flags. The
279
+ decision logic is encapsulated within the `self.fn` property, which uses
280
+ the `_run_sync` method to decide the execution mode.
281
+
282
+ Note:
283
+ The `self.fn` property is responsible for selecting the appropriate
284
+ execution path (sync or async) by leveraging the `_run_sync` method.
285
+
286
+ Args:
287
+ *args: Positional arguments to pass to the wrapped function.
288
+ **kwargs: Keyword arguments to pass to the wrapped function.
289
+
290
+ Raises:
291
+ Exception: Any exception that may be raised by the wrapped function.
292
+
293
+ See Also:
294
+ - :attr:`default`
295
+ - :meth:`_run_sync`
296
+ """
297
+ fn = self.get_fn()
298
+ _logger_debug(
299
+ "calling %s fn: %s with args: %s kwargs: %s", self, fn, args, kwargs
300
+ )
301
+ return fn(*args, **kwargs)
302
+
303
+ def __repr__(self) -> str:
304
+ return "<{} {}.{} at {}>".format(
305
+ self.__class__.__name__, self.__module__, self.__name__, hex(id(self))
306
+ )
307
+
308
+ @property
309
+ def fn(self):
310
+ # NOTE type hint doesnt work in py3.8 or py3.9, debug later
311
+ # -> Union[SyncFn[[CoroFn[P, T]], MaybeAwaitable[T]], SyncFn[[SyncFn[P, T]], MaybeAwaitable[T]]]:
312
+ """
313
+ Returns the final wrapped version of :attr:`ASyncFunction.__wrapped__` decorated with all of the a_sync goodness.
314
+
315
+ Returns:
316
+ The final wrapped function.
317
+
318
+ See Also:
319
+ - :meth:`_async_wrap`
320
+ - :meth:`_sync_wrap`
321
+ """
322
+ return self.get_fn()
323
+
324
+ cdef object get_fn(self):
325
+ fn = self._fn
326
+ if fn is None:
327
+ fn = self._async_wrap if self.is_async_def() else self._sync_wrap
328
+ self._fn = fn
329
+ return fn
330
+
331
+ if sys.version_info >= (3, 11) or TYPE_CHECKING:
332
+ # we can specify P.args in python>=3.11 but in lower versions it causes a crash. Everything should still type check correctly on all versions.
333
+ def map(
334
+ self,
335
+ *iterables: AnyIterable[P.args],
336
+ concurrency: Optional[int] = None,
337
+ task_name: str = "",
338
+ **function_kwargs: P.kwargs,
339
+ ) -> "TaskMapping[P, T]":
340
+ """
341
+ Creates a TaskMapping for the wrapped function with the given iterables.
342
+
343
+ Args:
344
+ *iterables: Iterable objects to be used as arguments for the function.
345
+ concurrency: Optional maximum number of concurrent tasks.
346
+ task_name: Optional name for the tasks.
347
+ **function_kwargs: Additional keyword arguments to pass to the function.
348
+
349
+ Returns:
350
+ A TaskMapping object for managing concurrent execution.
351
+
352
+ See Also:
353
+ - :class:`TaskMapping`
354
+ """
355
+ if TaskMapping is None:
356
+ _import_TaskMapping()
357
+
358
+ return TaskMapping(
359
+ self,
360
+ *iterables,
361
+ concurrency=concurrency,
362
+ name=task_name,
363
+ **function_kwargs,
364
+ )
365
+
366
+ async def any(
367
+ self,
368
+ *iterables: AnyIterable[P.args],
369
+ concurrency: Optional[int] = None,
370
+ task_name: str = "",
371
+ **function_kwargs: P.kwargs,
372
+ ) -> bint:
373
+ """
374
+ Checks if any result of the function applied to the iterables is truthy.
375
+
376
+ Args:
377
+ *iterables: Iterable objects to be used as arguments for the function.
378
+ concurrency: Optional maximum number of concurrent tasks.
379
+ task_name: Optional name for the tasks.
380
+ **function_kwargs: Additional keyword arguments to pass to the function.
381
+
382
+ Returns:
383
+ True if any result is truthy, otherwise False.
384
+
385
+ See Also:
386
+ - :meth:`map`
387
+ """
388
+ return await self.map(
389
+ *iterables,
390
+ concurrency=concurrency,
391
+ task_name=task_name,
392
+ **function_kwargs,
393
+ ).any(pop=True, sync=False)
394
+
395
+ async def all(
396
+ self,
397
+ *iterables: AnyIterable[P.args],
398
+ concurrency: Optional[int] = None,
399
+ task_name: str = "",
400
+ **function_kwargs: P.kwargs,
401
+ ) -> bint:
402
+ """
403
+ Checks if all results of the function applied to the iterables are truthy.
404
+
405
+ Args:
406
+ *iterables: Iterable objects to be used as arguments for the function.
407
+ concurrency: Optional maximum number of concurrent tasks.
408
+ task_name: Optional name for the tasks.
409
+ **function_kwargs: Additional keyword arguments to pass to the function.
410
+
411
+ Returns:
412
+ True if all results are truthy, otherwise False.
413
+
414
+ See Also:
415
+ - :meth:`map`
416
+ """
417
+ return await self.map(
418
+ *iterables,
419
+ concurrency=concurrency,
420
+ task_name=task_name,
421
+ **function_kwargs,
422
+ ).all(pop=True, sync=False)
423
+
424
+ async def min(
425
+ self,
426
+ *iterables: AnyIterable[P.args],
427
+ concurrency: Optional[int] = None,
428
+ task_name: str = "",
429
+ **function_kwargs: P.kwargs,
430
+ ) -> T:
431
+ """
432
+ Finds the minimum result of the function applied to the iterables.
433
+
434
+ Args:
435
+ *iterables: Iterable objects to be used as arguments for the function.
436
+ concurrency: Optional maximum number of concurrent tasks.
437
+ task_name: Optional name for the tasks.
438
+ **function_kwargs: Additional keyword arguments to pass to the function.
439
+
440
+ Returns:
441
+ The minimum result.
442
+
443
+ See Also:
444
+ - :meth:`map`
445
+ """
446
+ return await self.map(
447
+ *iterables,
448
+ concurrency=concurrency,
449
+ task_name=task_name,
450
+ **function_kwargs,
451
+ ).min(pop=True, sync=False)
452
+
453
+ async def max(
454
+ self,
455
+ *iterables: AnyIterable[P.args],
456
+ concurrency: Optional[int] = None,
457
+ task_name: str = "",
458
+ **function_kwargs: P.kwargs,
459
+ ) -> T:
460
+ """
461
+ Finds the maximum result of the function applied to the iterables.
462
+
463
+ Args:
464
+ *iterables: Iterable objects to be used as arguments for the function.
465
+ concurrency: Optional maximum number of concurrent tasks.
466
+ task_name: Optional name for the tasks.
467
+ **function_kwargs: Additional keyword arguments to pass to the function.
468
+
469
+ Returns:
470
+ The maximum result.
471
+
472
+ See Also:
473
+ - :meth:`map`
474
+ """
475
+ return await self.map(
476
+ *iterables,
477
+ concurrency=concurrency,
478
+ task_name=task_name,
479
+ **function_kwargs,
480
+ ).max(pop=True, sync=False)
481
+
482
+ async def sum(
483
+ self,
484
+ *iterables: AnyIterable[P.args],
485
+ concurrency: Optional[int] = None,
486
+ task_name: str = "",
487
+ **function_kwargs: P.kwargs,
488
+ ) -> T:
489
+ """
490
+ Calculates the sum of the results of the function applied to the iterables.
491
+
492
+ Args:
493
+ *iterables: Iterable objects to be used as arguments for the function.
494
+ concurrency: Optional maximum number of concurrent tasks.
495
+ task_name: Optional name for the tasks.
496
+ **function_kwargs: Additional keyword arguments to pass to the function.
497
+
498
+ Returns:
499
+ The sum of the results.
500
+
501
+ See Also:
502
+ - :meth:`map`
503
+ """
504
+ return await self.map(
505
+ *iterables,
506
+ concurrency=concurrency,
507
+ task_name=task_name,
508
+ **function_kwargs,
509
+ ).sum(pop=True, sync=False)
510
+
511
+ else:
512
+
513
+ def map(
514
+ self,
515
+ *iterables: AnyIterable[Any],
516
+ concurrency: Optional[int] = None,
517
+ task_name: str = "",
518
+ **function_kwargs: P.kwargs,
519
+ ) -> "TaskMapping[P, T]":
520
+ """
521
+ Creates a TaskMapping for the wrapped function with the given iterables.
522
+
523
+ Args:
524
+ *iterables: Iterable objects to be used as arguments for the function.
525
+ concurrency: Optional maximum number of concurrent tasks.
526
+ task_name: Optional name for the tasks.
527
+ **function_kwargs: Additional keyword arguments to pass to the function.
528
+
529
+ Returns:
530
+ A TaskMapping object for managing concurrent execution.
531
+
532
+ See Also:
533
+ - :class:`TaskMapping`
534
+ """
535
+ if TaskMapping is None:
536
+ _import_TaskMapping()
537
+
538
+ return TaskMapping(
539
+ self,
540
+ *iterables,
541
+ concurrency=concurrency,
542
+ name=task_name,
543
+ **function_kwargs,
544
+ )
545
+
546
+ async def any(
547
+ self,
548
+ *iterables: AnyIterable[Any],
549
+ concurrency: Optional[int] = None,
550
+ task_name: str = "",
551
+ **function_kwargs: P.kwargs,
552
+ ) -> bint:
553
+ """
554
+ Checks if any result of the function applied to the iterables is truthy.
555
+
556
+ Args:
557
+ *iterables: Iterable objects to be used as arguments for the function.
558
+ concurrency: Optional maximum number of concurrent tasks.
559
+ task_name: Optional name for the tasks.
560
+ **function_kwargs: Additional keyword arguments to pass to the function.
561
+
562
+ Returns:
563
+ True if any result is truthy, otherwise False.
564
+
565
+ See Also:
566
+ - :meth:`map`
567
+ """
568
+ return await self.map(
569
+ *iterables,
570
+ concurrency=concurrency,
571
+ task_name=task_name,
572
+ **function_kwargs,
573
+ ).any(pop=True, sync=False)
574
+
575
+ async def all(
576
+ self,
577
+ *iterables: AnyIterable[Any],
578
+ concurrency: Optional[int] = None,
579
+ task_name: str = "",
580
+ **function_kwargs: P.kwargs,
581
+ ) -> bint:
582
+ """
583
+ Checks if all results of the function applied to the iterables are truthy.
584
+
585
+ Args:
586
+ *iterables: Iterable objects to be used as arguments for the function.
587
+ concurrency: Optional maximum number of concurrent tasks.
588
+ task_name: Optional name for the tasks.
589
+ **function_kwargs: Additional keyword arguments to pass to the function.
590
+
591
+ Returns:
592
+ True if all results are truthy, otherwise False.
593
+
594
+ See Also:
595
+ - :meth:`map`
596
+ """
597
+ return await self.map(
598
+ *iterables,
599
+ concurrency=concurrency,
600
+ task_name=task_name,
601
+ **function_kwargs,
602
+ ).all(pop=True, sync=False)
603
+
604
+ async def min(
605
+ self,
606
+ *iterables: AnyIterable[Any],
607
+ concurrency: Optional[int] = None,
608
+ task_name: str = "",
609
+ **function_kwargs: P.kwargs,
610
+ ) -> T:
611
+ """
612
+ Finds the minimum result of the function applied to the iterables.
613
+
614
+ Args:
615
+ *iterables: Iterable objects to be used as arguments for the function.
616
+ concurrency: Optional maximum number of concurrent tasks.
617
+ task_name: Optional name for the tasks.
618
+ **function_kwargs: Additional keyword arguments to pass to the function.
619
+
620
+ Returns:
621
+ The minimum result.
622
+
623
+ See Also:
624
+ - :meth:`map`
625
+ """
626
+ return await self.map(
627
+ *iterables,
628
+ concurrency=concurrency,
629
+ task_name=task_name,
630
+ **function_kwargs,
631
+ ).min(pop=True, sync=False)
632
+
633
+ async def max(
634
+ self,
635
+ *iterables: AnyIterable[Any],
636
+ concurrency: Optional[int] = None,
637
+ task_name: str = "",
638
+ **function_kwargs: P.kwargs,
639
+ ) -> T:
640
+ """
641
+ Finds the maximum result of the function applied to the iterables.
642
+
643
+ Args:
644
+ *iterables: Iterable objects to be used as arguments for the function.
645
+ concurrency: Optional maximum number of concurrent tasks.
646
+ task_name: Optional name for the tasks.
647
+ **function_kwargs: Additional keyword arguments to pass to the function.
648
+
649
+ Returns:
650
+ The maximum result.
651
+
652
+ See Also:
653
+ - :meth:`map`
654
+ """
655
+ return await self.map(
656
+ *iterables,
657
+ concurrency=concurrency,
658
+ task_name=task_name,
659
+ **function_kwargs,
660
+ ).max(pop=True, sync=False)
661
+
662
+ async def sum(
663
+ self,
664
+ *iterables: AnyIterable[Any],
665
+ concurrency: Optional[int] = None,
666
+ task_name: str = "",
667
+ **function_kwargs: P.kwargs,
668
+ ) -> T:
669
+ """
670
+ Calculates the sum of the results of the function applied to the iterables.
671
+
672
+ Args:
673
+ *iterables: Iterable objects to be used as arguments for the function.
674
+ concurrency: Optional maximum number of concurrent tasks.
675
+ task_name: Optional name for the tasks.
676
+ **function_kwargs: Additional keyword arguments to pass to the function.
677
+
678
+ Returns:
679
+ The sum of the results.
680
+
681
+ See Also:
682
+ - :meth:`map`
683
+ """
684
+ return await self.map(
685
+ *iterables,
686
+ concurrency=concurrency,
687
+ task_name=task_name,
688
+ **function_kwargs,
689
+ ).sum(pop=True, sync=False)
690
+
691
+ @property
692
+ def _asyncified(self) -> CoroFn[P, T]:
693
+ """
694
+ Converts the wrapped function to an asynchronous function and applies both sync and async modifiers.
695
+
696
+ Raises:
697
+ TypeError: If the wrapped function is already asynchronous.
698
+
699
+ Returns:
700
+ The asynchronous version of the wrapped function.
701
+
702
+ See Also:
703
+ - :meth:`_asyncify`
704
+ """
705
+ asyncified = self.__asyncified
706
+ if asyncified is None:
707
+ if self.is_async_def():
708
+ raise TypeError(
709
+ f"Can only be applied to sync functions, not {self.__wrapped__}"
710
+ )
711
+
712
+ asyncified = self.__asyncified = self._asyncify(self._modified_fn)
713
+ return asyncified
714
+
715
+ @property
716
+ def _modified_fn(self) -> AnyFn[P, T]:
717
+ """
718
+ Applies modifiers to the wrapped function.
719
+
720
+ If the wrapped function is an asynchronous function, this method applies async modifiers.
721
+ If the wrapped function is a synchronous function, this method applies sync modifiers.
722
+
723
+ Returns:
724
+ The modified function.
725
+
726
+ See Also:
727
+ - :meth:`ModifierManager.apply_async_modifiers`
728
+ - :meth:`ModifierManager.apply_sync_modifiers`
729
+ """
730
+ modified_fn = self.__modified_fn
731
+ if modified_fn is None:
732
+
733
+ # recursively unwrap ASync objects to get to the original wrapped callable
734
+ wrapped = self.__wrapped__
735
+ while isinstance(wrapped, _ASyncFunction):
736
+ wrapped = wrapped.__wrapped__
737
+
738
+ if self.is_async_def():
739
+ modified_fn = self.__modified_fn = self.modifiers.apply_async_modifiers(wrapped)
740
+ else:
741
+ modified_fn = self.__modified_fn = self.modifiers.apply_sync_modifiers(wrapped)
742
+
743
+ return modified_fn
744
+
745
+ @property
746
+ def _async_wrap(self): # -> SyncFn[[CoroFn[P, T]], MaybeAwaitable[T]]:
747
+ """
748
+ The final wrapper if the wrapped function is an asynchronous function.
749
+
750
+ This method applies the appropriate modifiers and determines whether to await the result.
751
+
752
+ Returns:
753
+ The wrapped function with async handling.
754
+
755
+ See Also:
756
+ - :meth:`_run_sync`
757
+ - :meth:`_await`
758
+ """
759
+ async_wrap = self.__async_wrap
760
+ if async_wrap is None:
761
+
762
+ modified_fn = self._modified_fn
763
+ await_helper = self.get_await()
764
+
765
+ @wraps(modified_fn)
766
+ def async_wrap(*args: P.args, **kwargs: P.kwargs) -> MaybeAwaitable[T]: # type: ignore [name-defined]
767
+ # sourcery skip: assign-if-exp
768
+ # we dont want this so profiler outputs are more useful
769
+
770
+ # Must take place before coro is created, we're popping a kwarg.
771
+ should_await = self._run_sync(kwargs)
772
+ coro = modified_fn(*args, **kwargs)
773
+ if should_await:
774
+ return await_helper(coro)
775
+ else:
776
+ return coro
777
+
778
+ self.__async_wrap = async_wrap
779
+
780
+ return async_wrap
781
+
782
+ @property
783
+ def _sync_wrap(self): # -> SyncFn[[SyncFn[P, T]], MaybeAwaitable[T]]:
784
+ """
785
+ The final wrapper if the wrapped function is a synchronous function.
786
+
787
+ This method applies the appropriate modifiers and determines whether to run the function synchronously or asynchronously.
788
+
789
+ Returns:
790
+ The wrapped function with sync handling.
791
+
792
+ See Also:
793
+ - :meth:`_run_sync`
794
+ - :meth:`_asyncified`
795
+ """
796
+ sync_wrap = self.__sync_wrap
797
+ if sync_wrap is None:
798
+
799
+ modified_fn = self._modified_fn
800
+ asyncified = self._asyncified
801
+
802
+ @wraps(modified_fn)
803
+ def sync_wrap(*args: P.args, **kwargs: P.kwargs) -> MaybeAwaitable[T]: # type: ignore [name-defined]
804
+ if self._run_sync(kwargs):
805
+ return modified_fn(*args, **kwargs)
806
+ return asyncified(*args, **kwargs)
807
+
808
+ self.__sync_wrap = sync_wrap
809
+
810
+ return sync_wrap
811
+
812
+ cpdef bint is_async_def(self):
813
+ """
814
+ Checks if the wrapped function is an asynchronous function.
815
+
816
+ Returns:
817
+ True if the function is asynchronous, otherwise False.
818
+
819
+ See Also:
820
+ - :func:`asyncio.iscoroutinefunction`
821
+ """
822
+ if self.__async_def_cached:
823
+ return self.__async_def
824
+ cdef bint async_def
825
+
826
+ # recursively unwrap ASync callables to get the original wrapped fn
827
+ wrapped = self.__wrapped__
828
+ while isinstance(wrapped, _ASyncFunction):
829
+ wrapped = wrapped.__wrapped__
830
+
831
+ async_def = self.__async_def = iscoroutinefunction(wrapped)
832
+ self.__async_def_cached = True
833
+ return async_def
834
+
835
+ cpdef bint is_sync_default(self):
836
+ """
837
+ Determines the default execution mode (sync or async) for the function.
838
+
839
+ If the user did not specify a default, this method defers to the function's
840
+ definition (sync vs async def).
841
+
842
+ Returns:
843
+ True if the default is sync, False if async.
844
+
845
+ See Also:
846
+ - :attr:`default`
847
+ """
848
+ if self.__sync_default_cached:
849
+ return self.__sync_default
850
+
851
+ cdef str default = self.get_default()
852
+ cdef bint sync_default = (
853
+ True
854
+ if default == "sync"
855
+ else False if default == "async" else not self.is_async_def()
856
+ )
857
+ self.__sync_default = sync_default
858
+ self.__sync_default_cached = True
859
+ return sync_default
860
+
861
+ cdef inline bint _run_sync(self, dict kwargs):
862
+ """
863
+ Determines whether to run the function synchronously or asynchronously.
864
+
865
+ This method checks for a flag in the kwargs and defers to it if present.
866
+ If no flag is specified, it defers to the default execution mode.
867
+
868
+ Args:
869
+ kwargs: The keyword arguments passed to the function.
870
+
871
+ Returns:
872
+ True if the function should run synchronously, otherwise False.
873
+
874
+ See Also:
875
+ - :func:`_kwargs.get_flag_name`
876
+ """
877
+ cdef str flag = get_flag_name(kwargs)
878
+ if flag:
879
+ # If a flag was specified in the kwargs, we will defer to it.
880
+ return is_sync(flag, kwargs, pop_flag=True)
881
+ else:
882
+ # No flag specified in the kwargs, we will defer to 'default'.
883
+ return self.is_sync_default()
884
+
885
+
886
+ class ASyncFunction(_ASyncFunction, Generic[P, T]):
887
+
888
+ @overload
889
+ def __init__(self, fn: CoroFn[P, T], **modifiers: Unpack[ModifierKwargs]) -> None:
890
+ """
891
+ Initializes an ASyncFunction instance for a coroutine function.
892
+
893
+ Args:
894
+ fn: The coroutine function to wrap.
895
+ **modifiers: Keyword arguments for function modifiers.
896
+
897
+ Example:
898
+ async def my_coroutine(x: int) -> str:
899
+ return str(x)
900
+
901
+ func = ASyncFunction(my_coroutine, cache_type='memory')
902
+ """
903
+
904
+ @overload
905
+ def __init__(self, fn: SyncFn[P, T], **modifiers: Unpack[ModifierKwargs]) -> None:
906
+ """
907
+ Initializes an ASyncFunction instance for a synchronous function.
908
+
909
+ Args:
910
+ fn: The synchronous function to wrap.
911
+ **modifiers: Keyword arguments for function modifiers.
912
+
913
+ Example:
914
+ def my_function(x: int) -> str:
915
+ return str(x)
916
+
917
+ func = ASyncFunction(my_function, runs_per_minute=60)
918
+ """
919
+
920
+ def __init__(
921
+ self,
922
+ fn: AnyFn[P, T],
923
+ _skip_validate: bint = False,
924
+ **modifiers: Unpack[ModifierKwargs],
925
+ ) -> None:
926
+ _ASyncFunction.__init__(self, fn, _skip_validate=_skip_validate, **modifiers)
927
+ update_wrapper(self, fn)
928
+ if self.__doc__ is None:
929
+ self.__doc__ = f"Since `{self.__name__}` is an {self.__docstring_append__}"
930
+ else:
931
+ self.__doc__ += (
932
+ f"\n\nSince `{self.__name__}` is an {self.__docstring_append__}"
933
+ )
934
+
935
+ __docstring_append__ = ":class:`~a_sync.a_sync.function.ASyncFunction`, you can optionally pass either a `sync` or `asynchronous` kwarg with a boolean value."
936
+
937
+ @overload
938
+ def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T:
939
+ """
940
+ Calls the wrapped function synchronously.
941
+
942
+ Args:
943
+ *args: Positional arguments to pass to the wrapped function.
944
+ sync: Must be True to indicate synchronous execution.
945
+ **kwargs: Keyword arguments to pass to the wrapped function.
946
+
947
+ Example:
948
+ result = func(5, sync=True)
949
+ """
950
+
951
+ @overload
952
+ def __call__(
953
+ self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs
954
+ ) -> Coroutine[Any, Any, T]:
955
+ """
956
+ Calls the wrapped function asynchronously.
957
+
958
+ Args:
959
+ *args: Positional arguments to pass to the wrapped function.
960
+ sync: Must be False to indicate asynchronous execution.
961
+ **kwargs: Keyword arguments to pass to the wrapped function.
962
+
963
+ Example:
964
+ result = await func(5, sync=False)
965
+ """
966
+
967
+ @overload
968
+ def __call__(
969
+ self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
970
+ ) -> T:
971
+ """
972
+ Calls the wrapped function synchronously.
973
+
974
+ Args:
975
+ *args: Positional arguments to pass to the wrapped function.
976
+ asynchronous: Must be False to indicate synchronous execution.
977
+ **kwargs: Keyword arguments to pass to the wrapped function.
978
+
979
+ Example:
980
+ result = func(5, asynchronous=False)
981
+ """
982
+
983
+ @overload
984
+ def __call__(
985
+ self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs
986
+ ) -> Coroutine[Any, Any, T]:
987
+ """
988
+ Calls the wrapped function asynchronously.
989
+
990
+ Args:
991
+ *args: Positional arguments to pass to the wrapped function.
992
+ asynchronous: Must be True to indicate asynchronous execution.
993
+ **kwargs: Keyword arguments to pass to the wrapped function.
994
+
995
+ Example:
996
+ result = await func(5, asynchronous=True)
997
+ """
998
+
999
+ @overload
1000
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> MaybeCoro[T]:
1001
+ """
1002
+ Calls the wrapped function using the default execution mode.
1003
+
1004
+ Args:
1005
+ *args: Positional arguments to pass to the wrapped function.
1006
+ **kwargs: Keyword arguments to pass to the wrapped function.
1007
+
1008
+ Example:
1009
+ result = func(5)
1010
+ """
1011
+
1012
+
1013
+ if sys.version_info < (3, 10):
1014
+ _inherit = ASyncFunction[AnyFn[P, T], ASyncFunction[P, T]]
1015
+ else:
1016
+ _inherit = ASyncFunction[[AnyFn[P, T]], ASyncFunction[P, T]]
1017
+
1018
+
1019
+ cdef class ASyncDecorator(_ModifiedMixin):
1020
+ def __cinit__(self, **modifiers: Unpack[ModifierKwargs]) -> None:
1021
+ """
1022
+ Initializes an ASyncDecorator instance by validating the inputs.
1023
+
1024
+ Args:
1025
+ **modifiers: Keyword arguments for function modifiers.
1026
+
1027
+ Raises:
1028
+ ValueError: If 'default' is not 'sync', 'async', or None.
1029
+
1030
+ See Also:
1031
+ - :class:`ModifierManager`
1032
+ """
1033
+ if modifiers.get("default", object) not in ("sync", "async", None):
1034
+ if "default" not in modifiers:
1035
+ raise ValueError("you must pass a value for `default`")
1036
+ raise ValueError(
1037
+ f"'default' must be either 'sync', 'async', or None. You passed {modifiers['default']}."
1038
+ )
1039
+ self.modifiers = ModifierManager(modifiers)
1040
+
1041
+ @overload
1042
+ def __call__(self, func: AnyFn[Concatenate[B, P], T]) -> "ASyncBoundMethod[B, P, T]": # type: ignore [override]
1043
+ """
1044
+ Decorates a bound method with async or sync behavior based on the default modifier.
1045
+
1046
+ Args:
1047
+ func: The bound method to decorate.
1048
+
1049
+ Returns:
1050
+ An ASyncBoundMethod instance with the appropriate default behavior.
1051
+
1052
+ See Also:
1053
+ - :class:`ASyncBoundMethod`
1054
+ """
1055
+
1056
+ @overload
1057
+ def __call__(self, func: AnyFn[P, T]) -> ASyncFunction[P, T]: # type: ignore [override]
1058
+ """
1059
+ Decorates a function with async or sync behavior based on the default modifier.
1060
+
1061
+ Args:
1062
+ func: The function to decorate.
1063
+
1064
+ Returns:
1065
+ An ASyncFunction instance with the appropriate default behavior.
1066
+
1067
+ See Also:
1068
+ - :class:`ASyncFunction`
1069
+ """
1070
+
1071
+ def __call__(self, func: AnyFn[P, T]) -> ASyncFunction[P, T]: # type: ignore [override]
1072
+ """
1073
+ Decorates a function with async or sync behavior based on the default modifier.
1074
+
1075
+ Args:
1076
+ func: The function to decorate.
1077
+
1078
+ Returns:
1079
+ An ASyncFunction instance with the appropriate default behavior.
1080
+
1081
+ See Also:
1082
+ - :class:`ASyncFunction`
1083
+ """
1084
+ cdef str default = self.get_default()
1085
+ if default == "async":
1086
+ return ASyncFunctionAsyncDefault(func, **self.modifiers._modifiers)
1087
+ elif default == "sync":
1088
+ return ASyncFunctionSyncDefault(func, **self.modifiers._modifiers)
1089
+ elif iscoroutinefunction(func):
1090
+ return ASyncFunctionAsyncDefault(func, **self.modifiers._modifiers)
1091
+ else:
1092
+ return ASyncFunctionSyncDefault(func, **self.modifiers._modifiers)
1093
+
1094
+
1095
+ cdef set[uintptr_t] _is_genfunc_cache = set()
1096
+
1097
+ cdef void _check_not_genfunc_cached(func: Callable):
1098
+ cdef uintptr_t fid = id(func)
1099
+ if fid not in _is_genfunc_cache:
1100
+ _check_not_genfunc(func)
1101
+ _is_genfunc_cache.add(fid)
1102
+
1103
+ cdef void _check_not_genfunc(func: Callable):
1104
+ """Raises an error if the function is a generator or async generator.
1105
+
1106
+ Args:
1107
+ func: The function to check.
1108
+
1109
+ Raises:
1110
+ ValueError: If the function is a generator or async generator.
1111
+
1112
+ See Also:
1113
+ - :func:`inspect.isasyncgenfunction`
1114
+ - :func:`inspect.isgeneratorfunction`
1115
+ """
1116
+ if isasyncgenfunction(func) or isgeneratorfunction(func):
1117
+ raise ValueError("unable to decorate generator functions with this decorator")
1118
+
1119
+
1120
+ # Mypy helper classes
1121
+
1122
+
1123
+ class ASyncFunctionSyncDefault(ASyncFunction[P, T]):
1124
+ """A specialized :class:`~ASyncFunction` that defaults to synchronous execution.
1125
+
1126
+ This class is used when the :func:`~a_sync` decorator is applied with `default='sync'`.
1127
+ It provides type hints to indicate that the default call behavior is synchronous and
1128
+ supports IDE type checking for most use cases.
1129
+
1130
+ The wrapped function can still be called asynchronously by passing `sync=False`
1131
+ or `asynchronous=True` as a keyword argument.
1132
+
1133
+ Example:
1134
+ @a_sync(default='sync')
1135
+ async def my_function(x: int) -> str:
1136
+ return str(x)
1137
+
1138
+ # Synchronous call (default behavior)
1139
+ result = my_function(5) # returns "5"
1140
+
1141
+ # Asynchronous call
1142
+ result = await my_function(5, sync=False) # returns "5"
1143
+ """
1144
+
1145
+ @overload
1146
+ def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T:
1147
+ # TODO write specific docs for this overload
1148
+ ...
1149
+
1150
+ @overload
1151
+ def __call__(
1152
+ self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs
1153
+ ) -> Coroutine[Any, Any, T]:
1154
+ # TODO write specific docs for this overload
1155
+ ...
1156
+
1157
+ @overload
1158
+ def __call__(
1159
+ self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
1160
+ ) -> T:
1161
+ # TODO write specific docs for this overload
1162
+ ...
1163
+
1164
+ @overload
1165
+ def __call__(
1166
+ self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs
1167
+ ) -> Coroutine[Any, Any, T]:
1168
+ # TODO write specific docs for this overload
1169
+ ...
1170
+
1171
+ @overload
1172
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
1173
+ # TODO write specific docs for this overload
1174
+ ...
1175
+
1176
+ def __call__(_ASyncFunction self, *args: P.args, **kwargs: P.kwargs) -> MaybeCoro[T]:
1177
+ """Calls the wrapped function, defaulting to synchronous execution.
1178
+
1179
+ This method overrides the base :meth:`ASyncFunction.__call__` to provide a synchronous
1180
+ default behavior.
1181
+
1182
+ Args:
1183
+ *args: Positional arguments to pass to the wrapped function.
1184
+ **kwargs: Keyword arguments to pass to the wrapped function.
1185
+
1186
+ Raises:
1187
+ Exception: Any exception that may be raised by the wrapped function.
1188
+
1189
+ Returns:
1190
+ The result of the function call.
1191
+
1192
+ See Also:
1193
+ - :meth:`ASyncFunction.__call__`
1194
+ """
1195
+ return self.get_fn()(*args, **kwargs)
1196
+
1197
+ __docstring_append__ = ":class:`~a_sync.a_sync.function.ASyncFunctionSyncDefault`, you can optionally pass `sync=False` or `asynchronous=True` to force it to return a coroutine. Without either kwarg, it will run synchronously."
1198
+
1199
+
1200
+ class ASyncFunctionAsyncDefault(ASyncFunction[P, T]):
1201
+ """
1202
+ A specialized :class:`~ASyncFunction` that defaults to asynchronous execution.
1203
+
1204
+ This class is used when the :func:`~a_sync` decorator is applied with `default='async'`.
1205
+ It provides type hints to indicate that the default call behavior is asynchronous
1206
+ and supports IDE type checking for most use cases.
1207
+
1208
+ The wrapped function can still be called synchronously by passing `sync=True`
1209
+ or `asynchronous=False` as a keyword argument.
1210
+
1211
+ Example:
1212
+ @a_sync(default='async')
1213
+ async def my_function(x: int) -> str:
1214
+ return str(x)
1215
+
1216
+ # Asynchronous call (default behavior)
1217
+ result = await my_function(5) # returns "5"
1218
+
1219
+ # Synchronous call
1220
+ result = my_function(5, sync=True) # returns "5"
1221
+ """
1222
+
1223
+ @overload
1224
+ def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T:
1225
+ # TODO write specific docs for this overload
1226
+ ...
1227
+
1228
+ @overload
1229
+ def __call__(
1230
+ self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs
1231
+ ) -> Coroutine[Any, Any, T]:
1232
+ # TODO write specific docs for this overload
1233
+ ...
1234
+
1235
+ @overload
1236
+ def __call__(
1237
+ self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
1238
+ ) -> T:
1239
+ # TODO write specific docs for this overload
1240
+ ...
1241
+
1242
+ @overload
1243
+ def __call__(
1244
+ self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs
1245
+ ) -> Coroutine[Any, Any, T]:
1246
+ # TODO write specific docs for this overload
1247
+ ...
1248
+
1249
+ @overload
1250
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, T]: ...
1251
+ def __call__(_ASyncFunction self, *args: P.args, **kwargs: P.kwargs) -> MaybeCoro[T]:
1252
+ """Calls the wrapped function, defaulting to asynchronous execution.
1253
+
1254
+ This method overrides the base :meth:`ASyncFunction.__call__` to provide an asynchronous
1255
+ default behavior.
1256
+
1257
+ Args:
1258
+ *args: Positional arguments to pass to the wrapped function.
1259
+ **kwargs: Keyword arguments to pass to the wrapped function.
1260
+
1261
+ Raises:
1262
+ Exception: Any exception that may be raised by the wrapped function.
1263
+
1264
+ Returns:
1265
+ The result of the function call.
1266
+
1267
+ See Also:
1268
+ - :meth:`ASyncFunction.__call__`
1269
+ """
1270
+ return self.get_fn()(*args, **kwargs)
1271
+
1272
+ __docstring_append__ = ":class:`~a_sync.a_sync.function.ASyncFunctionAsyncDefault`, you can optionally pass `sync=True` or `asynchronous=False` to force it to run synchronously and return a value. Without either kwarg, it will return a coroutine for you to await."
1273
+
1274
+
1275
+ cdef class ASyncDecoratorSyncDefault(ASyncDecorator):
1276
+ @overload
1277
+ def __call__(self, func: AnyFn[Concatenate[B, P], T]) -> "ASyncBoundMethodSyncDefault[P, T]": # type: ignore [override]
1278
+ """
1279
+ Decorates a bound method with synchronous default behavior.
1280
+
1281
+ Args:
1282
+ func: The bound method to decorate.
1283
+
1284
+ Returns:
1285
+ An ASyncBoundMethodSyncDefault instance with synchronous default behavior.
1286
+
1287
+ See Also:
1288
+ - :class:`ASyncBoundMethodSyncDefault`
1289
+ """
1290
+
1291
+ @overload
1292
+ def __call__(self, func: AnyBoundMethod[P, T]) -> ASyncFunctionSyncDefault[P, T]: # type: ignore [override]
1293
+ """
1294
+ Decorates a bound method with synchronous default behavior.
1295
+
1296
+ Args:
1297
+ func: The bound method to decorate.
1298
+
1299
+ Returns:
1300
+ An ASyncFunctionSyncDefault instance with synchronous default behavior.
1301
+
1302
+ See Also:
1303
+ - :class:`ASyncFunctionSyncDefault`
1304
+ """
1305
+
1306
+ @overload
1307
+ def __call__(self, func: AnyFn[P, T]) -> ASyncFunctionSyncDefault[P, T]: # type: ignore [override]
1308
+ """
1309
+ Decorates a function with synchronous default behavior.
1310
+
1311
+ Args:
1312
+ func: The function to decorate.
1313
+
1314
+ Returns:
1315
+ An ASyncFunctionSyncDefault instance with synchronous default behavior.
1316
+
1317
+ See Also:
1318
+ - :class:`ASyncFunctionSyncDefault`
1319
+ """
1320
+
1321
+ def __call__(self, func: AnyFn[P, T]) -> ASyncFunctionSyncDefault[P, T]:
1322
+ # TODO write specific docs for this implementation
1323
+ return ASyncFunctionSyncDefault(func, **self.modifiers._modifiers)
1324
+
1325
+
1326
+ cdef class ASyncDecoratorAsyncDefault(ASyncDecorator):
1327
+ @overload
1328
+ def __call__(self, func: AnyFn[Concatenate[B, P], T]) -> "ASyncBoundMethodAsyncDefault[P, T]": # type: ignore [override]
1329
+ """
1330
+ Decorates a bound method with asynchronous default behavior.
1331
+
1332
+ Args:
1333
+ func: The bound method to decorate.
1334
+
1335
+ Returns:
1336
+ An ASyncBoundMethodAsyncDefault instance with asynchronous default behavior.
1337
+
1338
+ See Also:
1339
+ - :class:`ASyncBoundMethodAsyncDefault`
1340
+ """
1341
+
1342
+ @overload
1343
+ def __call__(self, func: AnyBoundMethod[P, T]) -> ASyncFunctionAsyncDefault[P, T]: # type: ignore [override]
1344
+ """
1345
+ Decorates a bound method with asynchronous default behavior.
1346
+
1347
+ Args:
1348
+ func: The bound method to decorate.
1349
+
1350
+ Returns:
1351
+ An ASyncFunctionAsyncDefault instance with asynchronous default behavior.
1352
+
1353
+ See Also:
1354
+ - :class:`ASyncFunctionAsyncDefault`
1355
+ """
1356
+
1357
+ @overload
1358
+ def __call__(self, func: AnyFn[P, T]) -> ASyncFunctionAsyncDefault[P, T]: # type: ignore [override]
1359
+ """
1360
+ Decorates a function with asynchronous default behavior.
1361
+
1362
+ Args:
1363
+ func: The function to decorate.
1364
+
1365
+ Returns:
1366
+ An ASyncFunctionAsyncDefault instance with asynchronous default behavior.
1367
+
1368
+ See Also:
1369
+ - :class:`ASyncFunctionAsyncDefault`
1370
+ """
1371
+
1372
+ def __call__(self, func: AnyFn[P, T]) -> ASyncFunctionAsyncDefault[P, T]:
1373
+ # TODO write specific docs for this implementation
1374
+ return ASyncFunctionAsyncDefault(func, **self.modifiers._modifiers)
1375
+
1376
+
1377
+ cdef inline void _import_TaskMapping():
1378
+ global TaskMapping
1379
+ from a_sync import TaskMapping