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

     

  • 相关阅读:
    Linux Shell 下的输出重定向
    解决 Scrapy-Redis 空跑问题,链接跑完后自动关闭爬虫
    数据清洗基本概念
    前端常见的跨域请求解决方案
    Pandas模块:表计算与数据分析
    Matplotlib模块:绘图和可视化
    numpy如何使用
    Gerapy 使用详解
    MySQL常见数据库引擎及比较
    基于scrapy-redis组件的分布式爬虫
  • 原文地址:https://www.cnblogs.com/sunxiuwen/p/9892552.html
Copyright © 2011-2022 走看看