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