about
REST framework框架本身在提供了异常处理,但是仅针对drf内部现有的接口开发相关的异常进行格式处理,比如限流、认证、权限之类的异常处理。
但是开发中我们还会使用到各种的数据或者进行各种网络请求,这些都有可能导致出现异常,这些异常在drf中是没有进行处理的,所以就会冒泡给django框架了,django框架会进行组织错误信息,作为html页面返回给客户端,也就是喜闻乐见的Django报错的大黄页了。
但对于前后端分离的项目中,通常都是json数据作为数据传输,你给个报错的大黄页,就不太合理了。因此为了避免出现这种情况,我们可以自定义一个属于自己的异常处理函数,对于drf无法处理的异常,我们自己编写异常处理的代码逻辑。
一些铺垫
isinstance
在restframework框架中,用到了isinstance来判断,一个对象所属于哪个类的实例化对象,这在继承中同样适用。
class Base:
pass
class Foo(Base):
pass
obj = Foo()
print(isinstance(obj, Foo)) # True
print(isinstance(obj, Base)) # True
关于视图
restframework框架的视图类都是APIView的子类,而APIView则是Django内置的View的子类。
class View: # Django的视图类
pass
class APIView(View): # restframework框架的视图类
pass
class UserViewSet(APIView): # 我们自己写的视图类包括其它诸如ModelViewSet也继承的是APIView
pass
关于返回response类
restframework框架中的Response类是Django的HttpResponseBase类的子类。
class HttpResponseBase: # Django的返回对象的类的基类
pass
class HttpResponse(HttpResponseBase): # HttpResponseBase的子类
pass
class JsonResponse(HttpResponse): # HttpResponse的子类
pass
class SimpleTemplateResponse(HttpResponse): # HttpResponse的子类
pass
class Response(SimpleTemplateResponse): # 这个是restframework框架常用的Response类,它本质上也是HttpResponse的子类
pass
关于异常类
restframework框架实现了自己的异常类APIException,其它细分的异常类如PermissionDenied、NotAuthenticated的异常类都是APIException的子类,而APIException异常类则是Python的Exception异常类的子类。
class Exception: # Python 内置异常类
pass
class APIException(Exception): # restframework框架异常类的父类
pass
class PermissionDenied(APIException): # restframework框架的各个细分类都是APIException的子类
pass
class NotAuthenticated(APIException): # restframework框架的各个细分类都是APIException的子类
pass
另外,在restframework框架中,框架本身实现了一些异常处理,那么对于框架之外的异常,比如需要根据业务需求返回不同的状态码、与第三方的连接出现了问题,比如数据库连接异常、短信接口异常,等等这些都不属于restframework框架本身的异常,当然restframework框架也没有实现相关的异常类。
所以我们在开发中要对各种异常进行统一处理,让返回结果具有一致性,遇到了问题也能通过错误状态码能及时的排查。
关于restframework框架内置的异常类
在restframework框架中,它内置了一些异常类,这些异常类在下面这个文件中可以找到:
from rest_framework import exceptions
异常类 | 描述 |
---|---|
APIException | restframework框架中所有异常类的父类,而它又是Exception的子类 |
ParseError | 解析错误 |
AuthenticationFailed | 认证失败 |
NotAuthenticated | 尚未认证 |
PermissionDenied | 权限拒绝 |
NotFound | 404 未找到 |
MethodNotAllowed | 请求方式不支持 |
NotAcceptable | 要获取的数据格式不支持 |
Throttled | 超过限流次数 |
ValidationError | 校验失败 |
也就是说,很多的没有在上面列出来的异常,就需要我们在自定义异常中自己处理了。
关于assert断言
断言通常在测试中写测试用例用的比较多,比如写个测试用例对于一个接口进行测试,接口返回的状态码是200是我们的预期,那么在用例中就可以通过断言来做,如果指定接口返回的状态码是200,则说明用例测试通过,否则就是测试失败。
import pytest # pip install pytest
import requests
def get():
response = requests.get('http://httpbin.org/get')
return response.status_code
def test_case01():
code = get()
assert (code, 200)
def test_case02():
code = get()
assert (code, 201)
if __name__ == '__main__':
pytest.main(["-s", "test.py"])
当然了,来单独看看assert这个知识点:
import random
def always_true():
return random.choice([True, False]) == True
# 如果这个函数返回了True,测试就会通过,通过的话就什么也不做
# 否则就抛出异常AssertionError: This should be True
# 你可以尝试多次运行这个测试,看看是否会偶尔通过
# 关于assert的具体用法
# 第一个参数是一个布尔值,如果为 False 就会抛出异常
# 第二个参数是具体的异常信息,可以任意指定
assert always_true(), 'This should be True'
源码梳理
drf在处理一个请求时,都会走APIView视图类的dispatch方法。这个方法中处理包括请求的封装,认证、权限、限流、版本,也包括对于异常的处理。
"""
观察APIView中的dispatch做了什么
"""
# 位置
# from rest_frameworkviews import APIView
class APIView(View):
def dispatch(self, request, *args, **kwargs):
# 封装request对象
request = self.initialize_request(request, *args, **kwargs)
# 异常处理从下面的try开始
try:
# 在initial中处理版本、认证、权限、限流
self.initial(request, *args, **kwargs)
# 根据请求方式反射让我们自己的写的视图类的对应的方法执行
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# 核心代码:请求正常,拿到的response
response = handler(request, *args, **kwargs)
except Exception as exc:
# 核心代码:对于请求异常的,都走这个分支,对异常进行处理
response = self.handle_exception(exc)
# 核心代码:最终走finalize_response方法对于响应结果进行统一化处理,然后返回给客户端
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
"""
从APIView中的dispatch中,对于异常的处理,走了self.handle_exception方法,我们观察这个方法做了什么
class APIView(View):
def dispatch(self, request, *args, **kwargs):
try:
pass
except Exception as exc:
# 对于请求异常的,都走这个分支,对异常进行处理
response = self.handle_exception(exc)
handle_exception方法本质上就做了:
1. 对于认证相关的异常进行响应头和状态码的处理
2. 调用具体的异常处理函数对错误信息进行处理,最终生成响应对象response
3. 对于restframework框架处理不了的异常,直接交给Django处理
"""
# 位置
# from rest_frameworkviews import APIView
class APIView(View):
def handle_exception(self, exc):
# 对于认证相关的异常进行响应头和状态码的处理
if isinstance(exc, (exceptions.NotAuthenticated,
exceptions.AuthenticationFailed)):
# WWW-Authenticate header for 401 responses, else coerce to 403
# 设置401或者403的响应头和状态码
auth_header = self.get_authenticate_header(self.request)
if auth_header:
exc.auth_header = auth_header
else:
exc.status_code = status.HTTP_403_FORBIDDEN
# 核心代码:调用并执行处理异常,生成Response的函数
exception_handler = self.get_exception_handler() # 找到那个函数
context = self.get_exception_handler_context() # 封装字典,包含后续异常处理中可能需要的,比如request对象,各种参数
response = exception_handler(exc, context) # 调用那个函数,拿到最终封装好的response
# 如果exception_handler函数的返回值是None,表示异常类型不是restframework框架能处理的了的异常,那就表示这个异常
# 需要交给Django来处理了,就走下面的if语句了
if response is None:
# Django处理异常直接就报错返回大黄页了,这个方法执行到此结束,就走不到下面那个return response了
self.raise_uncaught_exception(exc)
# 如果能走到这里,一定拿到了exception_handler函数封装后并返回response对象
# 这里再给它添加个exception等于True的属性,然后返回,表示异常处理restframework处理完了
# 将最后封装好的response对象给客户端返回
response.exception = True
return response
"""
从APIView中的handle_exception方法中,执行了self.get_exception_handler()方法,我们看下这个方法内部实现
class APIView(View):
def handle_exception(self, exc):
# 核心代码:调用并执行处理异常,生成Response的函数
exception_handler = self.get_exception_handler() # 找到那个函数
"""
# 位置
# from rest_frameworkviews import APIView
class APIView(View):
def get_exception_handler(self):
# 这里就把默认的异常处理函数进行返回,未来我们自己定义异常处理函数时,也就是覆盖掉它这里拿到的异常处理函数即可
return self.settings.EXCEPTION_HANDLER
"""
从APIView中的get_exception_handler方法拿到的具体函数就来自于restframework框架对于异常处理的默认的配置项
"""
# 位置
from rest_framework.settings import DEFAULTS # DEFAULTS是个字典,它内部存储的是restframework框架内置的一些默认配置
DEFAULTS = {
...省略...
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
...省略...
}
"""
我们来看看这个默认的异常函数都做了什么吧
本质上来说,默认的异常函数就是对特殊的认证和权限做一些额外的设置、然后判断异常类型是否是restframework框架能处理的了得异常,能处理的了,则封装一下相应的错误信息,如果处理,直接返回None,也就是我不处理了
而我们将来要做的就是重写这个异常函数,将restframework框架能处理的和不能处理的异常,都在这里进行处理好
"""
# 位置
from rest_framework.views import exception_handler
def exception_handler(exc, context):
"""
exc: 异常实例对象,发生异常时实例化出来的
context: 字典,异常发生时python解释器收集的执行上下文信息。所谓的执行上下文就是python解释器
执行代码时保存在内存中的变量、函数、类、对象、模块等一系列的信息组成的环境信息
"""
if isinstance(exc, Http404): # 当数据没有拿到时的异常
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied): # 权限异常
exc = exceptions.PermissionDenied()
# 因为restframework中所有的异常都继承自APIException,所以走这个
# 分支表示异常都是restframework框架内置异常类能处理的了的异常
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
# exc.detail就是我们异常类传的字符串类型的值,比如ValidationError("手机号格式不正确!")
# 当然也有可能是这么传的ValidationError([xxx, xxx])或者 ValidationError({"k": "v"})
# 下面的判断就是根据传的值不同,做不同的封装,客户端看到的也就不一样了
if isinstance(exc.detail, (list, dict)): # 如果是列表或者字典,用户看到的就是你传的列表或者字典
data = exc.detail
else: # 如果异常类传的是字符串,就封装成字典,例如: {"detail": "手机号格式不正确!"}
data = {'detail': exc.detail}
# 执行一些事务的回滚操作
set_rollback()
# 最终生成一个response对象再返回给客户端
return Response(data, status=exc.status_code, headers=headers)
# 对于restframework框架处理不了的异常,比如int("abc")这种报错,直接返回None,表示我不处理了,就会冒泡交给Django处理了
# 当然了,Django处理就会来个大黄页....
return None
"""
这里得到APIView中dispatch方法执行下面代码拿到的response,进行最后的封装处理,才返回给客户端
class APIView(View):
def dispatch(self, request, *args, **kwargs):
try:
# 核心代码:请求正常,拿到的response
response = handler(request, *args, **kwargs)
except Exception as exc:
# 核心代码:对于请求异常的,都走这个分支,对异常进行处理
response = self.handle_exception(exc)
# 核心代码:最终走finalize_response方法对于响应结果进行统一化处理,然后返回给客户端
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
整体来说,这个finalize_response方法主要作用就是:
1. 通过断言检查相应对象是否是HttpResponseBase子类实例化的对象
2. 如果response对象是Response的实例化对象的话,就去根据请求对象request,去判断该如何渲染响应结果
例如,如果是浏览器访问的接口,restframework框架就会返回一个调试页面,把相呼应结果嵌套到调试页面中
如果是其他类型的请求,比如是postman请求的,就直接返回json数据了
3. 对响应头做一些处理
当代码走完了了finalize_response,那么对于响应对象response的处理就算是完事了,可以返回给客户端了
"""
# 位置
# from rest_frameworkviews import APIView
class APIView(View):
def finalize_response(self, request, response, *args, **kwargs):
# 这个assert语法就为了判断拿到的response是不是HttpResponseBase子类实例化的对象,是就什么也不错
# 如果不是,则说明response是个野生的response对象,人家不认,就直接报错了
assert isinstance(response, HttpResponseBase), (
'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` '
'to be returned from the view, but received a `%s`'
% type(response)
)
# 如果这个response是Response的实例化对象,就添加一些额外的信息
if isinstance(response, Response):
# 去找下request对象中有没有accepted_renderer,也就是决定如何渲染响应结果
if not getattr(request, 'accepted_renderer', None):
neg = self.perform_content_negotiation(request, force=True)
request.accepted_renderer, request.accepted_media_type = neg
# restframework框架内置rest风格调试页面,下面这个response.accepted_renderer就能知道本次请求的客户端是什么
#比如浏览器发送的请求,相应内容就会呈现在页面中
# response.accepted_renderer: <rest_framework.renderers.BrowsableAPIRenderer object at 0x00000247CF7B30D0>
# response.accepted_media_type: text/html
# response.renderer_context: {'view': <apps.drf.views.exc.ExceptionTestView object at 0x00000247CF7B0F90>, 'args': (), 'kwargs': {}, 'request': <rest_framework.request.Request: GET '/drf/exc/'>}
# 如果是其他客户端比如postman发送的请求,则会直接返回json类型的响应数据
# response.accepted_renderer: <rest_framework.renderers.JSONRenderer object at 0x00000247CF670D90>
# response.accepted_media_type: application/json
# response.renderer_context: {'view': <apps.drf.views.exc.ExceptionTestView object at 0x00000247CF7BB410>, 'args': (), 'kwargs': {}, 'request': <rest_framework.request.Request: GET '/drf/exc/'>}
response.accepted_renderer = request.accepted_renderer
response.accepted_media_type = request.accepted_media_type
response.renderer_context = self.get_renderer_context()
# 下面这几行就是处理响应头
vary_headers = self.headers.pop('Vary', None)
if vary_headers is not None:
patch_vary_headers(response, cc_delim_re.split(vary_headers))
for key, value in self.headers.items():
response[key] = value
# 最终将response返回给客户端
return response
自定义异常函数
通过上面的源码梳理,我们知道,restframework框架对于不能处理的异常是直接不管的,交给了Django来做,而我们就需要去重写这个异常函数,去完善异常处理逻辑,把项目中所有的异常都能进行统一处理才是一个比较合理的状态。
那么怎么来做呢?需要两步。
1. 自定义异常处理函数
我在项目的根目录下创建了一个utils
目录,在这个目录内创建一个exc_exceptions.py
,当然你可以创建在别的目录和器其他的文件名称,这都无所谓的。
重要的是异常函数你要根据具体的项目需求进行设计:
from django.core.exceptions import PermissionDenied
from django.http.response import Http404
from rest_framework import status
from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.views import set_rollback
# 把常见的异常都在字典中进行映射,每个异常都有对应的错误码和错误信息,将来遇到新的异常,也都可以添加到这个字典中
# 至于异常对应的字典中,想要给客户端返回什么信息,都可以很自由根据需求来添加即可
errors_dict = {
"ValueError": {"error_code": 2001, "error_xxx": "ooo"},
"ZeroDivisionError": {"error_code": 2002},
"Http404": {"error_code": 1001},
"PermissionDenied": {"error_code": 1002},
"AuthenticationFailed": {"error_code": 1003},
"MethodNotAllowed": {"error_code": 1004},
}
def my_exception_handler(exc, context):
"""
自定义异常处理函数
:param exc: 异常类型对象
:param context: 抛出异常的上下文,是个字典,异常发生时python解释器收集的执行上下文信息。
所谓的执行上下文就是python解释器在执行代码时保存在内存中的
变量、函数、类、对象、模块等一系列的信息组成的环境信息。
:return: 封装好的Response响应对象
"""
# 定义异常返回的响应data,关于异常的所有要返回的信息,都往这个data字典中添加就行了
data = {}
# 定义一个响应头
headers = {}
# 拿到具体异常类的类名,方便去全局异常字典中提取对应的异常错误码和错误信息
exc_class_name = str(exc.__class__.__name__)
# 这几个if判断是restframework框架自己对几个特殊的异常进行的特殊处理
if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
# 如果这个是restframework框架定义的异常,做的进一步封装
if isinstance(exc, exceptions.APIException):
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
# 这里把restframework框架定义的异常的错误信息,添加到data字典中
data['error_detail'] = exc.detail
set_rollback() # 事务的回滚就由restframework框架来处理,我们不动
# 对于restframework框架处理不了的异常,我们都在这里处理
# 从字典中找到我们对应的异常类型,如果存在,返回对应的错误信息
if errors_dict.get(exc_class_name): # 去字典中看下程序报错的异常,我们有没有提前收录到字典中
# 把字典中收录的异常初始定义的错误码和错误信息,添加到data字典中
data.update(errors_dict.get(exc_class_name, {}))
else: # 对于我们没有在字典中收录的异常,都走这里统一处理就完了
# code是-1表示这个异常我们没有提前收录到字典中,错误码都统一为-1,你可以根据需求指定其他的错误码
data['error_code'] = -1
# 添加异常报错内容
data['error_detail'] = str(exc)
# 添加异常报错位置,想要再添加其它信息,你可以print一下context,看下哪些对你有用
data['error_context'] = str(context['view'])
# 添加下异常类型
data['error_type'] = exc_class_name
# 最终将异常对象封装好了,返回给调用者,也就是源码的handle_exception方法
return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR, headers=headers)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import PermissionDenied, ParseError, MethodNotAllowed
class ExceptionTestAuth(BaseAuthentication):
def authenticate(self, request):
int('adad')
# 1 / 0
# raise AuthenticationFailed("认证失败")
# return None
class ExceptionTestView(APIView):
authentication_classes = [ExceptionTestAuth]
def get(self, request):
return Response("OK")
{
"error_code": 2001,
"error_xxx": "ooo",
"error_detail": "invalid literal for int() with base 10: 'adad'",
"error_context": "<apps.drf.views.exc_view.ExceptionTestView object at 0x000001DAF231B0D0>",
"error_type": "ValueError"
}
2. 在settings中的restframework框架配置中进行应用
# drf相关配置以后编写在下面的字典中
REST_FRAMEWORK = {
"EXCEPTION_HANDLER": "utils.exc_exceptions.my_exception_handler",
}
其它的思路
除了上面的实现思路之外,自定义异常函数你也可以这样处理:
from django.http import Http404
from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import Throttled
from rest_framework.exceptions import PermissionDenied
from rest_framework.exceptions import NotAuthenticated
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.views import set_rollback
from rest_framework.exceptions import APIException
class ExtraException(APIException):
pass
def exception_handler(exc, context):
if isinstance(exc, Http404):
exc = exceptions.NotFound()
exc.ret_code = 2001
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
exc.ret_code = 2002
elif isinstance(exc, (AuthenticationFailed, NotAuthenticated)):
exc.ret_code = 2003
elif isinstance(exc, Throttled):
exc.ret_code = 2004
elif isinstance(exc, ValidationError):
exc.ret_code = 2005
# ...
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
# if isinstance(exc.detail, (list, dict)):
# data = exc.detail
# else:
# exc_code = getattr(exc, 'ret_code', None) or -1
# data = {'code': exc_code, 'detail': exc.detail}
exc_code = getattr(exc, 'ret_code', None) or -1
data = {'code': exc_code, 'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
# return None
data = {'code': -1, 'detail': str(exc)}
return Response(data, status=500)
也可以这样:
from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status
from django.db import DatabaseError
# 针对mysql、mongoDB、redis、第三方数据接口
def custom_exception_handler(exc, context):
"""
自定义异常函数
exc: 异常实例对象,发生异常时实例化出来的
context: 字典,异常发生时python解释器收集的执行上下文信息。
所谓的执行上下文就是python解释器在执行代码时保存在内存中的变量、函数、类、对象、模块等一系列的信息组成的环境信息。
"""
response = exception_handler(exc, context)
print(f"context={context}")
if response is None:
"""当前异常,drf无法处理"""
view = context["view"] # 获取异常发生时的视图类
request = context["request"] # 获取异常发生时的客户端请求对象
if isinstance(exc, ZeroDivisionError):
response = Response({"detail":"数学老师还有30秒达到战场,0不能作为除数!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
if isinstance(exc, TypeError):
print('[%s]: %s' % (view, exc))
response = Response({'detail': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return response
自定义异常类
有的场景中,我们可以通过自定义异常类,来简化业务代码,比如支付相关,可以根据业务需求来主动触发异常。
"""
可以将自定义的异常类在这个文件中实现
"""
from django.core.exceptions import PermissionDenied
from django.http.response import Http404
from rest_framework import status
from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.views import set_rollback
from rest_framework.exceptions import APIException
# 把常见的异常都在字典中进行映射,每个异常都有对应的错误码和错误信息,将来遇到新的异常,也都可以添加到这个字典中
# 至于异常对应的字典中,想要给客户端返回什么信息,都可以很自由根据需求来添加即可
errors_dict = {
"ValueError": {"error_code": 2001, "error_xxx": "ooo"},
"ZeroDivisionError": {"error_code": 2002},
"Http404": {"error_code": 1001},
"PermissionDenied": {"error_code": 1002},
"AuthenticationFailed": {"error_code": 1003},
"MethodNotAllowed": {"error_code": 1004},
"PayException": {"error_code": 3001}, # 为所有支付相关的异常指定错误码
}
class PayException(APIException): # 继承APIException即可
""" 自定义支付相关的异常类 """
pass
class XxxException(APIException): # 想为某个业务整个异常类,都直接定义即可,非常方便
""" 自定xxx相关的异常类 """
pass
def my_exception_handler(exc, context):
"""
自定义异常处理
:param exc: 异常类型
:param context: 抛出异常的上下文,字典,异常发生时python解释器收集的执行上下文信息。
所谓的执行上下文就是python解释器在执行代码时保存在内存中的变量、函数、类、对象、模块等一系列的信息组成的环境信息。
:return: Response响应对象
"""
# 定义异常返回的响应data,关于异常的所有要返回的信息,都往这个data字典中添加就行了
data = {}
# 定义一个响应头
headers = {}
# 拿到具体异常类的类名,方便去全局异常字典中提取对应的异常错误码和错误信息
exc_class_name = str(exc.__class__.__name__)
# 这几个if判断是restframework框架自己对几个特殊的异常进行的特殊处理
if isinstance(exc, Http404):
exc = exceptions.NotFound()
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
# 如果这个是restframework框架定义的异常,做的进一步封装
if isinstance(exc, exceptions.APIException):
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
# 这里把restframework框架定义的异常的错误信息,添加到data字典中
data['error_detail'] = exc.detail
set_rollback() # 事务的回滚就由restframework框架来处理,我们不动
# 对于restframework框架处理不了的异常,我们都在这里处理
# 从字典中找到我们对应的异常类型,如果存在,返回对应的错误信息
if errors_dict.get(exc_class_name): # 去字典中看下程序报错的异常,我们有没有提前收录到字典中
# 把字典中收录的异常初始定义的错误码和错误信息,添加到data字典中
data.update(errors_dict.get(exc_class_name, {}))
else: # 对于我们没有在字典中收录的异常,都走这里统一处理就完了
# code是-1表示这个异常我们没有提前收录到字典中,需要我们统一处理
data['error_code'] = -1
# 添加异常报错内容
data['error_detail'] = str(exc)
# 添加异常报错位置,想要再添加其它信息,你可以print一下context,看下哪些对你有用
data['error_context'] = str(context['view'])
# 添加下异常类型
data['error_type'] = exc_class_name
# 最终将异常对象封装好了,返回给调用者,也就是源码的handle_exception方法
return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR, headers=headers)
"""
在视图类中,可以根据业务需要,主动触发自定义的异常类,传递具体的错误信息
"""
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import PermissionDenied, ParseError, MethodNotAllowed
from utils.exc_exceptions import PayException
class ExceptionTestAuth(BaseAuthentication):
def authenticate(self, request):
# int('adad')
# 1 / 0
# raise AuthenticationFailed("认证失败")
return None
class ExceptionTestView(APIView):
authentication_classes = [ExceptionTestAuth]
def get(self, request):
# 原来如果用户支付时余额不足,我们可以直接return response对象
# return Response({"code": 3001, "msg": "余额不足"})
# 如果用自定义异常类的话,可以根据具体的业务需求来主动触发异常
# if xxx:
# raise PayException('余额不足')
# if xxx:
# raise PayException("查询的订单不存在")
# if ooo:
# raise PayException("订单状态不正确")
return Response("OK")
{
"error_detail": "余额不足",
"error_code": 3001,
"error_context": "<apps.drf.views.exc_view.ExceptionTestView object at 0x0000023086E02A90>",
"error_type": "PayException"
}