DRF에서 OpenAPI 3.0 규격에 맞춰 API 문서를 자동 생성해주는 라이브러리
pip install drf-spectacular
settings.py
INSTALLED_APPS = [
# ALL YOUR APPS
'drf_spectacular',
]
...
REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
SPECTACULAR_SETTINGS = {
"TITLE": "Core System API",
"DESCRIPTION": "Core System API Documents (240318)",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False,
# OTHER SETTINGS
}
urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
urlpatterns = [
# YOUR PATTERNS
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
# Optional UI:
path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]
Workflow & schema customization — drf-spectacular documentation
: 자동으로 데이터 스키마를 파악하여 문서화하기위해서 DRF에서 제공하는 Serializer 이용
각 View 클래스에 serializer_class
를 정의하면 해당 Serializer 클래스가 가진 스키마를 이용하여 자동으로 API별 문서를 제공해준다.
@extend_schema
데코레이션을 이용해서 뷰함수별로 스키마를 커스텀하여 정의할 수 있다.
inline_serailizer
를 이용하면 간단한 요청 및 응답 바디에 따로 Serialzer 클래스를 정의할 필요없이 필드를 명시할 수 있다.
OpenApiParameter
를 통해 각종 요청 파라미터를 명시할 수 있다.
(location=OpenApiParameter.PATH
와 같은 enum값을 통해 파라미터 종류별로 정의 가능)
응답값은 status code 별로 달리 정의할 수 있다.
OpenApiExample
를 이용하면 요청 및 응답 예시를 만들 수 있다.
(안해도 정의된 필드 기준으로 기본 예시 제공)
@extend_schema_view
, @extend_schema_serializer
데이코레이터를 사용하면 View 클래스, Serializer 클래스 단계에서 스키마 정의 가능하다. @extend_schema
가 정의되어 있다면 @extend_schema
가 우선된다.
(우선순위: @extend_schema
> @extend_schema_view
> @extend_schema_serializer
)
@extend_schema_field
를 이용하면 Serializer 클래스에 정의된 serializers.SerializerMethodField()
도 필드를 명시해서 문서에 포함시킬 수 있다.
사전 기능 정의
APIView를 이용한 Product 모델을 조회, 생성하는 기능
urls.py
# /main
urlpatterns = [
path("api/products/", include("product.urls")),
]
# /product
urlpatterns = [
path("", Product__View.as_view(), name="product_view"),
path("<int:id>", Product__DetailView.as_view(), name="product_detail_view"),
]
models.py
class Product(models.Model):
number = models.IntegerField(unique=True)
name = models.CharField(max_length=255)
price = models.FloatField()
stock = models.IntegerField()
class Category(models.Model):
name = models.CharField(max_length=255)
products = models.ManyToManyField(Product, related_name="categories")
views.py
class Product__DetailView(APIView):
def get(self, request, id):
product = Product.objects.get(id=id)
return Response(
Product__ResponseSerializer(product).data,
status=status.HTTP_200_OK,
)
class Product__View(APIView):
def post(self, request):
data = {
"number": 1,
"name": request.data.get("name"),
"price": request.data.get("price"),
"stock": 10,
}
product = Product.objects.create(**data)
return Response(
Product__ResponseSerializer(product).data,
status=status.HTTP_201_CREATED,
)
serializers.py
class Category__Serializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
class Product__ResponseSerializer(serializers.ModelSerializer):
category_list = serializers.SerializerMethodField()
categories = Category__Serializer(many=True)
def get_category_list(self, obj):
return list(obj.categories.values_list("id", flat=True))
class Meta:
model = Product
fields = "__all__"
drf-spectacular 적용
urls.py
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularRedocView,
SpectacularSwaggerView,
)
# /main
urlpatterns = [
path("api/products/", include("product.urls")),
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
# Optional UI:
path(
"api/schema/swagger-ui/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
path(
"api/schema/redoc/",
SpectacularRedocView.as_view(url_name="schema"),
name="redoc",
),
]
# /product
urlpatterns = [
path("", Product__View.as_view(), name="product_view"),
path("<int:id>", Product__DetailView.as_view(), name="product_detail_view"),
]
views.py
from drf_spectacular.utils import (
extend_schema,
OpenApiExample,
OpenApiParameter,
inline_serializer,
)
class Product__DetailView(APIView):
@extend_schema(
operation_id="get product detail",
tags=["Product"],
description="상품 상세조회",
parameters=[
OpenApiParameter(
name="id",
type=str,
required=True,
description="프로덕트 아이디",
location=OpenApiParameter.PATH,
),
],
responses={
200: Product__ResponseSerializer,
},
examples=[
OpenApiExample(
name="example",
response_only=True,
value={
"shop_id": "1",
"service_item_ids": [
1,
2,
3,
],
},
),
],
)
def get(self, request, id):
product = Product.objects.get(id=id)
return Response(
Product__ResponseSerializer(product).data,
status=status.HTTP_200_OK,
)
class Product__View(APIView):
@extend_schema(
operation_id="create product",
tags=["Product"],
description="상품 생성",
request=inline_serializer(
name="custom",
fields={
"shop_id": serializers.CharField(
max_length=100,
help_text="쇼핑몰 아이디",
required=True,
),
"service_item_ids": serializers.ListField(
child=serializers.IntegerField(),
help_text="서비스 아이템 아이디",
required=True,
),
},
),
responses={
200: Product__ResponseSerializer,
},
examples=[
OpenApiExample(
name="example",
request_only=True,
value={
"shop_id": "1",
"service_item_ids": [
1,
2,
3,
],
},
),
],
)
def post(self, request):
data = {
"number": 1,
"name": request.data.get("name"),
"price": request.data.get("price"),
"stock": 10,
}
product = Product.objects.create(**data)
return Response(
Product__ResponseSerializer(product).data,
status=status.HTTP_201_CREATED,
)
serializers.py
from drf_spectacular.utils import (
extend_schema_serializer,
OpenApiExample,
extend_schema_field,
)
class Category__Serializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
@extend_schema_serializer(
examples=[
OpenApiExample(
name="Example",
value={
"name": "노트북",
"price": "100",
},
)
]
)
class Product__ResponseSerializer(serializers.ModelSerializer):
category_list = serializers.SerializerMethodField()
categories = Category__Serializer(many=True)
@extend_schema_field(
serializers.ListField(
child=serializers.IntegerField(),
help_text="카테고리 아이디 리스트",
)
)
def get_category_list(self, obj):
return list(obj.categories.values_list("id", flat=True))
class Meta:
model = Product
fields = "__all__"
API 문서 확인
get product detail
create product
부가 설명
urls.py
에서 볼 수 있듯이 redoc외에 swagger-ui로도 확인할 수 있다.
redoc은 스키마 파악이 용이하고 swagger-ui로는 api를 excute할 수 있다.
serializers.py
에서 @extend_schema_serializer
를 이용해 예시를 만들었지만 @extend_schema
이 우선되어 예시가 적용되지 않는 걸 볼 수 있다.
@extend_schema
에서 정의한 예시는 실제 Serializer 클래스 스키마와 많이 다르더라도 정의한대로 문서화됨을 알 수 있다.
inline_serializer
, @extend_schema_field
등에서 필드를 정의할 때 serializers.Field
클래스를 이용했다. 공식문서에는 OpenApiTypes
또는 기본 파이썬 타입도 이용할 수 있다고 나와있다.
Workflow & schema customization — drf-spectacular documentation
get product detail
과 create product
api는 각각 다른 View 클래스로 정의되었지만 같은 tags=["product"]
를 명시하여 문서화에는 같은 태크로 묶여있다.
create product
api 문서의 응답 스키마를 보면 categories
필드로 정의된 Category__Serializer
클래스의 스키마도 표시됨을 볼 수 있다.
@extend_schema
에서는 decortion=
, serializers.Field()
에서는 help_text=
으로 설명을 덧붙일 수 있고 Serializer 클래스에서 따로 명시하지않는 필드들은 models.py
에서 정의한 필드의 verbose_name=
을 따라간다.