Browse Source
When a route uses a custom response class whose __init__ does not expose
a status_code parameter with an int default, and the route decorator
does not specify an explicit status_code, the status_code local variable
in get_openapi_path() was never assigned. This caused an
UnboundLocalError at the point where status_code is used to populate
the operation responses dict.
The bug does not surface with standard Starlette response classes
(JSONResponse, HTMLResponse, PlainTextResponse, StreamingResponse, etc.)
because they all declare status_code: int = 200 (or 307 for redirects)
in their __init__ signatures. It triggers when users define custom
response classes that wrap or omit the status_code parameter, e.g.:
class CustomResponse(Response):
def __init__(self, content, **kwargs):
super().__init__(content=content, status_code=200, **kwargs)
@app.get("/items", response_class=CustomResponse)
async def get_items(): ...
Accessing /openapi.json or /docs would crash with:
UnboundLocalError: cannot access local variable 'status_code'
Fix: initialize status_code = "200" before the conditional block so it
always has a safe default. The existing branches that extract status_code
from the route or from the response class signature still override this
default when applicable.
pull/14927/head
2 changed files with 62 additions and 0 deletions
@ -0,0 +1,61 @@ |
|||
from starlette.responses import Response |
|||
|
|||
from fastapi import FastAPI |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
|
|||
class CustomResponse(Response): |
|||
"""A custom response class whose __init__ does not expose status_code |
|||
as a parameter with an int default. This previously caused an |
|||
UnboundLocalError during OpenAPI schema generation when the route |
|||
did not specify an explicit status_code.""" |
|||
|
|||
media_type = "text/plain" |
|||
|
|||
def __init__(self, content: str, **kwargs: object) -> None: |
|||
super().__init__(content=content, status_code=200, **kwargs) |
|||
|
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
@app.get("/items", response_class=CustomResponse) |
|||
async def get_items(): |
|||
return "ok" |
|||
|
|||
|
|||
client = TestClient(app) |
|||
|
|||
|
|||
def test_get_response(): |
|||
response = client.get("/items") |
|||
assert response.status_code == 200 |
|||
assert response.text == "ok" |
|||
|
|||
|
|||
def test_openapi_schema(): |
|||
"""OpenAPI generation must not crash when the response class's __init__ |
|||
lacks a status_code parameter with an int default.""" |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items": { |
|||
"get": { |
|||
"summary": "Get Items", |
|||
"operationId": "get_items_items_get", |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"text/plain": {"schema": {"type": "string"}}}, |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
} |
|||
) |
|||
Loading…
Reference in new issue