Response model(응답 모델)
응답 모델은 API 라우트 경로가 반환하는 데이터의 템플릿 역할을 한다.
애플리케이션은 클라이언트에 반환할 응답을 렌더링하기 위해 pydantic을 사용한다.
class TodoItems(BaseModel):
todos: List[TodoItem]
class Config:
schema_extra = {
"example": {
"todos": [{"item": "Example schema 1!"}, {"item": "Example schema 2!"}]
}
}
Error handling(오류 처리)
오류 처리는 애플리케이션에서 발생하는 오류를 처리하는 로직과 방법을 의미하며, 오류를 반환할 때는 오류 상태 코드와 오류 메시지를 포함시켜야 한다.
FastAPI에서는 HTTPException 클래스를 사용해 예외를 발생시켜 처리한다.
HTTPException 인수
status_code: 오류 상태 코드
detail: 오류 메시지
headers: 헤더를 요구하는 응답을 위한 선택적 인수
@todo_router.post("/todo", status_code=201)
async def add_todo(todo: Todo) -> dict:
todo_list.append(todo)
return {"message": "Todo added successfully."}
위 코드는 데코레이터 함수에 status_code를 추가해서 기본 응답 코드(200)을 201로 변경하도록 하는 방법이다.
@todo_router.get("/todo/{todo_id}")
async def get_single_todo(
todo_id: int = Path(..., title="The ID of the todo to retrieve.")
) -> dict:
for todo in todo_list:
if todo.id == todo_id:
return {"todo": todo}
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Todo with supplied ID doesn't exist.",
)
위 코드는 HTTPException 클래스로 예외를 발생시켜 에러 상태 코드와 메시지를 전달하는 방법이다.
Templating(템플릿팅)
템플릿팅은 API가 보낸 다양한 형식의 데이터를 화면에 표시하는 프로세스다.
템플릿은 웹 애플리케이션상 프론트엔드 컴포넌트처럼 처리된다.
Jinja는 파이썬으로 작성된 템플릿팅 엔진으로, API 응답을 쉽게 렌더링할 수 있게 해준다.
$ pip install jinja2 python-multipart
Jinja는 중괄호 {}를 사용해서 html, 텍스트 등을 표현식 및 구문과 구분한다.
{{}} 구문은 변수 블록(variable block)이라고 하며 이 안에 변수를 지정한다.
{% … %} 구문은 if/else, 반복, 매크로 같은 구조(처리)를 제어할 때 사용한다.
{# … #} 구문은 주석을 기입할 때 사용한다.
필터 |는 가장 중요한 요소이며 특정 함수를 실행할 수 있게 해준다.
Jinja 템플릿 문법 가이드 문서
home.html – 부모 템플릿
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Packt Todo Application</title>
<link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" rel="stylesheet">
<link crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css"
integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" rel="stylesheet">
</head>
<body>
<header>
<nav class="navar">
<div class="container-fluid">
<center>
<h1>Packt Todo Application</h1>
</center>
</div>
</nav>
</header>
<div class="container-fluid">
{% block todo_container %}{% endblock %}
</div>
</body>
</html>
todo.html – 자식 템플릿(extends를 통해 home.html 상속)
{% extends "home.html" %}
{% block todo_container %}
<main class="container">
<hr>
<section class="container-fluid">
<form method="post">
<div class="col-auto">
<div class="input-group mb-3">
<input aria-describedby="button-addon2" aria-label="Add a todo" class="form-control" name="item"
placeholder="Purchase Packt's Python workshop course" type="text"
value="{{ item }}"/>
<button class="btn btn-outline-primary" data-mdb-ripple-color="dark" id="button-addon2"
type="submit">
Add Todo
</button>
</div>
</div>
</form>
</section>
{% if todo %}
<article class="card container-fluid">
<br/>
<h4>Todo ID: {{ todo.id }} </h4>
<p>
<strong>
Item: {{ todo.item }}
</strong>
</p>
</article>
{% else %}
<section class="container-fluid">
<h2 align="center">Todos</h2>
<br>
<div class="card">
<ul class="list-group list-group-flush">
{% for todo in todos %}
<li class="list-group-item">
{{ loop.index }}. <a href="/todo/{{ loop.index }}"> {{ todo.item }} </a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</section>
</main>
{% endblock %}
html 템플릿 파일의 예시이다. 루트 위치 templates 디렉토리 안에 템플릿 파일을 작성한다.
부모 템플릿의 todo_container 변수 블록에 자식 템플릿의 todo_container 블록에 담긴 콘텐츠를 넣어서 반환할 수 있다.
todo 템플릿에서는 {% if / else %} 구문을 통해 todo 변수가 전달 되었을 때만 todo 정보가 표시되고, 없으면 else 블록에 있는 콘텐츠를 표시하도록 작성했다.
class Todo(BaseModel):
id: Optional[int] = Field(None)
item: str
@classmethod
def as_form(cls, item: str = Form(...)):
return cls(item=item)
class Config:
schema_extra = {"example": {"id": 1, "item": "Example schema!"}}
모델에서는 as_form이라는 클래스 메서드를 추가해준다.
from fastapi import APIRouter, Path, HTTPException, status, Request, Depends
from fastapi.templating import Jinja2Templates
from model import Todo, TodoItem, TodoItems
todo_router = APIRouter()
todo_list = []
templates = Jinja2Templates(directory="templates/")
@todo_router.post("/todo", status_code=201)
async def add_todo(request: Request, todo: Todo = Depends(Todo.as_form)):
todo.id = len(todo_list) + 1
todo_list.append(todo)
return templates.TemplateResponse(
"todo.html", {"request": request, "todos": todo_list}
)
위 코드는 Jinja가 templates
폴더에 있는 todo.html 템플릿 파일을 사용해서 응답을 반환하도록 하는 코드이다.
home.html의 템플릿을 상속한 todo.html이 화면에 뿌려지게 된다.
Related