Skip to content

⚡️ Speed up function update_openapi by 45% - #17

Open
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-update_openapi-miforihg
Open

⚡️ Speed up function update_openapi by 45%#17
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-update_openapi-miforihg

Conversation

@codeflash-ai

@codeflash-ai codeflash-ai Bot commented Nov 26, 2025

Copy link
Copy Markdown

📄 45% (0.45x) speedup for update_openapi in src/titiler/core/titiler/core/utils.py

⏱️ Runtime : 95.0 microseconds 65.3 microseconds (best of 8 runs)

📝 Explanation and details

The optimization replaces a generator expression with next() with a simple for-loop for finding the OpenAPI route, achieving a 45% speedup (95.0μs → 65.3μs).

Key optimizations:

  • Eliminated generator overhead: The original next(route for route in app.router.routes if route.path == app.openapi_url) creates a generator object and uses the next() builtin, which has function call overhead
  • Direct iteration with early exit: The optimized version uses a plain for-loop that breaks immediately when the matching route is found, avoiding generator allocation and next() function calls
  • Cached attribute access: Stores app.openapi_url in a local variable to avoid repeated attribute lookups during iteration

Performance impact analysis:
Based on the line profiler results, the route lookup overhead dropped significantly - the original generator expression took 33.6% of total execution time (153,430ns), while the optimized for-loop approach distributes this cost across multiple lighter operations totaling much less time.

Test case effectiveness:
The optimization performs consistently well across all test scenarios:

  • Basic cases: 44-51% speedup for typical usage patterns
  • Edge cases: 28-50% improvement even when handling missing routes or custom configurations
  • Large scale: 25-66% speedup with many routes (up to 999), showing the optimization scales well as route count increases

This optimization is particularly valuable since update_openapi() is typically called during FastAPI application initialization, where even small improvements in startup time can be beneficial for serverless deployments or frequent application restarts.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 30 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
from __future__ import annotations

# imports
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route, request_response
from titiler.core.utils import update_openapi

# unit tests

# --- Basic Test Cases ---

def test_openapi_content_type_basic():
    """
    Basic test: After calling update_openapi, the /openapi.json endpoint should
    return the correct content-type header, and the OpenAPI schema should be valid JSON.
    """
    app = FastAPI()
    update_openapi(app) # 3.15μs -> 2.18μs (44.4% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)

def test_openapi_content_type_with_custom_openapi_url():
    """
    Basic test: The function should work with a custom OpenAPI URL.
    """
    app = FastAPI(openapi_url="/custom_openapi.json")
    update_openapi(app) # 3.21μs -> 2.13μs (50.9% faster)
    client = TestClient(app)
    response = client.get("/custom_openapi.json")

def test_openapi_content_type_multiple_calls():
    """
    Basic test: Calling update_openapi multiple times should not break the app.
    """
    app = FastAPI()
    update_openapi(app) # 3.11μs -> 2.07μs (50.2% faster)
    update_openapi(app) # 1.65μs -> 1.21μs (36.7% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)

# --- Edge Test Cases ---

def test_openapi_url_not_present():
    """
    Edge case: If the app has openapi_url set to None, update_openapi should raise.
    """
    app = FastAPI(openapi_url=None)
    # The app should not have an openapi route, so update_openapi should fail
    with pytest.raises(StopIteration):
        update_openapi(app) # 1.92μs -> 1.38μs (39.1% faster)

def test_openapi_route_missing():
    """
    Edge case: If the app's router does not contain the openapi route, update_openapi should raise.
    """
    app = FastAPI()
    # Remove the openapi route manually
    app.router.routes = [route for route in app.router.routes if route.path != app.openapi_url]
    with pytest.raises(StopIteration):
        update_openapi(app) # 1.70μs -> 1.30μs (30.6% faster)

def test_openapi_content_type_already_set():
    """
    Edge case: If the original endpoint sets the content-type to something else,
    update_openapi should override it.
    """
    app = FastAPI()
    # Patch the openapi route to set a custom content-type first
    openapi_route = next(route for route in app.router.routes if route.path == app.openapi_url)
    old_endpoint = openapi_route.endpoint

    async def custom_content_type_endpoint(req: Request) -> Response:
        response = await old_endpoint(req)
        response.headers["content-type"] = "application/json"
        return response

    openapi_route.app = request_response(custom_content_type_endpoint)
    update_openapi(app) # 2.43μs -> 1.89μs (28.2% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)

def test_openapi_content_type_with_other_routes():
    """
    Edge case: Ensure update_openapi only affects the OpenAPI route, not other routes.
    """
    app = FastAPI()

    @app.get("/hello")
    def hello():
        return {"msg": "world"}

    update_openapi(app) # 3.16μs -> 2.09μs (51.3% faster)
    client = TestClient(app)
    response = client.get("/hello")

def test_openapi_route_is_not_a_route_object():
    """
    Edge case: If the openapi route is not a Route instance, update_openapi should raise.
    Simulate this by monkey-patching the router.
    """
    app = FastAPI()
    # Replace the openapi route with a dummy object
    class DummyRoute:
        path = app.openapi_url
    app.router.routes = [DummyRoute() if route.path == app.openapi_url else route for route in app.router.routes]
    with pytest.raises(AttributeError):
        update_openapi(app) # 3.58μs -> 2.41μs (48.5% faster)

# --- Large Scale Test Cases ---

def test_openapi_content_type_with_many_routes():
    """
    Large scale: Add many routes to the app and ensure update_openapi still works.
    """
    app = FastAPI()
    # Add 500 dummy routes
    for i in range(500):
        @app.get(f"/route_{i}")
        def dummy_route(i=i):
            return {"route": i}
    update_openapi(app) # 3.82μs -> 2.81μs (36.2% faster)
    client = TestClient(app)
    # Check OpenAPI endpoint still works
    response = client.get(app.openapi_url)
    # Check a random route works
    response2 = client.get("/route_123")

def test_openapi_content_type_with_large_openapi_schema():
    """
    Large scale: Simulate an app with a large OpenAPI schema.
    """
    app = FastAPI()
    # Add 999 routes (limit per instructions)
    for i in range(999):
        @app.get(f"/item_{i}")
        def item(i=i):
            return {"item": i}
    update_openapi(app) # 4.23μs -> 3.14μs (34.8% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)
    # Check schema has many paths
    schema = response.json()

def test_openapi_content_type_performance():
    """
    Large scale: Ensure update_openapi does not significantly slow down OpenAPI response.
    (Basic timing test, not strict performance test.)
    """
    import time
    app = FastAPI()
    for i in range(500):
        @app.get(f"/foo_{i}")
        def foo(i=i):
            return {"foo": i}
    update_openapi(app) # 4.23μs -> 3.36μs (25.9% faster)
    client = TestClient(app)
    start = time.time()
    response = client.get(app.openapi_url)
    duration = time.time() - start
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
import pytest
# function to test
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route, request_response
from titiler.core.utils import update_openapi

# unit tests

# ----------- BASIC TEST CASES -----------

def test_basic_openapi_content_type_patch():
    """Test that update_openapi sets the correct content-type for the OpenAPI endpoint."""
    app = FastAPI()
    # Patch the OpenAPI route
    update_openapi(app) # 3.81μs -> 2.73μs (39.5% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)

def test_basic_openapi_route_still_works():
    """Test that the OpenAPI route still returns a valid OpenAPI spec after patching."""
    app = FastAPI(title="TestAPI", version="1.2.3")
    update_openapi(app) # 3.31μs -> 2.27μs (45.5% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)

def test_basic_openapi_patch_idempotency():
    """Test that applying update_openapi multiple times does not break the OpenAPI route."""
    app = FastAPI()
    update_openapi(app) # 3.43μs -> 2.15μs (59.3% faster)
    update_openapi(app) # 1.61μs -> 1.29μs (24.9% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)

# ----------- EDGE TEST CASES -----------

def test_openapi_url_custom_path():
    """Test that update_openapi works with a custom OpenAPI URL."""
    app = FastAPI(openapi_url="/custom_openapi.json")
    update_openapi(app) # 3.19μs -> 2.23μs (43.2% faster)
    client = TestClient(app)
    response = client.get("/custom_openapi.json")

def test_openapi_url_with_prefix():
    """Test that update_openapi works when the app has a root_path or OpenAPI path prefix."""
    app = FastAPI(openapi_url="/api/openapi.json", root_path="/api")
    update_openapi(app) # 3.37μs -> 2.18μs (54.6% faster)
    client = TestClient(app)
    response = client.get("/api/openapi.json")

def test_openapi_route_missing():
    """Test that update_openapi raises an error if the OpenAPI route is missing."""
    app = FastAPI(openapi_url=None)
    # Remove the OpenAPI route if present
    app.openapi_url = None
    # Remove route manually to simulate missing route
    app.router.routes = [r for r in app.router.routes if getattr(r, "path", None) != "/openapi.json"]
    with pytest.raises(StopIteration):
        update_openapi(app) # 2.01μs -> 1.34μs (49.9% faster)

def test_openapi_content_type_not_overwritten_for_other_routes():
    """Test that update_openapi only changes the OpenAPI route, not other routes."""
    app = FastAPI()
    @app.get("/hello")
    def hello():
        return {"msg": "hi"}
    update_openapi(app) # 2.92μs -> 2.18μs (33.8% faster)
    client = TestClient(app)
    response = client.get("/hello")

def test_openapi_route_with_additional_headers():
    """Test that update_openapi does not remove additional headers set by FastAPI."""
    app = FastAPI()
    update_openapi(app) # 3.15μs -> 2.20μs (43.2% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)

# ----------- LARGE SCALE TEST CASES -----------

def test_large_number_of_routes_openapi_patch():
    """Test that update_openapi still works when the app has many routes."""
    app = FastAPI()
    # Add 999 dummy routes
    for i in range(999):
        app.get(f"/route{i}")(lambda: {"route": i})
    update_openapi(app) # 4.55μs -> 2.82μs (61.3% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)
    # OpenAPI spec should contain all the routes
    for i in range(999):
        pass

def test_large_openapi_spec_size():
    """Test that update_openapi works with a large OpenAPI spec (many models and endpoints)."""
    app = FastAPI()
    # Add 500 endpoints with different response models
    for i in range(500):
        def make_endpoint(idx):
            def endpoint():
                return {"idx": idx}
            return endpoint
        app.get(f"/big/{i}")(make_endpoint(i))
    update_openapi(app) # 4.72μs -> 2.88μs (63.9% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)
    # The OpenAPI spec should contain all paths
    for i in range(500):
        pass

def test_large_openapi_patch_performance():
    """Test that update_openapi does not significantly slow down OpenAPI route (basic timing check)."""
    import time
    app = FastAPI()
    for i in range(200):
        app.get(f"/api/{i}")(lambda: {"id": i})
    update_openapi(app) # 4.48μs -> 2.70μs (65.9% faster)
    client = TestClient(app)
    start = time.time()
    response = client.get(app.openapi_url)
    elapsed = time.time() - start

# ----------- ADDITIONAL EDGE CASES -----------

def test_openapi_patch_with_multiple_apps():
    """Test that update_openapi works independently for multiple FastAPI app instances."""
    app1 = FastAPI()
    app2 = FastAPI()
    update_openapi(app1) # 3.50μs -> 2.30μs (52.2% faster)
    update_openapi(app2) # 1.63μs -> 1.25μs (31.0% faster)
    client1 = TestClient(app1)
    client2 = TestClient(app2)
    resp1 = client1.get(app1.openapi_url)
    resp2 = client2.get(app2.openapi_url)

def test_openapi_patch_with_dependency_injection():
    """Test that update_openapi does not break dependency injection in endpoints."""
    from fastapi import Depends
    app = FastAPI()
    def dep():
        return "dep"
    @app.get("/dep")
    def endpoint(val=Depends(dep)):
        return {"val": val}
    update_openapi(app) # 3.42μs -> 2.12μs (61.6% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)
    # The /dep endpoint should still work
    resp2 = client.get("/dep")

def test_openapi_patch_with_async_endpoints():
    """Test that update_openapi works when the OpenAPI route is async."""
    app = FastAPI()
    update_openapi(app) # 3.34μs -> 2.24μs (49.1% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)
    # The OpenAPI route is always async, but this confirms it works

def test_openapi_patch_with_subapp():
    """Test that update_openapi works for sub-apps mounted in the main app."""
    main_app = FastAPI()
    sub_app = FastAPI(openapi_url="/sub/openapi.json")
    update_openapi(sub_app) # 3.24μs -> 2.25μs (43.9% faster)
    main_app.mount("/sub", sub_app)
    client = TestClient(main_app)
    response = client.get("/sub/openapi.json")

def test_openapi_patch_with_no_routes():
    """Test that update_openapi works when the app has no user-defined routes."""
    app = FastAPI()
    update_openapi(app) # 3.07μs -> 2.23μs (37.7% faster)
    client = TestClient(app)
    response = client.get(app.openapi_url)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-update_openapi-miforihg and push.

Codeflash Static Badge

The optimization replaces a generator expression with `next()` with a simple for-loop for finding the OpenAPI route, achieving a **45% speedup** (95.0μs → 65.3μs).

**Key optimizations:**
- **Eliminated generator overhead:** The original `next(route for route in app.router.routes if route.path == app.openapi_url)` creates a generator object and uses the `next()` builtin, which has function call overhead
- **Direct iteration with early exit:** The optimized version uses a plain for-loop that breaks immediately when the matching route is found, avoiding generator allocation and `next()` function calls
- **Cached attribute access:** Stores `app.openapi_url` in a local variable to avoid repeated attribute lookups during iteration

**Performance impact analysis:**
Based on the line profiler results, the route lookup overhead dropped significantly - the original generator expression took 33.6% of total execution time (153,430ns), while the optimized for-loop approach distributes this cost across multiple lighter operations totaling much less time.

**Test case effectiveness:**
The optimization performs consistently well across all test scenarios:
- **Basic cases:** 44-51% speedup for typical usage patterns
- **Edge cases:** 28-50% improvement even when handling missing routes or custom configurations  
- **Large scale:** 25-66% speedup with many routes (up to 999), showing the optimization scales well as route count increases

This optimization is particularly valuable since `update_openapi()` is typically called during FastAPI application initialization, where even small improvements in startup time can be beneficial for serverless deployments or frequent application restarts.
@codeflash-ai
codeflash-ai Bot requested a review from mashraf-222 November 26, 2025 07:31
@codeflash-ai codeflash-ai Bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants