This rule raises an issue when a function is declared async but does not use any asynchronous features.
Declaring a function with async def signals that it’s a coroutine, typically because it needs to use await for
non-blocking operations (like I/O), or employs other asynchronous features like async for or async with.
If none of these asynchronous mechanisms are utilized within the function’s body, the async declaration is superfluous.
Using async unnecessarily can:
async def function creates a coroutine object. While modern Python is
efficient, this still incurs more overhead than a direct synchronous function call if the asynchronicity isn’t actually leveraged. The function’s
body will execute synchronously until an await (if any) is encountered. If there’s no await or other async feature, it
essentially runs synchronously but wrapped as a coroutine.await (or schedule it as a task) to execute the coroutine and get its result.
This is an unnecessary ceremony if the function itself isn’t truly asynchronous.Either remove the async keyword, or start using the appropriate asynchronous features.
An async method may also need to remain awaitable to fulfil an inherited contract. In that case, make the contract explicit in the code, for example by inheriting from the relevant base class or protocol and marking the method as an override.
async def my_function(): # Noncompliant
print("Hello from my function")
def my_function(): # Compliant
print("Hello from my function")
def another_function(): ...
async def my_function(): # Noncompliant
return another_function()
async def another_function(): ...
async def my_function(): # Compliant
return await another_function()
This rule does not raise an issue in the following cases:
@abstractmethod or @abc.abstractmethod are often async to
signal an async contract for subclasses, even if the abstract body itself is empty.__aenter__, __aexit__, or __anext__ form part of
the async protocol interface and must be async regardless of their body.async keyword meaningful
(e.g., framework route handlers).pass, …, or raise statements are
considered stubs or placeholders and are not flagged.await. This rule recognizes the following cases:
httpx.AsyncClient event hooks registered under the "request" or "response" keys of the
event_hooks parameter.confluent_kafka.aio.AIOConsumer.subscribe callbacks passed as on_assign, on_revoke, or
on_lost keyword arguments.
import httpx
async def log_request(request: httpx.Request) -> None: # Compliant - required coroutine callback
print(f"Request: {request.method} {request.url}")
client = httpx.AsyncClient(event_hooks={"request": [log_request]})
from confluent_kafka.aio import AIOConsumer
async def on_assign(consumer, partitions) -> None: # Compliant - required coroutine callback
print(f"Assigned: {partitions}")
consumer = AIOConsumer({"bootstrap.servers": "localhost:9092", "group.id": "mygroup"})
await consumer.subscribe(["topic"], on_assign=on_assign)