ez-a-sync 0.22.15__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.
- a_sync/ENVIRONMENT_VARIABLES.py +34 -3
- a_sync/__init__.py +32 -9
- a_sync/_smart.py +105 -6
- a_sync/_typing.py +56 -3
- a_sync/a_sync/_descriptor.py +174 -12
- a_sync/a_sync/_flags.py +64 -3
- a_sync/a_sync/_helpers.py +40 -8
- a_sync/a_sync/_kwargs.py +30 -6
- a_sync/a_sync/_meta.py +35 -6
- a_sync/a_sync/abstract.py +57 -9
- a_sync/a_sync/config.py +44 -7
- a_sync/a_sync/decorator.py +217 -37
- a_sync/a_sync/function.py +339 -47
- a_sync/a_sync/method.py +241 -52
- a_sync/a_sync/modifiers/__init__.py +39 -1
- a_sync/a_sync/modifiers/cache/__init__.py +75 -5
- a_sync/a_sync/modifiers/cache/memory.py +50 -6
- a_sync/a_sync/modifiers/limiter.py +55 -6
- a_sync/a_sync/modifiers/manager.py +46 -2
- a_sync/a_sync/modifiers/semaphores.py +84 -11
- a_sync/a_sync/singleton.py +43 -19
- a_sync/asyncio/__init__.py +137 -1
- a_sync/asyncio/as_completed.py +44 -38
- a_sync/asyncio/create_task.py +46 -10
- a_sync/asyncio/gather.py +72 -25
- a_sync/exceptions.py +178 -11
- a_sync/executor.py +51 -3
- a_sync/future.py +671 -29
- a_sync/iter.py +64 -7
- a_sync/primitives/_debug.py +59 -5
- a_sync/primitives/_loggable.py +36 -6
- a_sync/primitives/locks/counter.py +74 -7
- a_sync/primitives/locks/prio_semaphore.py +87 -8
- a_sync/primitives/locks/semaphore.py +68 -20
- a_sync/primitives/queue.py +65 -26
- a_sync/task.py +51 -15
- a_sync/utils/iterators.py +52 -16
- {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/METADATA +1 -1
- ez_a_sync-0.22.16.dist-info/RECORD +74 -0
- {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/WHEEL +1 -1
- tests/executor.py +150 -12
- tests/test_abstract.py +15 -0
- tests/test_base.py +198 -2
- tests/test_executor.py +23 -0
- tests/test_singleton.py +13 -1
- tests/test_task.py +45 -17
- ez_a_sync-0.22.15.dist-info/RECORD +0 -74
- {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/LICENSE.txt +0 -0
- {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/top_level.txt +0 -0
|
@@ -11,16 +11,38 @@ class CacheArgs(TypedDict):
|
|
|
11
11
|
"""Typed dictionary for cache arguments."""
|
|
12
12
|
|
|
13
13
|
cache_type: CacheType
|
|
14
|
+
"""The type of cache to use. Currently, only 'memory' is implemented."""
|
|
15
|
+
|
|
14
16
|
cache_typed: bool
|
|
17
|
+
"""Whether to consider types for cache keys."""
|
|
18
|
+
|
|
15
19
|
ram_cache_maxsize: Optional[int]
|
|
20
|
+
"""The maximum size for the LRU cache."""
|
|
21
|
+
|
|
16
22
|
ram_cache_ttl: Optional[int]
|
|
23
|
+
"""The time-to-live for items in the LRU cache."""
|
|
17
24
|
|
|
18
25
|
|
|
19
26
|
@overload
|
|
20
27
|
def apply_async_cache(
|
|
21
28
|
**modifiers: Unpack[CacheArgs],
|
|
22
29
|
) -> AsyncDecorator[P, T]:
|
|
23
|
-
"""
|
|
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
|
+
"""
|
|
24
46
|
|
|
25
47
|
|
|
26
48
|
@overload
|
|
@@ -28,7 +50,24 @@ def apply_async_cache(
|
|
|
28
50
|
coro_fn: int,
|
|
29
51
|
**modifiers: Unpack[CacheArgs],
|
|
30
52
|
) -> AsyncDecorator[P, T]:
|
|
31
|
-
"""
|
|
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
|
+
"""
|
|
32
71
|
|
|
33
72
|
|
|
34
73
|
@overload
|
|
@@ -36,7 +75,23 @@ def apply_async_cache(
|
|
|
36
75
|
coro_fn: CoroFn[P, T],
|
|
37
76
|
**modifiers: Unpack[CacheArgs],
|
|
38
77
|
) -> CoroFn[P, T]:
|
|
39
|
-
"""
|
|
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
|
+
"""
|
|
40
95
|
|
|
41
96
|
|
|
42
97
|
def apply_async_cache(
|
|
@@ -48,6 +103,8 @@ def apply_async_cache(
|
|
|
48
103
|
) -> AsyncDecoratorOrCoroFn[P, T]:
|
|
49
104
|
"""Applies an asynchronous cache to a coroutine function.
|
|
50
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
|
+
|
|
51
108
|
Args:
|
|
52
109
|
coro_fn: The coroutine function to apply the cache to, or an integer to set as the max size.
|
|
53
110
|
cache_type: The type of cache to use. Currently, only 'memory' is implemented.
|
|
@@ -60,8 +117,21 @@ def apply_async_cache(
|
|
|
60
117
|
FunctionNotAsync: If the provided function is not asynchronous.
|
|
61
118
|
NotImplementedError: If an unsupported cache type is specified.
|
|
62
119
|
|
|
63
|
-
|
|
64
|
-
|
|
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`
|
|
65
135
|
"""
|
|
66
136
|
|
|
67
137
|
# Parse Inputs
|
|
@@ -18,7 +18,8 @@ class CacheKwargs(TypedDict):
|
|
|
18
18
|
|
|
19
19
|
@overload
|
|
20
20
|
def apply_async_memory_cache(**kwargs: Unpack[CacheKwargs]) -> AsyncDecorator[P, T]:
|
|
21
|
-
"""
|
|
21
|
+
"""
|
|
22
|
+
Creates a decorator to apply an asynchronous LRU cache.
|
|
22
23
|
|
|
23
24
|
This overload is used when no coroutine function is provided. The returned
|
|
24
25
|
decorator can be applied to a coroutine function later.
|
|
@@ -26,6 +27,14 @@ def apply_async_memory_cache(**kwargs: Unpack[CacheKwargs]) -> AsyncDecorator[P,
|
|
|
26
27
|
Args:
|
|
27
28
|
**kwargs: Keyword arguments for cache configuration, including maxsize,
|
|
28
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.
|
|
29
38
|
"""
|
|
30
39
|
|
|
31
40
|
|
|
@@ -43,6 +52,13 @@ def apply_async_memory_cache(
|
|
|
43
52
|
coro_fn: An integer to set as maxsize for the cache.
|
|
44
53
|
**kwargs: Additional keyword arguments for cache configuration, including
|
|
45
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.
|
|
46
62
|
"""
|
|
47
63
|
|
|
48
64
|
|
|
@@ -50,7 +66,8 @@ def apply_async_memory_cache(
|
|
|
50
66
|
def apply_async_memory_cache(
|
|
51
67
|
coro_fn: CoroFn[P, T], **kwargs: Unpack[CacheKwargs]
|
|
52
68
|
) -> CoroFn[P, T]:
|
|
53
|
-
"""
|
|
69
|
+
"""
|
|
70
|
+
Applies an asynchronous LRU cache to a provided coroutine function.
|
|
54
71
|
|
|
55
72
|
This overload is used when a coroutine function is provided. The cache is
|
|
56
73
|
applied directly to the function.
|
|
@@ -59,6 +76,14 @@ def apply_async_memory_cache(
|
|
|
59
76
|
coro_fn: The coroutine function to be cached.
|
|
60
77
|
**kwargs: Keyword arguments for cache configuration, including maxsize,
|
|
61
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.
|
|
62
87
|
"""
|
|
63
88
|
|
|
64
89
|
|
|
@@ -66,7 +91,8 @@ def apply_async_memory_cache(
|
|
|
66
91
|
def apply_async_memory_cache(
|
|
67
92
|
coro_fn: Literal[None], **kwargs: Unpack[CacheKwargs]
|
|
68
93
|
) -> AsyncDecorator[P, T]:
|
|
69
|
-
"""
|
|
94
|
+
"""
|
|
95
|
+
Creates a decorator to apply an asynchronous LRU cache.
|
|
70
96
|
|
|
71
97
|
This duplicate overload is used when no coroutine function is provided. The
|
|
72
98
|
returned decorator can be applied to a coroutine function later.
|
|
@@ -75,6 +101,14 @@ def apply_async_memory_cache(
|
|
|
75
101
|
coro_fn: None, indicating no coroutine function is provided.
|
|
76
102
|
**kwargs: Keyword arguments for cache configuration, including maxsize,
|
|
77
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.
|
|
78
112
|
"""
|
|
79
113
|
|
|
80
114
|
|
|
@@ -84,7 +118,8 @@ def apply_async_memory_cache(
|
|
|
84
118
|
ttl: Optional[int] = None,
|
|
85
119
|
typed: bool = False,
|
|
86
120
|
) -> AsyncDecoratorOrCoroFn[P, T]:
|
|
87
|
-
"""
|
|
121
|
+
"""
|
|
122
|
+
Applies an asynchronous LRU cache to a coroutine function.
|
|
88
123
|
|
|
89
124
|
This function uses the `alru_cache` from the `async_lru` library to cache
|
|
90
125
|
the results of an asynchronous coroutine function. The cache can be configured
|
|
@@ -102,8 +137,17 @@ def apply_async_memory_cache(
|
|
|
102
137
|
TypeError: If `maxsize` is not a positive integer or None when `coro_fn` is None.
|
|
103
138
|
exceptions.FunctionNotAsync: If `coro_fn` is not an asynchronous function.
|
|
104
139
|
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
107
151
|
"""
|
|
108
152
|
# Parse Inputs
|
|
109
153
|
if isinstance(coro_fn, int):
|
|
@@ -17,8 +17,20 @@ def apply_rate_limit(
|
|
|
17
17
|
) -> AsyncDecorator[P, T]:
|
|
18
18
|
"""Decorator to apply a rate limit to an asynchronous function.
|
|
19
19
|
|
|
20
|
+
This overload allows specifying the number of allowed executions per minute.
|
|
21
|
+
|
|
20
22
|
Args:
|
|
21
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`
|
|
22
34
|
"""
|
|
23
35
|
|
|
24
36
|
|
|
@@ -29,9 +41,23 @@ def apply_rate_limit(
|
|
|
29
41
|
) -> CoroFn[P, T]:
|
|
30
42
|
"""Decorator to apply a rate limit to an asynchronous function.
|
|
31
43
|
|
|
44
|
+
This overload allows specifying either the number of allowed executions per minute
|
|
45
|
+
or an :class:`aiolimiter.AsyncLimiter` instance.
|
|
46
|
+
|
|
32
47
|
Args:
|
|
33
48
|
coro_fn: The coroutine function to be rate-limited.
|
|
34
|
-
runs_per_minute: The number of allowed executions per minute or an AsyncLimiter instance.
|
|
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`
|
|
35
61
|
"""
|
|
36
62
|
|
|
37
63
|
|
|
@@ -44,15 +70,38 @@ def apply_rate_limit(
|
|
|
44
70
|
This function can be used as a decorator to limit the number of times
|
|
45
71
|
an asynchronous function can be called per minute. It can be configured
|
|
46
72
|
with either an integer specifying the number of runs per minute or an
|
|
47
|
-
AsyncLimiter instance.
|
|
73
|
+
:class:`aiolimiter.AsyncLimiter` instance.
|
|
48
74
|
|
|
49
75
|
Args:
|
|
50
|
-
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.
|
|
51
|
-
runs_per_minute: The number of allowed executions per minute or an AsyncLimiter instance. If coro_fn is an integer, this should be None.
|
|
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.
|
|
52
78
|
|
|
53
79
|
Raises:
|
|
54
|
-
TypeError: If
|
|
55
|
-
exceptions.FunctionNotAsync: If
|
|
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`
|
|
56
105
|
"""
|
|
57
106
|
# Parse Inputs
|
|
58
107
|
if isinstance(coro_fn, (int, AsyncLimiter)):
|
|
@@ -5,6 +5,7 @@ from a_sync._typing import *
|
|
|
5
5
|
from a_sync.a_sync.config import user_set_default_modifiers, null_modifiers
|
|
6
6
|
from a_sync.a_sync.modifiers import cache, limiter, semaphores
|
|
7
7
|
|
|
8
|
+
# TODO give me a docstring
|
|
8
9
|
valid_modifiers = [
|
|
9
10
|
key
|
|
10
11
|
for key in ModifierKwargs.__annotations__
|
|
@@ -16,9 +17,35 @@ class ModifierManager(Dict[str, Any]):
|
|
|
16
17
|
"""Manages modifiers for asynchronous and synchronous functions.
|
|
17
18
|
|
|
18
19
|
This class is responsible for applying modifiers to functions, such as
|
|
19
|
-
caching, rate limiting, and semaphores for asynchronous functions.
|
|
20
|
+
caching, rate limiting, and semaphores for asynchronous functions. It also
|
|
21
|
+
handles synchronous functions, although no sync modifiers are currently
|
|
22
|
+
implemented.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
Creating a ModifierManager with specific modifiers:
|
|
26
|
+
|
|
27
|
+
>>> modifiers = ModifierKwargs(cache_type='memory', runs_per_minute=60)
|
|
28
|
+
>>> manager = ModifierManager(modifiers)
|
|
29
|
+
|
|
30
|
+
Applying modifiers to an asynchronous function:
|
|
31
|
+
|
|
32
|
+
>>> async def my_coro():
|
|
33
|
+
... pass
|
|
34
|
+
>>> modified_coro = manager.apply_async_modifiers(my_coro)
|
|
35
|
+
|
|
36
|
+
Applying modifiers to a synchronous function (no sync modifiers applied):
|
|
37
|
+
|
|
38
|
+
>>> def my_function():
|
|
39
|
+
... pass
|
|
40
|
+
>>> modified_function = manager.apply_sync_modifiers(my_function)
|
|
41
|
+
|
|
42
|
+
See Also:
|
|
43
|
+
- :class:`a_sync.a_sync.modifiers.cache`
|
|
44
|
+
- :class:`a_sync.a_sync.modifiers.limiter`
|
|
45
|
+
- :class:`a_sync.a_sync.modifiers.semaphores`
|
|
20
46
|
"""
|
|
21
47
|
|
|
48
|
+
# TODO give us docstrings
|
|
22
49
|
default: DefaultMode
|
|
23
50
|
cache_type: CacheType
|
|
24
51
|
cache_typed: bool
|
|
@@ -26,9 +53,13 @@ class ModifierManager(Dict[str, Any]):
|
|
|
26
53
|
ram_cache_ttl: Optional[int]
|
|
27
54
|
runs_per_minute: Optional[int]
|
|
28
55
|
semaphore: SemaphoreSpec
|
|
56
|
+
|
|
29
57
|
# sync modifiers
|
|
30
58
|
executor: Executor
|
|
31
|
-
"""
|
|
59
|
+
"""
|
|
60
|
+
This is not applied like a typical modifier but is still passed through the library with them for convenience.
|
|
61
|
+
The executor is used to run the sync function in an asynchronous context.
|
|
62
|
+
"""
|
|
32
63
|
|
|
33
64
|
__slots__ = ("_modifiers",)
|
|
34
65
|
|
|
@@ -95,6 +126,12 @@ class ModifierManager(Dict[str, Any]):
|
|
|
95
126
|
|
|
96
127
|
Returns:
|
|
97
128
|
The modified coroutine function.
|
|
129
|
+
|
|
130
|
+
Examples:
|
|
131
|
+
>>> async def my_coro():
|
|
132
|
+
... pass
|
|
133
|
+
>>> manager = ModifierManager(ModifierKwargs(runs_per_minute=60))
|
|
134
|
+
>>> modified_coro = manager.apply_async_modifiers(my_coro)
|
|
98
135
|
"""
|
|
99
136
|
# NOTE: THESE STACK IN REVERSE ORDER
|
|
100
137
|
if self.use_limiter:
|
|
@@ -122,6 +159,12 @@ class ModifierManager(Dict[str, Any]):
|
|
|
122
159
|
|
|
123
160
|
Returns:
|
|
124
161
|
The wrapped synchronous function.
|
|
162
|
+
|
|
163
|
+
Examples:
|
|
164
|
+
>>> def my_function():
|
|
165
|
+
... pass
|
|
166
|
+
>>> manager = ModifierManager(ModifierKwargs())
|
|
167
|
+
>>> modified_function = manager.apply_sync_modifiers(my_function)
|
|
125
168
|
"""
|
|
126
169
|
|
|
127
170
|
@functools.wraps(function)
|
|
@@ -167,5 +210,6 @@ class ModifierManager(Dict[str, Any]):
|
|
|
167
210
|
return self._modifiers[modifier_key] # type: ignore [literal-required]
|
|
168
211
|
|
|
169
212
|
|
|
213
|
+
# TODO give us docstrings
|
|
170
214
|
nulls = ModifierManager(null_modifiers)
|
|
171
215
|
user_defaults = ModifierManager(user_set_default_modifiers)
|
|
@@ -14,13 +14,39 @@ from a_sync.primitives import ThreadsafeSemaphore, DummySemaphore
|
|
|
14
14
|
def apply_semaphore( # type: ignore [misc]
|
|
15
15
|
semaphore: SemaphoreSpec,
|
|
16
16
|
) -> AsyncDecorator[P, T]:
|
|
17
|
-
"""
|
|
17
|
+
"""Create a decorator to apply a semaphore to a coroutine function.
|
|
18
18
|
|
|
19
19
|
This overload is used when the semaphore is provided as a single argument,
|
|
20
20
|
returning a decorator that can be applied to a coroutine function.
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
|
-
semaphore
|
|
23
|
+
semaphore (Union[int, asyncio.Semaphore, primitives.Semaphore]):
|
|
24
|
+
The semaphore to apply, which can be an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
Using as a decorator with an integer semaphore:
|
|
28
|
+
>>> @apply_semaphore(2)
|
|
29
|
+
... async def limited_concurrent_function():
|
|
30
|
+
... pass
|
|
31
|
+
|
|
32
|
+
Using as a decorator with an `asyncio.Semaphore`:
|
|
33
|
+
>>> sem = asyncio.Semaphore(2)
|
|
34
|
+
>>> @apply_semaphore(sem)
|
|
35
|
+
... async def another_function():
|
|
36
|
+
... pass
|
|
37
|
+
|
|
38
|
+
Using as a decorator with a `primitives.Semaphore`:
|
|
39
|
+
>>> sem = primitives.ThreadsafeSemaphore(2)
|
|
40
|
+
>>> @apply_semaphore(sem)
|
|
41
|
+
... async def yet_another_function():
|
|
42
|
+
... pass
|
|
43
|
+
|
|
44
|
+
See Also:
|
|
45
|
+
- :class:`asyncio.Semaphore`
|
|
46
|
+
- :class:`primitives.Semaphore`
|
|
47
|
+
|
|
48
|
+
Note:
|
|
49
|
+
`primitives.Semaphore` is a subclass of `asyncio.Semaphore`. Therefore, when the documentation refers to `asyncio.Semaphore`, it also includes `primitives.Semaphore` and any other subclasses.
|
|
24
50
|
"""
|
|
25
51
|
|
|
26
52
|
|
|
@@ -29,14 +55,36 @@ def apply_semaphore(
|
|
|
29
55
|
coro_fn: CoroFn[P, T],
|
|
30
56
|
semaphore: SemaphoreSpec,
|
|
31
57
|
) -> CoroFn[P, T]:
|
|
32
|
-
"""
|
|
58
|
+
"""Apply a semaphore directly to a coroutine function.
|
|
33
59
|
|
|
34
60
|
This overload is used when both the coroutine function and semaphore are provided,
|
|
35
61
|
directly applying the semaphore to the coroutine function.
|
|
36
62
|
|
|
37
63
|
Args:
|
|
38
|
-
coro_fn: The coroutine function to which the semaphore will be applied.
|
|
39
|
-
semaphore
|
|
64
|
+
coro_fn (Callable): The coroutine function to which the semaphore will be applied.
|
|
65
|
+
semaphore (Union[int, asyncio.Semaphore, primitives.Semaphore]):
|
|
66
|
+
The semaphore to apply, which can be an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`.
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
Applying directly to a function with an integer semaphore:
|
|
70
|
+
>>> async def my_coroutine():
|
|
71
|
+
... pass
|
|
72
|
+
>>> my_coroutine = apply_semaphore(my_coroutine, 3)
|
|
73
|
+
|
|
74
|
+
Applying directly with an `asyncio.Semaphore`:
|
|
75
|
+
>>> sem = asyncio.Semaphore(3)
|
|
76
|
+
>>> my_coroutine = apply_semaphore(my_coroutine, sem)
|
|
77
|
+
|
|
78
|
+
Applying directly with a `primitives.Semaphore`:
|
|
79
|
+
>>> sem = primitives.ThreadsafeSemaphore(3)
|
|
80
|
+
>>> my_coroutine = apply_semaphore(my_coroutine, sem)
|
|
81
|
+
|
|
82
|
+
See Also:
|
|
83
|
+
- :class:`asyncio.Semaphore`
|
|
84
|
+
- :class:`primitives.Semaphore`
|
|
85
|
+
|
|
86
|
+
Note:
|
|
87
|
+
`primitives.Semaphore` is a subclass of `asyncio.Semaphore`. Therefore, when the documentation refers to `asyncio.Semaphore`, it also includes `primitives.Semaphore` and any other subclasses.
|
|
40
88
|
"""
|
|
41
89
|
|
|
42
90
|
|
|
@@ -44,21 +92,46 @@ def apply_semaphore(
|
|
|
44
92
|
coro_fn: Optional[Union[CoroFn[P, T], SemaphoreSpec]] = None,
|
|
45
93
|
semaphore: SemaphoreSpec = None,
|
|
46
94
|
) -> AsyncDecoratorOrCoroFn[P, T]:
|
|
47
|
-
"""
|
|
95
|
+
"""Apply a semaphore to a coroutine function or return a decorator.
|
|
48
96
|
|
|
49
97
|
This function can be used to apply a semaphore to a coroutine function either by
|
|
50
98
|
passing the coroutine function and semaphore as arguments or by using the semaphore
|
|
51
99
|
as a decorator. It raises exceptions if the inputs are not valid.
|
|
52
100
|
|
|
53
101
|
Args:
|
|
54
|
-
coro_fn: The coroutine function to which the semaphore will be applied,
|
|
55
|
-
if the semaphore is to be used as a decorator.
|
|
56
|
-
semaphore
|
|
102
|
+
coro_fn (Optional[Callable]): The coroutine function to which the semaphore will be applied,
|
|
103
|
+
or None if the semaphore is to be used as a decorator.
|
|
104
|
+
semaphore (Union[int, asyncio.Semaphore, primitives.Semaphore]):
|
|
105
|
+
The semaphore to apply, which can be an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`.
|
|
57
106
|
|
|
58
107
|
Raises:
|
|
59
|
-
ValueError: If both coro_fn and semaphore are provided
|
|
108
|
+
ValueError: If both `coro_fn` and `semaphore` are provided and the first argument is an integer or `asyncio.Semaphore`.
|
|
60
109
|
exceptions.FunctionNotAsync: If the provided function is not a coroutine.
|
|
61
|
-
TypeError: If the semaphore is not an integer
|
|
110
|
+
TypeError: If the semaphore is not an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`.
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
Using as a decorator:
|
|
114
|
+
>>> @apply_semaphore(2)
|
|
115
|
+
... async def limited_concurrent_function():
|
|
116
|
+
... pass
|
|
117
|
+
|
|
118
|
+
Applying directly to a function:
|
|
119
|
+
>>> async def my_coroutine():
|
|
120
|
+
... pass
|
|
121
|
+
>>> my_coroutine = apply_semaphore(my_coroutine, 3)
|
|
122
|
+
|
|
123
|
+
Handling invalid inputs:
|
|
124
|
+
>>> try:
|
|
125
|
+
... apply_semaphore(3, 2)
|
|
126
|
+
... except ValueError as e:
|
|
127
|
+
... print(e)
|
|
128
|
+
|
|
129
|
+
See Also:
|
|
130
|
+
- :class:`asyncio.Semaphore`
|
|
131
|
+
- :class:`primitives.Semaphore`
|
|
132
|
+
|
|
133
|
+
Note:
|
|
134
|
+
`primitives.Semaphore` is a subclass of `asyncio.Semaphore`. Therefore, when the documentation refers to `asyncio.Semaphore`, it also includes `primitives.Semaphore` and any other subclasses.
|
|
62
135
|
"""
|
|
63
136
|
# Parse Inputs
|
|
64
137
|
if isinstance(coro_fn, (int, asyncio.Semaphore)):
|
a_sync/a_sync/singleton.py
CHANGED
|
@@ -6,32 +6,56 @@ class ASyncGenericSingleton(ASyncGenericBase, metaclass=ASyncSingletonMeta):
|
|
|
6
6
|
"""
|
|
7
7
|
A base class for creating singleton-esque ASync classes.
|
|
8
8
|
|
|
9
|
-
This class combines the functionality of ASyncGenericBase with a singleton pattern,
|
|
9
|
+
This class combines the functionality of :class:`ASyncGenericBase` with a singleton pattern,
|
|
10
10
|
ensuring that only one instance of the class exists per execution mode (sync/async).
|
|
11
|
-
It uses a custom metaclass to manage instance creation and caching.
|
|
11
|
+
It uses a custom metaclass :class:`ASyncSingletonMeta` to manage instance creation and caching.
|
|
12
12
|
|
|
13
|
-
Subclasses of ASyncGenericSingleton will have two instances instead of one:
|
|
13
|
+
Subclasses of :class:`ASyncGenericSingleton` will have two instances instead of one:
|
|
14
14
|
- one synchronous instance
|
|
15
15
|
- one asynchronous instance
|
|
16
16
|
|
|
17
17
|
This allows for proper behavior in both synchronous and asynchronous contexts
|
|
18
18
|
while maintaining the singleton pattern within each context.
|
|
19
19
|
|
|
20
|
+
Note:
|
|
21
|
+
This class is abstract and cannot be instantiated directly. Subclasses should define
|
|
22
|
+
the necessary properties and methods to specify the asynchronous behavior, as outlined
|
|
23
|
+
in :class:`ASyncABC`.
|
|
24
|
+
|
|
20
25
|
Example:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
.. code-block:: python
|
|
27
|
+
|
|
28
|
+
class MyAsyncSingleton(ASyncGenericSingleton):
|
|
29
|
+
@property
|
|
30
|
+
def __a_sync_flag_name__(self):
|
|
31
|
+
return "asynchronous"
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def __a_sync_flag_value__(self):
|
|
35
|
+
return self.asynchronous
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def __a_sync_default_mode__(cls):
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
@a_sync
|
|
42
|
+
def my_method(self):
|
|
43
|
+
# Method implementation
|
|
44
|
+
|
|
45
|
+
# These will return the same synchronous instance
|
|
46
|
+
sync_instance1 = MyAsyncSingleton(sync=True)
|
|
47
|
+
sync_instance2 = MyAsyncSingleton(sync=True)
|
|
48
|
+
|
|
49
|
+
# These will return the same asynchronous instance
|
|
50
|
+
async_instance1 = MyAsyncSingleton(asynchronous=True)
|
|
51
|
+
async_instance2 = MyAsyncSingleton(asynchronous=True)
|
|
52
|
+
|
|
53
|
+
assert sync_instance1 is sync_instance2
|
|
54
|
+
assert async_instance1 is async_instance2
|
|
55
|
+
assert sync_instance1 is not async_instance1
|
|
56
|
+
|
|
57
|
+
See Also:
|
|
58
|
+
- :class:`ASyncGenericBase` for base functionality.
|
|
59
|
+
- :class:`ASyncSingletonMeta` for the metaclass managing the singleton behavior.
|
|
60
|
+
- :class:`ASyncABC` for defining asynchronous and synchronous behavior.
|
|
37
61
|
"""
|