Skip to content

Commit e1dd040

Browse files
committed
Migrate to fastapi-error-map 1.0.0
1 parent 3448b21 commit e1dd040

20 files changed

Lines changed: 86 additions & 57 deletions

‎pyproject.toml‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies = [
1313
"bcrypt==5.0.0",
1414
"dishka==1.10.1",
1515
"fastapi==0.136.1",
16-
"fastapi-error-map==0.9.10",
16+
"fastapi-error-map==1.0.0",
1717
"psycopg[binary]==3.3.4",
1818
"pydantic-settings==2.14.1",
1919
"pyjwt[crypto]==2.12.1",
@@ -256,8 +256,10 @@ force-wrap-aliases = true
256256
split-on-trailing-comma = true
257257

258258
[tool.ruff.lint.per-file-ignores]
259-
"scripts/**" = ["INP001"] # runnable scripts, not an importable package
260-
"scripts/dishka/plot_dependencies_data.py" = ["T201"] # print
259+
"scripts/**" = [
260+
"INP001", # runnable scripts, not an importable package
261+
"T20", # print is legitimate output here
262+
]
261263
"src/app/outbound/persistence_sqla/alembic/**" = ["ALL"]
262264
"tests/**" = [
263265
"ARG002", # unused-method-argument

‎src/app/inbound/http/account/change_password.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
from dishka.integrations.fastapi import inject
55
from fastapi import APIRouter, Depends, status
66
from fastapi.security import APIKeyCookie
7-
from fastapi_error_map import ErrorAwareRouter
87
from pydantic import BaseModel, ConfigDict
98

109
from app.core.common.authorization.exceptions import AuthorizationError
1110
from app.core.common.exceptions import BusinessTypeError
1211
from app.inbound.http.errors.callbacks import log_info
12+
from app.inbound.http.errors.router import make_error_aware_router
1313
from app.inbound.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE
1414
from app.outbound.adapters.exceptions import PasswordHasherBusyError
1515
from app.outbound.auth_ctx.exceptions import AuthenticationChangeError, AuthenticationError, ReAuthenticationError
@@ -30,7 +30,7 @@ class ChangePasswordRequestSchema(BaseModel):
3030

3131

3232
def make_change_password_router(*, cookie_name: str) -> APIRouter:
33-
router = ErrorAwareRouter()
33+
router = make_error_aware_router(on_error=log_info)
3434

3535
@router.put(
3636
"/password/",
@@ -43,7 +43,6 @@ def make_change_password_router(*, cookie_name: str) -> APIRouter:
4343
ReAuthenticationError: status.HTTP_403_FORBIDDEN,
4444
PasswordHasherBusyError: HTTP_503_SERVICE_UNAVAILABLE_RULE,
4545
},
46-
default_on_error=log_info,
4746
status_code=status.HTTP_204_NO_CONTENT,
4847
dependencies=[Depends(APIKeyCookie(name=cookie_name))],
4948
description=getdoc(ChangePassword),

‎src/app/inbound/http/account/log_in.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
from dishka import FromDishka
44
from dishka.integrations.fastapi import inject
55
from fastapi import APIRouter, status
6-
from fastapi_error_map import ErrorAwareRouter
76

87
from app.core.common.authorization.exceptions import AuthorizationError
98
from app.core.common.exceptions import BusinessTypeError
109
from app.inbound.http.errors.callbacks import log_info
10+
from app.inbound.http.errors.router import make_error_aware_router
1111
from app.inbound.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE
1212
from app.outbound.adapters.exceptions import PasswordHasherBusyError
1313
from app.outbound.auth_ctx.exceptions import AlreadyAuthenticatedError, AuthenticationError
@@ -16,7 +16,7 @@
1616

1717

1818
def make_log_in_router() -> APIRouter:
19-
router = ErrorAwareRouter()
19+
router = make_error_aware_router(on_error=log_info)
2020

2121
@router.post(
2222
"/login/",
@@ -28,7 +28,6 @@ def make_log_in_router() -> APIRouter:
2828
AuthenticationError: status.HTTP_401_UNAUTHORIZED,
2929
PasswordHasherBusyError: HTTP_503_SERVICE_UNAVAILABLE_RULE,
3030
},
31-
default_on_error=log_info,
3231
status_code=status.HTTP_204_NO_CONTENT,
3332
description=getdoc(LogIn),
3433
)

‎src/app/inbound/http/account/log_out.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44
from dishka.integrations.fastapi import inject
55
from fastapi import APIRouter, Depends, status
66
from fastapi.security import APIKeyCookie
7-
from fastapi_error_map import ErrorAwareRouter
87

98
from app.core.common.authorization.exceptions import AuthorizationError
109
from app.inbound.http.errors.callbacks import log_info
10+
from app.inbound.http.errors.router import make_error_aware_router
1111
from app.inbound.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE
1212
from app.outbound.auth_ctx.exceptions import AuthenticationError
1313
from app.outbound.auth_ctx.handlers.log_out import LogOut
1414
from app.outbound.exceptions import StorageError
1515

1616

1717
def make_log_out_router(*, cookie_name: str) -> APIRouter:
18-
router = ErrorAwareRouter()
18+
router = make_error_aware_router(on_error=log_info)
1919

2020
@router.delete(
2121
"/logout/",
@@ -24,7 +24,6 @@ def make_log_out_router(*, cookie_name: str) -> APIRouter:
2424
StorageError: HTTP_503_SERVICE_UNAVAILABLE_RULE,
2525
AuthorizationError: status.HTTP_403_FORBIDDEN,
2626
},
27-
default_on_error=log_info,
2827
status_code=status.HTTP_204_NO_CONTENT,
2928
dependencies=[Depends(APIKeyCookie(name=cookie_name))],
3029
description=getdoc(LogOut),

‎src/app/inbound/http/account/sign_up.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
from dishka import FromDishka
44
from dishka.integrations.fastapi import inject
55
from fastapi import APIRouter, status
6-
from fastapi_error_map import ErrorAwareRouter
76

87
from app.core.commands.exceptions import UsernameAlreadyExistsError
98
from app.core.common.authorization.exceptions import AuthorizationError
109
from app.core.common.exceptions import BusinessTypeError
1110
from app.inbound.http.errors.callbacks import log_info
11+
from app.inbound.http.errors.router import make_error_aware_router
1212
from app.inbound.http.errors.rules import HTTP_503_SERVICE_UNAVAILABLE_RULE
1313
from app.outbound.adapters.exceptions import PasswordHasherBusyError
1414
from app.outbound.auth_ctx.exceptions import AlreadyAuthenticatedError
@@ -17,7 +17,7 @@
1717

1818

1919
def make_sign_up_router() -> APIRouter:
20-
router = ErrorAwareRouter()
20+
router = make_error_aware_router(on_error=log_info)
2121

2222
@router.post(
2323
"/signup/",
@@ -29,7 +29,6 @@ def make_sign_up_router() -> APIRouter:
2929
PasswordHasherBusyError: HTTP_503_SERVICE_UNAVAILABLE_RULE,
3030
UsernameAlreadyExistsError: status.HTTP_409_CONFLICT,
3131
},
32-
default_on_error=log_info,
3332
status_code=status.HTTP_204_NO_CONTENT,
3433
description=getdoc(SignUp),
3534
)
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from fastapi import APIRouter
22

33
from app.inbound.http.account.router import make_account_router
4+
from app.inbound.http.errors.openapi_responses import SERVER_ERROR_RESPONSES
45
from app.inbound.http.users.router import make_users_router
56

67

78
def make_v1_router(*, cookie_name: str) -> APIRouter:
8-
router = APIRouter(prefix="/api/v1")
9+
router = APIRouter(prefix="/api/v1", responses=SERVER_ERROR_RESPONSES)
910
router.include_router(make_account_router(cookie_name=cookie_name))
1011
router.include_router(make_users_router(cookie_name=cookie_name))
1112
return router
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from fastapi_error_map import StructuredErrorResponse, Translator, structured
2+
from starlette import status
3+
4+
5+
def internal_server_error(exc: Exception) -> StructuredErrorResponse:
6+
translate: Translator[StructuredErrorResponse] = structured()(status.HTTP_500_INTERNAL_SERVER_ERROR)
7+
return translate(exc)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import Any, Final
2+
3+
from fastapi_error_map import StructuredErrorResponse
4+
from starlette import status
5+
6+
type OpenApiResponses = dict[int | str, dict[str, Any]]
7+
8+
SERVER_ERROR_RESPONSES: Final[OpenApiResponses] = {
9+
status.HTTP_500_INTERNAL_SERVER_ERROR: {"model": StructuredErrorResponse},
10+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from typing import Any
2+
3+
from fastapi_error_map import ErrorAwareRouter, ErrorMap, OnError, structured
4+
5+
6+
def make_error_aware_router(
7+
*,
8+
error_map: ErrorMap | None = None,
9+
on_error: OnError | None = None,
10+
warn_on_unmapped: bool = True,
11+
**kwargs: Any,
12+
) -> ErrorAwareRouter:
13+
return ErrorAwareRouter(
14+
translator_factory=structured(),
15+
error_map=error_map,
16+
on_error=on_error,
17+
warn_on_unmapped=warn_on_unmapped,
18+
**kwargs,
19+
)
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from typing import Final
22

3-
from fastapi_error_map.rules import Rule, rule
3+
from fastapi_error_map import Rule, rule, structured
44
from starlette import status
55

6-
from app.inbound.http.errors.translators import ServiceUnavailableTranslator
6+
SERVICE_UNAVAILABLE_MESSAGE: Final[str] = "Service temporarily unavailable. Please try again later."
77

88
HTTP_503_SERVICE_UNAVAILABLE_RULE: Final[Rule] = rule(
99
status=status.HTTP_503_SERVICE_UNAVAILABLE,
10-
translator=ServiceUnavailableTranslator(),
10+
translator=structured(server_message=SERVICE_UNAVAILABLE_MESSAGE)(status.HTTP_503_SERVICE_UNAVAILABLE),
1111
)

0 commit comments

Comments
 (0)