zoukankan      html  css  js  c++  java
  • Django rest framework 版本控制(源码分析)

    一、前言

    通过DRF写的视图类,都会继承APIVIEW,而APIVIEW类继承VIEW,同时其重写了as_view方法【使之cbv能够url传参】,dispatch方法,重新封装了request对象,

    将老的request对象赋值为_request,

    前端传过来的所有数据给了:request.data

    之前的request.GET封装成:request.query_params

    二、版本控制是做什么用的, 我们为什么要用?

    开发是有周期,及版本迭代的,新版本上线,老版本也不能直接拉下,需要进行维护

    三、DRF版本控制源码分析

    之前我们学视图的时候知道APIView,也知道APIView返回View中的view函数,然后调用的dispatch方法~

    执行self.initial方法之前是各种赋值,包括request的重新封装赋值,下面是路由的分发,那我们看下这个方法都做了什么~~ 

    我们可以看到,我们的version版本信息赋值给了 request.version  版本控制方案赋值给了 request.versioning_scheme~~

    从而之后我们在视图中就可以通过request通过点属性,操作版本信息了

    其实这个版本控制方案~就是我们配置的版本控制的类~~

    也就是说,APIView通过这个方法初始化自己提供的组件~~

    DRF提供了5种版本控制类的方法:

    导入模块:from rest_framework import versioning  

    scheme.determine_version的执行取决与我们所引用的版本控制类是哪一个 

     

    这里以常用的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_param default_version DEFAULT_VERSION 都是在settings配置 #version_param :url中获取值的key #default_version :默认版本 #ALLOWED_VERSIONS:允许的版本 version = kwargs.get(self.version_param, self.default_version) #如果version不存在,或者version不在允许访问的版本列表中 if not self.is_allowed_version(version): #抛出异常 raise exceptions.NotFound(self.invalid_version_message) #返回版本 return version

    is_allowed_version方法

    #判断是否能访问当前版本
        def is_allowed_version(self, version):
            if not self.allowed_versions:
                return True
            #version存在并且等于默认版本或者version在允许访问的版本中
            #返回True or Fasle
            return ((version is not None and version == self.default_version) or
                    (version in self.allowed_versions))

    到这里我们获取到了具体访问的版本和控制版本的类

    回到最开始

     #2.1处理版本信息
            #version代表版本  scheme代表版本管理的类  determine_version返回的是一个元祖
            version, scheme = self.determine_version(request, *args, **kwargs)
            request.version, request.versioning_scheme = version, scheme

    我们将version 和scheme封装在request中如果我们访问的版本符合要求我们可以通过调用

    request.version, request.versioning_scheme 来获得版本号和控制版本的类

    BaseVersioning所有版本控制类都要继承的基类

    class BaseVersioning(object):
        default_version = api_settings.DEFAULT_VERSION
        allowed_versions = api_settings.ALLOWED_VERSIONS
        version_param = api_settings.VERSION_PARAM
    
        def determine_version(self, request, *args, **kwargs):
            msg = '{cls}.determine_version() must be implemented.'
            raise NotImplementedError(msg.format(
                cls=self.__class__.__name__
            ))
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            return _reverse(viewname, args, kwargs, request, format, **extra)
    
        #判断是否能访问当前版本
        def is_allowed_version(self, version):
            if not self.allowed_versions:
                return True
            #version存在并且等于默认版本或者version在允许访问的版本中
            #返回True or Fasle
            return ((version is not None and version == self.default_version) or
                    (version in self.allowed_versions))
    
    BaseVersioning
    View Code

    四、如何使用

    a. 基于url的get传参方式(应用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
    View Code

     如:/users?version=v1

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',            # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允许的版本
        'VERSION_PARAM': 'version'          # URL中获取值的key
    }
    setting配置
    from web.views import TestView
    
    urlpatterns = [
        url(r'^test/', TestView.as_view(),name='test'),
    ]
    urls.py
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import QueryParameterVersioning
    
    
    class TestView(APIView):
        versioning_class = QueryParameterVersioning
    
        def get(self, request, *args, **kwargs):
    
            # 获取版本
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
    
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    views.py

    b. 基于url的正则方式

    如:/v1/users/

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',            # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允许的版本
        'VERSION_PARAM': 'version'          # URL中获取值的key
    }
    settings.py
    from web.views import TestView
    
    urlpatterns = [
        url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
    ]
    urls.py
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import URLPathVersioning
    
    
    class TestView(APIView):
        versioning_class = URLPathVersioning
    
        def get(self, request, *args, **kwargs):
            # 获取版本
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
    
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    
    views.py
    views.py

    这种方式传参url写法源码有说明(URLPathVersioning):

    class BaseVersioning(object):
        default_version = api_settings.DEFAULT_VERSION
        allowed_versions = api_settings.ALLOWED_VERSIONS
        version_param = api_settings.VERSION_PARAM
    
        def determine_version(self, request, *args, **kwargs):
            msg = '{cls}.determine_version() must be implemented.'
            raise NotImplementedError(msg.format(
                cls=self.__class__.__name__
            ))
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            return _reverse(viewname, args, kwargs, request, format, **extra)
    
        #判断是否能访问当前版本
        def is_allowed_version(self, version):
            if not self.allowed_versions:
                return True
            #version存在并且等于默认版本或者version在允许访问的版本中
            #返回True or Fasle
            return ((version is not None and version == self.default_version) or
                    (version in self.allowed_versions))
    BaseVersioning
    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_param  default_version DEFAULT_VERSION 都是在settings配置
            #version_param :url中获取值的key
            #default_version :默认版本
            #ALLOWED_VERSIONS:允许的版本
            version = kwargs.get(self.version_param, self.default_version)
            #如果version不存在,或者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):
            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
            )
    URLPathVersioning

    c. 基于 accept 请求头方式

    如:Accept: application/json; version=1.0

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',            # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],   # 允许的版本
        'VERSION_PARAM': 'version'          # URL中获取值的key
    }
    settings.py
    from web.views import TestView
    
    urlpatterns = [
        url(r'^test/', TestView.as_view(), name='test'),
    ]
    
    urls
    urls.py
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import AcceptHeaderVersioning
    
    
    class TestView(APIView):
        versioning_class = AcceptHeaderVersioning
    
        def get(self, request, *args, **kwargs):
            # 获取版本 HTTP_ACCEPT头
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    
    views.py
    views.py

    基于版本控制类AcceptHeaderVersioning

    class AcceptHeaderVersioning(BaseVersioning):
        """
        GET /something/ HTTP/1.1
        Host: example.com
        Accept: application/json; version=1.0
        """
        invalid_version_message = _('Invalid version in "Accept" header.')
    
        def determine_version(self, request, *args, **kwargs):
            media_type = _MediaType(request.accepted_media_type)
            version = media_type.params.get(self.version_param, self.default_version)
            version = unicode_http_header(version)
            if not self.is_allowed_version(version):
                raise exceptions.NotAcceptable(self.invalid_version_message)
            return version
    
        # We don't need to implement `reverse`, as the versioning is based
        # on the `Accept` header, not on the request URL.
    AcceptHeaderVersioning

    d. 基于主机名方法

    如:v1.example.com

    ALLOWED_HOSTS = ['*']
    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',  # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],  # 允许的版本
        'VERSION_PARAM': 'version'  # URL中获取值的key
    }
    
    settings.py
    settings.py
    from web.views import TestView
    
    urlpatterns = [
        url(r'^test/', TestView.as_view(), name='test'),
    ]
    
     urls.py
    urls.py
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import HostNameVersioning
    
    
    class TestView(APIView):
        versioning_class = HostNameVersioning
    
        def get(self, request, *args, **kwargs):
            # 获取版本
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    views.py

    引用的的版本控制类

    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

    e. 基于django路由系统的namespace

    如:example.com/v1/users/

    REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',  # 默认版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],  # 允许的版本
        'VERSION_PARAM': 'version'  # URL中获取值的key
    }
    settings.py
    from django.conf.urls import url, include
    from web.views import TestView
    
    urlpatterns = [
        url(r'^v1/', ([
                          url(r'test/', TestView.as_view(), name='test'),
                      ], None, 'v1')),
        url(r'^v2/', ([
                          url(r'test/', TestView.as_view(), name='test'),
                      ], None, 'v2')),
    
    ]
    urls.py
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import NamespaceVersioning
    
    
    class TestView(APIView):
        versioning_class = NamespaceVersioning
    
        def get(self, request, *args, **kwargs):
            # 获取版本
            print(request.version)
            # 获取版本管理的类
            print(request.versioning_scheme)
            # 反向生成URL
            reverse_url = request.versioning_scheme.reverse('test', request=request)
            print(reverse_url)
    
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')
    
        def put(self, request, *args, **kwargs):
            return Response('PUT请求,响应内容')
    views.py

    引用的版本控制类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

     

  • 相关阅读:
    zoj 3627#模拟#枚举
    Codeforces 432D Prefixes and Suffixes kmp
    hdu 4778 Gems Fight! 状压dp
    CodeForces 379D 暴力 枚举
    HDU 4022 stl multiset
    手动转一下田神的2048
    【ZOJ】3785 What day is that day? ——KMP 暴力打表找规律
    poj 3254 状压dp
    C++中运算符的优先级
    内存中的数据对齐
  • 原文地址:https://www.cnblogs.com/sunxiuwen/p/9892552.html
Copyright © 2011-2022 走看看