REST API를 쉽게 구축할 수 있도록 도와주는 패키지입니다.
API 문서화 지원
Swagger
또는 OpenAPI Specification
형식으로 API 문서를 자동으로 생성할 수 있습니다. API의 엔드포인트, 매개변수, 응답 형식 등을 문서화하여 API의 이해도를 높여줍니다.
네임스페이스
관련된 엔드포인트를 그룹화하고 구조화하여 API 코드를 관리할 수 있는 네임스페이스 기능을 제공합니다.
요청 파싱 및 검증
클라이언트로부터 수신한 요청 데이터의 파싱과 검증을 쉽게 수행할 수 있는 기능을 제공합니다. 요청 데이터의 유효성을 검사하고 처리할 수 있습니다.
응답 구성
클라이언트에게 반환되는 응답의 형식을 쉽게 구성할 수 있습니다. JSON 형식의 응답 데이터를 반환하거나 사용자 정의 응답을 생성할 수 있습니다.
에러 처리
에러 핸들링과 예외 처리를 위한 도구를 제공하여 API에서 발생하는 오류를 관리하고 사용자에게 적절한 오류 메시지를 제공할 수 있습니다.
인증 및 권한 부여
Flask-RESTx는 JWT 및 다른 인증 기법을 사용하여 API에 인증 및 권한 부여 기능을 구현할 수 있습니다.
빠른 개발
Flask-RESTx는 간단하고 직관적인 인터페이스를 제공하여 빠르고 쉬운 API 개발을 도와줍니다.
💡 본 글은 예제 코드를 이용하여 설명합니다.
REST API를 설계, 문서화, 테스트하는 데 도움이 되는 오픈 소스 프레임워크 및 도구 모음입니다. Swagger는 API의 스펙을 정의하고, 이를 시각적이고 상호 작용 가능한 형태로 제공하여 개발자들이 API를 쉽게 이해하고 사용할 수 있도록 돕습니다.
OpenAPI Specification
Swagger는 OpenAPI Specification라고 하는 API 설계를 위한 명세를 사용합니다. 이 명세는 API 엔드포인트, 매개변수, 응답 형식, 인증 방법 등 API에 대한 자세한 정보를 포함합니다.
API 문서화
Swagger는 API를 자동으로 문서화할 수 있는 기능을 제공합니다. OpenAPI Specification에 따라 작성된 명세를 바탕으로 API의 엔드포인트, 매개변수, 응답 형식 등을 문서화하고 시각적으로 표현합니다.
상호 작용 가능한 API 문서
Swagger UI를 통해 API 문서를 시각적이고 상호 작용 가능한 형태로 제공합니다. 이 UI를 통해 사용자는 API를 테스트하고 인터랙티브하게 살펴볼 수 있습니다.
API 테스트 및 디버깅
Swagger UI를 사용하여 API를 테스트하고 디버깅하는 데 사용할 수 있습니다. 각 엔드포인트를 호출하고 요청을 보내며 응답을 확인할 수 있습니다.
생성된 코드의 테스트 및 문서
Swagger를 사용하여 생성된 클라이언트 라이브러리 및 서버 스텁 코드에 대한 테스트 및 문서를 생성할 수 있습니다. 이는 API를 사용하는 데 도움이 될 수 있습니다.
다양한 플랫폼 지원
Swagger는 여러 플랫폼 및 프로그래밍 언어에서 사용할 수 있습니다. 이는 개발자들이 자신의 선호하는 환경에서 API를 쉽게 사용할 수 있도록 돕습니다.
기본 구성은 다음과 같이 크게 3가지 영역으로 나누어 볼 수 있습니다.
위에서 부터 첫 번째 영역은 API의 제목, 설명, 버전 등이 표시가 되며 좌측 하단의 Authorize는 API Key를 사용하여 API를 이용할 때, 인증하는 버튼입니다.
두 번째 영역은 API가 모여있는 네임스페이스입니다. 해당 네임스페이스를 클릭하면 HTTP 메서드 별로 API가 표시됩니다.
API를 클릭하면 다음과 같이 API에 대한 요청 값과 응답 값에 대한 설명들이 나타납니다.
우측의 Try it out
을 클릭하면 API를 실행할 때, 필요한 값들을 직접 넣을 수 있습니다. API에 필요한 값들을 넣은 후 Execute
를 클릭하면 HTTP 요청이 전송됩니다.
세 번째 영역은 모든 API의 요청과 응답에 사용된 모델들을 모아놓은 영역입니다.
화살괄호(>)를 클릭하면 모델에 대한 설명들을 확인할 수 있습니다.
flask/source/config.py를 살펴봅니다.
기본 설정으로는 RESTX_VALIDATE
를 True로 지정하여, 클라이언트의 요청에 대해 입력 값을 자동으로 검증합니다.
RESTX_MASK_SWAGGER
를 False로 지정하여, 불필요한 마스크를 출력하지 않게 지정하였습니다.
class Config:
# 코드 생략
RESTX_VALIDATE = True
RESTX_MASK_SWAGGER = False
flask/source/my_app/api/v1/__init__.py를 살펴봅니다.
모든 api의 기본 경로를 "/api/v1"으로 지정하여 블루프린트를 설정하였습니다.
bp_api = Blueprint(
name="api",
import_name=__name__,
url_prefix="/api/v1"
)
인증 방식 중 API Key를 사용한 방식을 실습하기 위해 형식을 지정하였습니다.
API 객체를 생성할 때, Swagger와 관련된 정보와 API Key 정보를 지정하였습니다.
authorizations = {
"apikey": {
"type": "apiKey",
"in": "header",
"name": "X-API-KEY"
}
}
api = Api(
app=bp_api,
version="1.0",
title="RESTx API",
description="Flask-RESTx Example",
doc="/docs",
encoding="UTF-8",
authorizations=authorizations
)
실습을 위한 API를 등록하는 코드입니다.
from my_app.api.v1.auth import auth_namespace
from my_app.api.v1.post import post_namespace
api.add_namespace(ns=auth_namespace, path="/auth")
api.add_namespace(ns=post_namespace, path="/post")
flask/source/my_app/__init__.py를 살펴봅니다.
Flask 객체에서 register_blueprint를 이용하여 api 블루프린트를 등록하여 애플리케이션에 적용합니다.
def create_app():
app.config.from_object(obj=config["development"])
with app.app_context():
# 코드 생략
from my_app.api.v1 import bp_api
# 코드 생략
app.register_blueprint(blueprint=bp_api)
flask/source/my_app/api/v1/auth.py를 살펴봅니다.
이전 글인 Flask-JWT-Extended 예제에서 JWT의 위치를 headers로 지정하였었습니다.
따라서, API를 요청받을 때 HTTP 헤더 중 Authorization 필드에 Bearer {JWT} 형식으로 받아 드릴 수 있도록 지정합니다.
auth_namespace = Namespace(name="auth", description="Authentication API")
resource_parser = reqparse.RequestParser()
refresh_parser = reqparse.RequestParser()
resource_parser.add_argument("Authorization", help="Bearer {access_token}", type=str, required=True, location="headers", default="Bearer ")
refresh_parser.add_argument("Authorization", help="Bearer {refresh_token}", type=str, required=True, location="headers", default="Bearer ")
이후, expect
데코레이터를 이용하여 Authorization 필드를 검증하게 합니다.
@auth_namespace.expect(resource_parser)
JWT의 위치를 지정한 파서를 사용하는 API를 보면 아래의 이미지와 같이 HTTP header를 요구하는 것을 확인할 수 있습니다.
flask/source/my_app/api/v1/post.py를 살펴봅니다.
[2. 구성]에서 authorizations의 type이 apiKey인 키의 이름을 apikey로 지정하였었습니다.
doc 데코레이터의 security
인자에 지정한 키의 이름과 똑같이 전달합니다.
@post_namespace.doc(description="""게시글을 조화하는 API입니다.""", security="apikey")
[2. 구성]에서 authorizations에서 apikey가 위치하는 곳은 in이라는 키로 지정되고, 이름은 name이라는 키로 지정됩니다. 본 예제에서는 HTTP 헤더 필등 중 X-API-KEY로 지정하였습니다.
함수 내부에서는 헤더의 필드를 검사하고 KEY에 대한 검증 로직을 추가하면 됩니다.
if not "X-API-KEY" in request.headers:
return make_response(jsonify(msg="API key is missing"), 401)
api_key = request.headers.get(key="X-API-KEY", default="")
if "myapikey" != api_key:
return make_response(jsonify(msg="Invalid API key"), 403)
API Key를 사용하면 Swagger에서 Authorize 버튼을 통해 Key를 입력해 놓으면, API를 실행 시 별도의 입력을 필요로하지 않습니다.
flask/source/my_app/api/v1/auth.py를 살펴봅니다.
JWT를 발급받기 위해 인증 과정을 거치기 위해서는 사용자의 계정정보를 전달해야 합니다.
먼저, 전달받을 형식을 model
을 통해 지정합니다.
token_req_model = auth_namespace.model(name="토큰 발급 요청 모델", model={
"username": fields.String(required=True, description="사용자 아이디"),
"password": fields.String(required=True, description="사용자 비밀번호")
})
이후, 해당 형식을 받는 API에는 expect
데코레이터를 통해 검증을 수행하게 합니다.
[2. 구성]에서 RESTX_VALIDATE가 False라면, expect의 validate 인자에 True를 전달하여야 검증을 수행하게 됩니다.
@auth_namespace.expect(token_req_model)
만약, 전달받는 데이터가 리스트 형이라면 다음과 같이 모델을 리스트 형으로 변환해주면 됩니다.
@auth_namespace.expect([token_req_model])
데이터를 전달받으면 함수에서는 payload
를 통해 데이터에 접근할 수 있습니다. payload 데이터는 Dictionary와 같이 전달받은 모델의 키를 통해 값을 확인할 수 있습니다.
args = auth_namespace.payload
user = User.query.filter_by(username=args.get("username")).first()
리스트 형 데이터를 전달받을 때, 다른 model이나 데이텨 형식을 참조할 수 있습니다.
user_model = auth_namespace.model(name="사용자 모델", model={
"id": fields.String(description="사용자 식별 값"),
"username": fields.String(description="사용자 아이디"),
"roles": fields.List(description="사용자 역할", cls_or_instance=fields.String())
})
Swagger에서는 Payload라는 이름으로 전달받을 데이터의 형식을 확인할 수 있습니다.
또한, Model을 클릭하면 코드 상에서 지정한 description이 출력되어 변수들의 용도를 확인할 수 있습니다.
요청할 데이터에 파일이 필요하다면, 아래의 예시와 같이 type을 FileStorage로, location을 files로 지정합니다. Swagger에서는 첨부 파일 버튼이 생성되어 쉽게 테스트 해볼 수 있습니다.
from werkzeug.datastructures import FileStorage
file_parser = reqparse.RequestParser()
file_parser.add_argument(name="file", help="엑셀(XLSX) 파일", type=FileStorage, required=True, location="files")
@api_namespace.expect(resource_parser, file_parser)
💡 model의 필드에는 enum=["admin", "user"]과 같이 enum 인자를 통해 같은 열거형을 통해 정해진 값을 제한할 수 있으며, min/max/default 인자를 통해 Integer 형의 데이터 범위를 제한할 수 있습니다.
💡 model의 필드에는 validate=lambda x: x in [i for i in range(5)]와 같이 validate라는 인자에 lambda나 함수를 전달하여 검증 방식을 사용자가 지정할 수 있습니다.
flask/source/my_app/api/v1/auth.py를 살펴봅니다.
API 요청에 대한 결과 값을 반환하는 모델을 다음과 같이 작성하였습니다.
token_res_model = auth_namespace.model(name="토큰 발급 응답 모델", model={
"access_token": fields.String(description="액세스 토큰"),
"refresh_token" : fields.String(description="재발급 토큰")
})
API에는 marshal_with
이라는 데코레이터를 이용하면 모델을 통해 반환 형식을 지정할 수 있습니다. API 함수에서는 모델과 같은 형식으로 반환
하여야 합니다.
만약, 응답 형식이 리스트 형이라면 marshal_with에 as_list라는 인자에 True를 전달하면 됩니다.
@auth_namespace.marshal_with(fields=token_res_model, code=201, description="토큰 발급 성공")
def post(self):
"""토큰 발급"""
args = auth_namespace.payload
user = User.query.filter_by(username=args.get("username")).first()
if user and user.check_password(password=args.get("password")):
access_token = create_access_token(identity=user.id)
refresh_token = create_refresh_token(identity=user.id)
return dict(access_token=access_token, refresh_token=refresh_token), 201
API를 실행하면 Server response 부분에서 응답 값을 확인할 수 있습니다.
flask/source/my_app/api/v1/post.py를 살펴봅니다.
API 요청에 대한 결과 값을 반환하는 모델을 다음과 같이 작성하였습니다.
post_model = post_namespace.model(name="게시글 모델", model={
"id": fields.String(description="게시글 식별 값"),
"title": fields.String(description="게시글 제목"),
"content": fields.String(description="게시글 내용"),
"username" : fields.String(description="게시글 글쓴이"),
"created" : fields.String(description="게시글 작성 일시"),
"views" : fields.String(description="게시글 조회수"),
})
marshal_with은 다양한 응답 코드와 응답 메시지를 설정할 수 없었습니다. 하지만 반환 값과 모델이 일치하여야만 제대로 데이터가 나오는 점을 통해 일관성을 확보할 수 있는 장점이 있었습니다.
response
데코레이터를 사용하면 다양한 응답 코드와 응답 메시지를 설정할 수 있지만, 개발자의 실수 등으로 인하여 모델과 반환 값이 다를 수 있는 경우가 생길 수 있습니다.
아래의 예시는 응답 코드 별로 다른 모델을 반환하는 코드입니다. 응답 값이 리스트 형이면 [모델]과 같이 리스트 형으로 변경하면 됩니다.
@post_namespace.response(code=200, description="게시글 정보", model=[post_model])
@post_namespace.response(code=401, description="API key가 존재하지 않습니다.", model=error_message)
@post_namespace.response(code=403, description="허용되지 않은 API key입니다.", model=error_message)
response를 사용하면 상황 별 응답 코드 별로 다른 모델을 반환시킬 수 있습니다.