이번 챕터의 모든 링크는 2019년 2월 25일의 코드 스냅샷 기준입니다. flask-restful을 아예 모른다면, Quickstart를 읽어보고 오는 것을 추천합니다.

flask-restful도 코드 베이스가 작은 편이 아니지만, 그 모두를 우리가 알 필요는 없다. 이번에는 ApiResource 클래스라는, 다소 기초적인 내용을 다뤄 보겠다.

Resource

Resourceflask-restful을 구성하는 핵심 클래스 중 하나인데, 가장 먼저 눈에 띄는 부분은 MethodView를 상속받아 dispatch_request를 오버라이드했다는 것이다. flask_restful.Resource::dispatch_request

flask-restful이 오버라이드한 dispatch_request 메소드의 로직을 요약하자면,

  1. self에서 requested method에 해당하는 메소드를 가져오고, dispatch할 메소드가 발견되지 않는다면 assert statement를 통해 AssertionError를 raise한다.
  2. class attribute인 method_decorators에 접근해서, dispatch할 메소드에 해당하는 데코레이터들을 가져와 적용한다. 메소드 이름을 key로, 데코레이터 함수로 이루어진 리스트를 value로 두는 딕셔너리라고 보면 된다.
from functools import wraps

from flask_restful import Resource


def print_hello(fn):
  @wraps(fn)
  def wrapper(*args, **kwargs):
    print('hello')
    return fn(*args, **kwargs)
  return wrapper


class Dummy(Resource):
  method_decorators = {
    'get': [print_hello]
  }

  def get(self):
    return 'hi!'
  • print_hello 메소드는 get 메소드에 대해 @print_hello 형태로 데코레이션되지 않았으나, method_decorators에 정의되어 있으므로 dispatch_request에 의해 런타임에 데코레이션이 적용된다.
  • MethodView에서 지원하는 decorators 개념을 메소드 단위로 쪼개서 적용할 수 있도록 지원하기 위함이다.
  1. 메소드를 호출하고, 해당 메소드의 반환이 ResponseBase라면 바로 리턴, 그게 아니라면 representation을 가지고 mimetype(Content-Type 헤더의 값)을 새로 산정한다. flask-restful에서 이야기하는 representation이라는 개념은 조금 복잡하며, 이를 이해하는 것은 flask-restful의 코드 리딩에 매우 큰 도움을 주기에 따로 글을 쓸 것이다. 여기서 간단히 말하면 class attribute인 representations와 요청의 Accept 헤더를 가지고 best match(가장 써볼만한 mimetype)를 가져오고, 이게 없으면 그냥 리턴해서 Api 객체의 생성자에 전달된 default_mediatype을 따른다. flask-restful을 써먹는 걸 보면 대부분 후자의 로직을 타게 된다. default_mediatype의 기본값은 application/json이며 이에 대응되는 JSON serializer를 flask-restful이 미리 준비(flask_restful.representations.json.py::output_json)해 두어서, 따로 뭔가 설정하지 않으면 대충 JSON으로 적절히 response된다.

MethodView를 사용하던 것처럼, 해당 클래스를 상속받아 리소스를 정의하는 것이 가장 기본적인 practice다.

from flask_restful import Resource


class Dummy(Resource):
    def get(self):
        return {'hello': 'world'}

위에서 말했던 것처럼, default_mediatype과 이에 대응되어 있는 serializer 함수에 따라 Dummy::get 메소드의 반환값이 JSON string으로 dump된다. 일단 당장 편한 부분은, jsonify 함수로 감싸줄 필요가 없다는 것이다.

Api

일반적인 Flask extension 라이브러리들과 같이, 위에서 얘기했던 default_mediatype처럼 해당 extension에 대한 메타데이터들을 정리하고 이에 의존되는 메소드들을 정의해 두고 있다. Flask의 인스턴스와 extension의 요소들을 중간에서 엮어주는 역할이다. Api::add_resource 메소드를 통해 Resource의 서브클래스로서 정의된 view 핸들러를 라우팅하는 것이 핵심 기능이다. 맨 아래의 예제에 포함되어 있다.

ResourceApi 클래스만 알고 있으면 flask-restful은 이미 60% 이상 알고 있다고 보면 된다. 아래는 Resource와 Api만을 사용하는 flask-restful의 기초적인 사용 사례다.

from flask import Flask
from flask_restful import Api, Resource

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


class Dummy(Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(Dummy, '/', '/index')

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