Flask와 Smorest api를 이용한 책 데이터를 관리하는 기능 구현(Flask 2일차)
📌 개념 정리
책 관리 RESTful API는 Flask와 Flask-Smorest를 활용하여 구현됩니다. 이 API는 책 데이터를 관리하는 기능을 제공하며, 사용자는 책 목록을 조회하거나, 새 책을 추가하거나, 기존 책 정보를 수정하고 삭제할 수 있습니다. 주요 HTTP 메소드인 GET, POST, PUT, DELETE를 사용하여 다양한 기능을 구현합니다. Marshmallow를 사용해 데이터를 검증하고, 이를 통해 API와 클라이언트 간의 데이터 교환을 안전하게 처리합니다.
🚦 동작 원리 및 구조
이 책 관리 API는 크게 세 가지 부분으로 나뉩니다:
- app.py - Flask 애플리케이션을 설정하고, API를 실행하는 부분입니다. Flask-Smorest를 사용하여 API와 Swagger UI 설정을 처리합니다.
- schemas.py - Marshmallow를 사용하여 책 데이터를 위한 스키마를 정의하는 부분입니다. 책의 제목과 저자는 필수 항목으로 설정됩니다.
- api.py - 책 목록을 관리하는 API 엔드포인트를 구현한 부분으로, GET, POST, PUT, DELETE 메소드에 대한 처리가 이루어집니다.
API 엔드포인트는 /books로 시작하며, 특정 책을 조회하거나 수정하려면 /books/<int:book_id> 경로를 사용합니다.
💻 코드 예시 및 흐름 분석
app.py
이 파일은 Flask 애플리케이션을 설정하고, Flask-Smorest API를 등록하는 역할을 합니다.
from flask import Flask
from flask_smorest import Api
from api import book_blp
app = Flask(__name__)
app.config['API_TITLE'] = 'Book API'
app.config['API_VERSION'] = 'v1'
app.config['OPENAPI_VERSION'] = '3.0.2'
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
api = Api(app)
api.register_blueprint(book_blp)
if __name__ == '__main__':
app.run(debug=True)
설명: Flask를 사용해 애플리케이션을 만들고, Flask-Smorest를 통해 Swagger UI와 OpenAPI 문서를 설정합니다. book_blp는 api.py에서 정의한 블루프린트로, API 엔드포인트가 포함됩니다.
schemas.py
이 파일은 책의 데이터를 정의하는 Marshmallow 스키마를 포함하고 있습니다.
from marshmallow import Schema, fields
class BookSchema(Schema):
id = fields.Int(dump_only=True)
title = fields.String(required=True)
author = fields.String(required=True)
설명: 책 데이터의 필드를 정의하는 클래스입니다. title과 author는 필수 항목이고, id는 응답 데이터에서만 반환됩니다(dump_only).
api.py
이 파일에서는 책 목록을 관리하는 API 엔드포인트들을 구현합니다.
from flask.views import MethodView
from flask_smorest import Blueprint, abort
from schemas import BookSchema
book_blp = Blueprint('books', 'books', url_prefix='/books', description='Operations on books')
books = []
@book_blp.route('/')
class BookList(MethodView):
@book_blp.response(200, BookSchema(many=True))
def get(self):
return books
@book_blp.arguments(BookSchema)
@book_blp.response(201, BookSchema)
def post(self, new_data):
new_data['id'] = len(books) + 1
books.append(new_data)
return new_data
@book_blp.route('/<int:book_id>')
class Book(MethodView):
@book_blp.response(200, BookSchema)
def get(self, book_id):
book = next((book for book in books if book['id'] == book_id), None)
if book is None:
abort(404, message="Book not found.")
return book
@book_blp.arguments(BookSchema)
@book_blp.response(200, BookSchema)
def put(self, new_data, book_id):
book = next((book for book in books if book['id'] == book_id), None)
if book is None:
abort(404, message="Book not found.")
book.update(new_data)
return book
@book_blp.response(204)
def delete(self, book_id):
global books
book = next((book for book in books if book['id'] == book_id), None)
if book is None:
abort(404, message="Book not found.")
books = [book for book in books if book['id'] != book_id]
return ''
설명:
- BookList 클래스는 /books 경로에 대한 GET 및 POST 요청을 처리합니다.
- GET 요청은 책 목록을 반환하고,
- POST 요청은 새 책을 추가합니다.
- Book 클래스는 /books/<int:book_id> 경로에 대한 GET, PUT, DELETE 요청을 처리합니다.
- GET 요청은 특정 책을 조회하며,
- PUT 요청은 책 정보를 수정하고,
- DELETE 요청은 책을 삭제합니다.
🧪 실전 사례
예를 들어, 사용자가 /books 경로로 GET 요청을 보낸다면, 서버는 books 리스트에 저장된 모든 책 정보를 JSON 형태로 반환합니다. /books 경로로 POST 요청을 보내면, 새로운 책이 리스트에 추가되고, 클라이언트는 추가된 책 정보를 응답받습니다.
🧠 고급 팁 or 자주 하는 실수
- 책의 ID 관리: 실제 서비스에서는 ID가 중복되지 않도록 관리하기 위해 데이터베이스를 사용해야 합니다. 이 예제에서는 간단한 리스트를 사용하지만, 실제 구현 시에는 데이터베이스를 사용해야 합니다.
- 예외 처리: 예외 처리를 더욱 세밀하게 해주는 것이 좋습니다. 예를 들어, 책이 존재하지 않거나 데이터가 잘못된 경우에 대한 더 많은 예외 처리 로직을 추가할 수 있습니다.
✅ 마무리 요약 및 복습 포인트
- Flask와 Flask-Smorest를 사용하여 RESTful API를 만들 수 있다.
- Marshmallow를 사용하여 데이터를 검증하고 스키마를 정의할 수 있다.
- API 엔드포인트에서 GET, POST, PUT, DELETE를 구현하는 방법을 익혔다.
전체 코드
app.py
from flask import Flask
from flask_smorest import Api
from api import book_blp
app = Flask(__name__)
app.config['API_TITLE'] = 'Book API'
app.config['API_VERSION'] = 'v1'
app.config['OPENAPI_VERSION'] = '3.0.2'
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
api = Api(app)
api.register_blueprint(book_blp)
if __name__ == '__main__':
app.run(debug=True)
schemas.py
from marshmallow import Schema, fields
class BookSchema(Schema):
id = fields.Int(dump_only=True)
title = fields.String(required=True)
author = fields.String(required=True)
api.py
from flask.views import MethodView
from flask_smorest import Blueprint, abort
from schemas import BookSchema
book_blp = Blueprint('books', 'books', url_prefix='/books', description='Operations on books')
books = []
@book_blp.route('/')
class BookList(MethodView):
@book_blp.response(200, BookSchema(many=True))
def get(self):
return books
@book_blp.arguments(BookSchema)
@book_blp.response(201, BookSchema)
def post(self, new_data):
new_data['id'] = len(books) + 1
books.append(new_data)
return new_data
@book_blp.route('/<int:book_id>')
class Book(MethodView):
@book_blp.response(200, BookSchema)
def get(self, book_id):
book = next((book for book in books if book['id'] == book_id), None)
if book is None:
abort(404, message="Book not found.")
return book
@book_blp.arguments(BookSchema)
@book_blp.response(200, BookSchema)
def put(self, new_data, book_id):
book = next((book for book in books if book['id'] == book_id), None)
if book is None:
abort(404, message="Book not found.")
book.update(new_data)
return book
@book_blp.response(204)
def delete(self, book_id):
global books
book = next((book for book in books if book['id'] == book_id), None)
if book is None:
abort(404, message="Book not found.")
books = [book for book in books if book['id'] != book_id]
return ''