Skip to content
Open
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
2 changes: 2 additions & 0 deletions robyn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from robyn.argument_parser import Config
from robyn.authentication import AuthenticationHandler
from robyn.dependency_injection import DependencyMap
from robyn.exceptions import HTTPException
from robyn.env_populator import load_vars
from robyn.events import Events
from robyn.jsonify import jsonify
Expand Down Expand Up @@ -811,4 +812,5 @@ def cors_middleware(request):
"WebSocketDisconnect",
"JsonBody",
"MCPApp",
"HTTPException",
]
3 changes: 2 additions & 1 deletion robyn/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@


class HTTPException(Exception):
def __init__(self, status_code: int, detail: str | None = None) -> None:
def __init__(self, status_code: int, detail: str | None = None, headers: dict | None = None) -> None:
if detail is None:
detail = http.HTTPStatus(status_code).phrase
self.status_code = status_code
self.detail = detail
self.headers = headers or {}

def __str__(self) -> str:
return f"{self.status_code}: {self.detail}"
Expand Down
13 changes: 13 additions & 0 deletions robyn/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Callable, Dict, List, NamedTuple, Optional, Union, is_typeddict

from robyn import status_codes
from robyn.exceptions import HTTPException
from robyn._param_utils import QueryParamValidationError, parse_route_param_names, resolve_individual_params
from robyn.authentication import AuthenticationHandler, AuthenticationNotConfiguredError
from robyn.dependency_injection import DependencyMap
Expand Down Expand Up @@ -289,6 +290,12 @@ async def async_inner_handler(*args, **kwargs):
headers=Headers({"Content-Type": "application/json"}),
description=jsonify(err.error_detail),
)
except HTTPException as exc:
response = Response(
status_code=exc.status_code,
headers=Headers(exc.headers),
description=exc.detail or "",
)
except Exception as err:
if exception_handler is None:
raise
Expand All @@ -315,6 +322,12 @@ def inner_handler(*args, **kwargs):
headers=Headers({"Content-Type": "application/json"}),
description=jsonify(err.error_detail),
)
except HTTPException as exc:
response = Response(
status_code=exc.status_code,
headers=Headers(exc.headers),
description=exc.detail or "",
)
except Exception as err:
if exception_handler is None:
raise
Expand Down
35 changes: 35 additions & 0 deletions unit_tests/test_http_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from robyn.exceptions import HTTPException


def test_http_exception_default():
exc = HTTPException(404)
assert exc.status_code == 404
assert exc.detail == "Not Found"
assert exc.headers == {}


def test_http_exception_custom_detail():
exc = HTTPException(403, detail="Go away")
assert exc.detail == "Go away"


def test_http_exception_with_headers():
exc = HTTPException(
401,
detail="Token expired",
headers={"WWW-Authenticate": "Bearer"},
)
assert exc.status_code == 401
assert exc.detail == "Token expired"
assert exc.headers == {"WWW-Authenticate": "Bearer"}


def test_http_exception_str():
exc = HTTPException(500, detail="Internal error")
assert str(exc) == "500: Internal error"


def test_http_exception_repr():
exc = HTTPException(400, detail="Bad input")
assert "HTTPException" in repr(exc)
assert "400" in repr(exc)
Loading