ez-a-sync 0.22.14__py3-none-any.whl → 0.22.16__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 +37 -5
  2. a_sync/__init__.py +53 -12
  3. a_sync/_smart.py +231 -28
  4. a_sync/_typing.py +112 -15
  5. a_sync/a_sync/__init__.py +35 -10
  6. a_sync/a_sync/_descriptor.py +248 -38
  7. a_sync/a_sync/_flags.py +78 -9
  8. a_sync/a_sync/_helpers.py +46 -13
  9. a_sync/a_sync/_kwargs.py +33 -8
  10. a_sync/a_sync/_meta.py +149 -28
  11. a_sync/a_sync/abstract.py +150 -28
  12. a_sync/a_sync/base.py +34 -16
  13. a_sync/a_sync/config.py +85 -14
  14. a_sync/a_sync/decorator.py +441 -139
  15. a_sync/a_sync/function.py +709 -147
  16. a_sync/a_sync/method.py +437 -110
  17. a_sync/a_sync/modifiers/__init__.py +85 -5
  18. a_sync/a_sync/modifiers/cache/__init__.py +116 -17
  19. a_sync/a_sync/modifiers/cache/memory.py +130 -20
  20. a_sync/a_sync/modifiers/limiter.py +101 -22
  21. a_sync/a_sync/modifiers/manager.py +142 -16
  22. a_sync/a_sync/modifiers/semaphores.py +121 -15
  23. a_sync/a_sync/property.py +383 -82
  24. a_sync/a_sync/singleton.py +44 -19
  25. a_sync/aliases.py +0 -1
  26. a_sync/asyncio/__init__.py +140 -1
  27. a_sync/asyncio/as_completed.py +213 -79
  28. a_sync/asyncio/create_task.py +70 -20
  29. a_sync/asyncio/gather.py +125 -58
  30. a_sync/asyncio/utils.py +3 -3
  31. a_sync/exceptions.py +248 -26
  32. a_sync/executor.py +164 -69
  33. a_sync/future.py +1227 -168
  34. a_sync/iter.py +173 -56
  35. a_sync/primitives/__init__.py +14 -2
  36. a_sync/primitives/_debug.py +72 -18
  37. a_sync/primitives/_loggable.py +41 -10
  38. a_sync/primitives/locks/__init__.py +5 -2
  39. a_sync/primitives/locks/counter.py +107 -38
  40. a_sync/primitives/locks/event.py +21 -7
  41. a_sync/primitives/locks/prio_semaphore.py +262 -63
  42. a_sync/primitives/locks/semaphore.py +138 -89
  43. a_sync/primitives/queue.py +601 -60
  44. a_sync/sphinx/__init__.py +0 -1
  45. a_sync/sphinx/ext.py +160 -50
  46. a_sync/task.py +313 -112
  47. a_sync/utils/__init__.py +12 -6
  48. a_sync/utils/iterators.py +170 -50
  49. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/METADATA +1 -1
  50. ez_a_sync-0.22.16.dist-info/RECORD +74 -0
  51. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/WHEEL +1 -1
  52. tests/conftest.py +1 -2
  53. tests/executor.py +250 -9
  54. tests/fixtures.py +61 -32
  55. tests/test_abstract.py +22 -4
  56. tests/test_as_completed.py +54 -21
  57. tests/test_base.py +264 -19
  58. tests/test_cache.py +31 -15
  59. tests/test_decorator.py +54 -28
  60. tests/test_executor.py +31 -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 +28 -11
  70. tests/test_task.py +162 -36
  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.16.dist-info}/LICENSE.txt +0 -0
  73. {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,30 @@
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. This is not applied like the other modifiers but is used to manage the execution context for synchronous functions when they are called in an asynchronous manner.
20
+
21
+ See Also:
22
+ - :mod:`a_sync.a_sync.modifiers.cache`
23
+ - :mod:`a_sync.a_sync.modifiers.limiter`
24
+ - :mod:`a_sync.a_sync.modifiers.manager`
25
+ - :mod:`a_sync.a_sync.modifiers.semaphores`
26
+ """
27
+
1
28
  from aiolimiter import AsyncLimiter
2
29
 
3
30
  from a_sync._typing import *
@@ -6,14 +33,67 @@ from a_sync.a_sync.modifiers.manager import valid_modifiers
6
33
 
7
34
 
8
35
  def get_modifiers_from(thing: Union[dict, type, object]) -> ModifierKwargs:
36
+ """Extracts valid modifiers from a given object, type, or dictionary.
37
+
38
+ Args:
39
+ thing: The source from which to extract modifiers. It can be a dictionary,
40
+ a type, or an object.
41
+
42
+ Returns:
43
+ A ModifierKwargs object containing the valid modifiers extracted from the input.
44
+
45
+ Examples:
46
+ Extracting modifiers from a class:
47
+
48
+ >>> class Example:
49
+ ... cache_type = 'memory'
50
+ ... runs_per_minute = 60
51
+ >>> get_modifiers_from(Example)
52
+ ModifierKwargs({'cache_type': 'memory', 'runs_per_minute': 60})
53
+
54
+ Extracting modifiers from a dictionary:
55
+
56
+ >>> modifiers_dict = {'cache_type': 'memory', 'semaphore': 5}
57
+ >>> get_modifiers_from(modifiers_dict)
58
+ ModifierKwargs({'cache_type': 'memory', 'semaphore': ThreadsafeSemaphore(5)})
59
+
60
+ See Also:
61
+ - :class:`a_sync.a_sync.modifiers.manager.ModifierManager`
62
+ """
9
63
  if isinstance(thing, dict):
10
64
  apply_class_defined_modifiers(thing)
11
65
  return ModifierKwargs({modifier: thing[modifier] for modifier in valid_modifiers if modifier in thing}) # type: ignore [misc]
12
66
  return ModifierKwargs({modifier: getattr(thing, modifier) for modifier in valid_modifiers if hasattr(thing, modifier)}) # type: ignore [misc]
13
67
 
68
+
14
69
  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
-
70
+ """Applies class-defined modifiers to a dictionary of attributes.
71
+
72
+ This function modifies the input dictionary in place. If the 'semaphore' key
73
+ is present and its value is an integer, it is converted to a ThreadsafeSemaphore.
74
+ If the 'runs_per_minute' key is present and its value is an integer, it is
75
+ converted to an AsyncLimiter. If these keys are not present or their values
76
+ are not integers, the function will silently do nothing.
77
+
78
+ Args:
79
+ attrs_from_metaclass: A dictionary of attributes from a metaclass.
80
+
81
+ Examples:
82
+ Applying modifiers to a dictionary:
83
+
84
+ >>> attrs = {'semaphore': 3, 'runs_per_minute': 60}
85
+ >>> apply_class_defined_modifiers(attrs)
86
+ >>> attrs['semaphore']
87
+ ThreadsafeSemaphore(3)
88
+
89
+ >>> attrs['runs_per_minute']
90
+ AsyncLimiter(60)
91
+
92
+ See Also:
93
+ - :class:`a_sync.primitives.locks.ThreadsafeSemaphore`
94
+ - :class:`aiolimiter.AsyncLimiter`
95
+ """
96
+ if isinstance(val := attrs_from_metaclass.get("semaphore"), int):
97
+ attrs_from_metaclass["semaphore"] = ThreadsafeSemaphore(val)
98
+ if isinstance(val := attrs_from_metaclass.get("runs_per_minute"), int):
99
+ attrs_from_metaclass["runs_per_minute"] = AsyncLimiter(val)
@@ -8,53 +8,152 @@ 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
14
+ """The type of cache to use. Currently, only 'memory' is implemented."""
15
+
12
16
  cache_typed: bool
17
+ """Whether to consider types for cache keys."""
18
+
13
19
  ram_cache_maxsize: Optional[int]
20
+ """The maximum size for the LRU cache."""
21
+
14
22
  ram_cache_ttl: Optional[int]
23
+ """The time-to-live for items in the LRU cache."""
24
+
15
25
 
16
26
  @overload
17
27
  def apply_async_cache(
18
- coro_fn: Literal[None],
19
28
  **modifiers: Unpack[CacheArgs],
20
- ) -> AsyncDecorator[P, T]:...
21
-
29
+ ) -> AsyncDecorator[P, T]:
30
+ """Creates a decorator to apply an asynchronous cache.
31
+
32
+ This overload is used when no coroutine function is provided. The returned
33
+ decorator can be applied to a coroutine function later.
34
+
35
+ Args:
36
+ **modifiers: Cache configuration options such as `cache_type`, `cache_typed`, `ram_cache_maxsize`, and `ram_cache_ttl`.
37
+
38
+ Examples:
39
+ >>> @apply_async_cache(cache_type='memory', ram_cache_maxsize=100)
40
+ ... async def my_function():
41
+ ... pass
42
+
43
+ See Also:
44
+ :func:`apply_async_memory_cache`
45
+ """
46
+
47
+
22
48
  @overload
23
49
  def apply_async_cache(
24
50
  coro_fn: int,
25
51
  **modifiers: Unpack[CacheArgs],
26
- ) -> AsyncDecorator[P, T]:...
27
-
52
+ ) -> AsyncDecorator[P, T]:
53
+ """Creates a decorator with `ram_cache_maxsize` set by an integer.
54
+
55
+ This overload is used when an integer is provided as the first argument,
56
+ which sets the `ram_cache_maxsize` for the cache. The returned decorator
57
+ can be applied to a coroutine function later.
58
+
59
+ Args:
60
+ coro_fn: An integer to set as the max size for the cache.
61
+ **modifiers: Additional cache configuration options.
62
+
63
+ Examples:
64
+ >>> @apply_async_cache(100, cache_type='memory')
65
+ ... async def my_function():
66
+ ... pass
67
+
68
+ See Also:
69
+ :func:`apply_async_memory_cache`
70
+ """
71
+
72
+
28
73
  @overload
29
74
  def apply_async_cache(
30
75
  coro_fn: CoroFn[P, T],
31
76
  **modifiers: Unpack[CacheArgs],
32
- ) -> CoroFn[P, T]:...
33
-
77
+ ) -> CoroFn[P, T]:
78
+ """Applies an asynchronous cache directly to a coroutine function.
79
+
80
+ This overload is used when a coroutine function is provided. The cache is
81
+ applied directly to the function.
82
+
83
+ Args:
84
+ coro_fn: The coroutine function to be cached.
85
+ **modifiers: Cache configuration options such as `cache_type`, `cache_typed`, `ram_cache_maxsize`, and `ram_cache_ttl`.
86
+
87
+ Examples:
88
+ >>> async def my_function():
89
+ ... pass
90
+ >>> cached_function = apply_async_cache(my_function, cache_type='memory', ram_cache_maxsize=100)
91
+
92
+ See Also:
93
+ :func:`apply_async_memory_cache`
94
+ """
95
+
96
+
34
97
  def apply_async_cache(
35
98
  coro_fn: Union[CoroFn[P, T], CacheType, int] = None,
36
- cache_type: CacheType = 'memory',
99
+ cache_type: CacheType = "memory",
37
100
  cache_typed: bool = False,
38
101
  ram_cache_maxsize: Optional[int] = None,
39
102
  ram_cache_ttl: Optional[int] = None,
40
103
  ) -> AsyncDecoratorOrCoroFn[P, T]:
41
-
104
+ """Applies an asynchronous cache to a coroutine function.
105
+
106
+ This function can be used to apply a cache to a coroutine function, either by passing the function directly or by using it as a decorator. The cache type is currently limited to 'memory'. If an unsupported cache type is specified, a `NotImplementedError` is raised.
107
+
108
+ Args:
109
+ coro_fn: The coroutine function to apply the cache to, or an integer to set as the max size.
110
+ cache_type: The type of cache to use. Currently, only 'memory' is implemented.
111
+ cache_typed: Whether to consider types for cache keys.
112
+ ram_cache_maxsize: The maximum size for the LRU cache. If set to an integer, it overrides coro_fn.
113
+ ram_cache_ttl: The time-to-live for items in the LRU cache.
114
+
115
+ Raises:
116
+ TypeError: If 'ram_cache_maxsize' is not an integer or None.
117
+ FunctionNotAsync: If the provided function is not asynchronous.
118
+ NotImplementedError: If an unsupported cache type is specified.
119
+
120
+ Examples:
121
+ >>> @apply_async_cache(cache_type='memory', ram_cache_maxsize=100)
122
+ ... async def my_function():
123
+ ... pass
124
+
125
+ >>> async def my_function():
126
+ ... pass
127
+ >>> cached_function = apply_async_cache(my_function, cache_type='memory', ram_cache_maxsize=100)
128
+
129
+ >>> @apply_async_cache(100, cache_type='memory')
130
+ ... async def another_function():
131
+ ... pass
132
+
133
+ See Also:
134
+ :func:`apply_async_memory_cache`
135
+ """
136
+
42
137
  # Parse Inputs
43
138
  if isinstance(coro_fn, int):
44
139
  assert ram_cache_maxsize is None
45
140
  ram_cache_maxsize = coro_fn
46
141
  coro_fn = None
47
-
48
- # Validate
142
+
143
+ # Validate
49
144
  elif coro_fn is None:
50
145
  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)
146
+ raise TypeError(
147
+ "'lru_cache_maxsize' must be an integer or None.", ram_cache_maxsize
148
+ )
52
149
  elif not asyncio.iscoroutinefunction(coro_fn):
53
150
  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)
151
+
152
+ if cache_type == "memory":
153
+ cache_decorator = apply_async_memory_cache(
154
+ maxsize=ram_cache_maxsize, ttl=ram_cache_ttl, typed=cache_typed
155
+ )
57
156
  return cache_decorator if coro_fn is None else cache_decorator(coro_fn)
58
- elif cache_type == 'disk':
157
+ elif cache_type == "disk":
59
158
  pass
60
- raise NotImplementedError(f"cache_type: {cache_type}")
159
+ raise NotImplementedError(f"cache_type: {cache_type}")
@@ -7,34 +7,110 @@ from async_lru import alru_cache
7
7
  from a_sync import exceptions
8
8
  from a_sync._typing import *
9
9
 
10
+
10
11
  class CacheKwargs(TypedDict):
12
+ """Typed dictionary for cache keyword arguments."""
13
+
11
14
  maxsize: Optional[int]
12
15
  ttl: Optional[int]
13
16
  typed: bool
14
17
 
18
+
15
19
  @overload
16
- def apply_async_memory_cache(
17
- coro_fn: Literal[None],
18
- **kwargs: Unpack[CacheKwargs]
19
- ) -> AsyncDecorator[P, T]:...
20
-
20
+ def apply_async_memory_cache(**kwargs: Unpack[CacheKwargs]) -> AsyncDecorator[P, T]:
21
+ """
22
+ Creates a decorator to apply an asynchronous LRU cache.
23
+
24
+ This overload is used when no coroutine function is provided. The returned
25
+ decorator can be applied to a coroutine function later.
26
+
27
+ Args:
28
+ **kwargs: Keyword arguments for cache configuration, including maxsize,
29
+ ttl, and typed.
30
+
31
+ Examples:
32
+ >>> @apply_async_memory_cache(maxsize=128, ttl=60)
33
+ ... async def fetch_data():
34
+ ... pass
35
+
36
+ See Also:
37
+ - :func:`alru_cache` for the underlying caching mechanism.
38
+ """
39
+
40
+
21
41
  @overload
22
42
  def apply_async_memory_cache(
23
- coro_fn: int,
24
- **kwargs: Unpack[CacheKwargs]
25
- ) -> AsyncDecorator[P, T]:...
26
-
43
+ coro_fn: int, **kwargs: Unpack[CacheKwargs]
44
+ ) -> AsyncDecorator[P, T]:
45
+ """Creates a decorator with maxsize set by an integer.
46
+
47
+ This overload is used when an integer is provided as the coroutine function,
48
+ which sets the maxsize for the cache. The returned decorator can be applied
49
+ to a coroutine function later.
50
+
51
+ Args:
52
+ coro_fn: An integer to set as maxsize for the cache.
53
+ **kwargs: Additional keyword arguments for cache configuration, including
54
+ ttl and typed.
55
+
56
+ Examples:
57
+ >>> # This usage is not supported
58
+ >>> apply_async_memory_cache(128, ttl=60)
59
+
60
+ See Also:
61
+ - :func:`apply_async_memory_cache` for correct usage.
62
+ """
63
+
64
+
27
65
  @overload
28
66
  def apply_async_memory_cache(
29
- coro_fn: CoroFn[P, T],
30
- **kwargs: Unpack[CacheKwargs]
31
- ) -> CoroFn[P, T]:...
67
+ coro_fn: CoroFn[P, T], **kwargs: Unpack[CacheKwargs]
68
+ ) -> CoroFn[P, T]:
69
+ """
70
+ Applies an asynchronous LRU cache to a provided coroutine function.
71
+
72
+ This overload is used when a coroutine function is provided. The cache is
73
+ applied directly to the function.
74
+
75
+ Args:
76
+ coro_fn: The coroutine function to be cached.
77
+ **kwargs: Keyword arguments for cache configuration, including maxsize,
78
+ ttl, and typed.
79
+
80
+ Examples:
81
+ >>> async def fetch_data():
82
+ ... pass
83
+ >>> cached_fetch = apply_async_memory_cache(fetch_data, maxsize=128, ttl=60)
84
+
85
+ See Also:
86
+ - :func:`alru_cache` for the underlying caching mechanism.
87
+ """
88
+
32
89
 
33
90
  @overload
34
91
  def apply_async_memory_cache(
35
- coro_fn: Literal[None],
36
- **kwargs: Unpack[CacheKwargs]
37
- ) -> AsyncDecorator[P, T]:...
92
+ coro_fn: Literal[None], **kwargs: Unpack[CacheKwargs]
93
+ ) -> AsyncDecorator[P, T]:
94
+ """
95
+ Creates a decorator to apply an asynchronous LRU cache.
96
+
97
+ This duplicate overload is used when no coroutine function is provided. The
98
+ returned decorator can be applied to a coroutine function later.
99
+
100
+ Args:
101
+ coro_fn: None, indicating no coroutine function is provided.
102
+ **kwargs: Keyword arguments for cache configuration, including maxsize,
103
+ ttl, and typed.
104
+
105
+ Examples:
106
+ >>> @apply_async_memory_cache(maxsize=128, ttl=60)
107
+ ... async def fetch_data():
108
+ ... pass
109
+
110
+ See Also:
111
+ - :func:`alru_cache` for the underlying caching mechanism.
112
+ """
113
+
38
114
 
39
115
  def apply_async_memory_cache(
40
116
  coro_fn: Optional[Union[CoroFn[P, T], int]] = None,
@@ -42,19 +118,53 @@ def apply_async_memory_cache(
42
118
  ttl: Optional[int] = None,
43
119
  typed: bool = False,
44
120
  ) -> AsyncDecoratorOrCoroFn[P, T]:
121
+ """
122
+ Applies an asynchronous LRU cache to a coroutine function.
123
+
124
+ This function uses the `alru_cache` from the `async_lru` library to cache
125
+ the results of an asynchronous coroutine function. The cache can be configured
126
+ with a maximum size, a time-to-live (TTL), and whether the cache keys should
127
+ be typed.
128
+
129
+ Args:
130
+ coro_fn: The coroutine function to be cached, or an integer to set as maxsize.
131
+ maxsize: The maximum size of the cache. If set to -1, it is converted to None,
132
+ making the cache unbounded.
133
+ ttl: The time-to-live for cache entries in seconds. If None, entries do not expire.
134
+ typed: Whether to consider the types of arguments as part of the cache key.
135
+
136
+ Raises:
137
+ TypeError: If `maxsize` is not a positive integer or None when `coro_fn` is None.
138
+ exceptions.FunctionNotAsync: If `coro_fn` is not an asynchronous function.
139
+
140
+ Examples:
141
+ >>> @apply_async_memory_cache(maxsize=128, ttl=60)
142
+ ... async def fetch_data():
143
+ ... pass
144
+
145
+ >>> async def fetch_data():
146
+ ... pass
147
+ >>> cached_fetch = apply_async_memory_cache(fetch_data, maxsize=128, ttl=60)
148
+
149
+ See Also:
150
+ - :func:`alru_cache` for the underlying caching mechanism.
151
+ """
45
152
  # Parse Inputs
46
153
  if isinstance(coro_fn, int):
47
154
  assert maxsize is None
48
155
  maxsize = coro_fn
49
156
  coro_fn = None
50
-
51
- # Validate
157
+
158
+ # Validate
52
159
  elif coro_fn is None:
53
- if not (maxsize is None or isinstance(maxsize, int)):
54
- raise TypeError("'lru_cache_maxsize' must be a positive integer or None.", maxsize)
160
+ if maxsize not in [None, -1] and (not isinstance(maxsize, int) or maxsize <= 0):
161
+ raise TypeError(
162
+ "'lru_cache_maxsize' must be a positive integer or None.", maxsize
163
+ )
164
+
55
165
  elif not asyncio.iscoroutinefunction(coro_fn):
56
166
  raise exceptions.FunctionNotAsync(coro_fn)
57
-
167
+
58
168
  if maxsize == -1:
59
169
  maxsize = None
60
170
 
@@ -8,48 +8,127 @@ from a_sync import aliases, exceptions
8
8
  from a_sync._typing import *
9
9
 
10
10
 
11
+ LimiterSpec = Union[int, AsyncLimiter]
12
+
13
+
11
14
  @overload
12
15
  def apply_rate_limit(
13
- coro_fn: Literal[None],
14
16
  runs_per_minute: int,
15
- ) -> AsyncDecorator[P, T]:...
16
-
17
- @overload
18
- def apply_rate_limit(
19
- coro_fn: int,
20
- runs_per_minute: Literal[None],
21
- ) -> AsyncDecorator[P, T]:...
22
-
17
+ ) -> AsyncDecorator[P, T]:
18
+ """Decorator to apply a rate limit to an asynchronous function.
19
+
20
+ This overload allows specifying the number of allowed executions per minute.
21
+
22
+ Args:
23
+ runs_per_minute: The number of allowed executions per minute.
24
+
25
+ Examples:
26
+ Applying a rate limit of 60 executions per minute:
27
+
28
+ >>> @apply_rate_limit(60)
29
+ ... async def my_function():
30
+ ... pass
31
+
32
+ See Also:
33
+ :class:`aiolimiter.AsyncLimiter`
34
+ """
35
+
36
+
23
37
  @overload
24
38
  def apply_rate_limit(
25
39
  coro_fn: CoroFn[P, T],
26
- runs_per_minute: Union[int, AsyncLimiter],
27
- ) -> CoroFn[P, T]:...
28
-
40
+ runs_per_minute: LimiterSpec,
41
+ ) -> CoroFn[P, T]:
42
+ """Decorator to apply a rate limit to an asynchronous function.
43
+
44
+ This overload allows specifying either the number of allowed executions per minute
45
+ or an :class:`aiolimiter.AsyncLimiter` instance.
46
+
47
+ Args:
48
+ coro_fn: The coroutine function to be rate-limited.
49
+ runs_per_minute: The number of allowed executions per minute or an :class:`aiolimiter.AsyncLimiter` instance.
50
+
51
+ Examples:
52
+ Using an :class:`aiolimiter.AsyncLimiter` instance:
53
+
54
+ >>> async_limiter = AsyncLimiter(60)
55
+ >>> @apply_rate_limit(async_limiter)
56
+ ... async def my_function():
57
+ ... pass
58
+
59
+ See Also:
60
+ :class:`aiolimiter.AsyncLimiter`
61
+ """
62
+
63
+
29
64
  def apply_rate_limit(
30
65
  coro_fn: Optional[Union[CoroFn[P, T], int]] = None,
31
- runs_per_minute: Optional[Union[int, AsyncLimiter]] = None,
66
+ runs_per_minute: Optional[LimiterSpec] = None,
32
67
  ) -> AsyncDecoratorOrCoroFn[P, T]:
68
+ """Applies a rate limit to an asynchronous function.
69
+
70
+ This function can be used as a decorator to limit the number of times
71
+ an asynchronous function can be called per minute. It can be configured
72
+ with either an integer specifying the number of runs per minute or an
73
+ :class:`aiolimiter.AsyncLimiter` instance.
74
+
75
+ Args:
76
+ coro_fn: The coroutine function to be rate-limited. If an integer is provided, it is treated as runs per minute, and `runs_per_minute` should be None.
77
+ runs_per_minute: The number of allowed executions per minute or an :class:`aiolimiter.AsyncLimiter` instance. If `coro_fn` is an integer, this should be None.
78
+
79
+ Raises:
80
+ TypeError: If `runs_per_minute` is neither an integer nor an :class:`aiolimiter.AsyncLimiter` when `coro_fn` is None.
81
+ exceptions.FunctionNotAsync: If `coro_fn` is not an asynchronous function.
82
+
83
+ Examples:
84
+ Applying a rate limit of 60 executions per minute:
85
+
86
+ >>> @apply_rate_limit(60)
87
+ ... async def my_function():
88
+ ... pass
89
+
90
+ Using an :class:`aiolimiter.AsyncLimiter` instance:
91
+
92
+ >>> async_limiter = AsyncLimiter(60)
93
+ >>> @apply_rate_limit(async_limiter)
94
+ ... async def my_function():
95
+ ... pass
96
+
97
+ Specifying the rate limit directly in the decorator:
98
+
99
+ >>> @apply_rate_limit
100
+ ... async def my_function():
101
+ ... pass
102
+
103
+ See Also:
104
+ :class:`aiolimiter.AsyncLimiter`
105
+ """
33
106
  # Parse Inputs
34
- if isinstance(coro_fn, int):
107
+ if isinstance(coro_fn, (int, AsyncLimiter)):
35
108
  assert runs_per_minute is None
36
109
  runs_per_minute = coro_fn
37
110
  coro_fn = None
38
-
111
+
39
112
  elif coro_fn is None:
40
- if runs_per_minute is not None and not isinstance(runs_per_minute, int):
113
+ if runs_per_minute is not None and not isinstance(
114
+ runs_per_minute, (int, AsyncLimiter)
115
+ ):
41
116
  raise TypeError("'runs_per_minute' must be an integer.", runs_per_minute)
42
-
117
+
43
118
  elif not asyncio.iscoroutinefunction(coro_fn):
44
119
  raise exceptions.FunctionNotAsync(coro_fn)
45
-
120
+
46
121
  def rate_limit_decorator(coro_fn: CoroFn[P, T]) -> CoroFn[P, T]:
47
- limiter = runs_per_minute if isinstance(runs_per_minute, AsyncLimiter) else AsyncLimiter(runs_per_minute) if runs_per_minute else aliases.dummy
122
+ limiter = (
123
+ runs_per_minute
124
+ if isinstance(runs_per_minute, AsyncLimiter)
125
+ else AsyncLimiter(runs_per_minute) if runs_per_minute else aliases.dummy
126
+ )
127
+
48
128
  async def rate_limit_wrap(*args: P.args, **kwargs: P.kwargs) -> T:
49
129
  async with limiter: # type: ignore [attr-defined]
50
130
  return await coro_fn(*args, **kwargs)
131
+
51
132
  return rate_limit_wrap
52
-
133
+
53
134
  return rate_limit_decorator if coro_fn is None else rate_limit_decorator(coro_fn)
54
-
55
-