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怎么被重新封装的。

点进源码:

点进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方法会被执行,至于执行什么逻辑,我们可以自定义。

posted @ 2018-12-11 21:33  久末丶  阅读(248)  评论(0编辑  收藏  举报