Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add headers parameter to HTTPException #1435

Merged
merged 3 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions docs/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ how you return responses when errors or handled exceptions occur.

```python
from starlette.applications import Starlette
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse


HTML_404_PAGE = ...
HTML_500_PAGE = ...


async def not_found(request, exc):
async def not_found(request: Request, exc: HTTPException):
return HTMLResponse(content=HTML_404_PAGE, status_code=exc.status_code)

async def server_error(request, exc):
async def server_error(request: Request, exc: HTTPException):
return HTMLResponse(content=HTML_500_PAGE, status_code=exc.status_code)


Expand All @@ -40,14 +42,26 @@ In particular you might want to override how the built-in `HTTPException` class
is handled. For example, to use JSON style responses:

```python
async def http_exception(request, exc):
async def http_exception(request: Request, exc: HTTPException):
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)

exception_handlers = {
HTTPException: http_exception
}
```

The `HTTPException` is also equipped with the `headers` argument. Which allows the propagation
of the headers to the response class:

```python
async def http_exception(request: Request, exc: HTTPException):
return JSONResponse(
{"detail": exc.detail},
status_code=exc.status_code,
headers=exc.headers
)
```

## Errors and handled exceptions

It is important to differentiate between handled exceptions and errors.
Expand Down Expand Up @@ -76,7 +90,7 @@ The `HTTPException` class provides a base class that you can use for any
handled exceptions. The `ExceptionMiddleware` implementation defaults to
returning plain-text HTTP responses for any `HTTPException`.

* `HTTPException(status_code, detail=None)`
* `HTTPException(status_code, detail=None, headers=None)`

You should only raise `HTTPException` inside routing or endpoints. Middleware
classes should instead just return appropriate responses directly.
11 changes: 8 additions & 3 deletions starlette/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@


class HTTPException(Exception):
def __init__(self, status_code: int, detail: str = None) -> None:
def __init__(
self, status_code: int, detail: str = None, headers: dict = None
adriangb marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
if detail is None:
detail = http.HTTPStatus(status_code).phrase
self.status_code = status_code
self.detail = detail
self.headers = headers

def __repr__(self) -> str:
class_name = self.__class__.__name__
Expand Down Expand Up @@ -99,5 +102,7 @@ async def sender(message: Message) -> None:

def http_exception(self, request: Request, exc: HTTPException) -> Response:
if exc.status_code in {204, 304}:
return Response(status_code=exc.status_code)
return PlainTextResponse(exc.detail, status_code=exc.status_code)
return Response(status_code=exc.status_code, headers=exc.headers)
return PlainTextResponse(
exc.detail, status_code=exc.status_code, headers=exc.headers
)
11 changes: 11 additions & 0 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def not_modified(request):
raise HTTPException(status_code=304)


def with_headers(request):
raise HTTPException(status_code=200, headers={"x-potato": "always"})


class HandledExcAfterResponse:
async def __call__(self, scope, receive, send):
response = PlainTextResponse("OK", status_code=200)
Expand All @@ -34,6 +38,7 @@ async def __call__(self, scope, receive, send):
Route("/not_acceptable", endpoint=not_acceptable),
Route("/no_content", endpoint=no_content),
Route("/not_modified", endpoint=not_modified),
Route("/with_headers", endpoint=with_headers),
Route("/handled_exc_after_response", endpoint=HandledExcAfterResponse()),
WebSocketRoute("/runtime_error", endpoint=raise_runtime_error),
]
Expand Down Expand Up @@ -67,6 +72,12 @@ def test_not_modified(client):
assert response.text == ""


def test_with_headers(client):
response = client.get("/with_headers")
assert response.status_code == 200
assert response.headers["x-potato"] == "always"


def test_websockets_should_raise(client):
with pytest.raises(RuntimeError):
with client.websocket_connect("/runtime_error"):
Expand Down