ez-a-sync 0.22.14__py3-none-any.whl → 0.22.15__py3-none-any.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 +4 -3
- a_sync/__init__.py +30 -12
- a_sync/_smart.py +132 -28
- a_sync/_typing.py +56 -12
- a_sync/a_sync/__init__.py +35 -10
- a_sync/a_sync/_descriptor.py +74 -26
- a_sync/a_sync/_flags.py +14 -6
- a_sync/a_sync/_helpers.py +8 -7
- a_sync/a_sync/_kwargs.py +3 -2
- a_sync/a_sync/_meta.py +120 -28
- a_sync/a_sync/abstract.py +102 -28
- a_sync/a_sync/base.py +34 -16
- a_sync/a_sync/config.py +47 -13
- a_sync/a_sync/decorator.py +239 -117
- a_sync/a_sync/function.py +416 -146
- a_sync/a_sync/method.py +197 -59
- a_sync/a_sync/modifiers/__init__.py +47 -5
- a_sync/a_sync/modifiers/cache/__init__.py +46 -17
- a_sync/a_sync/modifiers/cache/memory.py +86 -20
- a_sync/a_sync/modifiers/limiter.py +52 -22
- a_sync/a_sync/modifiers/manager.py +98 -16
- a_sync/a_sync/modifiers/semaphores.py +48 -15
- a_sync/a_sync/property.py +383 -82
- a_sync/a_sync/singleton.py +1 -0
- a_sync/aliases.py +0 -1
- a_sync/asyncio/__init__.py +4 -1
- a_sync/asyncio/as_completed.py +177 -49
- a_sync/asyncio/create_task.py +31 -17
- a_sync/asyncio/gather.py +72 -52
- a_sync/asyncio/utils.py +3 -3
- a_sync/exceptions.py +78 -23
- a_sync/executor.py +118 -71
- a_sync/future.py +575 -158
- a_sync/iter.py +110 -50
- a_sync/primitives/__init__.py +14 -2
- a_sync/primitives/_debug.py +13 -13
- a_sync/primitives/_loggable.py +5 -4
- a_sync/primitives/locks/__init__.py +5 -2
- a_sync/primitives/locks/counter.py +38 -36
- a_sync/primitives/locks/event.py +21 -7
- a_sync/primitives/locks/prio_semaphore.py +182 -62
- a_sync/primitives/locks/semaphore.py +78 -77
- a_sync/primitives/queue.py +560 -58
- a_sync/sphinx/__init__.py +0 -1
- a_sync/sphinx/ext.py +160 -50
- a_sync/task.py +262 -97
- a_sync/utils/__init__.py +12 -6
- a_sync/utils/iterators.py +127 -43
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/METADATA +1 -1
- ez_a_sync-0.22.15.dist-info/RECORD +74 -0
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/WHEEL +1 -1
- tests/conftest.py +1 -2
- tests/executor.py +112 -9
- tests/fixtures.py +61 -32
- tests/test_abstract.py +7 -4
- tests/test_as_completed.py +54 -21
- tests/test_base.py +66 -17
- tests/test_cache.py +31 -15
- tests/test_decorator.py +54 -28
- tests/test_executor.py +8 -13
- tests/test_future.py +45 -8
- tests/test_gather.py +8 -2
- tests/test_helpers.py +2 -0
- tests/test_iter.py +55 -13
- tests/test_limiter.py +5 -3
- tests/test_meta.py +23 -9
- tests/test_modified.py +4 -1
- tests/test_semaphore.py +15 -8
- tests/test_singleton.py +15 -10
- tests/test_task.py +126 -28
- ez_a_sync-0.22.14.dist-info/RECORD +0 -74
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/LICENSE.txt +0 -0
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/top_level.txt +0 -0
a_sync/a_sync/method.py
CHANGED
|
@@ -16,7 +16,11 @@ from inspect import isawaitable
|
|
|
16
16
|
from a_sync._typing import *
|
|
17
17
|
from a_sync.a_sync import _helpers, _kwargs
|
|
18
18
|
from a_sync.a_sync._descriptor import ASyncDescriptor
|
|
19
|
-
from a_sync.a_sync.function import
|
|
19
|
+
from a_sync.a_sync.function import (
|
|
20
|
+
ASyncFunction,
|
|
21
|
+
ASyncFunctionAsyncDefault,
|
|
22
|
+
ASyncFunctionSyncDefault,
|
|
23
|
+
)
|
|
20
24
|
|
|
21
25
|
if TYPE_CHECKING:
|
|
22
26
|
from a_sync import TaskMapping
|
|
@@ -24,9 +28,10 @@ if TYPE_CHECKING:
|
|
|
24
28
|
|
|
25
29
|
logger = logging.getLogger(__name__)
|
|
26
30
|
|
|
31
|
+
|
|
27
32
|
class ASyncMethodDescriptor(ASyncDescriptor[I, P, T]):
|
|
28
33
|
"""
|
|
29
|
-
This class provides the core functionality for creating :class:`ASyncBoundMethod` objects,
|
|
34
|
+
This class provides the core functionality for creating :class:`ASyncBoundMethod` objects,
|
|
30
35
|
which can be used to define methods that can be called both synchronously and asynchronously.
|
|
31
36
|
"""
|
|
32
37
|
|
|
@@ -46,14 +51,22 @@ class ASyncMethodDescriptor(ASyncDescriptor[I, P, T]):
|
|
|
46
51
|
The result of the method call.
|
|
47
52
|
"""
|
|
48
53
|
# NOTE: This is only used by TaskMapping atm # TODO: use it elsewhere
|
|
49
|
-
logger.debug(
|
|
54
|
+
logger.debug(
|
|
55
|
+
"awaiting %s for instance: %s args: %s kwargs: %s",
|
|
56
|
+
self,
|
|
57
|
+
instance,
|
|
58
|
+
args,
|
|
59
|
+
kwargs,
|
|
60
|
+
)
|
|
50
61
|
return await self.__get__(instance, None)(*args, **kwargs)
|
|
51
62
|
|
|
52
63
|
@overload
|
|
53
|
-
def __get__(self, instance: None, owner: Type[I]) -> Self
|
|
64
|
+
def __get__(self, instance: None, owner: Type[I]) -> Self: ...
|
|
54
65
|
@overload
|
|
55
|
-
def __get__(self, instance: I, owner: Type[I]) -> "ASyncBoundMethod[I, P, T]"
|
|
56
|
-
def __get__(
|
|
66
|
+
def __get__(self, instance: I, owner: Type[I]) -> "ASyncBoundMethod[I, P, T]": ...
|
|
67
|
+
def __get__(
|
|
68
|
+
self, instance: Optional[I], owner: Type[I]
|
|
69
|
+
) -> Union[Self, "ASyncBoundMethod[I, P, T]"]:
|
|
57
70
|
"""
|
|
58
71
|
Get the bound method or the descriptor itself.
|
|
59
72
|
|
|
@@ -72,20 +85,42 @@ class ASyncMethodDescriptor(ASyncDescriptor[I, P, T]):
|
|
|
72
85
|
bound._cache_handle.cancel()
|
|
73
86
|
except KeyError:
|
|
74
87
|
from a_sync.a_sync.abstract import ASyncABC
|
|
88
|
+
|
|
75
89
|
if self.default == "sync":
|
|
76
|
-
bound = ASyncBoundMethodSyncDefault(
|
|
90
|
+
bound = ASyncBoundMethodSyncDefault(
|
|
91
|
+
instance, self.__wrapped__, self.__is_async_def__, **self.modifiers
|
|
92
|
+
)
|
|
77
93
|
elif self.default == "async":
|
|
78
|
-
bound = ASyncBoundMethodAsyncDefault(
|
|
94
|
+
bound = ASyncBoundMethodAsyncDefault(
|
|
95
|
+
instance, self.__wrapped__, self.__is_async_def__, **self.modifiers
|
|
96
|
+
)
|
|
79
97
|
elif isinstance(instance, ASyncABC):
|
|
80
98
|
try:
|
|
81
99
|
if instance.__a_sync_instance_should_await__:
|
|
82
|
-
bound = ASyncBoundMethodSyncDefault(
|
|
100
|
+
bound = ASyncBoundMethodSyncDefault(
|
|
101
|
+
instance,
|
|
102
|
+
self.__wrapped__,
|
|
103
|
+
self.__is_async_def__,
|
|
104
|
+
**self.modifiers,
|
|
105
|
+
)
|
|
83
106
|
else:
|
|
84
|
-
bound = ASyncBoundMethodAsyncDefault(
|
|
107
|
+
bound = ASyncBoundMethodAsyncDefault(
|
|
108
|
+
instance,
|
|
109
|
+
self.__wrapped__,
|
|
110
|
+
self.__is_async_def__,
|
|
111
|
+
**self.modifiers,
|
|
112
|
+
)
|
|
85
113
|
except AttributeError:
|
|
86
|
-
bound = ASyncBoundMethod(
|
|
114
|
+
bound = ASyncBoundMethod(
|
|
115
|
+
instance,
|
|
116
|
+
self.__wrapped__,
|
|
117
|
+
self.__is_async_def__,
|
|
118
|
+
**self.modifiers,
|
|
119
|
+
)
|
|
87
120
|
else:
|
|
88
|
-
bound = ASyncBoundMethod(
|
|
121
|
+
bound = ASyncBoundMethod(
|
|
122
|
+
instance, self.__wrapped__, self.__is_async_def__, **self.modifiers
|
|
123
|
+
)
|
|
89
124
|
instance.__dict__[self.field_name] = bound
|
|
90
125
|
logger.debug("new bound method: %s", bound)
|
|
91
126
|
# Handler for popping unused bound methods from bound method cache
|
|
@@ -103,7 +138,9 @@ class ASyncMethodDescriptor(ASyncDescriptor[I, P, T]):
|
|
|
103
138
|
Raises:
|
|
104
139
|
:class:`RuntimeError`: Always raised to prevent setting.
|
|
105
140
|
"""
|
|
106
|
-
raise RuntimeError(
|
|
141
|
+
raise RuntimeError(
|
|
142
|
+
f"cannot set {self.field_name}, {self} is what you get. sorry."
|
|
143
|
+
)
|
|
107
144
|
|
|
108
145
|
def __delete__(self, instance):
|
|
109
146
|
"""
|
|
@@ -115,7 +152,9 @@ class ASyncMethodDescriptor(ASyncDescriptor[I, P, T]):
|
|
|
115
152
|
Raises:
|
|
116
153
|
:class:`RuntimeError`: Always raised to prevent deletion.
|
|
117
154
|
"""
|
|
118
|
-
raise RuntimeError(
|
|
155
|
+
raise RuntimeError(
|
|
156
|
+
f"cannot delete {self.field_name}, you're stuck with {self} forever. sorry."
|
|
157
|
+
)
|
|
119
158
|
|
|
120
159
|
@functools.cached_property
|
|
121
160
|
def __is_async_def__(self) -> bool:
|
|
@@ -138,7 +177,10 @@ class ASyncMethodDescriptor(ASyncDescriptor[I, P, T]):
|
|
|
138
177
|
A timer handle for cache management.
|
|
139
178
|
"""
|
|
140
179
|
# NOTE: use `instance.__dict__.pop` instead of `delattr` so we don't create a strong ref to `instance`
|
|
141
|
-
return asyncio.get_event_loop().call_later(
|
|
180
|
+
return asyncio.get_event_loop().call_later(
|
|
181
|
+
300, instance.__dict__.pop, self.field_name
|
|
182
|
+
)
|
|
183
|
+
|
|
142
184
|
|
|
143
185
|
@final
|
|
144
186
|
class ASyncMethodDescriptorSyncDefault(ASyncMethodDescriptor[I, P, T]):
|
|
@@ -168,10 +210,18 @@ class ASyncMethodDescriptorSyncDefault(ASyncMethodDescriptor[I, P, T]):
|
|
|
168
210
|
"""Synchronous default version of the :meth:`~ASyncMethodDescriptor.sum` method."""
|
|
169
211
|
|
|
170
212
|
@overload
|
|
171
|
-
def __get__(
|
|
213
|
+
def __get__(
|
|
214
|
+
self, instance: None, owner: Type[I] = None
|
|
215
|
+
) -> "ASyncMethodDescriptorSyncDefault[I, P, T]": ...
|
|
172
216
|
@overload
|
|
173
|
-
def __get__(
|
|
174
|
-
|
|
217
|
+
def __get__(
|
|
218
|
+
self, instance: I, owner: Type[I] = None
|
|
219
|
+
) -> "ASyncBoundMethodSyncDefault[I, P, T]": ...
|
|
220
|
+
def __get__(
|
|
221
|
+
self, instance: Optional[I], owner: Type[I] = None
|
|
222
|
+
) -> (
|
|
223
|
+
"Union[ASyncMethodDescriptorSyncDefault, ASyncBoundMethodSyncDefault[I, P, T]]"
|
|
224
|
+
):
|
|
175
225
|
"""
|
|
176
226
|
Get the bound method or the descriptor itself.
|
|
177
227
|
|
|
@@ -189,13 +239,16 @@ class ASyncMethodDescriptorSyncDefault(ASyncMethodDescriptor[I, P, T]):
|
|
|
189
239
|
# we will set a new one in the finally block
|
|
190
240
|
bound._cache_handle.cancel()
|
|
191
241
|
except KeyError:
|
|
192
|
-
bound = ASyncBoundMethodSyncDefault(
|
|
242
|
+
bound = ASyncBoundMethodSyncDefault(
|
|
243
|
+
instance, self.__wrapped__, self.__is_async_def__, **self.modifiers
|
|
244
|
+
)
|
|
193
245
|
instance.__dict__[self.field_name] = bound
|
|
194
246
|
logger.debug("new bound method: %s", bound)
|
|
195
247
|
# Handler for popping unused bound methods from bound method cache
|
|
196
248
|
bound._cache_handle = self._get_cache_handle(instance)
|
|
197
249
|
return bound
|
|
198
250
|
|
|
251
|
+
|
|
199
252
|
@final
|
|
200
253
|
class ASyncMethodDescriptorAsyncDefault(ASyncMethodDescriptor[I, P, T]):
|
|
201
254
|
"""
|
|
@@ -213,21 +266,27 @@ class ASyncMethodDescriptorAsyncDefault(ASyncMethodDescriptor[I, P, T]):
|
|
|
213
266
|
|
|
214
267
|
all: ASyncFunctionAsyncDefault[Concatenate[AnyIterable[I], P], bool]
|
|
215
268
|
"""Asynchronous default version of the :meth:`~ASyncMethodDescriptor.all` method."""
|
|
216
|
-
|
|
269
|
+
|
|
217
270
|
min: ASyncFunctionAsyncDefault[Concatenate[AnyIterable[I], P], T]
|
|
218
271
|
"""Asynchronous default version of the :meth:`~ASyncMethodDescriptor.min` method."""
|
|
219
|
-
|
|
272
|
+
|
|
220
273
|
max: ASyncFunctionAsyncDefault[Concatenate[AnyIterable[I], P], T]
|
|
221
274
|
"""Asynchronous default version of the :meth:`~ASyncMethodDescriptor.max` method."""
|
|
222
|
-
|
|
275
|
+
|
|
223
276
|
sum: ASyncFunctionAsyncDefault[Concatenate[AnyIterable[I], P], T]
|
|
224
277
|
"""Asynchronous default version of the :meth:`~ASyncMethodDescriptor.sum` method."""
|
|
225
278
|
|
|
226
279
|
@overload
|
|
227
|
-
def __get__(
|
|
280
|
+
def __get__(
|
|
281
|
+
self, instance: None, owner: Type[I]
|
|
282
|
+
) -> "ASyncMethodDescriptorAsyncDefault[I, P, T]": ...
|
|
228
283
|
@overload
|
|
229
|
-
def __get__(
|
|
230
|
-
|
|
284
|
+
def __get__(
|
|
285
|
+
self, instance: I, owner: Type[I]
|
|
286
|
+
) -> "ASyncBoundMethodAsyncDefault[I, P, T]": ...
|
|
287
|
+
def __get__(
|
|
288
|
+
self, instance: Optional[I], owner: Type[I]
|
|
289
|
+
) -> "Union[ASyncMethodDescriptorAsyncDefault, ASyncBoundMethodAsyncDefault[I, P, T]]":
|
|
231
290
|
"""
|
|
232
291
|
Get the bound method or the descriptor itself.
|
|
233
292
|
|
|
@@ -245,13 +304,16 @@ class ASyncMethodDescriptorAsyncDefault(ASyncMethodDescriptor[I, P, T]):
|
|
|
245
304
|
# we will set a new one in the finally block
|
|
246
305
|
bound._cache_handle.cancel()
|
|
247
306
|
except KeyError:
|
|
248
|
-
bound = ASyncBoundMethodAsyncDefault(
|
|
307
|
+
bound = ASyncBoundMethodAsyncDefault(
|
|
308
|
+
instance, self.__wrapped__, self.__is_async_def__, **self.modifiers
|
|
309
|
+
)
|
|
249
310
|
instance.__dict__[self.field_name] = bound
|
|
250
311
|
logger.debug("new bound method: %s", bound)
|
|
251
312
|
# Handler for popping unused bound methods from bound method cache
|
|
252
313
|
bound._cache_handle = self._get_cache_handle(instance)
|
|
253
314
|
return bound
|
|
254
315
|
|
|
316
|
+
|
|
255
317
|
class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
256
318
|
"""
|
|
257
319
|
A bound method that can be called both synchronously and asynchronously.
|
|
@@ -259,6 +321,7 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
259
321
|
This class represents a method bound to an instance, which can be called
|
|
260
322
|
either synchronously or asynchronously based on various conditions.
|
|
261
323
|
"""
|
|
324
|
+
|
|
262
325
|
# NOTE: this is created by the Descriptor
|
|
263
326
|
|
|
264
327
|
_cache_handle: asyncio.TimerHandle
|
|
@@ -273,9 +336,9 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
273
336
|
__slots__ = "_is_async_def", "__weakself__"
|
|
274
337
|
|
|
275
338
|
def __init__(
|
|
276
|
-
self,
|
|
277
|
-
instance: I,
|
|
278
|
-
unbound: AnyFn[Concatenate[I, P], T],
|
|
339
|
+
self,
|
|
340
|
+
instance: I,
|
|
341
|
+
unbound: AnyFn[Concatenate[I, P], T],
|
|
279
342
|
async_def: bool,
|
|
280
343
|
**modifiers: Unpack[ModifierKwargs],
|
|
281
344
|
) -> None:
|
|
@@ -312,15 +375,21 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
312
375
|
return f"<{self.__class__.__name__} for function COLLECTED.COLLECTED.{self.__name__} bound to {self.__weakself__}>"
|
|
313
376
|
|
|
314
377
|
@overload
|
|
315
|
-
def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T
|
|
378
|
+
def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T: ...
|
|
316
379
|
@overload
|
|
317
|
-
def __call__(
|
|
380
|
+
def __call__(
|
|
381
|
+
self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs
|
|
382
|
+
) -> Coroutine[Any, Any, T]: ...
|
|
318
383
|
@overload
|
|
319
|
-
def __call__(
|
|
384
|
+
def __call__(
|
|
385
|
+
self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
|
|
386
|
+
) -> T: ...
|
|
320
387
|
@overload
|
|
321
|
-
def __call__(
|
|
388
|
+
def __call__(
|
|
389
|
+
self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs
|
|
390
|
+
) -> Coroutine[Any, Any, T]: ...
|
|
322
391
|
@overload
|
|
323
|
-
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> MaybeCoro[T]
|
|
392
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> MaybeCoro[T]: ...
|
|
324
393
|
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> MaybeCoro[T]:
|
|
325
394
|
"""
|
|
326
395
|
Call the bound method.
|
|
@@ -345,9 +414,13 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
345
414
|
pass
|
|
346
415
|
elif self._should_await(kwargs):
|
|
347
416
|
# The awaitable was not awaited, so now we need to check the flag as defined on 'self' and await if appropriate.
|
|
348
|
-
logger.debug(
|
|
417
|
+
logger.debug(
|
|
418
|
+
"awaiting %s for %s args: %s kwargs: %s", coro, self, args, kwargs
|
|
419
|
+
)
|
|
349
420
|
retval = _helpers._await(coro)
|
|
350
|
-
logger.debug(
|
|
421
|
+
logger.debug(
|
|
422
|
+
"returning %s for %s args: %s kwargs: %s", retval, self, args, kwargs
|
|
423
|
+
)
|
|
351
424
|
return retval # type: ignore [call-overload, return-value]
|
|
352
425
|
|
|
353
426
|
@property
|
|
@@ -375,9 +448,16 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
375
448
|
True if bound to an ASyncABC instance, False otherwise.
|
|
376
449
|
"""
|
|
377
450
|
from a_sync.a_sync.abstract import ASyncABC
|
|
451
|
+
|
|
378
452
|
return isinstance(self.__self__, ASyncABC)
|
|
379
453
|
|
|
380
|
-
def map(
|
|
454
|
+
def map(
|
|
455
|
+
self,
|
|
456
|
+
*iterables: AnyIterable[I],
|
|
457
|
+
concurrency: Optional[int] = None,
|
|
458
|
+
task_name: str = "",
|
|
459
|
+
**kwargs: P.kwargs,
|
|
460
|
+
) -> "TaskMapping[I, T]":
|
|
381
461
|
"""
|
|
382
462
|
Create a TaskMapping for this method.
|
|
383
463
|
|
|
@@ -391,9 +471,18 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
391
471
|
A TaskMapping instance for this method.
|
|
392
472
|
"""
|
|
393
473
|
from a_sync import TaskMapping
|
|
394
|
-
return TaskMapping(self, *iterables, concurrency=concurrency, name=task_name, **kwargs)
|
|
395
474
|
|
|
396
|
-
|
|
475
|
+
return TaskMapping(
|
|
476
|
+
self, *iterables, concurrency=concurrency, name=task_name, **kwargs
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
async def any(
|
|
480
|
+
self,
|
|
481
|
+
*iterables: AnyIterable[I],
|
|
482
|
+
concurrency: Optional[int] = None,
|
|
483
|
+
task_name: str = "",
|
|
484
|
+
**kwargs: P.kwargs,
|
|
485
|
+
) -> bool:
|
|
397
486
|
"""
|
|
398
487
|
Check if any of the results are truthy.
|
|
399
488
|
|
|
@@ -406,9 +495,17 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
406
495
|
Returns:
|
|
407
496
|
True if any result is truthy, False otherwise.
|
|
408
497
|
"""
|
|
409
|
-
return await self.map(
|
|
498
|
+
return await self.map(
|
|
499
|
+
*iterables, concurrency=concurrency, task_name=task_name, **kwargs
|
|
500
|
+
).any(pop=True, sync=False)
|
|
410
501
|
|
|
411
|
-
async def all(
|
|
502
|
+
async def all(
|
|
503
|
+
self,
|
|
504
|
+
*iterables: AnyIterable[I],
|
|
505
|
+
concurrency: Optional[int] = None,
|
|
506
|
+
task_name: str = "",
|
|
507
|
+
**kwargs: P.kwargs,
|
|
508
|
+
) -> bool:
|
|
412
509
|
"""
|
|
413
510
|
Check if all of the results are truthy.
|
|
414
511
|
|
|
@@ -421,9 +518,17 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
421
518
|
Returns:
|
|
422
519
|
True if all results are truthy, False otherwise.
|
|
423
520
|
"""
|
|
424
|
-
return await self.map(
|
|
521
|
+
return await self.map(
|
|
522
|
+
*iterables, concurrency=concurrency, task_name=task_name, **kwargs
|
|
523
|
+
).all(pop=True, sync=False)
|
|
425
524
|
|
|
426
|
-
async def min(
|
|
525
|
+
async def min(
|
|
526
|
+
self,
|
|
527
|
+
*iterables: AnyIterable[I],
|
|
528
|
+
concurrency: Optional[int] = None,
|
|
529
|
+
task_name: str = "",
|
|
530
|
+
**kwargs: P.kwargs,
|
|
531
|
+
) -> T:
|
|
427
532
|
"""
|
|
428
533
|
Find the minimum result.
|
|
429
534
|
|
|
@@ -436,9 +541,17 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
436
541
|
Returns:
|
|
437
542
|
The minimum result.
|
|
438
543
|
"""
|
|
439
|
-
return await self.map(
|
|
544
|
+
return await self.map(
|
|
545
|
+
*iterables, concurrency=concurrency, task_name=task_name, **kwargs
|
|
546
|
+
).min(pop=True, sync=False)
|
|
440
547
|
|
|
441
|
-
async def max(
|
|
548
|
+
async def max(
|
|
549
|
+
self,
|
|
550
|
+
*iterables: AnyIterable[I],
|
|
551
|
+
concurrency: Optional[int] = None,
|
|
552
|
+
task_name: str = "",
|
|
553
|
+
**kwargs: P.kwargs,
|
|
554
|
+
) -> T:
|
|
442
555
|
"""
|
|
443
556
|
Find the maximum result.
|
|
444
557
|
|
|
@@ -451,9 +564,17 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
451
564
|
Returns:
|
|
452
565
|
The maximum result.
|
|
453
566
|
"""
|
|
454
|
-
return await self.map(
|
|
567
|
+
return await self.map(
|
|
568
|
+
*iterables, concurrency=concurrency, task_name=task_name, **kwargs
|
|
569
|
+
).max(pop=True, sync=False)
|
|
455
570
|
|
|
456
|
-
async def sum(
|
|
571
|
+
async def sum(
|
|
572
|
+
self,
|
|
573
|
+
*iterables: AnyIterable[I],
|
|
574
|
+
concurrency: Optional[int] = None,
|
|
575
|
+
task_name: str = "",
|
|
576
|
+
**kwargs: P.kwargs,
|
|
577
|
+
) -> T:
|
|
457
578
|
"""
|
|
458
579
|
Calculate the sum of the results.
|
|
459
580
|
|
|
@@ -466,7 +587,9 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]):
|
|
|
466
587
|
Returns:
|
|
467
588
|
The sum of the results.
|
|
468
589
|
"""
|
|
469
|
-
return await self.map(
|
|
590
|
+
return await self.map(
|
|
591
|
+
*iterables, concurrency=concurrency, task_name=task_name, **kwargs
|
|
592
|
+
).sum(pop=True, sync=False)
|
|
470
593
|
|
|
471
594
|
def _should_await(self, kwargs: dict) -> bool:
|
|
472
595
|
"""
|
|
@@ -503,7 +626,9 @@ class ASyncBoundMethodSyncDefault(ASyncBoundMethod[I, P, T]):
|
|
|
503
626
|
A bound method with synchronous default behavior.
|
|
504
627
|
"""
|
|
505
628
|
|
|
506
|
-
def __get__(
|
|
629
|
+
def __get__(
|
|
630
|
+
self, instance: Optional[I], owner: Type[I]
|
|
631
|
+
) -> ASyncFunctionSyncDefault[P, T]:
|
|
507
632
|
"""
|
|
508
633
|
Get the bound method or descriptor.
|
|
509
634
|
|
|
@@ -517,15 +642,21 @@ class ASyncBoundMethodSyncDefault(ASyncBoundMethod[I, P, T]):
|
|
|
517
642
|
return ASyncBoundMethod.__get__(self, instance, owner)
|
|
518
643
|
|
|
519
644
|
@overload
|
|
520
|
-
def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T
|
|
645
|
+
def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T: ...
|
|
521
646
|
@overload
|
|
522
|
-
def __call__(
|
|
647
|
+
def __call__(
|
|
648
|
+
self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs
|
|
649
|
+
) -> Coroutine[Any, Any, T]: ...
|
|
523
650
|
@overload
|
|
524
|
-
def __call__(
|
|
651
|
+
def __call__(
|
|
652
|
+
self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
|
|
653
|
+
) -> T: ...
|
|
525
654
|
@overload
|
|
526
|
-
def __call__(
|
|
655
|
+
def __call__(
|
|
656
|
+
self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs
|
|
657
|
+
) -> Coroutine[Any, Any, T]: ...
|
|
527
658
|
@overload
|
|
528
|
-
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T
|
|
659
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: ...
|
|
529
660
|
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
530
661
|
"""
|
|
531
662
|
Call the bound method with synchronous default behavior.
|
|
@@ -539,6 +670,7 @@ class ASyncBoundMethodSyncDefault(ASyncBoundMethod[I, P, T]):
|
|
|
539
670
|
"""
|
|
540
671
|
return ASyncBoundMethod.__call__(self, *args, **kwargs)
|
|
541
672
|
|
|
673
|
+
|
|
542
674
|
class ASyncBoundMethodAsyncDefault(ASyncBoundMethod[I, P, T]):
|
|
543
675
|
"""
|
|
544
676
|
A bound method with asynchronous default behavior.
|
|
@@ -558,15 +690,21 @@ class ASyncBoundMethodAsyncDefault(ASyncBoundMethod[I, P, T]):
|
|
|
558
690
|
return ASyncBoundMethod.__get__(self, instance, owner)
|
|
559
691
|
|
|
560
692
|
@overload
|
|
561
|
-
def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T
|
|
693
|
+
def __call__(self, *args: P.args, sync: Literal[True], **kwargs: P.kwargs) -> T: ...
|
|
562
694
|
@overload
|
|
563
|
-
def __call__(
|
|
695
|
+
def __call__(
|
|
696
|
+
self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs
|
|
697
|
+
) -> Coroutine[Any, Any, T]: ...
|
|
564
698
|
@overload
|
|
565
|
-
def __call__(
|
|
699
|
+
def __call__(
|
|
700
|
+
self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
|
|
701
|
+
) -> T: ...
|
|
566
702
|
@overload
|
|
567
|
-
def __call__(
|
|
703
|
+
def __call__(
|
|
704
|
+
self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs
|
|
705
|
+
) -> Coroutine[Any, Any, T]: ...
|
|
568
706
|
@overload
|
|
569
|
-
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, T]
|
|
707
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, T]: ...
|
|
570
708
|
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, T]:
|
|
571
709
|
"""
|
|
572
710
|
Call the bound method with asynchronous default behavior.
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file contains all logic for ez-a-sync's "modifiers".
|
|
3
|
+
|
|
4
|
+
Modifiers modify the behavior of ez-a-sync's ASync objects in various ways.
|
|
5
|
+
|
|
6
|
+
Submodules:
|
|
7
|
+
cache: Handles caching mechanisms for async functions.
|
|
8
|
+
limiter: Manages rate limiting for async functions.
|
|
9
|
+
manager: Provides management of valid modifiers and their application.
|
|
10
|
+
semaphores: Implements semaphore logic for controlling concurrency.
|
|
11
|
+
|
|
12
|
+
The modifiers available are:
|
|
13
|
+
- `cache_type`: Specifies the type of cache to use, such as 'memory'.
|
|
14
|
+
- `cache_typed`: Determines if types are considered for cache keys.
|
|
15
|
+
- `ram_cache_maxsize`: Sets the maximum size for the LRU cache.
|
|
16
|
+
- `ram_cache_ttl`: Defines the time-to-live for items in the cache.
|
|
17
|
+
- `runs_per_minute`: Sets a rate limit for function execution.
|
|
18
|
+
- `semaphore`: Specifies a semaphore for controlling concurrency.
|
|
19
|
+
- `executor`: Defines the executor for synchronous functions.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
1
23
|
from aiolimiter import AsyncLimiter
|
|
2
24
|
|
|
3
25
|
from a_sync._typing import *
|
|
@@ -6,14 +28,34 @@ from a_sync.a_sync.modifiers.manager import valid_modifiers
|
|
|
6
28
|
|
|
7
29
|
|
|
8
30
|
def get_modifiers_from(thing: Union[dict, type, object]) -> ModifierKwargs:
|
|
31
|
+
"""Extracts valid modifiers from a given object, type, or dictionary.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
thing: The source from which to extract modifiers. It can be a dictionary,
|
|
35
|
+
a type, or an object.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A ModifierKwargs object containing the valid modifiers extracted from the input.
|
|
39
|
+
"""
|
|
9
40
|
if isinstance(thing, dict):
|
|
10
41
|
apply_class_defined_modifiers(thing)
|
|
11
42
|
return ModifierKwargs({modifier: thing[modifier] for modifier in valid_modifiers if modifier in thing}) # type: ignore [misc]
|
|
12
43
|
return ModifierKwargs({modifier: getattr(thing, modifier) for modifier in valid_modifiers if hasattr(thing, modifier)}) # type: ignore [misc]
|
|
13
44
|
|
|
45
|
+
|
|
14
46
|
def apply_class_defined_modifiers(attrs_from_metaclass: dict):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
47
|
+
"""Applies class-defined modifiers to a dictionary of attributes.
|
|
48
|
+
|
|
49
|
+
This function modifies the input dictionary in place. If the 'semaphore' key
|
|
50
|
+
is present and its value is an integer, it is converted to a ThreadsafeSemaphore.
|
|
51
|
+
If the 'runs_per_minute' key is present and its value is an integer, it is
|
|
52
|
+
converted to an AsyncLimiter. If these keys are not present or their values
|
|
53
|
+
are not integers, the function will silently do nothing.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
attrs_from_metaclass: A dictionary of attributes from a metaclass.
|
|
57
|
+
"""
|
|
58
|
+
if isinstance(val := attrs_from_metaclass.get("semaphore"), int):
|
|
59
|
+
attrs_from_metaclass["semaphore"] = ThreadsafeSemaphore(val)
|
|
60
|
+
if isinstance(val := attrs_from_metaclass.get("runs_per_minute"), int):
|
|
61
|
+
attrs_from_metaclass["runs_per_minute"] = AsyncLimiter(val)
|
|
@@ -8,53 +8,82 @@ from a_sync.a_sync.modifiers.cache.memory import apply_async_memory_cache
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class CacheArgs(TypedDict):
|
|
11
|
+
"""Typed dictionary for cache arguments."""
|
|
12
|
+
|
|
11
13
|
cache_type: CacheType
|
|
12
14
|
cache_typed: bool
|
|
13
15
|
ram_cache_maxsize: Optional[int]
|
|
14
16
|
ram_cache_ttl: Optional[int]
|
|
15
17
|
|
|
18
|
+
|
|
16
19
|
@overload
|
|
17
20
|
def apply_async_cache(
|
|
18
|
-
coro_fn: Literal[None],
|
|
19
21
|
**modifiers: Unpack[CacheArgs],
|
|
20
|
-
) -> AsyncDecorator[P, T]
|
|
21
|
-
|
|
22
|
+
) -> AsyncDecorator[P, T]:
|
|
23
|
+
"""Overload for when no coroutine function is provided."""
|
|
24
|
+
|
|
25
|
+
|
|
22
26
|
@overload
|
|
23
27
|
def apply_async_cache(
|
|
24
28
|
coro_fn: int,
|
|
25
29
|
**modifiers: Unpack[CacheArgs],
|
|
26
|
-
) -> AsyncDecorator[P, T]
|
|
27
|
-
|
|
30
|
+
) -> AsyncDecorator[P, T]:
|
|
31
|
+
"""Overload for when an integer is provided as the coroutine function."""
|
|
32
|
+
|
|
33
|
+
|
|
28
34
|
@overload
|
|
29
35
|
def apply_async_cache(
|
|
30
36
|
coro_fn: CoroFn[P, T],
|
|
31
37
|
**modifiers: Unpack[CacheArgs],
|
|
32
|
-
) -> CoroFn[P, T]
|
|
33
|
-
|
|
38
|
+
) -> CoroFn[P, T]:
|
|
39
|
+
"""Overload for when a coroutine function is provided."""
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
def apply_async_cache(
|
|
35
43
|
coro_fn: Union[CoroFn[P, T], CacheType, int] = None,
|
|
36
|
-
cache_type: CacheType =
|
|
44
|
+
cache_type: CacheType = "memory",
|
|
37
45
|
cache_typed: bool = False,
|
|
38
46
|
ram_cache_maxsize: Optional[int] = None,
|
|
39
47
|
ram_cache_ttl: Optional[int] = None,
|
|
40
48
|
) -> AsyncDecoratorOrCoroFn[P, T]:
|
|
41
|
-
|
|
49
|
+
"""Applies an asynchronous cache to a coroutine function.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
coro_fn: The coroutine function to apply the cache to, or an integer to set as the max size.
|
|
53
|
+
cache_type: The type of cache to use. Currently, only 'memory' is implemented.
|
|
54
|
+
cache_typed: Whether to consider types for cache keys.
|
|
55
|
+
ram_cache_maxsize: The maximum size for the LRU cache. If set to an integer, it overrides coro_fn.
|
|
56
|
+
ram_cache_ttl: The time-to-live for items in the LRU cache.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
TypeError: If 'ram_cache_maxsize' is not an integer or None.
|
|
60
|
+
FunctionNotAsync: If the provided function is not asynchronous.
|
|
61
|
+
NotImplementedError: If an unsupported cache type is specified.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
A decorator or the decorated coroutine function.
|
|
65
|
+
"""
|
|
66
|
+
|
|
42
67
|
# Parse Inputs
|
|
43
68
|
if isinstance(coro_fn, int):
|
|
44
69
|
assert ram_cache_maxsize is None
|
|
45
70
|
ram_cache_maxsize = coro_fn
|
|
46
71
|
coro_fn = None
|
|
47
|
-
|
|
48
|
-
# Validate
|
|
72
|
+
|
|
73
|
+
# Validate
|
|
49
74
|
elif coro_fn is None:
|
|
50
75
|
if ram_cache_maxsize is not None and not isinstance(ram_cache_maxsize, int):
|
|
51
|
-
raise TypeError(
|
|
76
|
+
raise TypeError(
|
|
77
|
+
"'lru_cache_maxsize' must be an integer or None.", ram_cache_maxsize
|
|
78
|
+
)
|
|
52
79
|
elif not asyncio.iscoroutinefunction(coro_fn):
|
|
53
80
|
raise exceptions.FunctionNotAsync(coro_fn)
|
|
54
|
-
|
|
55
|
-
if cache_type ==
|
|
56
|
-
cache_decorator = apply_async_memory_cache(
|
|
81
|
+
|
|
82
|
+
if cache_type == "memory":
|
|
83
|
+
cache_decorator = apply_async_memory_cache(
|
|
84
|
+
maxsize=ram_cache_maxsize, ttl=ram_cache_ttl, typed=cache_typed
|
|
85
|
+
)
|
|
57
86
|
return cache_decorator if coro_fn is None else cache_decorator(coro_fn)
|
|
58
|
-
elif cache_type ==
|
|
87
|
+
elif cache_type == "disk":
|
|
59
88
|
pass
|
|
60
|
-
raise NotImplementedError(f"cache_type: {cache_type}")
|
|
89
|
+
raise NotImplementedError(f"cache_type: {cache_type}")
|