Namu | 나무 개발자 블로그입니다


REST, RESTful, Flask-RESTful by namu

post image
image by sjh836.tistory.com

[목차]

  1. REST
  2. REST API
  3. RESTful
  4. Flask RESTful

[참조]

  1. REST- wikipedia
  2. Flask-RESTful 공식 문서


들어가며

RESTRepresentational State Transfer 의 약자로 네트워크 아키텍처 원리의 모음이다. 여기서 ‘네트워크 아키텍처 원리’란 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 일컫는다.

REST 는 로이 필딩(Roy Fielding)의 박사 학위 논문 에서 제안되었고, 이 원칙을 따르는 시스템을 RESTful 하다고 지칭한다.

이 글에서는 REST, REST API, RESTful 그리고 REST 확장 모듈인 Flask-RESTful 의 활용예를 살펴본다.


REST


REST 의 요소


REST 의 조건

다음은 REST 아키텍처에 적용되는 6가지 제한 조건이다.

  1. Server-Client 구조 - 일관적인 인터페이스로 분리
  2. Stateless - 각 요청 간 클라이언트의 컨텍스트가 서버에 저장되면 안 된다
  3. Cacheable - WWW 에서와 같이 클라이언트는 응답을 캐싱할 수 있어야 한다
  4. Layered System(계층화) - 로드 밸런싱 등 중간 서버의 기능은 시스템 규모 확장서을 향상시킨다
  5. Code-On-Demand(optional) - 서버가 클라이언트가 실행시킬 수 있는 로직을 포함해서 전송, 기능의 확장
  6. Uniform Interface - 인터페이스 일관성. 아키텍처를 단순화하고 작은 단위로 분리(decouple) 클라이언트-서버 간 독립성 높힘


REST API


REST API 의 기능


REST API Architecture 의 기본 원리

REST API 설계 예시


RESTful


Flask RESTful

참조 : Flask-RESTful 공식 문서 번역


분류


Quickstart

from flask import Flask
from flask_restful import Resource, Api

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

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

api.add_resource(HelloWorld, '/')

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

이것을 저장한 후 실행하면 된다. Flask debugging mode 를 활성화하면 코드 리로딩과 더 나은 에러 메시지를 보여 준다. 다만, 실제 운영 환경에서 디버그 모드는 절대 활성화되면 안 된다!

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

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

todos = {}

class TodoSimple(Resource):
    def get(self, todo_id):
        return {todo_id: todos[todo_id]}

    def put(self, todo_id):
        todos[todo_id] = request.form['data']
        return {todo_id: todos[todo_id]}

api.add_resource(TodoSimple, '/<string:todo_id>')

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

add_resource 부분에 추가한 리소스 정의에 따라 URI 에는 str 타입의 고유한 id 가 포함되어야 한다. REST 원리에 따라 각 HTTP 메서드 동작에 맞는 메서드가 호출된다. Flask-RESTful 은 view 메서드로부터 반환되는 여러 종류의 return 값을 이해한다. Flask 와 유사하게, iterable 하고 response 객체로 변환 가능한 모든 결과값을 Flask response object 에 담아서 있는 그대로 반환할 수 있다. 또한, 다중 return 값을 활용해서 response code 와 response headers 를 세팅할 수 있다. 아래 코드를 보자.

class Todo1(Resource):
    def get(self):
        # Default to 200 OK
        return {'task': 'Hello world'}

class Todo2(Resource):
    def get(self):
        # Set the response code to 201
        return {'task': 'Hello world'}, 201

class Todo3(Resource):
    def get(self):
        # Set the response code to 201 and return custom headers
        return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'}
api.add_resource(HelloWorld,
    '/',
    '/hello')

또한 당신의 resource methods 에 특정 변수를 경로의 endpoint 부분으로 매치시킬 수 있다.

api.add_resource(Todo,
    '/todo/<int:todo_id>', endpoint='todo_ep')
from flask_restful import reqparse

parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate to charge for this resource')
args = parser.parse_args()

여기서 reqparse.RequestParser.parse_args() 는 파이썬 dict 타입 값을 반환한다. reqparse 모듈을 사용하면 정상적인 에러 메시지가 제공된다. 인자(매개변수)가 유효성 검증에 실패하면, Flask-RESTful 은 400 Bad Request 와 함께 error에 대한 response highlighting 를 respond 할 것이다. inputs 모듈은 inputs.date(), inputs.url() 과 같이 여러 종류의 common conversion 함수를 제공한다. 또한, strict=True 인자와 함께 parse_args() 메서드가 호출되면 당신의 parser 에 정의되지 않은 arguments 가 request 에 포함된 경우 에러가 thrown 된다.

args = parser.parse_args(strict=True)
from flask_restful import fields, marshal_with

resource_fields = {
    'task':   fields.String,
    'uri':    fields.Url('todo_ep')
}

class TodoDao(object):
    def __init__(self, todo_id, task):
        self.todo_id = todo_id
        self.task = task

        # This field will not be sent in the response
        self.status = 'active'

class Todo(Resource):
    @marshal_with(resource_fields)
    def get(self, **kwargs):
        return TodoDao(todo_id='my_todo', task='Remember the milk')

위 예제는 파이썬 객체와 그것의 preparses 가 직렬화되는 과정을 보여준다. marshal_with() decorator 는 resource_fields 에 의해 describe 된 변환 결과를 apply 할 것이다. 객체로부터 추출된 유일한 field 는 ‘task’ 이다. fields.Url field 는 endpoint 이름을 포함하고, response 에서 그 endpoint 를 위한 URL 을 생성하는 특별한 field 이다. 당신이 필요한 수많은 field 타입들은 이미 포함되어 있다. 이를 위해 fields 가이드를 참조할 것.

from flask import Flask
from flask_restful import reqparse, abort, Api, Resource

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

TODOS = {
    'todo1': {'task': 'build an API'},
    'todo2': {'task': '?????'},
    'todo3': {'task': 'profit!'},
}


def abort_if_todo_doesnt_exist(todo_id):
    if todo_id not in TODOS:
        abort(404, message="Todo {} doesn't exist".format(todo_id))

parser = reqparse.RequestParser()
parser.add_argument('task')


# Todo
# shows a single todo item and lets you delete a todo item
class Todo(Resource):
    def get(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]

    def delete(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '', 204

    def put(self, todo_id):
        args = parser.parse_args()
        task = {'task': args['task']}
        TODOS[todo_id] = task
        return task, 201


# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(Resource):
    def get(self):
        return TODOS

    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo%i' % todo_id
        TODOS[todo_id] = {'task': args['task']}
        return TODOS[todo_id], 201

##
## Actually setup the Api resource routing here
##
api.add_resource(TodoList, '/todos')
api.add_resource(Todo, '/todos/<todo_id>')


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