Do you know flask-restful?
이라는 시리즈는 Flask에서 API를 클래스 기반으로 정의하는 데에 자주 사용되는 라이브러리인 flask-restful의 재밌는 요소들을 다룹니다. 공식 문서 + 필자가 코드 리딩을 하며 알아낸 것들로 내용을 채우려고 하며, real world에서 flask-restful을 사용하며 부딪혔던 상황들을 최대한 많이 공유하고자 합니다.
flask-restful은 Flask 자체에서 제공하는 Pluggable View의 MethodView 개념을 확장하여, 할당된 URI에 대한 HTTP 메소드별 핸들러를 클래스의 메소드 형태로 작성하는 것에 큰 도움을 준다. 따라서 flask-restful을 잘 이해하기 위해선 Flask의 Pluggable View 개념을 알고 있는 것이 좋다.
공식 문서에선 Pluggable Views라는 챕터에서 확인할 수 있는 내용인데, Django의 generic view(django.views.generic
)에 영감을 얻어 만들어진, 함수가 아니라 클래스로서 정의되는 뷰를 의미한다. 여기서 이야기하는 뷰는 그냥 하나 이상의 엔드포인트에 대한 요청을 처리하는 핸들러라고 생각하면 된다.
Pluggable View의 기본 클래스는 View
로, 단지 @app.route
에 함수로서 전달되던 요청 핸들러를 클래스에 정의된 메소드로 옮긴 것 뿐이다. View
클래스를 상속받고 dispatch_request
메소드를 정의하여 로직을 구현하면 된다. 이후 View
클래스에 정의된 as_view
메소드를 호출하며 엔드포인트 이름을 전달하면, 클래스에 정의된 dispatch_request
메소드를 view function처럼 다룰 수 있게 된다.
from flask import Flask
from flask.views import View
app = Flask(__name__)
class SayHello(View):
def dispatch_request(self):
return 'hello'
app.add_url_rule('/hello', view_func=SayHello.as_view('say_hello'))
dispatch_request
가 지원하는 HTTP method를 명시하려면, methods
라는 이름의 문자열로 이뤄진 연속열(list, tuple 등)을 class level attribute로 정의해 두면 된다.
...
class SayHello(View):
methods = ['GET']
def dispatch_request(self):
return 'hello'
...
@app.route
에 methods
인자를 전달했던 것과 동일하게, 명시해 둔 메소드 외의 요청에 대해서 405 METHOD NOT ALLOWED
를 반환한다. 단지 view function이 정의되는 계층이 한 단계 늘어났을 뿐이라 쓸모 없어 보일 수도 있겠지만, view들이 클래스 기반으로 정의되기에 DRY한 코드를 작성하는 등 여러 면에 대해 상속 구조라는 좋은 조력자가 생긴 것이다. 별도로 예제를 작성하진 않겠다.
View
를 써서 클래스를 정의하면, HTTP method에 대응되는 핸들러들을 메소드 형태로 각각 정의해 두고, dispatch_request
에선 요청에 따라 이들을 호출해주는 식으로 만들어볼 수 있다.
from flask import request, abort
class Base(View):
def dispatch_request(self, *args, **kwargs):
method = getattr(self, request.method.lower(), None)
if method is None:
abort(405)
method(*args, **kwargs)
class Dummy(Base):
def get(self):
...
def post(self):
...
def delete(self):
...
이렇게 직접 만들어줄 필요 없이, flask.views
패키지에는 MethodView
라는 클래스가 이미 존재하며, 실제로 위에서 작성한 예와 비슷하게 dispatch_request
가 미리 구현되어 있다.
from flask.views import MethodView
class Dummy(MethodView):
def get(self):
...
def post(self):
...
def delete(self):
...
Flask 0.8부턴 MethodView가 decorators
라는 class level attribute를 인식하게 되었는데, 이는 decorators
가 명시해 둔 데코레이터들을 해당 MethodView 하위의 모든 view function에 대해 적용하게 만든다.
from functools import wraps
from flask.views import MethodView
def print_hello_before_process(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
print('hello')
return fn(*args, **kwargs)
return wrapper
class Dummy(MethodView):
decorators = [print_hello_before_process]
def get(self):
...
def post(self):
...
오