跳转到主要内容

BestDoctor的REST服务的电池。

项目描述

RestDoctor

BestDoctor的REST服务的电池。

RestDoctor的用途是什么

在BestDoctor,我们有一个自己的API指南,其中说明了API应该如何构建。此外,我们使用Django,并且合理地使用Django Rest Framework。它足够灵活,但我们希望在某些地方有更多的控制和遵循自己的规则。

因此,我们编写了自己的DRF扩展,它具有以下特点:

  1. API版本之间的完全隔离
  2. 通过Accept头进行版本控制
  3. 声明式配置序列化和权限类,用于ViewViewSet
  4. 优化的模式生成

快速启动

restdoctor包添加为依赖项或通过pip安装,并将restdoctor添加到INSTALLED_APPS中。

之后,可以使用restdoctor中的ViewSet,将rest_framework的导入替换为restdoctor.rest_framework

以下是基于DRF教程的示例。之前是:

from django.contrib.auth.models import User
from rest_framework import viewsets
from rest_framework import permissions
from tutorial.quickstart.serializers import UserSerializer, UserListSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_serializer_class(self):
        if self.action == 'list':
            return UserListSerializer
        return self.serializer_class

现在是

from django.contrib.auth.models import User
from restdoctor.rest_framework import viewsets
from rest_framework import permissions
from tutorial.quickstart.serializers import UserSerializer, UserListSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class_map = {
        'default': UserSerializer,
        'list': {
            'response': UserListSerializer,
        },
    }
    permission_classes_map = {
        'default': [permissions.IsAuthenticated]
    }

进一步配置

为了解析Accept头中的格式,需要将中间件添加到应用程序的配置中

ROOT_URLCONF = ...

MIDDLEWARE = [
    ...,
    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',
]

API_PREFIXES = ('/api',)
API_FORMATS = ('full', 'compact')

之后,对于在API_PREFIXES中指定的前缀,将解析Accept头。在处理请求的View或ViewSet中,request将添加一个api_params属性。

安装和配置

在Settings中添加设置

ROOT_URLCONF = 'app.urls'

INSTALLED_APPS = [
    ...,
    'rest_framework',
    'restdoctor',
]

MIDDLEWARE = [
    ...,
    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',
]

API_FALLBACK_VERSION = 'fallback'
API_FALLBACK_FOR_APPLICATION_JSON_ONLY = False
API_DEFAULT_VERSION = 'v1'
API_DEFAULT_FORMAT = 'full'
API_PREFIXES = ('/api',)
API_FORMATS = ('full', 'compact')
API_RESOURCE_DISCRIMINATIVE_PARAM = 'view_type'
API_RESOURCE_DEFAULT = 'common'
API_RESOURCE_SET_PARAM = False
API_RESOURCE_SET_PARAM_FOR_DEFAULT = False
API_V1_URLCONF = 'api.v1_urls'
API_VERSIONS = {
    'fallback': ROOT_URLCONF,
    'v1': API_V1_URLCONF,
}

在项目中使用

rest_frameworkrestdoctor.rest_framework之间有选择时,尽量从restdoctor继承。

from restdoctor.rest_framework.serializers import ModelSerializer
from restdoctor.rest_framework.views import GenericAPIView, RetrieveAPIView, ListAPIView
from restdoctor.rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet

版本控制

RestDoctor根据Accept头将调用路由到隔离的UrlConf

  1. 首先,这意味着如果没有正确的Accept头,API接口可能无法访问并返回404。
  2. 其次,应用程序中可能有多个不同的API版本,它们将不会互相“看到”。

头部的通用格式如下

application/vnd.{vendor}.{version}[-{resource}][.{format}][+json]

其中vendor由应用程序级别的API_VENDOR_STRING参数设置,版本列表及其与UrlConf的对应关系由API_VERSIONS参数确定。

中间件ApiSelectorMiddleware负责对传入请求进行路由,需要将其包括在设置中。

ROOT_URLCONF = 'app.urls'

MIDDLEWARE = [
    ...,
    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',
]

API_V1_URLCONF = 'api.v1.urls'

API_VENDOR_STRING = 'RestDoctor'

API_FALLBACK_VERSION = 'fallback'
API_DEFAULT_VERSION = 'v1'
API_VERSIONS = {
    API_FALLBACK_VERSION: ROOT_URLCONF,
    API_DEFAULT_VERSION: API_V1_URLCONF,
}

API_VERSIONS的路由在Accept以application/vnd.{vendor}开始时触发,如果没有指定版本,则使用API_DEFAULT_VERSION。如果Accept不包含正确的vendor字符串,则选择API_FALLBACK_VERSION

版本可以按格式{version}-{resource}指定,此时ResourceViewSet将使用此信息来选择ViewSet

此外,还可以额外指定{format}以选择响应格式,实际上是在SerializerClassMapApiView中选择序列化器。

此外,格式也可以有版本。如果{format}API_FORMATS中设置为version:{2,3,5},而请求的Accept中只出现版本号version:5,则从大到小选择序列化器。

在成功确定来自Accept头的API版本和参数后,中间件将选择用于进一步处理请求的具体UrlConf,并将api_params属性添加到对象request中。

API响应格式

我们的API指南中定义了响应格式,由RestDoctorRenderer(restdoctor.rest_framework.renderers.RestDoctorRenderer)负责。它仅在包含api_params属性的请求中使用,并且通过在基本View和ViewSet混合类NegotiatedMixinrestdoctor.rest_framework.mixins.NegotiatedMixin)中指定的content_negotiation_class来工作。

SerializerClassMapApiView

DRF允许以相当紧凑的方式定义ModelSeraizlier + ModelViewSet,但留下了一些地方的自由度,而不在其他地方提供。

例如,可以在ViewSet类中重写serializer_class,或者通过ViewSet.get_serializer_class动态定义它,但不能单独为请求和响应重写序列化器。也就是说,不能为update操作指定单独的序列化器,同时使用用于retrieve操作的序列化器来返回更改后的实体。

SerializerClassMapApiView 允许声明式地设置各种 action 的序列化器,分别针对 request 和 response。

ViewSet 的基本 mixin 支持允许在 rest_framework.viewsets 的导入中使用 ReadOnlyModelViewSet,在 restdoctor.rest_framework.viewsets 中透明地替换。

serializer_class_map

SerializerClassMapApiView 允许为不同的 action 和响应格式分别设置序列化器,针对 request 和 response 处理阶段。

from restdoctor.rest_framework.viewsets import ModelViewSet

from app.api.serializers import (
    MyDefaultSerializer, MyCompactSerializer, MyAntoherSerializer,
    MyCreateSerializer, MyUpdateSerializer,
)


class MyApiView(SerializerClassMapApiView):
    """Пример работы с serializer_class_map."""

    serializer_class_map = {
        'default': MyDefaultSerializer,
        'default.compact': MyCompactSerializer,
         'create': {
            'request': MyCreateSerializer,
         },
         'update': {
            'request': MyUpdateSerializer,
            'request.version:3': MyVersion3UpdateSerializer,
            'request.version:2': MyVersionUpdateSerializer,
         },
         'list': {
            'response.another_format': MyAnotherSerializer,
            'meta': MyMetaSerializer,
        }
    }

在这个例子中,我们为 ViewSet 设置了 MyDefaultSerializer 作为基本序列化器。但针对 createupdate action,我们重新定义了处理 request 的序列化器。

此外,我们为 compact 格式定义了序列化器,并为 listupdate action 定义了 another_formatversion:2version:3 格式的序列化器。版本格式的工作原理是寻找精确或较小版本的序列化器。另外,还添加了额外的元信息生成。

permission_classes_map

类似于 serializer_class_map,可以通过定义 permission_classes_map 来声明式地设置不同 action 的不同 permission_classes 集合。

from restdoctor.rest_framework.viewsets import ModelViewSet

from app.api.permissions import PermissionA, PermissionB


class MyViewSet(ModelViewSet):
    permission_classes_map = {
        'default': [PermissionA],
        'retrieve': [PermissionB],
    }

关于 action 的注意

在 DRF 中,action 在使用 Router 注册 ViewSet 时出现。此时,list 和 detail 资源使用不同的 action_maps 进行区分。

list_action_map = {'get': 'list', 'post': 'create'}
detail_action_map = {'get': 'retrieve', 'put': 'update'}

Django 路由机制创建了一个处理函数,该函数实例化带有相应参数的 View/ViewSet。同一个 ViewSet 类将在 UrlConf 中出现两次,具有不同的 action_map。在处理请求时,根据 HTTP 方法确定 action 并调用相应的 ViewSet 实例方法。在处理请求时,ViewSet 始终设置 self.action

然而,对于 View 来说并非如此,因此 SerializerClassMapApiView 添加了一个 action 属性,该属性与 serializer_class_map 中的序列化器搜索相关联。

混合和 ModelViewSet

混合为 ModelViewSet 定义了 'list''retrieve''create''update''destroy' action 的基本操作。

它们与 DRF 版本的主要区别在于,它们使用 SerializerClassMapApiView.get_request_serializerSerializerClassMapApiView.get_response_serializer 而不是 View.get_serializer

RetrieveModelMixin

定义了 retrieve action 的处理器。定义了 get_item 方法。

class RetrieveModelMixin(BaseListModelMixin):
    def retrieve(self, request: Request, *args: typing.Any, **kwargs: typing.Any) -> Response:
        item = self.get_item(request_serializer)
        ...


    def get_item(self, request_serializer: BaseSerializer) -> typing.Union[typing.Dict, QuerySet]:
        return self.get_object()

即可以使用 RetrieveModelMixin 来处理任何字典,而不仅仅是模型,只需重新定义 ViewSet.get_item 即可。

ListModelMixin

定义了 list action 的处理器。定义了 get_collection 方法。

class ListModelMixin(BaseListModelMixin):
    def list(self, request: Request, *args: typing.Any, **kwargs: typing.Any) -> Response:
        queryset = self.get_collection()
        ...

    def get_collection(self, request_serializer: BaseSerializer) -> typing.Union[typing.List, QuerySet]:
        return self.filter_queryset(self.get_queryset())

即可以使用 ListModelMixin 来处理任何集合,而不仅仅是模型,只需重新定义 ViewSet.get_collection 即可。在此,如果为 list 指定了序列化器,则它将被用于查询参数,这将允许获取这些参数并附加到 filterset 上。

定义了额外的 meta 信息生成。定义了 get_meta_data 方法。

class ListModelMixin(BaseListModelMixin):
    def get_meta_data(self) -> typing.Dict[str, typing.Any]:
        return {'test': typing.Any}

即可以使用 ListModelMixin 来生成额外的 meta 信息。为了正确工作,需要定义 meta 的序列化器。

    serializer_class_map = {
         'default': MyDefaultSerializer,
         'list': {
            'meta': MyMetaSerializer,
        }
    }

为分页中选择的数据设置了 perform_list 处理器。为了工作,需要重新定义 perform_list 方法。

class ListModelMixin(BaseListModelMixin):
    def perform_list(self, data: typing.Union[typing.List, QuerySet]) -> None:
        Sender(data)

ListModelViewSet

仅设置了 list action 的处理器。

ReadOnlyModelViewSet

设置了 listretrieve action 的处理器。

CreateUpdateReadModelViewSet

设置了 listretrievecreateupdate action 的处理器。

ModelViewSet

包含全部 action:listretrievecreateupdatedestroy

PydanticSerializer

要使用基于 pydantic 的序列化器,需要从 PydanticSerializer 继承,在 Meta 中指定 pydantic_modelpydantic_use_aliases(如有必要)。

pydantic_use_aliases 参数允许使用 pydantic 模型的别名 进行序列化。

class PydanticSerializer(PydanticSerializer):
    class Meta:
        pydantic_model = PydanticModel
        pydantic_use_aliases = True

生成方案

支持生成 3.0.2 和 3.1.0 版本的 openapi 方案。默认方案由 API_DEFAULT_OPENAPI_VERSION 参数指定,默认为 3.0.2

生成方案的示例(设置版本)

python3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator > your_app/static/openapi.schema

生成方案示例版本 openapi 3.0.2

python3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator30 > your_app/static/openapi-30.schema

生成方案示例版本 openapi 3.1.0

python3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator31 > your_app/static/openapi-31.schema

生成选项

API_STRICT_SCHEMA_VALIDATION
  • 字段描述(help_text),模型中的 verbose_name 为必填项
  • 检查字段注释与属性 allow_null 是否一致
  • 检查字段注释与属性 many 是否一致

如果任何检查失败,生成方案将因错误而终止。

API_SCHEMA_PRIORITIZE_SERIALIZER_PARAMETERS

启用此选项后,即使它们重复,也会选择序列化器的字段。

API_SCHEMA_FILTER_MAP_PATH

自定义 DjangoFilterBackend 过滤器处理方案的路径,默认为 restdoctor.rest_framework.schema.filters.FILTER_MAP

pre-commit

此存储库使用通过 pre-commit 配置的 git 钩,因此如果计划进一步修改存储库,则需要使用以下命令初始化 pre-commit:

make install-hooks

项目详情


发布历史 发布通知 | RSS 源

下载文件

下载适用于您平台的应用程序文件。如果您不确定选择哪一个,请了解有关 安装包 的更多信息。

源分布

restdoctor-0.0.64.tar.gz (83.5 kB 查看哈希值)

上传时间

构建分布

restdoctor-0.0.64-py3-none-any.whl (117.9 kB 查看哈希值)

上传时间 Python 3

支持