Flask로 REST API 구현하기 - 2. 파일 분리, 문서화

oneofakindscene·2021년 8월 16일
0

flask

목록 보기
3/4
post-thumbnail

파일 분리

  • 1편에서는 app.py 하나로 api를 만들었음 <- 코드가 복잡해질 수록 "파일 분리"가 필요성은 커지게된다.
  • 이번에는 app.py, to_do.py 파일 2개로 api를 만들어보고자 함
    • app.py : API정보 및 기능이 구현된 파일을 가져와 기능별로 namespace를 부여해줌 => API 전체적인 그림을 그려주는 부분
    • to_do.py : 메소드 등 기능이 구현된 파일 => API 세부 동작(기능)을 구현하는 부분
  • 이러한 파일분리를 통해 문제가 발생했을 때 좀 더 쉽게 대응가능하고 가독성도 높일 수 있음
  • app.py 코드
from flask import Flask
from flask_restx import Resource, Api
from to_do import Todo # 기능구현한 파일이 여기에서 import된다

app = Flask(__name__)

# api = Api(app) # 이전 실습에서는 여기까지만 구현했음
# 아래와 같이, version, title, description... 등의 내용들을 추가해서 넣어줄 수 있다.
api = Api(
            app,
            version='0.1',
            title="Scene's API Server",
            description="Scene's Todo API Server!",
            terms_url="/",
            contact="oneofakindscene@gmail.com",
            license="MIT"
)

# 이 파일이 아닌 외부에 있는 파일(to_do.py)에 클래스를 구현하고 여기서 import한 다음 add_resource()를 통해 클래스를 등록
api.add_namespace(Todo, '/todos') # namespace에서 url 들어갈 부분 머로 들어가는지 확인해야함 => 여기선 /todos

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

add_namespace()

  • api.add_namespace(Todo, '/todos') <- api 객체를 특정 경로('/todos')에 등록 할 수 있게함
    • http://localhost/todos <- 이런식으로 api 호출할 때 사용

문서화

  • api를 효율적으로 프론트앤드 개발자에게 전달 할 수 있는 방안이 문서화
  • http://localhost 이렇게만 접속해보면 아래 스샷과 같은 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 = Api(
    app,
    version='0.1',
    title="Scene's API Server",
    description="Scene's Todo API Server!",
    terms_url="/",
    contact="onoofakindscene@gmail.com",
    license="MIT"
)

"""메소드설명"""

  • 메소드를 정의한 후 바로 아랫줄에 Python의 Comment("""Comment""")를 이용하여 Document에 설명을 추가 할 수 있습니다.
@Todo.route('')
class TodoPost(Resource):
    @Todo.expect(todo_fields)
    @Todo.response(201, 'Success', todo_fields_with_id)
    def post(self):
        """post comment : Todo 리스트에 할 일을 등록 합니다."""
        global count
        global todos

        idx = count
        count += 1
        todos[idx] = request.json.get('data')

        return {
                   'todo_id': idx,
                   'data': todos[idx]
               }, 201
  • 스샷

Namespace()

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

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

    todo.py 에 있는 Namespace 객체의 생성자 파라미터를 수정하면 아래와 같다.

Todo = Namespace(
    name="Namespace의 name : Todos",
    description="Namespace의 Description : Todo 리스트를 작성하기 위해 사용하는 API.",
)
  • Namespace의 name, description이 적용된 모습

Namespace.model()

  • Namespace.model()에 대해 설명 하자면, Namespace.model()은 입력, 출력에 대한 스키마를 나타내는 객체
  • flask_restx 내의 field 클래스를 이용하여, 설명(description), 필수 여부(required), 예시(example)를 넣을 수 있습니다.
  • 또한, Namespace.inherit()을 이용하여, Namespace.model() 을 상속 받을 수 있습니다.

    to_do.py 에 있는 Namespace.model(), Namespace.inhert()를 활용하여 입력, 출력에 대한 스키마 정의해주기

Todo = Namespace(
    name="Namespace의 name : Todos",
    description="Namespace의 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_fields 상속 받음
    'todo_id': fields.Integer(description='a Todo ID')
})
  • 아래 스샷을 보면 to_do.py파일에서 Namespace.model(), Namespace.inherit()을 사용해서 example 및 schema가 들어간 것을 확인할 수 있다.
    • 위 코드에선 todo_fields, todo_fields_with_id 부분임

Namespace.doc()

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

  • params: dict 객체를 받으며, 로는 파라미터 변수명, 으로는 설명을 적을 수 있습니다.
  • responses: dict 객체를 받으며, 로는 Status Code(202, 500... 같은거), 으로는 설명을 적을 수 있습니다.

    to_do.py 에서 Namespace.doc(params={dict}), Namespace.doc(responses={dict}) 적용된 부분

@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):
        """delete comment : To_do 리스트에 todo_id와 일치하는 ID를 가진 할 일을 삭제합니다."""
        del todos[todo_id]
        return {
        	  "delete": "success"
               }, 202
  • 적용된 스샷 : 파라미터와 Status Code에 대한 설명이 추가된 것을 알 수 있다.

Namespace.expect(), Namespace.response()

Namespace.expect()

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

Namespace.response()

to_do.py 에서 Namespace.expect(), Namespace.response() 된 부분

@Todo.route('')
class TodoPost(Resource):
    @Todo.expect(todo_fields)
    @Todo.response(201, 'Success', todo_fields_with_id)
    def post(self):
        """post comment : To_do 리스트에 할 일을 등록 합니다."""
        global count
        global todos

        idx = count
        count += 1
        todos[idx] = request.json.get('data')

        return {
                   'todo_id': idx,
                   'data': todos[idx]
               }, 201
  • Namespace.expect(), Namespace.response() 적용된 스샷

질문사항

  • 아래 코드에서 status code가 200인 이유와 다른 def와 달리 return {dict}, 200 이런식으로 안들어가는 이유
@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]
        }
  • 1) 202가 아닌 200인 이유는?
    • 이는 HTTP Status code에 따릅니다. 200 Code는 요청 성공을 뜻하여, 202는 해당 요청이 허용 되었음을 알리는 코드입니다. https://ko.wikipedia.org/wi...
  • 2) 그리고 다른 함수와 달리 def get()만 return에 콤마(,) 찍고 200이 안들어간 이유가 멀까요?
    • 200은 default 값이기 때문에, 다른 코드는 status code를 명시 하여야 하지만, 200은 넣지 않아도 됩니다.

References

profile
oneofakindscene

0개의 댓글