-
-
Save IMBlues/e36e792159729f429f9abf656ba24d10 to your computer and use it in GitHub Desktop.
| # -*- coding: utf-8 -*- | |
| import functools | |
| from collections import namedtuple | |
| from dataclasses import dataclass, field | |
| from typing import TYPE_CHECKING, Optional, Type | |
| from django.conf import settings | |
| from django.utils.module_loading import import_string | |
| from rest_framework import status | |
| try: | |
| from drf_yasg.utils import swagger_auto_schema | |
| SKIP_SWAGGER_SCHEMA = False | |
| except ImportError: | |
| SKIP_SWAGGER_SCHEMA = True | |
| from rest_framework.serializers import BaseSerializer | |
| if TYPE_CHECKING: | |
| from rest_framework.request import Request | |
| ResponseParams = namedtuple("ResponseParams", "data,params") | |
| @dataclass | |
| class SerializerInjector: | |
| """A injector for injecting serializer as dependency""" | |
| in_cls: Type[BaseSerializer] | |
| out_cls: Type[BaseSerializer] | |
| config: dict = field(default_factory=dict) | |
| in_raw_params: dict = field(default_factory=dict) | |
| out_raw_params: dict = field(default_factory=dict) | |
| _default_config_value = {"data_from": "query_params", "return_validated_data": True, "remain_request": True} | |
| def __post_init__(self): | |
| self.in_raw_params = self.in_raw_params or dict(raise_exception=True) | |
| self.config = self.config or {} | |
| self.out_raw_params = self.out_raw_params or {} | |
| for extend_config_name, default_value in self._default_config_value.items(): | |
| setattr( | |
| self, | |
| f"{extend_config_name}", | |
| self.config.get(extend_config_name, default_value), | |
| ) | |
| try: | |
| self.resp_cls = import_string(settings.SERIALIZER_INJECTOR_RESP_CLS) | |
| except AttributeError: | |
| self.resp_cls = import_string("rest_framework.response.Response") | |
| def __str__(self): | |
| return f"Injector<In:{self.in_cls.__class__.__name__}, Out:{self.out_cls.__class__.__name__}>" | |
| def update_out_params(self, params: dict): | |
| """Update out params""" | |
| self.out_raw_params.update(params) | |
| def get_serializer_instance(self, request: "Request") -> "BaseSerializer": | |
| """Get in serializer instance""" | |
| slz_obj = self.in_cls(data=getattr(request, self.data_from)) # type: ignore | |
| slz_obj.is_valid(**self.in_raw_params) | |
| return slz_obj | |
| def get_validated_data(self, request: "Request") -> dict: | |
| """Get validated data via in_serializer""" | |
| return self.get_serializer_instance(request).validated_data | |
| def get_in_params(self, request: "Request") -> dict: | |
| """Get extra params before view logic""" | |
| if self.return_validated_data: # type: ignore | |
| return {"validated_data": self.get_validated_data(request)} | |
| else: | |
| return {"serializer_instance": self.get_serializer_instance(request)} | |
| def get_response(self, data): | |
| """Get Response data""" | |
| return self.resp_cls(data=self.out_cls(data, **self.out_raw_params).data) | |
| def serializer_inject( | |
| in_cls: Type[BaseSerializer] = None, | |
| out_cls: Type[BaseSerializer] = None, | |
| config: Optional[dict] = None, | |
| in_params: Optional[dict] = None, | |
| out_params: Optional[dict] = None, | |
| swagger_params: Optional[dict] = None, | |
| ): | |
| def decorator_serializer_inject(func): | |
| injector = SerializerInjector(in_cls, out_cls, config, in_params, out_params) | |
| if not SKIP_SWAGGER_SCHEMA: | |
| default_params = {} | |
| if in_cls: | |
| if injector.data_from == "query_params": | |
| default_params = {"query_serializer": in_cls()} | |
| else: | |
| default_params = {"request_body": in_cls()} | |
| if out_cls: | |
| default_params.update({"responses": {status.HTTP_200_OK: out_cls()}}) | |
| default_params.update(swagger_params or {}) | |
| func = swagger_auto_schema(**default_params)(func) | |
| @functools.wraps(func) | |
| def decorated(*args, **kwargs): | |
| in_content = {} | |
| if in_cls: | |
| in_content.update(**injector.get_in_params(args[1])) | |
| if not injector.remain_request: | |
| args = args[0] + args[1:] | |
| original_data = func(*args, **kwargs, **in_content) | |
| if not out_cls: | |
| return original_data | |
| # support runtime serializer params, like "context" | |
| if isinstance(original_data, ResponseParams): | |
| injector.update_out_params(original_data.params) | |
| original_data = original_data.data | |
| return injector.get_response(original_data) | |
| return decorated | |
| return decorator_serializer_inject |
可以在 蓝鲸用户管理项目 查看最新的更新
@IMBlues Thanks. But I am wondered why such a mature - IMO - framework does not have DI by default. I mean it is really cool and every body needs it. Besides it is really peculiar that we have not bootstrapping process for our DRF app. I guess we have a problem in DRF, Django.
@IMBlues Thanks. But I am wondered why such a mature - IMO - framework does not have DI by default. I mean it is really cool and every body needs it. Besides it is really peculiar that we have not bootstrapping process for our DRF app. I guess we have a problem in DRF, Django.
Haha, I wondered it too. Dependency injection is not a particularly trendy concept, and the DRF community should consider supporting such syntactic sugar natively. Maybe sometime I'll raise an issue with the community and see if they'll accept it.
And I had updated some code in our SDK project(the code in this gist is not updated), you can install the sdk directly(althought we only have readme in chinese version, but I believe the code is pretty and easy reading) or just copy it to your own project. Enjoy!
Examples