FastAPI 공식문서 따라하기[22] - Handling Errors
https://fastapi.tiangolo.com/tutorial/handling-errors/ 공식문서 따라하는 글
☑️ Handling Errors
예외처리에 대한 내용이다.
- 만약 클라이언트가 접근할 수 없는 자원에 접근했거나
- 존재하지 않는 자원에서 접근을 원하거나
- 권한 자체가 없는 경우
우리는 에러를 내야한다.
일반적으로 400범위(400 ~ 499)의 에러를 내면서 클라이언트쪽에서 문제가 있음을 알려야 한다.
☑️ Use HTTPException
에러를 포함한 HTTP responses
를 클라이언트한테 리턴해야할때가 있다.
이를 리턴하기 위해서는 HTTPException
을 임포트해야한다.
1
from fastapi import FastAPI, HTTPException
☑️ Raise an HTTPException
in your code
파이썬에서 에러를 직접 발생시키기 위해서는 return
이 아닌, raise
를 사용해야 한다.
해당 구문을 사용하면서 보안이나 의존성 부분에 명시적으로 눈에 띄기에 더 효율적이다.
다음 예제는 클라이언트가 item
의 ID를 보냈는데, 만약 해당 item
이 존재하지 않는다면 404
에러를 발생시키는 예제이다.
1
2
3
4
5
6
7
8
9
10
11
12
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"kms" : "Kms is Handsome guy"}
@app.get("/items/{item_id}")
async def read_item(item_id : str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found by kms")
return {"item": items[item_id]}
이렇게 작성하고 kms
와 같은 잘못된 요청을 보냈을때는
404
에러가 뜰 것이다.
detail
안에 내용도 잘 써져있다.
해당 예제에서는
detail
에str
값을 넣었지만,list
나dict
같은 형태도 보낼 수 있다고 한다.
☑️ Add custom headers
어떤한 문제가 생겼을 경우 헤더에다가 HTTP error
를 추가할 수 있다.
위의 코드에서
1
2
3
async def read_item(item_id : str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found by kms", headers={"Kms-error": "There goes KMS error"},)
라고 선언하고, 잘못된 내용을 입력할 경우
이렇게 우리가 설정한 헤더가 들어간걸 볼 수 있다.
☑️ Install custom exception handlers
만약 예외처리를 내가 만들어서 처리하고 싶다면 @app.exception_handler()
구문을 통해 처리할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class CustomException(Exception):
def __init__(self,name: str):
self.name = name
app = FastAPI()
items = {"kms" : "Kms is Handsome guy"}
@app.exception_handler(CustomException)
async def unvicorn_exception_handelr(request: Request, exc: CustomException):
return JSONResponse(
status_code=418,
content={"message" : f"Oops! {exc.name} did something.. There goes a rainbow"},
)
@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
if name == "yolo":
raise CustomException(name=name)
return {"unicorn_name": name}
만약 yolo
를 요청으로 보내면 에러가 뜨는 코드이다.
강제로 에러를 발생시키고 이를 exception_handler가 받아서 처리한다.
☑️ Override the default excpetion heandlers
FastAPI
는 기본적인 예외처리 기능을 가지고 있다.
유효하지 않은 요청을 받을때 raise
를 통해 default JSON Responses
를 포함한 HTTPException
을 발생시킬 수 있다.
만약 요청에 유효하지 않은 데이터가 포함되어 있다면, FastAPI
는 내부적으로 RequestValidationError
를 발생시킨다.
RequestValidationError
를 임포트하고, 이것을 사용하기 위해서는 @app.excpetion_handler(RequestValidationError)
구문을 사용하면 된다.
다음은 예제이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return PlainTextResponse(str(exc), status_code=400)
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 3:
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
return {"item_id": item_id}
이렇게 작성하고, 아예 자료형이 다른 /items/kms
같은 요청을 보내면 다음과 같이 뜬다.
RequestValidationError
는 Pydnantic의 ValidationError
의 서브 클래스다.
Pydantic의 response_model
을 사용했는데, 데이터 에러가 발생할 시 로그를 통해 오류를 볼 수 있지만 클라이언트, 사용자는 이걸 볼 수 없다.
대신에 500
이라는 HTTP status code
를 보게 된다. 이는 ValidationError
가 코드 어딘가에 있기 때문이라고 한다.
위의 오류에 해당하는 내용을 수정하는 동안에는 클라이언트/사용자는 오류에 대한 내부 정보에 접근할 수 없게 해야한다. 보안 취약성이 노출될 수 있기 때문이다.
☑️ Override the HTTPException
error handler
위와 같은 방법으로 HTTPException
을 오버라이딩 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from fastapi import FastAPI, HTTPException
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 3:
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
return {"item_id": item_id}
똑같이 아예 자료형이 다른 /items/kms
같은 요청을 보내면 다음과 같이 뜬다.
☑️ Use the RequestValidationError
body
만약 RequestBody
로 들어온 값에 유효하지 않은 데이터가 포함되어 있다면 RequestValidationError
를 일으킨다.
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
from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
)
class Item(BaseModel):
title: str
size: int
@app.post("/items/")
async def create_item(item: Item):
return item
1
2
3
4
5
6
7
8
curl -X 'POST' \
'http://localhost:8000/items/' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"title": "kms",
"size": "12d"
}'
이렇게 size
에 str
을 넣어서 예외를 발생시켜보면,
요로콤 뜬다.
FastAPI’s HTTPException
vs Starlette’s HTTPException
FastAPI는 자체적인 HTTPException
을 가지고 있다. 이것은 Starlette’s의 HTTPException
을 상속받은 것이다.
다른점은 FastAPI의 HTTPException
은 response에 헤더를 추가하는것이 허용된다는 것이다. 이는 OAuth 2.0
이나 몇몇 보안 툴에 쓰인다고 한다.
고로 FastAPI의 HTTPException
을 사용하는게 좋다고 한다.
그러나 만약 예외처리 핸들러가 필요하다면 Starlette’s의 HTTPException
을 사용하라고 한다.
왜냐하면 Starlett가 가지고 있는 내부코드나 외부 플러그인을 통해서 Starlette의 HTTPException
을 발생시키는데, 이 과정에서 작성한 예외 핸들러를 찾고 실행할 수 있기 때문이라고 한다.
☑️ Re-use FastAPI’s exception handlers
만약 기본 예외처리기를 함께 예외를 사용하려면 from fastapi.exception_handlers
를 통해 기본예외처리기를 사용할 수 있다.
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
from fastapi import FastAPI, HTTPException
from fastapi.exception_handlers import (
http_exception_handler,
request_validation_exception_handler,
)
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
print(f"OMG! An HTTP error!: {repr(exc)}")
return await http_exception_handler(request, exc)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
print(f"OMG! The client sent invalid data!: {exc}")
return await request_validation_exception_handler(request, exc)
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 3:
raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
return {"item_id": item_id}
예외처리기를 만들고 그 안에서 또 기본 예외처리를 하고 있다.
나는 어디에 쓰일지 크게 몰라서 적어만 둔다.