注意:一定要跟著博主的解說再看代碼的中文注釋及其下面的一行代碼!!!
說到api版本控制,就是我們的前端人員請求的后臺介面可能有多個版本,后臺的介面地址一般是有兩種形式,博主現以這兩種形式逐一解釋api版本控制組件的原始碼剖析,
第一種api版本控制的url格式一般是:http://localhost:8000/user/select/?version=v1,第二種是:http://localhost:8000/user/v1/select/,分別對應以下兩種
1、我們依然是使用流程來決議原始碼,首先我們肯定是匹配user下select路由的視圖類進入as_view方法
from django.conf.urls import url from . import views app_name = '[user]' urlpatterns = [ # 這是get請求引數的 url(r'select/', views.UserView.as_view(), name="select"), # 用戶資訊查詢所有 # 這是urlpath路徑的引數 url(r'^?P<version>[v1|v2])/select/$', views.UserView.as_view(), name="select"), # 用戶資訊查詢所有,與上者只存其一 ]
2、在看下UserView下有沒有as_view方法
class UserView(APIView): def get(self, request): print(request.version) return Response(data=https://www.cnblogs.com/aitiknowledge/p/{"code": 200, "result": "res"})
3、會發現UserView下沒有as_view方法,這個是可以查看APIView父類下有沒有as_view方法,若沒有,就以此類推,顯而易見,APIView下有as_view方法,博主添上代碼已中文注釋
def as_view(cls, **initkwargs): """ Store the original class on the view function. This allows us to discover information about the view when we do URL reverse lookups. Used for breadcrumb generation. """ if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet): def force_evaluation(): raise RuntimeError( 'Do not evaluate the `.queryset` attribute directly, ' 'as the result will be cached and reused between requests. ' 'Use `.all()` or call `.get_queryset()` instead.' ) cls.queryset._fetch_all = force_evaluation # 執行父類的as_view方法,這里的cls就是請求視圖類 view = super(APIView, cls).as_view(**initkwargs) view.cls = cls view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. return csrf_exempt(view)APIView.as_view()
4、但是APIView類的as_view方法中又去執行了父類的as_view方法,super關鍵字已經在前幾篇博客中提及,朋友們可前往查看,博主就再一次添上View父類的as_view方法代碼及中文注釋
def as_view(cls, **initkwargs): """Main entry point for a request-response process.""" for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) # 執行view方法 def view(request, *args, **kwargs): # 實體化請求視圖類物件,即self是請求視圖類物件 self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get # 注意:這里的request物件還是原生的request物件 self.request = request self.args = args self.kwargs = kwargs # dispatch方法至關重要 return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return viewView.as_view()
5、在上面的方法中他就會執行到里面的view方法,根據中文注釋,最后再一次從請求視圖類開始尋找dispatch方法,請求視圖類中沒有,就又回到了APIView類中,執行dispatch方法,添上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還是請求視圖類物件 self.args = args self.kwargs = kwargs # 這里是對原生的request加工處理,回傳一個新的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 if request.method.lower() in self.http_method_names: # 通過python的反射機制反射到請求視圖類的方法名稱 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.responseAPIView.dispatch()
6、api版本控制組件就在initial方法里頭,我們點進去看下,添上initial方法及中文注釋
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. # rest framework的api的版本控制 # 類似于:api/?version=v1 或者是 api/v1/ version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted # 查看原始碼,用戶驗證的方法,這個request 是加工之后的request self.perform_authentication(request) # 用戶權限驗證 self.check_permissions(request) # 用戶訪問頻率限制 self.check_throttles(request)APIView.initial()
7、里面就會有版本控制的代碼陳述句:version, schema = self.determine_version(request, *args, **kwargs),接下來就添上涉及determine_version方法的一些重要代碼及中文注釋
class APIView(View): # 如果請求視圖類中沒有versioning_class變數及值,就會去匹配全域組態檔的值 versioning_class = api_settings.DEFAULT_VERSIONING_CLASS 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) """ # 這里判斷self.versioning_class是否有值 if self.versioning_class is None: return (None, None) # 實體化版本控制類物件 scheme = self.versioning_class() from rest_framework.versioning import BaseVersioning # 這里就對應上一個方法的版本值和呼叫的版本控制類物件 return (scheme.determine_version(request, *args, **kwargs), scheme)APIView.determine_version()
8、從上面就可以看出我們可以在請求視圖類中撰寫上面提及的變數,但是這個變數的值就不是串列了,還有代碼中的注釋,值可以是一個我們自定義或者框架自帶的版本控制類,隨后添上Django框架中versioning.py檔案的部分版本控制類
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 return ((version is not None and version == self.default_version) or (version in self.allowed_versions)) 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,這里傳遞視圖的名稱之外,只需要傳遞新封裝的request物件即可 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 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. # 撰寫url路由的規則就需要遵循下面的撰寫規則,這個很重要 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 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,這里傳遞視圖的名稱之外,只需要傳遞新封裝的request物件即可 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 )versioning.py
9、最后,我們若想要自定義,就需要繼承里面的基類,就和上面的類似,而且其實我們是不需要自定義,因為Django中已經夠開發使用,如果在全域中配置,方式如下:
REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "URLPathVersioning", "DEFAULT_VERSION": "v1", # 默認的版本 "ALLOWED_VERSIONS": ["v1", "v2"] # 允許的版本 }
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/137682.html
標籤:Python
上一篇:查詢/查詢顯示報錯:
下一篇:求推薦統計學,演算法方面的書籍
