old/토이프로젝트

Django - Swagger 연동하기 2편 - swagger parameter schema

뒷골목프로그래머 2022. 5. 5. 20:42
반응형

안녕하세요. 글쓰는 개발자 입니다.

 지난 번 Django - Swagger 연동하기 1편 - 기본설정에 이어서 swagger_auto_schema에 관해 세팅 방법을 몇 가지 소개하고자 합니다.

 

Django - Swagger 연동하기 1편 - 기본 설정

안녕하세요. 글쓰는 개발자입니다. 토이 프로젝트를 진행하면서 비록 혼자 진행하지만 최대한 현업 실무에 가깝게 또는 더 나아가 실무에 적용하고 싶은 기술과 방법론 등을 실험하고 있는데요

backstreet-programmer.tistory.com

 

 1편에서 API 문서화를 하는 이유는 Backend와 Frontend 간의 원활한 커뮤니케이션을 통한 비용 최소화에 그 목적이 있다고 말씀드렸습니다. 이를 위해서는 단순히 Swagger를 연동하고 별도의 페이지를 생성하는 것으로 그치는 것이 아니라 API의 schema를 표현 할 수 있어야하는데요. 고맙게도 drf-yasg는 @swagger_auto_schema를 제공하고 몇 가지 테크닉을 통해 원하는 schema를 적용 할 수 있습니다.

 

 저는 실무에서 Serializer를 사용합니다. 하지만, serializer를 잘 모르시는 분들을 위해 먼저, 쉽게 사용할 수 있는 openapi를 소개드리고 Serializer 사용법을 알려드리겠습니다.

 

본 글을 통해 아래와 같은 방법을 사용하실 수 있습니다.

1. @swagger_auto_schema 활용
2. drf_yasg의 openapi 활용
3. @swagger_auto_schema 와 serializer 활용
4. Querystring schema
5. request body schema
6. List 내부에 Dictionary가 있는 형태와 같이 복잡한 형태의 Request Body Schema 작성

 

 

1. 환경 설정

 QueryString과 request body schema 적용을 위해 get, post 방식의 view 함수를 작성하겠습니다.

@swagger_auto_schema() 데코레이터를 활용하기위해 이를 import 하고 get, post 메소드 각각 데코레이터를 적용 시킵니다.

// sample_swagger/views.py

from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework.response import Response # Create your views here.
from drf_yasg.utils import swagger_auto_schema

class TestView(APIView):
    permission_classes = [permissions.AllowAny]
    @swagger_auto_schema()
    def get(self, request):
        return Response("Swagger 연동 테스트")
    
    @swagger_auto_schema()
    def post(self, request):
        return Response("Swagger Schema")

 

2. openapi 사용

 openapi 사용시 schema 작성을 위한 code가 길어지기 때문에 open_api_params.py를 별도로 생성하여 view.py에 import해 사용하는 방식을 택하겠습니다. 개발 규모가 매우 작고 별도로 파일을 분리할 필요가 없다면 바로 작성하셔도 좋지만 개인적으로 분리하시는 것을 추천 드립니다.

 

1) GET, QueryString

 openapi.Parameter 의 첫번 째 인자로 parameter 명칭을 넣고 description에 설명을, type에서 data type을 설정합니다. 아래 그림과 같이 다양한 형식의 data type을 지원하고 있습니다. 마지막으로 default 값이 있는 경우 설정을 하면 완료입니다.

// sample_swagger/open_api_params.py

from drf_yasg import openapi

get_params = [
	openapi.Parameter(
        "start_date",
        openapi.IN_QUERY,
        description="yyyy-mm-dd",
        type=openapi.FORMAT_DATE,
        default=""
    ),
    openapi.Parameter(
        "end_date",
        openapi.IN_QUERY,
        description="yyyy-mm-dd",
        type=openapi.FORMAT_DATE,
        default=""
    )
]

paramter 세팅을 마치고 작성한 openapi schema를 view에 추가합니다. get 함수 위에 데코레이터를 추가하고 manual_paramters 값으로 작성한 get_params를 추가합니다.

// sample_swagger/views.py

...
from .open_api_params import get_params

class TestView(APIView):
    permission_classes = [permissions.AllowAny]
    @swagger_auto_schema(manual_parameters=get_params)
    def get(self, request):
        return Response("Swagger 연동 테스트")
...

swagger를 실행하면 아래와 같이 QueryString으로 전달할 인자를 입력하는 UI가 생성된 것을 확인할 수 있습니다.

2) POST, Request Body

get 방식과 다르게 reqeust_body로 전달하고 Object 타입을 사용 합니다.

// sample_swagger/open_api_params.py

...
post_params = openapi.Schema(
    type=openapi.TYPE_OBJECT, 
    properties={
        'x': openapi.Schema(type=openapi.TYPE_STRING, description='string'),
        'y': openapi.Schema(type=openapi.TYPE_STRING, description='string'),
    }
)
...

from .open_api_params import get_params, post_params

class TestView(APIView):
    permission_classes = [permissions.AllowAny]
    @swagger_auto_schema(manual_parameters=get_params)
    def get(self, request):
        return Response("Swagger 연동 테스트")
    
    @swagger_auto_schema(request_body=post_params)
    def post(self, request):
        return Response("Swagger Schema")

 

3. serializer 사용

 저는 실무에서 serializer를 주로 활용 합니다. Serializer를 활용해 코드를 작성하면 가독성이 좋아 직관적으로 의미를 이해할 수 있고 openapi를 활용하는 것에 비해 코드 길이 자체도 줄어드는 장점이 있습니다. Serializer 를 적용할 view와 serializer를 생성하여 튜토리얼을 진행하겠습니다.

 

1) 환경설정

 views.py에 SerializerView를 추가하고 urls.py에 serializer를 추가합니다. 그리고 serializer를 생성합니다.

//sample_swagger/views.py

...
    
class SerializerView(APIView):
    permission_classes = [permissions.AllowAny]
    @swagger_auto_schema()
    def get(self, request):
        return Response("Swagger 연동 테스트")
    
    @swagger_auto_schema()
    def post(self, request):
        return Response("Swagger Schema")
// sample_swagger.py/urls.py

from django.urls import path
from sample_swagger.views import TestView, SerializerView
urlpatterns = [ 
            path('v1/test/', TestView.as_view(), name='test'),
            path('v1/serializer/', SerializerView.as_view(), name='serializer'),
        ]
// sample_swagger/serializers.py

from rest_framework import serializers

 

2) GET, QueryString

 serializer를 활용할 경우 @swagger_auto_schema에서 query_serializer를 사용합니다. 먼저, 생성한 serializers.py에 request, response schema를 설정합니다. 저는 아래와 같은 형태의 Schema를 작성하고자 합니다.

// request
{
    'param1': integer,
    'param2': string,
    'param3': date,
}

// response
{
    'status': 200,
    'message': 'SUCCESS'
}

 Request, Response 각각을 위한 serializer class를 2개 생성합니다. 사용하고 싶은 parameter 명칭과 동일한 변수를 선언하고 serializers에 내장된 데이터 타입을 선언합니다.

// sample_swagger/serializers.py

from rest_framework import serializers

class GetRequestSerializer(serializers.Serializer):
    param1 = serializers.IntegerField()
    param2 = serializers.CharField(max_length=20)
    param3 = serializers.DateField()
    
class GetResponseSerializer(serializers.Serializer):
    status = serializers.CharField()
    message = serializers.CharField()

 

 그리고 views.py에서 openapi에서 사용한 manual_parameter 대신 query_serializer를 사용합니다. 이번에는 serializers에서 생성한 GetResponseSerializer 사용을 위해 responses도 @swagger_auto_schema 데코레이터에 추가합니다.

...
from .serializers import GetRequestSerializer, GetResponseSerializer

...
    
class SerializerView(APIView):
    permission_classes = [permissions.AllowAny]
    @swagger_auto_schema(query_serializer=GetRequestSerializer, responses={"200":GetResponseSerializer})
    def get(self, request):
        return Response("Swagger 연동 테스트")
    
    @swagger_auto_schema()
    def post(self, request):
        return Response("Swagger Schema")

 세팅 완료 후 다시 swagger를 확인해 보면, 아래 그림과 같이 Request / Response 모두 Schema를 확인 할 수 있습니다.

 

3) Post, request body

 request body 세팅을 설명하면서 복잡한 형태를 serializer로 작성하는 방법을 공유하겠습니다. 튜토리얼 수준의 학습을 하는 경우에는 API의 request가 간단한 형태로 전달되지만 실무에서는 훨씬더 복잡한 경우가 많습니다. 모든 값이 Integer, String 형태이거나 key-value가 언제나 1대 1로 매칭이 되면 좋겠지만 value가 배열로 전달되고 그 내부에 다시 객체가 존재하는 경우도 꽤 많습니다. 저는 처음에 이것을 openapi로 적용해서 사용해보았는데 여간 귀찮은 것이 아니고 코드 가독성도 매우 좋지 않아서 swagger 세팅 때문에 에러가 발생하는 어처구니 없는 상황도 있었습니다. 이것이 제가 serializer를 사용하게 된 근본적인 원인이고 방법을 알려드리겠습니다.

 

저는 아래와 같은 형태의 Schema를 작성하고자 합니다. 일반적인 Schema 작성법에 의해 문제가 되는 부분은 student_list 부분 입니다. 배열 내부에 객체가 있는 형태로 조금 복잡합니다.

//request
{
    "school_name": "KNU",
    "student_list": [
        {
            "first_name": "Hanseul",
            "last_name": "Jo"
        },
        {
            "first_name": "Hanseul",
            "last_name": "Cho"
        }
    ]
}

//response
{
    "status": 201,
    "message": "SUCCESS"
}

 

  우선 student_list 배열 내부의 객체를 신경 쓰지 않고 Serializer를 작성하겠습니다. 저는 PostRequestSerializer와 PostResponseSerializer를 작성하고 views.py에 적용했습니다.

// sample_swagger/serializers.py

...

class PostRequestSerializer(serializers.Serializer):
    school_name = serializers.CharField()
    student_list = serializers.ListField()

class PostResponseSerializer(serializers.Serializer):
    status = serializers.CharField()
    message = serializers.CharField()
// sample_swagger/views.py

...
from .serializers import GetRequestSerializer, GetResponseSerializer, PostRequestSerializer, PostResponseSerializer
...
    
class SerializerView(APIView):
...    
    @swagger_auto_schema(request_body=PostRequestSerializer, responses={"201": PostResponseSerializer})
    def post(self, request):
        return Response("Swagger Schema")

 

swagger를 살펴보니 Request와 Response가 정상적으로 잘 생성되었습니다. 하지만 student_list를 살펴보면 단순 배열일 뿐, 배열 내부에 원래 구현하고자 했던 first_name, last_name을 포함하는 객체는 없습니다.

 

그렇다면, 이제 student_list 배열 내부에 객체를 추가하겠습니다. 다시 serializers.py로 돌아가 보겠습니다. 일단 객체를 형상화 할 수 있는 Serializer를 생성합니다. 그리고 PostRequestSerializer의 student_list 부분에 넣어줍니다. 기존에 ListFiled로 선언된 것을 지우고 작성한 PostInnerDictSerializer를 추가합니다. 그리고 many=True 옵션을 주어 객체를 List로 감싸줍니다.

// sample_swagger/serializers.py

...

class PostInnerDictSerializer(serializers.Serializer):
    first_name = serializers.CharField()
    last_name = serializers.CharField()

class PostRequestSerializer(serializers.Serializer):
    school_name = serializers.CharField()
    student_list = PostInnerDictSerializer(many=True)

class PostResponseSerializer(serializers.Serializer):
    status = serializers.CharField()
    message = serializers.CharField()

 

이제 아래와 같이 student_list에 객체가 추가된 것을 확인하실 수 있습니다.

 

 여기까지 따라오시느라 수고 많으셨습니다. 개발 초기에 API문서화 관련 세팅을 마치시고 협업시 커뮤니케이션 비용을 최소화 하셨으면 좋겠습니다. 부족한 글 끝까지 읽어주셔서 감사합니다.

 

Referrences
https://drf-yasg.readthedocs.io/en/stable/custom_spec.html
반응형