DRF-Spectacular 사용법

donguk im·2021년 10월 6일
3
post-thumbnail
post-custom-banner

OAS3.0 이란?

OAS는 프로그램의 내부는 다양한 언어로 개발되어있을 지라도, API문서 형태는 동일한 규격으로 나타냄으로 언어에 상관없이 문서만 보고 각 인터페이스의 역할과 구조를 알 수 있게해주는 통일된 API 문서 규격

OAS3.0 (Open Api Spec 3.0)은 2.0보다 재사용성, 유연성이 향상된 구조로 바뀌었다.

drf-spectacular란?

drf(django rest framwork)를 통해서 설계된 API를 OAS3.0 에 맞게 문서화를 도와주는 라이브러리

drf-spectacular 사용법

1. 설치 및 설정

설치

$ pip install drf-spectacular  # pip을 사용하면 이렇게 설치
$ poetry add drf-spectacular  # poetry를 사용하면 이렇게 설치

django app 등록 및 설정

# settings.py

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'drf_spectacular',
    # ...
]

REST_FRAMEWORK = {
    # YOUR SETTINGS  drf의 schema 클래스를 drf-specacular의 AutoSchema로 교체해줍니다.
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}

docs API View 등록 (필수)

path()로 적어도 SpectacularJSONAPIView와 SpectacularYAMLAPIView둘 중 하나는 반드시 등록이 되어야 한다. Open API 문서를 API로 조회할 수 있어야 Swagger 또는 Redoc와 같은 API Document UI를 사용할 수 있다.

# Spectacular Document API
app_name = 'api'
urlpatterns += [
    path("docs/json/", SpectacularJSONAPIView.as_view(), name="schema-json"),
    path('schema/', SpectacularAPIView.as_view(), name='schema'),
    path('schema/user', SpectacularAPIView.as_view(), name='user_schema'),
    path('schema/swagger-ui/', SpectacularSwaggerView.as_view(**url_name='api:schema'**), name='swagger-ui'),
    path('schema/redoc/', SpectacularRedocView.as_view(url_name='api:schema'), name='redoc'),
]

name_space가 존재하는 urls.py에서 swagger-ui를 사용하려면 SpectacularSwaggerView.as_view()url_name을 매개변수로 전달해줘야만 한다.
전달하지 않으면, default url_name 을 사용 하기때문에 NoReverseMatchException 발생!

settings 커스터마이징 (선택사항)

SPECTACULAR_SETTINGS에는 API Doc의 개요에 관한 내용과 그외 다양한 옵션들이 존재한다.

# settings.py 

SPECTACULAR_SETTINGS = {
    # General schema metadata. Refer to spec for valid inputs
    # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#openapi-object
    'TITLE': 'drf-spectacular API Document',
    'DESCRIPTION': 'drf-specatular 를 사용해서 만든 API 문서입니다.',
    # Optional: MAY contain "name", "url", "email"
    'CONTACT': {'name': '김성렬', 'url': 'http://www.example.com/support', 'email': 'KimSoungRyoul@gmail.com'},
    # Swagger UI를 좀더 편리하게 사용하기위해 기본옵션들을 수정한 값들입니다.
    'SWAGGER_UI_SETTINGS': {
        # https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/  <- 여기 들어가면 어떤 옵션들이 더 있는지 알수있습니다.
        'dom_id': '#swagger-ui',  # required(default)
        'layout': 'BaseLayout',  # required(default)
        'deepLinking': True,  # API를 클릭할때 마다 SwaggerUI의 url이 변경됩니다. (특정 API url 공유시 유용하기때문에 True설정을 사용합니다)
        'persistAuthorization': True,  # True 이면 SwaggerUI상 Authorize에 입력된 정보가 새로고침을 하더라도 초기화되지 않습니다.
        'displayOperationId': True,  # True이면 API의 urlId 값을 노출합니다. 대체로 DRF api name둘과 일치하기때문에 api를 찾을때 유용합니다.
        'filter': True,  # True 이면 Swagger UI에서 'Filter by Tag' 검색이 가능합니다
    },
    # Optional: MUST contain "name", MAY contain URL
    'LICENSE': {
        'name': 'MIT License',
        'url': 'https://github.com/KimSoungRyoul/DjangoBackendProgramming/blob/main/LICENSE',
    },
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,  # OAS3 Meta정보 API를 비노출 처리합니다.
     
     # https://www.npmjs.com/package/swagger-ui-dist 해당 링크에서 최신버전을 확인후 취향에 따라 version을 수정해서 사용하세요.
    'SWAGGER_UI_DIST': '//unpkg.com/swagger-ui-dist@3.38.0',  # Swagger UI 버전을 조절할수 있습니다.
    
}

2. 사용방법

  • @extend_schema
    메소드 단위의 데코레이터 , 하나의 메소드(path)에 해당하는 문서화를 커스터마이징 할때 사용하고, 가장 핵심이 되는 데코레이터⭐️⭐️⭐️⭐️⭐️
    사용 가능한 파라메터는 아래와 같다.
    def extend_schema(
      	operation_id: Optional[str] = None,
    	  parameters: Optional[List[Union[OpenApiParameter, _SerializerType]]] = None,
    		request: Any = empty,
    **		responses: Any = empty, 
    		auth: Optional[List[str]] = None,
    		description: Optional[str] = None,
    		summary: Optional[str] = None,
    		deprecated: Optional[bool] = None,
    		tags: Optional[List[str]] = None,
    		exclude: bool = False,
    		operation: Optional[Dict] = None,
    		methods: Optional[List[str]] = None,
    		versions: Optional[List[str]] = None,
    		examples: Optional[List[OpenApiExample]] = None,
    		
    		# operation_id : 자동으로 설정되는 id 값, 대체로 수동할당하여 쓰진 않음
    		# parameters : 해당 path로 받기로 예상된 파라미터 값 (Serializer or OpenApiParameter 사용)
    		# request : 요청시 전달될 content의 형태
    		# responses : 응답시 전달될 content의 형태
    		# auth : 해당 method에 접근하기 위한 인증방법
    		# description: 해당 method 설명
    		# summary : 해당 method 요약
    		# deprecated : 해당 method 사용여부 
    		# tags : 문서상 보여줄 묶음의 단위
    		# exclude : 문서에서 제외여부  
    		# operation : ??? json -> yaml 하기위한 dictionary??? 
    		# methods : 요청 받을 Http method 목록
    		# versions : 문서화 할때 사용할 openAPI 버전
    		# examples : 요청/응답에 대한 예시
    		# ...methods...
    )

    💡 커스텀 하기위해 사용된 OpenApiParameter, OpenApiExample의 사용법을 최대한 Example에 담았습니다.
         추가로 더 궁금하신 점이 있다면, 공식문서를 참고해주세요.

    Example

    class UserViewSet(viewsets.ModelViewSet):
        """
        API endpoint that allows users to be viewed or edited.
        """
        queryset = User.objects.all().order_by('-date_joined')
        serializer_class = UserSerializer
        permission_classes = [permissions.AllowAny]
    
        @extend_schema(
            tags=['테스트'],
            description='테스트를 위한 메소드입니다',
            responses=GroupSerializer,
            examples=[
                OpenApiExample(
                    response_only=True,
                    summary="이거는 Response Body Example입니다.",
                    name="success_example",
                    value={
                        "url": "https://dashboard.datamaker.io/",
                        "name": "데이터메이커",
                    },
                ),
            ],
            parameters=[
                OpenApiParameter(
                    name="path_param",
                    type=str,
                    location=OpenApiParameter.PATH,
                    description="아이디 입니다.",
                    required=True,
                ),
                OpenApiParameter(
                    name="text_param",
                    type=str,
                    description="text_param 입니다.",
                    required=False,
                ),
                OpenApiParameter(
                    name="select_param",
                    type=str,
                    description="first_param 입니다.",
    								#enum : 받을 수 있는 값을 제한함
                    enum=['선택1', '선택2', '선택3'], 
                    examples=[
                        OpenApiExample(
                            name="이것은 Select Parameter Example입니다.",
                            summary="요약입니다",
                            description="설명글은 길게 작성합니다",
                            value="선택1",
                        ),
                        OpenApiExample(
                            "이것은 Select Parameter Example2입니다.",
                            summary="두번째 요약입니다",
                            description="두번째 설명글은 더 길게 작성합니다",
                            value="선택4",
                        ),
                    ],
                ),
                OpenApiParameter(
                    name="date_param",
                    type=OpenApiTypes.DATE,
                    location=OpenApiParameter.QUERY,
                    description="date filter",
                    examples=[
                        OpenApiExample(
                            name="이것은 Query Parameter Example입니다.",
                            summary="요약입니다",
                            description="설명글은 길게 작성합니다",
                            value="1991-03-02",
                        ),
                        OpenApiExample(
                            name="이것은 Query Parameter Example2입니다.",
                            summary="두번째 요약입니다",
                            description="두번째 설명글은 더 길게 작성합니다",
                            value="1993-08-30",
                        ),
                    ],
                ),
            ],
        )
        @action(
            detail=False
        )
        def test(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
            return Response('')

  • @extend_schema_view 클래스 단위의 데코레이터, 하나의 ViewSet에 속한 method들의 문서화를 커스터마이징 할 때 사용
    우선순위는 @extend_schema 와 동시에 쓰인다면 @extend_schema가 우선순위를 가진다. view_name은 기본적으로 list,retrieve,create,update,delete 가 있고 , @action을 통해 만든 커스텀 메소드도 메소드 명으로 스키마를 커스텀 할 수 있다

    Example

    @extend_schema_view(
    		#사용법 method_name = extend_schema()
        list=extend_schema(
    				tags=['extend_schema_view'], 
    				description='extend_schema_view로 꾸미기'
    		)
    )
    class UserViewSet(viewsets.ModelViewSet):
    		...code...
  • @extend_schema_serializer serializer 자체의 스키마 커스텀을 원한다면 사용할 수 있다.
    적용 우선순위는 method > viewset > serializer 이다.

    Parameters

    def extend_schema_serializer(
            many: Optional[bool] = None,
            exclude_fields: Optional[List[str]] = None,
            deprecate_fields: Optional[List[str]] = None,
            examples: Optional[List[OpenApiExample]] = None,
            component_name: Optional[str] = None,
    ) -> Callable[[F], F]:
    
    # many : ???
    # exclude_fields : Serializer에 정의된 필드중 schema상 제외할 필드
    # deprecate_fields : Serializer에 정의된 필드중 schema상 deprecate 표시할 필드
    # examples : request/response에 대한 예시, extend_schema에 사용하던 방식과 동일
    # component_name : 스키마의 이름 ,default는 Serializer에 정의된 model의 이름

    Example

    @extend_schema_serializer(
        exclude_fields=["password"],
        examples=[
            OpenApiExample(
                "Valid example 1",
                summary="short summary",
                description="longer description",
                value={
                    "is_superuser": True,
                    "username": "string",
                    "first_name": "string",
                    "last_name": "string",
                    "email": "user@example.com",
                    "is_staff": False,
                    "is_active": True,
                    "date_joined": "2021-04-18 04:14:30",
                    "user_type": "customer",
                },
                request_only=True,   # view Layer에서 사용한 Example Object와 동일한 동작을 한다.
                response_only=False,
            ),
        ],
    )
    class UserCustomSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = User
            fields = '__all__'

  • @extend_schema_field SerializerMethodField를 사용하는 경우에 해당 Field의 메타정보를 알 수 없다.
    그때 해당 데코레이터를 사용해서 스키마에 사용될 필드의 타입힌트를 제공할 수 있다.

    Parameters

    field, component_name을 파라메터로 받지만, 보통 serializer 에서 필드를 분리해서 관리하진 않으니 field인자만 사용하면 될 것 같다.
    def extend_schema_field(
            field: Union[_SerializerType, Field, OpenApiTypes, Dict],
            component_name: Optional[str] = None
    ) -> Callable[[F], F]:
    
    # field : 필드의 형태, 타입
    # component_name : 스키마의 이름 ,default는 Serializer에 정의된 model의 이름

    Example

    class CustomUserSerializer(serializers.HyperlinkedModelSerializer):
        field_custom = serializers.SerializerMethodField(method_name="get_field_custom")
    
        class Meta:
            model = User
            fields = '__all__'
    
        @extend_schema_field(OpenApiTypes.DATETIME)
        def get_field_custom(self, data):
            return '2021-03-06 20:54:00'

참조

drf-spectaculr 공식 문서

Django Rest Framework API Document Generator (feat. drf-spectacular) -김성렬님

profile
고양이를 좋아하는 개발자 성장기 (Java, Python) 🐈...❗️
post-custom-banner

2개의 댓글

comment-user-thumbnail
2022년 3월 18일

urls.py 에서 path('schema/swagger-ui/', SpectacularSwaggerView.as_view(**url_name='api:schema'**), name='swagger-ui'), 이렇게 쓰셧는데 url_name 인자 앞뒤로 ** 쓴 것은 파이썬 문법인가요 아니면 강조를 하기 위해 임의로 사용하신 표현인가요??

답글 달기
comment-user-thumbnail
2024년 5월 27일

감사합니다.

답글 달기