Django 和 Django REST framework 是 Python 开发者常用的框架组合,通常来说,一个典型的 DRF 式 API 可能长这个样子:
from rest_framework.generics import ListAPIView
class ProfileViewSet(ListAPIView):
def login(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data
...
return Response(data=ProfileSerializer(results, many=True).data)
这样写在逻辑上是简单的,可以让开发者对用户请求处理有一个清晰的脉络,但同时也会带来问题:Serializer
的逻辑和主逻辑混杂,使单元测试构造困难。
同时,输入输出的代码在多个 API 中是有一定程度重复的, D.R.Y
重度患者无法接受。
新贵框架 FastAPI 的 依赖注入特性 就能够很好的解决以上两点:
from fastapi import Depends, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
然而,现实中的工程中切换框架往往是成本高昂的,仅仅为了依赖注入而切换框架显得有些小题大做。所以,如果能在 Django & DRF 中实现类似依赖注入的功能,会较大程度提高 views
的可读性并降低 TDD 的门槛,间接提高代码质量。
同时我们需要满足几个条件:
drf-yasg
综上,我写了一个 简单的文件 ,你可以将它 Copy 到你的 DRF 项目中就可以改造原来的 ViewSet
(当前需求是比较简单的,封装成 SDK 然后安装依赖的成本反而高于直接复制粘贴,这样大家可以一起偷懒)
原来的 ViewSet
(包含 drf-yasg
的 schema 生成)
class ProfileViewSet(ListAPIView):
@swagger_auto_schema(
request_body=LoginSerializer,
responses={status.HTTP_200_OK: ProfileSerializer()},
)
def login(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data
...
return Response(data=ProfileSerializer(results, many=True).data)
改造之后的效果:
from some_path.inject import serializer_inject
class ProfileViewSet(ListAPIView):
@serializer_inject(
in_cls=LoginSerializer,
out_cls=ProfileSerializer,
# 选择去掉原有的 request 依赖
config={"remain_request": False},
out_params={"many": True},
)
def login(self, validated_data: dict):
# 原来的逻辑部分
...
return results
(可以通过 gist 评论 获取更多的例子)
这样的改造我们得到了一些好处:
ViewSet
Serializer
drf-yasg
request
对象的情况下,单元测试的用例构造被简化成了 dict
当然仍旧还有不完美的地方:
Type Annotation
,在声明上较 FastAPI
更为冗余context
的 Serializer
需要通过 inject.ResponseParams
类来包装一次,显得不那么纯粹,暂时也没有更好的思路,