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.

Files changed (49) hide show
  1. a_sync/ENVIRONMENT_VARIABLES.py +34 -3
  2. a_sync/__init__.py +32 -9
  3. a_sync/_smart.py +105 -6
  4. a_sync/_typing.py +56 -3
  5. a_sync/a_sync/_descriptor.py +174 -12
  6. a_sync/a_sync/_flags.py +64 -3
  7. a_sync/a_sync/_helpers.py +40 -8
  8. a_sync/a_sync/_kwargs.py +30 -6
  9. a_sync/a_sync/_meta.py +35 -6
  10. a_sync/a_sync/abstract.py +57 -9
  11. a_sync/a_sync/config.py +44 -7
  12. a_sync/a_sync/decorator.py +217 -37
  13. a_sync/a_sync/function.py +339 -47
  14. a_sync/a_sync/method.py +241 -52
  15. a_sync/a_sync/modifiers/__init__.py +39 -1
  16. a_sync/a_sync/modifiers/cache/__init__.py +75 -5
  17. a_sync/a_sync/modifiers/cache/memory.py +50 -6
  18. a_sync/a_sync/modifiers/limiter.py +55 -6
  19. a_sync/a_sync/modifiers/manager.py +46 -2
  20. a_sync/a_sync/modifiers/semaphores.py +84 -11
  21. a_sync/a_sync/singleton.py +43 -19
  22. a_sync/asyncio/__init__.py +137 -1
  23. a_sync/asyncio/as_completed.py +44 -38
  24. a_sync/asyncio/create_task.py +46 -10
  25. a_sync/asyncio/gather.py +72 -25
  26. a_sync/exceptions.py +178 -11
  27. a_sync/executor.py +51 -3
  28. a_sync/future.py +671 -29
  29. a_sync/iter.py +64 -7
  30. a_sync/primitives/_debug.py +59 -5
  31. a_sync/primitives/_loggable.py +36 -6
  32. a_sync/primitives/locks/counter.py +74 -7
  33. a_sync/primitives/locks/prio_semaphore.py +87 -8
  34. a_sync/primitives/locks/semaphore.py +68 -20
  35. a_sync/primitives/queue.py +65 -26
  36. a_sync/task.py +51 -15
  37. a_sync/utils/iterators.py +52 -16
  38. {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/METADATA +1 -1
  39. ez_a_sync-0.22.16.dist-info/RECORD +74 -0
  40. {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/WHEEL +1 -1
  41. tests/executor.py +150 -12
  42. tests/test_abstract.py +15 -0
  43. tests/test_base.py +198 -2
  44. tests/test_executor.py +23 -0
  45. tests/test_singleton.py +13 -1
  46. tests/test_task.py +45 -17
  47. ez_a_sync-0.22.15.dist-info/RECORD +0 -74
  48. {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/LICENSE.txt +0 -0
  49. {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
- """Overload for when no coroutine function is provided."""
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
- """Overload for when an integer is provided as the coroutine function."""
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
- """Overload for when a coroutine function is provided."""
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
- Returns:
64
- A decorator or the decorated coroutine function.
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
- """Creates a decorator to apply an asynchronous LRU cache.
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
- """Applies an asynchronous LRU cache to a provided coroutine function.
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
- """Creates a decorator to apply an asynchronous LRU cache.
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
- """Applies an asynchronous LRU cache to a coroutine function.
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
- Returns:
106
- A decorator if `coro_fn` is None, otherwise the cached coroutine function.
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 'runs_per_minute' is neither an integer nor an AsyncLimiter when 'coro_fn' is None.
55
- exceptions.FunctionNotAsync: If 'coro_fn' is not an asynchronous function.
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
- """This is not applied like a typical modifier. The executor is used to run the sync function in an asynchronous context."""
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
- """Applies a semaphore to a coroutine function.
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: The semaphore to apply, which can be an integer or an asyncio.Semaphore object.
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
- """Applies a semaphore to a coroutine function.
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: The semaphore to apply, which can be an integer or an asyncio.Semaphore object.
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
- """Applies a semaphore to a coroutine function or returns a decorator.
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, or None
55
- if the semaphore is to be used as a decorator.
56
- semaphore: The semaphore to apply, which can be an integer or an asyncio.Semaphore object.
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 as invalid inputs.
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 or an asyncio.Semaphore object.
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)):
@@ -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
- class MyAsyncSingleton(ASyncGenericSingleton):
22
- @a_sync
23
- def my_method(self):
24
- # Method implementation
25
-
26
- # These will return the same synchronous instance
27
- sync_instance1 = MyAsyncSingleton(sync=True)
28
- sync_instance2 = MyAsyncSingleton(sync=True)
29
-
30
- # These will return the same asynchronous instance
31
- async_instance1 = MyAsyncSingleton(asynchronous=True)
32
- async_instance2 = MyAsyncSingleton(asynchronous=True)
33
-
34
- assert sync_instance1 is sync_instance2
35
- assert async_instance1 is async_instance2
36
- assert sync_instance1 is not async_instance1
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
  """