Browse Source

Fix unstable operation IDs for multi-method routes

Make generated operation IDs deterministic when a route is registered with
multiple HTTP methods. This avoids depending on set iteration order and adds
a regression test to keep the behavior stable.
pull/15227/head
esubaalew 4 months ago
parent
commit
abb4ef9284
  1. 2
      fastapi/utils.py
  2. 27
      tests/test_generate_unique_id_function.py

2
fastapi/utils.py

@ -96,7 +96,7 @@ def generate_unique_id(route: "APIRoute") -> str:
operation_id = f"{route.name}{route.path_format}" operation_id = f"{route.name}{route.path_format}"
operation_id = re.sub(r"\W", "_", operation_id) operation_id = re.sub(r"\W", "_", operation_id)
assert route.methods assert route.methods
operation_id = f"{operation_id}_{list(route.methods)[0].lower()}" operation_id = f"{operation_id}_{sorted(route.methods)[0].lower()}"
return operation_id return operation_id

27
tests/test_generate_unique_id_function.py

@ -1,8 +1,10 @@
import warnings import warnings
from types import SimpleNamespace
from fastapi import APIRouter, FastAPI from fastapi import APIRouter, FastAPI
from fastapi.routing import APIRoute from fastapi.routing import APIRoute
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from fastapi.utils import generate_unique_id
from inline_snapshot import snapshot from inline_snapshot import snapshot
from pydantic import BaseModel from pydantic import BaseModel
@ -29,6 +31,31 @@ class Message(BaseModel):
description: str description: str
class MethodsSet(set[str]):
def __init__(self, values: set[str], iteration_order: list[str]) -> None:
super().__init__(values)
self.iteration_order = iteration_order
def __iter__(self):
return iter(self.iteration_order)
def test_generate_unique_id_with_multiple_methods_is_stable():
route_a = SimpleNamespace(
name="handler",
path_format="/items",
methods=MethodsSet({"POST", "DELETE"}, ["POST", "DELETE"]),
)
route_b = SimpleNamespace(
name="handler",
path_format="/items",
methods=MethodsSet({"POST", "DELETE"}, ["DELETE", "POST"]),
)
assert generate_unique_id(route_a) == "handler_items_delete"
assert generate_unique_id(route_a) == generate_unique_id(route_b)
def test_top_level_generate_unique_id(): def test_top_level_generate_unique_id():
app = FastAPI(generate_unique_id_function=custom_generate_unique_id) app = FastAPI(generate_unique_id_function=custom_generate_unique_id)
router = APIRouter() router = APIRouter()

Loading…
Cancel
Save