versioning.py

版本控制

为接口进行版本控制仅仅是终止已部署客户端的一种“礼貌”方式。

罗伊·菲尔丁

API 版本控制允许你在不同的客户端之间更改行为。REST 框架提供多种不同的版本控制方案。

版本控制由传入的客户端请求决定,它可以基于请求 URL 或请求头。

有多种有效的方法来实现版本控制。 非版本控制系统也可能适用,特别是如果你在为长期系统进行设计,并且有多个不受你控制的客户端。

REST 框架中的版本控制

当启用 API 版本控制时,request.version 属性将包含一个字符串,该字符串对应于传入客户端请求中请求的版本。

默认情况下,版本控制未启用,并且 request.version 将始终返回 None

根据版本改变行为

如何改变 API 行为取决于你,但一个你通常需要的示例是在较新版本中切换到不同的序列化样式。例如

def get_serializer_class(self):
    if self.request.version == 'v1':
        return AccountSerializerVersion1
    return AccountSerializer

反转版本化 API 的 URL

REST 框架中包含的 reverse 函数与版本控制方案相关联。你需要确保将当前 request 作为关键字参数包含在内,如下所示。

from rest_framework.reverse import reverse

reverse('bookings-list', request=request)

上述函数将应用适合请求版本的任何 URL 转换。例如

  • 如果使用 NamespaceVersioning,并且 API 版本为“v1”,则将使用的 URL 查找为 'v1:bookings-list',它可能会解析为 http://example.org/v1/bookings/ 等 URL。
  • 如果使用 QueryParameterVersioning,并且 API 版本为 1.0,则返回的 URL 可能类似于 http://example.org/bookings/?version=1.0

版本化 API 和超链接序列化器

在将超链接序列化样式与基于 URL 的版本控制方案结合使用时,请确保将请求作为上下文包含在序列化器中。

def get(self, request):
    queryset = Booking.objects.all()
    serializer = BookingsSerializer(queryset, many=True, context={'request': request})
    return Response({'all_bookings': serializer.data})

这样做将允许任何返回的 URL 包含适当的版本控制。

配置版本控制方案

版本控制方案由 DEFAULT_VERSIONING_CLASS 设置键定义。

REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning'
}

除非明确设置,否则 DEFAULT_VERSIONING_CLASS 的值将为 None。在这种情况下,request.version 属性将始终返回 None

您还可以在单个视图上设置版本控制方案。通常您不需要这样做,因为在全局范围内使用单个版本控制方案更有意义。如果您确实需要这样做,请使用 versioning_class 属性。

class ProfileList(APIView):
    versioning_class = versioning.QueryParameterVersioning

其他版本控制设置

以下设置键也用于控制版本控制

  • DEFAULT_VERSION。当没有版本控制信息时,应为 request.version 使用的值。默认为 None
  • ALLOWED_VERSIONS。如果设置,此值将限制版本控制方案可能返回的版本集,并且如果提供的版本不在此集中,将引发错误。请注意,用于 DEFAULT_VERSION 设置的值始终被视为 ALLOWED_VERSIONS 集的一部分(除非它是 None)。默认为 None
  • VERSION_PARAM。应用于任何版本控制参数的字符串,例如在媒体类型或 URL 查询参数中。默认为 'version'

您还可以通过定义自己的版本控制方案并使用 default_versionallowed_versionsversion_param 类变量,在每个视图或每个视图集基础上设置您的版本控制类以及这三个值。例如,如果您想使用 URLPathVersioning

from rest_framework.versioning import URLPathVersioning
from rest_framework.views import APIView

class ExampleVersioning(URLPathVersioning):
    default_version = ...
    allowed_versions = ...
    version_param = ...

class ExampleView(APIVIew):
    versioning_class = ExampleVersioning

API 参考

AcceptHeaderVersioning

此方案要求客户端在 Accept 标头中将版本指定为媒体类型的一部分。版本包含为媒体类型参数,它补充了主媒体类型。

以下是一个使用接受标头版本控制样式的 HTTP 请求示例。

GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0

在上面的示例请求中,request.version 属性将返回字符串 '1.0'

基于接受标头的版本控制通常被认为是最佳实践,尽管其他样式可能适合您的客户端要求。

使用带有供应商媒体类型的接受标头

严格来说,json 媒体类型未指定为 包含其他参数。如果您正在构建一个规范良好的公共 API,可以考虑使用 供应商媒体类型。为此,请配置您的渲染器以使用具有自定义媒体类型的基于 JSON 的渲染器

class BookingsAPIRenderer(JSONRenderer):
    media_type = 'application/vnd.megacorp.bookings+json'

您的客户端请求现在看起来像这样

GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/vnd.megacorp.bookings+json; version=1.0

URLPathVersioning

此方案要求客户端将版本指定为 URL 路径的一部分。

GET /v1/bookings/ HTTP/1.1
Host: example.com
Accept: application/json

您的 URL 配置必须包含一个模式,该模式使用 'version' 关键字参数匹配版本,以便版本控制方案可以使用此信息。

urlpatterns = [
    re_path(
        r'^(?P<version>(v1|v2))/bookings/$',
        bookings_list,
        name='bookings-list'
    ),
    re_path(
        r'^(?P<version>(v1|v2))/bookings/(?P<pk>[0-9]+)/$',
        bookings_detail,
        name='bookings-detail'
    )
]

NamespaceVersioning

对于客户端,此方案与 URLPathVersioning 相同。唯一的区别在于它在 Django 应用程序中的配置方式,因为它使用 URL 名称空间,而不是 URL 关键字参数。

GET /v1/something/ HTTP/1.1
Host: example.com
Accept: application/json

使用此方案,request.version 属性是根据与传入请求路径匹配的 namespace 确定的。

在以下示例中,我们为一组视图提供了两个不同的可能的 URL 前缀,每个前缀都在不同的名称空间下

# bookings/urls.py
urlpatterns = [
    re_path(r'^$', bookings_list, name='bookings-list'),
    re_path(r'^(?P<pk>[0-9]+)/$', bookings_detail, name='bookings-detail')
]

# urls.py
urlpatterns = [
    re_path(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
    re_path(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]

如果您只需要一个简单的版本控制方案,则 URLPathVersioningNamespaceVersioning 都是合理的。URLPathVersioning 方法可能更适合小型临时项目,而 NamespaceVersioning 可能更容易管理大型项目。

HostNameVersioning

主机名版本控制方案要求客户端将请求的版本指定为 URL 中主机名的一部分。

例如,以下是向 http://v1.example.com/bookings/ URL 发出的 HTTP 请求

GET /bookings/ HTTP/1.1
Host: v1.example.com
Accept: application/json

默认情况下,此实现期望主机名与这个简单的正则表达式相匹配

^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$

请注意,第一组括在括号中,表示这是主机名的匹配部分。

HostNameVersioning 方案在调试模式下使用起来可能很尴尬,因为您通常会访问原始 IP 地址,例如 127.0.0.1。网上有各种教程,介绍如何 使用自定义子域名访问 localhost,在这种情况您可能会发现这些教程很有用。

基于主机名的版本控制可能特别有用,如果您需要根据版本将传入请求路由到不同的服务器,因为您可以为不同的 API 版本配置不同的 DNS 记录。

QueryParameterVersioning

此方案是一种简单的样式,它将版本作为 URL 中的查询参数包含在内。例如

GET /something/?version=0.1 HTTP/1.1
Host: example.com
Accept: application/json

自定义版本控制方案

要实现自定义版本控制方案,请对 BaseVersioning 进行子类化并覆盖 .determine_version 方法。

示例

以下示例使用自定义 X-API-Version 标头来确定请求的版本。

class XAPIVersionScheme(versioning.BaseVersioning):
    def determine_version(self, request, *args, **kwargs):
        return request.META.get('HTTP_X_API_VERSION', None)

如果您的版本控制方案基于请求 URL,您还将希望更改确定版本化 URL 的方式。为此,您应该覆盖类上的 .reverse() 方法。请参阅源代码以获取示例。