2. Swagger

nine·2022년 8월 1일
0

2-1. Swagger란?


스웨거(Swagger)는 개발자가 REST 웹 서비스를 설계, 빌드, 문서화, 소비하는 일을 도와주는 대형 도구 생태계의 지원을 받는 오픈 소스 소프트웨어 프레임워크이다. - 위키백과

Swagger는 즉 프론트엔드와의 의사소통을 원활하게 도와 주는 API문서를 동적으로 생성해 주는 역할을 한다. Flask-RESTX는 Swagger문서 작성 기능을 기본적으로 제공하기 때문에 이를 사용하면 손쉽게 깔끔한 API문서를 작성할 수 있다.
 

2-2. Flask-RESTX를 활용한 Swagger 문서 작성


 이번에도 예시 프로젝트를 통해서 정리해보려고 한다. 이번 예시에서는 Blueprint/Namespace를 사용한 모듈화는 사용하지 않고 하나의 파일로 작성해보았다.

 
todos.py

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

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

Todos = Namespace("Todos")
api.add_namespace(Todos, "/todos")

todo_list = []

@Todos.route("")
class Todo_program(Resource):
    def get(self):
        return {"todos" : todo_list}, 200

    def post(self):
        data = request.json
        todo_list.append(data["todo"])
        return {"status" : "success", "todo" : data["todo"]}, 201

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

http://127.0.0.1:5000/todos로 GET, POST 요청을 보내면 해당하는 요청을 처리하고 응답을 반환하는 프로그램이다. GET요청을 보내면 현재 todo_list에 담겨 있는 값들과 status code 200을 반환한다. POST요청은 todo_list에 todo를 추가하는 것인데, 이를 위해서 {"todo" : "something"}과 같은 JSON 포맷의 데이터를 함꼐 보내주어야 한다. 정상적으로 데이터가 전달되었다면 todo_listtodo를 추가하고 성공했다는 메시지와 status code 201을 반환한다.

이제 이를 바탕으로 Swagger를 이용한 API문서를 본격적으로 작성해 본다.

Swagger

 
 사실 이미 API문서는 작성되어 있다. 프로그램을 실행한 뒤 http://127.0.0.1:5000/에 접속해 보면 아래와 같은 창이 뜬다.Flask-RESTX는 프로그램 실행 시점에 이러한 API문서를 동적으로 생성한다. 그러니까 위에서 언급한 API문서를 만든다는 것은 사실 이 만들어진 API문서를 프론트엔드 파트에서 알아보기 쉽도록 꾸미는 것이다.

먼저 해당 페이지의 제목을 고쳐 이 문서가 어떤 문서인지를 표기해보도록 하겠다. 아래의 코드를 추가해보자.

...(생략)

app = Flask(__name__)
api = Api(
    app,
	version='1.0.0',
	title='Todo list API',
	description='Flask-RESTX swagger 설명을 위한 API문서 입니다.'
)

...(생략)

위 캡쳐에서 확인할 수 있듯이 문서의 이름과 설명, 버전이 추가되었다. 이 외에도 API객체의 생성자에는 여러 가지 인자들이 있는데 그 인자들에 값을 추가하는 것으로 API문서에 정보를 더 입력할 수 있다.

이 다음은 Todos 네임스페이스에 설명을 추가해 보도록 한다.

...(생략)

Todos = Namespace(
    name="Todos",
    description="할 일을 등록하고 확인하는 API"
    )
    
...(생략)

Todos 네임스페이스 옆에 설명이 추가된 것을 확인할 수 있다.

그 다음 해당 네임스페이스를 클릭하면 아래와 같이 HTTP method 별 API를 확인할 수 있다. 이제 method별로 API가 하는 일에 대한 설명을 입력해 본다.

...(생략)

@Todos.route("")
class Todo_program(Resource):
    def get(self):
        "현재 작성된 할 일을 반환한다"
        return {"todos" : todo_list}, 200

    def post(self):
        "todo list에 할 일을 추가한다"
        data = request.json
        todo_list.append(data["todo"])
        return {"status" : "success", "todo" : data["todo"]}, 201

...(생략)

각 method를 처리하는 함수의 최상단에 String으로 설명을 작성하면 이렇게 추가된 것을 확인할 수 있다.
이제 정말 마지막으로 각 API가 필요로 하는 request, 반환하는 response를 정의해 준다.

 

  • GET method

      GET method는 request로 어떠한 파라미터를 요구하지 않는다. 따라서 반환하는 response만 정의해 주면 된다. 반환값은 {"todos" : [...]}와 같은 JSON형식이다. 이러한 JSON형식의 response를 Swagger에 정의하려면 Namespace객체의 model메소드를 사용해야 한다. GET method의 반환 모델 todos_get_response_model을 다음과 같이 작성한다. List형식의 값을 예시로 넣기 위해서는 다음과 같이 입력해야 한다.

    from flask import Flask
    from flask import request
    from flask_restx import Api, Namespace, Resource, fields # fields 추가
    ...(생략)
     
    todos_get_response_model = Todos.model(name='todos_get_resopnse', model={
        "todos" : fields.List(fields.String(), default=["example", "for", "what", "to", "do"]),
    })
    
    ...(생략)

    그 다음 이 모델을 Swagger에 적용하기 위해 다음과 같이 작성한다.

    ...(생략)
    
    class Todo_program(Resource):
       @Todos.response(
           code=200,
           description="현재 todo list에 있는 할 일들을 모두 반환",
           model=todos_get_response_model
           ) # @Todos.response() 데코레이터 사용
       def get(self):
           "현재 작성된 할 일을 반환한다"
           return {"todos" : todo_list}, 200
    
     ...(생략)

    @Todo.response() 데코레이터를 추가하고 위와 같은 인자들에 값을 입력하면 Swagger의 reseponse영역에 model을 등록할 수 있다. 이렇게 잘 등록된 것을 확인할 수 있다.

  • POST method

     POST method는 request로 JSON형식의 데이터를 요구하고 response로도 JSON형식의 데이터를 반환한다. 즉 2가지의 model을 정의해야 한다. String형식의 값을 넣기 위해서는 아래와 같이 작성해야 한다.

    ...(생략)
    
    todos_post_request_model = Todos.model(name='todos_post_request', model={
       "todo" : fields.String(required=True, example="what to do")
    })
    
    todos_post_response_model = Todos.model(name='todos_post_response', model={
        "status" : fields.String(example="success"),
        "todo" : fields.String(example="what to do")
    })
    
    ...(생략)

    그 다음 이 모델을 Swagger에 적용하기 위해 다음과 같이 작성한다.

    ...(생략)
    
      @Todos.response(
          code=200,
          description="현재 todo list에 있는 할 일들을 모두 반환",
          model=todos_get_response_model
          ) # @Todos.response() 추가
      def get(self):
          "현재 작성된 할 일을 반환한다"
          return {"todos" : todo_list}, 200
    
      @Todos.expect(todos_post_request_model) # @Todos.expect() 추가
      @Todos.doc(params={"payload" : "추가할 할 일"}) # # @Todos.doc() 추가
      @Todos.response(
          code=201,
          description="성공 여부와 입력한 할 일을 다시 반환한다",
          model=todos_post_response_model
      ) # @Todos.response() 추가
      def post(self):
          "todo list에 할 일을 추가한다"
          data = request.json
          todo_list.append(data["todo"])
          return {"status" : "success", "todo" : data["todo"]}, 201
    
    ...(생략)

    @Todos.response() 데코레이터는 위에 설명한 것과 같다. @Todos.expect() 데코레이터는 Swagger의 파라미터 영역에 작성한 모델을 등록하게 해 준다. @Todos.doc() 데코레이터는 파라미터 영역에 추가한 model에 대한 설명을 추가하는 역할을 한다. 모두 적용된 결과는 다음과 같다.

 이렇게 해서 Flask-RESTX를 활용한 Swagger문서 작성이 끝났다. 이렇게 문서를 작성하여 관리하면 분명히 프론트엔드와의 협업이 매우 용이해진다. 특히, 저렇게 예시를 보여주는 것 뿐 아니라 Try it out버튼을 통해 직접 실행해볼 수 있기 때문에 백엔드가 어떻게 돌아가는지 잘 몰라도 빠르게 이해시킬 수 있었다. 이러한 장점이 있지만 아주 사소한 단점이 있다면 코드가 매우 지저분해진다는 것이다. 앞으로 리뷰해볼 코드들도 모두 Swagger문서가 작성이 되어있기 때문에 코드가 매우 지저분하다. 따라서 그럴 일은 없겠지만 누군가가 이 시리즈를 참고하게 된다면 추후의 코드들을 좀 쉽게 알아볼 수 있도록 돕기 위해 해당 포스트를 작성하게 되었다. 다음 게시글부터는 캡스톤 프로젝트에서 내가 맡아 구현했던 파트들을 다시 보면서 리뷰할 예정이다.

 
전체 코드

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

app = Flask(__name__)
api = Api(
    app,
    version='1.0.0',
    title='Todo list API',
    description='Flask-RESTX swagger 설명을 위한 API문서 입니다.',
)

Todos = Namespace(
    name="Todos",
    description="할 일을 등록하고 확인하는 API"
    )

api.add_namespace(Todos, "/todos")

todos_get_response_model = Todos.model(name='todos_get_resopnse', model={
    "todos" : fields.List(fields.String(), default=["example", "for", "what", "to", "do"]),
})

todos_post_request_model = Todos.model(name='todos_post_request', model={
    "todo" : fields.String(required=True, example="what to do")
})

todos_post_response_model = Todos.model(name='todos_post_response', model={
    "status" : fields.String(example="success"),
    "todo" : fields.String(example="what to do")
})

todo_list = []

@Todos.route("")
class Todo_program(Resource):
    @Todos.response(
        code=200,
        description="현재 todo list에 있는 할 일들을 모두 반환",
        model=todos_get_response_model
        )
    def get(self):
        "현재 작성된 할 일을 반환한다"
        return {"todos" : todo_list}, 200

    @Todos.expect(todos_post_request_model)
    @Todos.doc(params={"payload" : "추가할 할 일"})
    @Todos.response(
        code=201,
        description="성공 여부와 입력한 할 일을 다시 반환한다",
        model=todos_post_response_model
    )
    def post(self):
        "todo list에 할 일을 추가한다"
        data = request.json
        todo_list.append(data["todo"])
        return {"status" : "success", "todo" : data["todo"]}, 201

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

0개의 댓글