在异常发生时重试Django视图最简单的方法是什么?

mpgws1up  于 2022-12-30  发布在  Go
关注(0)|答案(2)|浏览(121)

我希望在出现异常时(在我的例子中是底层数据库中的序列化错误)重新运行Django视图函数,并希望它使用完全相同的参数运行,包括相同的request对象-就像客户端重新请求了URL一样。
视图中有许多数据库查询,其中任何一个都可能引发异常--只孤立地重新运行其中一个查询是行不通的,所以我认为需要将整个视图函数 Package 在try/except块和循环中,直到成功。
但是,我有几个视图函数可能会引发这样的异常,所以我希望有一个通用的解决方案。我还希望能够重试一定的次数,然后失败。
有什么简单的解决办法吗?

jum4pzuy

jum4pzuy1#

您可以通过编写一个装饰器来实现这一点:

def retry_on_exception(view):
    def wrapper(*args, **kwargs):
        while True:
            try:
                return view(*args, **kwargs):
            except (TheExceptions, IWant, ToCatch):
                pass
    return wrapper

并在视图上使用此命令:

@retry_on_exception
def my_view(request, foo, bar):
    return HttpResponse("My stuff")

显然,这将无限期地重试,因此许多逻辑可以改进,您也可以编写装饰器来接受它想要查找的异常,因此您可以为每个视图定制它。

rdrgkggo

rdrgkggo2#

您可以使用下面的retry()来重试视图零次或多次,间隔秒数为零或更多:

from django.db import DatabaseError
from time import sleep

def retry(count, interval=1):
    def _retry(func):
        def core(*args, **kwargs):
            nonlocal count
            if callable(count):
                count = 2
            for _ in range(count+1):
                try:
                    return func(*args, **kwargs)
                except DatabaseError as e:
                    print(e)
                    sleep(interval)
        return core
    
    if callable(count):
        return _retry(count)
    else:
        return _retry

例如,我有**Person模型如下。* 我使用PostgreSQL**:

# "store/models.py"

class Person(models.Model):
    name = models.CharField(max_length=30)

然后,除了每次出现**IntegrityError异常时的正常尝试之外,test()视图**(带有以下@retry(4, 2))还可以重试4次(间隔为2秒),如下所示:

# "store/views.py"

from django.db import DatabaseError, IntegrityError, transaction
from time import sleep
from .models import Person
from django.http import HttpResponse

def retry(count, interval=1):
    def _retry(func):
        def core(*args, **kwargs):
            nonlocal count
            if callable(count):
                count = 2
            for _ in range(count+1):
                try:
                    return func(*args, **kwargs)
                except DatabaseError as e:
                    print(e)
                    sleep(interval)
        return core
    
    if callable(count):
        return _retry(count)
    else:
        return _retry

@retry(4, 2) # Here
@transaction.atomic
def test(request):
    qs = Person.objects.get(name="John")
    qs.name = "David"
    qs.save()

    raise IntegrityError("Exception occurs")

    return HttpResponse("Test")

如您所见,除了正常尝试之外,test视图还重试了4次,最后出现了**ValueError异常**,因为返回了None

Exception occurs # Normal try
Exception occurs # 1st retry
Exception occurs # 2nd retry
Exception occurs # 3rd retry
Exception occurs # 4th retry
Internal Server Error: /store/test/
Traceback (most recent call last):
  File "C:\Users\kai\AppData\Local\Programs\Python\Python39\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\kai\AppData\Local\Programs\Python\Python39\lib\site-packages\django\core\handlers\base.py", line 188, in _get_response
    self.check_response(response, callback)
  File "C:\Users\kai\AppData\Local\Programs\Python\Python39\lib\site-packages\django\core\handlers\base.py", line 309, in check_response
    raise ValueError(
ValueError: The view store.views.core didn't return an HttpResponse object. It returned None instead.
[28/Dec/2022 16:42:41] "GET /store/test/ HTTP/1.1" 500 88950

并且,根据下面的PostgreSQL查询日志,事务运行了5次(浅蓝色为正常尝试,黄色为4次重试)。* 您可以检查如何记录PostgreSQL查询

注意,如果装饰器的顺序是@transaction.atomic,则@retry(4, 2)如下所示:

# "store/views.py"

# ...

@transaction.atomic # Here
@retry(4, 2) # Here
def test(request):
    # ...

出现以下错误:
store.models.Person.DoesNotExist:人员匹配查询不存在
因此,装饰器的顺序必须是@retry(4, 2),然后是@transaction.atomic,如下所示:

# "store/views.py"

# ...

@retry(4, 2) # Here
@transaction.atomic # Here
def test(request):
    # ...

默认情况下,@retry会重试**test()视图2次,间隔为1秒,每次出现IntegrityError异常**时,@retry会进行一次正常尝试,如下所示:

# "store/views.py"

# ...

@retry # Here
@transaction.atomic
def test(request):
    # ...

如您所见,除了正常尝试之外,test视图还重试了2次,然后最终出现**ValueError异常**,因为返回了None

Exception occurs # Normal try
Exception occurs # 1st retry
Exception occurs # 2nd retry
Internal Server Error: /store/test/
Traceback (most recent call last):
  File "C:\Users\kai\AppData\Local\Programs\Python\Python39\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\kai\AppData\Local\Programs\Python\Python39\lib\site-packages\django\core\handlers\base.py", line 188, in _get_response
    self.check_response(response, callback)
  File "C:\Users\kai\AppData\Local\Programs\Python\Python39\lib\site-packages\django\core\handlers\base.py", line 309, in check_response
    raise ValueError(
ValueError: The view store.views.core didn't return an HttpResponse object. It returned None instead.
[28/Dec/2022 19:08:07] "GET /store/test/ HTTP/1.1" 500 88950

并且,根据以下PostgreSQL查询日志,事务运行3次(浅蓝色为正常尝试,黄色为2次重试):

另外,如果您想在@retry正常尝试的基础上,以0.5秒的间隔默认重试**test() view**3次,则需要分别将count = 2更改为count = 3,将interval=1更改为interval=0.5,如下所示:

from django.db import DatabaseError
from time import sleep
                 # ↓ Here
def retry(count, interval=0.5):
    def _retry(func):
        def core(*args, **kwargs):
            nonlocal count
            if callable(count):
                count = 3 # <= Here
            for _ in range(count+1):
                try:
                    return func(*args, **kwargs)
                except DatabaseError as e:
                    print(e)
                    sleep(interval)
        return core
    
    if callable(count):
        return _retry(count)
    else:
        return _retry

相关问题