ez-a-sync 0.32.29__cp310-cp310-win32.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 (177) hide show
  1. a_sync/ENVIRONMENT_VARIABLES.py +42 -0
  2. a_sync/__init__.pxd +2 -0
  3. a_sync/__init__.py +145 -0
  4. a_sync/_smart.c +22803 -0
  5. a_sync/_smart.cp310-win32.pyd +0 -0
  6. a_sync/_smart.pxd +2 -0
  7. a_sync/_smart.pyi +202 -0
  8. a_sync/_smart.pyx +674 -0
  9. a_sync/_typing.py +258 -0
  10. a_sync/a_sync/__init__.py +60 -0
  11. a_sync/a_sync/_descriptor.c +20528 -0
  12. a_sync/a_sync/_descriptor.cp310-win32.pyd +0 -0
  13. a_sync/a_sync/_descriptor.pyi +33 -0
  14. a_sync/a_sync/_descriptor.pyx +422 -0
  15. a_sync/a_sync/_flags.c +6074 -0
  16. a_sync/a_sync/_flags.cp310-win32.pyd +0 -0
  17. a_sync/a_sync/_flags.pxd +3 -0
  18. a_sync/a_sync/_flags.pyx +92 -0
  19. a_sync/a_sync/_helpers.c +14521 -0
  20. a_sync/a_sync/_helpers.cp310-win32.pyd +0 -0
  21. a_sync/a_sync/_helpers.pxd +3 -0
  22. a_sync/a_sync/_helpers.pyi +10 -0
  23. a_sync/a_sync/_helpers.pyx +167 -0
  24. a_sync/a_sync/_kwargs.c +12194 -0
  25. a_sync/a_sync/_kwargs.cp310-win32.pyd +0 -0
  26. a_sync/a_sync/_kwargs.pxd +2 -0
  27. a_sync/a_sync/_kwargs.pyx +64 -0
  28. a_sync/a_sync/_meta.py +210 -0
  29. a_sync/a_sync/abstract.c +12411 -0
  30. a_sync/a_sync/abstract.cp310-win32.pyd +0 -0
  31. a_sync/a_sync/abstract.pyi +141 -0
  32. a_sync/a_sync/abstract.pyx +221 -0
  33. a_sync/a_sync/base.c +14932 -0
  34. a_sync/a_sync/base.cp310-win32.pyd +0 -0
  35. a_sync/a_sync/base.pyi +60 -0
  36. a_sync/a_sync/base.pyx +271 -0
  37. a_sync/a_sync/config.py +168 -0
  38. a_sync/a_sync/decorator.py +651 -0
  39. a_sync/a_sync/flags.c +5272 -0
  40. a_sync/a_sync/flags.cp310-win32.pyd +0 -0
  41. a_sync/a_sync/flags.pxd +72 -0
  42. a_sync/a_sync/flags.pyi +74 -0
  43. a_sync/a_sync/flags.pyx +72 -0
  44. a_sync/a_sync/function.c +37846 -0
  45. a_sync/a_sync/function.cp310-win32.pyd +0 -0
  46. a_sync/a_sync/function.pxd +28 -0
  47. a_sync/a_sync/function.pyi +571 -0
  48. a_sync/a_sync/function.pyx +1381 -0
  49. a_sync/a_sync/method.c +29774 -0
  50. a_sync/a_sync/method.cp310-win32.pyd +0 -0
  51. a_sync/a_sync/method.pxd +9 -0
  52. a_sync/a_sync/method.pyi +525 -0
  53. a_sync/a_sync/method.pyx +1023 -0
  54. a_sync/a_sync/modifiers/__init__.pxd +1 -0
  55. a_sync/a_sync/modifiers/__init__.py +101 -0
  56. a_sync/a_sync/modifiers/cache/__init__.py +160 -0
  57. a_sync/a_sync/modifiers/cache/memory.py +165 -0
  58. a_sync/a_sync/modifiers/limiter.py +132 -0
  59. a_sync/a_sync/modifiers/manager.c +16149 -0
  60. a_sync/a_sync/modifiers/manager.cp310-win32.pyd +0 -0
  61. a_sync/a_sync/modifiers/manager.pxd +5 -0
  62. a_sync/a_sync/modifiers/manager.pyi +219 -0
  63. a_sync/a_sync/modifiers/manager.pyx +299 -0
  64. a_sync/a_sync/modifiers/semaphores.py +173 -0
  65. a_sync/a_sync/property.c +27260 -0
  66. a_sync/a_sync/property.cp310-win32.pyd +0 -0
  67. a_sync/a_sync/property.pyi +376 -0
  68. a_sync/a_sync/property.pyx +819 -0
  69. a_sync/a_sync/singleton.py +63 -0
  70. a_sync/aliases.py +3 -0
  71. a_sync/async_property/__init__.pxd +1 -0
  72. a_sync/async_property/__init__.py +1 -0
  73. a_sync/async_property/cached.c +20386 -0
  74. a_sync/async_property/cached.cp310-win32.pyd +0 -0
  75. a_sync/async_property/cached.pxd +10 -0
  76. a_sync/async_property/cached.pyi +45 -0
  77. a_sync/async_property/cached.pyx +178 -0
  78. a_sync/async_property/proxy.c +34654 -0
  79. a_sync/async_property/proxy.cp310-win32.pyd +0 -0
  80. a_sync/async_property/proxy.pxd +2 -0
  81. a_sync/async_property/proxy.pyi +124 -0
  82. a_sync/async_property/proxy.pyx +474 -0
  83. a_sync/asyncio/__init__.pxd +6 -0
  84. a_sync/asyncio/__init__.py +164 -0
  85. a_sync/asyncio/as_completed.c +18841 -0
  86. a_sync/asyncio/as_completed.cp310-win32.pyd +0 -0
  87. a_sync/asyncio/as_completed.pxd +8 -0
  88. a_sync/asyncio/as_completed.pyi +109 -0
  89. a_sync/asyncio/as_completed.pyx +269 -0
  90. a_sync/asyncio/create_task.c +15902 -0
  91. a_sync/asyncio/create_task.cp310-win32.pyd +0 -0
  92. a_sync/asyncio/create_task.pxd +2 -0
  93. a_sync/asyncio/create_task.pyi +51 -0
  94. a_sync/asyncio/create_task.pyx +271 -0
  95. a_sync/asyncio/gather.c +16679 -0
  96. a_sync/asyncio/gather.cp310-win32.pyd +0 -0
  97. a_sync/asyncio/gather.pyi +107 -0
  98. a_sync/asyncio/gather.pyx +218 -0
  99. a_sync/asyncio/igather.c +12676 -0
  100. a_sync/asyncio/igather.cp310-win32.pyd +0 -0
  101. a_sync/asyncio/igather.pxd +1 -0
  102. a_sync/asyncio/igather.pyi +7 -0
  103. a_sync/asyncio/igather.pyx +182 -0
  104. a_sync/asyncio/sleep.c +9593 -0
  105. a_sync/asyncio/sleep.cp310-win32.pyd +0 -0
  106. a_sync/asyncio/sleep.pyi +14 -0
  107. a_sync/asyncio/sleep.pyx +49 -0
  108. a_sync/debugging.c +15362 -0
  109. a_sync/debugging.cp310-win32.pyd +0 -0
  110. a_sync/debugging.pyi +76 -0
  111. a_sync/debugging.pyx +107 -0
  112. a_sync/exceptions.c +13312 -0
  113. a_sync/exceptions.cp310-win32.pyd +0 -0
  114. a_sync/exceptions.pyi +376 -0
  115. a_sync/exceptions.pyx +446 -0
  116. a_sync/executor.py +619 -0
  117. a_sync/functools.c +12738 -0
  118. a_sync/functools.cp310-win32.pyd +0 -0
  119. a_sync/functools.pxd +7 -0
  120. a_sync/functools.pyi +33 -0
  121. a_sync/functools.pyx +139 -0
  122. a_sync/future.py +1497 -0
  123. a_sync/iter.c +37271 -0
  124. a_sync/iter.cp310-win32.pyd +0 -0
  125. a_sync/iter.pxd +11 -0
  126. a_sync/iter.pyi +370 -0
  127. a_sync/iter.pyx +981 -0
  128. a_sync/primitives/__init__.pxd +1 -0
  129. a_sync/primitives/__init__.py +53 -0
  130. a_sync/primitives/_debug.c +15757 -0
  131. a_sync/primitives/_debug.cp310-win32.pyd +0 -0
  132. a_sync/primitives/_debug.pxd +12 -0
  133. a_sync/primitives/_debug.pyi +52 -0
  134. a_sync/primitives/_debug.pyx +223 -0
  135. a_sync/primitives/_loggable.c +11529 -0
  136. a_sync/primitives/_loggable.cp310-win32.pyd +0 -0
  137. a_sync/primitives/_loggable.pxd +4 -0
  138. a_sync/primitives/_loggable.pyi +66 -0
  139. a_sync/primitives/_loggable.pyx +102 -0
  140. a_sync/primitives/locks/__init__.pxd +8 -0
  141. a_sync/primitives/locks/__init__.py +17 -0
  142. a_sync/primitives/locks/counter.c +17679 -0
  143. a_sync/primitives/locks/counter.cp310-win32.pyd +0 -0
  144. a_sync/primitives/locks/counter.pxd +12 -0
  145. a_sync/primitives/locks/counter.pyi +151 -0
  146. a_sync/primitives/locks/counter.pyx +260 -0
  147. a_sync/primitives/locks/event.c +17063 -0
  148. a_sync/primitives/locks/event.cp310-win32.pyd +0 -0
  149. a_sync/primitives/locks/event.pxd +22 -0
  150. a_sync/primitives/locks/event.pyi +43 -0
  151. a_sync/primitives/locks/event.pyx +185 -0
  152. a_sync/primitives/locks/prio_semaphore.c +25590 -0
  153. a_sync/primitives/locks/prio_semaphore.cp310-win32.pyd +0 -0
  154. a_sync/primitives/locks/prio_semaphore.pxd +25 -0
  155. a_sync/primitives/locks/prio_semaphore.pyi +217 -0
  156. a_sync/primitives/locks/prio_semaphore.pyx +597 -0
  157. a_sync/primitives/locks/semaphore.c +26509 -0
  158. a_sync/primitives/locks/semaphore.cp310-win32.pyd +0 -0
  159. a_sync/primitives/locks/semaphore.pxd +21 -0
  160. a_sync/primitives/locks/semaphore.pyi +197 -0
  161. a_sync/primitives/locks/semaphore.pyx +454 -0
  162. a_sync/primitives/queue.py +1022 -0
  163. a_sync/py.typed +0 -0
  164. a_sync/sphinx/__init__.py +3 -0
  165. a_sync/sphinx/ext.py +289 -0
  166. a_sync/task.py +932 -0
  167. a_sync/utils/__init__.py +105 -0
  168. a_sync/utils/iterators.py +297 -0
  169. a_sync/utils/repr.c +15799 -0
  170. a_sync/utils/repr.cp310-win32.pyd +0 -0
  171. a_sync/utils/repr.pyi +2 -0
  172. a_sync/utils/repr.pyx +73 -0
  173. ez_a_sync-0.32.29.dist-info/METADATA +367 -0
  174. ez_a_sync-0.32.29.dist-info/RECORD +177 -0
  175. ez_a_sync-0.32.29.dist-info/WHEEL +5 -0
  176. ez_a_sync-0.32.29.dist-info/licenses/LICENSE.txt +17 -0
  177. ez_a_sync-0.32.29.dist-info/top_level.txt +1 -0
@@ -0,0 +1,5 @@
1
+ cdef class ModifierManager:
2
+ cdef readonly dict[str, object] _modifiers
3
+ cdef str __default
4
+ cpdef object apply_async_modifiers(self, coro_fn)
5
+ cdef str get_default(self)
@@ -0,0 +1,219 @@
1
+ from a_sync._typing import *
2
+ from _typeshed import Incomplete
3
+ from a_sync.a_sync.config import (
4
+ null_modifiers as null_modifiers,
5
+ user_set_default_modifiers as user_set_default_modifiers,
6
+ )
7
+ from a_sync.a_sync.modifiers import (
8
+ cache as cache,
9
+ limiter as limiter,
10
+ semaphores as semaphores,
11
+ )
12
+ from typing import Any
13
+
14
+ valid_modifiers: Tuple[str, ...]
15
+
16
+ class ModifierManager(Dict[str, Any]):
17
+ """Manages modifiers for asynchronous and synchronous functions.
18
+
19
+ This class is responsible for applying modifiers to functions, such as
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`
46
+ """
47
+
48
+ default: DefaultMode
49
+ cache_type: CacheType
50
+ cache_typed: bool
51
+ ram_cache_maxsize: Optional[int]
52
+ ram_cache_ttl: Optional[int]
53
+ runs_per_minute: Optional[int]
54
+ semaphore: SemaphoreSpec
55
+ executor: Executor
56
+ def __init__(self, modifiers: ModifierKwargs) -> None:
57
+ """Initializes the ModifierManager with the given modifiers.
58
+
59
+ Args:
60
+ modifiers: A dictionary of modifiers to be applied.
61
+
62
+ Raises:
63
+ ValueError: If an unsupported modifier is provided.
64
+ """
65
+
66
+ def __getattribute__(self, modifier_key: str) -> Any:
67
+ """Gets the value of a modifier.
68
+
69
+ Args:
70
+ modifier_key: The key of the modifier to retrieve.
71
+
72
+ Returns:
73
+ The value of the modifier, or the default value if not set.
74
+
75
+ Examples:
76
+ >>> manager = ModifierManager(cache_type='memory')
77
+ >>> manager.cache_type
78
+ 'memory'
79
+ """
80
+
81
+ @property
82
+ def use_limiter(self) -> bool:
83
+ """Determines if a rate limiter should be used.
84
+
85
+ Examples:
86
+ >>> manager = ModifierManager(runs_per_minute=60)
87
+ >>> manager.use_limiter
88
+ True
89
+ """
90
+
91
+ @property
92
+ def use_semaphore(self) -> bool:
93
+ """Determines if a semaphore should be used.
94
+
95
+ Examples:
96
+ >>> manager = ModifierManager(semaphore=Semaphore(5))
97
+ >>> manager.use_semaphore
98
+ True
99
+ """
100
+
101
+ @property
102
+ def use_cache(self) -> bool:
103
+ """Determines if caching should be used.
104
+
105
+ Examples:
106
+ >>> manager = ModifierManager(cache_type='memory')
107
+ >>> manager.use_cache
108
+ True
109
+ """
110
+
111
+ def apply_async_modifiers(self, coro_fn: CoroFn[P, T]) -> CoroFn[P, T]:
112
+ """Applies asynchronous modifiers to a coroutine function.
113
+
114
+ Args:
115
+ coro_fn: The coroutine function to modify.
116
+
117
+ Returns:
118
+ The modified coroutine function.
119
+
120
+ Examples:
121
+ >>> async def my_coro():
122
+ ... pass
123
+ >>> manager = ModifierManager(runs_per_minute=60)
124
+ >>> modified_coro = manager.apply_async_modifiers(my_coro)
125
+ """
126
+
127
+ def apply_sync_modifiers(self, function: SyncFn[P, T]) -> SyncFn[P, T]:
128
+ """Wraps a synchronous function.
129
+
130
+ Note:
131
+ There are no sync modifiers at this time, but they will be added here for convenience.
132
+
133
+ Args:
134
+ function: The synchronous function to wrap.
135
+
136
+ Returns:
137
+ The wrapped synchronous function.
138
+
139
+ Examples:
140
+ >>> def my_function():
141
+ ... pass
142
+ >>> manager = ModifierManager()
143
+ >>> modified_function = manager.apply_sync_modifiers(my_function)
144
+ """
145
+
146
+ def keys(self) -> KeysView[str]:
147
+ """Returns the keys of the modifiers.
148
+
149
+ Examples:
150
+ >>> manager = ModifierManager(cache_type='memory')
151
+ >>> list(manager.keys())
152
+ ['cache_type']
153
+ """
154
+
155
+ def values(self) -> ValuesView[Any]:
156
+ """Returns the values of the modifiers.
157
+
158
+ Examples:
159
+ >>> manager = ModifierManager(cache_type='memory')
160
+ >>> list(manager.values())
161
+ ['memory']
162
+ """
163
+
164
+ def items(self) -> ItemsView[str, Any]:
165
+ """Returns the items of the modifiers.
166
+
167
+ Examples:
168
+ >>> manager = ModifierManager(cache_type='memory')
169
+ >>> list(manager.items())
170
+ [('cache_type', 'memory')]
171
+ """
172
+
173
+ def __contains__(self, key: str) -> bool:
174
+ """Checks if a key is in the modifiers.
175
+
176
+ Args:
177
+ key: The key to check.
178
+
179
+ Examples:
180
+ >>> manager = ModifierManager(cache_type='memory')
181
+ >>> 'cache_type' in manager
182
+ True
183
+ """
184
+
185
+ def __iter__(self) -> Iterator[str]:
186
+ """Returns an iterator over the modifier keys.
187
+
188
+ Examples:
189
+ >>> manager = ModifierManager(cache_type='memory')
190
+ >>> list(iter(manager))
191
+ ['cache_type']
192
+ """
193
+
194
+ def __len__(self) -> int:
195
+ """Returns the number of modifiers.
196
+
197
+ Examples:
198
+ >>> manager = ModifierManager(cache_type='memory')
199
+ >>> len(manager)
200
+ 1
201
+ """
202
+
203
+ def __getitem__(self, modifier_key: str):
204
+ """Gets the value of a modifier by key.
205
+
206
+ Args:
207
+ modifier_key: The key of the modifier to retrieve.
208
+
209
+ Returns:
210
+ The value of the modifier.
211
+
212
+ Examples:
213
+ >>> manager = ModifierManager(cache_type='memory')
214
+ >>> manager['cache_type']
215
+ 'memory'
216
+ """
217
+
218
+ NULLS: Incomplete
219
+ USER_DEFAULTS: Incomplete
@@ -0,0 +1,299 @@
1
+ # mypy: disable-error-code=valid-type
2
+ import typing
3
+ from libc.stdint cimport uint8_t
4
+
5
+ from a_sync._typing import CoroFn, ModifierKwargs, P, SyncFn, T
6
+ from a_sync.a_sync.config import user_set_default_modifiers, null_modifiers
7
+ from a_sync.a_sync.modifiers import cache, limiter, semaphores
8
+ from a_sync.functools cimport wraps
9
+
10
+
11
+ # cdef typing
12
+ cdef object Any = typing.Any
13
+ cdef object Iterator = typing.Iterator
14
+ del typing
15
+
16
+ # cdef modifier decorators
17
+ cdef object apply_async_cache = cache.apply_async_cache
18
+ cdef object apply_rate_limit = limiter.apply_rate_limit
19
+ cdef object apply_semaphore = semaphores.apply_semaphore
20
+ del cache, limiter, semaphores
21
+
22
+
23
+ # TODO give me a docstring
24
+ valid_modifiers = tuple(
25
+ key
26
+ for key in ModifierKwargs.__annotations__
27
+ if not key.startswith("_") and not key.endswith("_")
28
+ )
29
+
30
+ cdef tuple[str, ...] _valid_modifiers = valid_modifiers
31
+
32
+ cdef object _getattribute = object.__getattribute__
33
+
34
+ cdef class ModifierManager:
35
+ """Manages modifiers for asynchronous and synchronous functions.
36
+
37
+ This class is responsible for applying modifiers to functions, such as
38
+ caching, rate limiting, and semaphores for asynchronous functions. It also
39
+ handles synchronous functions, although no sync modifiers are currently
40
+ implemented.
41
+
42
+ Examples:
43
+ Creating a ModifierManager with specific modifiers:
44
+
45
+ >>> modifiers = ModifierKwargs(cache_type='memory', runs_per_minute=60)
46
+ >>> manager = ModifierManager(modifiers)
47
+
48
+ Applying modifiers to an asynchronous function:
49
+
50
+ >>> async def my_coro():
51
+ ... pass
52
+ >>> modified_coro = manager.apply_async_modifiers(my_coro)
53
+
54
+ Applying modifiers to a synchronous function (no sync modifiers applied):
55
+
56
+ >>> def my_function():
57
+ ... pass
58
+ >>> modified_function = manager.apply_sync_modifiers(my_function)
59
+
60
+ See Also:
61
+ - :class:`a_sync.a_sync.modifiers.cache`
62
+ - :class:`a_sync.a_sync.modifiers.limiter`
63
+ - :class:`a_sync.a_sync.modifiers.semaphores`
64
+ """
65
+ def __cinit__(self, dict[str, object] modifiers, bint _skip_check = False) -> None:
66
+ """Initializes the ModifierManager with the given modifiers.
67
+
68
+ Args:
69
+ modifiers: A dictionary of modifiers to be applied.
70
+
71
+ Raises:
72
+ ValueError: If an unsupported modifier is provided.
73
+ """
74
+ cdef str key
75
+ if not _skip_check:
76
+ for key in modifiers.keys():
77
+ if key not in _valid_modifiers:
78
+ raise ValueError(f"'{key}' is not a supported modifier.")
79
+ self._modifiers = modifiers
80
+
81
+ def __repr__(self) -> str:
82
+ """Returns a string representation of the modifiers."""
83
+ return str(self._modifiers)
84
+
85
+ def __getattribute__(self, str modifier_key) -> Any:
86
+ """Gets the value of a modifier.
87
+
88
+ Args:
89
+ modifier_key: The key of the modifier to retrieve.
90
+
91
+ Returns:
92
+ The value of the modifier, or the default value if not set.
93
+
94
+ Examples:
95
+ >>> manager = ModifierManager(cache_type='memory')
96
+ >>> manager.cache_type
97
+ 'memory'
98
+ """
99
+ if modifier_key not in _valid_modifiers:
100
+ return _getattribute(self, modifier_key)
101
+ return (
102
+ self._modifiers[modifier_key]
103
+ if modifier_key in self._modifiers
104
+ else USER_DEFAULTS._modifiers[modifier_key]
105
+ )
106
+
107
+ def __reduce__(self):
108
+ return ModifierManager, (self._modifiers, True)
109
+
110
+ cdef str get_default(self):
111
+ cdef str default = self.__default
112
+ if default is None:
113
+ if "default" in self._modifiers:
114
+ default = self._modifiers["default"]
115
+ else:
116
+ default = USER_DEFAULTS._modifiers["default"]
117
+ self.__default = default
118
+ return default
119
+
120
+ @property
121
+ def use_limiter(self) -> bint:
122
+ """Determines if a rate limiter should be used.
123
+
124
+ Examples:
125
+ >>> manager = ModifierManager(runs_per_minute=60)
126
+ >>> manager.use_limiter
127
+ True
128
+ """
129
+ return self.runs_per_minute != NULLS.runs_per_minute
130
+
131
+ @property
132
+ def use_semaphore(self) -> bint:
133
+ """Determines if a semaphore should be used.
134
+
135
+ Examples:
136
+ >>> manager = ModifierManager(semaphore=SemaphoreSpec())
137
+ >>> manager.use_semaphore
138
+ True
139
+ """
140
+ return self.semaphore != NULLS.semaphore
141
+
142
+ @property
143
+ def use_cache(self) -> bint:
144
+ """Determines if caching should be used.
145
+
146
+ Examples:
147
+ >>> manager = ModifierManager(cache_type='memory')
148
+ >>> manager.use_cache
149
+ True
150
+ """
151
+ return any(
152
+ [
153
+ self.cache_type != NULLS.cache_type,
154
+ self.ram_cache_maxsize != NULLS.ram_cache_maxsize,
155
+ self.ram_cache_ttl != NULLS.ram_cache_ttl,
156
+ self.cache_typed != NULLS.cache_typed,
157
+ ]
158
+ )
159
+
160
+ cpdef object apply_async_modifiers(self, coro_fn: CoroFn[P, T]):
161
+ """Applies asynchronous modifiers to a coroutine function.
162
+
163
+ Args:
164
+ coro_fn: The coroutine function to modify.
165
+
166
+ Returns:
167
+ The modified coroutine function.
168
+
169
+ Examples:
170
+ >>> async def my_coro():
171
+ ... pass
172
+ >>> manager = ModifierManager(runs_per_minute=60)
173
+ >>> modified_coro = manager.apply_async_modifiers(my_coro)
174
+ """
175
+ # NOTE: THESE STACK IN REVERSE ORDER
176
+ if self.use_limiter:
177
+ coro_fn = apply_rate_limit(coro_fn, self.runs_per_minute)
178
+ if self.use_semaphore:
179
+ coro_fn = apply_semaphore(coro_fn, self.semaphore)
180
+ if self.use_cache:
181
+ coro_fn = apply_async_cache(
182
+ coro_fn,
183
+ cache_type=self.cache_type or "memory",
184
+ cache_typed=self.cache_typed,
185
+ ram_cache_maxsize=self.ram_cache_maxsize,
186
+ ram_cache_ttl=self.ram_cache_ttl,
187
+ )
188
+ return coro_fn
189
+
190
+ def apply_sync_modifiers(self, function: SyncFn[P, T]) -> SyncFn[P, T]:
191
+ """Wraps a synchronous function.
192
+
193
+ Note:
194
+ There are no sync modifiers at this time, but they will be added here for convenience.
195
+
196
+ Args:
197
+ function: The synchronous function to wrap.
198
+
199
+ Returns:
200
+ The wrapped synchronous function.
201
+
202
+ Examples:
203
+ >>> def my_function():
204
+ ... pass
205
+ >>> manager = ModifierManager()
206
+ >>> modified_function = manager.apply_sync_modifiers(my_function)
207
+ """
208
+
209
+ @wraps(function)
210
+ def sync_modifier_wrap(*args: P.args, **kwargs: P.kwargs) -> T:
211
+ return function(*args, **kwargs)
212
+
213
+ return sync_modifier_wrap
214
+
215
+ # Dictionary-like API
216
+ def keys(self) -> KeysView[str]: # type: ignore [override]
217
+ """Returns the keys of the modifiers.
218
+
219
+ Examples:
220
+ >>> manager = ModifierManager(cache_type='memory')
221
+ >>> list(manager.keys())
222
+ ['cache_type']
223
+ """
224
+ return self._modifiers.keys()
225
+
226
+ def values(self) -> ValuesView[Any]: # type: ignore [override]
227
+ """Returns the values of the modifiers.
228
+
229
+ Examples:
230
+ >>> manager = ModifierManager(cache_type='memory')
231
+ >>> list(manager.values())
232
+ ['memory']
233
+ """
234
+ return self._modifiers.values()
235
+
236
+ def items(self) -> ItemsView[str, Any]: # type: ignore [override]
237
+ """Returns the items of the modifiers.
238
+
239
+ Examples:
240
+ >>> manager = ModifierManager(cache_type='memory')
241
+ >>> list(manager.items())
242
+ [('cache_type', 'memory')]
243
+ """
244
+ return self._modifiers.items()
245
+
246
+ def __contains__(self, str key) -> bint: # type: ignore [override]
247
+ """Checks if a key is in the modifiers.
248
+
249
+ Args:
250
+ key: The key to check.
251
+
252
+ Examples:
253
+ >>> manager = ModifierManager(cache_type='memory')
254
+ >>> 'cache_type' in manager
255
+ True
256
+ """
257
+ return key in self._modifiers
258
+
259
+ def __iter__(self) -> Iterator[str]:
260
+ """Returns an iterator over the modifier keys.
261
+
262
+ Examples:
263
+ >>> manager = ModifierManager(cache_type='memory')
264
+ >>> list(iter(manager))
265
+ ['cache_type']
266
+ """
267
+ return iter(self._modifiers)
268
+
269
+ def __len__(self) -> uint8_t:
270
+ """Returns the number of modifiers.
271
+
272
+ Examples:
273
+ >>> manager = ModifierManager(cache_type='memory')
274
+ >>> len(manager)
275
+ 1
276
+ """
277
+ return len(<dict>self._modifiers)
278
+
279
+ def __getitem__(self, str modifier_key) -> Any:
280
+ """Gets the value of a modifier by key.
281
+
282
+ Args:
283
+ modifier_key: The key of the modifier to retrieve.
284
+
285
+ Returns:
286
+ The value of the modifier.
287
+
288
+ Examples:
289
+ >>> manager = ModifierManager(cache_type='memory')
290
+ >>> manager['cache_type']
291
+ 'memory'
292
+ """
293
+ return self._modifiers[modifier_key] # type: ignore [literal-required]
294
+
295
+
296
+ # TODO give us docstrings
297
+ cdef public ModifierManager NULLS, USER_DEFAULTS
298
+ NULLS = ModifierManager(null_modifiers)
299
+ USER_DEFAULTS = ModifierManager(user_set_default_modifiers)
@@ -0,0 +1,173 @@
1
+ # mypy: disable-error-code=valid-type
2
+ # mypy: disable-error-code=misc
3
+ import asyncio
4
+ import functools
5
+ from typing import Optional, Union, overload
6
+
7
+ from a_sync import exceptions, primitives
8
+ from a_sync._typing import P, T, AsyncDecorator, AsyncDecoratorOrCoroFn, CoroFn, SemaphoreSpec
9
+
10
+
11
+ @overload
12
+ def apply_semaphore( # type: ignore [misc]
13
+ semaphore: SemaphoreSpec,
14
+ ) -> AsyncDecorator[P, T]:
15
+ """Create a decorator to apply a semaphore to a coroutine function.
16
+
17
+ This overload is used when the semaphore is provided as a single argument,
18
+ returning a decorator that can be applied to a coroutine function.
19
+
20
+ Args:
21
+ semaphore (Union[int, asyncio.Semaphore, primitives.Semaphore]):
22
+ The semaphore to apply, which can be an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`.
23
+
24
+ Examples:
25
+ Using as a decorator with an integer semaphore:
26
+ >>> @apply_semaphore(2)
27
+ ... async def limited_concurrent_function():
28
+ ... pass
29
+
30
+ Using as a decorator with an `asyncio.Semaphore`:
31
+ >>> sem = asyncio.Semaphore(2)
32
+ >>> @apply_semaphore(sem)
33
+ ... async def another_function():
34
+ ... pass
35
+
36
+ Using as a decorator with a `primitives.Semaphore`:
37
+ >>> sem = primitives.ThreadsafeSemaphore(2)
38
+ >>> @apply_semaphore(sem)
39
+ ... async def yet_another_function():
40
+ ... pass
41
+
42
+ See Also:
43
+ - :class:`asyncio.Semaphore`
44
+ - :class:`primitives.Semaphore`
45
+
46
+ Note:
47
+ `primitives.Semaphore` implements the same API as `asyncio.Semaphore`. Therefore, when the documentation refers to `asyncio.Semaphore`, it also includes `primitives.Semaphore` and any other implementations that conform to the same interface.
48
+ """
49
+
50
+
51
+ @overload
52
+ def apply_semaphore(
53
+ coro_fn: CoroFn[P, T],
54
+ semaphore: SemaphoreSpec,
55
+ ) -> CoroFn[P, T]:
56
+ """Apply a semaphore directly to a coroutine function.
57
+
58
+ This overload is used when both the coroutine function and semaphore are provided,
59
+ directly applying the semaphore to the coroutine function.
60
+
61
+ Args:
62
+ coro_fn (Callable): The coroutine function to which the semaphore will be applied.
63
+ semaphore (Union[int, asyncio.Semaphore, primitives.Semaphore]):
64
+ The semaphore to apply, which can be an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`.
65
+
66
+ Examples:
67
+ Applying directly to a function with an integer semaphore:
68
+ >>> async def my_coroutine():
69
+ ... pass
70
+ >>> my_coroutine = apply_semaphore(my_coroutine, 3)
71
+
72
+ Applying directly with an `asyncio.Semaphore`:
73
+ >>> sem = asyncio.Semaphore(3)
74
+ >>> my_coroutine = apply_semaphore(my_coroutine, sem)
75
+
76
+ Applying directly with a `primitives.Semaphore`:
77
+ >>> sem = primitives.ThreadsafeSemaphore(3)
78
+ >>> my_coroutine = apply_semaphore(my_coroutine, sem)
79
+
80
+ See Also:
81
+ - :class:`asyncio.Semaphore`
82
+ - :class:`primitives.Semaphore`
83
+
84
+ Note:
85
+ `primitives.Semaphore` implements the same API as `asyncio.Semaphore`. Therefore, when the documentation refers to `asyncio.Semaphore`, it also includes `primitives.Semaphore` and any other implementations that conform to the same interface.
86
+ """
87
+
88
+
89
+ def apply_semaphore(
90
+ coro_fn: Optional[Union[CoroFn[P, T], SemaphoreSpec]] = None,
91
+ semaphore: SemaphoreSpec = None,
92
+ ) -> AsyncDecoratorOrCoroFn[P, T]:
93
+ """Apply a semaphore to a coroutine function or return a decorator.
94
+
95
+ This function can be used to apply a semaphore to a coroutine function either by
96
+ passing the coroutine function and semaphore as arguments or by using the semaphore
97
+ as a decorator. It raises exceptions if the inputs are not valid.
98
+
99
+ Args:
100
+ coro_fn (Optional[Callable]): The coroutine function to which the semaphore will be applied,
101
+ or None if the semaphore is to be used as a decorator.
102
+ semaphore (Union[int, asyncio.Semaphore, primitives.Semaphore]):
103
+ The semaphore to apply, which can be an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`.
104
+
105
+ Raises:
106
+ ValueError: If `coro_fn` is an integer or `asyncio.Semaphore` and `semaphore` is not None.
107
+ exceptions.FunctionNotAsync: If the provided function is not a coroutine.
108
+ TypeError: If the semaphore is not an integer, an `asyncio.Semaphore`, or a `primitives.Semaphore`.
109
+
110
+ Examples:
111
+ Using as a decorator:
112
+ >>> @apply_semaphore(2)
113
+ ... async def limited_concurrent_function():
114
+ ... pass
115
+
116
+ Applying directly to a function:
117
+ >>> async def my_coroutine():
118
+ ... pass
119
+ >>> my_coroutine = apply_semaphore(my_coroutine, 3)
120
+
121
+ Handling invalid inputs:
122
+ >>> try:
123
+ ... apply_semaphore(3, 2)
124
+ ... except ValueError as e:
125
+ ... print(e)
126
+
127
+ See Also:
128
+ - :class:`asyncio.Semaphore`
129
+ - :class:`primitives.Semaphore`
130
+
131
+ Note:
132
+ `primitives.Semaphore` implements the same API as `asyncio.Semaphore`. Therefore, when the documentation refers to `asyncio.Semaphore`, it also includes `primitives.Semaphore` and any other implementations that conform to the same interface.
133
+ """
134
+ # Parse Inputs
135
+ if isinstance(coro_fn, (int, asyncio.Semaphore, primitives.Semaphore)):
136
+ if semaphore is not None:
137
+ raise ValueError("You can only pass in one arg.")
138
+ semaphore = coro_fn
139
+ coro_fn = None
140
+
141
+ elif not asyncio.iscoroutinefunction(coro_fn):
142
+ raise exceptions.FunctionNotAsync(coro_fn)
143
+
144
+ # Create the semaphore if necessary
145
+ if isinstance(semaphore, int):
146
+ semaphore = primitives.ThreadsafeSemaphore(semaphore)
147
+ elif not isinstance(semaphore, (asyncio.Semaphore, primitives.Semaphore)):
148
+ raise TypeError(
149
+ f"'semaphore' must either be an integer or a Semaphore object. You passed {semaphore}"
150
+ )
151
+
152
+ # Create and return the decorator
153
+ if isinstance(semaphore, primitives.Semaphore):
154
+ # NOTE: Our `Semaphore` primitive can be used as a decorator.
155
+ # While you can use it the `async with` way like any other semaphore and we could make this code section cleaner,
156
+ # applying it as a decorator adds some useful info to its debug logs so we do that here if we can.
157
+ semaphore_decorator = semaphore
158
+
159
+ else:
160
+
161
+ def semaphore_decorator(coro_fn: CoroFn[P, T]) -> CoroFn[P, T]:
162
+ @functools.wraps(coro_fn)
163
+ async def semaphore_wrap(*args, **kwargs) -> T:
164
+ async with semaphore: # type: ignore [union-attr]
165
+ return await coro_fn(*args, **kwargs)
166
+
167
+ return semaphore_wrap
168
+
169
+ return semaphore_decorator if coro_fn is None else semaphore_decorator(coro_fn)
170
+
171
+
172
+ dummy_semaphore = primitives.DummySemaphore()
173
+ """A dummy semaphore that does not enforce any concurrency limits."""