zoukankan      html  css  js  c++  java
  • rest framework之版本控制

    一、版本控制的使用

     版本控制允许在不同的客户端之间更改行为,其实质就是后台根据客户端传递过来的版本信息做相应的动作,比如不同版本对应不同的序列化样式:

    def get_serializer_class(self):
        if self.request.version == 'v1':
            return UserSerializerVersion1
        return UserSerializer

    (一)URLPathVersioning

    1、在settings中配置DEFAULT_VERSIONING_CLASS

    REST_FRAMEWORK = {
        "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
    
    }

      DEFAULT_VERSIONING_CLASS除非显式设置否则将会是None。在这种情况下request.version属性将总是返回None。当然还可以在单个视图上设置版本控制方案。通常不需要这样做,因为全局使用单一版本控制方案更有意义。如果确实需要这样做,请在视图类中使用versioning_class属性。

    versioning_class = MyVersioning #非元祖或者列表

    2、其它配置参数

    (1)DEFAULT_VERSION. 当版本控制信息不存在时用于设置request.version的默认值,默认设置为None

    (2)ALLOWED_VERSIONS. 如果设置了此值将限制版本控制方案可能返回的版本集,如果客户端请求提供的版本不在此集中,则会引发错误。请注意,用于DEFAULT_VERSION的值应该总是在ALLOWED_VERSIONS设置的集合中(除非是None)。该配置默认是 None

    (3)VERSION_PARAM. 一个应当用于任何版本控制系统参数的字符串,例如媒体类型或URL查询参数。默认值是'version'

    3、完整配置

    REST_FRAMEWORK = {
        "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
        "DEFAULT_VERSION":'v1',
        "ALLOWED_VERSIONS":['v1','v2'],
        "VERSION_PARAM":'version',
    
    }

     4、路由配置

        re_path('(?P<version>[v1|v2]+)/books/$', views.BookView.as_view(), name="books"),

    5、视图中获取版本信息

    from rest_framework.generics import ListAPIView
    
    class BookView(ListAPIView):
    
        def get(self, request, *args, **kwargs):
            #获取版本
            version = request.version
            #获取处理版本对象
            version_obj = request.versioning_scheme
            #可以使用处理版本对象反向生成url,传递request是为了传递version参数
            url = request.versioning_scheme.reverse(viewname='books', request=request)
            return HttpResponse('...')

    (二)其它API

    1、QueryParameterVersioning

    class QueryParameterVersioning(BaseVersioning):
        """
        GET /something/?version=0.1 HTTP/1.1
        Host: example.com
        Accept: application/json
        """
        invalid_version_message = _('Invalid version in query parameter.')
    
        def determine_version(self, request, *args, **kwargs):
            version = request.query_params.get(self.version_param, self.default_version)
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            url = super(QueryParameterVersioning, self).reverse(
                viewname, args, kwargs, request, format, **extra
            )
            if request.version is not None:
                return replace_query_param(url, self.version_param, request.version)
            return url
    QueryParameterVersioning

    此方案是一种在 URL 中包含版本信息作为查询参数的简单方案,路由中没有对参数进行分组:

    re_path('books/$', views.BookView.as_view(), name="books"),

    访问时需要传递参数:

    http://127.0.0.1:8020/books/?version=v1

    后台通过query_params参数来获取:

    class BookView(ListAPIView):
    
        def get(self, request, *args, **kwargs):
            #获取版本
            version = request.query_params.get('version')
            return HttpResponse('...')

    当然,这只是它实现的大概原理,如果使用这种版本控制方式只需要像URLPathVersioning进行配置即可。

    2、NamespaceVersioning

    class NamespaceVersioning(BaseVersioning):
        """
        To the client this is the same style as `URLPathVersioning`.
        The difference is in the backend - this implementation uses
        Django's URL namespaces to determine the version.
    
        An example URL conf that is namespaced into two separate versions
    
        # users/urls.py
        urlpatterns = [
            url(r'^/users/$', users_list, name='users-list'),
            url(r'^/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
        ]
    
        # urls.py
        urlpatterns = [
            url(r'^v1/', include('users.urls', namespace='v1')),
            url(r'^v2/', include('users.urls', namespace='v2'))
        ]
    
        GET /1.0/something/ HTTP/1.1
        Host: example.com
        Accept: application/json
        """
        invalid_version_message = _('Invalid version in URL path. Does not match any version namespace.')
    
        def determine_version(self, request, *args, **kwargs):
            resolver_match = getattr(request, 'resolver_match', None)
            if resolver_match is None or not resolver_match.namespace:
                return self.default_version
    
            # Allow for possibly nested namespaces.
            possible_versions = resolver_match.namespace.split(':')
            for version in possible_versions:
                if self.is_allowed_version(version):
                    return version
            raise exceptions.NotFound(self.invalid_version_message)
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            if request.version is not None:
                viewname = self.get_versioned_viewname(viewname, request)
            return super(NamespaceVersioning, self).reverse(
                viewname, args, kwargs, request, format, **extra
            )
    
        def get_versioned_viewname(self, viewname, request):
            return request.version + ':' + viewname
    NamespaceVersioning

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

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

    urlpatterns = [
        url(r'^v1/books/', include('books.urls', namespace='v1')),
        url(r'^v2/books/', include('books.urls', namespace='v2'))
    ]

    3、HostNameVersioning

    class HostNameVersioning(BaseVersioning):
        """
        GET /something/ HTTP/1.1
        Host: v1.example.com
        Accept: application/json
        """
        hostname_regex = re.compile(r'^([a-zA-Z0-9]+).[a-zA-Z0-9]+.[a-zA-Z0-9]+$')
        invalid_version_message = _('Invalid version in hostname.')
    
        def determine_version(self, request, *args, **kwargs):
            hostname, separator, port = request.get_host().partition(':')
            match = self.hostname_regex.match(hostname)
            if not match:
                return self.default_version
            version = match.group(1)
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    
        # We don't need to implement `reverse`, as the hostname will already be
        # preserved as part of the REST framework `reverse` implementation.
    HostNameVersioning

    主机名版本控制方案要求客户端将请求的版本指定为URL中主机名的一部分。 例如,以下是对http://v1.example.com/books/的HTTP请求:

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

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

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

    二、源码剖析

    (一)URLPathVersioningAPI源码

    1、dispatch

        def dispatch(self, request, *args, **kwargs):
            """
            `.dispatch()` is pretty much the same as Django's regular dispatch,
            but with extra hooks for startup, finalize, and exception handling.
            """
            self.args = args
            self.kwargs = kwargs
            #rest-framework重构request对象
            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            self.headers = self.default_response_headers  # deprecate?
            try:
                self.initial(request, *args, **kwargs)
    
                # Get the appropriate handler method
                #这里和CBV一样进行方法的分发
                if request.method.lower() in self.http_method_names:
                    handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
                else:
                    handler = self.http_method_not_allowed
    
                response = handler(request, *args, **kwargs)
    
            except Exception as exc:
                response = self.handle_exception(exc)
    
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response

    在APIView中的dispatch方法中进行了原request的封装以及其它组件的添加,所以这是rest framework功能的起点,在initial方法中含有与版本相关的内容。

    2、initial

     def initial(self, request, *args, **kwargs):
            """
            Runs anything that needs to occur prior to calling the method handler.
            """
    ...
            # Determine the API version, if versioning is in use.
            #与版本相关
            version, scheme = self.determine_version(request, *args, **kwargs)
            request.version, request.versioning_scheme = version, scheme
    ...

      在这里调用了self.determine_version,这里的self指的是APIView对象,返回值就是对应的版本以及版本对象,并且将版本以及版本对象赋值给了request对象,在self.determine_version方法中调用了配置中版本类对象的determine_version方法。

    3、determine_version

    这里的determine_version指的是版本类中的方法,加入配置的版本API是URLPathVersioning:

    class URLPathVersioning(BaseVersioning):
        """
        To the client this is the same style as `NamespaceVersioning`.
        The difference is in the backend - this implementation uses
        Django's URL keyword arguments to determine the version.
    
        An example URL conf for two views that accept two different versions.
    
        urlpatterns = [
            url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
            url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
        ]
    
        GET /1.0/something/ HTTP/1.1
        Host: example.com
        Accept: application/json
        """
        invalid_version_message = _('Invalid version in URL path.')
    
        def determine_version(self, request, *args, **kwargs):
            version = kwargs.get(self.version_param, self.default_version)
            if version is None:
                version = self.default_version
    
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    ...

    在这里通过kwargs获取分组参数的值,而键值是配置文件中配置好的,如果没有就是用默认配置键version。

    (二)反向生成url

    可以看到在每一个API中都有url反向生成的函数:

    class URLPathVersioning(BaseVersioning):
    ...
         
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            if request.version is not None:
                kwargs = {} if (kwargs is None) else kwargs
                kwargs[self.version_param] = request.version
    
            return super(URLPathVersioning, self).reverse(
                viewname, args, kwargs, request, format, **extra
            )
    
    ...

    在这个函数当中,会把获取到的版本值放入kwargs中去,然后调用父类的reverse方法进行解析,这与django中的reverse相似,只不过reverse后面的参数是版本通过传递的request进行处理的:

    reverse(viewname="books",kwargs={'version':kwargs["version"]})

    参考文档:https://q1mi.github.io/Django-REST-framework-documentation/api-guide/versioning_zh/

  • 相关阅读:
    创业4
    创业3
    PowerBI开发 第十四篇:使用M公式添加列
    PowerBI开发 第十三篇:增量刷新
    PowerBI开发 第十二篇:钻取
    SSIS 连接数据
    SSIS 调试和故障排除
    SQL Server 日志和代理的错误日志
    SSIS 检查点(CheckPoint)内幕
    SSIS 如何处理逻辑类型的转换?
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11516529.html
Copyright © 2011-2022 走看看