Easy Async
py_simple.easy_async
easy_async is built to simplify asynchronous code execution.
EasyAsyncError
Bases: Exception
Raised when a py_simple async function fails to complete.
Wraps the underlying error (an exception raised inside one of the functions being run, a thread pool failure, etc.) so py_simple functions can fail with one consistent, easy-to-read exception instead of a random builtin one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Human-readable description of what went wrong. |
required |
run_after_delay(func, delay, *args)
async
Waits asynchronously before running a function.
Raises EasyAsyncError if the function raises an exception after the delay finishes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
callable
|
The function to execute. |
required |
delay
|
float
|
Number of seconds to wait before running the function. |
required |
*args
|
Positional arguments to pass to the function. |
()
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple
|
A tuple containing |
Example
import asyncio
from py_simple import run_after_delay
def greet(name):
return f"Hello, {name}!"
async def main():
return await run_after_delay(greet, 1.0, "Sam")
asyncio.run(main()) # -> ("greet", "Hello, Sam!")
import asyncio
def greet(name):
return f"Hello, {name}!"
async def main():
await asyncio.sleep(1.0)
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, greet, "Sam")
return ("greet", result)
run_at_the_same_time_no_params(functions)
async
Runs multiple zero-argument functions at the same time and returns their results.
Raises EasyAsyncError if any function raises an exception while running.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
functions
|
list
|
Functions to run, each taking no arguments. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list
|
A list of (name, result) tuples, one per function. |
Example
import asyncio
from py_simple import run_at_the_same_time_no_params
def add():
return 1 + 1
def sub():
return 4 - 2
async def main():
return await run_at_the_same_time_no_params([add, sub])
asyncio.run(main()) # -> [("add", 2), ("sub", 2)]
import asyncio
def add():
return 1 + 1
def sub():
return 4 - 2
async def main():
loop = asyncio.get_running_loop()
functions = [add, sub]
tasks = [loop.run_in_executor(None, f) for f in functions]
results = await asyncio.gather(*tasks)
return list(zip((f.__name__ for f in functions), results))
asyncio.run(main())
run_at_the_same_time_with_params(functions_and_args)
async
Runs multiple functions at the same time, each with its own arguments, and returns their results.
Raises EasyAsyncError if any function raises an exception while running.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
functions_and_args
|
list[tuple]
|
Functions to run, each given as a tuple where the first item is the function and the remaining items are the positional arguments to call it with, e.g. (func, arg1, arg2). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list
|
A list of (name, result) tuples, one per function. |
Example
import asyncio
from py_simple import run_at_the_same_time_with_params
def add(a, b):
return a + b
def sub(a, b):
return a - b
async def main():
return await run_at_the_same_time_with_params([
(add, 1, 1),
(sub, 4, 2),
])
asyncio.run(main()) # -> [("add", 2), ("sub", 2)]
import asyncio
def add(a, b):
return a + b
def sub(a, b):
return a - b
async def main():
loop = asyncio.get_running_loop()
functions_and_args = [(add, 1, 1), (sub, 4, 2)]
tasks = [
loop.run_in_executor(None, item[0], *item[1:])
for item in functions_and_args
]
results = await asyncio.gather(*tasks)
names = [item[0].__name__ for item in functions_and_args]
return list(zip(names, results))
asyncio.run(main())
run_concurrent_map(func, items)
async
Applies a function to a list of items concurrently and returns the results in the original order.
Raises EasyAsyncError if any function call raises an exception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
callable
|
The function to call on each item. |
required |
items
|
list
|
The list of items to pass one by one into the function. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list
|
The list of return values in the same order as items. |
Example
import asyncio
from py_simple import run_concurrent_map
def square(n):
return n * n
async def main():
return await run_concurrent_map(square, [1, 2, 3, 4])
asyncio.run(main()) # -> [1, 4, 9, 16]
import asyncio
def square(n):
return n * n
async def main():
loop = asyncio.get_running_loop()
items = [1, 2, 3, 4]
tasks = [loop.run_in_executor(None, square, item) for item in items]
return await asyncio.gather(*tasks)
asyncio.run(main())
run_periodically(func, interval, times, *args)
async
Runs the same function repeatedly on a fixed schedule and returns the results in order.
Waits interval seconds before each run (including the first),
runs func in a thread pool so it never blocks the event loop, and
collects one result per run. Raises EasyAsyncError if any run raises
an exception while running.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
callable
|
The function to run, each time with the same positional arguments. |
required |
interval
|
float
|
Seconds to wait before each run, including the first one. Must be greater than 0. |
required |
times
|
int
|
How many times to run the function. Must be at least 1. |
required |
*args
|
Positional arguments to pass to func on every run. |
()
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
list
|
One result per run, in the order the runs finished, e.g. [4, 4, 4] for a function that returns 2 + 2. |
Raises:
| Type | Description |
|---|---|
EasyAsyncError
|
If a run raises an exception, or if interval or times are out of range. |
Example
import asyncio
from py_simple import run_periodically
def add():
return 2 + 2
async def main():
return await run_periodically(add, 1.0, 3)
asyncio.run(main()) # -> [4, 4, 4]
import asyncio
def add():
return 2 + 2
async def main():
loop = asyncio.get_running_loop()
results = []
for _ in range(3):
await asyncio.sleep(1.0)
results.append(await loop.run_in_executor(None, add))
return results
asyncio.run(main()) # -> [4, 4, 4]
run_with_retry(func, attempts, delay, *args)
async
Runs a function repeatedly until it succeeds or all attempts are
exhausted, waiting between attempts.
Raises EasyAsyncError if every attempt fails.
Args:
func (callable): The function to execute.
attempts (int): Number of attempts to make before giving up.
delay (float): Time to wait between failed attempts, in seconds.
*args: Positional arguments to pass to the function.
Returns:
tuple: A tuple containing `(func.__name__, result)`.
Example:
=== "The Py_simple Way"
```python
import asyncio
from py_simple import run_with_retry
def add(a, b):
return a + b
async def main():
return await run_with_retry(add, 4, 2.0, 2, 3)
asyncio.run(main()) # -> ("add", 5)
```
=== "The Traditional Way"
```python
import asyncio
def add(a, b):
return a + b
async def main():
loop = asyncio.get_running_loop()
attempts = 4
for attempt in range(attempts):
try:
result = await loop.run_in_executor(None, add, 2, 3)
return (add.__name__, result)
except Exception as e:
if attempt == attempts - 1:
raise EasyAsyncError(f"
ERROR: {e}") from None await asyncio.sleep(2.0)
asyncio.run(main())
```
run_with_timeout(func, timeout, *args)
async
Runs a function asynchronously with a timeout limit.
Raises EasyAsyncError if the function times out or raises an exception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
callable
|
The function to execute. |
required |
timeout
|
float
|
Maximum time to wait in seconds. |
required |
*args
|
Positional arguments to pass to the function. |
()
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple
|
A tuple containing |
Example
import asyncio
from py_simple import run_with_timeout
def slow_add(a, b):
return a + b
async def main():
return await run_with_timeout(slow_add, 2.0, 3, 5)
asyncio.run(main()) # -> ("slow_add", 8)
import asyncio
def slow_add(a, b):
return a + b
async def main():
loop = asyncio.get_running_loop()
result = await asyncio.wait_for(
loop.run_in_executor(None, slow_add, 3, 5),
timeout=2.0,
)
return (slow_add.__name__, result)
asyncio.run(main())