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
a_sync/a_sync/_descriptor.py
CHANGED
|
@@ -1,25 +1,55 @@
|
|
|
1
1
|
"""
|
|
2
|
-
This module contains the ASyncDescriptor class, which is used to create sync/async methods
|
|
2
|
+
This module contains the :class:`ASyncDescriptor` class, which is used to create dual-function sync/async methods
|
|
3
3
|
and properties.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The :class:`ASyncDescriptor` class provides functionality for mapping operations across multiple instances
|
|
6
|
+
and includes utility methods for common operations such as checking if all or any results are truthy,
|
|
7
|
+
and finding the minimum, maximum, or sum of results of the method or property mapped across multiple instances.
|
|
8
|
+
|
|
9
|
+
See Also:
|
|
10
|
+
- :class:`~a_sync.a_sync.function.ASyncFunction`
|
|
11
|
+
- :class:`~a_sync.a_sync.method.ASyncMethodDescriptor`
|
|
6
12
|
"""
|
|
7
13
|
|
|
8
14
|
import functools
|
|
9
15
|
|
|
10
16
|
from a_sync._typing import *
|
|
11
17
|
from a_sync.a_sync import decorator
|
|
12
|
-
from a_sync.a_sync.function import ASyncFunction,
|
|
18
|
+
from a_sync.a_sync.function import ASyncFunction, ModifierManager, _ModifiedMixin
|
|
13
19
|
|
|
14
20
|
if TYPE_CHECKING:
|
|
15
21
|
from a_sync import TaskMapping
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
|
|
24
|
+
class ASyncDescriptor(_ModifiedMixin, Generic[I, P, T]):
|
|
18
25
|
"""
|
|
19
|
-
A descriptor base class for
|
|
26
|
+
A descriptor base class for dual-function ASync methods and properties.
|
|
20
27
|
|
|
21
28
|
This class provides functionality for mapping operations across multiple instances
|
|
22
|
-
and includes utility methods for common operations
|
|
29
|
+
and includes utility methods for common operations such as checking if all or any
|
|
30
|
+
results are truthy, and finding the minimum, maximum, or sum of results of the method
|
|
31
|
+
or property mapped across multiple instances.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
To create a dual-function method or property, subclass :class:`ASyncDescriptor` and implement
|
|
35
|
+
the desired functionality. You can then use the provided utility methods to perform operations
|
|
36
|
+
across multiple instances.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
class MyDescriptor(ASyncDescriptor):
|
|
40
|
+
def __init__(self, func):
|
|
41
|
+
super().__init__(func)
|
|
42
|
+
|
|
43
|
+
class MyClass:
|
|
44
|
+
my_method = MyDescriptor(lambda x: x * 2)
|
|
45
|
+
|
|
46
|
+
instance = MyClass()
|
|
47
|
+
result = instance.my_method.map([1, 2, 3])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
See Also:
|
|
51
|
+
- :class:`~a_sync.a_sync.function.ASyncFunction`
|
|
52
|
+
- :class:`~a_sync.a_sync.method.ASyncMethodDescriptor`
|
|
23
53
|
"""
|
|
24
54
|
|
|
25
55
|
__wrapped__: AnyFn[Concatenate[I, P], T]
|
|
@@ -28,13 +58,13 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
28
58
|
__slots__ = "field_name", "_fget"
|
|
29
59
|
|
|
30
60
|
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
_fget: AnyFn[Concatenate[I, P], T],
|
|
33
|
-
field_name: Optional[str] = None,
|
|
61
|
+
self,
|
|
62
|
+
_fget: AnyFn[Concatenate[I, P], T],
|
|
63
|
+
field_name: Optional[str] = None,
|
|
34
64
|
**modifiers: ModifierKwargs,
|
|
35
65
|
) -> None:
|
|
36
66
|
"""
|
|
37
|
-
Initialize the
|
|
67
|
+
Initialize the :class:`ASyncDescriptor`.
|
|
38
68
|
|
|
39
69
|
Args:
|
|
40
70
|
_fget: The function to be wrapped.
|
|
@@ -45,19 +75,21 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
45
75
|
ValueError: If _fget is not callable.
|
|
46
76
|
"""
|
|
47
77
|
if not callable(_fget):
|
|
48
|
-
raise ValueError(f
|
|
78
|
+
raise ValueError(f"Unable to decorate {_fget}")
|
|
49
79
|
self.modifiers = ModifierManager(modifiers)
|
|
50
80
|
if isinstance(_fget, ASyncFunction):
|
|
51
81
|
self.modifiers.update(_fget.modifiers)
|
|
52
82
|
self.__wrapped__ = _fget
|
|
53
83
|
elif asyncio.iscoroutinefunction(_fget):
|
|
54
|
-
self.__wrapped__: AsyncUnboundMethod[I, P, T] =
|
|
84
|
+
self.__wrapped__: AsyncUnboundMethod[I, P, T] = (
|
|
85
|
+
self.modifiers.apply_async_modifiers(_fget)
|
|
86
|
+
)
|
|
55
87
|
else:
|
|
56
88
|
self.__wrapped__ = _fget
|
|
57
89
|
|
|
58
90
|
self.field_name = field_name or _fget.__name__
|
|
59
|
-
"""The name of the field the
|
|
60
|
-
|
|
91
|
+
"""The name of the field the :class:`ASyncDescriptor` is bound to."""
|
|
92
|
+
|
|
61
93
|
functools.update_wrapper(self, self.__wrapped__)
|
|
62
94
|
|
|
63
95
|
def __repr__(self) -> str:
|
|
@@ -65,7 +97,7 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
65
97
|
|
|
66
98
|
def __set_name__(self, owner, name):
|
|
67
99
|
"""
|
|
68
|
-
Set the field name when the
|
|
100
|
+
Set the field name when the :class:`ASyncDescriptor` is assigned to a class.
|
|
69
101
|
|
|
70
102
|
Args:
|
|
71
103
|
owner: The class owning this descriptor.
|
|
@@ -73,18 +105,32 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
73
105
|
"""
|
|
74
106
|
self.field_name = name
|
|
75
107
|
|
|
76
|
-
def map(
|
|
108
|
+
def map(
|
|
109
|
+
self, *instances: AnyIterable[I], **bound_method_kwargs: P.kwargs
|
|
110
|
+
) -> "TaskMapping[I, T]":
|
|
77
111
|
"""
|
|
78
|
-
Create a TaskMapping for the given instances.
|
|
112
|
+
Create a :class:`TaskMapping` for the given instances.
|
|
79
113
|
|
|
80
114
|
Args:
|
|
81
115
|
*instances: Iterable of instances to map over.
|
|
82
116
|
**bound_method_kwargs: Additional keyword arguments for the bound method.
|
|
83
117
|
|
|
84
118
|
Returns:
|
|
85
|
-
A TaskMapping object.
|
|
119
|
+
A :class:`TaskMapping` object.
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
class MyDescriptor(ASyncDescriptor):
|
|
123
|
+
def __init__(self, func):
|
|
124
|
+
super().__init__(func)
|
|
125
|
+
|
|
126
|
+
class MyClass:
|
|
127
|
+
my_method = MyDescriptor(lambda x: x * 2)
|
|
128
|
+
|
|
129
|
+
instance = MyClass()
|
|
130
|
+
result = instance.my_method.map([1, 2, 3])
|
|
86
131
|
"""
|
|
87
132
|
from a_sync.task import TaskMapping
|
|
133
|
+
|
|
88
134
|
return TaskMapping(self, *instances, **bound_method_kwargs)
|
|
89
135
|
|
|
90
136
|
@functools.cached_property
|
|
@@ -93,7 +139,18 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
93
139
|
Create an :class:`~ASyncFunction` that checks if all results are truthy.
|
|
94
140
|
|
|
95
141
|
Returns:
|
|
96
|
-
An ASyncFunction object.
|
|
142
|
+
An :class:`ASyncFunction` object.
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
class MyDescriptor(ASyncDescriptor):
|
|
146
|
+
def __init__(self, func):
|
|
147
|
+
super().__init__(func)
|
|
148
|
+
|
|
149
|
+
class MyClass:
|
|
150
|
+
my_method = MyDescriptor(lambda x: x > 0)
|
|
151
|
+
|
|
152
|
+
instance = MyClass()
|
|
153
|
+
result = await instance.my_method.all([1, 2, 3])
|
|
97
154
|
"""
|
|
98
155
|
return decorator.a_sync(default=self.default)(self._all)
|
|
99
156
|
|
|
@@ -103,7 +160,18 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
103
160
|
Create an :class:`~ASyncFunction` that checks if any result is truthy.
|
|
104
161
|
|
|
105
162
|
Returns:
|
|
106
|
-
An ASyncFunction object.
|
|
163
|
+
An :class:`ASyncFunction` object.
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
class MyDescriptor(ASyncDescriptor):
|
|
167
|
+
def __init__(self, func):
|
|
168
|
+
super().__init__(func)
|
|
169
|
+
|
|
170
|
+
class MyClass:
|
|
171
|
+
my_method = MyDescriptor(lambda x: x > 0)
|
|
172
|
+
|
|
173
|
+
instance = MyClass()
|
|
174
|
+
result = await instance.my_method.any([-1, 0, 1])
|
|
107
175
|
"""
|
|
108
176
|
return decorator.a_sync(default=self.default)(self._any)
|
|
109
177
|
|
|
@@ -113,7 +181,20 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
113
181
|
Create an :class:`~ASyncFunction` that returns the minimum result.
|
|
114
182
|
|
|
115
183
|
Returns:
|
|
116
|
-
An ASyncFunction object.
|
|
184
|
+
An :class:`ASyncFunction` object.
|
|
185
|
+
|
|
186
|
+
Examples:
|
|
187
|
+
```python
|
|
188
|
+
class MyDescriptor(ASyncDescriptor):
|
|
189
|
+
def __init__(self, func):
|
|
190
|
+
super().__init__(func)
|
|
191
|
+
|
|
192
|
+
class MyClass:
|
|
193
|
+
my_method = MyDescriptor(lambda x: x)
|
|
194
|
+
|
|
195
|
+
instance = MyClass()
|
|
196
|
+
result = await instance.my_method.min([3, 1, 2])
|
|
197
|
+
```
|
|
117
198
|
"""
|
|
118
199
|
return decorator.a_sync(default=self.default)(self._min)
|
|
119
200
|
|
|
@@ -123,7 +204,18 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
123
204
|
Create an :class:`~ASyncFunction` that returns the maximum result.
|
|
124
205
|
|
|
125
206
|
Returns:
|
|
126
|
-
An ASyncFunction object.
|
|
207
|
+
An :class:`ASyncFunction` object.
|
|
208
|
+
|
|
209
|
+
Examples:
|
|
210
|
+
class MyDescriptor(ASyncDescriptor):
|
|
211
|
+
def __init__(self, func):
|
|
212
|
+
super().__init__(func)
|
|
213
|
+
|
|
214
|
+
class MyClass:
|
|
215
|
+
my_method = MyDescriptor(lambda x: x)
|
|
216
|
+
|
|
217
|
+
instance = MyClass()
|
|
218
|
+
result = await instance.my_method.max([3, 1, 2])
|
|
127
219
|
"""
|
|
128
220
|
return decorator.a_sync(default=self.default)(self._max)
|
|
129
221
|
|
|
@@ -133,11 +225,30 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
133
225
|
Create an :class:`~ASyncFunction` that returns the sum of results.
|
|
134
226
|
|
|
135
227
|
Returns:
|
|
136
|
-
An ASyncFunction object.
|
|
228
|
+
An :class:`ASyncFunction` object.
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
```python
|
|
232
|
+
class MyDescriptor(ASyncDescriptor):
|
|
233
|
+
def __init__(self, func):
|
|
234
|
+
super().__init__(func)
|
|
235
|
+
|
|
236
|
+
class MyClass:
|
|
237
|
+
my_method = MyDescriptor(lambda x: x)
|
|
238
|
+
|
|
239
|
+
instance = MyClass()
|
|
240
|
+
result = await instance.my_method.sum([1, 2, 3])
|
|
241
|
+
```
|
|
137
242
|
"""
|
|
138
243
|
return decorator.a_sync(default=self.default)(self._sum)
|
|
139
244
|
|
|
140
|
-
async def _all(
|
|
245
|
+
async def _all(
|
|
246
|
+
self,
|
|
247
|
+
*instances: AnyIterable[I],
|
|
248
|
+
concurrency: Optional[int] = None,
|
|
249
|
+
name: str = "",
|
|
250
|
+
**kwargs: P.kwargs,
|
|
251
|
+
) -> bool:
|
|
141
252
|
"""
|
|
142
253
|
Check if all results are truthy.
|
|
143
254
|
|
|
@@ -149,10 +260,31 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
149
260
|
|
|
150
261
|
Returns:
|
|
151
262
|
A boolean indicating if all results are truthy.
|
|
152
|
-
"""
|
|
153
|
-
return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).all(pop=True, sync=False)
|
|
154
263
|
|
|
155
|
-
|
|
264
|
+
Examples:
|
|
265
|
+
```python
|
|
266
|
+
class MyDescriptor(ASyncDescriptor):
|
|
267
|
+
def __init__(self, func):
|
|
268
|
+
super().__init__(func)
|
|
269
|
+
|
|
270
|
+
class MyClass:
|
|
271
|
+
my_method = MyDescriptor(lambda x: x > 0)
|
|
272
|
+
|
|
273
|
+
instance = MyClass()
|
|
274
|
+
result = await instance.my_method._all([1, 2, 3])
|
|
275
|
+
```
|
|
276
|
+
"""
|
|
277
|
+
return await self.map(
|
|
278
|
+
*instances, concurrency=concurrency, name=name, **kwargs
|
|
279
|
+
).all(pop=True, sync=False)
|
|
280
|
+
|
|
281
|
+
async def _any(
|
|
282
|
+
self,
|
|
283
|
+
*instances: AnyIterable[I],
|
|
284
|
+
concurrency: Optional[int] = None,
|
|
285
|
+
name: str = "",
|
|
286
|
+
**kwargs: P.kwargs,
|
|
287
|
+
) -> bool:
|
|
156
288
|
"""
|
|
157
289
|
Check if any result is truthy.
|
|
158
290
|
|
|
@@ -164,10 +296,31 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
164
296
|
|
|
165
297
|
Returns:
|
|
166
298
|
A boolean indicating if any result is truthy.
|
|
167
|
-
"""
|
|
168
|
-
return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).any(pop=True, sync=False)
|
|
169
299
|
|
|
170
|
-
|
|
300
|
+
Examples:
|
|
301
|
+
```python
|
|
302
|
+
class MyDescriptor(ASyncDescriptor):
|
|
303
|
+
def __init__(self, func):
|
|
304
|
+
super().__init__(func)
|
|
305
|
+
|
|
306
|
+
class MyClass:
|
|
307
|
+
my_method = MyDescriptor(lambda x: x > 0)
|
|
308
|
+
|
|
309
|
+
instance = MyClass()
|
|
310
|
+
result = await instance.my_method._any([-1, 0, 1])
|
|
311
|
+
```
|
|
312
|
+
"""
|
|
313
|
+
return await self.map(
|
|
314
|
+
*instances, concurrency=concurrency, name=name, **kwargs
|
|
315
|
+
).any(pop=True, sync=False)
|
|
316
|
+
|
|
317
|
+
async def _min(
|
|
318
|
+
self,
|
|
319
|
+
*instances: AnyIterable[I],
|
|
320
|
+
concurrency: Optional[int] = None,
|
|
321
|
+
name: str = "",
|
|
322
|
+
**kwargs: P.kwargs,
|
|
323
|
+
) -> T:
|
|
171
324
|
"""
|
|
172
325
|
Find the minimum result.
|
|
173
326
|
|
|
@@ -179,10 +332,31 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
179
332
|
|
|
180
333
|
Returns:
|
|
181
334
|
The minimum result.
|
|
182
|
-
"""
|
|
183
|
-
return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).min(pop=True, sync=False)
|
|
184
335
|
|
|
185
|
-
|
|
336
|
+
Examples:
|
|
337
|
+
```python
|
|
338
|
+
class MyDescriptor(ASyncDescriptor):
|
|
339
|
+
def __init__(self, func):
|
|
340
|
+
super().__init__(func)
|
|
341
|
+
|
|
342
|
+
class MyClass:
|
|
343
|
+
my_method = MyDescriptor(lambda x: x)
|
|
344
|
+
|
|
345
|
+
instance = MyClass()
|
|
346
|
+
result = await instance.my_method._min([3, 1, 2])
|
|
347
|
+
```
|
|
348
|
+
"""
|
|
349
|
+
return await self.map(
|
|
350
|
+
*instances, concurrency=concurrency, name=name, **kwargs
|
|
351
|
+
).min(pop=True, sync=False)
|
|
352
|
+
|
|
353
|
+
async def _max(
|
|
354
|
+
self,
|
|
355
|
+
*instances: AnyIterable[I],
|
|
356
|
+
concurrency: Optional[int] = None,
|
|
357
|
+
name: str = "",
|
|
358
|
+
**kwargs: P.kwargs,
|
|
359
|
+
) -> T:
|
|
186
360
|
"""
|
|
187
361
|
Find the maximum result.
|
|
188
362
|
|
|
@@ -194,10 +368,31 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
194
368
|
|
|
195
369
|
Returns:
|
|
196
370
|
The maximum result.
|
|
197
|
-
"""
|
|
198
|
-
return await self.map(*instances, concurrency=concurrency, name=name, **kwargs).max(pop=True, sync=False)
|
|
199
371
|
|
|
200
|
-
|
|
372
|
+
Examples:
|
|
373
|
+
```python
|
|
374
|
+
class MyDescriptor(ASyncDescriptor):
|
|
375
|
+
def __init__(self, func):
|
|
376
|
+
super().__init__(func)
|
|
377
|
+
|
|
378
|
+
class MyClass:
|
|
379
|
+
my_method = MyDescriptor(lambda x: x)
|
|
380
|
+
|
|
381
|
+
instance = MyClass()
|
|
382
|
+
result = await instance.my_method._max([3, 1, 2])
|
|
383
|
+
```
|
|
384
|
+
"""
|
|
385
|
+
return await self.map(
|
|
386
|
+
*instances, concurrency=concurrency, name=name, **kwargs
|
|
387
|
+
).max(pop=True, sync=False)
|
|
388
|
+
|
|
389
|
+
async def _sum(
|
|
390
|
+
self,
|
|
391
|
+
*instances: AnyIterable[I],
|
|
392
|
+
concurrency: Optional[int] = None,
|
|
393
|
+
name: str = "",
|
|
394
|
+
**kwargs: P.kwargs,
|
|
395
|
+
) -> T:
|
|
201
396
|
"""
|
|
202
397
|
Calculate the sum of results.
|
|
203
398
|
|
|
@@ -209,11 +404,26 @@ class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
|
|
|
209
404
|
|
|
210
405
|
Returns:
|
|
211
406
|
The sum of the results.
|
|
407
|
+
|
|
408
|
+
Examples:
|
|
409
|
+
```python
|
|
410
|
+
class MyDescriptor(ASyncDescriptor):
|
|
411
|
+
def __init__(self, func):
|
|
412
|
+
super().__init__(func)
|
|
413
|
+
|
|
414
|
+
class MyClass:
|
|
415
|
+
my_method = MyDescriptor(lambda x: x)
|
|
416
|
+
|
|
417
|
+
instance = MyClass()
|
|
418
|
+
result = await instance.my_method._sum([1, 2, 3])
|
|
419
|
+
```
|
|
212
420
|
"""
|
|
213
|
-
return await self.map(
|
|
421
|
+
return await self.map(
|
|
422
|
+
*instances, concurrency=concurrency, name=name, **kwargs
|
|
423
|
+
).sum(pop=True, sync=False)
|
|
214
424
|
|
|
215
425
|
def __init_subclass__(cls) -> None:
|
|
216
426
|
for attr in cls.__dict__.values():
|
|
217
427
|
if attr.__doc__ and "{cls}" in attr.__doc__:
|
|
218
428
|
attr.__doc__ = attr.__doc__.replace("{cls}", f":class:`{cls.__name__}`")
|
|
219
|
-
return super().__init_subclass__()
|
|
429
|
+
return super().__init_subclass__()
|
a_sync/a_sync/_flags.py
CHANGED
|
@@ -2,27 +2,71 @@
|
|
|
2
2
|
This module provides functionality for handling synchronous and asynchronous flags
|
|
3
3
|
in the ez-a-sync library.
|
|
4
4
|
|
|
5
|
-
ez-a-sync uses 'flags' to indicate whether objects
|
|
5
|
+
ez-a-sync uses 'flags' to indicate whether objects or function calls will be synchronous or asynchronous.
|
|
6
6
|
|
|
7
|
-
You can use any of the provided flags, whichever makes most sense for your use case.
|
|
7
|
+
You can use any of the provided flags, whichever makes the most sense for your use case.
|
|
8
|
+
|
|
9
|
+
AFFIRMATIVE_FLAGS: Set of flags indicating synchronous behavior. Currently includes "sync".
|
|
10
|
+
|
|
11
|
+
NEGATIVE_FLAGS: Set of flags indicating asynchronous behavior. Currently includes "asynchronous".
|
|
12
|
+
|
|
13
|
+
VIABLE_FLAGS: Set of all valid flags, combining both synchronous and asynchronous indicators.
|
|
14
|
+
|
|
15
|
+
Functions:
|
|
16
|
+
- negate_if_necessary: Negates the flag value if necessary based on the flag type.
|
|
17
|
+
- validate_flag_value: Validates that the flag value is a boolean.
|
|
8
18
|
"""
|
|
9
19
|
|
|
10
20
|
from typing import Any
|
|
11
21
|
|
|
12
22
|
from a_sync import exceptions
|
|
13
23
|
|
|
14
|
-
AFFIRMATIVE_FLAGS = {
|
|
15
|
-
"""Set of flags indicating synchronous behavior.
|
|
24
|
+
AFFIRMATIVE_FLAGS = {"sync"}
|
|
25
|
+
"""Set of flags indicating synchronous behavior.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
>>> 'sync' in AFFIRMATIVE_FLAGS
|
|
29
|
+
True
|
|
30
|
+
|
|
31
|
+
>>> 'async' in AFFIRMATIVE_FLAGS
|
|
32
|
+
False
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
NEGATIVE_FLAGS = {"asynchronous"}
|
|
36
|
+
"""Set of flags indicating asynchronous behavior.
|
|
16
37
|
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
Examples:
|
|
39
|
+
>>> 'asynchronous' in NEGATIVE_FLAGS
|
|
40
|
+
True
|
|
41
|
+
|
|
42
|
+
>>> 'sync' in NEGATIVE_FLAGS
|
|
43
|
+
False
|
|
44
|
+
"""
|
|
19
45
|
|
|
20
46
|
VIABLE_FLAGS = AFFIRMATIVE_FLAGS | NEGATIVE_FLAGS
|
|
21
|
-
"""Set of all valid flags.
|
|
47
|
+
"""Set of all valid flags, combining both synchronous and asynchronous indicators.
|
|
48
|
+
|
|
49
|
+
A-Sync uses 'flags' to indicate whether objects or function calls will be sync or async.
|
|
50
|
+
You can use any of the provided flags, whichever makes most sense for your use case.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
>>> 'sync' in VIABLE_FLAGS
|
|
54
|
+
True
|
|
55
|
+
|
|
56
|
+
>>> 'asynchronous' in VIABLE_FLAGS
|
|
57
|
+
True
|
|
58
|
+
|
|
59
|
+
>>> 'invalid' in VIABLE_FLAGS
|
|
60
|
+
False
|
|
61
|
+
"""
|
|
62
|
+
|
|
22
63
|
|
|
23
64
|
def negate_if_necessary(flag: str, flag_value: bool) -> bool:
|
|
24
65
|
"""Negate the flag value if necessary based on the flag type.
|
|
25
66
|
|
|
67
|
+
This function checks if the provided flag is in the set of affirmative or negative flags
|
|
68
|
+
and negates the flag value accordingly. If the flag is not recognized, it raises an exception.
|
|
69
|
+
|
|
26
70
|
Args:
|
|
27
71
|
flag: The flag to check.
|
|
28
72
|
flag_value: The value of the flag.
|
|
@@ -31,7 +75,17 @@ def negate_if_necessary(flag: str, flag_value: bool) -> bool:
|
|
|
31
75
|
The potentially negated flag value.
|
|
32
76
|
|
|
33
77
|
Raises:
|
|
34
|
-
|
|
78
|
+
exceptions.InvalidFlag: If the flag is not recognized.
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
>>> negate_if_necessary('sync', True)
|
|
82
|
+
True
|
|
83
|
+
|
|
84
|
+
>>> negate_if_necessary('asynchronous', True)
|
|
85
|
+
False
|
|
86
|
+
|
|
87
|
+
See Also:
|
|
88
|
+
- :func:`validate_flag_value`: Validates that the flag value is a boolean.
|
|
35
89
|
"""
|
|
36
90
|
validate_flag_value(flag, flag_value)
|
|
37
91
|
if flag in AFFIRMATIVE_FLAGS:
|
|
@@ -40,10 +94,13 @@ def negate_if_necessary(flag: str, flag_value: bool) -> bool:
|
|
|
40
94
|
return bool(not flag_value)
|
|
41
95
|
raise exceptions.InvalidFlag(flag)
|
|
42
96
|
|
|
97
|
+
|
|
43
98
|
def validate_flag_value(flag: str, flag_value: Any) -> bool:
|
|
44
99
|
"""
|
|
45
100
|
Validate that the flag value is a boolean.
|
|
46
101
|
|
|
102
|
+
This function ensures that the provided flag value is of type boolean. If not, it raises an exception.
|
|
103
|
+
|
|
47
104
|
Args:
|
|
48
105
|
flag: The flag being validated.
|
|
49
106
|
flag_value: The value to validate.
|
|
@@ -52,7 +109,19 @@ def validate_flag_value(flag: str, flag_value: Any) -> bool:
|
|
|
52
109
|
The validated flag value.
|
|
53
110
|
|
|
54
111
|
Raises:
|
|
55
|
-
|
|
112
|
+
exceptions.InvalidFlagValue: If the flag value is not a boolean.
|
|
113
|
+
|
|
114
|
+
Examples:
|
|
115
|
+
>>> validate_flag_value('sync', True)
|
|
116
|
+
True
|
|
117
|
+
|
|
118
|
+
>>> validate_flag_value('asynchronous', 'yes')
|
|
119
|
+
Traceback (most recent call last):
|
|
120
|
+
...
|
|
121
|
+
exceptions.InvalidFlagValue: Invalid flag value for 'asynchronous': 'yes'
|
|
122
|
+
|
|
123
|
+
See Also:
|
|
124
|
+
- :func:`negate_if_necessary`: Negates the flag value if necessary based on the flag type.
|
|
56
125
|
"""
|
|
57
126
|
if not isinstance(flag_value, bool):
|
|
58
127
|
raise exceptions.InvalidFlagValue(flag, flag_value)
|
a_sync/a_sync/_helpers.py
CHANGED
|
@@ -17,13 +17,21 @@ def _await(awaitable: Awaitable[T]) -> T:
|
|
|
17
17
|
Await an awaitable object in a synchronous context.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
|
-
awaitable: The awaitable object to be awaited.
|
|
21
|
-
|
|
22
|
-
Returns:
|
|
23
|
-
The result of the awaitable.
|
|
20
|
+
awaitable (Awaitable[T]): The awaitable object to be awaited.
|
|
24
21
|
|
|
25
22
|
Raises:
|
|
26
|
-
|
|
23
|
+
exceptions.SyncModeInAsyncContextError: If the event loop is already running.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
>>> async def example_coroutine():
|
|
27
|
+
... return 42
|
|
28
|
+
...
|
|
29
|
+
>>> result = _await(example_coroutine())
|
|
30
|
+
>>> print(result)
|
|
31
|
+
42
|
|
32
|
+
|
|
33
|
+
See Also:
|
|
34
|
+
- :func:`asyncio.run`: For running the main entry point of an asyncio program.
|
|
27
35
|
"""
|
|
28
36
|
try:
|
|
29
37
|
return a_sync.asyncio.get_event_loop().run_until_complete(awaitable)
|
|
@@ -32,27 +40,52 @@ def _await(awaitable: Awaitable[T]) -> T:
|
|
|
32
40
|
raise exceptions.SyncModeInAsyncContextError from None
|
|
33
41
|
raise
|
|
34
42
|
|
|
43
|
+
|
|
35
44
|
def _asyncify(func: SyncFn[P, T], executor: Executor) -> CoroFn[P, T]: # type: ignore [misc]
|
|
36
45
|
"""
|
|
37
|
-
Convert a synchronous function to a coroutine function.
|
|
46
|
+
Convert a synchronous function to a coroutine function using an executor.
|
|
38
47
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
This function submits the synchronous function to the provided executor and wraps
|
|
49
|
+
the resulting future in a coroutine function. This allows the synchronous function
|
|
50
|
+
to be executed asynchronously.
|
|
51
|
+
|
|
52
|
+
Note:
|
|
53
|
+
The function `_asyncify` uses `asyncio.futures.wrap_future` to wrap the future
|
|
54
|
+
returned by the executor, specifying the event loop with `a_sync.asyncio.get_event_loop()`.
|
|
55
|
+
Ensure that your environment supports this usage or adjust the import if necessary.
|
|
42
56
|
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
Args:
|
|
58
|
+
func (SyncFn[P, T]): The synchronous function to be converted.
|
|
59
|
+
executor (Executor): The executor used to run the synchronous function.
|
|
45
60
|
|
|
46
61
|
Raises:
|
|
47
|
-
|
|
62
|
+
exceptions.FunctionNotSync: If the input function is a coroutine function or an instance of :class:`~a_sync.a_sync.function.ASyncFunction`.
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
>>> from concurrent.futures import ThreadPoolExecutor
|
|
66
|
+
>>> def sync_function(x):
|
|
67
|
+
... return x * 2
|
|
68
|
+
...
|
|
69
|
+
>>> executor = ThreadPoolExecutor()
|
|
70
|
+
>>> async_function = _asyncify(sync_function, executor)
|
|
71
|
+
>>> result = await async_function(3)
|
|
72
|
+
>>> print(result)
|
|
73
|
+
6
|
|
74
|
+
|
|
75
|
+
See Also:
|
|
76
|
+
- :class:`concurrent.futures.Executor`: For managing pools of threads or processes.
|
|
77
|
+
- :func:`asyncio.to_thread`: For running blocking code in a separate thread.
|
|
48
78
|
"""
|
|
49
79
|
from a_sync.a_sync.function import ASyncFunction
|
|
80
|
+
|
|
50
81
|
if asyncio.iscoroutinefunction(func) or isinstance(func, ASyncFunction):
|
|
51
82
|
raise exceptions.FunctionNotSync(func)
|
|
83
|
+
|
|
52
84
|
@functools.wraps(func)
|
|
53
85
|
async def _asyncify_wrap(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
54
86
|
return await asyncio.futures.wrap_future(
|
|
55
|
-
executor.submit(func, *args, **kwargs),
|
|
87
|
+
executor.submit(func, *args, **kwargs),
|
|
56
88
|
loop=a_sync.asyncio.get_event_loop(),
|
|
57
89
|
)
|
|
90
|
+
|
|
58
91
|
return _asyncify_wrap
|