본문 바로가기
Web 개발/FAST API (인프런 강의 내용)

1 실습6 ERROR 처리

by yororing 2024. 4. 18.

앞 단계 참조 링크:

 

현재 파일 내용:

# .../todos/src/main.py 내용

from fastapi import FastAPI, Body
from pydantic import BaseModel

app = FastAPI()

# 첫 화면 API
@app.get("/")
def health_check_handler():
    return {"ping": "pong"}

# POST 생성을 위해 사용자로부터 전달받을 request 클래스 생성
class CreateToDoRequest(BaseModel):
    id: int
    contents: str
    is_done: bool

# 데이터베이스 역할하는 딕셔너리 생성
todo_data = {
    1: {
        "id": 1,
        "content": "실전! FastAPI 섹션 0 수강",
        "is_done": True,
    },
    2: {
        "id": 2,
        "content": "실전! FastAPI 섹션 1 수강",
        "is_done": False,
    },
    3: {
        "id": 3,
        "content": "실전! FastAPI 섹션 2 수강",
        "is_done": False,
    },
}

# GET Method 사용하여 전체 조회 API
@app.get("/todos")
def get_todos_handler(order: str | None = None):
    rt = list(todo_data.values())
    if order and order == "DESC":
       return rt[::-1]
    return rt

# GET Method 사용하여 단일 조회 API
@app.get("/todos/{todo_id}")
def get_todo_handler(todo_id: int):
    return todo_data.get(todo_id, {})

# POST Medthod 사용하여 todo 생성 API
@app.post("/todos")
def create_todos_handler(request: CreateToDoRequest):
    todo_data[request.id] = request.dict()
    return todo_data[request.id]

# PATCH Method 사용하여 is_done 값 수정 API
@app.patch("/todos/{todo_id}")
def update_todo_handler(
    todo_id: int,
    is_done: bool = Body(..., embed=True)
    ):
    todo = todo_data.get(todo_id)
    if todo:
        todo["is_done"] = is_done
        return todo
    return {}

# DELETE Method 사용하여 todo 아이템 삭제 API
@app.delete("/todos/{todo_id}")
def delete_todo_handler(todo_id: int):
    todo_data.pop(todo_id, None)
    return todo_data

 

06 상태 코드 (Status Code) 적용하기

  • 목표: ToDo API에 상태 코드 적용하기
  • 사용방법: 데코레이터에 status_code=n을 명시적으로 작성 (기본값 = 200)
    • Method (GET, POST, PUT, PATCH, DELETE 등)를 mapping해준 decorator에 'status_code=n'을 반점 (,) 뒤에 적어주면 됨 (default = 200)
  • 자주 사용되는 상태 코드들:
큰 분류 상태 코드 설명
200번대
(요청 성공)
200 OK 요청 성공, 범용적, GET / POST / PUT / PATCH
201 Created 요청 성공, 새로운 자원 생성, POST
204 No Content 요청 성공, 응답할 자원 없음, DELETE
400번대
(요청 실패)
(의도적으로
서버에서
발생시키는 에러)
400 Bad Request 요청 실패, 요청이 잘못된 경우 (query param, request body)
401 Unauthorized 인증 실패
403 Forbidden 권한 문제 또는 잘못된 method
404 Not Found 존재하지 않은 자원 요청 또는 잘못된 endpoint 요청
500번대
(다른 에러)
(예상치 못한
상황에서
발생하는 에러)
500 Internal Server Error 범용적인 서버 에러 (원인 불명확)
502 Bad Gateway Reverse Proxy (e.g., Nginx)에서 서버(upstream 서버)의 응답을 처리할 수 없는 경우 
503 Service Unavailable 서버가 요청을 처리할 수 없는 경우 (e.g., 서버의 일시적 부하, 서버 다운)

 

1. GET 전체 조회 항상 status_code=200 설정

1) 데코레이터에 status_code=n 명시적으로 작성

# ../todos/src/main.py
...
@app.get("/todos", status_code=200)	# 추가됨
def get_todos_handler(order:str | None = None):
	ret = list(todo_data.values())
    if order and order == "DESC":
    	return ret[::-1]
    return ret
...

2) FastAPI 웹 서버 자동 재시작 (--reload 옵션 사용)

  • 웹 서버 실행 후 코드 변경 시 변경사항을 SwaggerUI에 반영하기 위해선 웹 서버 재시작 (uvicorn 종료 후 다시 실행) 필수
  • reload 옵션 사용 시 변화가 감지되면 자동으로 서버를 FastAPI가 재시작 됨
  • 방금 수정한 main.py를 저장 (ctrl + S) 하면 자동으로 재시작되어 변경사항이 반영됨
  • 우선 시작 안했으면 시작하기
$ uvicorn main:app --reload
INFO:     Will watch for changes in these directories: ['C:\\Users\\관리자\\Desktop\\projects\\todos\\src']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [352] using StatReload
INFO:     Started server process [8700]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
  • 시작 후 문서를 변경했으면 저장하기 (Ctrl + S)

3) Swagger UI 문서 확인

  • 브라우저에 http://127.0.0.1:8000/docs 입력 또는 새로고침
  • GET /todos Get Todos Handler (전체 조회) 클릭 > Try it out > Execute 클릭 시
  •  

  • Server Response를 보면 Code 아래 상태 코드 200이 잘 반환되는 것 확인

2. GET 단일 조회 예외처리 설정 - 없는 자원 요청 (Resource Not Found)

1) 필요한 패키지 참조:  fastapi의 HTTPException

# .../todos/src/main.py 내용

from fastapi import FastAPI, Body, HTTPException  # 추가됨
from pydantic import BaseModel
...

2) 데코레이터에 status_code=n 작성raise로 Exception 발생

# .../todos/src/main.py
...
@app.get("/todos/{todo_id}", status_code=200)	# 추가됨
def get_todo_handler(todo_id: int):
    todo = todo_data.get(todo_id)		# 추가됨
    if todo:					# 추가됨
        return todo				# 추가됨
    raise HTTPException(status_code=404, detail="ToDo Not Found")  # 추가됨
...
  • @app.get("todos/{todo_id}, status_code=200)
    • 정상적으로 처리될 시 상태 코드 200을 반환
  • todo = todo_data.get(todo_id)
    • 사용자로부터 받은 todo_id를 todo_data에서 조회하여 해당 todo_id의 todo 값을 todo 변수에 할당
    • todo 값이 없을 경우 (즉, 사용자로부터 받은 todo_id 값이 todo_data에 없을 경우) 기본값=None을 할당 
  • if todo: return todo
    • todo 값이 있을 경우 해당 todo_id의 todo 값을 반환
  • raise HTTPException(status_code=404, detail="ToDo Not Found")
    • todo 값이 없을 경우 raise를 통해 exception 발생시켜줌
    • 상태 코드는 404
    • detail 인자를 통해 원하는 error message 표현

3) FastAPI 웹 서버 자동 재시작 (--reload 옵션 사용)

  • 웹 서버 실행 후 코드 변경 시 변경사항을 SwaggerUI에 반영하기 위해선 웹 서버 재시작 (uvicorn 종료 후 다시 실행) 필수
  • reload 옵션 사용 시 변화가 감지되면 자동으로 서버를 FastAPI가 재시작 됨
  • 방금 수정한 main.py를 저장 (ctrl + S) 하면 자동으로 재시작되어 변경사항이 반영됨
  • 우선 시작 안했으면 시작하기
  • 시작 후 문서를 변경했으면 저장하기 (Ctrl + S)
# main.py에서 Ctrl + S 시 터미널에 다음이 출력됨
WARNING:  StatReload detected changes in 'main.py'. Reloading...
INFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [8700]
Process SpawnProcess-1:
Traceback (most recent call last):
  File "C:\Users\관리자\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\관리자\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 684, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\관리자\AppData\Local\Programs\Python\Python312\Lib\multiprocessing\process.py", line 314, in _bootstrap
    self.run()
  File "C:\Users\관리자\AppData\Local\Programs\Python\Python312\Lib\multiprocessing\process.py", line 108, in run    
    self._target(*self._args, **self._kwargs)
  File "C:\Users\관리자\AppData\Local\Programs\Python\Python312\Lib\site-packages\uvicorn\_subprocess.py", line 78, in subprocess_started
    target(sockets=sockets)
  File "C:\Users\관리자\AppData\Local\Programs\Python\Python312\Lib\site-packages\uvicorn\server.py", line 65, in run
    return asyncio.run(self.serve(sockets=sockets))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\관리자\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Users\관리자\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 123, in run
    raise KeyboardInterrupt()
KeyboardInterrupt
INFO:     Started server process [14232]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

4) Swagger 문서 확인

  • 브라우저에 http://127.0.0.1:8000/docs 입력 또는 새로고침
  • GET /todos/{todo_id} Get Todo Handler (단일 조회) 클릭 > Try it out 클릭
  • todo_id 에 1 적기 (존재하는 값) > Execute 클릭 시
  •  
  •  상태 코드 200이 잘 반환되는 것 확인
  • todo_id 에 10 적기 (존재하지 않는 값) > Execute 클릭 시
  • 상태 코드 404가 반환됨 + 'detail'에 'ToDo Not Found'라고 출력되는 것 확인

3. POST 항상 status_code=201 설정 - 새로운 자원 생성

1) 데코레이터에 status_code=n 명시적으로 작성

# ../todos/src/main.py
...
@app.post("/todos", status_code=201)	# 추가됨
def create_todo_handler(request: CreateToDoRequest):
    todo_data[request.id] = request.dict()
    return todo_data[request.id]
...

2) FastAPI 웹 서버 자동 재시작 (--reload 옵션 사용)

  • 웹 서버 실행 후 코드 변경 시 변경사항을 SwaggerUI에 반영하기 위해선 웹 서버 재시작 (uvicorn 종료 후 다시 실행) 필수
  • reload 옵션 사용 시 변화가 감지되면 자동으로 서버를 FastAPI가 재시작 됨
  • 방금 수정한 main.py를 저장 (ctrl + S) 하면 자동으로 재시작되어 변경사항이 반영됨
  • 우선 시작 안했으면 시작하기
  • 시작 후 문서를 변경했으면 저장하기 (Ctrl + S)

3) Swagger 문서 확인

  • 브라우저에 http://127.0.0.1:8000/docs 입력 또는 새로고침
  • POST / todos CreateTodos Handler (todo 생성) 클릭 > Try it out 클릭
  • Request body에 다음과 같이 입력
{
  "id": 4,
  "contents": "test",
  "is_done": true
}
  • Execute 클릭 시
  • 상태 코드 201이 반환됨 + 'id'값을 4로 갖고 있는 todo가 response에 반환되는 것 확인

4. PATCH 예외처리 설정 - 없는 자원 요청 (Resource Not Found)

1) 필요한 패키지 참조:  fastapi의 HTTPException

# .../todos/src/main.py 내용

from fastapi import FastAPI, Body, HTTPException  # 추가됨
from pydantic import BaseModel
...

2) 데코레이터에 status_code=n 명시적으로 작성

# ../todos/src/main.py
...
@app.patch("/todos/{todo_id}", status_code=200)  # 추가됨
def update_todo_handler(
    todo_id: int,
    is_done: bool = Body(..., embed=True)
):
    todo = todo_data.get(todo_id)
    if todo:
        todo["is_done"] = is_done
        return todo
    raise HTTPException(status_code=404, detail="ToDo Not Found")  # 변경됨
...
  • @app.patch("todos/{todo_id}, status_code=200)
    • 정상적으로 처리될 시 상태 코드 200을 반환
  • raise HTTPException(status_code=404, detail="ToDo Not Found")
    • todo 값이 없을 경우 raise를 통해 exception 발생시켜줌
    • 상태 코드는 404
    • detail 인자를 통해 원하는 error message 표현

3) FastAPI 웹 서버 자동 재시작 (--reload 옵션 사용)

  • 웹 서버 실행 후 코드 변경 시 변경사항을 SwaggerUI에 반영하기 위해선 웹 서버 재시작 (uvicorn 종료 후 다시 실행) 필수
  • reload 옵션 사용 시 변화가 감지되면 자동으로 서버를 FastAPI가 재시작 됨
  • 방금 수정한 main.py를 저장 (ctrl + S) 하면 자동으로 재시작되어 변경사항이 반영됨
  • 우선 시작 안했으면 시작하기
  • 시작 후 문서를 변경했으면 저장하기 (Ctrl + S)

3) Swagger 문서 확인

  • 브라우저에 http://127.0.0.1:8000/docs 입력 또는 새로고침
  • PATCH /todos/{todo_id} Update Todo Handler (todo 수정) 클릭 > Try it out 클릭
  • Parameters의 todo_id에 3 (존재하는 값) 입력; Request body에 '{"is_done": true}' 입력
  • Execute 클릭 시 
  •  
  • 상태 코드 200이 반환됨 + 'id'가 3인 todo의 'is_done' 상태가 true로 수정/업데이트된 것 확인
  • Parameters의 todo_id에 7 (존재하지 않는 값) 입력; Request body에 '{"is_done": true}' 입력 Execute 클릭 시
  • 상태 코드 404가 반환됨 + 'detail'에 'ToDo Not Found'라고 출력되는 것 확인

5. DELETE 예외처리 설정 - 응답할 자원 없음 및 없는 자원 요청 (Resource Not Found)

1) 데코레이터에 status_code=n 명시적으로 작성

# ../todos/src/main.py
...
@app.delete("/todos/{todo_id}", status_code=204)  # 추가됨
def delete_todo_handler(todo_id: int):
    todo = todo_data.pop(todo_id, None)           # 변경됨
    if todo:                                      # 추가됨
        return                                    # 추가됨
    raise HTTPException(status_code=404, detail="ToDo Not Found")  # 추가됨
...
  • @app.delete("todos/{todo_id}, status_code=204)
    • 204는 삭제 처리 시 사용 → 응답할 자원이 없을 때 사용
  • todo = todo_data.pop(todo_id, None)
    • todo_data에서 사용자로부터 받은 todo_id (key 값)에 해당하는 value 값을 삭제한 todo_data를 todo변수에 담음
    • 사용자로부터 받은 todo_id 값 (key 값)이 todo_data에 없을 경우 None을 todo 변수에 담음
  • if todo: return           
    • 사용자로부터 받은 todo_id (key 값)이 기존 todo_data에 있다면 아무것도 반환되지 않고 상태 코드 204가 출력됨
      • 기존의 todo를 삭제한 것이기에 반환값이 없어서 return을 아예 안 주거나 return을 비워서 줘도 됨
  • raise HTTPException(status_code=404, detail="ToDo Not Found")
    • todo 값이 없을 경우 raise를 통해 exception 발생시켜줌
    • 상태 코드는 404
    • detail 인자를 통해 원하는 error message 표현

2) FastAPI 웹 서버 자동 재시작 (--reload 옵션 사용)

  • 웹 서버 실행 후 코드 변경 시 변경사항을 SwaggerUI에 반영하기 위해선 웹 서버 재시작 (uvicorn 종료 후 다시 실행) 필수
  • reload 옵션 사용 시 변화가 감지되면 자동으로 서버를 FastAPI가 재시작 됨
  • 방금 수정한 main.py를 저장 (ctrl + S) 하면 자동으로 재시작되어 변경사항이 반영됨
  • 우선 시작 안했으면 시작하기
  • 시작 후 문서를 변경했으면 저장하기 (Ctrl + S)

3) Swagger 문서 확인

  • 브라우저에 http://127.0.0.1:8000/docs 입력 또는 새로고침
  • DELETE /todos/{todo_id} Delete Todo Handler (todo 삭제) 클릭 > Try it out 클릭
  • Parameters의 todo_id에 3 입력 (존재하는 값) > Execute 클릭 시
  • Server response를 보면
    • 상태 코드 204이 반환되는 것 확인
    • Response headers만 있는 것 확인
    • Response body가 없다는 것은 반환값이 없다는 것을 의미 (다른 API Server Response와 비교해보기)
  • Parameters의 todo_id에 7 (존재하지 않는 값) 입력 > Execute 클릭 시
  • Server response를 보면
    • 상태 코드 404가 되는 것 확인
    • Response body에서 'detail'로 'ToDo Not Found'가 출력되는 것 확인

 

이때까지의 코드들:

# .../todos/src/main.py 내용

from fastapi import FastAPI, Body, HTTPException
from pydantic import BaseModel

app = FastAPI()

# 첫 화면 API
@app.get("/")
def health_check_handler():
    return {"ping": "pong"}

# POST 생성을 위해 사용자로부터 전달받을 request 클래스 생성
class CreateToDoRequest(BaseModel):
    id: int
    contents: str
    is_done: bool

# 데이터베이스 역할하는 딕셔너리 생성
todo_data = {
    1: {
        "id": 1,
        "content": "실전! FastAPI 섹션 0 수강",
        "is_done": True,
    },
    2: {
        "id": 2,
        "content": "실전! FastAPI 섹션 1 수강",
        "is_done": False,
    },
    3: {
        "id": 3,
        "content": "실전! FastAPI 섹션 2 수강",
        "is_done": False,
    },
}

# GET Method 사용하여 전체 조회 API
@app.get("/todos", status_code=200)
def get_todos_handler(order: str | None = None):
    rt = list(todo_data.values())
    if order and order == "DESC":
       return rt[::-1]
    return rt

# GET Method 사용하여 단일 조회 API
@app.get("/todos/{todo_id}", status_code=200)
def get_todo_handler(todo_id: int):
    todo = todo_data.get(todo_id)
    if todo:
        return todo
    raise HTTPException(status_code=404, detail="ToD Not Found")

# POST Medthod 사용하여 todo 생성 API
@app.post("/todos", status_code=201)
def create_todos_handler(request: CreateToDoRequest):
    todo_data[request.id] = request.dict()
    return todo_data[request.id]

# PATCH Method 사용하여 is_done 값 수정 API
@app.patch("/todos/{todo_id}", status_code=200)
def update_todo_handler(
    todo_id: int,
    is_done: bool = Body(..., embed=True)
    ):
    todo = todo_data.get(todo_id)
    if todo:
        todo["is_done"] = is_done
        return todo
    raise HTTPException(status_code=404, detail="ToDo Not Found")

# DELETE Method 사용하여 todo 아이템 삭제 API
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo_handler(todo_id: int):
    todo = todo_data.pop(todo_id, None)
    if todo:
        return
    raise HTTPException(status_code=404, detail="ToDo Not Found")