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.

Files changed (73) hide show
  1. a_sync/ENVIRONMENT_VARIABLES.py +4 -3
  2. a_sync/__init__.py +30 -12
  3. a_sync/_smart.py +132 -28
  4. a_sync/_typing.py +56 -12
  5. a_sync/a_sync/__init__.py +35 -10
  6. a_sync/a_sync/_descriptor.py +74 -26
  7. a_sync/a_sync/_flags.py +14 -6
  8. a_sync/a_sync/_helpers.py +8 -7
  9. a_sync/a_sync/_kwargs.py +3 -2
  10. a_sync/a_sync/_meta.py +120 -28
  11. a_sync/a_sync/abstract.py +102 -28
  12. a_sync/a_sync/base.py +34 -16
  13. a_sync/a_sync/config.py +47 -13
  14. a_sync/a_sync/decorator.py +239 -117
  15. a_sync/a_sync/function.py +416 -146
  16. a_sync/a_sync/method.py +197 -59
  17. a_sync/a_sync/modifiers/__init__.py +47 -5
  18. a_sync/a_sync/modifiers/cache/__init__.py +46 -17
  19. a_sync/a_sync/modifiers/cache/memory.py +86 -20
  20. a_sync/a_sync/modifiers/limiter.py +52 -22
  21. a_sync/a_sync/modifiers/manager.py +98 -16
  22. a_sync/a_sync/modifiers/semaphores.py +48 -15
  23. a_sync/a_sync/property.py +383 -82
  24. a_sync/a_sync/singleton.py +1 -0
  25. a_sync/aliases.py +0 -1
  26. a_sync/asyncio/__init__.py +4 -1
  27. a_sync/asyncio/as_completed.py +177 -49
  28. a_sync/asyncio/create_task.py +31 -17
  29. a_sync/asyncio/gather.py +72 -52
  30. a_sync/asyncio/utils.py +3 -3
  31. a_sync/exceptions.py +78 -23
  32. a_sync/executor.py +118 -71
  33. a_sync/future.py +575 -158
  34. a_sync/iter.py +110 -50
  35. a_sync/primitives/__init__.py +14 -2
  36. a_sync/primitives/_debug.py +13 -13
  37. a_sync/primitives/_loggable.py +5 -4
  38. a_sync/primitives/locks/__init__.py +5 -2
  39. a_sync/primitives/locks/counter.py +38 -36
  40. a_sync/primitives/locks/event.py +21 -7
  41. a_sync/primitives/locks/prio_semaphore.py +182 -62
  42. a_sync/primitives/locks/semaphore.py +78 -77
  43. a_sync/primitives/queue.py +560 -58
  44. a_sync/sphinx/__init__.py +0 -1
  45. a_sync/sphinx/ext.py +160 -50
  46. a_sync/task.py +262 -97
  47. a_sync/utils/__init__.py +12 -6
  48. a_sync/utils/iterators.py +127 -43
  49. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/METADATA +1 -1
  50. ez_a_sync-0.22.15.dist-info/RECORD +74 -0
  51. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/WHEEL +1 -1
  52. tests/conftest.py +1 -2
  53. tests/executor.py +112 -9
  54. tests/fixtures.py +61 -32
  55. tests/test_abstract.py +7 -4
  56. tests/test_as_completed.py +54 -21
  57. tests/test_base.py +66 -17
  58. tests/test_cache.py +31 -15
  59. tests/test_decorator.py +54 -28
  60. tests/test_executor.py +8 -13
  61. tests/test_future.py +45 -8
  62. tests/test_gather.py +8 -2
  63. tests/test_helpers.py +2 -0
  64. tests/test_iter.py +55 -13
  65. tests/test_limiter.py +5 -3
  66. tests/test_meta.py +23 -9
  67. tests/test_modified.py +4 -1
  68. tests/test_semaphore.py +15 -8
  69. tests/test_singleton.py +15 -10
  70. tests/test_task.py +126 -28
  71. ez_a_sync-0.22.14.dist-info/RECORD +0 -74
  72. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/LICENSE.txt +0 -0
  73. {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 ASyncFunction, ASyncFunctionAsyncDefault, ASyncFunctionSyncDefault
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("awaiting %s for instance: %s args: %s kwargs: %s", self, instance, args, kwargs)
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__(self, instance: Optional[I], owner: Type[I]) -> Union[Self, "ASyncBoundMethod[I, P, T]"]:
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(instance, self.__wrapped__, self.__is_async_def__, **self.modifiers)
90
+ bound = ASyncBoundMethodSyncDefault(
91
+ instance, self.__wrapped__, self.__is_async_def__, **self.modifiers
92
+ )
77
93
  elif self.default == "async":
78
- bound = ASyncBoundMethodAsyncDefault(instance, self.__wrapped__, self.__is_async_def__, **self.modifiers)
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(instance, self.__wrapped__, self.__is_async_def__, **self.modifiers)
100
+ bound = ASyncBoundMethodSyncDefault(
101
+ instance,
102
+ self.__wrapped__,
103
+ self.__is_async_def__,
104
+ **self.modifiers,
105
+ )
83
106
  else:
84
- bound = ASyncBoundMethodAsyncDefault(instance, self.__wrapped__, self.__is_async_def__, **self.modifiers)
107
+ bound = ASyncBoundMethodAsyncDefault(
108
+ instance,
109
+ self.__wrapped__,
110
+ self.__is_async_def__,
111
+ **self.modifiers,
112
+ )
85
113
  except AttributeError:
86
- bound = ASyncBoundMethod(instance, self.__wrapped__, self.__is_async_def__, **self.modifiers)
114
+ bound = ASyncBoundMethod(
115
+ instance,
116
+ self.__wrapped__,
117
+ self.__is_async_def__,
118
+ **self.modifiers,
119
+ )
87
120
  else:
88
- bound = ASyncBoundMethod(instance, self.__wrapped__, self.__is_async_def__, **self.modifiers)
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(f"cannot set {self.field_name}, {self} is what you get. sorry.")
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(f"cannot delete {self.field_name}, you're stuck with {self} forever. sorry.")
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(300, instance.__dict__.pop, self.field_name)
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__(self, instance: None, owner: Type[I] = None) -> "ASyncMethodDescriptorSyncDefault[I, P, T]":...
213
+ def __get__(
214
+ self, instance: None, owner: Type[I] = None
215
+ ) -> "ASyncMethodDescriptorSyncDefault[I, P, T]": ...
172
216
  @overload
173
- def __get__(self, instance: I, owner: Type[I] = None) -> "ASyncBoundMethodSyncDefault[I, P, T]":...
174
- def __get__(self, instance: Optional[I], owner: Type[I] = None) -> "Union[ASyncMethodDescriptorSyncDefault, ASyncBoundMethodSyncDefault[I, P, T]]":
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(instance, self.__wrapped__, self.__is_async_def__, **self.modifiers)
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__(self, instance: None, owner: Type[I]) -> "ASyncMethodDescriptorAsyncDefault[I, P, T]":...
280
+ def __get__(
281
+ self, instance: None, owner: Type[I]
282
+ ) -> "ASyncMethodDescriptorAsyncDefault[I, P, T]": ...
228
283
  @overload
229
- def __get__(self, instance: I, owner: Type[I]) -> "ASyncBoundMethodAsyncDefault[I, P, T]":...
230
- def __get__(self, instance: Optional[I], owner: Type[I]) -> "Union[ASyncMethodDescriptorAsyncDefault, ASyncBoundMethodAsyncDefault[I, P, T]]":
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(instance, self.__wrapped__, self.__is_async_def__, **self.modifiers)
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__(self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs) -> Coroutine[Any, Any, T]:...
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__(self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs) -> T:...
384
+ def __call__(
385
+ self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
386
+ ) -> T: ...
320
387
  @overload
321
- def __call__(self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs) -> Coroutine[Any, Any, T]:...
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("awaiting %s for %s args: %s kwargs: %s", coro, self, args, kwargs)
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("returning %s for %s args: %s kwargs: %s", retval, self, args, kwargs)
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(self, *iterables: AnyIterable[I], concurrency: Optional[int] = None, task_name: str = "", **kwargs: P.kwargs) -> "TaskMapping[I, T]":
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
- async def any(self, *iterables: AnyIterable[I], concurrency: Optional[int] = None, task_name: str = "", **kwargs: P.kwargs) -> bool:
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(*iterables, concurrency=concurrency, task_name=task_name, **kwargs).any(pop=True, sync=False)
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(self, *iterables: AnyIterable[I], concurrency: Optional[int] = None, task_name: str = "", **kwargs: P.kwargs) -> bool:
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(*iterables, concurrency=concurrency, task_name=task_name, **kwargs).all(pop=True, sync=False)
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(self, *iterables: AnyIterable[I], concurrency: Optional[int] = None, task_name: str = "", **kwargs: P.kwargs) -> T:
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(*iterables, concurrency=concurrency, task_name=task_name, **kwargs).min(pop=True, sync=False)
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(self, *iterables: AnyIterable[I], concurrency: Optional[int] = None, task_name: str = "", **kwargs: P.kwargs) -> T:
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(*iterables, concurrency=concurrency, task_name=task_name, **kwargs).max(pop=True, sync=False)
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(self, *iterables: AnyIterable[I], concurrency: Optional[int] = None, task_name: str = "", **kwargs: P.kwargs) -> T:
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(*iterables, concurrency=concurrency, task_name=task_name, **kwargs).sum(pop=True, sync=False)
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__(self, instance: Optional[I], owner: Type[I]) -> ASyncFunctionSyncDefault[P, T]:
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__(self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs) -> Coroutine[Any, Any, T]:...
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__(self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs) -> T:...
651
+ def __call__(
652
+ self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
653
+ ) -> T: ...
525
654
  @overload
526
- def __call__(self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs) -> Coroutine[Any, Any, T]:...
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__(self, *args: P.args, sync: Literal[False], **kwargs: P.kwargs) -> Coroutine[Any, Any, T]:...
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__(self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs) -> T:...
699
+ def __call__(
700
+ self, *args: P.args, asynchronous: Literal[False], **kwargs: P.kwargs
701
+ ) -> T: ...
566
702
  @overload
567
- def __call__(self, *args: P.args, asynchronous: Literal[True], **kwargs: P.kwargs) -> Coroutine[Any, Any, T]:...
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
- if 'semaphore' in attrs_from_metaclass and isinstance(val := attrs_from_metaclass['semaphore'], int):
16
- attrs_from_metaclass['semaphore'] = ThreadsafeSemaphore(val)
17
- if "runs_per_minute" in attrs_from_metaclass and isinstance(val := attrs_from_metaclass['runs_per_minute'], int):
18
- attrs_from_metaclass['runs_per_minute'] = AsyncLimiter(val)
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 = 'memory',
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("'lru_cache_maxsize' must be an integer or None.", ram_cache_maxsize)
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 == 'memory':
56
- cache_decorator = apply_async_memory_cache(maxsize=ram_cache_maxsize, ttl=ram_cache_ttl, typed=cache_typed)
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 == 'disk':
87
+ elif cache_type == "disk":
59
88
  pass
60
- raise NotImplementedError(f"cache_type: {cache_type}")
89
+ raise NotImplementedError(f"cache_type: {cache_type}")