zoukankan      html  css  js  c++  java
  • 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方法会被执行,至于执行什么逻辑,我们可以自定义。

  • 相关阅读:
    嵊泗
    窗函数介绍
    射频与微波测量之失真参数
    C# 文件与路径操作
    史密斯圆图
    Winform 关闭Form而不销毁Form的内存
    射频与微波测量之S参数
    东极岛
    C#中正则表达式解析字符串信息
    射频微波相关公式
  • 原文地址:https://www.cnblogs.com/jiumo/p/10105431.html
Copyright © 2011-2022 走看看