Skip to content

Commit b628d45

Browse files
committed
Add support for custom HTTP exception classes
1 parent 75e44dd commit b628d45

File tree

5 files changed

+57
-9
lines changed

5 files changed

+57
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
55

66
## [Unreleased]
77

8+
## [5.1.4]
9+
10+
### Fixed
11+
12+
* Fixed a bug where a custom HTTP exception was not being copied correctly so its unique properties are ignored
13+
814
## [5.1.3]
915

1016
### Fixed

cadwyn/structure/versions.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import email.message
22
import functools
3+
import http
34
import inspect
45
import json
56
from collections import defaultdict
@@ -8,7 +9,7 @@
89
from contextvars import ContextVar
910
from datetime import date
1011
from enum import Enum
11-
from typing import TYPE_CHECKING, ClassVar, Union
12+
from typing import TYPE_CHECKING, ClassVar, Union, cast
1213

1314
from fastapi import BackgroundTasks, HTTPException, params
1415
from fastapi import Request as FastapiRequest
@@ -616,12 +617,13 @@ async def _convert_endpoint_response_to_version( # noqa: C901
616617
detail = response_info.body["detail"]
617618
else:
618619
detail = response_info.body
620+
if detail is None:
621+
detail = http.HTTPStatus(response_info.status_code).phrase
622+
raised_exception.detail = cast(str, detail)
623+
raised_exception.headers = dict(response_info.headers)
624+
raised_exception.status_code = response_info.status_code
619625

620-
raise HTTPException(
621-
status_code=response_info.status_code,
622-
detail=detail,
623-
headers=dict(response_info.headers),
624-
)
626+
raise raised_exception
625627
return response_info._response
626628
return response_info.body
627629

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "cadwyn"
3-
version = "5.1.3"
3+
version = "5.1.4"
44
description = "Production-ready community-driven modern Stripe-like API versioning in FastAPI"
55
authors = [{ name = "Stanislav Zmiev", email = "zmievsa@gmail.com" }]
66
license = "MIT"

tests/test_data_migrations.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
from collections.abc import Callable, Coroutine
44
from contextvars import ContextVar
55
from io import StringIO
6-
from typing import Any, Literal, Union
6+
from typing import Any, Literal, Optional, Union
77

88
import fastapi
99
import pytest
1010
from dirty_equals import IsPartialDict, IsStr
1111
from fastapi import APIRouter, Body, Cookie, File, Header, HTTPException, Query, Request, Response, UploadFile
1212
from fastapi.responses import JSONResponse
1313
from fastapi.routing import APIRoute
14+
from fastapi.testclient import TestClient
1415
from pydantic import BaseModel, Field, RootModel
1516
from starlette.responses import StreamingResponse
1617

@@ -32,6 +33,7 @@
3233
from cadwyn.structure.schemas import schema
3334
from cadwyn.structure.versions import Version, VersionBundle
3435
from tests.conftest import (
36+
CreateVersionedApp,
3537
CreateVersionedClients,
3638
client,
3739
version_change,
@@ -1226,3 +1228,41 @@ def response_converter(response: ResponseInfo):
12261228
resp_2001 = client_2001.post(f"/{endpoint}", json={"i": ["original_request"]})
12271229
assert resp_2001.status_code == 200
12281230
assert resp_2001.json() == {"i": ["original_request", endpoint]}
1231+
1232+
1233+
def test__response_migrations__with_custom_http_exception(
1234+
create_versioned_app: CreateVersionedApp,
1235+
router: VersionedAPIRouter,
1236+
) -> None:
1237+
class CustomHTTPException(HTTPException):
1238+
error_code: Optional[str] = None
1239+
1240+
def __init__(self, detail: str, error_code: Optional[str] = None):
1241+
self.error_code = error_code
1242+
super().__init__(status_code=400, detail=detail)
1243+
1244+
def http_exception_handler(request, exc):
1245+
# Check if the exception has an error_code attribute
1246+
error_code = exc.error_code if hasattr(exc, "error_code") else "generic_error"
1247+
1248+
return JSONResponse(
1249+
status_code=exc.status_code,
1250+
content={"code": error_code, "message": exc.detail},
1251+
)
1252+
1253+
# Register exception handler for Cadwyn
1254+
1255+
@router.post("/test")
1256+
async def endpoint():
1257+
raise CustomHTTPException("Cadwyn error occurred", error_code="cadwyn_error")
1258+
1259+
app = create_versioned_app(version_change())
1260+
app.add_exception_handler(HTTPException, http_exception_handler)
1261+
with TestClient(app) as client:
1262+
resp = client.post("/test", headers={"X-API-VERSION": "2000-01-01"})
1263+
assert resp.status_code == 400
1264+
assert resp.json() == {"code": "cadwyn_error", "message": "Cadwyn error occurred"}
1265+
1266+
resp_2001 = client.post("/test", headers={"X-API-VERSION": "2001-01-01"})
1267+
assert resp_2001.status_code == 400
1268+
assert resp_2001.json() == {"code": "cadwyn_error", "message": "Cadwyn error occurred"}

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)