flask-smorest

황지원·2024년 2월 7일
post-thumbnail

flask-smorest를 활용하여 REST API 생성

1. flask-smorest 란?

  • REST API 를 쉽게 작성할 수 있도록 도와주는 Flask 라이브러리
  • Flask-RESTful 보다 더 많은 기능과 OpenAPI(Swagger) 문서 자동 생성 기능을 제공

2. Library 설치

pip3 install flask-smorest

3. API 구축 (1) OpenAPI 설정

OpenAPI는 RESTAPI를 더 쉽고 이뻐보이게 하는 툴

(1) app.py
✅ flask app 진입점

from flask import Flask
from flask_smorest import Api
from api import blp

app = Flask(__name__)

# OpenAPI 관련 설정
app.config["API_TITLE"] = "My API"
app.config["API_VERSION"] = "v1"
app.config["OPENAPI_VERSION"] = "3.1.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
# 이 swagger 가 페이지를 이뻐보이게 하는 것

app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"

api = Api(app)
api.register_blueprint(blp)

if __name__ == "__main__":
    app.run(debug=True)

4. API 구축 (2) Schemas 설정

(2) schemas.py

✅ 데이터 검증과 직렬화에 사용되는 Marshmallow 스키마를 정의(데이터 스키마 검증)

from marshmallow import Schema, fields
# marshmallow 라는 모듈에서 Schema와 fields를 불러옴

class ItemSchema(Schema)
# 클래스명을 ItemSchema 라고 하고 marshmallo 에서의 Schema를 상속받음

    id = fields.Int(dump_only=True)
    name = fields.Str(required=True)
    description = fields.Str()
    # 위 세 줄의 형태를 객체라고 하며, 이 객체를 나중에 직렬화하거나
    역직렬화할 때 실제로 거기에 데이터에서 뭔가 잘못된 것이 있는지 체크해줌
    
  

데이터가 몇개 없을 때는 스키마를 만드는 것이 귀찮겠지만 나중에 데이터가 많고 구조가 복잡해질 경우에 스키마를 사용하면 안심하게 데이터를 프론트로 보내던 받던 할 수 있다.

<마시멜로우 설치>

pip3 install marshmallow

5. API 구축 (3) (개념) Blueprint

:블루프린트는 애플리케이션의 특정 기능 별로 라우팅, 뷰,함수, 정적 파일 등의 관리가 가능하다.

✅ API가 복잡해질수록 관리의 필요성이 증가함

🔔 주요기능

  • 모듈화 : 블루프린트를 사용하면 애플리케이션의 서로 다른 부분을 별도의 모듈로 나누어 관리할 수 있다. 이는 코드의 재사용성을 높이고, 유지보수를 용이하게 한다.

  • 블루프린트는 자체 URL 규칙을 가지고있으며, 이를 통해 애플리케이션의 라우팅을 체계적으로 관리할 수 있다.

  • 기능별 분리 : 블루프린트를 사용하면 특정 기능에 대한 라우팅, 뷰, 함수, 에러 핸들러, 템플릿 등을 그룹화할 수 있다.

from flask import Flask, Blueprint, render_template, request

app = Flask(__name__)

# 첫 번째 블루프린트
my_blueprint = Blueprint('my_blueprint', __name__)

@my_blueprint.route('/hello')
def hello():
    return "Hello from my blueprint!"

@my_blueprint.route('/greet/<name>')
def greet(name):
    return f"Hello, {name}!"

# 두 번째 블루프린트
another_blueprint = Blueprint('another_blueprint', __name__, url_prefix='/another')

# /another/world
@another_blueprint.route('/world')
def world():
    return "Hello, world, from another blueprint!"

# /another/echo
@another_blueprint.route('/echo', methods=['POST'])
def echo():
    data = request.json
    return f"Received: {data}"

# 블루프린트에 템플릿을 사용하는 예제
@another_blueprint.route('/template')
def using_template():
    return render_template('example.html')

# 세 번째 블루프린트
third_blueprint = Blueprint('third_blueprint', __name__, url_prefix='/third')

@third_blueprint.route('/bye')
def goodbye():
    return "Goodbye from the third blueprint!"

# 애플리케이션에 블루프린트 등록
app.register_blueprint(my_blueprint)
app.register_blueprint(another_blueprint)
app.register_blueprint(third_blueprint)

if __name__ == "__main__":
    app.run(debug=True)

6. API 구축 (4) (개념) Abort

  • abort 함수는 API 개발 과정에서 오류 처리를 위해 사용된다.
  • abort 를 사용하여 클라이언트에 오류상태와 메시지를 전달한다.

✅ Flask 와 Flask-smorest 에서 abort 함수는 요청 처리 중에 오류가 발생했을 때 사용된다. 이 함수를 호출하면 특정 HTTP 상태와 함께 응답이 클라이언트한테 전송되며, 선택적으로 오류메시지나 추가정보를 포함시킬 수 있다.

from flask_smorest import abort

# 오류 상황에서 abort 호출
abort(404, message = "Resource not found")

✅ abort를 사용한 오류 처리

from flask import Flask, abort

app = Flask(__name__)

@app.route('/example')
def example():
    error_condition = True
    
    # 어떠한 조건에서 오류를 발생시키고 처리
    if error_condition:
        abort(500, description = "An error occurred while processing the request.")
    
     # 정상적인 응답
    return "Success"

✅ abort를 사용하지 않은 오류 처리

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/example')
def example():
    # 어떠한 조건에서 오류를 발생시키고 처리
    error_condition = True

    if error_condition:
        error_response = jsonify({"error": "An error occurred while processing the request."})
        error_response.status_code = 500
        return error_response

    # 정상적인 응답
    return "Success!"

⚠️ 주요 차이점 및 이유

1) HTTP 상태 코드 전송:

  • abort 를 사용하면 함수에서 바로 HTTP 상태 코드를 지정할 수 있다. 반면에 jsonify와 같은 방법을 사용하면 상태 코드를 별도로 설정해야한다.

2) 의미있는 오류 메시지 전달:

  • abort를 사용하면 오류 메시지를 description 매개 변수를 통해 쉽게 전달할 수 있다.
    jsonify 를 사용하면 응답 본문에 메시지를 추가해야한다.

3) RESTful API 표준화:

  • RESTful API에서는 특정한 상태코드와 오류 메시지의 표준을 정하는 것이 중요하다. abort를 사용하면 이러한 표준을 쉽게 따를 수 있다.

4) 가독성과 유지보수성:

  • abort를 사용하면 오류 처리 부분이 더 간결하고 가독성이 높아진다.
    코드가 명확하게 오류 상황을 처리하고 해당 상태 코드를 클라이언트에게 반환한다.

    ❗️ abort 함수 사용 시 주의사항

  • 오류 메시지를 명확하게 제공하여 클라이언트가 오류의 원인을 쉽게 이해할 수 있도록 한다.

  • 안전하지 않은 정보는 오류 메시지에 포함하지 않도록 주의한다.
    (ex. 시스템 경로, 개인정보 등)

7. API 구축 (5) API 생성

(3) api.py
: api 생성 및 블루프린트 정의

from flask.view import MethodView
from flask_smorest import Blueprint, abort
from schemas import ItemSchema

# 블루프린트 생성: 'items'라는 이름으로, URL 접두사는 '/items'
blp = Blueprint("items", "items", url_prefix = "/items", description = "Operation on items")

# 간단한 데이터 저장소 역할을 하는 리스트
items = []

# 'ItemList' 클래스 - GET 및 POST 요청을 처리
@blp.route("/")
class ItemList(MethodView):
    @blp.response(200)
    def get(self):
        # 모든 아이템을 반환하는 GET 요청 처리
        return items
        
    @blp.arguments(ItemSchema)
    @blp.response(201, description = "Item added")
    def post(self, new_data):
        # 새 아이템을 추가하는 POST 요청 처리
        items.append(new_data)
        return new_data
        
# 'Item' 클래스 - GET, PUT, DELETE 요청을 처리
@blp.route("/<int:item_id>")
class Item(MethodView):
    @blp.response(200)
    def get(self, item_id):
        # 특정 ID를 가진 아이템을 반환하는 GET 요청 처리
        # next() => 반복문에서 값이 있으면 값을 반환하고, 없으면 None 을 반환함
        # next 는 조건을 만족하는 첫번째 아이템을 반환하고, 그 이후의 아이템은 무시한다.
        item = next((item for item in items if item["id"] == item_id), None)
        if item is None:
            abort(404, message= "Item not found")
        return item
        
    @blp.arguments(ItemSchema)
    @blp.response(200, description = "Item updated")
    def put(self, new_data, item_id):
        # 특정 ID를 가진 아이템을 업데이트 하는 PUT 요청 처리
        item = next((item for item in items if item["id"] == item_id), None)
        item.update(new_data)
        return item
        
    @blp.response(204, description = "Item deleted")
    def delete(self, item_id):
        # 특정 ID 를 가진 아이템을 삭제하는 DELETE 요청 처리
        global items
        if not any(item for item in items if item["id"] == item_id):
            abort(404, message = "Item not found")
        items = [item for item in items if item["id"] != item_id]
        return ''
profile
개발 광기를 드러내보쟈..

0개의 댓글