Django rest framework 版本控制(原始碼分析四)
基於上述分析
#2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制 self.initial(request, *args, **kwargs)
#2.1處理版本資訊 #version代表版本 scheme代表版本管理的類 determine_version返回的是一個元祖 version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme= version, scheme
進入determine_version方法
def determine_version(self, request, *args, **kwargs): """ If versioning is being used, then determine any API version for the incoming request. Returns a two-tuple of (version, versioning_scheme) """#versioning_class = api_settings.DEFAULT_VERSIONING_CLASS #如果我們沒有配置版本控制類,將不做版本控制 if self.versioning_class is None: return (None, None) #scheme就是版本控制的類 #versioning_class = api_settings.DEFAULT_VERSIONING_CLASS scheme = self.versioning_class()#返回版本 和版本控制的類 return (scheme.determine_version(request, *args, **kwargs), scheme)
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
例子
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 urlQueryParameterVersioning
如:/users?version=v1
REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 預設版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key }settings
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view(),name='test'), ] urls.pyurls
#!/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
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'), ]urls
#!/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
這種方式傳參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
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view(), name='test'), ]urls
#!/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
基於版本控制類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
from django.conf.urls import url, include 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 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
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 + ':' + viewnameNamespaceVersioning
基於上述分析
#2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制 self.initial(request, *args, **kwargs)
#2.1處理版本資訊 #version代表版本 scheme代表版本管理的類 determine_version返回的是一個元祖 version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme
進入determine_version方法
def determine_version(self, request, *args, **kwargs): """ If versioning is being used, then determine any API version for the incoming request. Returns a two-tuple of (version, versioning_scheme) """ #versioning_class = api_settings.DEFAULT_VERSIONING_CLASS #如果我們沒有配置版本控制類,將不做版本控制 if self.versioning_class is None: return (None, None) #scheme就是版本控制的類 #versioning_class = api_settings.DEFAULT_VERSIONING_CLASS scheme = self.versioning_class() #返回版本 和版本控制的類 return (scheme.determine_version(request, *args, **kwargs), scheme)
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
例子
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 urlQueryParameterVersioning
如:/users?version=v1
REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 預設版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key }settings
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view(),name='test'), ] urls.pyurls
#!/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
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'), ]urls
#!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response <