A brief practice of implementing retry mechanism in python

Posted by domwells27 on Tue, 09 Nov 2021 06:29:17 +0100

Recently, when writing interface test scripts, I encountered the following test scenario

1. System A will create A piece of data, which will be pushed to system B after successful creation;

2. Due to the communication between the two systems, the data will not be synchronized from system A to system B immediately, with A short time difference;

There are two interfaces I want to debug. One is to call an interface in system A to generate data; Second, system B calls another interface to process data.

After the actual operation, I found A problem: after calling interface A, interface B will be called immediately. From the code level, this time difference is very short. If the data has been generated by system A, but has not been pushed to system B in such A short time, resulting in the failure to find this data when calling interface B, an error will be reported

First solution

The initial solution is to use time.sleep(). After calling interface A, wait for A period of time, such as time.sleep(5), wait for 5s, and then call interface B

Because after waiting for 5s, the data can generally be pushed from system A to system B

Of course, if it is not synchronized to system B after 5s, an error will still be reported when calling the B interface, so this is not a good solution

Second solution

After surfing the Internet, I found that python has a library that can implement the retry mechanism: tenacity

Here are some reference blogs found. You can see the basic usage: https://www.cnblogs.com/wuzhibinsuib/p/13443622.html

Next, let's talk about our experimental results and understanding

Its simple usage is to add the @ retry modifier to the code that needs to be retried. The exception thrown by the code will be caught by the decorator and retried

The key here is to catch the exception thrown by the code

 

Example 1 [ if an error is reported, the system will try again all the time ]

@retry
def test_retry1():
    print("Wait for retry.....")
    raise Exception  # adopt raise Returns an error directly

@retry
def test_retry2():
    print("Wait for retry.....")
    return "hello" + 1  # A man-made error. Here I add strings and integers. Because of different types, an error will be reported, so a retry will be triggered

After the above two codes are run, the "wait for retry" will be printed until the operation is stopped manually

 

Example 2 [set the maximum number of retries stop_after_attempt]

@retry(stop=stop_after_attempt(5))
def test_retry():
    print("Wait for retry.....")
    return "hello" + 1 

Receive with stop   stop_after_attempt: when retrying the specified number of times, the retrying ends, as follows: 5 retries

 

Example 3 [set the maximum restart time. If it fails, retry for 5s]  

@retry(stop=stop_after_delay(5))
def test_retry():
    print("Wait for retry.....")
    return "hello" + 1 

 

Example 4 [ retry after specific error ]

@retry(retry=retry_if_exception_type(TypeError))
def test_retry1():
    print("Wait for retry.....")
    return "hello" + 1  # Capture type error. Try again when type error occurs

@retry(retry=retry_if_exception_type(SyntaxError))
def test_retry2():
    print("Wait for retry.....")
    raise SyntaxError  # Catch syntax errors and try again when syntax errors occur

 

Example 5 [ retry after meeting the customized conditions ]

# First, a function is defined symbol,Its function is to judge whether the incoming value is None;It returns a Boolean value if the result value=None,Then return true,Otherwise return False
def symbol(value):
    return value is None


# In the decorator retry=retry_if_result(symbol),Express handle test_retry The result of the function is passed in symbol,judge test_retry Is the result of None,
# If = None, retry. If it is not equal to None, end and return the function value (so the condition for retry is whether the result of test_retry is the result defined by the condition function)
@retry(stop=stop_after_attempt(3), retry=retry_if_result(symbol), reraise=True) def test_retry(): print("Wait for retry.....") return None
The symbol() function is a defined conditional function, test_ The retry () function is the function that you want to retry. It is passed through the retry in the decorator_ if_ Result (), see the comments of the above code for the specific meaning

Next, start processing my interface test script and try again using the custom conditions in example 5 above

First, handle the method that needs to be retried. I specify that when this method does not receive the pushed data, it returns None

    def seal_regist(code):

        seal_data = self.get_seal_data(code)

        try:
            if seal_data["data"]["list"]:

                r = requests.post(url, json=payload, headers=headers)
                return r.json()

            else:
                print("There is no data to be printed in the list{},Please check the system~".format(code))
                return None

        except Exception as e:
            raise e

Define a conditional function

def test_retry(value):
    """Retry condition function"""
    return value is None

to   seal_ Register() function plus retry decorator

@retry(stop=stop_after_delay(10), retry=retry_if_result(test_retry))
    def seal_regist(code):
      .....
      .....

If   seal_ If register() returns None, retry. The maximum retry time is 10s

ps. because the login cookie is required in the retry function, the method of obtaining the login cookie was written in before. However, if the retry mechanism is added, the login cookie will be obtained again when the retry starts, indicating that the login is frequent and the login interface call fails. Therefore, in order to avoid this situation, I put the method of obtaining the login cookie outside, In this way, no matter how many times you retry, you can use a cookie obtained at the beginning (so if you encounter a situation similar to mine, put out those methods that only need to obtain data once to avoid repeated requests and exceptions caused by the interface)