django์ swagger ๋ง๋ค๊ธฐ, drf-spectacular
- ์นดํ ๊ณ ๋ฆฌ ์์
- 2024. 4. 4. 22:00
class KakaoAuthorize(PublicView):
@extend_schema(
parameters=[
OpenApiParameter(
name="code",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
description="Authorization code",
required=True,
)
],
responses={
status.HTTP_200_OK: LoginTokenSerializer(),
status.HTTP_400_BAD_REQUEST: BAD_REQUEST_RESPONSE
},
)
def get(self, request, *args, **kwargs):
๐API ์คํ ๊ณต์ ๋ฅผ ์ํ swagger
๊ฐ๋ฐ์๋ค์๊ฒ swagger๋ ์์ฃผ ์น์ํ ์ด๋ฆ์ด๋ค. ํ๋ก ํธ์๋์ ๋ฐฑ์๋ ๋ชจ๋ ์์ฃผ ํ์ฉํ๋ ๊ธฐ๋ฅ์ธ๋ฐ, API ์คํ์ ๋ํ ๋ช ์ธ๋ฅผ ํ์ธํ ์ ์๋ '๊ณต์ ๋ฌธ์'๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค. ์น ๋ธ๋ผ์ฐ์ ธ๋ฅผ ์ด์ฉํด GUIํํ๋ก ์ ๊ณต๋๊ธฐ ๋๋ฌธ์ ๋ณด๊ธฐ ํธํ๊ณ , ์ค์ ๋ก API๋ฅผ ํธ์ถํด ๋ณผ ์ ์๊ธฐ๋ ํ๋ค. (๋ณด์ ๋๋ฌธ์ ์ค์ ์ด์ ์๋ฒ์์๋ swagger๋ก ํธ์ถ์ ๋ง์๋๊ธฐ๋ ํ๋ค.)
์์ ์ ์ฅ๊ณ ํ๋ก์ ํธ๋ฅผ ์งํํ ๋๋ drf-yasg๋ฅผ ์ฌ์ฉํ์๋๋ฐ, ์ต๊ทผ์ drf-spectacular๋ผ๋๊ฒ ๋ ์ ๋๊ฐ๋ ํจํค์ง์ธ ๊ฒ ๊ฐ๋ค. ์ง์ํ๋ ๋ฒ์ ์ด ๋ ๋์๊ฐ ๋ณด๋ค. drf-spectacular๋ฅผ ์ฌ์ฉํ๋ฉด์ ์ด๊ฒ์ ๊ฒ ์ธํ ์ ์ข ํด๋ณด๋ฉด์ ์ฌ์ฉ๋ฒ์ ์ ๋ฆฌํด ๋ณธ๋ค.
โฌ๏ธ ์ค์นํ๊ธฐ
๋จผ์ ํจํค์ง๋ฅผ ์ค์นํด์ผํ๋ค.
pip install drf-spectacular
ํ์ฌ๊ธฐ์ค 0.27.1๋ฒ์ ์ด ์ต์ ๋ฒ์ ์ธ๋ฐ, ๊ณ์ํด์ ๋ฒ์ ์ ๋๊ณ ์๋ ๋ชจ์์ด๋ค. ๊นํ๋งํฌ : https://github.com/tfranzel/drf-spectacular
ํจํค์ง๋ฅผ ์ค์นํ๊ณ ๋ช๊ฐ์ง ์ธํ ์ด ํ์ํ๋ฐ, ์ผ๋จ settings.py์ ์๋์ ๊ฐ์ ์ฝ๋๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
# settings.py
INSTALLED_APPS = [
...
'drf_spectacular', # installed_app์ ์ถ๊ฐ
...
]
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
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 ๋ฌธ์์
๋๋ค.',
'SWAGGER_UI_SETTINGS': {
'dom_id': '#swagger-ui',
'layout': 'BaseLayout',
'deepLinking': True,
'displayOperationId': True,
'filter': True,
},
'LICENSE': {
'name': 'MIT License',
},
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'SWAGGER_UI_DIST': '//unpkg.com/swagger-ui-dist@3.38.0',
}
SPECTACULAR_SETTINGS๊ฐ ์ค์ ๋ก swaggerํ๋ฉด์ ์ด๊ฒ์ ๊ฒ ์ต์ ์ ๋ฃ๋ ์ค์ ์ธ๋ฐ, ์์ธํ ์ค์ ๊ฐ๋ค์ ์๊ธฐ๋ฅผ ๊ฐ๋ฉด ํ์ธํ ์ ์๋ค.
์ด์ ๋ค์์ผ๋ก urls.py๋ฅผ ์์ ํ๋ฉด ์ค์จ๊ฑฐ ํ๋ฉด์ ๋์ธ ์ ์๋ค.
# urls.py
from drf_spectacular.views import SpectacularJSONAPIView, SpectacularYAMLAPIView, SpectacularSwaggerView, \
SpectacularRedocView
urlpatterns = [
path("swagger.json/", SpectacularJSONAPIView.as_view(), name="schema-json"),
path("swagger.yaml/", SpectacularYAMLAPIView.as_view(), name="swagger-yaml"),
path("swagger/", SpectacularSwaggerView.as_view(url_name="schema-json"), name="swagger-ui", ),
path("redoc/", SpectacularRedocView.as_view(url_name="schema-json"), name="redoc", ),
...
]
์ผ๋ฐ์ ์ธ url path๋ ~/swagger ์ง๋ง json์ด๋ yamlํํ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ณ ์ถ์ ํด๋ผ์ด์ธํธ๊ฐ ์๊ธฐ ๋๋ฌธ์ swagger.json, swagger.yaml๋ ์ถ๊ฐํ๋ค.
์ด์ django ํ๋ก์ ํธ๋ฅผ ์คํํ๋ฉด swaggerํ๋ฉด์ด ๋จ๋๊ฒ์ ํ์ธํ ์ ์๋ค!!
๐ง๐ป ์ปค์คํฐ๋ง์ด์ง
์์ฒ๋ผ ์ค์น๋ง ํ๊ณ swagger๋ฅผ ๋์ฐ๋ฉด ๋จ์ํ API ๋ฆฌ์คํธ๋ง ๋์ค๊ณ ๋ค๋ฅธ ์ ๋ณด๋ค์ ํ๊ธฐ๋์ง ์๋๋ค. ์ด๋ฐ ๊ฒ๋ค๊น์ง ์๋์ผ๋ก ์ฐพ์์ ๋ณด์ฌ์ค๋ค๋ฉด ์์ฃผ ์ข๊ฒ ์ง๋ง, ๊ฐ API๋ง๋ค request๋ response type์ ์ผ์ผ์ด ์ง์ ํด ์ค์ผ ์ข ๋ swagger๋ฅผ ์ ํ์ฉํ ์ ์๋ค.
์๋๋ ์นด์นด์ค ์ธ์ฆ์ ํ๋ API์ ๋ํด parameter๋ฅผ ์ธํ ํ ์์์ด๋ค.
class KakaoAuthorize(PublicView):
@extend_schema(
parameters=[
OpenApiParameter(
name="code",
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
description="Authorization code",
required=True,
)
],
responses={
status.HTTP_200_OK: LoginTokenSerializer(),
status.HTTP_400_BAD_REQUEST: BAD_REQUEST_RESPONSE
},
)
def get(self, request, *args, **kwargs):
...
๊ธฐ๋ณธ์ ์ผ๋ก parameters, request, responses 3๊ฐ๋ง ์ ์ธํ ํ๋ฉด ๋๋ค. parameters๋ ํค๋๋ path์ ์ธํ ๋๋ ๊ฐ, request๋ body์ ์ธํ ๋๋ ๊ฐ, response๋ ์๋ต๊ฐ์ด๋ค.
parameters๊ฐ์ ๊ฒฝ์ฐ OpenApiParameter๋ฅผ ์ฌ์ฉํด ํ๊ธฐํ ์ ์๊ณ , request์ response๋ serializer๋ก ํํํ ์ ์๋๋ฐ, serializer๋ก ํํํ๋ฉด ์ด๋ค ํ์ ์ field์ธ์ง๋ง ๋ณด์ฌ์ค ์ ์๋ค. drf-spectacular์์ ์ ๊ณตํ๋ OpenApiRequest์ OpenApiResponse๋ฅผ ํ์ฉํ๋ฉด serializer์ ๋ณด + ์์ ์์ฒญ/์๋ต๊ณผ ๊ฐ์ ์ถ๊ฐ์ ์ธ ์ ๋ณด๋ฅผ ์ธํ ํ ์ ์๋ค.
์์ responses์์ 200์ธ๊ฒฝ์ฐ๋ LoginTokenSerializer๋ผ๋ serializer์ ๋ณด๋ฅผ ๋ด๋ ค์ฃผ๊ณ , 400 ์๋ต์ผ ๊ฒฝ์ฐ BAD_REQUEST_RESPONSE๋ผ๋ OpenApiResponse ํด๋์ค๋ฅผ ๋ด๋ ค์ฃผ๋๋ฐ, ๊ฐ ํด๋์ค ๊ตฌํ์ ์๋์ ๊ฐ๋ค.
class LoginTokenSerializer(serializers.ModelSerializer):
class Meta:
model = LoginToken
depth = 1
fields = ['access_token', 'refresh_token', 'expires_in', 'refresh_token_expires_in', 'secret_key']
class BadRequestResponse(OpenApiResponse):
def __init__(self):
super().__init__(
inline_serializer(
name="BadRequest",
fields={
"error_code": serializers.CharField(),
"message": serializers.CharField(),
"display_type": serializers.CharField(),
},
allow_null=True,
),
description="Bad request",
examples=[
OpenApiExample(
name="Bad JSON passed in",
value={
"error_code": "notification",
"message": "",
"display_type": "event_type and event_parameters fields are required",
},
status_codes=["400"],
),
OpenApiExample(
name="Not good JSON",
value={
"detail": "JSON parse error - Expecting ':' delimiter: line 2 column 17 (char 18)"
},
status_codes=["400"],
),
],
)
์ ์ฝ๋๊ฐ swagger์์ ํํ๋๋ ํ๋ฉด์ ์๋์ ๊ฐ๋ค.
200์ด serializer, 400์ด OpenApiResponse๋ฅผ ์ฌ์ฉํ ํญ๋ชฉ์ด๋ค. serializer์ ๊ฒฝ์ฐ ๊ฐ field์ type์ด ์ ํ์๊ธด ํ์ง๋ง, ๊ตฌ์ฒด์ ์ผ๋ก ์ด๋ค ๊ฐ์ด ๋์ค๋์ง๋ ํ์ธํ ์ ์๋ค. (์ฌ์ค ์ด ์ ๋๋ง ํด๋...) 400์ ๊ฒฝ์ฐ๋ ์ค์ ๋ก field์ ์ด๋ค ๊ฐ์ด ๋ค์ด๊ฐ์ง ์ธํ ํ ์ ์์ด์ ๊ฐ๋ฐํ ๋ ๋ ํจ๊ณผ์ ์ด๋ค.
์ ๋ง ์๋ฒฝํ๊ฒ swagger๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ OpenApiResponse๋ฅผ ๊ฐ API๋ง๋ค ๋ง๋ค์ด์ฃผ๋๊ฒ ์ข๊ฒ ์ง๋ง, ๋์ถฉ ์ด๋ type์ธ์ง๋ง ์์๋ ๊ฐ๋ฐ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ๊ณ ๋ฏผํด ๋ณผ ํ์๊ฐ ์๋ค. ๊ฐ๋ฐ ํ์ ์ปค๋ฎค๋์ผ์ด์ ๋น๋๊ฐ ๋๋ค๋ฉด ๊ตณ์ด swagger์ ๋ชจ๋ ์ ๋ณด๋ฅผ ๋ ธ์ถํ๋ ๊ฒ๋ณด๋ค๋, ๋ฑ ํ์์ ๋ณด๋ง ๋ณด์ฌ์ฃผ๊ณ ๋ค๋ฅธ ๊ฑด ๋ค๋ฅธ ์์ฌ์ํต ์ฑ๋์ ํตํด ์งํํ๋ ๊ฒ ๋ ์ข์ ์ ์๋ค. ๋ฐ๋๋ก ํ์ Cost๊ฐ ๋๋ค๊ณ ์๊ฐํ๋ค๋ฉด (๋ฌผ๋ฆฌ์ ์ธ ์์น๊ฐ ๋ฉ์ด์ ?) ์๋ฒฝํ Swagger๋ฌธ์๋ฅผ ๊ณต์ ํ๋ ๊ฒ์ด ๋ ๋ฐ๋์งํ ์ ์๋ค. ์ง๋ฆฌ์ ์ผ๋ฐ์ผ
๐ ๋ง์น๋ฉฐ
์ด์ ์ ์ฌ์ฉํ๋ drf-yasg์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์๋ ํฐ ์ฐจ์ด๊ฐ ์๋ค. ํ์ง๋ง OpenApi 3.0 ์คํ์ ์ ๊ณตํ๋ ๊ฒ์ drf-spectacular๋ง ์ ๊ณตํ๋ฉฐ, drf-yasg๋ฌธ์์๋ drf-spectacular๋ฅผ ์ฌ์ฉํ๋ผ๊ณ ์ ํ์๋ค ใ ใ
swagger๋ฅผ ์ฌ์ฉํ๋ ์ด์ ๋ ๊ฐ๋ฐ ํธ์์ด๊ธฐ ๋๋ฌธ์, swagger๋ฅผ ์ฌ์ฉํ๋ฉด์ ๊ฐ๋ฐ์ผ์ ์ด ๊ธธ์ด์ง๋ค๊ฑฐ๋ ์์ ์ด ์ด๋ ค์ด ๊ฒฝ์ฐ ์ฌ์ฉํ์ง ์๋ ๊ฒ ๋ฐ๋์งํ๋ค. ์ผ์ ์ํ ์ผ์ ํ์ง ์๋ ๊ฒ์ด ์ ์ผ ์ข๋ค๊ณ ์๊ฐํ๋ค.
ํ์ง๋ง ํน์ ํด๋ผ์ด์ธํธ๋ค์ swagger๋ฅผ ๋ณด๊ณ ์๋์ผ๋ก API ํธ์ถ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ธฐ๋ ํ๋ค. ๋ฐ๋ผ์ ๊ผญ ํ์ํ๊ฑฐ๋, ๊ฐ๋ฐ ์์ฐ์ฑ์ด ๋์์ง๋ค๋ฉด ๋ฌด์กฐ๊ฑด ์ฌ์ฉํ๋๊ฒ ์ด๋์ด๋ผ๊ณ ์๊ฐํ๋ค.