DRF - 解析器元件
DRF之解析器元件
引入
Django RestFramework幫助我們實現了處理application/json協議請求的資料,另外,我們也提到,如果不使用DRF,直接從request.body裡面拿到原始的客戶端請求的位元組資料,經過decode,然後json反序列化之後,也可以得到一個Python字典型別的資料。
但是,這種方式並不被推薦,因為已經有了非常優秀的第三方工具,那就是Django RestFramework的解析器元件。
解析器元件的使用
首先,來看看解析器元件的使用,稍後我們一起剖析其原始碼:
from django.http import JsonResponse from rest_framework.views import APIView from rest_framework.parsers import JSONParser, FormParser # Create your views here. class LoginView(APIView): parser_classes = [FormParser] def get(self, request): return render(request, 'parserver/login.html') def post(self, request): # request是被drf封裝的新物件,基於django的request # request.data是一個property,用於對資料進行校驗 # request.data最後會找到self.parser_classes中的解析器 # 來實現對資料進行解析 print(request.data) # {'username': 'jiumo', 'password': 123} return JsonResponse({"status_code": 200, "code": "OK"})
使用方式非常簡單,分為如下兩步:
- from rest_framework.views import APIView
- 繼承APIView
- 直接使用request.data就可以獲取Json資料
如果我們只需要解析Json資料,不允許任何其他型別的資料請求,可以這樣做:
- from rest_framework.parsers import JsonParser
- 給檢視類定義一個parser_classes變數,值為列表型別[JsonParser]
- 如果parser_classes = [], 那就不處理任何資料型別的請求了
首先,需要明確一點,我們肯定需要在request物件上做文章,只有有了使用者請求,我們的解析才有意義,沒有請求,就沒有解析,更沒有處理請求的邏輯,所以,我們需要弄明白,在整個流程中,request物件是什麼時候才出現的,是在繫結url和處理檢視之間的對映關係的時候嗎?我們來看看原始碼:
@classonlymethod 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)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs 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 view
看到了嗎?在執行view函式的時候,那麼什麼時候執行view函式呢?當然是請求到來,根據url查詢對映表,找到檢視函式,然後執行view函式並傳入request物件,所以,如果是我,我可以在這個檢視函式裡面加入處理application/json的功能:
@classonlymethod
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))
def view(request, *args, **kwargs):
if request.content_type == "application/json":
import json
return HttpResponse(json.dumps({"error": "Unsupport content type!"}))
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
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 view
看到了吧,然後我們試試傳送json請求,看看返回結果如何?事實上,我們可以在這裡,也可以在這之後的任何地方進行功能的新增。
那麼,DRF是如何做的呢?我們在使用的時候只是繼承了APIView,然後直接使用request.data,所以,斗膽猜測,功能肯定是在APIView中定義的,具體在哪個地方呢?接下來,我們一起來分析一下DRF解析器原始碼,看看DRF在什麼地方加入了這個功能。
我們通過面向物件的方式,給類的某個方法新增了功能,呼叫重寫的方法,就實現了功能擴充套件,但是上面除了request.data,我們沒有呼叫任何新的方法,所以,問題就在這個request.data上,它絕不僅僅是一個普通的物件屬性。
好了,有了這個共同的認識,我們接下來驗證一下我們的看法。
解析器元件原始碼剖析
請看下圖:
上圖詳細描述了整個過程,最重要的就是重新定義的request物件,和parser_classes變數,也就是我們在上面使用的類變數。好了,通過分析原始碼,驗證了我們的猜測。
原始碼部分:
當我們傳送post請求時,request.data會在這一步進行解析資料。 所以關鍵的地方在.data部分。data是request的靜態方法。所以我們找原始碼的時候應該找request中的靜態方法data。我們都知道這個request是在APIView中的dispatch方法重新封裝了。所以應該去APIVew中找dispatch方法,需要找到request怎麼被重新封裝的。
點進原始碼:
點進Resquest原始碼,找data靜態方法:
這一步就是資料解析的過程,點進去原始碼,
點進原始碼:
點進原始碼self.parser,
這就又回到例項化request的時候了。
點進去原始碼:
這裡這個self,一層一層往上找,就找到了這個self.指的是檢視類函式。所以在試圖類函式中,定義了哪些解析器,他就支援哪些解析器。
class LoginView(APIView):
parser_classes = [FormParser]
def get(self, request):
pass
DRF支援三種格式編碼的解析:
from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
點進去原始碼我們可以看到DRF中的解析器,預設支援三種:
JSONParser, FormParser, MultiPartParser
那如果在我們的試圖類函式中沒有定義這個變數,那他是怎麼走預設的解析器?
那我們點進parser_classes原始碼:
當我們點進api_settings中發現,api_settings是一個類的例項化物件,
但是在這個類中並沒有DEFAULT_PARSER_CLASSES方法。
這裡看一個知識點,在python基礎中,一個類例項化後會進行初始化,執行__init__方法,但對於例項化沒有的屬性會走__getattr__方法。
所以,沒有DEFAULT_PARSER_CLASSES方法,會走__getattr__方法,
而在我們的DEFAULTS中有 DEFAULT_PARSER_CLASSES
,
接下來:
點進去settings,找REST_FRAMEWORK,找不到就去全域性settings中找。找不到就去預設字典字典中找,如果還是找不到,就賦值為空字典。
這就是解析器的原始碼
知識點複習回顧:getattr
在學習面向物件時,我們知道可以通過物件加點號獲取該物件的屬性,也可以通過物件的dict訪問屬性,請看下面的程式碼:
class Father(object):
country = "china"
class Person(Father):
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("pizza", 18)
print(p.__dict__) # {'name': 'pizza', 'age': 18}
print(Person.__dict__) # {'__module__': '__main__', '__init__': <function Person.__init__ at 0x103f132f0>, '__doc__': None}
print(p.name) # jiumo
print(p.age) # 18
print(p.country) # china 如果物件不存在這個屬性,則會到其父類中查詢這個屬性
print(p.hobby) # 如果在父類中也找不到這個屬性,則會報錯:AttributeError: 'Person' object has no attribute 'hobby'
物件的屬性查詢首先會在該物件的一個名為dict的字典中查詢這個屬性,如果找不到,則會到其父類中查詢這個屬性,如果在父類中都也找不到對應的屬性,這會丟擲異常AttributeError,我們可以通過在類中定義一個getattr來重定向未查詢到屬性後的行為,請看下面的程式碼:
class Father(object):
country = "china"
class Person(Father):
def __init__(self, name, age):
self.name = name
self.age = age
def __getattr__(self, value):
raise ValueError("屬性%s不存在" % value)
p = Person("pizza", 18)
print(p.hobby) # ValueError: 屬性hobby不存在
可以看到,我們能夠重新定義異常,也可以做其他任何事情,這就是getattr,一句話總結,通過物件查詢屬性,如果找不到屬性,且該物件有getattr方法,那麼getattr方法會被執行,至於執行什麼邏輯,我們可以自定義。