This rule raises an issue when an asynchronous function accepts a timeout parameter instead of using the timeout context managers.

Why is this an issue?

Modern asynchronous Python libraries like asyncio, anyio, and trio promote a principle called structured concurrency. A key aspect of this is that the caller of an asynchronous function should be responsible for managing timeouts and cancellation, not the callee.

When an async function accepts a timeout parameter, it violates this principle:

  1. Coupling between logic and timeout handling: The function dictates how the timeout is handled internally, rather than letting the caller decide.
  2. Preempting caller control: The caller might want to enforce a different timeout duration or coordinate timeouts across several concurrent operations. An internal timeout parameter makes this difficult or impossible.
  3. Reducing composability: Combining functions that manage their own timeouts can lead to complex and unpredictable behavior, especially when nesting calls or running tasks concurrently under a shared deadline.

Instead, the caller should use the timeout features provided by the concurrency library (e.g., async with asyncio.timeout() or with trio.move_on_after()). This separates the concern of what the function does from how long the caller is willing to wait for it.

How to fix it in Asyncio

Code examples

Noncompliant code example

import asyncio

async def example_function(timeout): # Noncompliant
    await asyncio.sleep(timeout)

async def main():
    await example_function(5)

Compliant solution

import asyncio

async def example_function():
    await asyncio.sleep(5)

async def main():
    async with asyncio.timeout(5): # Compliant
        await example_function()

How to fix it in Trio

Code examples

Noncompliant code example

import trio

async def example_function(timeout): # Noncompliant
    await trio.sleep(timeout)

async def main():
    await example_function(5)

trio.run(main)

Compliant solution

import trio

async def example_function():
    await trio.sleep(5)

async def main():
    with trio.move_on_after(5): # Compliant
        await example_function()

trio.run(main)

How to fix it in AnyIO

Code examples

Noncompliant code example

import anyio

async def example_function(timeout): # Noncompliant
    await anyio.sleep(timeout)

async def main():
    await example_function(5)

anyio.run(main)

Compliant solution

import anyio

async def example_function():
    await anyio.sleep(5)

async def main():
    with anyio.move_on_after(5): # Compliant
        await example_function()

anyio.run(main)

Resources

Documentation