More Related Content Similar to API 자동화 문서 도입기.pdf (20) API 자동화 문서 도입기.pdf1. API 자동화문서도입기 1
API 자동화문서도입기
Tech Project 📄 API 자동화문서
상태 Not started
완료시간 0
태그
라떼는말이야….
API 명세를하나하나손으로썼다고…
요즘젊은것들은…..
라떼는말이야….
요즘젊은것들은…..
어떻게구현했냐구요?
OpenAPI란?
Swagger란?
번외) JSON보다YAML 형식이많이이용되는이유
drf에서의API 자동화문서tool
drf-spectacular를우리프로젝트에적용하기위해서는
문제를해결하기위해서는.. 작동원리를알아야한다
말은쉽지…코드로뜯어봅시다
Reference
2. API 자동화문서도입기 2
•⭐[Try it Out]을통해직접api를실행해볼수있습니다!
어떻게구현했냐구요?
drf-spectacular를이용했습니다
OpenAPI란?
우리가아는이Open API가아닙니다!
OpenApI Specification(OAS)를OpenAPI라고부름
RESTful API를정의된규칙에맞게API Spec를json이나yaml으로표현하는방식
예전2.0 버전까지는Swagger 2.0와같이불렸다가Swagger가OpenAPI Initiative로이관되면서, 현재3.0버전부터는OpenAPI
3.0 Specification으로칭함(여전히혼용되고있음)
Swagger란?
크게아래세가지의toolset
Swagger Codegen: 브라우저기반의편집기, OpenAPI Spec을쉽게작성할수있게도와줌
Swagger Editor: OpenAPI spec에맞게생성된Spec를수정할수있음
⭐Swagger UI : OpenAPI JSON/YAML을API 문서로렌더링
3. API 자동화문서도입기 3
번외) JSON보다YAML 형식이많이이용되는이유
가독성
YAML은들여쓰기와줄바꿈이가능
YAML은주석을지원하기때문에문서에설명이나메모를추가하기가용이
#JSON 형식
{
"openapi": "3.0.0",
"info": {
"title": "예시 API",
"version": "1.0.0"
},
"paths": {
"/users": {
"get": {
"summary": "모든 사용자 조회",
"responses": {
"200": {
"description": "성공",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}
}ㅇ
# YAML 형식
openapi: 3.0.0
info:
title: 예시 API
version: 1.0.0
paths:
/users:
get:
summary: 모든 사용자 조회
responses:
200:
description: 성공
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: integer
name:
type: string
4. API 자동화문서도입기 4
drf에서의API 자동화문서tool
drf native
drf-yasg
drf-yasg — drf-yasg 1.21.6 documentation
https://drf-yasg.readthedocs.io/en/stable/
drf-spectcular
drf-spectacular — drf-spectacular documentation
https://drf-spectacular.readthedocs.io/en/latest/
drf-spectcular를선택한이유
drf-native : swagger-ui가아니어서가독성이떨어지며테스트불가. 기능상저하로거의아무도사용X
drf-yasg vs drf-spectcular
drf-yasg : OpenAPI 3.0 지원X
drf-spectcular : OpenAPI 3.0 지원O
5. API 자동화문서도입기 5
OpenAPI 3.0은?
멀티url 가능
⭐components를통해중복되는부분을최소화함
paths:
/users/{userId}:
get:
summary: Get a user by ID
parameters:
...
responses:
'200':
description: A single user.
content:
application/json:
schema:
type: object
properties:
id:
type: integer
name:
type: string
/users:
get:
summary: Get all users
responses:
'200':
description: A list of users.
content:
application/json:
schema:
type: array
paths:
/users/{userId}:
get:
summary: Get a user by ID
parameters:
...
responses:
'200':
description: A single user.
content:
application/json:
schema:
$ref: '#/components/schemas/User'
/users:
get:
summary: Get all users
responses:
'200':
description: A list of users.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
6. API 자동화문서도입기 6
items:
type: object
properties:
id:
type: integer
name:
type: string
type: object
properties:
id:
type: integer
name:
type: string
drf-spectacular를우리프로젝트에적용하기위해서는
공식문서를보고그대로하면될줄알았지…만…역시나…
- drf-spectacular에서는request 및response를serializer의schema를따라서자동으로설정해준다
우리는response를serializer로처리하지않음. output_dto를이용한다
- drf 및drf-spectacular에서는multiple serializer를지원하지않는다.
우리는output_dto를이용해한응답값에여러개의serializer data를넣고있다.
문제를해결하기위해서는.. 작동원리를알아야한다
url 획득
urlpatterns으로부터endpoint(url → view mapping)을획득
view class 분석← 이친구에서serialzier 대신DTO도찾을수있게해주고
endpoints를iterate하면서각url, method에대해서request, response를찾기
스키마생성← 이친구에서DTO의스키마를생성할수있도록해보자
위에서수집한정보를바탕으로swagger 스키마를생성함
문서화
생성된swagger 스키마를기반으로API 문서화수행
7. API 자동화문서도입기 7
말은쉽지…코드로뜯어봅시다
view class 분석← 이친구에서serialzier 대신DTO도찾을수있게해주고
어떻게알았냐면…며칠동안break point찍어가면서찾아봄…
drf_spectacular/openapi.py
class AutoSchema(ViewInspector):
...
def get_operation(self, path, path_regex, path_prefix, method, registry: ComponentRegistry): """ override this for custom
...
operation['responses'] = self._get_response_bodies()
...
def _get_response_bodies(self, direction='response'):
response_serializers = self.get_response_serializers()
...
def _get_request_body(self, direction='request'):
...
custom하기
REST_FRAMEWORK = {
...
'DEFAULT_SCHEMA_CLASS': 'shipdan_application.settings.base_schema.DTOSchema',
}
settings/base_schema.py
class DTOSchema(AutoSchema):
def _get_response_bodies(self, direction='response'):
response_serializers = self.get_response_serializers()
if hasattr(response_serializers, 'dto_name'):
response_serializers: BaseDTO
component = self.resolve_dto(response_serializers)
response = {200: {'content': {'application/json': {'schema': component.ref}}}}
return response
else:
response = super()._get_response_bodies(direction)
return response
def _get_request_body(self, direction='request'):
if self.method not in ('PUT', 'PATCH', 'POST'):
return None
request_serializers = self.get_request_serializer()
if hasattr(request_serializers, 'dto_name'):
request_serializers: BaseDTO
component = self.resolve_dto(request_serializers)
request = {'content': {'application/json': {'schema': component.ref}}}
return request
else:
request = super()._get_request_body(direction)
return request
def resolve_dto(self, dto,) -> ResolvedComponent:
component = ResolvedComponent(
name=dto.dto_name,
type=ResolvedComponent.SCHEMA,
object=dto,
)
if component not in self.registry:
dto.schema_render(self)
return self.registry[component]
8. API 자동화문서도입기 8
dtos/base.py
class BaseDTO(metaclass=DTOMeta)
...
@classmethod
def schema_render(cls, auto_schema: Optional['DTOSchema'] = None):
responses = {}
component = ResolvedComponent(
name=cls.dto_name,
type=ResolvedComponent.SCHEMA,
object=cls,
)
if component in auto_schema.registry:
# 이거면 맨 처음이 아님
return auto_schema.registry[component].ref
for key, val in cls.dto_fields.items():
responses[key] = build_type(val, auto_schema)
component = ResolvedComponent(
name=cls.dto_name,
type=ResolvedComponent.SCHEMA,
object=cls,
schema={'type': 'object', 'properties': responses}
)
auto_schema.registry.register(component)
return auto_schema.registry[component].ref
dtos/base.py
def build_type(schema, auto_schema: Optional['DTOSchema'] = None):
openapi_type_mapping = get_openapi_type_mapping()
origin_schema = typing.get_origin(schema) # typing을 쓰고 있기 때문에
if hasattr(schema, 'dto_fields'):
val: BaseDTO
return schema.schema_render(auto_schema)
elif origin_schema is Union:
return build_union_type(schema, auto_schema)
elif origin_schema is list:
return build_array_type(schema, auto_schema)
elif origin_schema is dict:
return openapi_type_mapping[PYTHON_TYPE_MAPPING[schema.__origin__]]
elif type(schema) is ForwardRef:
return build_forward_value(schema, auto_schema)
else:
return openapi_type_mapping[PYTHON_TYPE_MAPPING[schema]]
def build_array_type(schema, auto_schema: Optional['DTOSchema'] = None):
schema = typing.get_args(schema)[0]
return {'type': 'array', 'items': build_type(schema, auto_schema)}
def build_union_type(schema, auto_schema: Optional['DTOSchema'] = None):
responses = {}
union_args: tuple = typing.get_args(schema)
if type(None) in union_args:
child_val = list(set(union_args) - {type(None)})[0]
responses['required'] = False
else:
child_val = union_args[0]
a = build_type(child_val, auto_schema)
return {**responses, **a}
def build_forward_value(schema: ForwardRef, auto_schema: Optional['DTOSchema'] = None):
try:
_schema = schema.__forward_value__
if _schema is None:
_schema = _DTODict[schema.__forward_arg__]
if _schema is None:
raise Exception()
return build_type(_schema, auto_schema)
except:
'''warning 아직 정의되지 않은 forward value입니다.'''
return {'type': schema.__forward_arg__}
Reference
9. API 자동화문서도입기 9
Django Rest Framework API Document Generator (feat. drf-spectacular)
drf-yasg의단점그리고Open API Specification 3.0
https://techblog.yogiyo.co.kr/django-rest-framework-api-document-generator-feat-drf-spectacular-585fca
bec404
https://swagger.io/blog/news/whats-new-in-openapi-3-0/