ez-a-sync 0.22.15__py3-none-any.whl → 0.22.16__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ez-a-sync might be problematic. Click here for more details.

Files changed (49) hide show
  1. a_sync/ENVIRONMENT_VARIABLES.py +34 -3
  2. a_sync/__init__.py +32 -9
  3. a_sync/_smart.py +105 -6
  4. a_sync/_typing.py +56 -3
  5. a_sync/a_sync/_descriptor.py +174 -12
  6. a_sync/a_sync/_flags.py +64 -3
  7. a_sync/a_sync/_helpers.py +40 -8
  8. a_sync/a_sync/_kwargs.py +30 -6
  9. a_sync/a_sync/_meta.py +35 -6
  10. a_sync/a_sync/abstract.py +57 -9
  11. a_sync/a_sync/config.py +44 -7
  12. a_sync/a_sync/decorator.py +217 -37
  13. a_sync/a_sync/function.py +339 -47
  14. a_sync/a_sync/method.py +241 -52
  15. a_sync/a_sync/modifiers/__init__.py +39 -1
  16. a_sync/a_sync/modifiers/cache/__init__.py +75 -5
  17. a_sync/a_sync/modifiers/cache/memory.py +50 -6
  18. a_sync/a_sync/modifiers/limiter.py +55 -6
  19. a_sync/a_sync/modifiers/manager.py +46 -2
  20. a_sync/a_sync/modifiers/semaphores.py +84 -11
  21. a_sync/a_sync/singleton.py +43 -19
  22. a_sync/asyncio/__init__.py +137 -1
  23. a_sync/asyncio/as_completed.py +44 -38
  24. a_sync/asyncio/create_task.py +46 -10
  25. a_sync/asyncio/gather.py +72 -25
  26. a_sync/exceptions.py +178 -11
  27. a_sync/executor.py +51 -3
  28. a_sync/future.py +671 -29
  29. a_sync/iter.py +64 -7
  30. a_sync/primitives/_debug.py +59 -5
  31. a_sync/primitives/_loggable.py +36 -6
  32. a_sync/primitives/locks/counter.py +74 -7
  33. a_sync/primitives/locks/prio_semaphore.py +87 -8
  34. a_sync/primitives/locks/semaphore.py +68 -20
  35. a_sync/primitives/queue.py +65 -26
  36. a_sync/task.py +51 -15
  37. a_sync/utils/iterators.py +52 -16
  38. {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/METADATA +1 -1
  39. ez_a_sync-0.22.16.dist-info/RECORD +74 -0
  40. {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/WHEEL +1 -1
  41. tests/executor.py +150 -12
  42. tests/test_abstract.py +15 -0
  43. tests/test_base.py +198 -2
  44. tests/test_executor.py +23 -0
  45. tests/test_singleton.py +13 -1
  46. tests/test_task.py +45 -17
  47. ez_a_sync-0.22.15.dist-info/RECORD +0 -74
  48. {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/LICENSE.txt +0 -0
  49. {ez_a_sync-0.22.15.dist-info → ez_a_sync-0.22.16.dist-info}/top_level.txt +0 -0
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 / fn calls will be sync or async.
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
- Returns:
35
- A string description of the target.
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.__get_desc(target)} and there should only be one.\n"
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 not.
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
- Oh, and you can also use kwargs!
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