-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcustom_factory.py
More file actions
80 lines (57 loc) · 2 KB
/
Copy pathcustom_factory.py
File metadata and controls
80 lines (57 loc) · 2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
"""You need a whole different envelope — not ``{code, message, details}``.
Write your own factory: ``(status) -> (err) -> body``.
``simple()`` and ``structured()`` are built the same way.
Body is RFC 9457 problem details (all members optional; ``instance`` omitted here).
``rule(headers=)`` sets the ``application/problem+json`` content type.
Run: python -m examples.custom_factory
- GET /accounts/0/ -> 403 application/problem+json
{"type": "about:blank", "title": "Forbidden", "status": 403, "detail": "..."}
"""
from collections.abc import Callable
from http import HTTPStatus
from typing import Final
from fastapi import FastAPI
from pydantic import BaseModel
from starlette import status
from typing_extensions import TypedDict
from fastapi_error_map import ErrorAwareRouter, rule
PROBLEM_JSON: Final[str] = "application/problem+json"
class ProblemDetail(TypedDict):
type: str
title: str
status: int
detail: str
def problem_detail(status_code: int) -> Callable[[Exception], ProblemDetail]:
title = HTTPStatus(status_code).phrase
def translate(err: Exception) -> ProblemDetail:
return ProblemDetail(
type="about:blank",
title=title,
status=status_code,
detail=str(err),
)
return translate
class ForbiddenError(Exception): ...
class Account(BaseModel):
account_id: int
def make_app() -> FastAPI:
router = ErrorAwareRouter(translator_factory=problem_detail)
@router.get(
"/accounts/{account_id}/",
error_map={
ForbiddenError: rule(
status.HTTP_403_FORBIDDEN,
headers={"Content-Type": PROBLEM_JSON},
),
},
)
def get_account(account_id: int) -> Account:
if account_id == 0:
raise ForbiddenError("you do not own this account")
return Account(account_id=account_id)
app = FastAPI()
app.include_router(router)
return app
if __name__ == "__main__":
import uvicorn
uvicorn.run(make_app())