Async Property Decorator
The `async-property` library provides Python decorators (`@async_property`, `@async_cached_property`) that enable defining asynchronous properties in classes. It allows getter methods to be coroutines, which can then be awaited when accessed. It also includes `AwaitLoader` for concurrently loading multiple cached async properties. The current version is 0.2.2, with a release cadence that appears maintenance-driven, having had releases in 2019 and 2023.
Warnings
- gotcha Async properties do not support asynchronous setters. Python's `property` mechanism does not natively support async setters, and `async-property` only addresses async getters.
- gotcha Forgetting to `await` the async property will result in getting a coroutine object instead of the property's computed value. This is a common pitfall in `asyncio` applications.
- gotcha While `async-property` enables awaitable properties, the general Python community often advises against computationally expensive or I/O-bound operations in properties, as property access is semantically expected to be cheap and fast. Overusing async properties for heavy tasks might lead to less readable code or mask performance bottlenecks if not carefully designed.
- breaking Python 3.14 includes significant changes to `asyncio`'s internal implementation, particularly affecting nested event loops and workarounds like `nest_asyncio`. While `async-property` itself doesn't use `nest_asyncio`, issues related to `asyncio.run()` in environments like Jupyter notebooks on Python 3.14+ can indirectly affect how async properties are tested or used in such contexts.
Install
-
pip install async-property
Imports
- async_property
from async_property import async_property
- async_cached_property
from async_property import async_cached_property
- AwaitLoader
from async_property import AwaitLoader
Quickstart
import asyncio
from async_property import async_property, async_cached_property, AwaitLoader
async def fetch_data(delay: float, value: str):
await asyncio.sleep(delay)
return value
class MyClass:
def __init__(self, initial_value: str):
self._initial_value = initial_value
@async_property
async def regular_async_prop(self) -> str:
print("Fetching regular_async_prop...")
return await fetch_data(0.1, f"Regular: {self._initial_value}")
@async_cached_property
async def cached_async_prop(self) -> str:
print("Fetching cached_async_prop (only once)...")
return await fetch_data(0.2, f"Cached: {self._initial_value}")
class MyAwaitableClass(AwaitLoader):
def __init__(self, initial_value: str):
self._initial_value = initial_value
async def load(self):
print("AwaitLoader load method called.")
@async_cached_property
async def await_loader_prop1(self) -> str:
print("Fetching await_loader_prop1...")
return await fetch_data(0.05, f"AwaitLoader Prop1: {self._initial_value}")
@async_cached_property
async def await_loader_prop2(self) -> str:
print("Fetching await_loader_prop2...")
return await fetch_data(0.03, f"AwaitLoader Prop2: {self._initial_value}")
async def main():
instance = MyClass("test")
# Accessing regular async property
print(f"-> {await instance.regular_async_prop}")
print(f"-> {await instance.regular_async_prop}") # Fetches again
# Accessing cached async property
print(f"-> {await instance.cached_async_prop}")
print(f"-> {await instance.cached_async_prop}") # Returns cached value
# Using AwaitLoader
awaitable_instance = await MyAwaitableClass("loader_data")
print(f"-> {awaitable_instance.await_loader_prop1}")
print(f"-> {awaitable_instance.await_loader_prop2}")
if __name__ == "__main__":
asyncio.run(main())