OAS는 프로그램의 내부는 다양한 언어로 개발되어있을 지라도, API문서 형태는 동일한 규격으로 나타냄으로 언어에 상관없이 문서만 보고 각 인터페이스의 역할과 구조를 알 수 있게해주는 통일된 API 문서 규격
OAS3.0 (Open Api Spec 3.0)은 2.0보다 재사용성, 유연성이 향상된 구조로 바뀌었다.
drf(django rest framwork)를 통해서 설계된 API를 OAS3.0 에 맞게 문서화를 도와주는 라이브러리
$ pip install drf-spectacular # pip을 사용하면 이렇게 설치
$ poetry add drf-spectacular # poetry를 사용하면 이렇게 설치
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'drf_spectacular',
# ...
]
REST_FRAMEWORK = {
# YOUR SETTINGS drf의 schema 클래스를 drf-specacular의 AutoSchema로 교체해줍니다.
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
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 발생!
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 버전을 조절할수 있습니다.
}
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에 담았습니다.
추가로 더 궁금하신 점이 있다면, 공식문서를 참고해주세요.
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(
#사용법 method_name = extend_schema()
list=extend_schema(
tags=['extend_schema_view'],
description='extend_schema_view로 꾸미기'
)
)
class UserViewSet(viewsets.ModelViewSet):
...code...
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의 이름
@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__'
def extend_schema_field(
field: Union[_SerializerType, Field, OpenApiTypes, Dict],
component_name: Optional[str] = None
) -> Callable[[F], F]:
# field : 필드의 형태, 타입
# component_name : 스키마의 이름 ,default는 Serializer에 정의된 model의 이름
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'
Django Rest Framework API Document Generator (feat. drf-spectacular) -김성렬님
urls.py 에서
path('schema/swagger-ui/', SpectacularSwaggerView.as_view(**url_name='api:schema'**), name='swagger-ui'),
이렇게 쓰셧는데 url_name 인자 앞뒤로 ** 쓴 것은 파이썬 문법인가요 아니면 강조를 하기 위해 임의로 사용하신 표현인가요??