Coverage for rdflib_ocdm/retry_utils.py: 94%
24 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-11-01 22:02 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-11-01 22:02 +0000
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3# Copyright 2023 Arcangelo Massari <arcangelo.massari@unibo.it>
4#
5# Permission to use, copy, modify, and/or distribute this software for any purpose
6# with or without fee is hereby granted, provided that the above copyright notice
7# and this permission notice appear in all copies.
8#
9# THE SOFTWARE IS PROVIDED 'AS IS' AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
12# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
13# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15# SOFTWARE.
17from __future__ import annotations
19import random
20import time
21from functools import wraps
22from typing import Any, Callable, Optional, TypeVar, Union
24T = TypeVar('T')
27def execute_with_retry(func: Callable[..., T], *args: Any, max_retries: int = 5,
28 base_wait_time: int = 1, reporter: Any = None, **kwargs: Any) -> T:
29 """
30 A function that executes the given function with retry logic and exponential backoff.
31 This is useful when you can't use the decorator directly.
33 :param func: The function to execute with retry logic
34 :param args: Positional arguments to pass to the function
35 :param max_retries: Maximum number of retry attempts before giving up
36 :param base_wait_time: Initial wait time in seconds, which will be increased exponentially
37 :param reporter: Optional reporter object with add_sentence method for logging
38 :param kwargs: Keyword arguments to pass to the function
39 :return: The result of the function call
40 """
41 retry_count = 0
43 while retry_count <= max_retries: 43 ↛ exitline 43 didn't return from function 'execute_with_retry' because the condition on line 43 was always true
44 try:
45 return func(*args, **kwargs)
46 except Exception as e:
47 retry_count += 1
48 if retry_count <= max_retries:
49 # Calculate wait time with exponential backoff and some randomness
50 wait_time = (base_wait_time * (2 ** (retry_count - 1))) + (random.random() * 0.5)
52 # Log the retry attempt
53 message = f"Query attempt {retry_count}/{max_retries} failed: {e}. Retrying in {wait_time:.2f} seconds..."
54 if reporter and hasattr(reporter, 'add_sentence'):
55 reporter.add_sentence(message)
56 else:
57 print(message)
59 time.sleep(wait_time)
60 else:
61 # All retries failed
62 error_message = f"Failed after {max_retries} attempts: {e}"
63 if reporter and hasattr(reporter, 'add_sentence'): 63 ↛ 65line 63 didn't jump to line 65 because the condition on line 63 was always true
64 reporter.add_sentence(f"[ERROR] {error_message}")
65 raise ValueError(error_message)