[python 인터넷] Flask로 REST API 구현하기 - 2. 파일 분리, 문서화

저번 시간에는 Flask-RESTX 에 대한 기본적인 사용 법을 알아보고, 이를 이용하여 간단한 API Server를 만들어 보았습니다.

모두가 스파게티 코드를 원하지 않잖아요.

여러분은 당신의 코드가 스파게티 코드가 되는 것을 원치 않을 것 입니다. 그러므로 파일 분리는 우리가 무슨 어플리케이션을 만들던 필수적인 과정입니다.

저번 시간에 구현한 간단한 API 서버가 여러 가지 기능을 동시에 구현 한다고 가정 해 보겠습니다. 간단한 게시판을 위해 API 서버를 만든다고 했을 때, 로그인, 회원 가입, 게시글, 댓글, 대댓글, 사용자 수정 등등... 대충 어림잡아 몇 백줄을 넘길 것 입니다. 코드 하나에 문제가 발생했을 때 대처 하기도 어려울 뿐더러, 가독성도 매우 떨어집니다. 그럼 Flask RESTX 에서는 파일 분리를 어떻게 실시 할까요?

add_namespace()

flask-restx내부의 Api 객체의 add_namespace() 는 flask-restx.Namespace 객체를 특정 경로에 등록 할 수 있게 해줍니다. 그러면 flask-restx.Namespace는 무엇이냐? 어떻게 보면 Flask 객체에 Blueprint가 있다면, Api 객체에는 Namespace가 있는 격입니다. Blueprint를 모른다고요? 백문이 불여일견, 코드로 보시겠습니다.

namespace = Namespace('hello')  # 첫 번째

@namespace.route('/')
class HelloWorld(Resource):  
    def get(self):
        return {"hello" : "world!"}, 201, {"hi":"hello"}
		
api.add_namespace(namespace, '/hello')  


@api.route('/hello')  # 두 번째
class HelloWorld(Resource):
    def get(self):
        return {"hello" : "world!"}, 201, {"hi":"hello"}

그럼 이야기는 간단해 졌습니다! 외부에서 클래스를 구현하고, 이를 import 한 다음 add_resource()를 통해 클래스를 등록 해 주면 됩니다.

다음은 Flask로 REST API 구현하기 - 1. Flask-RESTX 에서 구현한 Todo API Server 와 같은 기능을 합니다.

app.py

from flask import Flask
from flask_restx import Resource, Api
from todo import Todo

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

api.add_namespace(Todo, '/todos')

if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=80)

todo.py

from flask import request
from flask_restx import Resource, Api, Namespace


todos = {}
count = 1


Todo = Namespace('Todo')

@Todo.route('')
class TodoPost(Resource):
    def post(self):
        global count
        global todos
        
        idx = count
        count += 1
        todos[idx] = request.json.get('data')
        
        return {
            'todo_id': idx,
            'data': todos[idx]
        }


@Todo.route('/<int:todo_id>')
class TodoSimple(Resource):
    def get(self, todo_id):
        return {
            'todo_id': todo_id,
            'data': todos[todo_id]
        }

    def put(self, todo_id):
        todos[todo_id] = request.json.get('data')
        return {
            'todo_id': todo_id,
            'data': todos[todo_id]
        }
    
    def delete(self, todo_id):
        del todos[todo_id]
        return {
            "delete" : "success"
        }

또 다른 문제, 문서화.

자! 대충 파일 분리도 하고, 유지 보수 문제도 조금은 해결 했습니다. 문제는 무엇이냐면, 우리가 만든 API들을 어떻게 프론트앤드 개발자에게 전달 할 수 있을까요? 정답은 문서화 입니다. 한번, 방금 짠 API 서버를 실행 한 후, 'http://localhost/' 에 들어가 볼까요?

우리가 몰랐던 API 문서

 

우리는 이런 홈페이지를 만든 기억이 없습니다. 그렇습니다. 이는 flask-RESTX 의 기본 기능으로 제공하는 Swagger 기반의 홈페이지 입니다.

이를 제대로 활용 하면, 죽여주는 API 웹 문서를 만들어 줄 수 있을 것 같습니다. 지금은 API에 대한 설명, 예시, 데이터 타입 아무것도 명시 되어 있지 않는 상황입니다. 우리는 어떻게 하면 좋을까요?

Api()

일단 가장 위에 있는 설명부터 만지는 게 좋아 보입니다. 위 설명은 Api 객체의 생성자를 호출 할 때, 해당하는 파라미터로 값을 넣어 주면 수정 가능합니다. 파라미터는 다음과 같습니다.

  • version: API Server의 버전을 명시합니다.
  • title: API Server의 이름을 명시합니다.
  • description: API Server의 설명을 명시합니다.
  • terms_url: API Server의 Base Url을 명시합니다.
  • contact: 제작자 E-Mail 등을 삽입합니다.
  • license: API Server의 라이센스를 명시 합니다.
  • license_url: API Server의 라이센스 링크를 명시 합니다.

한번 app.py 에 있는 Api 객체의 생성자 파라미터를 수정 해 볼까요?

app.py

...

api = Api(
    app,
    version='0.1',
    title="JustKode's API Server",
    description="JustKode's Todo API Server!",
    terms_url="/",
    contact="justkode@kakao.com",
    license="MIT"
)

...

전보다 조금 괜찮아졌어요!

 

Namespace()

Namespace 객체도 생성자 파라미터를 조정하여, 내용을 수정 할 수 있습니다. 한 번 볼까요?

  • title: Namespace의 이름을 명시합니다.
  • description: Namespace의 설명을 명시합니다.

한번 todo.py 에 있는 Namespace 객체의 생성자 파라미터를 수정 해 볼까요?

todo.py

...

Todo = Namespace(
    name="Todos",
    description="Todo 리스트를 작성하기 위해 사용하는 API.",
)

...

Todos 옆에 설명이 추가 되었어요!

 

"""설명"""

Python의 Comment를 이용하여 Document에 설명을 추가 할 수 있습니다.

todo.py

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

...

@Todo.route('')
class TodoPost(Resource):
    def post(self):
        """Todo 리스트에 할 일을 등록 합니다."""
        global count
        global todos
        
        idx = count
        count += 1
        todos[idx] = request.json.get('data')
        
        return {
            'todo_id': idx,
            'data': todos[idx]
        }


@Todo.route('/<int:todo_id>')
class TodoSimple(Resource):
    def get(self, todo_id):
        """Todo 리스트에 todo_id와 일치하는 ID를 가진 할 일을 가져옵니다."""
        return {
            'todo_id': todo_id,
            'data': todos[todo_id]
        }
    
    def put(self, todo_id):
        """Todo 리스트에 todo_id와 일치하는 ID를 가진 할 일을 수정합니다."""
        todos[todo_id] = request.json.get('data')
        return {
            'todo_id': todo_id,
            'data': todos[todo_id]
        }
    
    def delete(self, todo_id):
        """Todo 리스트에 todo_id와 일치하는 ID를 가진 할 일을 삭제합니다."""
        del todos[todo_id]
        return {
            "delete" : "success"
        }

...

각 API 옆에 설명이 추가 되었어요!

 

Namespace.doc(), Namespace.expect(), Namespace.response()

참고: Namespace 객체가 아닌 Api 객체에서도 작동합니다.

일단 많은 것들을 설명 하기 전에 Namespace.model()에 대해 설명 하고자 합니다. Namespace.model()은 입력, 출력에 대한 스키마를 나타내는 객체 입니다. flask_restx 내의 field 클래스를 이용하여, 설명, 필수 여부, 예시를 넣을 수 있습니다.

또한 Namespace.inherit()을 이용하여, Namespace.model() 을 상속 받을 수 있습니다.

todo.py

...

todo_fields = Todo.model('Todo', {  # Model 객체 생성
    'data': fields.String(description='a Todo', required=True, example="what to do")
})

todo_fields_with_id = Todo.inherit('Todo With ID', todo_fields, {  # todo_fields 상속 받음
    'todo_id': fields.Integer(description='a Todo ID')
})

...

스키마 모델이 추가 됐어요!

 

Namespace.doc()

Namespace.doc() 데코레이터를 이용하여, Status Code 마다 설명을 추가하거나, 쿼리 파라미터에 대한 설명을 추가 할 수 있습니다.

  • paramsdict 객체를 받으며, 키로는 파라미터 변수명, 값으로는 설명을 적을 수 있습니다.
  • responsesdict 객체를 받으며, 키로는 Status Code, 값으로는 설멍을 적을 수 있습니다.

todo.py

...

@Todo.route('/<int:todo_id>')
@Todo.doc(params={'todo_id': 'An ID'})
class TodoSimple(Resource):

	[...]
	
	@Todo.doc(responses={202: 'Success'})
    @Todo.doc(responses={500: 'Failed'})
    def delete(self, todo_id):
        """Todo 리스트에 todo_id와 일치하는 ID를 가진 할 일을 삭제합니다."""
        del todos[todo_id]
        return {
            "delete" : "success"
        }, 202

...

파라미터와 Status Code에 대한 설명이 추가 됐어요!

 

Namespace.expect()

말 그대로, 특정 스키마가 들어 올것을 기대 한다. 라고 보면 됩니다. Namespace.Model 객체를 등록하면 됩니다.

Namespace.response()

말 그대로, 특정 스키마가 반환 된다. 라는 것을 알려 준다고 보면 됩니다.

첫 번째 파라미터로 Status Code, 두 번째 파라미터로 설명, 세 번째 파라미터로 Namespace.Model 객체가 들어 갑니다.

todo.py

...

class TodoPost(Resource):
    @Todo.expect(todo_fields)
    @Todo.response(201, 'Success', todo_fields_with_id)
    def post(self):
        """Todo 리스트에 할 일을 등록 합니다."""
        global count
        global todos
        
        idx = count
        count += 1
        todos[idx] = request.json.get('data')
        
        return {
            'todo_id': idx,
            'data': todos[idx]
        }, 201
		
...

Parameters와 Responses에 대한 설명이 추가 됐어요!

 

전체 코드

app.py

from flask import Flask
from flask_restx import Resource, Api
from todo import Todo

app = Flask(__name__)
api = Api(
    app,
    version='0.1',
    title="JustKode's API Server",
    description="JustKode's Todo API Server!",
    terms_url="/",
    contact="justkode@kakao.com",
    license="MIT"
)

api.add_namespace(Todo, '/todos')

if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=80)

todo.py

from flask import request
from flask_restx import Resource, Api, Namespace, fields


todos = {}
count = 1


Todo = Namespace(
    name="Todos",
    description="Todo 리스트를 작성하기 위해 사용하는 API.",
)

todo_fields = Todo.model('Todo', {  # Model 객체 생성
    'data': fields.String(description='a Todo', required=True, example="what to do")
})

todo_fields_with_id = Todo.inherit('Todo With ID', todo_fields, {
    'todo_id': fields.Integer(description='a Todo ID')
})

@Todo.route('')
class TodoPost(Resource):
    @Todo.expect(todo_fields)
    @Todo.response(201, 'Success', todo_fields_with_id)
    def post(self):
        """Todo 리스트에 할 일을 등록 합니다."""
        global count
        global todos
        
        idx = count
        count += 1
        todos[idx] = request.json.get('data')
        
        return {
            'todo_id': idx,
            'data': todos[idx]
        }, 201

@Todo.route('/<int:todo_id>')
@Todo.doc(params={'todo_id': 'An ID'})
class TodoSimple(Resource):
    @Todo.response(200, 'Success', todo_fields_with_id)
    @Todo.response(500, 'Failed')
    def get(self, todo_id):
        """Todo 리스트에 todo_id와 일치하는 ID를 가진 할 일을 가져옵니다."""
        return {
            'todo_id': todo_id,
            'data': todos[todo_id]
        }
    
    @Todo.response(202, 'Success', todo_fields_with_id)
    @Todo.response(500, 'Failed')
    def put(self, todo_id):
        """Todo 리스트에 todo_id와 일치하는 ID를 가진 할 일을 수정합니다."""
        todos[todo_id] = request.json.get('data')
        return {
            'todo_id': todo_id,
            'data': todos[todo_id]
        }, 202
    
    @Todo.doc(responses={202: 'Success'})
    @Todo.doc(responses={500: 'Failed'})
    def delete(self, todo_id):
        """Todo 리스트에 todo_id와 일치하는 ID를 가진 할 일을 삭제합니다."""
        del todos[todo_id]
        return {
            "delete" : "success"
        }, 202

다음 시간에는...

다음 시간에는 Flask를 통해 사용자 인증을 해보는 방법을 알아 보겠습니다. 혹시, 이 포스트의 설명이 부족하다면, 댓글을 달아 주시거나, Flask-RESTX Document를 참고 해 주세요!

 

[출처] https://justkode.kr/python/flask-restapi-2

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
431 [Python 인터넷] django REST framework로 간단한 api 만들기 file 졸리운_곰 2023.03.08 10
430 [Python 인터넷] Quickstart - Django REST framework 졸리운_곰 2023.03.08 21
429 [python][자료구조] [MongoDB] Document Query(조회) – find() 메소드 졸리운_곰 2023.03.08 7
428 [python][자료구조] Python(flask)으로 mongoDB 제어하기 – pymongo file 졸리운_곰 2023.03.08 30
427 [python, tkinter] [python/GUI] tkinter 로 GUI 만들기(기초예제, 단위 변환기 만들기) file 졸리운_곰 2023.03.07 17
426 [Python 인터넷] 세상에서 가장 간단한 Airflow 튜토리얼 file 졸리운_곰 2023.03.05 11
425 [Python 인터넷] Flask 또는 Django를 Production 서버로 배포(Deploy)하기 file 졸리운_곰 2023.03.03 10
424 [Python 인터넷] django 웹사이트 배포를 하며 겪은 시행착오 졸리운_곰 2023.03.03 3
423 [Python 인터넷] Windows에서 Django와 Apache 연동하기 졸리운_곰 2023.03.02 28
422 [python][자료구조] django sqlite3 MySQL로 전환하기 file 졸리운_곰 2023.03.02 6
421 [python][자료구조] django에서 db.sqlite3 데이터를 mysql로 옮기기 졸리운_곰 2023.03.02 5
420 [python][자료구조] Python - JSON 파일 읽고 쓰는 방법 졸리운_곰 2023.02.04 19
419 [python][자료구조] [인코딩] 유니코드 인코딩 처리 (특히 json 입출력 시) 졸리운_곰 2023.02.04 8
418 [Python 인터넷] 구글로 공부하는 파이썬 - 부록 (IIS, Apache 로 Flask 돌리기) file 졸리운_곰 2023.01.26 20
417 [python 인터넷] Flask로 REST API 구현하기 - 3. JWT로 사용자 인증하기 졸리운_곰 2023.01.26 8
» [python 인터넷] Flask로 REST API 구현하기 - 2. 파일 분리, 문서화 졸리운_곰 2023.01.26 8
415 [python 인터넷] Flask로 REST API 구현하기 - 1. Flask-RESTX file 졸리운_곰 2023.01.26 11
414 [python 인터넷] 초간단 API서버 만들기 - 1부 (Python + Flask + Apache) file 졸리운_곰 2023.01.24 11
413 [python][인터넷] Flask 또는 Django를 Production 서버로 배포(Deploy)하기 file 졸리운_곰 2023.01.17 9
412 [python][flask] (flask) windows에서 flask와 apache 연동 file 졸리운_곰 2023.01.17 7
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED