Coverage for rdflib_ocdm / retry_utils.py: 91%
24 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-30 21:23 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-30 21:23 +0000
1#!/usr/bin/python
3# SPDX-FileCopyrightText: 2023-2026 Arcangelo Massari <arcangelo.massari@unibo.it>
4#
5# SPDX-License-Identifier: ISC
7from __future__ import annotations
9import random
10import time
11from typing import Callable, TypeVar
13T = TypeVar("T")
16def execute_with_retry(
17 func: Callable[..., T],
18 *args: object,
19 max_retries: int = 5,
20 base_wait_time: float = 1,
21 reporter: object | None = None,
22 **kwargs: object,
23) -> T:
24 """
25 A function that executes the given function with retry logic
26 and exponential backoff. This is useful when you can't use the
27 decorator directly.
29 :param func: The function to execute with retry logic
30 :param args: Positional arguments to pass to the function
31 :param max_retries: Maximum number of retry attempts before
32 giving up
33 :param base_wait_time: Initial wait time in seconds, which will
34 be increased exponentially
35 :param reporter: Optional reporter object with add_sentence
36 method for logging
37 :param kwargs: Keyword arguments to pass to the function
38 :return: The result of the function call
39 """
40 retry_count = 0
42 while retry_count <= max_retries: 42 ↛ 70line 42 didn't jump to line 70 because the condition on line 42 was always true
43 try:
44 return func(*args, **kwargs)
45 except Exception as e:
46 retry_count += 1
47 if retry_count <= max_retries:
48 # Calculate wait time with exponential backoff and some randomness
49 wait_time = (base_wait_time * (2 ** (retry_count - 1))) + (
50 random.random() * 0.5
51 )
53 # Log the retry attempt
54 message = (
55 f"Query attempt {retry_count}/{max_retries}"
56 f" failed: {e}."
57 f" Retrying in {wait_time:.2f} seconds..."
58 )
59 if reporter is not None and hasattr(reporter, "add_sentence"):
60 reporter.add_sentence(message) # type: ignore[attr-defined]
61 else:
62 print(message)
64 time.sleep(wait_time)
65 else:
66 error_message = f"Failed after {max_retries} attempts: {e}"
67 if reporter is not None and hasattr(reporter, "add_sentence"): 67 ↛ 69line 67 didn't jump to line 69 because the condition on line 67 was always true
68 reporter.add_sentence(f"[ERROR] {error_message}") # type: ignore[attr-defined]
69 raise ValueError(error_message)
70 raise ValueError(f"Failed after {max_retries} attempts")