Search code examples
pythonpyserialpython-decorators

Implementing retry decorator one method higher than exception


I am trying to implement the retry decorator on a serial query. A general idea of my code is shown below. I am struggling to get it to retry when the decorator is one method up in the hierarchy. How can I have the method be retried when it's one method up from the method that throws the exception?

One complication that is frustrating is my increment time per retry depends on the actual command. Some commands require more time than others. That's why I have the extra_time_per_retry passed in, and couldn't implement the retry decorator using the traditional @retry style.

FYI the _serial is created in the class on init via pySerial.

I got it to work with the retry decorator directly above the method that throws the exception. I would like it to be two above to keep my code clean.

I have tried feeding the retry decorator the exact exception type, but couldn't get it to work.

def _query_with_retries(self, cmd, extra_time_per_retry):
    _retriable_query = retry(stop_max_attempt_number=5,
                             wait_incrementing_start=self._serial.timeout + extra_time_per_retry,
                             wait_incrementing_increment=10)(self._query)
    return _retriable_query(cmd)

def _query(self, cmd):
    cmd_msg = cmd + '\r'
    self._serial.reset_input_buffer()
    self._serial.reset_output_buffer()
    self._serial.write(cmd_msg)

    return self._readlines()

def _readlines(self):
    response_str = self._serial.read_until('\r', 256)  # Max 256 bytes

    # Parse response here, if a bad one set bad_response = true

    if bad_response:
        raise ResponseError("Response had custom error xyz")

Solution

  • I guess you could pack your call to _readlines() into an exception handling block, reraising the error:

    python 3.x

    @retry
    def _query(self, cmd):
        cmd_msg = cmd + '\r'
        self._serial.reset_input_buffer()
        self._serial.reset_output_buffer()
        self._serial.write(cmd_msg)
    
        try:
            answer = self._readlines()
        except Exception as e:
            raise e
        return answer
    

    python 2.x

    @retry
    def _query(self, cmd):
        cmd_msg = cmd + '\r'
        self._serial.reset_input_buffer()
        self._serial.reset_output_buffer()
        self._serial.write(cmd_msg)
    
        try:
            answer = self._readlines()
        except Exception:
            t, v, tb = sys.exc_info()
            raise t, v, tb
        return answer
    

    This way, you catch the exception directly when it occurs, and raise it inside the method which will be retried. I am not sure whether this declutters enough for you, however it should work.

    Some might complain about using a blank except Exception, however since I am reraising it always immediately, I do not see any harm.