Thanks to visit codestin.com
Credit goes to github.com

Skip to content

⚡️ Reuse coroutine inspection result#14291

Closed
dolfinus wants to merge 1 commit into
fastapi:masterfrom
dolfinus:feature/reuse-inspect-result
Closed

⚡️ Reuse coroutine inspection result#14291
dolfinus wants to merge 1 commit into
fastapi:masterfrom
dolfinus:feature/reuse-inspect-result

Conversation

@dolfinus

@dolfinus dolfinus commented Nov 4, 2025

Copy link
Copy Markdown
Contributor

#14262 implemented caching of inspect module calls within Dependant class, but it left one clause outside, and it is called on every request.

Reusing inspect.iscoroutinefunction result reduces time spent in get_route_handler from 2.23 µs to 1.33 µs:

perf.py
import asyncio
import time
from fastapi import FastAPI, Depends, Header, Query
from fastapi.routing import APIRoute
from typing import Optional


# -----------------------
# Dependencies
# -----------------------

# Sync dependency
def get_token_header(x_token: str = Header(...)) -> str:
    if x_token != "secret-token":
        raise ValueError("Invalid token")
    return x_token


# Async dependency
async def get_query_param(q: Optional[str] = Query(None)) -> str:
    await asyncio.sleep(0.001)  # simulate small async delay
    return q or "default"


# Nested dependency (sync)
def get_user(token: str = Depends(get_token_header)) -> dict:
    return {"username": "test_user", "token": token}


# Nested dependency (async)
async def get_full_context(
    user: dict = Depends(get_user),
    query: str = Depends(get_query_param),
) -> dict:
    await asyncio.sleep(0.001)
    return {"user": user, "query": query}


# -----------------------
# App & route definition
# -----------------------

app = FastAPI()


@app.get("/items/{item_id}", dependencies=[Depends(get_token_header)])
async def read_item(
    item_id: int,
    context: dict = Depends(get_full_context),
) -> dict:
    return {"item_id": item_id, "context": context}


# -----------------------
# Performance test
# -----------------------

def test_get_route_handler_performance(loop_count: int = 50_000_000):
    route: APIRoute = app.routes[-1]
    assert route.path == '/items/{item_id}'

    start_time = time.perf_counter()

    for _ in range(loop_count):
        route.get_route_handler()

    duration = time.perf_counter() - start_time

    print(f"Called get_route_handler() {loop_count:,} times in {duration:.4f}s")
    print(f"Average per call: {duration / loop_count * 1e6:.2f} µs")


if __name__ == "__main__":
    test_get_route_handler_performance()

Before:
callable_cache_fastapi_0 121

After:
callable_cache_fastapi_0 121_half_patched

@YuriiMotov YuriiMotov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@dolfinus, thank you!

Before:

$ python perf.py 
Called get_route_handler() 50,000,000 times in 49.6951s
Average per call: 0.99 µs

After:

$ python perf.py 
Called get_route_handler() 50,000,000 times in 31.5395s
Average per call: 0.63 µs

@YuriiMotov YuriiMotov removed their assignment Nov 5, 2025
@github-actions github-actions Bot added the conflicts Automatically generated when a PR has a merge conflict label Dec 2, 2025
@github-actions

github-actions Bot commented Dec 2, 2025

Copy link
Copy Markdown
Contributor

This pull request has a merge conflict that needs to be resolved.

@dolfinus dolfinus closed this Dec 2, 2025
@dolfinus

dolfinus commented Dec 2, 2025

Copy link
Copy Markdown
Contributor Author

Implemented by #14434

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflicts Automatically generated when a PR has a merge conflict refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants