drf-yasgの個人的ベストプラクティス
drf-yasgについて
DjangoのRESTフレームワークAPIに関してドキュメント自動生成ツールが欲しかったので、色々調べたところ
This project is no longer being maintained. Please consider drf-yasg as an alternative/successor. I haven't personally used it, but it looks feature-complete and is actively maintained.
https://github.com/axnsan12/drf-yasg
だととのことです。
drf-yasgが推奨ということだったのでこちらを採用することにしました。
ここら辺の用語説明に関しては、GPTにお願いしてまとめてもらいました。
SwaggerとOpenAPIは、APIの設計とドキュメンテーションのための業界標準となっている仕様です。Swaggerは、オープンソースのAPI仕様ツールであり、OpenAPI Specification(以前はSwagger Specificationとして知られていました)を使用してAPIの形式を記述します。一方、OpenAPIはSwaggerの進化形であり、APIの記述やドキュメンテーションに関する包括的な仕様を提供しています。
Django Rest Framework(DRF)は、DjangoベースのWeb APIフレームワークであり、APIの作成や管理を容易にする多くのツールや機能を提供しています。DRFを使用することで、APIの作成と管理が簡単になり、リッチな機能やセキュリティを組み込むことができます。
そして、drf-yasgはDRFの拡張であり、Swagger UIとOpenAPI Specificationを使用して、DRFで構築されたAPIの視覚的なドキュメンテーションを提供します。drf-yasgを利用することで、開発者は簡単かつ効果的にAPIのエンドポイントやリクエスト・レスポンスの仕様を確認し、実際のリクエストを試すことができます。
この組み合わせにより、DRFでAPIを構築する開発者はSwagger UIを通じて直感的で見やすいドキュメンテーションを提供でき、同時にOpenAPI Specificationによって標準的なAPI仕様を満たすことができます。これにより、APIのクライアントや他の開発者がAPIを理解しやすくなり、相互運用性が向上します。
個人的ベストプラクティス
試行錯誤の結果以下に落ち着きました。
def map_drf_to_openapi_type(drf_type):
"""
Map Django REST framework types to OpenAPI types.
You may need to extend this mapping based on your specific use case.
Add more mappings as needed
"""
type_mapping = {
serializers.CharField: openapi.TYPE_STRING,
serializers.IntegerField: openapi.TYPE_INTEGER,
serializers.FloatField: openapi.TYPE_NUMBER,
serializers.BooleanField: openapi.TYPE_BOOLEAN,
serializers.DateField: openapi.TYPE_STRING,
serializers.DateTimeField: openapi.TYPE_STRING,
}
return type_mapping.get(drf_type, openapi.TYPE_STRING)
def generate_swagger_schema(serializer):
properties = {}
for field_name, field in serializer().fields.fields.items():
field_type = map_drf_to_openapi_type(type(field))
if isinstance(field, serializers.DateField):
properties[field_name] = openapi.Schema(type=field_type, format='date')
elif isinstance(field, serializers.DateTimeField):
properties[field_name] = openapi.Schema(type=field_type, format='date-time')
else:
properties[field_name] = openapi.Schema(type=field_type)
return properties
from generate_swagger_schema import generate_swagger_schema
class TESTView(APIView):
manual_parameters = [
openapi.Parameter(
"parameter1",
openapi.IN_QUERY,
description="パラメータ1",
type=openapi.TYPE_ARRAY,
items=openapi.Items(type=openapi.TYPE_STRING),
),
openapi.Parameter(
"parameter2",
openapi.IN_QUERY,
type=openapi.TYPE_STRING,
description="パラメータ2_enum",
enum=['type1', 'type2'],
),
]
# many = Trueのとき
success_schema = openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"status": openapi.Schema(type=openapi.TYPE_STRING, example="200"),
"message": openapi.Schema(type=openapi.TYPE_STRING, example="success"),
"official_blog_detail": openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties=generate_swagger_schema(OfficialBlogSerializer)
),
),
}
)
# many = falseのとき
success_schema = openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"status": openapi.Schema(type=openapi.TYPE_STRING, example="200"),
"message": openapi.Schema(type=openapi.TYPE_STRING, example="success"),
"official_blog_detail": openapi.Schema(
type=openapi.TYPE_OBJECT,
properties=generate_swagger_schema(OfficialBlogSerializer)
),
}
)
error_schema = openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"status": openapi.Schema(type=openapi.TYPE_STRING, example="400"),
"message": openapi.Schema(type=openapi.TYPE_STRING, example="error"),
"official_blog_detail": openapi.Schema(type=openapi.TYPE_STRING, example=""),
}
)
schema_response = {
status.HTTP_200_OK: openapi.Response(
description="成功時のレスポンス",
schema=success_schema,
),
status.HTTP_400_BAD_REQUEST: openapi.Response(
description="エラー時のレスポンス",
schema=error_schema,
),
}
@swagger_auto_schema(
manual_parameters=manual_parameters,
responses=schema_response,
)
def get(self, request, **kwargs):
"""
説明文
# Markdown
- 箇条書き
- の場合
"""
try:
context = {
"status": "200",
"message": "sucsess",
'my_info': RetrieveInfomationRelatedUser(request).get_mypage_info(),
}
except Exception:
context = {
"status": "400",
"message": "error",
"my_info": "",
}
return Response(context)
UI表示
解説
アーキテクチャとして、Djangoシリアライザを考えた時に、自分はシリアライザdomain/entityに属するべきだと考えています。
つまり、Viewに出てくるべきではないと思ってますし、DB周りの処理を持たせるべきではないと思ってます。
また、ViewにはControllerにresquestを丸投げして、終わりにしたいです。
となると、一般的によくある書き方が通用しません。
シリアライザにステータスコードとメッセージを書くとことも考えましたが、ちょっといけてないなぁっと思いました。
でも、シリアライザのフィールド情報は反映したい。
となって、
class TestView(APIView):
@swagger_auto_schema(
responses={
200: openapi.Response(
description="成功時のレスポンス",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"status": openapi.Schema(type=openapi.TYPE_STRING, example="200"),
"message": openapi.Schema(type=openapi.TYPE_STRING, example="success"),
"official_blog_detail": OfficialBlogSerializer
}
)
),
},
)
としたかったのですが、
TypeError: Object of type SerializerMetaclass is not JSON serializable
に苦しめられて、GPTと相談して行ったところ、マッピングするという結論になりました。
Discussion