ez-a-sync 0.22.14__py3-none-any.whl → 0.22.15__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 +4 -3
- a_sync/__init__.py +30 -12
- a_sync/_smart.py +132 -28
- a_sync/_typing.py +56 -12
- a_sync/a_sync/__init__.py +35 -10
- a_sync/a_sync/_descriptor.py +74 -26
- a_sync/a_sync/_flags.py +14 -6
- a_sync/a_sync/_helpers.py +8 -7
- a_sync/a_sync/_kwargs.py +3 -2
- a_sync/a_sync/_meta.py +120 -28
- a_sync/a_sync/abstract.py +102 -28
- a_sync/a_sync/base.py +34 -16
- a_sync/a_sync/config.py +47 -13
- a_sync/a_sync/decorator.py +239 -117
- a_sync/a_sync/function.py +416 -146
- a_sync/a_sync/method.py +197 -59
- a_sync/a_sync/modifiers/__init__.py +47 -5
- a_sync/a_sync/modifiers/cache/__init__.py +46 -17
- a_sync/a_sync/modifiers/cache/memory.py +86 -20
- a_sync/a_sync/modifiers/limiter.py +52 -22
- a_sync/a_sync/modifiers/manager.py +98 -16
- a_sync/a_sync/modifiers/semaphores.py +48 -15
- a_sync/a_sync/property.py +383 -82
- a_sync/a_sync/singleton.py +1 -0
- a_sync/aliases.py +0 -1
- a_sync/asyncio/__init__.py +4 -1
- a_sync/asyncio/as_completed.py +177 -49
- a_sync/asyncio/create_task.py +31 -17
- a_sync/asyncio/gather.py +72 -52
- a_sync/asyncio/utils.py +3 -3
- a_sync/exceptions.py +78 -23
- a_sync/executor.py +118 -71
- a_sync/future.py +575 -158
- a_sync/iter.py +110 -50
- a_sync/primitives/__init__.py +14 -2
- a_sync/primitives/_debug.py +13 -13
- a_sync/primitives/_loggable.py +5 -4
- a_sync/primitives/locks/__init__.py +5 -2
- a_sync/primitives/locks/counter.py +38 -36
- a_sync/primitives/locks/event.py +21 -7
- a_sync/primitives/locks/prio_semaphore.py +182 -62
- a_sync/primitives/locks/semaphore.py +78 -77
- a_sync/primitives/queue.py +560 -58
- a_sync/sphinx/__init__.py +0 -1
- a_sync/sphinx/ext.py +160 -50
- a_sync/task.py +262 -97
- a_sync/utils/__init__.py +12 -6
- a_sync/utils/iterators.py +127 -43
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/METADATA +1 -1
- ez_a_sync-0.22.15.dist-info/RECORD +74 -0
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/WHEEL +1 -1
- tests/conftest.py +1 -2
- tests/executor.py +112 -9
- tests/fixtures.py +61 -32
- tests/test_abstract.py +7 -4
- tests/test_as_completed.py +54 -21
- tests/test_base.py +66 -17
- tests/test_cache.py +31 -15
- tests/test_decorator.py +54 -28
- tests/test_executor.py +8 -13
- tests/test_future.py +45 -8
- tests/test_gather.py +8 -2
- tests/test_helpers.py +2 -0
- tests/test_iter.py +55 -13
- tests/test_limiter.py +5 -3
- tests/test_meta.py +23 -9
- tests/test_modified.py +4 -1
- tests/test_semaphore.py +15 -8
- tests/test_singleton.py +15 -10
- tests/test_task.py +126 -28
- ez_a_sync-0.22.14.dist-info/RECORD +0 -74
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/LICENSE.txt +0 -0
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.15.dist-info}/top_level.txt +0 -0
a_sync/ENVIRONMENT_VARIABLES.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
from typed_envs import EnvVarFactory
|
|
3
2
|
|
|
4
3
|
envs = EnvVarFactory("EZASYNC")
|
|
@@ -6,7 +5,9 @@ envs = EnvVarFactory("EZASYNC")
|
|
|
6
5
|
# We have some envs here to help you debug your custom class implementations
|
|
7
6
|
|
|
8
7
|
# If you're only interested in debugging a specific class, set this to the class name
|
|
9
|
-
DEBUG_CLASS_NAME = envs.create_env("DEBUG_CLASS_NAME", str, default=
|
|
8
|
+
DEBUG_CLASS_NAME = envs.create_env("DEBUG_CLASS_NAME", str, default="", verbose=False)
|
|
10
9
|
|
|
11
10
|
# Set this to enable debug mode on all classes
|
|
12
|
-
DEBUG_MODE = envs.create_env(
|
|
11
|
+
DEBUG_MODE = envs.create_env(
|
|
12
|
+
"DEBUG_MODE", bool, default=DEBUG_CLASS_NAME, verbose=False
|
|
13
|
+
)
|
a_sync/__init__.py
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module initializes the a_sync library by importing and organizing various components, utilities, and classes.
|
|
3
|
+
It provides a convenient and unified interface for asynchronous programming with a focus on flexibility and efficiency.
|
|
4
|
+
|
|
5
|
+
The `a_sync` library offers decorators and base classes to facilitate writing both synchronous and asynchronous code.
|
|
6
|
+
It includes the `@a_sync()` decorator and the `ASyncGenericBase` class, which allow for creating functions and classes
|
|
7
|
+
that can operate in both synchronous and asynchronous contexts. Additionally, it provides enhanced asyncio primitives,
|
|
8
|
+
such as queues and locks, with extra functionality.
|
|
9
|
+
|
|
10
|
+
Modules and components included:
|
|
11
|
+
- `aliases`, `exceptions`, `iter`, `task`: Core modules of the library.
|
|
12
|
+
- `ASyncGenericBase`, `ASyncGenericSingleton`, `a_sync`: Base classes and decorators for dual-context execution.
|
|
13
|
+
- `apply_semaphore`: Function to apply semaphores to coroutines.
|
|
14
|
+
- `ASyncCachedPropertyDescriptor`, `ASyncPropertyDescriptor`, `cached_property`, `property`: Property descriptors for async properties.
|
|
15
|
+
- `as_completed`, `create_task`, `gather`: Enhanced asyncio functions.
|
|
16
|
+
- Executors: `AsyncThreadPoolExecutor`, `AsyncProcessPoolExecutor`, `PruningThreadPoolExecutor` for async execution.
|
|
17
|
+
- Iterators: `ASyncFilter`, `ASyncSorter`, `ASyncIterable`, `ASyncIterator` for async iteration.
|
|
18
|
+
- Utilities: `all`, `any`, `as_yielded` for async utilities.
|
|
19
|
+
|
|
20
|
+
Alias for backward compatibility:
|
|
21
|
+
- `ASyncBase` is an alias for `ASyncGenericBase`, which will be removed eventually, probably in version 0.1.0.
|
|
22
|
+
"""
|
|
1
23
|
|
|
2
24
|
from a_sync import aliases, exceptions, iter, task
|
|
3
25
|
from a_sync.a_sync import ASyncGenericBase, ASyncGenericSingleton, a_sync
|
|
4
26
|
from a_sync.a_sync.modifiers.semaphores import apply_semaphore
|
|
5
|
-
from a_sync.a_sync.property import
|
|
27
|
+
from a_sync.a_sync.property import (
|
|
28
|
+
ASyncCachedPropertyDescriptor,
|
|
29
|
+
ASyncPropertyDescriptor,
|
|
30
|
+
cached_property,
|
|
31
|
+
property,
|
|
32
|
+
)
|
|
6
33
|
from a_sync.asyncio import as_completed, create_task, gather
|
|
7
34
|
from a_sync.executor import *
|
|
8
35
|
from a_sync.executor import AsyncThreadPoolExecutor as ThreadPoolExecutor
|
|
@@ -29,16 +56,13 @@ __all__ = [
|
|
|
29
56
|
"exceptions",
|
|
30
57
|
"iter",
|
|
31
58
|
"task",
|
|
32
|
-
|
|
33
59
|
# builtins
|
|
34
60
|
"sorted",
|
|
35
61
|
"filter",
|
|
36
|
-
|
|
37
62
|
# asyncio
|
|
38
63
|
"create_task",
|
|
39
|
-
"gather",
|
|
64
|
+
"gather",
|
|
40
65
|
"as_completed",
|
|
41
|
-
|
|
42
66
|
# functions
|
|
43
67
|
"a_sync",
|
|
44
68
|
"all",
|
|
@@ -47,33 +71,27 @@ __all__ = [
|
|
|
47
71
|
"exhaust_iterator",
|
|
48
72
|
"exhaust_iterators",
|
|
49
73
|
"map",
|
|
50
|
-
|
|
51
74
|
# classes
|
|
52
75
|
"ASyncIterable",
|
|
53
76
|
"ASyncIterator",
|
|
54
77
|
"ASyncGenericSingleton",
|
|
55
|
-
"TaskMapping",
|
|
56
|
-
|
|
78
|
+
"TaskMapping",
|
|
57
79
|
# property
|
|
58
80
|
"cached_property",
|
|
59
81
|
"property",
|
|
60
82
|
"ASyncPropertyDescriptor",
|
|
61
83
|
"ASyncCachedPropertyDescriptor",
|
|
62
|
-
|
|
63
84
|
# semaphores
|
|
64
85
|
"Semaphore",
|
|
65
86
|
"PrioritySemaphore",
|
|
66
87
|
"ThreadsafeSemaphore",
|
|
67
|
-
|
|
68
88
|
# queues
|
|
69
89
|
"Queue",
|
|
70
90
|
"ProcessingQueue",
|
|
71
91
|
"SmartProcessingQueue",
|
|
72
|
-
|
|
73
92
|
# locks
|
|
74
93
|
"CounterLock",
|
|
75
94
|
"Event",
|
|
76
|
-
|
|
77
95
|
# executors
|
|
78
96
|
"AsyncThreadPoolExecutor",
|
|
79
97
|
"PruningThreadPoolExecutor",
|
a_sync/_smart.py
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines smart future and task utilities for the a_sync library.
|
|
3
|
+
These utilities provide enhanced functionality for managing asynchronous tasks and futures,
|
|
4
|
+
including task shielding and a custom task factory for creating SmartTask instances.
|
|
5
|
+
"""
|
|
1
6
|
|
|
2
7
|
import asyncio
|
|
3
8
|
import logging
|
|
@@ -17,11 +22,22 @@ _Key = Tuple[_Args, _Kwargs]
|
|
|
17
22
|
|
|
18
23
|
logger = logging.getLogger(__name__)
|
|
19
24
|
|
|
25
|
+
|
|
20
26
|
class _SmartFutureMixin(Generic[T]):
|
|
27
|
+
"""
|
|
28
|
+
Mixin class that provides common functionality for smart futures and tasks.
|
|
29
|
+
|
|
30
|
+
This mixin provides methods for managing waiters and integrating with a smart processing queue.
|
|
31
|
+
"""
|
|
32
|
+
|
|
21
33
|
_queue: Optional["SmartProcessingQueue[Any, Any, T]"] = None
|
|
22
34
|
_key: _Key
|
|
23
35
|
_waiters: "weakref.WeakSet[SmartTask[T]]"
|
|
36
|
+
|
|
24
37
|
def __await__(self: Union["SmartFuture", "SmartTask"]) -> Generator[Any, None, T]:
|
|
38
|
+
"""
|
|
39
|
+
Await the smart future or task, handling waiters and logging.
|
|
40
|
+
"""
|
|
25
41
|
if self.done():
|
|
26
42
|
return self.result() # May raise too.
|
|
27
43
|
self._asyncio_future_blocking = True
|
|
@@ -32,33 +48,64 @@ class _SmartFutureMixin(Generic[T]):
|
|
|
32
48
|
if not self.done():
|
|
33
49
|
raise RuntimeError("await wasn't used with future")
|
|
34
50
|
return self.result() # May raise too.
|
|
51
|
+
|
|
35
52
|
@property
|
|
36
53
|
def num_waiters(self: Union["SmartFuture", "SmartTask"]) -> int:
|
|
37
54
|
# NOTE: we check .done() because the callback may not have ran yet and its very lightweight
|
|
55
|
+
"""
|
|
56
|
+
Get the number of waiters currently awaiting the future or task.
|
|
57
|
+
"""
|
|
38
58
|
if self.done():
|
|
39
59
|
# if there are any waiters left, there won't be once the event loop runs once
|
|
40
60
|
return 0
|
|
41
|
-
return sum(getattr(waiter,
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
return sum(getattr(waiter, "num_waiters", 1) or 1 for waiter in self._waiters)
|
|
62
|
+
|
|
63
|
+
def _waiter_done_cleanup_callback(
|
|
64
|
+
self: Union["SmartFuture", "SmartTask"], waiter: "SmartTask"
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Callback to clean up waiters when a waiter task is done.
|
|
68
|
+
|
|
69
|
+
Removes the waiter from _waiters, and _queue._futs if applicable
|
|
70
|
+
"""
|
|
44
71
|
if not self.done():
|
|
45
72
|
self._waiters.remove(waiter)
|
|
73
|
+
|
|
46
74
|
def _self_done_cleanup_callback(self: Union["SmartFuture", "SmartTask"]) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Callback to clean up waiters and remove the future from the queue when done.
|
|
77
|
+
"""
|
|
47
78
|
self._waiters.clear()
|
|
48
79
|
if queue := self._queue:
|
|
49
80
|
queue._futs.pop(self._key)
|
|
50
81
|
|
|
51
82
|
|
|
52
83
|
class SmartFuture(_SmartFutureMixin[T], asyncio.Future):
|
|
84
|
+
"""
|
|
85
|
+
A smart future that tracks waiters and integrates with a smart processing queue.
|
|
86
|
+
|
|
87
|
+
Inherits from both _SmartFutureMixin and asyncio.Future, providing additional functionality
|
|
88
|
+
for tracking waiters and integrating with a smart processing queue.
|
|
89
|
+
"""
|
|
90
|
+
|
|
53
91
|
_queue = None
|
|
54
92
|
_key = None
|
|
93
|
+
|
|
55
94
|
def __init__(
|
|
56
|
-
self,
|
|
57
|
-
*,
|
|
58
|
-
queue: Optional["SmartProcessingQueue[Any, Any, T]"],
|
|
59
|
-
key: Optional[_Key] = None,
|
|
95
|
+
self,
|
|
96
|
+
*,
|
|
97
|
+
queue: Optional["SmartProcessingQueue[Any, Any, T]"],
|
|
98
|
+
key: Optional[_Key] = None,
|
|
60
99
|
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
61
100
|
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Initialize the SmartFuture with an optional queue and key.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
queue: Optional; a smart processing queue.
|
|
106
|
+
key: Optional; a key identifying the future.
|
|
107
|
+
loop: Optional; the event loop.
|
|
108
|
+
"""
|
|
62
109
|
super().__init__(loop=loop)
|
|
63
110
|
if queue:
|
|
64
111
|
self._queue = weakref.proxy(queue)
|
|
@@ -66,55 +113,94 @@ class SmartFuture(_SmartFutureMixin[T], asyncio.Future):
|
|
|
66
113
|
self._key = key
|
|
67
114
|
self._waiters = weakref.WeakSet()
|
|
68
115
|
self.add_done_callback(SmartFuture._self_done_cleanup_callback)
|
|
116
|
+
|
|
69
117
|
def __repr__(self):
|
|
70
118
|
return f"<{type(self).__name__} key={self._key} waiters={self.num_waiters} {self._state}>"
|
|
119
|
+
|
|
71
120
|
def __lt__(self, other: "SmartFuture[T]") -> bool:
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
121
|
+
"""
|
|
122
|
+
Compare the number of waiters to determine priority in a heap.
|
|
123
|
+
Lower values indicate higher priority, so more waiters means 'less than'.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
other: Another SmartFuture to compare with.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if self has more waiters than other.
|
|
130
|
+
"""
|
|
77
131
|
return self.num_waiters > other.num_waiters
|
|
78
132
|
|
|
133
|
+
|
|
79
134
|
def create_future(
|
|
80
135
|
*,
|
|
81
|
-
queue: Optional["SmartProcessingQueue"] = None,
|
|
82
|
-
key: Optional[_Key] = None,
|
|
136
|
+
queue: Optional["SmartProcessingQueue"] = None,
|
|
137
|
+
key: Optional[_Key] = None,
|
|
83
138
|
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
84
139
|
) -> SmartFuture[V]:
|
|
140
|
+
"""
|
|
141
|
+
Create a SmartFuture instance.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
queue: Optional; a smart processing queue.
|
|
145
|
+
key: Optional; a key identifying the future.
|
|
146
|
+
loop: Optional; the event loop.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
A SmartFuture instance.
|
|
150
|
+
"""
|
|
85
151
|
return SmartFuture(queue=queue, key=key, loop=loop or asyncio.get_event_loop())
|
|
86
152
|
|
|
153
|
+
|
|
87
154
|
class SmartTask(_SmartFutureMixin[T], asyncio.Task):
|
|
155
|
+
"""
|
|
156
|
+
A smart task that tracks waiters and integrates with a smart processing queue.
|
|
157
|
+
|
|
158
|
+
Inherits from both _SmartFutureMixin and asyncio.Task, providing additional functionality
|
|
159
|
+
for tracking waiters and integrating with a smart processing queue.
|
|
160
|
+
"""
|
|
161
|
+
|
|
88
162
|
def __init__(
|
|
89
|
-
self,
|
|
90
|
-
coro: Awaitable[T],
|
|
91
|
-
*,
|
|
92
|
-
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
163
|
+
self,
|
|
164
|
+
coro: Awaitable[T],
|
|
165
|
+
*,
|
|
166
|
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
93
167
|
name: Optional[str] = None,
|
|
94
168
|
) -> None:
|
|
169
|
+
"""
|
|
170
|
+
Initialize the SmartTask with a coroutine and optional event loop.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
coro: The coroutine to run in the task.
|
|
174
|
+
loop: Optional; the event loop.
|
|
175
|
+
name: Optional; the name of the task.
|
|
176
|
+
"""
|
|
95
177
|
super().__init__(coro, loop=loop, name=name)
|
|
96
178
|
self._waiters: Set["asyncio.Task[T]"] = set()
|
|
97
179
|
self.add_done_callback(SmartTask._self_done_cleanup_callback)
|
|
98
180
|
|
|
99
|
-
|
|
181
|
+
|
|
182
|
+
def smart_task_factory(
|
|
183
|
+
loop: asyncio.AbstractEventLoop, coro: Awaitable[T]
|
|
184
|
+
) -> SmartTask[T]:
|
|
100
185
|
"""
|
|
101
186
|
Task factory function that an event loop calls to create new tasks.
|
|
102
|
-
|
|
187
|
+
|
|
103
188
|
This factory function utilizes ez-a-sync's custom :class:`~SmartTask` implementation.
|
|
104
|
-
|
|
189
|
+
|
|
105
190
|
Args:
|
|
106
191
|
loop: The event loop.
|
|
107
192
|
coro: The coroutine to run in the task.
|
|
108
|
-
|
|
193
|
+
|
|
109
194
|
Returns:
|
|
110
195
|
A SmartTask instance running the provided coroutine.
|
|
111
196
|
"""
|
|
112
197
|
return SmartTask(coro, loop=loop)
|
|
113
198
|
|
|
199
|
+
|
|
114
200
|
def set_smart_task_factory(loop: asyncio.AbstractEventLoop = None) -> None:
|
|
115
201
|
"""
|
|
116
202
|
Set the event loop's task factory to :func:`~smart_task_factory` so all tasks will be SmartTask instances.
|
|
117
|
-
|
|
203
|
+
|
|
118
204
|
Args:
|
|
119
205
|
loop: Optional; the event loop. If None, the current event loop is used.
|
|
120
206
|
"""
|
|
@@ -122,7 +208,10 @@ def set_smart_task_factory(loop: asyncio.AbstractEventLoop = None) -> None:
|
|
|
122
208
|
loop = a_sync.asyncio.get_event_loop()
|
|
123
209
|
loop.set_task_factory(smart_task_factory)
|
|
124
210
|
|
|
125
|
-
|
|
211
|
+
|
|
212
|
+
def shield(
|
|
213
|
+
arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = None
|
|
214
|
+
) -> SmartFuture[T]:
|
|
126
215
|
"""
|
|
127
216
|
Wait for a future, shielding it from cancellation.
|
|
128
217
|
|
|
@@ -148,11 +237,18 @@ def shield(arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = Non
|
|
|
148
237
|
res = await shield(something())
|
|
149
238
|
except CancelledError:
|
|
150
239
|
res = None
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
arg: The awaitable to shield from cancellation.
|
|
243
|
+
loop: Optional; the event loop. Deprecated since Python 3.8.
|
|
151
244
|
"""
|
|
152
245
|
if loop is not None:
|
|
153
|
-
warnings.warn(
|
|
154
|
-
|
|
155
|
-
|
|
246
|
+
warnings.warn(
|
|
247
|
+
"The loop argument is deprecated since Python 3.8, "
|
|
248
|
+
"and scheduled for removal in Python 3.10.",
|
|
249
|
+
DeprecationWarning,
|
|
250
|
+
stacklevel=2,
|
|
251
|
+
)
|
|
156
252
|
inner = asyncio.ensure_future(arg, loop=loop)
|
|
157
253
|
if inner.done():
|
|
158
254
|
# Shortcut.
|
|
@@ -162,6 +258,7 @@ def shield(arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = Non
|
|
|
162
258
|
# special handling to connect SmartFutures to SmartTasks if enabled
|
|
163
259
|
if (waiters := getattr(inner, "_waiters", None)) is not None:
|
|
164
260
|
waiters.add(outer)
|
|
261
|
+
|
|
165
262
|
def _inner_done_callback(inner):
|
|
166
263
|
if outer.cancelled():
|
|
167
264
|
if not inner.cancelled():
|
|
@@ -187,4 +284,11 @@ def shield(arg: Awaitable[T], *, loop: Optional[asyncio.AbstractEventLoop] = Non
|
|
|
187
284
|
return outer
|
|
188
285
|
|
|
189
286
|
|
|
190
|
-
__all__ = [
|
|
287
|
+
__all__ = [
|
|
288
|
+
"create_future",
|
|
289
|
+
"shield",
|
|
290
|
+
"SmartFuture",
|
|
291
|
+
"SmartTask",
|
|
292
|
+
"smart_task_factory",
|
|
293
|
+
"set_smart_task_factory",
|
|
294
|
+
]
|
a_sync/_typing.py
CHANGED
|
@@ -7,16 +7,47 @@ to enhance type checking and provide better IDE support.
|
|
|
7
7
|
import asyncio
|
|
8
8
|
from concurrent.futures._base import Executor
|
|
9
9
|
from decimal import Decimal
|
|
10
|
-
from typing import (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
from typing import (
|
|
11
|
+
TYPE_CHECKING,
|
|
12
|
+
Any,
|
|
13
|
+
AsyncGenerator,
|
|
14
|
+
AsyncIterable,
|
|
15
|
+
AsyncIterator,
|
|
16
|
+
Awaitable,
|
|
17
|
+
Callable,
|
|
18
|
+
Coroutine,
|
|
19
|
+
DefaultDict,
|
|
20
|
+
Deque,
|
|
21
|
+
Dict,
|
|
22
|
+
Generator,
|
|
23
|
+
Generic,
|
|
24
|
+
ItemsView,
|
|
25
|
+
Iterable,
|
|
26
|
+
Iterator,
|
|
27
|
+
KeysView,
|
|
28
|
+
List,
|
|
29
|
+
Literal,
|
|
30
|
+
Mapping,
|
|
31
|
+
NoReturn,
|
|
32
|
+
Optional,
|
|
33
|
+
Protocol,
|
|
34
|
+
Set,
|
|
35
|
+
Tuple,
|
|
36
|
+
Type,
|
|
37
|
+
TypedDict,
|
|
38
|
+
TypeVar,
|
|
39
|
+
Union,
|
|
40
|
+
ValuesView,
|
|
41
|
+
final,
|
|
42
|
+
overload,
|
|
43
|
+
runtime_checkable,
|
|
44
|
+
)
|
|
15
45
|
|
|
16
46
|
from typing_extensions import Concatenate, ParamSpec, Self, Unpack
|
|
17
47
|
|
|
18
48
|
if TYPE_CHECKING:
|
|
19
49
|
from a_sync import ASyncGenericBase
|
|
50
|
+
|
|
20
51
|
B = TypeVar("B", bound=ASyncGenericBase)
|
|
21
52
|
else:
|
|
22
53
|
B = TypeVar("B")
|
|
@@ -27,7 +58,7 @@ V = TypeVar("V")
|
|
|
27
58
|
I = TypeVar("I")
|
|
28
59
|
"""A :class:`TypeVar` that is used to represent instances of a common class."""
|
|
29
60
|
|
|
30
|
-
E = TypeVar(
|
|
61
|
+
E = TypeVar("E", bound=Exception)
|
|
31
62
|
TYPE = TypeVar("TYPE", bound=Type)
|
|
32
63
|
|
|
33
64
|
P = ParamSpec("P")
|
|
@@ -51,6 +82,7 @@ SyncFn = Callable[P, T]
|
|
|
51
82
|
AnyFn = Union[CoroFn[P, T], SyncFn[P, T]]
|
|
52
83
|
"Type alias for any function, whether synchronous or asynchronous."
|
|
53
84
|
|
|
85
|
+
|
|
54
86
|
class CoroBoundMethod(Protocol[I, P, T]):
|
|
55
87
|
"""
|
|
56
88
|
Protocol for coroutine bound methods.
|
|
@@ -59,13 +91,15 @@ class CoroBoundMethod(Protocol[I, P, T]):
|
|
|
59
91
|
class MyClass:
|
|
60
92
|
async def my_method(self, x: int) -> str:
|
|
61
93
|
return str(x)
|
|
62
|
-
|
|
94
|
+
|
|
63
95
|
instance = MyClass()
|
|
64
96
|
bound_method: CoroBoundMethod[MyClass, [int], str] = instance.my_method
|
|
65
97
|
"""
|
|
98
|
+
|
|
66
99
|
__self__: I
|
|
67
100
|
__call__: Callable[P, Awaitable[T]]
|
|
68
101
|
|
|
102
|
+
|
|
69
103
|
class SyncBoundMethod(Protocol[I, P, T]):
|
|
70
104
|
"""
|
|
71
105
|
Protocol for synchronous bound methods.
|
|
@@ -74,36 +108,43 @@ class SyncBoundMethod(Protocol[I, P, T]):
|
|
|
74
108
|
class MyClass:
|
|
75
109
|
def my_method(self, x: int) -> str:
|
|
76
110
|
return str(x)
|
|
77
|
-
|
|
111
|
+
|
|
78
112
|
instance = MyClass()
|
|
79
113
|
bound_method: SyncBoundMethod[MyClass, [int], str] = instance.my_method
|
|
80
114
|
"""
|
|
115
|
+
|
|
81
116
|
__self__: I
|
|
82
117
|
__call__: Callable[P, T]
|
|
83
118
|
|
|
119
|
+
|
|
84
120
|
AnyBoundMethod = Union[CoroBoundMethod[Any, P, T], SyncBoundMethod[Any, P, T]]
|
|
85
121
|
"Type alias for any bound method, whether synchronous or asynchronous."
|
|
86
122
|
|
|
123
|
+
|
|
87
124
|
@runtime_checkable
|
|
88
125
|
class AsyncUnboundMethod(Protocol[I, P, T]):
|
|
89
126
|
"""
|
|
90
127
|
Protocol for unbound asynchronous methods.
|
|
91
|
-
|
|
128
|
+
|
|
92
129
|
An unbound method is a method that hasn't been bound to an instance of a class yet.
|
|
93
130
|
It's essentially the function object itself, before it's accessed through an instance.
|
|
94
131
|
"""
|
|
132
|
+
|
|
95
133
|
__get__: Callable[[I, Type], CoroBoundMethod[I, P, T]]
|
|
96
134
|
|
|
135
|
+
|
|
97
136
|
@runtime_checkable
|
|
98
137
|
class SyncUnboundMethod(Protocol[I, P, T]):
|
|
99
138
|
"""
|
|
100
139
|
Protocol for unbound synchronous methods.
|
|
101
|
-
|
|
140
|
+
|
|
102
141
|
An unbound method is a method that hasn't been bound to an instance of a class yet.
|
|
103
142
|
It's essentially the function object itself, before it's accessed through an instance.
|
|
104
143
|
"""
|
|
144
|
+
|
|
105
145
|
__get__: Callable[[I, Type], SyncBoundMethod[I, P, T]]
|
|
106
146
|
|
|
147
|
+
|
|
107
148
|
AnyUnboundMethod = Union[AsyncUnboundMethod[I, P, T], SyncUnboundMethod[I, P, T]]
|
|
108
149
|
"Type alias for any unbound method, whether synchronous or asynchronous."
|
|
109
150
|
|
|
@@ -122,19 +163,21 @@ AsyncDecorator = Callable[[CoroFn[P, T]], CoroFn[P, T]]
|
|
|
122
163
|
AsyncDecoratorOrCoroFn = Union[AsyncDecorator[P, T], CoroFn[P, T]]
|
|
123
164
|
"Type alias for either an asynchronous decorator or a coroutine function."
|
|
124
165
|
|
|
125
|
-
DefaultMode = Literal[
|
|
166
|
+
DefaultMode = Literal["sync", "async", None]
|
|
126
167
|
"Type alias for default modes of operation."
|
|
127
168
|
|
|
128
|
-
CacheType = Literal[
|
|
169
|
+
CacheType = Literal["memory", None]
|
|
129
170
|
"Type alias for cache types."
|
|
130
171
|
|
|
131
172
|
SemaphoreSpec = Optional[Union[asyncio.Semaphore, int]]
|
|
132
173
|
"Type alias for semaphore specifications."
|
|
133
174
|
|
|
175
|
+
|
|
134
176
|
class ModifierKwargs(TypedDict, total=False):
|
|
135
177
|
"""
|
|
136
178
|
TypedDict for keyword arguments that modify the behavior of asynchronous operations.
|
|
137
179
|
"""
|
|
180
|
+
|
|
138
181
|
default: DefaultMode
|
|
139
182
|
cache_type: CacheType
|
|
140
183
|
cache_typed: bool
|
|
@@ -145,6 +188,7 @@ class ModifierKwargs(TypedDict, total=False):
|
|
|
145
188
|
# sync modifiers
|
|
146
189
|
executor: Executor
|
|
147
190
|
|
|
191
|
+
|
|
148
192
|
AnyIterable = Union[AsyncIterable[K], Iterable[K]]
|
|
149
193
|
"Type alias for any iterable, whether synchronous or asynchronous."
|
|
150
194
|
|
a_sync/a_sync/__init__.py
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module enables developers to write both synchronous and asynchronous code without having to write redundant code.
|
|
3
|
+
|
|
4
|
+
The two main objects you should use are
|
|
5
|
+
- a decorator `@a_sync()`
|
|
6
|
+
- a base class `ASyncGenericBase` which can be used to create classes that can be utilized in both synchronous and asynchronous contexts.
|
|
7
|
+
|
|
8
|
+
The rest of the objects are exposed for type checking only, you should not make use of them otherwise.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# TODO: double check on these before adding them to docs
|
|
12
|
+
# - two decorators @:class:`property` and @:class:`cached_property` for the creation of dual-function properties and cached properties, respectively.
|
|
1
13
|
|
|
2
14
|
from a_sync.a_sync.base import ASyncGenericBase
|
|
3
15
|
from a_sync.a_sync.decorator import a_sync
|
|
4
|
-
from a_sync.a_sync.function import
|
|
16
|
+
from a_sync.a_sync.function import (
|
|
17
|
+
ASyncFunction,
|
|
18
|
+
ASyncFunctionAsyncDefault,
|
|
19
|
+
ASyncFunctionSyncDefault,
|
|
20
|
+
)
|
|
5
21
|
from a_sync.a_sync.modifiers.semaphores import apply_semaphore
|
|
22
|
+
|
|
6
23
|
# NOTE: Some of these we purposely import without including in __all__. Do not remove.
|
|
7
|
-
from a_sync.a_sync.property import (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
24
|
+
from a_sync.a_sync.property import (
|
|
25
|
+
ASyncCachedPropertyDescriptor,
|
|
26
|
+
ASyncCachedPropertyDescriptorAsyncDefault,
|
|
27
|
+
ASyncCachedPropertyDescriptorSyncDefault,
|
|
28
|
+
ASyncPropertyDescriptor,
|
|
29
|
+
ASyncPropertyDescriptorAsyncDefault,
|
|
30
|
+
ASyncPropertyDescriptorSyncDefault,
|
|
31
|
+
HiddenMethod,
|
|
32
|
+
HiddenMethodDescriptor,
|
|
33
|
+
cached_property,
|
|
34
|
+
property,
|
|
35
|
+
)
|
|
11
36
|
from a_sync.a_sync.singleton import ASyncGenericSingleton
|
|
12
37
|
|
|
13
38
|
|
|
@@ -15,14 +40,14 @@ __all__ = [
|
|
|
15
40
|
# entrypoints
|
|
16
41
|
"a_sync",
|
|
17
42
|
"ASyncGenericBase",
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
"ASyncFunction",
|
|
21
|
-
|
|
43
|
+
# maybe entrypoints (?)
|
|
44
|
+
# TODO: double check how I intended for these to be used
|
|
22
45
|
"property",
|
|
23
46
|
"cached_property",
|
|
47
|
+
# classes exposed for type hinting only
|
|
48
|
+
"ASyncFunction",
|
|
24
49
|
"ASyncPropertyDescriptor",
|
|
25
50
|
"ASyncCachedPropertyDescriptor",
|
|
26
51
|
"HiddenMethod",
|
|
27
52
|
"HiddenMethodDescriptor",
|
|
28
|
-
]
|
|
53
|
+
]
|