1. 程式人生 > >DRF - 解析器元件

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上,它絕不僅僅是一個普通的物件屬性。

好了,有了這個共同的認識,我們接下來驗證一下我們的看法。

解析器元件原始碼剖析

請看下圖:

parser

上圖詳細描述了整個過程,最重要的就是重新定義的request物件,和parser_classes變數,也就是我們在上面使用的類變數。好了,通過分析原始碼,驗證了我們的猜測。

原始碼部分:

當我們傳送post請求時,request.data會在這一步進行解析資料。 所以關鍵的地方在.data部分。data是request的靜態方法。所以我們找原始碼的時候應該找request中的靜態方法data。我們都知道這個request是在APIView中的dispatch方法重新封裝了。所以應該去APIVew中找dispatch方法,需要找到request怎麼被重新封裝的。

  • img

點進原始碼:

img

點進Resquest原始碼,找data靜態方法:

img

這一步就是資料解析的過程,點進去原始碼,

img

點進原始碼:

img

點進原始碼self.parser,

img

這就又回到例項化request的時候了。

img

點進去原始碼:

img

這裡這個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原始碼:

img

當我們點進api_settings中發現,api_settings是一個類的例項化物件,img

但是在這個類中並沒有DEFAULT_PARSER_CLASSES方法。

這裡看一個知識點,在python基礎中,一個類例項化後會進行初始化,執行__init__方法,但對於例項化沒有的屬性會走__getattr__方法。

img

所以,沒有DEFAULT_PARSER_CLASSES方法,會走__getattr__方法,

img

而在我們的DEFAULTS中有 DEFAULT_PARSER_CLASSES

接下來:

img

點進去settings,找REST_FRAMEWORK,找不到就去全域性settings中找。找不到就去預設字典字典中找,如果還是找不到,就賦值為空字典。

img

1544535077095

1544533994717

1544534093275

這就是解析器的原始碼

知識點複習回顧: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方法會被執行,至於執行什麼邏輯,我們可以自定義。