[DRF] Schemas

강민성·2024년 9월 29일

DRF API Guide

목록 보기
22/28

스키마 (Schema)

머신이 읽을 수 있는 [스키마]는 API를 통해 제공되는 리소스가 무엇인지, 해당 리소스의 URL이 무엇인지, 어떻게 표현되는지, 그리고 어떤 작업을 지원하는지 설명합니다.
— Heroku, Heroku 플랫폼 API의 JSON 스키마

사용 중지 공지:

REST 프레임워크에서 제공하는 OpenAPI 스키마 생성 지원은 이제 3rd-party 패키지를 사용하도록 권장되며, 이는 향후 릴리즈에서 기본 지원 기능이 별도의 패키지로 분리된 후 더 이상 제공되지 않을 예정입니다.

완전한 대체 패키지로, drf-spectacular 패키지를 추천합니다. 이 패키지는 자동 및 사용자 정의 옵션을 통해 REST 프레임워크 API에서 OpenAPI 3 스키마를 생성하는 폭넓은 지원을 제공합니다. 자세한 정보는 API 문서화를 참조하세요.

API 스키마는 참조 문서를 생성하거나, API와 상호 작용할 수 있는 동적 클라이언트 라이브러리를 생성하는 등 다양한 용도로 사용할 수 있는 유용한 도구입니다.

Django REST 프레임워크는 OpenAPI 스키마의 자동 생성을 지원합니다.

개요

스키마 생성은 여러 구성 요소로 이루어져 있습니다. 전체적인 개요를 살펴보는 것이 좋습니다.

  • SchemaGenerator: 최상위 클래스이며, 설정된 URL 패턴을 순회하며 APIView 하위 클래스를 찾아 스키마 표현을 요청하고, 최종 스키마 객체를 컴파일하는 역할을 합니다.
  • AutoSchema: 각 뷰의 스키마 탐색에 필요한 모든 세부 정보를 캡슐화합니다. schema 속성을 통해 각 뷰에 연결됩니다. 스키마를 사용자 정의하려면 AutoSchema를 하위 클래스로 만들면 됩니다.
  • generateschema 관리 명령어를 통해 정적 스키마를 오프라인에서 생성할 수 있습니다.
  • 또는, SchemaView를 라우팅하여 스키마를 동적으로 생성하고 제공할 수 있습니다.
  • settings.DEFAULT_SCHEMA_CLASS는 프로젝트의 기본 스키마로 사용할 AutoSchema 하위 클래스를 지정할 수 있습니다.

아래에 보다 자세한 설명이 있습니다.

OpenAPI 스키마 생성

의존성 설치

pip install pyyaml uritemplate inflection
  • pyyaml: YAML 기반의 OpenAPI 형식으로 스키마를 생성하는 데 사용됩니다.
  • uritemplate: 경로의 매개변수를 내부적으로 얻는 데 사용됩니다.
  • inflection: 목록 엔드포인트에서 작업을 보다 적절하게 복수형으로 만드는 데 사용됩니다.

generateschema 관리 명령어로 정적 스키마 생성

스키마가 정적이면 generateschema 관리 명령어를 사용할 수 있습니다:

./manage.py generateschema --file openapi-schema.yml

이렇게 생성된 스키마는 자동으로 추론할 수 없는 추가 정보를 주석으로 달 수 있습니다.

스키마를 버전 관리에 포함하고 각 새로운 릴리스마다 업데이트하거나, 사이트의 정적 미디어에서 API 스키마를 제공할 수 있습니다.

SchemaView로 동적 스키마 생성

외래 키 선택지가 데이터베이스 값에 따라 달라지는 등의 이유로 동적 스키마가 필요하다면, 스키마를 동적으로 생성하고 제공하는 SchemaView를 라우팅할 수 있습니다.

SchemaView를 라우팅하려면 get_schema_view() 헬퍼를 사용하십시오.

urls.py 예시:

from rest_framework.schemas import get_schema_view

urlpatterns = [
    # ...
    # `get_schema_view()` 헬퍼를 사용하여 프로젝트 URL에 `SchemaView` 추가.
    #   * `title` 및 `description` 매개변수는 `SchemaGenerator`에 전달됨.
    #   * `reverse()`에서 사용할 뷰 이름을 제공함.
    path(
        "openapi",
        get_schema_view(
            title="Your Project", description="API for all things …", version="1.0.0"
        ),
        name="openapi-schema",
    ),
    # ...
]

get_schema_view() 함수

get_schema_view() 헬퍼는 다음의 키워드 인수를 받습니다:

  • title: 스키마 정의에 대한 설명 제목을 제공하는 데 사용됩니다.
  • description: 더 긴 설명 텍스트를 제공합니다.
  • version: API의 버전을 지정합니다.
  • url: 스키마의 표준 기본 URL을 전달하는 데 사용될 수 있습니다.
schema_view = get_schema_view(
    title='Server Monitoring API',
    url='https://www.example.org/api/'
)
  • urlconf: API 스키마를 생성할 URL 설정의 가져오기 경로 문자열입니다. 기본값은 Django의 ROOT_URLCONF 설정 값입니다.
schema_view = get_schema_view(
    title='Server Monitoring API',
    url='https://www.example.org/api/',
    urlconf='myproject.urls'
)
  • patterns: 스키마 탐색을 제한할 URL 패턴 목록입니다. 예를 들어, myproject.api URL만 스키마에 노출하려면:
schema_url_patterns = [
    path('api/', include('myproject.api.urls')),
]

schema_view = get_schema_view(
    title='Server Monitoring API',
    url='https://www.example.org/api/',
    patterns=schema_url_patterns,
)
  • public: 스키마가 뷰 권한을 우회해야 하는지 여부를 지정하는 데 사용됩니다. 기본값은 False입니다.
  • generator_class: SchemaView에 전달할 SchemaGenerator 하위 클래스를 지정하는 데 사용됩니다.
  • authentication_classes: 스키마 엔드포인트에 적용할 인증 클래스 목록을 지정하는 데 사용됩니다. 기본값은 settings.DEFAULT_AUTHENTICATION_CLASSES입니다.
  • permission_classes: 스키마 엔드포인트에 적용할 권한 클래스 목록을 지정하는 데 사용됩니다. 기본값은 settings.DEFAULT_PERMISSION_CLASSES입니다.
  • renderer_classes: API 루트 엔드포인트를 렌더링하는 데 사용할 렌더러 클래스 집합을 전달하는 데 사용됩니다.

SchemaGenerator

스키마 수준의 커스터마이징

rest_framework.schemas.openapi 모듈의 SchemaGenerator는 라우팅된 URL 패턴 목록을 순회하며 각 뷰에 대한 스키마를 요청하고, 그 결과를 OpenAPI 스키마로 모읍니다.

일반적으로 SchemaGenerator를 직접 인스턴스화할 필요는 없지만, 다음과 같이 할 수 있습니다:

generator = SchemaGenerator(title='Stock Prices API')

인자들:

  • title (필수): API의 이름입니다.
  • description: 좀 더 긴 설명 텍스트입니다.
  • version: API의 버전입니다. 기본값은 0.1.0입니다.
  • url: API 스키마의 루트 URL입니다. 스키마가 경로 접두사 하에 포함되지 않는 한, 이 옵션은 필수 사항이 아닙니다.
  • patterns: 스키마를 생성할 때 검사할 URL 목록입니다. 기본값은 프로젝트의 URL 설정입니다.
  • urlconf: 스키마를 생성할 때 사용할 URL 설정 모듈 이름입니다. 기본값은 settings.ROOT_URLCONF입니다.

상위 레벨의 스키마를 커스터마이징하기 위해 rest_framework.schemas.openapi.SchemaGenerator를 서브클래싱하고, 이 서브클래스를 generateschema 명령어 또는 get_schema_view() 헬퍼 함수의 인자로 제공합니다.

get_schema(self, request=None, public=False)

OpenAPI 스키마를 나타내는 딕셔너리를 반환합니다:

generator = SchemaGenerator(title='Stock Prices API')
schema = generator.get_schema()

request 인자는 선택 사항이며, 스키마 생성 시 사용자별 권한을 적용하고자 할 때 사용할 수 있습니다.

생성된 딕셔너리를 커스터마이징하려면 이 메소드를 재정의하는 것이 좋습니다. 예를 들어, 상위 정보 객체에 서비스 약관을 추가하고자 할 때 다음과 같이 할 수 있습니다:

class TOSSchemaGenerator(SchemaGenerator):
    def get_schema(self, *args, **kwargs):
        schema = super().get_schema(*args, **kwargs)
        schema["info"]["termsOfService"] = "https://example.com/tos.html"
        return schema

AutoSchema

뷰 별 커스터마이징

rest_framework.schemas.openapi 모듈의 AutoSchema는 기본적으로 APIViewschema 속성을 통해 접근 가능한 인스턴스를 사용하여 뷰에 대한 introspection(내부 구조 확인)을 수행합니다.

auto_schema = some_view.schema

AutoSchema는 각 뷰, 요청 메소드 및 경로에 필요한 OpenAPI 요소를 제공합니다:

  • OpenAPI 컴포넌트 목록. DRF 용어로는 요청 및 응답 본문을 설명하는 직렬화기(serializer)의 매핑입니다.
  • 페이징, 필터링 등에 대한 경로 및 쿼리 파라미터를 포함하여 엔드포인트를 설명하는 적절한 OpenAPI 작업 객체(operation object).
components = auto_schema.get_components(...)
operation = auto_schema.get_operation(...)

스키마를 컴파일할 때, SchemaGenerator는 각 뷰, 허용된 메소드 및 경로에 대해 get_components()get_operation()을 호출합니다.

참고: 컴포넌트의 자동 introspection과 많은 작업 매개변수는 GenericAPIView의 관련 속성 및 메소드(get_serializer(), pagination_class, filter_backends 등)에 의존합니다. 따라서 기본 APIView 서브클래스의 경우, 기본 introspection은 사실상 URL의 경로 파라미터로 제한됩니다.

AutoSchema는 스키마 생성을 위한 뷰 introspection을 캡슐화합니다. 이를 통해 스키마 생성 로직을 단일 위치에 유지할 수 있으며, 이는 이미 방대한 뷰, 직렬화기 및 필드 API를 더욱 분산시키지 않도록 합니다.

이 패턴을 유지하면서 스키마 로직이 뷰, 직렬화기 또는 필드로 누출되지 않도록 하는 것이 좋습니다. 예를 들어 다음과 같은 코드를 작성할 수 있습니다:

class CustomSchema(AutoSchema):
    """
    뷰에서 schema_extra_info를 사용하는 AutoSchema 서브클래스입니다.
    """
    ...

class CustomView(APIView):
    schema = CustomSchema()
    schema_extra_info = ...  # 추가 정보

이 경우, AutoSchema 서브클래스가 뷰에서 schema_extra_info를 찾습니다. 이는 문제가 되지는 않지만 스키마 로직이 여러 곳에 분산되게 됩니다.

대신, 다음과 같이 AutoSchema를 서브클래스화하여 추가 정보가 뷰로 누출되지 않도록 하는 것이 좋습니다:

class BaseSchema(AutoSchema):
    """
    추가 정보를 사용할 수 있는 AutoSchema 서브클래스입니다.
    """
    ...

class CustomSchema(BaseSchema):
    extra_info = ...  # 추가 정보

이 스타일은 조금 더 장황하지만, 스키마 관련 코드를 캡슐화하여 보다 응집력 있는 방식으로 유지할 수 있습니다. 결과적으로 나머지 API 코드도 더욱 깔끔해질 것입니다.

만약 여러 뷰 클래스에 적용되는 옵션이 있다면, 각 뷰마다 서브클래스를 생성하기보다는, 해당 옵션을 기본 AutoSchema 서브클래스의 __init__() 키워드 인자로 지정하는 것이 더 편리할 수 있습니다:

class CustomSchema(BaseSchema):
    def __init__(self, **kwargs):
        # 추가 정보를 나중에 사용할 수 있도록 저장합니다.
        self.extra_info = kwargs.pop("extra_info")
        super().__init__(**kwargs)
class CustomView(APIView):
    schema = CustomSchema(extra_info=...)  # 추가 정보

이 방법은 자주 사용되는 옵션에 대해 각 뷰마다 커스텀 서브클래스를 생성하지 않아도 되는 장점이 있습니다.

모든 AutoSchema 메소드가 관련된 __init__() 키워드를 노출하는 것은 아니지만, 자주 필요한 옵션에 대해서는 해당 키워드를 사용할 수 있습니다.


AutoSchema 메소드

get_components()

요청 및 응답 본문을 설명하는 OpenAPI 컴포넌트를 생성하며, 이들의 속성은 직렬화기에서 파생됩니다.

컴포넌트 이름을 생성된 표현에 매핑하는 딕셔너리를 반환합니다. 기본적으로 이 딕셔너리는 하나의 쌍만 포함하지만, 여러 직렬화기를 사용하는 뷰의 경우 get_components()를 재정의하여 여러 쌍을 반환할 수 있습니다.

get_component_name()

직렬화기에서 컴포넌트의 이름을 계산합니다.

API에 중복된 컴포넌트 이름이 있으면 경고가 발생할 수 있습니다. 이 경우 get_component_name()을 재정의하거나 component_name __init__() 키워드를 전달하여 다른 이름을 제공할 수 있습니다.

get_reference()

직렬화기 컴포넌트에 대한 참조를 반환합니다. 이 메소드는 get_schema()를 재정의할 때 유용할 수 있습니다.

map_serializer()

직렬화기를 OpenAPI 표현에 매핑합니다.

대부분의 직렬화기는 기본 OpenAPI 객체 타입에 맞아야 하지만, 특정 필드 수준 또는 직렬화기 수준에서 이를 커스터마이징하고자 할 때 map_serializer()를 재정의할 수 있습니다.

map_field()

개별 직렬화기 필드를 스키마 표현으로 매핑합니다. 기본 구현은 Django REST Framework에서 제공하는 기본 필드를 처리합니다.

SerializerMethodField 인스턴스나 스키마가 알려지지 않은 커스텀 필드 하위 클래스에 대해, 올바른 스키마를 생성하려면 map_field()를 재정의해야 합니다:

class CustomSchema(AutoSchema):
    """커스텀 필드 스키마를 추가로 지원하는 ``AutoSchema``의 확장입니다."""

    def map_field(self, field):
        # 여기에서 SerializerMethodFields 또는 커스텀 필드를 처리합니다.
        # ...
        return super().map_field(field)

서드파티 패키지 작성자는 AutoSchema 서브클래스와 map_field()를 재정의하는 믹스인을 제공하여 사용자가 커스텀 필드에 대한 스키마를 쉽게 생성할 수 있도록 해야 합니다.

get_tags()

OpenAPI는 작업을 태그로 그룹화합니다. 기본적으로 태그는 라우팅된 URL의 첫 번째 경로 세그먼트에서 가져옵니다. 예를 들어, /users/{id}/와 같은 URL은 users 태그를 생성합니다.

태그를 수동으로 지정하려면 __init__()에서 키워드 인자를 전달하거나, get_tags()를 재정의하여 커스텀 로직을 적용할 수 있습니다.


get_operation()

엔드포인트를 설명하는 OpenAPI 작업 객체를 반환하며, 여기에는 페이징, 필터링 등을 위한 경로 및 쿼리 파라미터가 포함됩니다.

이 메소드는 get_components()와 함께 뷰 introspection의 주요 진입점입니다.


get_operation_id()

모든 작업에는 고유한 operationId가 있어야 합니다. 기본적으로 operationId는 모델 이름, 직렬화기 이름 또는 뷰 이름에서 유추됩니다. operationId는 관례적으로 camelCase 형식을 따르며, 예를 들어 "listItems", "retrieveItem", "updateItem" 등과 같은 형식으로 표현됩니다.


get_operation_id_base()

같은 모델 이름을 가진 여러 뷰가 있을 경우, 중복된 operationId가 발생할 수 있습니다.

이 문제를 해결하려면 get_operation_id_base()를 재정의하여 ID의 이름 부분에 대한 다른 기준을 제공할 수 있습니다.


get_serializer()

만약 뷰에서 get_serializer()를 구현했다면, 이 메소드는 그 결과를 반환합니다.


get_request_serializer()

기본적으로는 get_serializer()를 반환하지만, 요청 객체와 응답 객체를 구분하기 위해 이 메소드를 재정의할 수 있습니다.


get_response_serializer()

기본적으로는 get_serializer()를 반환하지만, 요청 객체와 응답 객체를 구분하기 위해 이 메소드를 재정의할 수 있습니다.


AutoSchema __init__() 키워드 인자

AutoSchema는 기본으로 생성된 값이 적절하지 않을 때 자주 사용되는 커스터마이징을 위해 몇 가지 __init__() 키워드 인자를 제공합니다.

사용 가능한 인자들은 다음과 같습니다:

  • tags: 태그 목록을 지정합니다.
  • component_name: 컴포넌트 이름을 지정합니다.
  • operation_id_base: operationId의 리소스 이름 부분을 지정합니다.

이 키워드 인자들은 뷰에서 AutoSchema 인스턴스를 선언할 때 전달할 수 있습니다:

class PetDetailView(generics.RetrieveUpdateDestroyAPIView):
    schema = AutoSchema(
        tags=['Pets'],
        component_name='Pet',
        operation_id_base='Pet',
    )
    ...

Pet 모델과 PetSerializer 직렬화기를 사용하는 예시에서는, 위의 키워드 인자들이 필요하지 않을 수 있습니다. 그러나 종종 여러 뷰가 동일한 모델을 대상으로 하거나, 동일한 이름의 직렬화기를 사용하는 여러 뷰가 있는 경우, 이러한 키워드 인자를 전달해야 할 수 있습니다.

관련된 커스터마이징이 자주 필요한 경우, 각 뷰마다 AutoSchema를 서브클래싱하지 않도록 프로젝트에 맞춘 기본 AutoSchema 서브클래스를 생성하고, 추가 __init__() 키워드 인자를 받아 처리할 수 있습니다.

Reference

DRF API Guide - Schemas

profile
Back-end Junior Developer

0개의 댓글