ez-a-sync 0.22.14__py3-none-any.whl → 0.22.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ez-a-sync might be problematic. Click here for more details.
- a_sync/ENVIRONMENT_VARIABLES.py +37 -5
- a_sync/__init__.py +53 -12
- a_sync/_smart.py +231 -28
- a_sync/_typing.py +112 -15
- a_sync/a_sync/__init__.py +35 -10
- a_sync/a_sync/_descriptor.py +248 -38
- a_sync/a_sync/_flags.py +78 -9
- a_sync/a_sync/_helpers.py +46 -13
- a_sync/a_sync/_kwargs.py +33 -8
- a_sync/a_sync/_meta.py +149 -28
- a_sync/a_sync/abstract.py +150 -28
- a_sync/a_sync/base.py +34 -16
- a_sync/a_sync/config.py +85 -14
- a_sync/a_sync/decorator.py +441 -139
- a_sync/a_sync/function.py +709 -147
- a_sync/a_sync/method.py +437 -110
- a_sync/a_sync/modifiers/__init__.py +85 -5
- a_sync/a_sync/modifiers/cache/__init__.py +116 -17
- a_sync/a_sync/modifiers/cache/memory.py +130 -20
- a_sync/a_sync/modifiers/limiter.py +101 -22
- a_sync/a_sync/modifiers/manager.py +142 -16
- a_sync/a_sync/modifiers/semaphores.py +121 -15
- a_sync/a_sync/property.py +383 -82
- a_sync/a_sync/singleton.py +44 -19
- a_sync/aliases.py +0 -1
- a_sync/asyncio/__init__.py +140 -1
- a_sync/asyncio/as_completed.py +213 -79
- a_sync/asyncio/create_task.py +70 -20
- a_sync/asyncio/gather.py +125 -58
- a_sync/asyncio/utils.py +3 -3
- a_sync/exceptions.py +248 -26
- a_sync/executor.py +164 -69
- a_sync/future.py +1227 -168
- a_sync/iter.py +173 -56
- a_sync/primitives/__init__.py +14 -2
- a_sync/primitives/_debug.py +72 -18
- a_sync/primitives/_loggable.py +41 -10
- a_sync/primitives/locks/__init__.py +5 -2
- a_sync/primitives/locks/counter.py +107 -38
- a_sync/primitives/locks/event.py +21 -7
- a_sync/primitives/locks/prio_semaphore.py +262 -63
- a_sync/primitives/locks/semaphore.py +138 -89
- a_sync/primitives/queue.py +601 -60
- a_sync/sphinx/__init__.py +0 -1
- a_sync/sphinx/ext.py +160 -50
- a_sync/task.py +313 -112
- a_sync/utils/__init__.py +12 -6
- a_sync/utils/iterators.py +170 -50
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/METADATA +1 -1
- ez_a_sync-0.22.16.dist-info/RECORD +74 -0
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/WHEEL +1 -1
- tests/conftest.py +1 -2
- tests/executor.py +250 -9
- tests/fixtures.py +61 -32
- tests/test_abstract.py +22 -4
- tests/test_as_completed.py +54 -21
- tests/test_base.py +264 -19
- tests/test_cache.py +31 -15
- tests/test_decorator.py +54 -28
- tests/test_executor.py +31 -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 +28 -11
- tests/test_task.py +162 -36
- ez_a_sync-0.22.14.dist-info/RECORD +0 -74
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/LICENSE.txt +0 -0
- {ez_a_sync-0.22.14.dist-info → ez_a_sync-0.22.16.dist-info}/top_level.txt +0 -0
tests/executor.py
CHANGED
|
@@ -1,11 +1,252 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
time.sleep(5)
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
import pytest
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
from a_sync import ProcessPoolExecutor, ThreadPoolExecutor, PruningThreadPoolExecutor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.asyncio
|
|
10
|
+
async def test_process_pool_executor_run():
|
|
11
|
+
"""Tests the :class:`ProcessPoolExecutor` by running and submitting the work function asynchronously.
|
|
12
|
+
|
|
13
|
+
This test ensures that the `run` method of the `ProcessPoolExecutor` returns a coroutine
|
|
14
|
+
when executed with a synchronous function.
|
|
15
|
+
|
|
16
|
+
See Also:
|
|
17
|
+
- :meth:`ProcessPoolExecutor.run`
|
|
18
|
+
- :func:`asyncio.iscoroutine`
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
executor = ProcessPoolExecutor(1)
|
|
22
|
+
coro = executor.run(time.sleep, 0.1)
|
|
23
|
+
assert asyncio.iscoroutine(coro)
|
|
24
|
+
await coro
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.asyncio
|
|
28
|
+
async def test_thread_pool_executor_run():
|
|
29
|
+
"""Tests the :class:`ThreadPoolExecutor` by running and submitting the work function asynchronously.
|
|
30
|
+
|
|
31
|
+
This test ensures that the `run` method of the `ThreadPoolExecutor` returns a coroutine
|
|
32
|
+
when executed with a synchronous function.
|
|
33
|
+
|
|
34
|
+
See Also:
|
|
35
|
+
- :meth:`ThreadPoolExecutor.run`
|
|
36
|
+
- :func:`asyncio.iscoroutine`
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
executor = ThreadPoolExecutor(1)
|
|
40
|
+
coro = executor.run(time.sleep, 0.1)
|
|
41
|
+
assert asyncio.iscoroutine(coro)
|
|
42
|
+
await coro
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.asyncio
|
|
46
|
+
async def test_pruning_thread_pool_executor_run():
|
|
47
|
+
"""Tests the :class:`PruningThreadPoolExecutor` by running and submitting the work function asynchronously.
|
|
48
|
+
|
|
49
|
+
This test ensures that the `run` method of the `PruningThreadPoolExecutor` returns a coroutine
|
|
50
|
+
when executed with a synchronous function.
|
|
51
|
+
|
|
52
|
+
See Also:
|
|
53
|
+
- :meth:`PruningThreadPoolExecutor.run`
|
|
54
|
+
- :func:`asyncio.iscoroutine`
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
executor = PruningThreadPoolExecutor(1)
|
|
58
|
+
coro = executor.run(time.sleep, 0.1)
|
|
59
|
+
assert asyncio.iscoroutine(coro)
|
|
60
|
+
await coro
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_process_pool_executor_submit():
|
|
65
|
+
"""Tests the :class:`ProcessPoolExecutor` by submitting the work function.
|
|
66
|
+
|
|
67
|
+
This test ensures that the `submit` method of the `ProcessPoolExecutor` returns an
|
|
68
|
+
:class:`asyncio.Future` when executed with a synchronous function.
|
|
69
|
+
|
|
70
|
+
Note:
|
|
71
|
+
The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`.
|
|
72
|
+
This is specific to the implementation of the executors in the `a_sync` library, which adapts the
|
|
73
|
+
behavior to integrate with the asyncio event loop.
|
|
74
|
+
|
|
75
|
+
See Also:
|
|
76
|
+
- :meth:`ProcessPoolExecutor.submit`
|
|
77
|
+
- :class:`asyncio.Future`
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
executor = ProcessPoolExecutor(1)
|
|
81
|
+
fut = executor.submit(time.sleep, 0.1)
|
|
82
|
+
assert isinstance(fut, asyncio.Future)
|
|
83
|
+
await fut
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.asyncio
|
|
87
|
+
async def test_thread_pool_executor_submit():
|
|
88
|
+
"""Tests the :class:`ThreadPoolExecutor` by submitting the work function.
|
|
89
|
+
|
|
90
|
+
This test ensures that the `submit` method of the `ThreadPoolExecutor` returns an
|
|
91
|
+
:class:`asyncio.Future` when executed with a synchronous function.
|
|
92
|
+
|
|
93
|
+
Note:
|
|
94
|
+
The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`.
|
|
95
|
+
This is specific to the implementation of the executors in the `a_sync` library, which adapts the
|
|
96
|
+
behavior to integrate with the asyncio event loop.
|
|
97
|
+
|
|
98
|
+
See Also:
|
|
99
|
+
- :meth:`ThreadPoolExecutor.submit`
|
|
100
|
+
- :class:`asyncio.Future`
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
executor = ThreadPoolExecutor(1)
|
|
104
|
+
fut = executor.submit(time.sleep, 0.1)
|
|
105
|
+
assert isinstance(fut, asyncio.Future)
|
|
106
|
+
await fut
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_pruning_thread_pool_executor_submit():
|
|
111
|
+
"""Tests the :class:`PruningThreadPoolExecutor` by submitting the work function.
|
|
112
|
+
|
|
113
|
+
This test ensures that the `submit` method of the `PruningThreadPoolExecutor` returns an
|
|
114
|
+
:class:`asyncio.Future` when executed with a synchronous function.
|
|
115
|
+
|
|
116
|
+
Note:
|
|
117
|
+
The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`.
|
|
118
|
+
This is specific to the implementation of the executors in the `a_sync` library, which adapts the
|
|
119
|
+
behavior to integrate with the asyncio event loop.
|
|
120
|
+
|
|
121
|
+
See Also:
|
|
122
|
+
- :meth:`PruningThreadPoolExecutor.submit`
|
|
123
|
+
- :class:`asyncio.Future`
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
executor = PruningThreadPoolExecutor(1)
|
|
127
|
+
fut = executor.submit(time.sleep, 0.1)
|
|
128
|
+
assert isinstance(fut, asyncio.Future)
|
|
129
|
+
await fut
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.asyncio
|
|
133
|
+
async def test_process_pool_executor_sync_run():
|
|
134
|
+
"""Tests the :class:`ProcessPoolExecutor` by running and submitting the work function synchronously.
|
|
135
|
+
|
|
136
|
+
This test ensures that the `run` method of the `ProcessPoolExecutor` returns a coroutine
|
|
137
|
+
when executed with a synchronous function.
|
|
138
|
+
|
|
139
|
+
See Also:
|
|
140
|
+
- :meth:`ProcessPoolExecutor.run`
|
|
141
|
+
- :func:`asyncio.iscoroutine`
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
executor = ProcessPoolExecutor(0)
|
|
145
|
+
coro = executor.run(time.sleep, 0.1)
|
|
146
|
+
assert asyncio.iscoroutine(coro)
|
|
147
|
+
await coro
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@pytest.mark.asyncio
|
|
151
|
+
async def test_thread_pool_executor_sync_run():
|
|
152
|
+
"""Tests the :class:`ThreadPoolExecutor` by running and submitting the work function synchronously.
|
|
153
|
+
|
|
154
|
+
This test ensures that the `run` method of the `ThreadPoolExecutor` returns a coroutine
|
|
155
|
+
when executed with a synchronous function.
|
|
156
|
+
|
|
157
|
+
See Also:
|
|
158
|
+
- :meth:`ThreadPoolExecutor.run`
|
|
159
|
+
- :func:`asyncio.iscoroutine`
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
executor = ThreadPoolExecutor(0)
|
|
163
|
+
coro = executor.run(time.sleep, 0.1)
|
|
164
|
+
assert asyncio.iscoroutine(coro)
|
|
165
|
+
await coro
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.mark.asyncio
|
|
169
|
+
async def test_pruning_thread_pool_executor_sync_run():
|
|
170
|
+
"""Tests the :class:`PruningThreadPoolExecutor` by running and submitting the work function synchronously.
|
|
171
|
+
|
|
172
|
+
This test ensures that the `run` method of the `PruningThreadPoolExecutor` returns a coroutine
|
|
173
|
+
when executed with a synchronous function.
|
|
174
|
+
|
|
175
|
+
See Also:
|
|
176
|
+
- :meth:`PruningThreadPoolExecutor.run`
|
|
177
|
+
- :func:`asyncio.iscoroutine`
|
|
178
|
+
|
|
179
|
+
"""
|
|
180
|
+
executor = PruningThreadPoolExecutor(0)
|
|
181
|
+
coro = executor.run(time.sleep, 0.1)
|
|
182
|
+
assert asyncio.iscoroutine(coro)
|
|
183
|
+
await coro
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pytest.mark.asyncio
|
|
187
|
+
async def test_process_pool_executor_sync_submit():
|
|
188
|
+
"""Tests the :class:`ProcessPoolExecutor` by submitting the work function synchronously.
|
|
189
|
+
|
|
190
|
+
This test ensures that the `submit` method of the `ProcessPoolExecutor` returns an
|
|
191
|
+
:class:`asyncio.Future` when executed with a synchronous function.
|
|
192
|
+
|
|
193
|
+
Note:
|
|
194
|
+
The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`.
|
|
195
|
+
This is specific to the implementation of the executors in the `a_sync` library, which adapts the
|
|
196
|
+
behavior to integrate with the asyncio event loop.
|
|
197
|
+
|
|
198
|
+
See Also:
|
|
199
|
+
- :meth:`ProcessPoolExecutor.submit`
|
|
200
|
+
- :class:`asyncio.Future`
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
executor = ProcessPoolExecutor(0)
|
|
204
|
+
fut = executor.submit(time.sleep, 0.1)
|
|
205
|
+
assert isinstance(fut, asyncio.Future)
|
|
206
|
+
await fut
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@pytest.mark.asyncio
|
|
210
|
+
async def test_thread_pool_executor_sync_submit():
|
|
211
|
+
"""Tests the :class:`ThreadPoolExecutor` by submitting the work function synchronously.
|
|
212
|
+
|
|
213
|
+
This test ensures that the `submit` method of the `ThreadPoolExecutor` returns an
|
|
214
|
+
:class:`asyncio.Future` when executed with a synchronous function.
|
|
215
|
+
|
|
216
|
+
Note:
|
|
217
|
+
The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`.
|
|
218
|
+
This is specific to the implementation of the executors in the `a_sync` library, which adapts the
|
|
219
|
+
behavior to integrate with the asyncio event loop.
|
|
220
|
+
|
|
221
|
+
See Also:
|
|
222
|
+
- :meth:`ThreadPoolExecutor.submit`
|
|
223
|
+
- :class:`asyncio.Future`
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
executor = ThreadPoolExecutor(0)
|
|
227
|
+
fut = executor.submit(time.sleep, 0.1)
|
|
228
|
+
assert isinstance(fut, asyncio.Future)
|
|
229
|
+
await fut
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@pytest.mark.asyncio
|
|
233
|
+
async def test_pruning_thread_pool_executor_sync_submit():
|
|
234
|
+
"""Tests the :class:`PruningThreadPoolExecutor` by submitting the work function synchronously.
|
|
235
|
+
|
|
236
|
+
This test ensures that the `submit` method of the `PruningThreadPoolExecutor` returns an
|
|
237
|
+
:class:`asyncio.Future` when executed with a synchronous function.
|
|
238
|
+
|
|
239
|
+
Note:
|
|
240
|
+
The `submit` method in this context returns an `asyncio.Future`, not a `concurrent.futures.Future`.
|
|
241
|
+
This is specific to the implementation of the executors in the `a_sync` library, which adapts the
|
|
242
|
+
behavior to integrate with the asyncio event loop.
|
|
243
|
+
|
|
244
|
+
See Also:
|
|
245
|
+
- :meth:`PruningThreadPoolExecutor.submit`
|
|
246
|
+
- :class:`asyncio.Future`
|
|
247
|
+
|
|
248
|
+
"""
|
|
249
|
+
executor = PruningThreadPoolExecutor(0)
|
|
250
|
+
fut = executor.submit(time.sleep, 0.1)
|
|
251
|
+
assert isinstance(fut, asyncio.Future)
|
|
252
|
+
await fut
|
tests/fixtures.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import asyncio
|
|
3
2
|
import time
|
|
4
3
|
from threading import current_thread, main_thread
|
|
@@ -11,97 +10,124 @@ from a_sync import ASyncBase
|
|
|
11
10
|
from a_sync.a_sync._meta import ASyncMeta, ASyncSingletonMeta
|
|
12
11
|
from a_sync.a_sync.singleton import ASyncGenericSingleton
|
|
13
12
|
|
|
14
|
-
increment = pytest.mark.parametrize(
|
|
13
|
+
increment = pytest.mark.parametrize("i", range(10))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WrongThreadError(Exception): ...
|
|
15
17
|
|
|
16
|
-
class WrongThreadError(Exception):
|
|
17
|
-
...
|
|
18
18
|
|
|
19
19
|
class TestClass(ASyncBase):
|
|
20
20
|
def __init__(self, v: int, sync: bool = False):
|
|
21
21
|
self.v = v
|
|
22
22
|
self.sync = sync
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
async def test_fn(self) -> int:
|
|
25
25
|
if self.sync == False and main_thread() != current_thread():
|
|
26
|
-
raise WrongThreadError(
|
|
26
|
+
raise WrongThreadError(
|
|
27
|
+
"This should be running on an event loop in the main thread."
|
|
28
|
+
)
|
|
27
29
|
elif self.sync == True and main_thread() != current_thread():
|
|
28
|
-
raise WrongThreadError(
|
|
30
|
+
raise WrongThreadError("This should be awaited in the main thread")
|
|
29
31
|
return self.v
|
|
30
|
-
|
|
32
|
+
|
|
31
33
|
@a_sync.aka.property
|
|
32
34
|
async def test_property(self) -> int:
|
|
33
35
|
if self.sync == False and main_thread() != current_thread():
|
|
34
|
-
raise WrongThreadError(
|
|
36
|
+
raise WrongThreadError(
|
|
37
|
+
"This should be running on an event loop in the main thread."
|
|
38
|
+
)
|
|
35
39
|
elif self.sync == True and main_thread() != current_thread():
|
|
36
|
-
raise WrongThreadError(
|
|
40
|
+
raise WrongThreadError("This should be awaited in the main thread")
|
|
37
41
|
return self.v * 2
|
|
38
|
-
|
|
42
|
+
|
|
39
43
|
@a_sync.alias.cached_property
|
|
40
44
|
async def test_cached_property(self) -> int:
|
|
41
45
|
if self.sync == False and main_thread() != current_thread():
|
|
42
|
-
raise WrongThreadError(
|
|
46
|
+
raise WrongThreadError(
|
|
47
|
+
"This should be running on an event loop in the main thread."
|
|
48
|
+
)
|
|
43
49
|
elif self.sync == True and main_thread() != current_thread():
|
|
44
|
-
raise WrongThreadError(
|
|
50
|
+
raise WrongThreadError("This should be awaited in the main thread")
|
|
45
51
|
await asyncio.sleep(2)
|
|
46
52
|
return self.v * 3
|
|
47
53
|
|
|
54
|
+
|
|
48
55
|
class TestSync(ASyncBase):
|
|
49
56
|
main = main_thread()
|
|
57
|
+
|
|
50
58
|
def __init__(self, v: int, sync: bool):
|
|
51
59
|
self.v = v
|
|
52
60
|
self.sync = sync
|
|
53
|
-
|
|
61
|
+
|
|
54
62
|
def test_fn(self) -> int:
|
|
55
63
|
# Sync bound methods are actually async functions that are run in an executor and awaited
|
|
56
64
|
if self.sync == False and main_thread() == current_thread():
|
|
57
|
-
raise WrongThreadError(
|
|
65
|
+
raise WrongThreadError(
|
|
66
|
+
"This should be running in an executor, not the main thread."
|
|
67
|
+
)
|
|
58
68
|
elif self.sync == True and main_thread() != current_thread():
|
|
59
|
-
raise WrongThreadError(
|
|
69
|
+
raise WrongThreadError(
|
|
70
|
+
"This should be running synchronously in the main thread"
|
|
71
|
+
)
|
|
60
72
|
return self.v
|
|
61
|
-
|
|
73
|
+
|
|
62
74
|
@a_sync.aka.property
|
|
63
75
|
def test_property(self) -> int:
|
|
64
76
|
if self.sync == False and main_thread() == current_thread():
|
|
65
|
-
raise WrongThreadError(
|
|
77
|
+
raise WrongThreadError(
|
|
78
|
+
"This should be running in an executor, not the main thread."
|
|
79
|
+
)
|
|
66
80
|
if self.sync == True and main_thread() == current_thread():
|
|
67
81
|
# Sync properties are actually async functions that are run in an executor and awaited
|
|
68
|
-
raise WrongThreadError(
|
|
82
|
+
raise WrongThreadError(
|
|
83
|
+
"This should be running in an executor, not the main thread."
|
|
84
|
+
)
|
|
69
85
|
return self.v * 2
|
|
70
|
-
|
|
86
|
+
|
|
71
87
|
@a_sync.alias.cached_property
|
|
72
88
|
def test_cached_property(self) -> int:
|
|
73
89
|
if self.sync == False and main_thread() == current_thread():
|
|
74
|
-
raise WrongThreadError(
|
|
90
|
+
raise WrongThreadError(
|
|
91
|
+
"This should be running in an executor, not the main thread."
|
|
92
|
+
)
|
|
75
93
|
if self.sync == True and main_thread() == current_thread():
|
|
76
94
|
# Sync properties are actually async functions that are run in an executor and awaited
|
|
77
|
-
raise WrongThreadError(
|
|
95
|
+
raise WrongThreadError(
|
|
96
|
+
"This should be running in an executor, not the main thread."
|
|
97
|
+
)
|
|
78
98
|
time.sleep(2)
|
|
79
99
|
return self.v * 3
|
|
80
100
|
|
|
101
|
+
|
|
81
102
|
class TestLimiter(TestClass):
|
|
82
103
|
limiter = 1
|
|
83
|
-
|
|
104
|
+
|
|
105
|
+
|
|
84
106
|
class TestInheritor(TestClass):
|
|
85
107
|
pass
|
|
86
108
|
|
|
109
|
+
|
|
87
110
|
class TestMeta(TestClass, metaclass=ASyncMeta):
|
|
88
111
|
pass
|
|
89
112
|
|
|
113
|
+
|
|
90
114
|
class TestSingleton(ASyncGenericSingleton, TestClass):
|
|
91
115
|
runs_per_minute = 100
|
|
92
116
|
pass
|
|
93
117
|
|
|
118
|
+
|
|
94
119
|
class TestSingletonMeta(TestClass, metaclass=ASyncSingletonMeta):
|
|
95
120
|
semaphore = 1
|
|
96
121
|
pass
|
|
97
122
|
|
|
123
|
+
|
|
98
124
|
class TestSemaphore(ASyncBase):
|
|
99
|
-
#semaphore=1 # NOTE: this is detected propely by undecorated test_fn but not the properties
|
|
100
|
-
|
|
125
|
+
# semaphore=1 # NOTE: this is detected propely by undecorated test_fn but not the properties
|
|
126
|
+
|
|
101
127
|
def __init__(self, v: int, sync: bool):
|
|
102
128
|
self.v = v
|
|
103
129
|
self.sync = sync
|
|
104
|
-
|
|
130
|
+
|
|
105
131
|
# spec on class and function both working
|
|
106
132
|
@a_sync.a_sync(semaphore=1)
|
|
107
133
|
async def test_fn(self) -> int:
|
|
@@ -109,11 +135,11 @@ class TestSemaphore(ASyncBase):
|
|
|
109
135
|
return self.v
|
|
110
136
|
|
|
111
137
|
# spec on class, function, property all working
|
|
112
|
-
@a_sync.aka.property(
|
|
138
|
+
@a_sync.aka.property("async", semaphore=1)
|
|
113
139
|
async def test_property(self) -> int:
|
|
114
140
|
await asyncio.sleep(1)
|
|
115
141
|
return self.v * 2
|
|
116
|
-
|
|
142
|
+
|
|
117
143
|
# spec on class, function, property all working
|
|
118
144
|
@a_sync.alias.cached_property(semaphore=50)
|
|
119
145
|
async def test_cached_property(self) -> int:
|
|
@@ -121,7 +147,7 @@ class TestSemaphore(ASyncBase):
|
|
|
121
147
|
return self.v * 3
|
|
122
148
|
|
|
123
149
|
|
|
124
|
-
def _test_kwargs(fn, default: Literal[
|
|
150
|
+
def _test_kwargs(fn, default: Literal["sync", "async", None]):
|
|
125
151
|
# force async
|
|
126
152
|
assert asyncio.get_event_loop().run_until_complete(fn(sync=False)) == 2
|
|
127
153
|
assert asyncio.get_event_loop().run_until_complete(fn(asynchronous=True)) == 2
|
|
@@ -132,18 +158,21 @@ def _test_kwargs(fn, default: Literal['sync','async',None]):
|
|
|
132
158
|
assert asyncio.get_event_loop().run_until_complete(fn(asynchronous=False)) == 2
|
|
133
159
|
assert fn(sync=True) == 2
|
|
134
160
|
assert fn(asynchronous=False) == 2
|
|
135
|
-
if default ==
|
|
161
|
+
if default == "sync":
|
|
136
162
|
assert fn() == 2
|
|
137
|
-
elif default ==
|
|
163
|
+
elif default == "async":
|
|
138
164
|
assert asyncio.get_event_loop().run_until_complete(fn()) == 2
|
|
139
165
|
|
|
166
|
+
|
|
140
167
|
async def sample_task(n):
|
|
141
168
|
await asyncio.sleep(0.01)
|
|
142
169
|
return n
|
|
143
170
|
|
|
171
|
+
|
|
144
172
|
async def timeout_task(n):
|
|
145
173
|
await asyncio.sleep(0.1)
|
|
146
174
|
return n
|
|
147
175
|
|
|
176
|
+
|
|
148
177
|
async def sample_exc(n):
|
|
149
|
-
raise ValueError("Sample error")
|
|
178
|
+
raise ValueError("Sample error")
|
tests/test_abstract.py
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
|
-
|
|
2
1
|
import sys
|
|
3
2
|
|
|
4
3
|
import pytest
|
|
5
4
|
|
|
6
5
|
from a_sync.a_sync.abstract import ASyncABC
|
|
7
6
|
|
|
8
|
-
_methods =
|
|
7
|
+
_methods = "__a_sync_default_mode__", "__a_sync_flag_name__", "__a_sync_flag_value__"
|
|
9
8
|
if sys.version_info >= (3, 12):
|
|
10
9
|
_MIDDLE = "without an implementation for abstract methods"
|
|
11
10
|
_methods = (f"'{method}'" for method in _methods)
|
|
12
11
|
else:
|
|
13
12
|
_MIDDLE = "with abstract methods"
|
|
14
13
|
|
|
14
|
+
|
|
15
15
|
def test_abc_direct_init():
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
"""Test that ASyncABC cannot be instantiated directly.
|
|
17
|
+
|
|
18
|
+
This test verifies that attempting to instantiate the abstract base class
|
|
19
|
+
`ASyncABC` raises a `TypeError`. This is expected behavior for abstract
|
|
20
|
+
base classes in Python, which require subclasses to implement all abstract
|
|
21
|
+
methods before instantiation.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
TypeError: If `ASyncABC` is instantiated without implementing abstract methods.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
>>> from a_sync.a_sync.abstract import ASyncABC
|
|
28
|
+
>>> ASyncABC()
|
|
29
|
+
TypeError: Can't instantiate abstract class ASyncABC with abstract methods ...
|
|
30
|
+
"""
|
|
31
|
+
with pytest.raises(
|
|
32
|
+
TypeError,
|
|
33
|
+
match=f"Can't instantiate abstract class ASyncABC {_MIDDLE} {', '.join(_methods)}",
|
|
34
|
+
):
|
|
35
|
+
ASyncABC()
|
tests/test_as_completed.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import asyncio
|
|
3
2
|
|
|
4
3
|
import a_sync
|
|
@@ -11,7 +10,10 @@ from tests.fixtures import sample_exc, sample_task, timeout_task
|
|
|
11
10
|
async def test_as_completed_with_awaitables():
|
|
12
11
|
tasks = [sample_task(i) for i in range(5)]
|
|
13
12
|
results = [await result for result in a_sync.as_completed(tasks, aiter=False)]
|
|
14
|
-
assert sorted(results) == list(
|
|
13
|
+
assert sorted(results) == list(
|
|
14
|
+
range(5)
|
|
15
|
+
), "Results should be in ascending order from 0 to 4"
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
@pytest.mark.asyncio_cooperative
|
|
17
19
|
async def test_as_completed_with_awaitables_aiter():
|
|
@@ -19,42 +21,61 @@ async def test_as_completed_with_awaitables_aiter():
|
|
|
19
21
|
results = []
|
|
20
22
|
async for result in a_sync.as_completed(tasks, aiter=True):
|
|
21
23
|
results.append(result)
|
|
22
|
-
assert sorted(results) == list(
|
|
24
|
+
assert sorted(results) == list(
|
|
25
|
+
range(5)
|
|
26
|
+
), "Results should be in ascending order from 0 to 4"
|
|
27
|
+
|
|
23
28
|
|
|
24
29
|
@pytest.mark.asyncio_cooperative
|
|
25
30
|
async def test_as_completed_with_mapping():
|
|
26
|
-
tasks = {
|
|
31
|
+
tasks = {"task1": sample_task(1), "task2": sample_task(2)}
|
|
27
32
|
results = {}
|
|
28
33
|
for result in a_sync.as_completed(tasks, aiter=False):
|
|
29
34
|
key, value = await result
|
|
30
35
|
results[key] = value
|
|
31
|
-
assert results == {
|
|
36
|
+
assert results == {"task1": 1, "task2": 2}, "Results should match the input mapping"
|
|
37
|
+
|
|
32
38
|
|
|
33
39
|
@pytest.mark.asyncio_cooperative
|
|
34
40
|
async def test_as_completed_with_mapping_aiter():
|
|
35
|
-
tasks = {
|
|
41
|
+
tasks = {"task1": sample_task(1), "task2": sample_task(2)}
|
|
36
42
|
results = {}
|
|
37
43
|
async for key, result in a_sync.as_completed(tasks, aiter=True):
|
|
38
44
|
results[key] = result
|
|
39
|
-
assert results == {
|
|
45
|
+
assert results == {"task1": 1, "task2": 2}, "Results should match the input mapping"
|
|
46
|
+
|
|
40
47
|
|
|
41
48
|
@pytest.mark.asyncio_cooperative
|
|
42
49
|
async def test_as_completed_with_timeout():
|
|
43
50
|
tasks = [timeout_task(i) for i in range(2)]
|
|
44
51
|
with pytest.raises(asyncio.TimeoutError):
|
|
45
|
-
[
|
|
52
|
+
[
|
|
53
|
+
await result
|
|
54
|
+
for result in a_sync.as_completed(tasks, aiter=False, timeout=0.05)
|
|
55
|
+
]
|
|
56
|
+
|
|
46
57
|
|
|
47
58
|
@pytest.mark.asyncio_cooperative
|
|
48
59
|
async def test_as_completed_with_timeout_aiter():
|
|
49
60
|
tasks = [timeout_task(i) for i in range(2)]
|
|
50
61
|
with pytest.raises(asyncio.TimeoutError):
|
|
51
|
-
[
|
|
62
|
+
[
|
|
63
|
+
result
|
|
64
|
+
async for result in a_sync.as_completed(tasks, aiter=True, timeout=0.05)
|
|
65
|
+
]
|
|
66
|
+
|
|
52
67
|
|
|
53
68
|
@pytest.mark.asyncio_cooperative
|
|
54
69
|
async def test_as_completed_return_exceptions():
|
|
55
70
|
tasks = [sample_exc(i) for i in range(1)]
|
|
56
|
-
results = [
|
|
57
|
-
|
|
71
|
+
results = [
|
|
72
|
+
await result
|
|
73
|
+
for result in a_sync.as_completed(tasks, aiter=False, return_exceptions=True)
|
|
74
|
+
]
|
|
75
|
+
assert isinstance(
|
|
76
|
+
results[0], ValueError
|
|
77
|
+
), f"The result should be an exception {results}"
|
|
78
|
+
|
|
58
79
|
|
|
59
80
|
@pytest.mark.asyncio_cooperative
|
|
60
81
|
async def test_as_completed_return_exceptions_aiter():
|
|
@@ -64,11 +85,17 @@ async def test_as_completed_return_exceptions_aiter():
|
|
|
64
85
|
results.append(result)
|
|
65
86
|
assert isinstance(results[0], ValueError), "The result should be an exception"
|
|
66
87
|
|
|
88
|
+
|
|
67
89
|
@pytest.mark.asyncio_cooperative
|
|
68
90
|
async def test_as_completed_with_tqdm_disabled():
|
|
69
91
|
tasks = [sample_task(i) for i in range(5)]
|
|
70
|
-
results = [
|
|
71
|
-
|
|
92
|
+
results = [
|
|
93
|
+
await result for result in a_sync.as_completed(tasks, aiter=False, tqdm=False)
|
|
94
|
+
]
|
|
95
|
+
assert sorted(results) == list(
|
|
96
|
+
range(5)
|
|
97
|
+
), "Results should be in ascending order from 0 to 4"
|
|
98
|
+
|
|
72
99
|
|
|
73
100
|
@pytest.mark.asyncio_cooperative
|
|
74
101
|
async def test_as_completed_with_tqdm_disabled_aiter():
|
|
@@ -76,23 +103,29 @@ async def test_as_completed_with_tqdm_disabled_aiter():
|
|
|
76
103
|
results = []
|
|
77
104
|
async for result in a_sync.as_completed(tasks, aiter=True, tqdm=False):
|
|
78
105
|
results.append(result)
|
|
79
|
-
assert sorted(results) == list(
|
|
106
|
+
assert sorted(results) == list(
|
|
107
|
+
range(5)
|
|
108
|
+
), "Results should be in ascending order from 0 to 4"
|
|
109
|
+
|
|
80
110
|
|
|
81
111
|
@pytest.mark.asyncio_cooperative
|
|
82
112
|
async def test_as_completed_with_mapping_and_return_exceptions():
|
|
83
|
-
tasks = {
|
|
113
|
+
tasks = {"task1": sample_exc(1), "task2": sample_task(2)}
|
|
84
114
|
results = {}
|
|
85
115
|
for result in a_sync.as_completed(tasks, return_exceptions=True, aiter=False):
|
|
86
116
|
key, value = await result
|
|
87
117
|
results[key] = value
|
|
88
|
-
assert isinstance(results[
|
|
89
|
-
assert results[
|
|
118
|
+
assert isinstance(results["task1"], ValueError), "Result should be ValueError"
|
|
119
|
+
assert results["task2"] == 2, "Results should match the input mapping"
|
|
120
|
+
|
|
90
121
|
|
|
91
122
|
@pytest.mark.asyncio_cooperative
|
|
92
123
|
async def test_as_completed_with_mapping_and_return_exceptions_aiter():
|
|
93
|
-
tasks = {
|
|
124
|
+
tasks = {"task1": sample_exc(1), "task2": sample_task(2)}
|
|
94
125
|
results = {}
|
|
95
|
-
async for key, result in a_sync.as_completed(
|
|
126
|
+
async for key, result in a_sync.as_completed(
|
|
127
|
+
tasks, return_exceptions=True, aiter=True
|
|
128
|
+
):
|
|
96
129
|
results[key] = result
|
|
97
|
-
assert isinstance(results[
|
|
98
|
-
assert results[
|
|
130
|
+
assert isinstance(results["task1"], ValueError), "Result should be ValueError"
|
|
131
|
+
assert results["task2"] == 2, "Results should match the input mapping"
|