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

1#!/usr/bin/python 

2 

3# SPDX-FileCopyrightText: 2023-2026 Arcangelo Massari <arcangelo.massari@unibo.it> 

4# 

5# SPDX-License-Identifier: ISC 

6 

7from __future__ import annotations 

8 

9import random 

10import time 

11from typing import Callable, TypeVar 

12 

13T = TypeVar("T") 

14 

15 

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. 

28 

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 

41 

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 ) 

52 

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) 

63 

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")