01. REST API

1. REST API란?

REST API의 의미를 알아보기 위해선 우선 각 단어의 뜻을 알아야한다.

REST란 Representational State Transfer의 약자로 Representational (표현), State (상태), Transfer (전송)의 의미다. 전송은 클라이언트와 서버 사이의 전송을 말한다.

API는 Application Programming Interface의 약자로 프로그램간의 상호작용을 뜻으로 데이터 교환이라고 생각하면 된다.

RESTful한 API는 자원(Resource, 데이터) 중심으로 설계되며, HTTP 프로토콜의 메소드(GET, POST, PUT, DELETE 등)을 사용하여 해당 자원에 대한 CRUD(Create, Read, Update, Delete) 작업을 수행한다.

자원에 대한 작업을 좀 더 풀어서 설명하자면

  • 자원 식별 : 사용자 정보 자원은 /users/{userId} URL를 통해 식별된다.
  • 자원 표현 : 사용자 정보는 JSON, XML의 형태로 클라이언트에게 전달된다.
  • 자원에 대한 행위 : 사용자 정보를 조회하기 위해 GET/users/{userId} 요청을 사용하고, 사용자를 생성하기 위해 POST/users 요청을 사용한다.
    • GET(자원 읽기), POST(자원 생성), PUT(자원 업데이트), DELETE(자원 삭제)

REST API는 다양해진 서비스의 형태(모바일 앱, 웹, 와치 앱, 티비 앱)에 대응하기 위해 등장했다. (안드로이드용, IOS 서버용, Homepage용 서버를 따로 구축하지 않아도 된다는 것이다!)
이를 위해 클라이언트의 형태와 상관없이 문자열 기반의 XML, JSON과 같은 데이터를 주고 받게 되었다. 기존에는 서비스가 대부분 웹의 형태였기 때문에 ASP, JSP, PHP 등을 사용했었다.

2. REST API 핵심 원칙

  1. 자원 기반의 URL
    : REST API는 인터넷 상의 '자원'을 다룬다. 여기서 '자원'이란 데이터베이스의 레코드, 문서, 이미지 등 인터넷에서 접근 가능한 모든 것을 의미한다.
  • 예시) 사용자 정보를 나타내는 자원이 있다. 이 자원은 일반적으로 /users라는 URL로 표현된다. 특정 사용자를 나타내려면, /users/123과 같이 고유 식별자(ID)를 URL에 추가해야한다.
  1. 상태 없음 (Statelessness)
    : '상태 없음'은 서버가 클라이언트의 상테 (로그인 상태나 이전 페이지 기록 등)을 기억하지 않는다는 것을 의미한다. 그래서 각 API 요청을 다른 요청과 독립적으로 처리되기 때문에 이전 요청에 대한 정보는 기억하지 않고 모든 요청은 필요한 모든 정보 (사용자 인정 정보와 같은)를 포함해야 한다.
    1. 독립적인 요청
    2. 서버의 상태 저장 없음.
    3. 세션 관리의 부재

  2. 표준화된 메소드 사용
    : REST API는 HTTP 표준 메소드를 사용하여 자원에 대한 다양한 작업을 수행한다. 여기서 표준 메소드라는 것은 GET, POST, PUT, DELETE를 의미한다. (GET - 조회, POST - 생성, PUT - 업데이트, DELETE - 삭제)

  3. 통신을 위한 표현 (Representation)
    : REST API는 클라이언트와 서버 간에 데이터를 주고 받을 때, 특정 형식을 사용한다. 이 형식은 API가 '표현'하는 방법을 결정한다.

  • 예시) 사용자 데이터가 JSON 형태로 전송되어 클라이언트에 표시될 수 있다. (JSON 형식은 REST API에서 매우 일반적으로 사용되는 데이터 표현 방식)

REST API의 이러한 원칙들은 인터넷 상의 자원에 대해 일관되고 효율적인 접근 방식을 제공한다. 이를 통해 다양한 클라이언트에서 웹 서버와 효과적으로 소통할 수 있게 된다.

3. REST API 구성 요소

  1. 자원 (Resource) - URI (/feeds/:feed_id)
    : 클라이언트는 위와 같은 URI 형식을 통해 서버에 데이터를 요청한다.

  2. 행위 (method, status) - HTTP Method
    : POST, GET, PUT, DELETE (HTTP 프로토콜 메서드)
    → CRUD

  3. 표현 (Representation)
    : 서버에서 클라이언트로의 데이터 전달 방법이다.
    ex) XML, JSON, TEXT, RSS

4. REST API 해석

1. 게시물 API

  • feeds/1
    • GET : id가 1인 게시글 데이터 보내줘
    • POST : id가 1인 게시글 만들어줘
    • PUT : id가 1인 게시글 수정해줘 (업데이트 해줘) → 이때는 업데이트할 데이터까지 보내줘야한다.
    • DELETE : id가 1인 게시글 삭제해줘
  • feeds/all
    • GET : 게시글을 전부 다 보내줘 (페이지 네이션 작업 필요. 30개씩 데이터가 보인다던지)
    • POST : X
    • PUT : X
    • DELETE : X
  • myinfo
    • GET : 내 정보 보여줘
    • POST : X
    • PUT : 내 정보를 수정해줘 (업데이트 해줘)
    • DELETE : 내 정보를 삭제해줘 (회원 탈퇴)
  • users/1
    • GET : 1번 유저 정보 보여줘

2. 인스타그램 API

HTTP Methon에 따라 분류해보았다.

  • GET
    1. /users/{username}
      : 특정 사용자의 프로필 정보를 조회
    2. /posts
      : 모든 게시물의 목록을 조회
    3. /posts/{post_id}/comments
      : 특정 게시물에 대한 댓글 목록을 조회
  • POST
    1. /posts
      : 새로운 게시물을 생성
    2. /posts/{post_id}/comments
      : 특정 게시물에 댓글을 작성
    3. /users/{username}/follow
      : 특정 사용자를 팔로우
  • put
    1. /posts/{post_id}
      : 특정 게시물 수정
  • DELETE
    1. /posts/{post_id}
      : 특정 게시물 삭제
    2. /users/{username}/follow
      : 특정 사용자 언팔로우

02. jsonify 활용하여 REST API 생성

1. jsonify란?

jsonify 함수는 Python 데이터를 JSON 형식으로 변환하여 HTTP 응답으로 반환하는 간편한 방법을 제공한다.
Python의 기본 데이터 타입(dict, list 등)을 JSON 문자열로 변환한다. 반환된 JSON 응답에는 기본적으로 application/json이라는 Content-Type 헤더가 포함된다.
Flask 1.1.x이후 버전에서는 jsonify를 사용하지 않고도 return 문에서 딕셔너리를 직접 반환할 수 있으며, Flask가 자동으로 JSON 응답으로 변환해 준다. 즉, jsonify는 기본적으로 사용되는 로직으로 변경되었기 때문에 사용성의 의미가 없어졌다. 단, 리스트나 객체의 형태 반환은 자동 변환이 안되는 것에 주의한다!

앞에서 계속 얘기한 JSON은 무엇인가?
JSON은 JavaScript Object Notation의 약자로 컴퓨터 간 상호작용을 할 때 자주 사용하는 형식으로 {key:value} 형태로 이루어진 데이터 포맷이다. (lik dict!)

{
    "glossary": {
        "title": "example glossary",
		"GlossDiv": {
            "title": "S",
			"GlossList": {
                "GlossEntry": {
                    "ID": "SGML",
					"SortAs": "SGML",
					"GlossTerm": "Standard Generalized Markup Language",
					"Acronym": "SGML",
					"Abbrev": "ISO 8879:1986",
					"GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
						"GlossSeeAlso": ["GML", "XML"]
                    },
					"GlossSee": "markup"
                }
            }
        }
    }
}
  • 위 코드를 JSON Formatter & Validator홈페이지에서 Process로 검색하게 되면 JSON 데이터가 어떻게 구성이 되어 있는지 볼 수 있다.

2. Library import

# 필요한 라이브러리 불러오기.
from flask import Flask		# 기본
from flask import request
from flask import jsonify

3. GET Method

# 1. 전체 게시물을 불러오는 API
@app.route('/api/v1/feeds', methods=['GET'])
def show_all_feeds():
   # return jsonify({'result':'success', 'data': {"feed1":"data", "feed2":"data2"}})
	return {'result':'success', 'data': {"feed1":"data", "feed2":"data2"}}

# 2. 특정 게시물을 불러오는 API
@app.route('/api/v1/feeds/<int:feed_id>', methods=['GET'])
def show_one_feed(feed_id):
   print(feed_id)
   return jsonify({'result':'success', 'data': {"feed1":"data"}})

4. POST Method

@app.route('/api/v1/feeds', methods=['POST'])
def create_one_feed():
    name = request.form['name']
    age = request.form['age']
    print(name, age)
    return jsonify({'result':'success'})

코드를 위처럼 작성한 뒤 우린 아직 실제로 데이터를 가지고 있는 것이 없기 때문에 postman이라는 프로그램을 통해 코드가 작동되는 지만 확인해보려고 한다.
처음 postman 프로그램을 사용하는 거라면 프로그램 설치, 로그인 (회원가입), 새 Workspaces, Collections 생성한 뒤 실행하면

위 사진과 같은 결과를 얻을 수 있다.

5. 데이터 추가하기

datas = [{"items": [{"name": "item1", "price": 10}]}]

@app.route("/api/v1/datas", methods=['GET'])
def get_datas():
    return {'datas':datas}

@app.route("/api/v1/datas", methods=['POST'])
def create_data():
    request_data = request.get_json()
    new_data = {"items": request_data.get("items", [])}
    datas.append(new_data)
    return new_data, 201
  • 위와 같이 추가된 데이터는 잠깐 memory 영역에 저장이 되고, flask를 종료하면 데이터가 사라진다.

코드 마지막에 사용된 201은 response status code 인데 201 이 외에도 다양한 code는 HTTP response status codes - MDN 사이트에서 확인할 수 있다.


03. flask-restfulapi 활용하여 REST API 생성

1. flask-restful api 설치

flask-restful api의 경우 외부에서 가져오는 모듈이기 때문에 아래 코드를 실행하여 설치할 수 있다.

> pip install flask-restful

또한 관련 설명이나 예제는 Flask-RESTful사이트를 통해 확인할 수 있다.

2. Library import

from flask_restful import Api, Resource

api = Api(app)	# app 넘겨주기 (initializing)

위 코드로 라이브러리를 불러와 flask-restful api를 사용할 수 있다.

3. flask-restful api를 활용하여 API 구축

  • app.py
from flask import Flask, request
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

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

# Item 리소스관련 코드
class Item(Resource):
    # 특정 아이템 조회
    def get(self, name):
        for item in items:
            if item['name'] == name:
                return item
        return {'message': 'Item not found'}, 404

    # 새 아이템 추가
    def post(self, name):
        for item in items:
            if item['name'] == name:
                return {'message': f"An item with name '{name}' already exists."}, 400

        data = request.get_json()
        item = {'name': name, 'price': data['price']}
        items.append(item)
        return item, 201
		
	# 아이템 업데이트
    def put(self, name):
        data = request.get_json()
        for item in items:
            if item['name'] == name:
                item['price'] = data['price']
                return item

        # 아이템이 존재하지 않으면 새로운 아이템을 추가
        new_item = {'name': name, 'price': data['price']}
        items.append(new_item)
        return new_item
		
	# 아이템 삭제
    def delete(self, name):
        global items
        items = [item for item in items if item['name'] != name]
        return {'message': 'Item deleted'}

# Item 리소스 등록 → 경로 추가
api.add_resource(Item, '/item/<string:name>')

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

이 코드는 한 파일 안에 여러 CRUD 기능을 다 넣은 것이다.

기능별로 파일을 분리해보자!

  1. app.py
from flask import Flask
from flask_restful import Api
from resources.item import Item

app = Flask(__name__)
api = Api(app)

api.add_resource(Item, '/item/<string:name>')

if __name__ == '__main__':
    app.run(debug=True)
  1. resources/item.py
from flask_restful import Resource, request

items = []

class Item(Resource):
    # 특정 아이템 조회
    def get(self, name):
        for item in items:
            if item['name'] == name:
                return item
        return {'message': 'Item not found'}, 404

    # 새 아이템 추가
    def post(self, name):
        for item in items:
            if item['name'] == name:
                return {'message': f"An item with name '{name}' already exists."}, 400

        data = request.get_json()
        item = {'name': name, 'price': data['price']}
        items.append(item)
        return item, 201
		
	# 아이템 업데이트
    def put(self, name):
        data = request.get_json()
        for item in items:
            if item['name'] == name:
                item['price'] = data['price']
                return item

        # 아이템이 존재하지 않으면 새로운 아이템을 추가
        new_item = {'name': name, 'price': data['price']}
        items.append(new_item)
        return new_item
		
	# 아이템 삭제
    def delete(self, name):
        global items
        items = [item for item in items if item['name'] != name]
        return {'message': 'Item deleted'}

4. flask-restful api 장/단점

  • 장점
  1. 간단한 RESTful API 개발을 빠르게 할 수 있다.
  2. 직관적인 코드
  3. 낮은 러닝커브
  • 단점
  1. OpenAPI 및 Swagger 같은 문서화 도구를 직접 지원하지 않는다는 점

04. flask-smorest 활용하여 REST API 생성

1. flask-smorest란?

flask-smorest란 REST API를 쉽게 작성할 수 있도록 도와주는 Flask 라이브러리다. Flask-RESTful보다 더 많은 기능과 OpenAPI(Swagger) 문서 자동 생성 기능을 제공한다.
설치는 flaks-restful과 동일하게 외부에서 가져오는 모듈이기 때문에 코드를 실행하여 설치한다.

> pip install flask-smorest 

해당 모듈에 대한 설명 아래 사이트들에서 확인할 수 있다.

2. API 구축

smorest를 설정을 하려면 몇가지를 해야한다.

(1) OpenAPI 설정

  • app.py
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"
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)

(2) Schemas 설정

  • schemas.py
    : 데이터 검증과 직렬화에 사용되는 Marshmallow 스키마를 정의한다. (데이터 스키마 검증)
from marshmallow import Schema, fields

class ItemSchema(Schema):
		# id 필드가 직렬화(즉, Python 객체에서 JSON으로 변환) 과정에서만 사용되고, (서버->클라)
		# 역직렬화(즉, JSON에서 Python 객체로 변환) 과정에서는 무시된다 (클라->서버)
		# 즉, id 값은 서버에서 관리하겠다는 뜻
    id = fields.Int(dump_only=True)
    name = fields.Str(required=True)
    description = fields.Str()

데이터의 구조가 단순할 때는 스키마를 만드는 작업이 귀찮게 느껴질 수도 있다. 하지만 데이터 구조가 복잡해지면 스키마 구조화 덕분에 불필요한 오류가 발생하는 것을 방지할 수 있다.

  • Marshmallow 라이브러리 설치
>pip install marshmallow
  • Marshmallow 스키마란?
    : Python에서 사용되는 - ORM/ODM(Object-Relational Mapper/Object-Document Mapper)과 함께 사용되는 라이브러리다.
    • 복잡한 데이터 타입을 파이썬 데이터 타입으로 변환(직렬화)하고, 파이썬 데이터를 JSON과 같은 형식으로 변환(역직렬화)하는 데 사용한다.
      → 이를 통해 데이터 검증, 데이터 직렬화/역직렬화 등이 쉽게 가능해졌다.
    • Marshmallow 스키마는 데이터의 구조와 타입을 정의한다.
    • 예를 들어, 특정 API 요청이나 응답에서 예상되는 데이터 형식을 스키마로 정의할 수 있으며, 이를 통해 데이터 유효성 검사, 직렬화 및 역직렬화를 자동화할 수 있다.

(3) (개념) Blueprint

Blueprint는 애플리케이션의 특정 기능별로 라우팅, 뷰 함수, 템플릿, 정적 파일 등의 관리가 가능하게 해주는 함수이다. API가 복잡해질수록 관리의 필요성이 증가한다.

  • 주요 기능
    1. 모듈화
      : - 블루프린트를 사용하면 애플리케이션의 서로 다른 부분을 별도의 모듈로 나누어 관리할 수 있다. 이는 코드의 재사용성을 높이고, 유지보수를 용이하게 한다.
    2. 라우팅 관리
      : 블루프린트는 자체 URL 규칙을 가지고 있으며, 이를 통해 애플리케이션의 라우팅을 체계적으로 관리할 수 있다.
    3. 기능별 분리
      : 블루프린트를 사용하면 특정 기능에 대한 라우팅, 뷰 함수, 에러 핸들러, 템플릿 등을 그룹화할 수 있다.
  • 코드 예시
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)
  • 블루프린트를 사용하여 Flask 애플리케이션의 다양한 URL 경로와 기능을 효과적으로 구성하는 방법을 보여준다. 각각의 기능별로 라우팅과 뷰 함수를 그룹화하여 애플리케이션을 체계적으로 관리할 수 있게 해준다.

(4) (개념) Abort

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

  1. 기본 사용법
    : abort 함수는 flask_smorest에서 가져올 수 있으며, 주로 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:
          # abort를 사용했을 때랑 다른 부분
          error_response = jsonify({"error": "An error occurred while processing the request."})
          error_response.status_code = 500
          return error_response
          
        # 정상적인 응답
        return "Success!"
  2. 주요 차이점 및 이유

    1. HTTP 상태 코드 전송
      : abort를 사용하면 함수에서 바로 HTTP 상태 코드를 지정할 수 있다. 반면에 jsonify와 같은 방법을 사용하면 상태 코드를 별도로 설정해야한다.
    2. 의미 있는 오류 메시지 전달
      : abort를 사용하면 오류 메시지를 description 매개변수를 통해 쉽게 전달할 수 있다. jsonify를 사용하면 응답 본문에 메시지를 추가해야 한다.
    3. RESTful API 표준화
      : RESTful API에서는 특정한 상태 코드와 오류 메시지의 표준을 정하는 것이 중요하다. abort를 사용하면 이러한 표준을 쉽게 따를 수 있다.
    4. 가독성과 유지보수성
      : abort를 사용하면 오류 처리 부분이 더 간결하고 가독성이 높아진다. 코드가 명확하게 오류 상황을 처리하고 해당 상태 코드를 클라이언트에게 반환한다.
  3. abort 함수 사용 시 주의사항

    1. 오류 메시지를 명확하게 제공하여 클라이언트가 오류의 원인을 쉽게 이해할 수 있도록 한다.
    2. 안전하지 않은 정보는 오류 메시지에 포함하지 않도록 주의한다. (예: 시스템 경로, 개인 정보 등)

(5) API 생성

  • api.py
    : api 파일을 생성하고 blueprint를 정의한다.
from flask.views import MethodView
from flask_smorest import Blueprint, abort
from schemas import ItemSchema

# 블루프린트 생성: 'items'라는 이름으로, URL 접두사는 '/items'
blp = Blueprint("items", "items", url_prefix="/items", description="Operations 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)
        if item is None:
            abort(404, message="Item not found")
        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 ''
  • API List
    • GET (ItemList) - /items/
      : 모든 아이템을 반환
    • POST (ItemList) - /items/
      : 새 아이템을 추가
    • GET (Item) - /items/<int:item_id>
      : 특정 ID를 가진 아이템을 반환
    • PUT (Item) - /items/<int:item_id>
      : 특정 ID를 가진 아이템을 업데이트
    • DELETE (Item) - /items/<int:item_id>
      : 특정 ID를 가진 아이템을 삭제

(6) 실행 및 테스트

  • http://localhost:5000/items/ 주소에 접속하여 API를 테스트.

  • Swagger UI 문서는 http://localhost:5000/api-docs/ 에서 확인할 수 있다.

  • OAS (Open Api Specification) - Swagger UI

    • OpenAPI = Specification
    • Swagger = Tools for implementing the specification

    OAS(OpenAPI Specification)는 RESTful 웹서비스를 약속된 규칙에 따라 약속된 규칙에 맞게 API 스펙을 json과 yaml 형식으로 표현한다. 이를 통해, 직접 소스코드를 보거나 추가 문서 필요없이 서비스를 이해할 수 있다.

    Swagger는 OpenAPI 스펙을 맞춘 api-docs를 이용하여 html 페이지로 문서화해주는 프레임워크로, RESTful API의 설계 및 문서화에 매우 도움을 준다.

    • Swagger UI의 기능
      : Swagger UI를 통해 다음과 같은 작업을 수행할 수 있습니다:
      1. API 명세 확인
        : 각 API 엔드포인트에 대한 상세한 정보를 볼 수 있다. 이에는 HTTP 메소드, 경로, 요청 매개변수, 응답 형식 등이 포함된다.
      2. API 테스트
        : UI에서 직접 API 엔드포인트를 테스트할 수 있다. 요청 매개변수를 입력하고, 실제 요청을 보내보며, 응답을 확인할 수 있다.
      3. 요청 및 응답 예시 확인
        : 각 API에 대해 정의된 요청 및 응답 예시를 볼 수 있으며, 이는 API 사용 방법을 이해하는 데 도움이 된다.
    • OpenAPI Specification(API Resuorces)
      1. API 디자인 및 문서화
        : Swagger 또는 OpenAPI Specification은 API를 디자인하고 문서화하기 위한 완벽한 형식을 제공한다. 코드를 작성하기 전에 리소스와 작업을 정의하고 API를 설계할 수 있다.
      2. 시각화
        : API의 작업을 시각적으로 표현하여 내부 개발자 및 외부 소비자가 API를 신속하게 채택할 수 있도록 도와준다.
      3. 자동화 및 코드 생성
        : Swagger 또는 OpenAPI Specification을 기반으로 하면 서버 스텁 및 클라이언트 SDK의 프레임워크를 자동으로 생성할 수 있다. 이를 통해 더 빠르고 일관된 개발을 할 수 있다.
      4. 테스트 자동화
        : OAS 정의에서 성공적인 응답을 정의함으로써 테스트 케이스 생성을 자동화할 수 있다.
      5. API 모니터링
        : OAS 정의에서 정의한 작업을 사용하여 API 모니터를 생성할 수 있다. 이를 통해 API의 성능과 가용성을 모니터링할 수 있다.
      6. 배포
        : Swagger 또는 OpenAPI Specification은 AWS, IBM, Apigee와 같은 선도적인 API 게이트웨이에서 지원되어 API를 배포하고 관리하는 데 도움이 된다.

    이러한 특징들은 API의 전체 수명 주기를 지원하며, API를 효율적으로 디자인하고 관리하는 데 도움이 된다. Swagger 또는 OpenAPI Specification을 사용하면 API 개발 및 문서화의 표준을 확립하고 팀간 협업을 촉진할 수 있다.

    • 접속 방법
      • Flask 애플리케이션이 실행되고 있는 서버의 주소에 /swagger-ui를 추가하여 접속한다.
      • 예를 들어, 로컬 환경에서 애플리케이션을 실행하는 경우, 브라우저에서 http://localhost:5000/swagger-ui로 접속하면 Swagger UI를 볼 수 있다.

    Swagger UI는 API 개발 및 테스트를 보다 쉽고 효율적으로 만들어 주는 강력한 툴이다. Flask와 Flask-Smorest를 사용하여 구축한 API에 대한 문서를 자동으로 생성하고, 이를 인터랙티브한 방식으로 탐색할 수 있게 해준다.

3. flask-smorest의 장/단점

  • 장점
    1. OpenAPI 및 Swagger를 쉽게 통합하여 API 문서화를 지원한다.
    2. 강력한 기능을 가진 일부 확장 모듈을 지원한다.
    3. Flask-Smorest는 Flask-RESTful과 마찬가지로 확장성이 있다.
  • 단점
    1. 상대적으로 Flask-RESTful에 비해 높은 러닝 커브가 있을 수 있다.
    2. 몇 가지 복잡한 기능을 구현하는 데 추가 설정이 필요할 수 있다.

05. 실습

※ 책 관리 API 만들기

책 정보를 관리하는 간단한 RESTful API를 만들어 본다. 이 API는 책의 목록을 보여주고, 새로운 책을 추가하며, 특정 책의 정보를 업데이트하고 삭제할 수 있어야한다.

  • 요구 사항
    1. 애플리케이션 설정 (app.py)
      : Flask 애플리케이션을 생성하고, Flask-Smorest API를 설정한다.
    2. 책 스키마 정의 (schemas.py)
      : Marshmallow를 사용하여 책 정보를 위한 스키마를 정의한다. 책은 최소한 'title'(제목)과 'author'(저자) 필드를 가져야한다.
    3. API 엔드포인트 구현 (api.py)
    • 책 목록을 보여주는 GET 엔드포인트를 만든다.
    • 새 책을 추가하는 POST 엔드포인트를 만든다.
    • 특정 책의 정보를 업데이트하는 PUT 엔드포인트를 만든다.
    • 특정 책을 삭제하는 DELETE 엔드포인트를 만든다.
    1. 데이터 저장소
      : 책의 데이터는 메모리 내의 간단한 리스트로 관리한다.
  • 개발 요구사항
    1. Flask와 Flask-Smorest를 사용하여 위의 요구 사항을 만족하는 API를 구현한다.
    2. 각 파일(app.py, schemas.py, api.py)에 필요한 코드를 작성한다.
    3. 책 정보를 관리하는 로직을 구현하고, 적절한 HTTP 응답을 반환하도록 한다.
    4. Marshmallow 스키마를 사용하여 입력 데이터를 검증한다.
  1. app.py - 애플리케이션 설정
    : Flask 애플리케이션과 Flask-Smorest API의 기본 설정을 포함한다. Swagger UI 경로도 설정된다.

    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)
  2. schemas.py - 책 스키마 정의
    : 책 정보를 위한 스키마를 정의한다. 여기서는 책의 제목과 저자가 필요하며, 책의 고유 ID는 자동으로 설정된다.

    from marshmallow import Schema, fields
    
    class BookSchema(Schema):
      id = fields.Int(dump_only=True)
      title = fields.String(required=True)
      author = fields.String(required=True)
  3. api.py - API 엔드 포인트 구현
    : 책 목록을 관리하는 API 엔드포인트를 구현한다.
    GET, POST, PUT, DELETE 메소드를 사용하여 책 목록을 조회, 추가, 수정, 삭제할 수 있다.

    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 ''
  • API List
    1. GET - /books/
      : 모든 책 목록을 반환, 책 목록 (JSON)
    2. POST - /books/
      : 새 책 추가, 추가된 책 (JSON)
    3. GET - /books/<book_id>
      : 특정 ID의 책 반환, 특정 책 (JSON)
    4. PUT - /books/<book_id>
      : 특정 ID의 책 정보 업데이트, 업데이트된 책 (JSON)
    5. DELETE - /books/<book_id>
      : 특정 ID의 책 삭제, 없음 (HTTP 상태 204)

[2일차 후기]

코드를 복붙해서 하다보니 완벽하게 이해하지는 못하지만 뭔가 살이 더 붙는게 느껴진다...
(아파서 하루동안 아무것도 못했더니 1일치 공부가 밀린 이 부담감🥲)


[참고 자료]

  • [오즈스쿨 스타트업 웹 개발 초격차캠프 백엔드 Flask 강의]
profile
백엔드 코린이😁

0개의 댓글