diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 808646cc2..19d05c351 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -274,6 +274,8 @@ def get_openapi_path( route_response_media_type: Optional[str] = current_response_class.media_type if route.include_in_schema: for method in route.methods: + if method == "HEAD" and "GET" in route.methods: + continue operation = get_openapi_operation_metadata( route=route, method=method, operation_ids=operation_ids ) diff --git a/fastapi/routing.py b/fastapi/routing.py index 457481e32..956ffacba 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -490,7 +490,9 @@ class APIRoute(routing.Route): self.name = get_name(endpoint) if name is None else name self.path_regex, self.path_format, self.param_convertors = compile_path(path) if methods is None: - methods = ["GET"] + methods = ["GET", "HEAD"] + elif "GET" in methods: + methods = set(methods).union({"HEAD"}) self.methods: Set[str] = {method.upper() for method in methods} if isinstance(generate_unique_id_function, DefaultPlaceholder): current_generate_unique_id: Callable[[APIRoute], str] = ( @@ -1724,7 +1726,7 @@ class APIRouter(routing.Router): response_description=response_description, responses=responses, deprecated=deprecated, - methods=["GET"], + methods=["GET", "HEAD"], operation_id=operation_id, response_model_include=response_model_include, response_model_exclude=response_model_exclude, diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py index bd16fe925..b5b5c6500 100644 --- a/tests/test_extra_routes.py +++ b/tests/test_extra_routes.py @@ -14,6 +14,11 @@ class Item(BaseModel): price: Optional[float] = None +@app.head("/items/{item_id}") +def head_item(item_id: str): + return JSONResponse(None, headers={"x-fastapi-item-id": item_id}) + + @app.api_route("/items/{item_id}", methods=["GET"]) def get_items(item_id: str): return {"item_id": item_id} @@ -31,11 +36,6 @@ def delete_item(item_id: str, item: Item): return {"item_id": item_id, "item": item} -@app.head("/items/{item_id}") -def head_item(item_id: str): - return JSONResponse(None, headers={"x-fastapi-item-id": item_id}) - - @app.options("/items/{item_id}") def options_item(item_id: str): return JSONResponse(None, headers={"x-fastapi-item-id": item_id}) diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001.py b/tests/test_tutorial/test_first_steps/test_tutorial001.py index 6cc9fc228..54c6cc36b 100644 --- a/tests/test_tutorial/test_first_steps/test_tutorial001.py +++ b/tests/test_tutorial/test_first_steps/test_tutorial001.py @@ -18,6 +18,9 @@ def test_get_path(path, expected_status, expected_response): assert response.status_code == expected_status assert response.json() == expected_response + response = client.head(path) + assert response.status_code == expected_status + def test_openapi_schema(): response = client.get("/openapi.json")