ez-a-sync 0.33.4__cp313-cp313-musllinux_1_2_i686.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- a_sync/ENVIRONMENT_VARIABLES.py +42 -0
- a_sync/__init__.pxd +2 -0
- a_sync/__init__.py +145 -0
- a_sync/_smart.c +22830 -0
- a_sync/_smart.cpython-313-i386-linux-musl.so +0 -0
- a_sync/_smart.pxd +2 -0
- a_sync/_smart.pyi +202 -0
- a_sync/_smart.pyx +674 -0
- a_sync/_typing.py +258 -0
- a_sync/a_sync/__init__.py +60 -0
- a_sync/a_sync/_descriptor.c +20537 -0
- a_sync/a_sync/_descriptor.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/_descriptor.pyi +33 -0
- a_sync/a_sync/_descriptor.pyx +422 -0
- a_sync/a_sync/_flags.c +6082 -0
- a_sync/a_sync/_flags.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/_flags.pxd +3 -0
- a_sync/a_sync/_flags.pyx +92 -0
- a_sync/a_sync/_helpers.c +14529 -0
- a_sync/a_sync/_helpers.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/_helpers.pxd +3 -0
- a_sync/a_sync/_helpers.pyi +10 -0
- a_sync/a_sync/_helpers.pyx +167 -0
- a_sync/a_sync/_kwargs.c +12202 -0
- a_sync/a_sync/_kwargs.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/_kwargs.pxd +2 -0
- a_sync/a_sync/_kwargs.pyx +64 -0
- a_sync/a_sync/_meta.py +210 -0
- a_sync/a_sync/abstract.c +12420 -0
- a_sync/a_sync/abstract.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/abstract.pyi +141 -0
- a_sync/a_sync/abstract.pyx +221 -0
- a_sync/a_sync/base.c +14940 -0
- a_sync/a_sync/base.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/base.pyi +60 -0
- a_sync/a_sync/base.pyx +271 -0
- a_sync/a_sync/config.py +168 -0
- a_sync/a_sync/decorator.py +651 -0
- a_sync/a_sync/flags.c +5272 -0
- a_sync/a_sync/flags.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/flags.pxd +72 -0
- a_sync/a_sync/flags.pyi +74 -0
- a_sync/a_sync/flags.pyx +72 -0
- a_sync/a_sync/function.c +37856 -0
- a_sync/a_sync/function.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/function.pxd +28 -0
- a_sync/a_sync/function.pyi +571 -0
- a_sync/a_sync/function.pyx +1381 -0
- a_sync/a_sync/method.c +29662 -0
- a_sync/a_sync/method.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/method.pxd +9 -0
- a_sync/a_sync/method.pyi +523 -0
- a_sync/a_sync/method.pyx +1023 -0
- a_sync/a_sync/modifiers/__init__.pxd +1 -0
- a_sync/a_sync/modifiers/__init__.py +101 -0
- a_sync/a_sync/modifiers/cache/__init__.py +160 -0
- a_sync/a_sync/modifiers/cache/memory.py +165 -0
- a_sync/a_sync/modifiers/limiter.py +132 -0
- a_sync/a_sync/modifiers/manager.c +16157 -0
- a_sync/a_sync/modifiers/manager.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/modifiers/manager.pxd +5 -0
- a_sync/a_sync/modifiers/manager.pyi +219 -0
- a_sync/a_sync/modifiers/manager.pyx +299 -0
- a_sync/a_sync/modifiers/semaphores.py +173 -0
- a_sync/a_sync/property.c +27268 -0
- a_sync/a_sync/property.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/property.pyi +376 -0
- a_sync/a_sync/property.pyx +819 -0
- a_sync/a_sync/singleton.py +63 -0
- a_sync/aliases.py +3 -0
- a_sync/async_property/__init__.pxd +1 -0
- a_sync/async_property/__init__.py +1 -0
- a_sync/async_property/cached.c +20397 -0
- a_sync/async_property/cached.cpython-313-i386-linux-musl.so +0 -0
- a_sync/async_property/cached.pxd +10 -0
- a_sync/async_property/cached.pyi +45 -0
- a_sync/async_property/cached.pyx +178 -0
- a_sync/async_property/proxy.c +34662 -0
- a_sync/async_property/proxy.cpython-313-i386-linux-musl.so +0 -0
- a_sync/async_property/proxy.pxd +2 -0
- a_sync/async_property/proxy.pyi +124 -0
- a_sync/async_property/proxy.pyx +474 -0
- a_sync/asyncio/__init__.pxd +6 -0
- a_sync/asyncio/__init__.py +164 -0
- a_sync/asyncio/as_completed.c +18849 -0
- a_sync/asyncio/as_completed.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/as_completed.pxd +8 -0
- a_sync/asyncio/as_completed.pyi +109 -0
- a_sync/asyncio/as_completed.pyx +269 -0
- a_sync/asyncio/create_task.c +15912 -0
- a_sync/asyncio/create_task.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/create_task.pxd +2 -0
- a_sync/asyncio/create_task.pyi +51 -0
- a_sync/asyncio/create_task.pyx +271 -0
- a_sync/asyncio/gather.c +16687 -0
- a_sync/asyncio/gather.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/gather.pyi +107 -0
- a_sync/asyncio/gather.pyx +218 -0
- a_sync/asyncio/igather.c +13080 -0
- a_sync/asyncio/igather.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/igather.pxd +1 -0
- a_sync/asyncio/igather.pyi +8 -0
- a_sync/asyncio/igather.pyx +183 -0
- a_sync/asyncio/sleep.c +9601 -0
- a_sync/asyncio/sleep.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/sleep.pyi +14 -0
- a_sync/asyncio/sleep.pyx +49 -0
- a_sync/debugging.c +15370 -0
- a_sync/debugging.cpython-313-i386-linux-musl.so +0 -0
- a_sync/debugging.pyi +76 -0
- a_sync/debugging.pyx +107 -0
- a_sync/exceptions.c +13320 -0
- a_sync/exceptions.cpython-313-i386-linux-musl.so +0 -0
- a_sync/exceptions.pyi +376 -0
- a_sync/exceptions.pyx +446 -0
- a_sync/executor.py +619 -0
- a_sync/functools.c +12746 -0
- a_sync/functools.cpython-313-i386-linux-musl.so +0 -0
- a_sync/functools.pxd +7 -0
- a_sync/functools.pyi +33 -0
- a_sync/functools.pyx +139 -0
- a_sync/future.py +1497 -0
- a_sync/iter.c +37279 -0
- a_sync/iter.cpython-313-i386-linux-musl.so +0 -0
- a_sync/iter.pxd +11 -0
- a_sync/iter.pyi +370 -0
- a_sync/iter.pyx +981 -0
- a_sync/primitives/__init__.pxd +1 -0
- a_sync/primitives/__init__.py +53 -0
- a_sync/primitives/_debug.c +15765 -0
- a_sync/primitives/_debug.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/_debug.pxd +12 -0
- a_sync/primitives/_debug.pyi +52 -0
- a_sync/primitives/_debug.pyx +223 -0
- a_sync/primitives/_loggable.c +11538 -0
- a_sync/primitives/_loggable.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/_loggable.pxd +4 -0
- a_sync/primitives/_loggable.pyi +66 -0
- a_sync/primitives/_loggable.pyx +102 -0
- a_sync/primitives/locks/__init__.pxd +8 -0
- a_sync/primitives/locks/__init__.py +17 -0
- a_sync/primitives/locks/counter.c +17938 -0
- a_sync/primitives/locks/counter.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/locks/counter.pxd +12 -0
- a_sync/primitives/locks/counter.pyi +151 -0
- a_sync/primitives/locks/counter.pyx +267 -0
- a_sync/primitives/locks/event.c +17072 -0
- a_sync/primitives/locks/event.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/locks/event.pxd +22 -0
- a_sync/primitives/locks/event.pyi +43 -0
- a_sync/primitives/locks/event.pyx +185 -0
- a_sync/primitives/locks/prio_semaphore.c +25635 -0
- a_sync/primitives/locks/prio_semaphore.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/locks/prio_semaphore.pxd +25 -0
- a_sync/primitives/locks/prio_semaphore.pyi +217 -0
- a_sync/primitives/locks/prio_semaphore.pyx +597 -0
- a_sync/primitives/locks/semaphore.c +26553 -0
- a_sync/primitives/locks/semaphore.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/locks/semaphore.pxd +21 -0
- a_sync/primitives/locks/semaphore.pyi +197 -0
- a_sync/primitives/locks/semaphore.pyx +454 -0
- a_sync/primitives/queue.py +1026 -0
- a_sync/py.typed +0 -0
- a_sync/sphinx/__init__.py +3 -0
- a_sync/sphinx/ext.py +289 -0
- a_sync/task.py +934 -0
- a_sync/utils/__init__.py +105 -0
- a_sync/utils/iterators.py +297 -0
- a_sync/utils/repr.c +15866 -0
- a_sync/utils/repr.cpython-313-i386-linux-musl.so +0 -0
- a_sync/utils/repr.pyi +2 -0
- a_sync/utils/repr.pyx +73 -0
- ez_a_sync-0.33.4.dist-info/METADATA +368 -0
- ez_a_sync-0.33.4.dist-info/RECORD +177 -0
- ez_a_sync-0.33.4.dist-info/WHEEL +5 -0
- ez_a_sync-0.33.4.dist-info/licenses/LICENSE.txt +17 -0
- ez_a_sync-0.33.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from a_sync.a_sync.modifiers.manager cimport ModifierManager
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
|
|
28
|
+
from typing import Union
|
|
29
|
+
|
|
30
|
+
from aiolimiter import AsyncLimiter
|
|
31
|
+
|
|
32
|
+
from a_sync._typing import ModifierKwargs
|
|
33
|
+
from a_sync.a_sync.modifiers.manager import valid_modifiers
|
|
34
|
+
from a_sync.primitives.locks import ThreadsafeSemaphore
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_modifiers_from(thing: Union[dict, type, object]) -> ModifierKwargs:
|
|
38
|
+
"""Extracts valid modifiers from a given object, type, or dictionary.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
thing: The source from which to extract modifiers. It can be a dictionary,
|
|
42
|
+
a type, or an object.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A ModifierKwargs object containing the valid modifiers extracted from the input.
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
Extracting modifiers from a class:
|
|
49
|
+
|
|
50
|
+
>>> class Example:
|
|
51
|
+
... cache_type = 'memory'
|
|
52
|
+
... runs_per_minute = 60
|
|
53
|
+
>>> get_modifiers_from(Example)
|
|
54
|
+
ModifierKwargs({'cache_type': 'memory', 'runs_per_minute': 60})
|
|
55
|
+
|
|
56
|
+
Extracting modifiers from a dictionary:
|
|
57
|
+
|
|
58
|
+
>>> modifiers_dict = {'cache_type': 'memory', 'semaphore': 5}
|
|
59
|
+
>>> get_modifiers_from(modifiers_dict)
|
|
60
|
+
ModifierKwargs({'cache_type': 'memory', 'semaphore': ThreadsafeSemaphore(5)})
|
|
61
|
+
|
|
62
|
+
See Also:
|
|
63
|
+
- :class:`a_sync.a_sync.modifiers.manager.ModifierManager`
|
|
64
|
+
"""
|
|
65
|
+
if isinstance(thing, dict):
|
|
66
|
+
apply_class_defined_modifiers(thing)
|
|
67
|
+
return ModifierKwargs((modifier, thing[modifier]) for modifier in valid_modifiers if modifier in thing) # type: ignore [misc]
|
|
68
|
+
return ModifierKwargs((modifier, getattr(thing, modifier)) for modifier in valid_modifiers if hasattr(thing, modifier)) # type: ignore [misc]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def apply_class_defined_modifiers(attrs_from_metaclass: dict):
|
|
72
|
+
"""Applies class-defined modifiers to a dictionary of attributes.
|
|
73
|
+
|
|
74
|
+
This function modifies the input dictionary in place. If the 'semaphore' key
|
|
75
|
+
is present and its value is an integer, it is converted to a ThreadsafeSemaphore.
|
|
76
|
+
If the 'runs_per_minute' key is present and its value is an integer, it is
|
|
77
|
+
converted to an AsyncLimiter. If these keys are not present or their values
|
|
78
|
+
are not integers, the function will silently do nothing.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
attrs_from_metaclass: A dictionary of attributes from a metaclass.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
Applying modifiers to a dictionary:
|
|
85
|
+
|
|
86
|
+
>>> attrs = {'semaphore': 3, 'runs_per_minute': 60}
|
|
87
|
+
>>> apply_class_defined_modifiers(attrs)
|
|
88
|
+
>>> attrs['semaphore']
|
|
89
|
+
ThreadsafeSemaphore(3)
|
|
90
|
+
|
|
91
|
+
>>> attrs['runs_per_minute']
|
|
92
|
+
AsyncLimiter(60)
|
|
93
|
+
|
|
94
|
+
See Also:
|
|
95
|
+
- :class:`a_sync.primitives.locks.ThreadsafeSemaphore`
|
|
96
|
+
- :class:`aiolimiter.AsyncLimiter`
|
|
97
|
+
"""
|
|
98
|
+
if isinstance(val := attrs_from_metaclass.get("semaphore"), int):
|
|
99
|
+
attrs_from_metaclass["semaphore"] = ThreadsafeSemaphore(val)
|
|
100
|
+
if isinstance(val := attrs_from_metaclass.get("runs_per_minute"), int):
|
|
101
|
+
attrs_from_metaclass["runs_per_minute"] = AsyncLimiter(val)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# mypy: disable-error-code=valid-type
|
|
2
|
+
# mypy: disable-error-code=misc
|
|
3
|
+
from asyncio import iscoroutinefunction
|
|
4
|
+
from typing import Optional, TypedDict, Union, overload
|
|
5
|
+
|
|
6
|
+
from typing_extensions import Unpack
|
|
7
|
+
|
|
8
|
+
from a_sync import exceptions
|
|
9
|
+
from a_sync._typing import AsyncDecorator, AsyncDecoratorOrCoroFn, CoroFn, P, T, CacheType
|
|
10
|
+
from a_sync.a_sync.modifiers.cache.memory import apply_async_memory_cache
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CacheArgs(TypedDict):
|
|
14
|
+
"""Typed dictionary for cache arguments."""
|
|
15
|
+
|
|
16
|
+
cache_type: CacheType
|
|
17
|
+
"""The type of cache to use. Currently, only 'memory' is implemented."""
|
|
18
|
+
|
|
19
|
+
cache_typed: bool
|
|
20
|
+
"""Whether to consider types for cache keys."""
|
|
21
|
+
|
|
22
|
+
ram_cache_maxsize: Optional[int]
|
|
23
|
+
"""The maximum size for the LRU cache."""
|
|
24
|
+
|
|
25
|
+
ram_cache_ttl: Optional[int]
|
|
26
|
+
"""The time-to-live for items in the LRU cache."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def apply_async_cache(
|
|
31
|
+
**modifiers: Unpack[CacheArgs],
|
|
32
|
+
) -> AsyncDecorator[P, T]:
|
|
33
|
+
"""Creates a decorator to apply an asynchronous cache.
|
|
34
|
+
|
|
35
|
+
This overload is used when no coroutine function is provided. The returned
|
|
36
|
+
decorator can be applied to a coroutine function later.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
**modifiers: Cache configuration options such as `cache_type`, `cache_typed`, `ram_cache_maxsize`, and `ram_cache_ttl`.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
>>> @apply_async_cache(cache_type='memory', ram_cache_maxsize=100)
|
|
43
|
+
... async def my_function():
|
|
44
|
+
... pass
|
|
45
|
+
|
|
46
|
+
See Also:
|
|
47
|
+
:func:`apply_async_memory_cache`
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def apply_async_cache(
|
|
53
|
+
coro_fn: int,
|
|
54
|
+
**modifiers: Unpack[CacheArgs],
|
|
55
|
+
) -> AsyncDecorator[P, T]:
|
|
56
|
+
"""Creates a decorator with `ram_cache_maxsize` set by an integer.
|
|
57
|
+
|
|
58
|
+
This overload is used when an integer is provided as the first argument,
|
|
59
|
+
which sets the `ram_cache_maxsize` for the cache. The returned decorator
|
|
60
|
+
can be applied to a coroutine function later.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
coro_fn: An integer to set as the max size for the cache.
|
|
64
|
+
**modifiers: Additional cache configuration options.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
>>> @apply_async_cache(100, cache_type='memory')
|
|
68
|
+
... async def my_function():
|
|
69
|
+
... pass
|
|
70
|
+
|
|
71
|
+
See Also:
|
|
72
|
+
:func:`apply_async_memory_cache`
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@overload
|
|
77
|
+
def apply_async_cache(
|
|
78
|
+
coro_fn: CoroFn[P, T],
|
|
79
|
+
**modifiers: Unpack[CacheArgs],
|
|
80
|
+
) -> CoroFn[P, T]:
|
|
81
|
+
"""Applies an asynchronous cache directly to a coroutine function.
|
|
82
|
+
|
|
83
|
+
This overload is used when a coroutine function is provided. The cache is
|
|
84
|
+
applied directly to the function.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
coro_fn: The coroutine function to be cached.
|
|
88
|
+
**modifiers: Cache configuration options such as `cache_type`, `cache_typed`, `ram_cache_maxsize`, and `ram_cache_ttl`.
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
>>> async def my_function():
|
|
92
|
+
... pass
|
|
93
|
+
>>> cached_function = apply_async_cache(my_function, cache_type='memory', ram_cache_maxsize=100)
|
|
94
|
+
|
|
95
|
+
See Also:
|
|
96
|
+
:func:`apply_async_memory_cache`
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def apply_async_cache(
|
|
101
|
+
coro_fn: Union[CoroFn[P, T], CacheType, int] = None,
|
|
102
|
+
cache_type: CacheType = "memory",
|
|
103
|
+
cache_typed: bool = False,
|
|
104
|
+
ram_cache_maxsize: Optional[int] = None,
|
|
105
|
+
ram_cache_ttl: Optional[int] = None,
|
|
106
|
+
) -> AsyncDecoratorOrCoroFn[P, T]:
|
|
107
|
+
"""Applies an asynchronous cache to a coroutine function.
|
|
108
|
+
|
|
109
|
+
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.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
coro_fn: The coroutine function to apply the cache to, or an integer to set as the max size.
|
|
113
|
+
cache_type: The type of cache to use. Currently, only 'memory' is implemented.
|
|
114
|
+
cache_typed: Whether to consider types for cache keys.
|
|
115
|
+
ram_cache_maxsize: The maximum size for the LRU cache. If set to an integer, it overrides coro_fn.
|
|
116
|
+
ram_cache_ttl: The time-to-live for items in the LRU cache.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
TypeError: If 'ram_cache_maxsize' is not an integer or None.
|
|
120
|
+
FunctionNotAsync: If the provided function is not asynchronous.
|
|
121
|
+
NotImplementedError: If an unsupported cache type is specified.
|
|
122
|
+
|
|
123
|
+
Examples:
|
|
124
|
+
>>> @apply_async_cache(cache_type='memory', ram_cache_maxsize=100)
|
|
125
|
+
... async def my_function():
|
|
126
|
+
... pass
|
|
127
|
+
|
|
128
|
+
>>> async def my_function():
|
|
129
|
+
... pass
|
|
130
|
+
>>> cached_function = apply_async_cache(my_function, cache_type='memory', ram_cache_maxsize=100)
|
|
131
|
+
|
|
132
|
+
>>> @apply_async_cache(100, cache_type='memory')
|
|
133
|
+
... async def another_function():
|
|
134
|
+
... pass
|
|
135
|
+
|
|
136
|
+
See Also:
|
|
137
|
+
:func:`apply_async_memory_cache`
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# Parse Inputs
|
|
141
|
+
if isinstance(coro_fn, int):
|
|
142
|
+
assert ram_cache_maxsize is None
|
|
143
|
+
ram_cache_maxsize = coro_fn
|
|
144
|
+
coro_fn = None
|
|
145
|
+
|
|
146
|
+
# Validate
|
|
147
|
+
elif coro_fn is None:
|
|
148
|
+
if ram_cache_maxsize is not None and not isinstance(ram_cache_maxsize, int):
|
|
149
|
+
raise TypeError("'lru_cache_maxsize' must be an integer or None.", ram_cache_maxsize)
|
|
150
|
+
elif not iscoroutinefunction(coro_fn):
|
|
151
|
+
raise exceptions.FunctionNotAsync(coro_fn)
|
|
152
|
+
|
|
153
|
+
if cache_type == "memory":
|
|
154
|
+
cache_decorator = apply_async_memory_cache(
|
|
155
|
+
maxsize=ram_cache_maxsize, ttl=ram_cache_ttl, typed=cache_typed
|
|
156
|
+
)
|
|
157
|
+
return cache_decorator if coro_fn is None else cache_decorator(coro_fn)
|
|
158
|
+
elif cache_type == "disk":
|
|
159
|
+
pass
|
|
160
|
+
raise NotImplementedError(f"cache_type: {cache_type}")
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# mypy: disable-error-code=valid-type
|
|
2
|
+
# mypy: disable-error-code=misc
|
|
3
|
+
|
|
4
|
+
from async_lru import alru_cache
|
|
5
|
+
|
|
6
|
+
from a_sync._typing import *
|
|
7
|
+
from a_sync.exceptions import FunctionNotAsync
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CacheKwargs(TypedDict):
|
|
11
|
+
"""Typed dictionary for cache keyword arguments."""
|
|
12
|
+
|
|
13
|
+
maxsize: Optional[int]
|
|
14
|
+
ttl: Optional[int]
|
|
15
|
+
typed: bool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@overload
|
|
19
|
+
def apply_async_memory_cache(**kwargs: Unpack[CacheKwargs]) -> AsyncDecorator[P, T]:
|
|
20
|
+
"""
|
|
21
|
+
Creates a decorator to apply an asynchronous LRU cache.
|
|
22
|
+
|
|
23
|
+
This overload is used when no coroutine function is provided. The returned
|
|
24
|
+
decorator can be applied to a coroutine function later.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
**kwargs: Keyword arguments for cache configuration, including maxsize,
|
|
28
|
+
ttl, and typed.
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
>>> @apply_async_memory_cache(maxsize=128, ttl=60)
|
|
32
|
+
... async def fetch_data():
|
|
33
|
+
... pass
|
|
34
|
+
|
|
35
|
+
See Also:
|
|
36
|
+
- :func:`alru_cache` for the underlying caching mechanism.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@overload
|
|
41
|
+
def apply_async_memory_cache(coro_fn: int, **kwargs: Unpack[CacheKwargs]) -> AsyncDecorator[P, T]:
|
|
42
|
+
"""Creates a decorator with maxsize set by an integer.
|
|
43
|
+
|
|
44
|
+
This overload is used when an integer is provided as the coroutine function,
|
|
45
|
+
which sets the maxsize for the cache. The returned decorator can be applied
|
|
46
|
+
to a coroutine function later.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
coro_fn: An integer to set as maxsize for the cache.
|
|
50
|
+
**kwargs: Additional keyword arguments for cache configuration, including
|
|
51
|
+
ttl and typed.
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
>>> # This usage is not supported
|
|
55
|
+
>>> apply_async_memory_cache(128, ttl=60)
|
|
56
|
+
|
|
57
|
+
See Also:
|
|
58
|
+
- :func:`apply_async_memory_cache` for correct usage.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@overload
|
|
63
|
+
def apply_async_memory_cache(coro_fn: CoroFn[P, T], **kwargs: Unpack[CacheKwargs]) -> CoroFn[P, T]:
|
|
64
|
+
"""
|
|
65
|
+
Applies an asynchronous LRU cache to a provided coroutine function.
|
|
66
|
+
|
|
67
|
+
This overload is used when a coroutine function is provided. The cache is
|
|
68
|
+
applied directly to the function.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
coro_fn: The coroutine function to be cached.
|
|
72
|
+
**kwargs: Keyword arguments for cache configuration, including maxsize,
|
|
73
|
+
ttl, and typed.
|
|
74
|
+
|
|
75
|
+
Examples:
|
|
76
|
+
>>> async def fetch_data():
|
|
77
|
+
... pass
|
|
78
|
+
>>> cached_fetch = apply_async_memory_cache(fetch_data, maxsize=128, ttl=60)
|
|
79
|
+
|
|
80
|
+
See Also:
|
|
81
|
+
- :func:`alru_cache` for the underlying caching mechanism.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@overload
|
|
86
|
+
def apply_async_memory_cache(
|
|
87
|
+
coro_fn: Literal[None], **kwargs: Unpack[CacheKwargs]
|
|
88
|
+
) -> AsyncDecorator[P, T]:
|
|
89
|
+
"""
|
|
90
|
+
Creates a decorator to apply an asynchronous LRU cache.
|
|
91
|
+
|
|
92
|
+
This duplicate overload is used when no coroutine function is provided. The
|
|
93
|
+
returned decorator can be applied to a coroutine function later.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
coro_fn: None, indicating no coroutine function is provided.
|
|
97
|
+
**kwargs: Keyword arguments for cache configuration, including maxsize,
|
|
98
|
+
ttl, and typed.
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
>>> @apply_async_memory_cache(maxsize=128, ttl=60)
|
|
102
|
+
... async def fetch_data():
|
|
103
|
+
... pass
|
|
104
|
+
|
|
105
|
+
See Also:
|
|
106
|
+
- :func:`alru_cache` for the underlying caching mechanism.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def apply_async_memory_cache(
|
|
111
|
+
coro_fn: Optional[Union[CoroFn[P, T], int]] = None,
|
|
112
|
+
maxsize: Optional[int] = None,
|
|
113
|
+
ttl: Optional[int] = None,
|
|
114
|
+
typed: bool = False,
|
|
115
|
+
) -> AsyncDecoratorOrCoroFn[P, T]:
|
|
116
|
+
"""
|
|
117
|
+
Applies an asynchronous LRU cache to a coroutine function.
|
|
118
|
+
|
|
119
|
+
This function uses the `alru_cache` from the `async_lru` library to cache
|
|
120
|
+
the results of an asynchronous coroutine function. The cache can be configured
|
|
121
|
+
with a maximum size, a time-to-live (TTL), and whether the cache keys should
|
|
122
|
+
be typed.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
coro_fn: The coroutine function to be cached, or an integer to set as maxsize.
|
|
126
|
+
maxsize: The maximum size of the cache. If set to -1, it is converted to None,
|
|
127
|
+
making the cache unbounded.
|
|
128
|
+
ttl: The time-to-live for cache entries in seconds. If None, entries do not expire.
|
|
129
|
+
typed: Whether to consider the types of arguments as part of the cache key.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
TypeError: If `maxsize` is not a positive integer or None when `coro_fn` is None.
|
|
133
|
+
exceptions.FunctionNotAsync: If `coro_fn` is not an asynchronous function.
|
|
134
|
+
|
|
135
|
+
Examples:
|
|
136
|
+
>>> @apply_async_memory_cache(maxsize=128, ttl=60)
|
|
137
|
+
... async def fetch_data():
|
|
138
|
+
... pass
|
|
139
|
+
|
|
140
|
+
>>> async def fetch_data():
|
|
141
|
+
... pass
|
|
142
|
+
>>> cached_fetch = apply_async_memory_cache(fetch_data, maxsize=128, ttl=60)
|
|
143
|
+
|
|
144
|
+
See Also:
|
|
145
|
+
- :func:`alru_cache` for the underlying caching mechanism.
|
|
146
|
+
"""
|
|
147
|
+
# Parse Inputs
|
|
148
|
+
if isinstance(coro_fn, int):
|
|
149
|
+
assert maxsize is None
|
|
150
|
+
maxsize = coro_fn
|
|
151
|
+
coro_fn = None
|
|
152
|
+
|
|
153
|
+
# Validate
|
|
154
|
+
elif coro_fn is None:
|
|
155
|
+
if maxsize not in [None, -1] and (not isinstance(maxsize, int) or maxsize <= 0):
|
|
156
|
+
raise TypeError("'lru_cache_maxsize' must be a positive integer or None.", maxsize)
|
|
157
|
+
|
|
158
|
+
elif not iscoroutinefunction(coro_fn):
|
|
159
|
+
raise FunctionNotAsync(coro_fn)
|
|
160
|
+
|
|
161
|
+
if maxsize == -1:
|
|
162
|
+
maxsize = None
|
|
163
|
+
|
|
164
|
+
cache_decorator = alru_cache(maxsize=maxsize, ttl=ttl, typed=typed)
|
|
165
|
+
return cache_decorator if coro_fn is None else cache_decorator(coro_fn)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# mypy: disable-error-code=valid-type
|
|
2
|
+
# mypy: disable-error-code=misc
|
|
3
|
+
from asyncio import iscoroutinefunction
|
|
4
|
+
|
|
5
|
+
from aiolimiter import AsyncLimiter
|
|
6
|
+
|
|
7
|
+
from a_sync import aliases, exceptions
|
|
8
|
+
from a_sync._typing import *
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
LimiterSpec = Union[int, AsyncLimiter]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@overload
|
|
15
|
+
def apply_rate_limit(
|
|
16
|
+
runs_per_minute: int,
|
|
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
|
+
|
|
37
|
+
@overload
|
|
38
|
+
def apply_rate_limit(
|
|
39
|
+
coro_fn: CoroFn[P, T],
|
|
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
|
+
|
|
64
|
+
def apply_rate_limit(
|
|
65
|
+
coro_fn: Optional[Union[CoroFn[P, T], int]] = None,
|
|
66
|
+
runs_per_minute: Optional[LimiterSpec] = None,
|
|
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
|
+
"""
|
|
106
|
+
# Parse Inputs
|
|
107
|
+
if isinstance(coro_fn, (int, AsyncLimiter)):
|
|
108
|
+
assert runs_per_minute is None
|
|
109
|
+
runs_per_minute = coro_fn
|
|
110
|
+
coro_fn = None
|
|
111
|
+
|
|
112
|
+
elif coro_fn is None:
|
|
113
|
+
if runs_per_minute is not None and not isinstance(runs_per_minute, (int, AsyncLimiter)):
|
|
114
|
+
raise TypeError("'runs_per_minute' must be an integer.", runs_per_minute)
|
|
115
|
+
|
|
116
|
+
elif not iscoroutinefunction(coro_fn):
|
|
117
|
+
raise exceptions.FunctionNotAsync(coro_fn)
|
|
118
|
+
|
|
119
|
+
def rate_limit_decorator(coro_fn: CoroFn[P, T]) -> CoroFn[P, T]:
|
|
120
|
+
limiter = (
|
|
121
|
+
runs_per_minute
|
|
122
|
+
if isinstance(runs_per_minute, AsyncLimiter)
|
|
123
|
+
else AsyncLimiter(runs_per_minute) if runs_per_minute else aliases.dummy
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
async def rate_limit_wrap(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
127
|
+
async with limiter: # type: ignore [attr-defined]
|
|
128
|
+
return await coro_fn(*args, **kwargs)
|
|
129
|
+
|
|
130
|
+
return rate_limit_wrap
|
|
131
|
+
|
|
132
|
+
return rate_limit_decorator if coro_fn is None else rate_limit_decorator(coro_fn)
|