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.
- a_sync/ENVIRONMENT_VARIABLES.py +34 -3
- a_sync/__init__.py +32 -9
- a_sync/_smart.py +105 -6
- a_sync/_typing.py +56 -3
- a_sync/a_sync/_descriptor.py +174 -12
- a_sync/a_sync/_flags.py +64 -3
- a_sync/a_sync/_helpers.py +40 -8
- a_sync/a_sync/_kwargs.py +30 -6
- a_sync/a_sync/_meta.py +35 -6
- a_sync/a_sync/abstract.py +57 -9
- a_sync/a_sync/config.py +44 -7
- a_sync/a_sync/decorator.py +217 -37
- a_sync/a_sync/function.py +339 -47
- a_sync/a_sync/method.py +241 -52
- a_sync/a_sync/modifiers/__init__.py +39 -1
- a_sync/a_sync/modifiers/cache/__init__.py +75 -5
- a_sync/a_sync/modifiers/cache/memory.py +50 -6
- a_sync/a_sync/modifiers/limiter.py +55 -6
- a_sync/a_sync/modifiers/manager.py +46 -2
- a_sync/a_sync/modifiers/semaphores.py +84 -11
- a_sync/a_sync/singleton.py +43 -19
- a_sync/asyncio/__init__.py +137 -1
- a_sync/asyncio/as_completed.py +44 -38
- a_sync/asyncio/create_task.py +46 -10
- a_sync/asyncio/gather.py +72 -25
- a_sync/exceptions.py +178 -11
- a_sync/executor.py +51 -3
- a_sync/future.py +671 -29
- a_sync/iter.py +64 -7
- a_sync/primitives/_debug.py +59 -5
- a_sync/primitives/_loggable.py +36 -6
- a_sync/primitives/locks/counter.py +74 -7
- a_sync/primitives/locks/prio_semaphore.py +87 -8
- a_sync/primitives/locks/semaphore.py +68 -20
- a_sync/primitives/queue.py +65 -26
- a_sync/task.py +51 -15
- a_sync/utils/iterators.py +52 -16
- {ez_a_sync-0.22.15.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.15.dist-info → ez_a_sync-0.22.16.dist-info}/WHEEL +1 -1
- tests/executor.py +150 -12
- tests/test_abstract.py +15 -0
- tests/test_base.py +198 -2
- tests/test_executor.py +23 -0
- tests/test_singleton.py +13 -1
- tests/test_task.py +45 -17
- ez_a_sync-0.22.15.dist-info/RECORD +0 -74
- {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/LICENSE.txt +0 -0
- {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/top_level.txt +0 -0
a_sync/exceptions.py
CHANGED
|
@@ -14,16 +14,14 @@ if TYPE_CHECKING:
|
|
|
14
14
|
class ASyncFlagException(ValueError):
|
|
15
15
|
"""
|
|
16
16
|
Base exception class for flag-related errors in the a_sync library.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
viable_flags = VIABLE_FLAGS
|
|
20
|
-
"""
|
|
21
|
-
The set of viable flags.
|
|
22
17
|
|
|
23
|
-
A-Sync uses 'flags' to indicate whether objects
|
|
18
|
+
A-Sync uses 'flags' to indicate whether objects or function calls will be sync or async.
|
|
24
19
|
You can use any of the provided flags, whichever makes most sense for your use case.
|
|
25
20
|
"""
|
|
26
21
|
|
|
22
|
+
viable_flags = VIABLE_FLAGS
|
|
23
|
+
"""The set of viable flags."""
|
|
24
|
+
|
|
27
25
|
def desc(self, target) -> str:
|
|
28
26
|
"""
|
|
29
27
|
Returns a description of the target for the flag error message.
|
|
@@ -31,8 +29,13 @@ class ASyncFlagException(ValueError):
|
|
|
31
29
|
Args:
|
|
32
30
|
target: The target object or string to describe.
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
Examples:
|
|
33
|
+
>>> exception = ASyncFlagException()
|
|
34
|
+
>>> exception.desc("kwargs")
|
|
35
|
+
"flags present in 'kwargs'"
|
|
36
|
+
|
|
37
|
+
>>> exception.desc("some_target")
|
|
38
|
+
'flag attributes defined on some_target'
|
|
36
39
|
"""
|
|
37
40
|
if target == "kwargs":
|
|
38
41
|
return "flags present in 'kwargs'"
|
|
@@ -43,6 +46,15 @@ class ASyncFlagException(ValueError):
|
|
|
43
46
|
class NoFlagsFound(ASyncFlagException):
|
|
44
47
|
"""
|
|
45
48
|
Raised when no viable flags are found in the target.
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
>>> try:
|
|
52
|
+
... raise NoFlagsFound("some_target")
|
|
53
|
+
... except NoFlagsFound as e:
|
|
54
|
+
... print(e)
|
|
55
|
+
There are no viable a_sync flag attributes defined on some_target:
|
|
56
|
+
Viable flags: {'sync', 'asynchronous'}
|
|
57
|
+
This is likely an issue with a custom subclass definition.
|
|
46
58
|
"""
|
|
47
59
|
|
|
48
60
|
def __init__(self, target, kwargs_keys=None):
|
|
@@ -64,6 +76,15 @@ class NoFlagsFound(ASyncFlagException):
|
|
|
64
76
|
class TooManyFlags(ASyncFlagException):
|
|
65
77
|
"""
|
|
66
78
|
Raised when multiple flags are found, but only one was expected.
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
>>> try:
|
|
82
|
+
... raise TooManyFlags("some_target", ["flag1", "flag2"])
|
|
83
|
+
... except TooManyFlags as e:
|
|
84
|
+
... print(e)
|
|
85
|
+
There are multiple a_sync flag attributes defined on some_target and there should only be one.
|
|
86
|
+
Present flags: ['flag1', 'flag2']
|
|
87
|
+
This is likely an issue with a custom subclass definition.
|
|
67
88
|
"""
|
|
68
89
|
|
|
69
90
|
def __init__(self, target, present_flags):
|
|
@@ -73,8 +94,11 @@ class TooManyFlags(ASyncFlagException):
|
|
|
73
94
|
Args:
|
|
74
95
|
target: The target object where flags were found.
|
|
75
96
|
present_flags: The flags that were found.
|
|
97
|
+
|
|
98
|
+
See Also:
|
|
99
|
+
- :class:`ASyncFlagException`
|
|
76
100
|
"""
|
|
77
|
-
err = f"There are multiple a_sync {self.
|
|
101
|
+
err = f"There are multiple a_sync {self.desc(target)} and there should only be one.\n"
|
|
78
102
|
err += f"Present flags: {present_flags}\n"
|
|
79
103
|
err += "This is likely an issue with a custom subclass definition."
|
|
80
104
|
super().__init__(err)
|
|
@@ -83,6 +107,14 @@ class TooManyFlags(ASyncFlagException):
|
|
|
83
107
|
class InvalidFlag(ASyncFlagException):
|
|
84
108
|
"""
|
|
85
109
|
Raised when an invalid flag is encountered.
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
>>> try:
|
|
113
|
+
... raise InvalidFlag("invalid_flag")
|
|
114
|
+
... except InvalidFlag as e:
|
|
115
|
+
... print(e)
|
|
116
|
+
'flag' must be one of: {'sync', 'asynchronous'}. You passed invalid_flag.
|
|
117
|
+
This code should not be reached and likely indicates an issue with a custom subclass definition.
|
|
86
118
|
"""
|
|
87
119
|
|
|
88
120
|
def __init__(self, flag: Optional[str]):
|
|
@@ -91,6 +123,9 @@ class InvalidFlag(ASyncFlagException):
|
|
|
91
123
|
|
|
92
124
|
Args:
|
|
93
125
|
flag: The invalid flag.
|
|
126
|
+
|
|
127
|
+
See Also:
|
|
128
|
+
- :class:`ASyncFlagException`
|
|
94
129
|
"""
|
|
95
130
|
err = f"'flag' must be one of: {self.viable_flags}. You passed {flag}."
|
|
96
131
|
err += "\nThis code should not be reached and likely indicates an issue with a custom subclass definition."
|
|
@@ -100,6 +135,13 @@ class InvalidFlag(ASyncFlagException):
|
|
|
100
135
|
class InvalidFlagValue(ASyncFlagException):
|
|
101
136
|
"""
|
|
102
137
|
Raised when a flag has an invalid value.
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
>>> try:
|
|
141
|
+
... raise InvalidFlagValue("some_flag", "not_a_boolean")
|
|
142
|
+
... except InvalidFlagValue as e:
|
|
143
|
+
... print(e)
|
|
144
|
+
'some_flag' should be boolean. You passed not_a_boolean.
|
|
103
145
|
"""
|
|
104
146
|
|
|
105
147
|
def __init__(self, flag: str, flag_value: Any):
|
|
@@ -109,6 +151,9 @@ class InvalidFlagValue(ASyncFlagException):
|
|
|
109
151
|
Args:
|
|
110
152
|
flag: The flag with an invalid value.
|
|
111
153
|
flag_value: The invalid value of the flag.
|
|
154
|
+
|
|
155
|
+
See Also:
|
|
156
|
+
- :class:`ASyncFlagException`
|
|
112
157
|
"""
|
|
113
158
|
super().__init__(f"'{flag}' should be boolean. You passed {flag_value}.")
|
|
114
159
|
|
|
@@ -116,6 +161,16 @@ class InvalidFlagValue(ASyncFlagException):
|
|
|
116
161
|
class FlagNotDefined(ASyncFlagException):
|
|
117
162
|
"""
|
|
118
163
|
Raised when a flag is not defined on an object.
|
|
164
|
+
|
|
165
|
+
Examples:
|
|
166
|
+
>>> class SomeClass:
|
|
167
|
+
... pass
|
|
168
|
+
...
|
|
169
|
+
>>> try:
|
|
170
|
+
... raise FlagNotDefined(SomeClass, "some_flag")
|
|
171
|
+
... except FlagNotDefined as e:
|
|
172
|
+
... print(e)
|
|
173
|
+
<class '__main__.SomeClass'> flag some_flag is not defined.
|
|
119
174
|
"""
|
|
120
175
|
|
|
121
176
|
def __init__(self, obj: Type, flag: str):
|
|
@@ -125,6 +180,9 @@ class FlagNotDefined(ASyncFlagException):
|
|
|
125
180
|
Args:
|
|
126
181
|
obj: The object where the flag is not defined.
|
|
127
182
|
flag: The undefined flag.
|
|
183
|
+
|
|
184
|
+
See Also:
|
|
185
|
+
- :class:`ASyncFlagException`
|
|
128
186
|
"""
|
|
129
187
|
super().__init__(f"{obj} flag {flag} is not defined.")
|
|
130
188
|
|
|
@@ -132,12 +190,26 @@ class FlagNotDefined(ASyncFlagException):
|
|
|
132
190
|
class ImproperFunctionType(ValueError):
|
|
133
191
|
"""
|
|
134
192
|
Raised when a function that should be sync is async or vice-versa.
|
|
193
|
+
|
|
194
|
+
See Also:
|
|
195
|
+
- :class:`FunctionNotAsync`
|
|
196
|
+
- :class:`FunctionNotSync`
|
|
135
197
|
"""
|
|
136
198
|
|
|
137
199
|
|
|
138
200
|
class FunctionNotAsync(ImproperFunctionType):
|
|
139
201
|
"""
|
|
140
202
|
Raised when a function expected to be async is not.
|
|
203
|
+
|
|
204
|
+
Examples:
|
|
205
|
+
>>> def some_function():
|
|
206
|
+
... pass
|
|
207
|
+
...
|
|
208
|
+
>>> try:
|
|
209
|
+
... raise FunctionNotAsync(some_function)
|
|
210
|
+
... except FunctionNotAsync as e:
|
|
211
|
+
... print(e)
|
|
212
|
+
`coro_fn` must be a coroutine function defined with `async def`. You passed <function some_function at 0x...>.
|
|
141
213
|
"""
|
|
142
214
|
|
|
143
215
|
def __init__(self, fn):
|
|
@@ -146,6 +218,9 @@ class FunctionNotAsync(ImproperFunctionType):
|
|
|
146
218
|
|
|
147
219
|
Args:
|
|
148
220
|
fn: The function that is not async.
|
|
221
|
+
|
|
222
|
+
See Also:
|
|
223
|
+
- :class:`ImproperFunctionType`
|
|
149
224
|
"""
|
|
150
225
|
super().__init__(
|
|
151
226
|
f"`coro_fn` must be a coroutine function defined with `async def`. You passed {fn}."
|
|
@@ -154,7 +229,17 @@ class FunctionNotAsync(ImproperFunctionType):
|
|
|
154
229
|
|
|
155
230
|
class FunctionNotSync(ImproperFunctionType):
|
|
156
231
|
"""
|
|
157
|
-
Raised when a function expected to be sync is
|
|
232
|
+
Raised when a function expected to be sync is actually async.
|
|
233
|
+
|
|
234
|
+
Examples:
|
|
235
|
+
>>> async def some_async_function():
|
|
236
|
+
... pass
|
|
237
|
+
...
|
|
238
|
+
>>> try:
|
|
239
|
+
... raise FunctionNotSync(some_async_function)
|
|
240
|
+
... except FunctionNotSync as e:
|
|
241
|
+
... print(e)
|
|
242
|
+
`func` must be a coroutine function defined with `def`. You passed <function some_async_function at 0x...>.
|
|
158
243
|
"""
|
|
159
244
|
|
|
160
245
|
def __init__(self, fn):
|
|
@@ -163,6 +248,9 @@ class FunctionNotSync(ImproperFunctionType):
|
|
|
163
248
|
|
|
164
249
|
Args:
|
|
165
250
|
fn: The function that is not sync.
|
|
251
|
+
|
|
252
|
+
See Also:
|
|
253
|
+
- :class:`ImproperFunctionType`
|
|
166
254
|
"""
|
|
167
255
|
super().__init__(
|
|
168
256
|
f"`func` must be a coroutine function defined with `def`. You passed {fn}."
|
|
@@ -172,6 +260,13 @@ class FunctionNotSync(ImproperFunctionType):
|
|
|
172
260
|
class ASyncRuntimeError(RuntimeError):
|
|
173
261
|
"""
|
|
174
262
|
Raised for runtime errors in asynchronous operations.
|
|
263
|
+
|
|
264
|
+
Examples:
|
|
265
|
+
>>> try:
|
|
266
|
+
... raise ASyncRuntimeError(RuntimeError("Some runtime error"))
|
|
267
|
+
... except ASyncRuntimeError as e:
|
|
268
|
+
... print(e)
|
|
269
|
+
Some runtime error
|
|
175
270
|
"""
|
|
176
271
|
|
|
177
272
|
def __init__(self, e: RuntimeError):
|
|
@@ -180,6 +275,9 @@ class ASyncRuntimeError(RuntimeError):
|
|
|
180
275
|
|
|
181
276
|
Args:
|
|
182
277
|
e: The original runtime error.
|
|
278
|
+
|
|
279
|
+
See Also:
|
|
280
|
+
- :class:`RuntimeError`
|
|
183
281
|
"""
|
|
184
282
|
super().__init__(str(e))
|
|
185
283
|
|
|
@@ -187,11 +285,23 @@ class ASyncRuntimeError(RuntimeError):
|
|
|
187
285
|
class SyncModeInAsyncContextError(ASyncRuntimeError):
|
|
188
286
|
"""
|
|
189
287
|
Raised when synchronous code is used within an asynchronous context.
|
|
288
|
+
|
|
289
|
+
Examples:
|
|
290
|
+
>>> try:
|
|
291
|
+
... raise SyncModeInAsyncContextError()
|
|
292
|
+
... except SyncModeInAsyncContextError as e:
|
|
293
|
+
... print(e)
|
|
294
|
+
The event loop is already running, which means you're trying to use an `ASyncFunction` synchronously from within an async context.
|
|
295
|
+
Check your traceback to determine which, then try calling asynchronously instead with one of the following kwargs:
|
|
296
|
+
{'sync', 'asynchronous'}
|
|
190
297
|
"""
|
|
191
298
|
|
|
192
299
|
def __init__(self, err: str = ""):
|
|
193
300
|
"""
|
|
194
301
|
Initializes the SyncModeInAsyncContextError exception.
|
|
302
|
+
|
|
303
|
+
See Also:
|
|
304
|
+
- :class:`ASyncRuntimeError`
|
|
195
305
|
"""
|
|
196
306
|
if not err:
|
|
197
307
|
err = "The event loop is already running, which means you're trying to use an `ASyncFunction` synchronously from within an async context.\n"
|
|
@@ -203,6 +313,16 @@ class SyncModeInAsyncContextError(ASyncRuntimeError):
|
|
|
203
313
|
class MappingError(Exception):
|
|
204
314
|
"""
|
|
205
315
|
Base class for errors related to :class:`~TaskMapping`.
|
|
316
|
+
|
|
317
|
+
Examples:
|
|
318
|
+
>>> from a_sync import TaskMapping
|
|
319
|
+
>>> try:
|
|
320
|
+
... raise MappingError(TaskMapping(), "Some mapping error")
|
|
321
|
+
... except MappingError as e:
|
|
322
|
+
... print(e)
|
|
323
|
+
Some mapping error:
|
|
324
|
+
<TaskMapping object at 0x...>
|
|
325
|
+
{}
|
|
206
326
|
"""
|
|
207
327
|
|
|
208
328
|
_msg: str
|
|
@@ -214,8 +334,11 @@ class MappingError(Exception):
|
|
|
214
334
|
Args:
|
|
215
335
|
mapping: The TaskMapping where the error occurred.
|
|
216
336
|
msg: An optional message describing the error.
|
|
337
|
+
|
|
338
|
+
See Also:
|
|
339
|
+
- :class:`TaskMapping`
|
|
217
340
|
"""
|
|
218
|
-
msg = msg or self._msg + f":\n{mapping}"
|
|
341
|
+
msg = (msg or self._msg) + f":\n{mapping}"
|
|
219
342
|
if mapping:
|
|
220
343
|
msg += f"\n{dict(mapping)}"
|
|
221
344
|
super().__init__(msg)
|
|
@@ -225,6 +348,16 @@ class MappingError(Exception):
|
|
|
225
348
|
class MappingIsEmptyError(MappingError):
|
|
226
349
|
"""
|
|
227
350
|
Raised when a TaskMapping is empty and an operation requires it to have items.
|
|
351
|
+
|
|
352
|
+
Examples:
|
|
353
|
+
>>> from a_sync import TaskMapping
|
|
354
|
+
>>> try:
|
|
355
|
+
... raise MappingIsEmptyError(TaskMapping())
|
|
356
|
+
... except MappingIsEmptyError as e:
|
|
357
|
+
... print(e)
|
|
358
|
+
TaskMapping does not contain anything to yield:
|
|
359
|
+
<TaskMapping object at 0x...>
|
|
360
|
+
{}
|
|
228
361
|
"""
|
|
229
362
|
|
|
230
363
|
_msg = "TaskMapping does not contain anything to yield"
|
|
@@ -233,6 +366,18 @@ class MappingIsEmptyError(MappingError):
|
|
|
233
366
|
class MappingNotEmptyError(MappingError):
|
|
234
367
|
"""
|
|
235
368
|
Raised when a TaskMapping is not empty and an operation requires it to be empty.
|
|
369
|
+
|
|
370
|
+
Examples:
|
|
371
|
+
>>> from a_sync import TaskMapping
|
|
372
|
+
>>> task_mapping = TaskMapping()
|
|
373
|
+
>>> task_mapping['key'] = 'value'
|
|
374
|
+
>>> try:
|
|
375
|
+
... raise MappingNotEmptyError(task_mapping)
|
|
376
|
+
... except MappingNotEmptyError as e:
|
|
377
|
+
... print(e)
|
|
378
|
+
TaskMapping already contains some data. In order to use `map`, you need a fresh one:
|
|
379
|
+
<TaskMapping object at 0x...>
|
|
380
|
+
{'key': 'value'}
|
|
236
381
|
"""
|
|
237
382
|
|
|
238
383
|
_msg = "TaskMapping already contains some data. In order to use `map`, you need a fresh one"
|
|
@@ -241,6 +386,18 @@ class MappingNotEmptyError(MappingError):
|
|
|
241
386
|
class PersistedTaskException(Exception):
|
|
242
387
|
"""
|
|
243
388
|
Raised when an exception persists in an asyncio Task.
|
|
389
|
+
|
|
390
|
+
Examples:
|
|
391
|
+
>>> import asyncio
|
|
392
|
+
>>> async def some_task():
|
|
393
|
+
... raise ValueError("Some error")
|
|
394
|
+
...
|
|
395
|
+
>>> task = asyncio.create_task(some_task())
|
|
396
|
+
>>> try:
|
|
397
|
+
... raise PersistedTaskException(ValueError("Some error"), task)
|
|
398
|
+
... except PersistedTaskException as e:
|
|
399
|
+
... print(e)
|
|
400
|
+
ValueError: Some error
|
|
244
401
|
"""
|
|
245
402
|
|
|
246
403
|
def __init__(self, exc: E, task: asyncio.Task) -> None:
|
|
@@ -250,6 +407,9 @@ class PersistedTaskException(Exception):
|
|
|
250
407
|
Args:
|
|
251
408
|
exc: The exception that persisted.
|
|
252
409
|
task: The asyncio Task where the exception occurred.
|
|
410
|
+
|
|
411
|
+
See Also:
|
|
412
|
+
- :class:`asyncio.Task`
|
|
253
413
|
"""
|
|
254
414
|
super().__init__(f"{exc.__class__.__name__}: {exc}", task)
|
|
255
415
|
self.exception = exc
|
|
@@ -259,4 +419,11 @@ class PersistedTaskException(Exception):
|
|
|
259
419
|
class EmptySequenceError(ValueError):
|
|
260
420
|
"""
|
|
261
421
|
Raised when an operation is attempted on an empty sequence but items are required.
|
|
422
|
+
|
|
423
|
+
Examples:
|
|
424
|
+
>>> try:
|
|
425
|
+
... raise EmptySequenceError("Sequence is empty")
|
|
426
|
+
... except EmptySequenceError as e:
|
|
427
|
+
... print(e)
|
|
428
|
+
Sequence is empty
|
|
262
429
|
"""
|
a_sync/executor.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
With these executors, you can simply run sync functions in your executor with `await executor.run(fn, *args)`.
|
|
3
3
|
|
|
4
|
-
`executor.submit(fn, *args)` will work the same as the concurrent.futures implementation, but will return an asyncio.Future instead of a concurrent.futures.Future
|
|
4
|
+
`executor.submit(fn, *args)` will work the same as the `concurrent.futures` implementation, but will return an `asyncio.Future` instead of a `concurrent.futures.Future`.
|
|
5
5
|
|
|
6
6
|
This module provides several executor classes:
|
|
7
7
|
- _AsyncExecutorMixin: A mixin providing asynchronous run and submit methods, with support for synchronous mode.
|
|
@@ -45,12 +45,21 @@ class _AsyncExecutorMixin(cf.Executor, _DebugDaemonMixin):
|
|
|
45
45
|
A shorthand way to call `await asyncio.get_event_loop().run_in_executor(this_executor, fn, *args)`.
|
|
46
46
|
Doesn't `await this_executor.run(fn, *args)` look so much better?
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
In synchronous mode, the function is executed directly in the current thread.
|
|
49
|
+
In asynchronous mode, the function is submitted to the executor and awaited.
|
|
49
50
|
|
|
50
51
|
Args:
|
|
51
52
|
fn: The function to run.
|
|
52
53
|
*args: Positional arguments for the function.
|
|
53
54
|
**kwargs: Keyword arguments for the function.
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
>>> async def example():
|
|
58
|
+
>>> result = await executor.run(some_function, arg1, arg2, kwarg1=value1)
|
|
59
|
+
>>> print(result)
|
|
60
|
+
|
|
61
|
+
See Also:
|
|
62
|
+
- :meth:`submit` for submitting functions to the executor.
|
|
54
63
|
"""
|
|
55
64
|
return (
|
|
56
65
|
fn(*args, **kwargs)
|
|
@@ -60,12 +69,20 @@ class _AsyncExecutorMixin(cf.Executor, _DebugDaemonMixin):
|
|
|
60
69
|
|
|
61
70
|
def submit(self, fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> "asyncio.Future[T]": # type: ignore [override]
|
|
62
71
|
"""
|
|
63
|
-
Submits a job to the executor and returns an asyncio.Future that can be awaited for the result without blocking.
|
|
72
|
+
Submits a job to the executor and returns an `asyncio.Future` that can be awaited for the result without blocking.
|
|
64
73
|
|
|
65
74
|
Args:
|
|
66
75
|
fn: The function to submit.
|
|
67
76
|
*args: Positional arguments for the function.
|
|
68
77
|
**kwargs: Keyword arguments for the function.
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
>>> future = executor.submit(some_function, arg1, arg2, kwarg1=value1)
|
|
81
|
+
>>> result = await future
|
|
82
|
+
>>> print(result)
|
|
83
|
+
|
|
84
|
+
See Also:
|
|
85
|
+
- :meth:`run` for running functions with the executor.
|
|
69
86
|
"""
|
|
70
87
|
if self.sync_mode:
|
|
71
88
|
fut = asyncio.get_event_loop().create_future()
|
|
@@ -89,6 +106,10 @@ class _AsyncExecutorMixin(cf.Executor, _DebugDaemonMixin):
|
|
|
89
106
|
def sync_mode(self) -> bool:
|
|
90
107
|
"""
|
|
91
108
|
Indicates if the executor is in synchronous mode (max_workers == 0).
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
>>> if executor.sync_mode:
|
|
112
|
+
>>> print("Executor is in synchronous mode.")
|
|
92
113
|
"""
|
|
93
114
|
return self._max_workers == 0
|
|
94
115
|
|
|
@@ -96,6 +117,9 @@ class _AsyncExecutorMixin(cf.Executor, _DebugDaemonMixin):
|
|
|
96
117
|
def worker_count_current(self) -> int:
|
|
97
118
|
"""
|
|
98
119
|
Returns the current number of workers.
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
>>> print(f"Current worker count: {executor.worker_count_current}")
|
|
99
123
|
"""
|
|
100
124
|
return len(getattr(self, f"_{self._workers}"))
|
|
101
125
|
|
|
@@ -108,6 +132,9 @@ class _AsyncExecutorMixin(cf.Executor, _DebugDaemonMixin):
|
|
|
108
132
|
fn: The function being executed.
|
|
109
133
|
*args: Positional arguments for the function.
|
|
110
134
|
**kwargs: Keyword arguments for the function.
|
|
135
|
+
|
|
136
|
+
See Also:
|
|
137
|
+
- :meth:`_start_debug_daemon` to start the debug daemon.
|
|
111
138
|
"""
|
|
112
139
|
# TODO: make prettier strings for other types
|
|
113
140
|
if type(fn).__name__ == "function":
|
|
@@ -171,6 +198,11 @@ class AsyncProcessPoolExecutor(_AsyncExecutorMixin, cf.ProcessPoolExecutor):
|
|
|
171
198
|
mp_context: The multiprocessing context. Defaults to None.
|
|
172
199
|
initializer: An initializer callable. Defaults to None.
|
|
173
200
|
initargs: Arguments for the initializer. Defaults to ().
|
|
201
|
+
|
|
202
|
+
Examples:
|
|
203
|
+
>>> executor = AsyncProcessPoolExecutor(max_workers=4)
|
|
204
|
+
>>> future = executor.submit(some_function, arg1, arg2)
|
|
205
|
+
>>> result = await future
|
|
174
206
|
"""
|
|
175
207
|
if max_workers == 0:
|
|
176
208
|
super().__init__(1, mp_context, initializer, initargs)
|
|
@@ -213,6 +245,11 @@ class AsyncThreadPoolExecutor(_AsyncExecutorMixin, cf.ThreadPoolExecutor):
|
|
|
213
245
|
thread_name_prefix: Prefix for thread names. Defaults to ''.
|
|
214
246
|
initializer: An initializer callable. Defaults to None.
|
|
215
247
|
initargs: Arguments for the initializer. Defaults to ().
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
>>> executor = AsyncThreadPoolExecutor(max_workers=10, thread_name_prefix="MyThread")
|
|
251
|
+
>>> future = executor.submit(some_function, arg1, arg2)
|
|
252
|
+
>>> result = await future
|
|
216
253
|
"""
|
|
217
254
|
if max_workers == 0:
|
|
218
255
|
super().__init__(1, thread_name_prefix, initializer, initargs)
|
|
@@ -240,6 +277,9 @@ def _worker(
|
|
|
240
277
|
initializer: The initializer function.
|
|
241
278
|
initargs: Arguments for the initializer.
|
|
242
279
|
timeout: Timeout duration for pruning inactive threads.
|
|
280
|
+
|
|
281
|
+
See Also:
|
|
282
|
+
- :class:`PruningThreadPoolExecutor` for more details on thread pruning.
|
|
243
283
|
"""
|
|
244
284
|
if initializer is not None:
|
|
245
285
|
try:
|
|
@@ -326,6 +366,11 @@ class PruningThreadPoolExecutor(AsyncThreadPoolExecutor):
|
|
|
326
366
|
initializer: An initializer callable. Defaults to None.
|
|
327
367
|
initargs: Arguments for the initializer. Defaults to ().
|
|
328
368
|
timeout: Timeout duration for pruning inactive threads. Defaults to TEN_MINUTES.
|
|
369
|
+
|
|
370
|
+
Examples:
|
|
371
|
+
>>> executor = PruningThreadPoolExecutor(max_workers=5, timeout=300)
|
|
372
|
+
>>> future = executor.submit(some_function, arg1, arg2)
|
|
373
|
+
>>> result = await future
|
|
329
374
|
"""
|
|
330
375
|
|
|
331
376
|
self._timeout = timeout
|
|
@@ -342,6 +387,9 @@ class PruningThreadPoolExecutor(AsyncThreadPoolExecutor):
|
|
|
342
387
|
def _adjust_thread_count(self):
|
|
343
388
|
"""
|
|
344
389
|
Adjusts the number of threads based on workload and idle threads.
|
|
390
|
+
|
|
391
|
+
See Also:
|
|
392
|
+
- :func:`_worker` for the worker function that handles thread pruning.
|
|
345
393
|
"""
|
|
346
394
|
with self._adjusting_lock:
|
|
347
395
|
# if idle threads are available, don't spin new threads
|