api-to-dataframe 1.2.2__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: api-to-dataframe
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: A package to convert API responses to pandas dataframe
5
- Home-page: https://github.com/IvanildoBarauna/api-to-dataframe
6
5
  License: MIT
7
6
  Author: IvanildoBarauna
8
7
  Author-email: ivanildo.jnr@outlook.com
9
8
  Requires-Python: >=3.9,<4.0
10
- Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Development Status :: 5 - Production/Stable
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: License :: OSI Approved :: MIT License
13
12
  Classifier: Programming Language :: Python :: 3
@@ -16,15 +15,19 @@ Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
18
17
  Classifier: Programming Language :: Python :: 3.8
18
+ Requires-Dist: logging (>=0.4.9.6,<0.5.0.0)
19
19
  Requires-Dist: pandas (>=2.2.2,<3.0.0)
20
20
  Requires-Dist: requests (>=2.32.3,<3.0.0)
21
- Project-URL: Documentation, https://github.com/IvanildoBarauna/api-to-dataframe#readme
21
+ Project-URL: Documentation, https://github.com/IvanildoBarauna/api-to-dataframe/blob/main/README.md
22
+ Project-URL: Homepage, https://pypi.org/project/api-to-dataframe
23
+ Project-URL: ImplementationCase, https://github.com/IvanildoBarauna/ETL-awesome-api
22
24
  Project-URL: Issues, https://github.com/IvanildoBarauna/api-to-dataframe/issues
25
+ Project-URL: Notebook, https://github.com/IvanildoBarauna/api-to-dataframe/blob/main/notebooks/example.ipynb
23
26
  Project-URL: Repository, https://github.com/IvanildoBarauna/api-to-dataframe
24
27
  Description-Content-Type: text/markdown
25
28
 
26
29
  # API to DataFrame
27
- Your solution to convert API responses to Pandas DataFrames with retry strategies and detailed reports.
30
+ Python library that simplifies obtaining data from API endpoints by converting them directly into Pandas DataFrames. This library offers robust features, including retry strategies for failed requests.
28
31
 
29
32
  [![Github](https://img.shields.io/badge/-Go_to_Github_repository-05122A?style=flat&logo=github)](https://github.com/IvanildoBarauna/api-to-dataframe)
30
33
 
@@ -44,18 +47,13 @@ Your solution to convert API responses to Pandas DataFrames with retry strategie
44
47
  ![Python](https://img.shields.io/badge/-Python-05122A?style=flat&logo=python)&nbsp;
45
48
  ![Docker](https://img.shields.io/badge/-Docker-05122A?style=flat&logo=docker)&nbsp;
46
49
  ![Poetry](https://img.shields.io/badge/-Poetry-05122A?style=flat&logo=poetry)&nbsp;
47
- ![GitHub Actions](https://img.shields.io/badge/-GitHub_Actions-05122A?style=flat&logo=githubactions)&nbsp;
50
+ ![GitHub Actions](https://img.shields.io/badge/-GitHub_Actions-05122A?style=flat&logo=githubactions)&nbsp;
48
51
  ![CodeCov](https://img.shields.io/badge/-CodeCov-05122A?style=flat&logo=codecov)&nbsp;
49
52
  ![pypi](https://img.shields.io/badge/-pypi-05122A?style=flat&logo=pypi)&nbsp;
50
53
  ![pandas](https://img.shields.io/badge/-pandas-05122A?style=flat&logo=pandas)&nbsp;
51
54
  ![pytest](https://img.shields.io/badge/-pytest-05122A?style=flat&logo=pytest)&nbsp;
52
55
 
53
56
 
54
-
55
- ## Library Description
56
-
57
- Python library that simplifies obtaining data from API endpoints by converting them directly into Pandas DataFrames. This library offers robust features, including retry strategies for failed requests and automatic generation of detailed reports on the received data.
58
-
59
57
  ## Installation
60
58
 
61
59
  To install the package using pip, use the following command:
@@ -79,29 +77,28 @@ from api_to_dataframe import ClientBuilder, RetryStrategies
79
77
  # Create a client for simple ingest data from API (timeout 1 second)
80
78
  client = ClientBuilder(endpoint="https://api.example.com")
81
79
 
82
- # if you can define timeout with LinearStrategy and set headers:
80
+ # if you can define timeout with LINEAR_RETRY_STRATEGY and set headers:
83
81
  headers = {
84
82
  "application_name": "api_to_dataframe"
85
83
  }
86
84
  client = ClientBuilder(endpoint="https://api.example.com"
87
- ,retry_strategy=RetryStrategies.LinearStrategy
85
+ ,retry_strategy=RetryStrategies.LINEAR_RETRY_STRATEGY
88
86
  ,connection_timeout=2
89
87
  ,headers=headers)
90
88
 
91
89
  """
92
- NOTE: by default the quantity of retries is 3 and the time between retries is 1 second, but you can define manually, like this:
93
-
90
+ NOTE: by default the quantity of retries is 3 and the time between retries is 1 second, but you can define manually.
94
91
  """
95
92
 
96
93
  client = ClientBuilder(endpoint="https://api.example.com"
97
- ,retry_strategy=RetryStrategies.LinearStrategy
94
+ ,retry_strategy=RetryStrategies.LINEAR_RETRY_STRATEGY
98
95
  ,connection_timeout=10
99
96
  ,headers=headers
100
97
  ,retries=5
101
98
  ,initial_delay=10)
102
-
103
99
 
104
- ### timeout, retry_strategy and headers are opcionals parameters
100
+
101
+ ### timeout, retry_strategy and headers are opcionals parameters
105
102
 
106
103
  # Get data from the API
107
104
  data = client.get_api_data()
@@ -1,5 +1,5 @@
1
1
  # API to DataFrame
2
- Your solution to convert API responses to Pandas DataFrames with retry strategies and detailed reports.
2
+ Python library that simplifies obtaining data from API endpoints by converting them directly into Pandas DataFrames. This library offers robust features, including retry strategies for failed requests.
3
3
 
4
4
  [![Github](https://img.shields.io/badge/-Go_to_Github_repository-05122A?style=flat&logo=github)](https://github.com/IvanildoBarauna/api-to-dataframe)
5
5
 
@@ -19,18 +19,13 @@ Your solution to convert API responses to Pandas DataFrames with retry strategie
19
19
  ![Python](https://img.shields.io/badge/-Python-05122A?style=flat&logo=python)&nbsp;
20
20
  ![Docker](https://img.shields.io/badge/-Docker-05122A?style=flat&logo=docker)&nbsp;
21
21
  ![Poetry](https://img.shields.io/badge/-Poetry-05122A?style=flat&logo=poetry)&nbsp;
22
- ![GitHub Actions](https://img.shields.io/badge/-GitHub_Actions-05122A?style=flat&logo=githubactions)&nbsp;
22
+ ![GitHub Actions](https://img.shields.io/badge/-GitHub_Actions-05122A?style=flat&logo=githubactions)&nbsp;
23
23
  ![CodeCov](https://img.shields.io/badge/-CodeCov-05122A?style=flat&logo=codecov)&nbsp;
24
24
  ![pypi](https://img.shields.io/badge/-pypi-05122A?style=flat&logo=pypi)&nbsp;
25
25
  ![pandas](https://img.shields.io/badge/-pandas-05122A?style=flat&logo=pandas)&nbsp;
26
26
  ![pytest](https://img.shields.io/badge/-pytest-05122A?style=flat&logo=pytest)&nbsp;
27
27
 
28
28
 
29
-
30
- ## Library Description
31
-
32
- Python library that simplifies obtaining data from API endpoints by converting them directly into Pandas DataFrames. This library offers robust features, including retry strategies for failed requests and automatic generation of detailed reports on the received data.
33
-
34
29
  ## Installation
35
30
 
36
31
  To install the package using pip, use the following command:
@@ -54,29 +49,28 @@ from api_to_dataframe import ClientBuilder, RetryStrategies
54
49
  # Create a client for simple ingest data from API (timeout 1 second)
55
50
  client = ClientBuilder(endpoint="https://api.example.com")
56
51
 
57
- # if you can define timeout with LinearStrategy and set headers:
52
+ # if you can define timeout with LINEAR_RETRY_STRATEGY and set headers:
58
53
  headers = {
59
54
  "application_name": "api_to_dataframe"
60
55
  }
61
56
  client = ClientBuilder(endpoint="https://api.example.com"
62
- ,retry_strategy=RetryStrategies.LinearStrategy
57
+ ,retry_strategy=RetryStrategies.LINEAR_RETRY_STRATEGY
63
58
  ,connection_timeout=2
64
59
  ,headers=headers)
65
60
 
66
61
  """
67
- NOTE: by default the quantity of retries is 3 and the time between retries is 1 second, but you can define manually, like this:
68
-
62
+ NOTE: by default the quantity of retries is 3 and the time between retries is 1 second, but you can define manually.
69
63
  """
70
64
 
71
65
  client = ClientBuilder(endpoint="https://api.example.com"
72
- ,retry_strategy=RetryStrategies.LinearStrategy
66
+ ,retry_strategy=RetryStrategies.LINEAR_RETRY_STRATEGY
73
67
  ,connection_timeout=10
74
68
  ,headers=headers
75
69
  ,retries=5
76
70
  ,initial_delay=10)
77
-
78
71
 
79
- ### timeout, retry_strategy and headers are opcionals parameters
72
+
73
+ ### timeout, retry_strategy and headers are opcionals parameters
80
74
 
81
75
  # Get data from the API
82
76
  data = client.get_api_data()
@@ -1,37 +1,40 @@
1
1
  [tool.poetry]
2
2
  name = "api-to-dataframe"
3
- version = "1.2.2"
3
+ version = "1.3.0"
4
4
  description = "A package to convert API responses to pandas dataframe"
5
5
  authors = ["IvanildoBarauna <ivanildo.jnr@outlook.com>"]
6
6
  readme = "README.md"
7
7
  license = "MIT"
8
8
  classifiers=[
9
- "Development Status :: 3 - Alpha",
9
+ "Development Status :: 5 - Production/Stable",
10
10
  "Intended Audience :: Developers",
11
11
  "License :: OSI Approved :: MIT License",
12
12
  "Programming Language :: Python :: 3",
13
13
  "Programming Language :: Python :: 3.8",
14
14
  ]
15
15
 
16
- homepage = "https://github.com/IvanildoBarauna/api-to-dataframe"
17
- repository = "https://github.com/IvanildoBarauna/api-to-dataframe"
18
- documentation = "https://github.com/IvanildoBarauna/api-to-dataframe#readme"
19
16
 
20
17
  [tool.poetry.urls]
21
- Homepage = "https://github.com/IvanildoBarauna/api-to-dataframe"
22
- Documentation = "https://github.com/IvanildoBarauna/api-to-dataframe#readme"
18
+ Homepage = "https://pypi.org/project/api-to-dataframe"
19
+ Documentation = "https://github.com/IvanildoBarauna/api-to-dataframe/blob/main/README.md"
20
+ Notebook = 'https://github.com/IvanildoBarauna/api-to-dataframe/blob/main/notebooks/example.ipynb'
21
+ ImplementationCase = 'https://github.com/IvanildoBarauna/ETL-awesome-api'
23
22
  Issues = "https://github.com/IvanildoBarauna/api-to-dataframe/issues"
23
+ Repository = "https://github.com/IvanildoBarauna/api-to-dataframe"
24
24
 
25
25
  [tool.poetry.dependencies]
26
26
  python = "^3.9"
27
27
  pandas = "^2.2.2"
28
28
  requests = "^2.32.3"
29
+ logging = "^0.4.9.6"
29
30
 
30
31
  [tool.poetry.group.dev.dependencies]
31
32
  poetry-dynamic-versioning = "^1.3.0"
32
33
  pytest = "^8.2.2"
33
34
  coverage = "^7.5.3"
34
35
  responses = "^0.25.3"
36
+ pylint = "^3.2.5"
37
+ black = "^24.4.2"
35
38
 
36
39
 
37
40
  [build-system]
@@ -46,3 +49,11 @@ include = ["api_to_dataframe*"]
46
49
  [tool.poetry-dynamic-versioning]
47
50
  enable = true
48
51
  versioning = "semantic"
52
+
53
+ [tool.pylint.'MESSAGES CONTROL']
54
+ disable = [
55
+ "C0114", # missing-module-docstring
56
+ "C0116", # missing-function-docstring
57
+ "C0115", # missing-class-docstring
58
+ "R0903", # too-few-public-methods
59
+ ]
@@ -0,0 +1,84 @@
1
+ from api_to_dataframe.models.retainer import retry_strategies, Strategies
2
+ from api_to_dataframe.models.get_data import GetData
3
+ from api_to_dataframe.utils.logger import log, LogLevel
4
+
5
+
6
+ class ClientBuilder:
7
+ def __init__( # pylint: disable=too-many-arguments
8
+ self,
9
+ endpoint: str,
10
+ headers: dict = None,
11
+ retry_strategy: Strategies = Strategies.NO_RETRY_STRATEGY,
12
+ retries: int = 3,
13
+ initial_delay: int = 1,
14
+ connection_timeout: int = 1,
15
+ ):
16
+ """
17
+ Initializes the ClientBuilder object.
18
+
19
+ Args:
20
+ endpoint (str): The API endpoint to connect to.
21
+ headers (dict, optional): The headers to use for the API request. Defaults to None.
22
+ retry_strategy (Strategies, optional): Defaults to Strategies.NoRetryStrategy.
23
+ retries (int): The number of times to retry a failed request. Defaults to 3.
24
+ initial_delay (int): The delay between retries in seconds. Defaults to 1.
25
+ connection_timeout (int): The timeout for the connection in seconds. Defaults to 2.
26
+
27
+ Raises:
28
+ ValueError: If endpoint is an empty string.
29
+ ValueError: If retries is not a non-negative integer.
30
+ ValueError: If delay is not a non-negative integer.
31
+ ValueError: If connection_timeout is not a non-negative integer.
32
+ """
33
+
34
+ if headers is None:
35
+ headers = {}
36
+ if endpoint == "":
37
+ log("endpoint param is mandatory", LogLevel.ERROR)
38
+ raise ValueError
39
+ if not isinstance(retries, int) or retries < 0:
40
+ log("retries must be a non-negative integer", LogLevel.ERROR)
41
+ raise ValueError
42
+ if not isinstance(initial_delay, int) or initial_delay < 0:
43
+ log("delay must be a non-negative integer", LogLevel.ERROR)
44
+ raise ValueError
45
+ if not isinstance(connection_timeout, int) or connection_timeout < 0:
46
+ log("connection_timeout must be a non-negative integer", LogLevel.ERROR)
47
+ raise ValueError
48
+
49
+ self.endpoint = endpoint
50
+ self.retry_strategy = retry_strategy
51
+ self.connection_timeout = connection_timeout
52
+ self.headers = headers
53
+ self.retries = retries
54
+ self.delay = initial_delay
55
+
56
+ @retry_strategies
57
+ def get_api_data(self):
58
+ """
59
+ Retrieves data from the API using the defined endpoint and retry strategy.
60
+
61
+ Returns:
62
+ dict: The response from the API.
63
+ """
64
+ response = GetData.get_response(
65
+ endpoint=self.endpoint,
66
+ headers=self.headers,
67
+ connection_timeout=self.connection_timeout,
68
+ )
69
+
70
+ return response.json()
71
+
72
+ def _get_raw_api_data(self):
73
+ response = GetData.get_response(
74
+ endpoint=self.endpoint,
75
+ headers=self.headers,
76
+ connection_timeout=self.connection_timeout,
77
+ )
78
+ return response
79
+
80
+ @staticmethod
81
+ def api_to_dataframe(response: dict):
82
+ df = GetData.to_dataframe(response)
83
+ log("serialized to dataframe: OK", LogLevel.INFO)
84
+ return df
@@ -0,0 +1,29 @@
1
+ import requests
2
+ import pandas as pd
3
+
4
+ from api_to_dataframe.utils.logger import log, LogLevel
5
+
6
+
7
+ class GetData:
8
+ @staticmethod
9
+ def get_response(endpoint: str, headers: dict, connection_timeout: int):
10
+ response = requests.get(endpoint, timeout=connection_timeout, headers=headers)
11
+ response.raise_for_status()
12
+ log("get_response: ok", LogLevel.INFO)
13
+ return response
14
+
15
+ @staticmethod
16
+ def to_dataframe(response):
17
+ try:
18
+ df = pd.DataFrame(response)
19
+ except Exception as err:
20
+ log(f"Error serializing to dataframe: {err}", LogLevel.ERROR)
21
+ raise TypeError(
22
+ f"Invalid response for transform in dataframe: {err}"
23
+ ) from err
24
+
25
+ if df.empty:
26
+ log("DataFrame is empty", LogLevel.ERROR)
27
+ raise ValueError("::: DataFrame is empty :::")
28
+
29
+ return df
@@ -0,0 +1,41 @@
1
+ import time
2
+ from enum import Enum
3
+ from requests.exceptions import RequestException
4
+ from api_to_dataframe.utils.logger import log, LogLevel
5
+
6
+
7
+ class Strategies(Enum):
8
+ NO_RETRY_STRATEGY = 0
9
+ LINEAR_RETRY_STRATEGY = 1
10
+ EXPONENTIAL_RETRY_STRATEGY = 2
11
+
12
+
13
+ def retry_strategies(func):
14
+ def wrapper(*args, **kwargs): # pylint: disable=inconsistent-return-statements
15
+ retry_number = 0
16
+ while retry_number < args[0].retries:
17
+ try:
18
+ log(
19
+ f"Trying for the {retry_number + 1} of {args[0].retries} retries. "
20
+ f"Using {args[0].retry_strategy}",
21
+ LogLevel.INFO,
22
+ )
23
+ return func(*args, **kwargs)
24
+ except RequestException as e:
25
+ retry_number += 1
26
+
27
+ if args[0].retry_strategy == Strategies.NO_RETRY_STRATEGY:
28
+ raise e
29
+ if args[0].retry_strategy == Strategies.LINEAR_RETRY_STRATEGY:
30
+ time.sleep(args[0].delay)
31
+ elif args[0].retry_strategy == Strategies.EXPONENTIAL_RETRY_STRATEGY:
32
+ time.sleep(args[0].delay * 2**retry_number)
33
+
34
+ if retry_number == args[0].retries:
35
+ log(
36
+ f"Failed after {retry_number} retries using {args[0].retry_strategy}",
37
+ LogLevel.ERROR,
38
+ )
39
+ raise e
40
+
41
+ return wrapper
@@ -0,0 +1,27 @@
1
+ import logging
2
+ from enum import Enum
3
+
4
+
5
+ class LogLevel(Enum):
6
+ INFO = logging.INFO
7
+ WARNING = logging.WARNING
8
+ ERROR = logging.ERROR
9
+
10
+
11
+ # Configure logging once at the start of your program
12
+ logging.basicConfig(
13
+ encoding="utf-8",
14
+ format="%(asctime)s :: %(levelname)s :: %(message)s",
15
+ datefmt="%Y-%m-%d %H:%M:%S %Z",
16
+ level=logging.DEBUG,
17
+ )
18
+
19
+
20
+ def log(message: str, level: LogLevel):
21
+ logger = logging.getLogger("api-to-dataframe")
22
+ logger.log(level.value, message)
23
+
24
+
25
+ log("This is an info message", LogLevel.INFO)
26
+ log("This is a warning message", LogLevel.WARNING)
27
+ log("This is an error message", LogLevel.ERROR)
@@ -1,76 +0,0 @@
1
- from api_to_dataframe.models.retainer import RetryStrategies, Strategies
2
- from api_to_dataframe.models.get_data import GetData
3
-
4
-
5
- class ClientBuilder:
6
- def __init__(self,
7
- endpoint: str,
8
- headers: dict = None,
9
- retry_strategy: Strategies = Strategies.NoRetryStrategy,
10
- retries: int = 3,
11
- initial_delay: int = 1,
12
- connection_timeout: int = 1):
13
-
14
- """
15
- Initializes the ClientBuilder object.
16
-
17
- Args:
18
- endpoint (str): The API endpoint to connect to.
19
- headers (dict, optional): The headers to use for the API request. Defaults to None.
20
- retry_strategy (Strategies, optional): The strategy to use for retrying failed requests. Defaults to Strategies.NoRetryStrategy.
21
- retries (int, optional): The number of times to retry a failed request. Defaults to 3.
22
- delay (int, optional): The delay between retries in seconds. Defaults to 1.
23
- connection_timeout (int, optional): The timeout for the connection in seconds. Defaults to 2.
24
-
25
- Raises:
26
- ValueError: If endpoint is an empty string.
27
- ValueError: If retries is not a non-negative integer.
28
- ValueError: If delay is not a non-negative integer.
29
- ValueError: If connection_timeout is not a non-negative integer.
30
- """
31
-
32
- if headers is None:
33
- headers = {}
34
- if endpoint == "":
35
- raise ValueError("::: endpoint param is mandatory :::")
36
- if not isinstance(retries, int) or retries < 0:
37
- raise ValueError("retries must be a non-negative integer")
38
- if not isinstance(initial_delay, int) or initial_delay < 0:
39
- raise ValueError("delay must be a non-negative integer")
40
- if not isinstance(connection_timeout, int) or connection_timeout < 0:
41
- raise ValueError("connection_timeout must be a non-negative integer")
42
-
43
- self.endpoint = endpoint
44
- self.retry_strategy = retry_strategy
45
- self.connection_timeout = connection_timeout
46
- self.headers = headers
47
- self.retries = retries
48
- self.delay = initial_delay
49
-
50
- @RetryStrategies
51
- def get_api_data(self):
52
- """
53
- Retrieves data from the API using the defined endpoint and retry strategy.
54
-
55
- Returns:
56
- dict: The response from the API.
57
- """
58
- response = GetData.get_response(
59
- endpoint=self.endpoint,
60
- headers=self.headers,
61
- connection_timeout=self.connection_timeout
62
- )
63
- return response.json()
64
-
65
- def _get_raw_api_data(self):
66
- response = GetData.get_response(
67
- endpoint=self.endpoint,
68
- headers=self.headers,
69
- connection_timeout=self.connection_timeout
70
- )
71
- return response
72
-
73
- @staticmethod
74
- def api_to_dataframe(response: dict):
75
- df = GetData.to_dataframe(response)
76
- return df
@@ -1,30 +0,0 @@
1
- import requests
2
- from requests.exceptions import HTTPError, Timeout
3
- import pandas as pd
4
-
5
- # from api_to_dataframe.common.utils.retry_strategies import RetryStrategies
6
- from api_to_dataframe.models.retainer import RetryStrategies
7
-
8
-
9
- class GetData:
10
- @staticmethod
11
- def get_response(endpoint: str,
12
- headers: dict,
13
- connection_timeout: int):
14
-
15
-
16
-
17
- response = requests.get(endpoint, timeout=connection_timeout, headers=headers)
18
- response.raise_for_status()
19
- return response
20
- @staticmethod
21
- def to_dataframe(response):
22
- try:
23
- df = pd.DataFrame(response)
24
- except Exception as err:
25
- raise TypeError(f"Invalid response for transform in dataframe: {err}")
26
-
27
- if df.empty:
28
- raise ValueError("::: DataFrame is empty :::")
29
- else:
30
- return df
@@ -1,30 +0,0 @@
1
- import time
2
- from enum import Enum
3
-
4
-
5
- class Strategies(Enum):
6
- NoRetryStrategy = 0
7
- LinearRetryStrategy = 1
8
- ExponentialRetryStrategy = 2
9
-
10
-
11
- def RetryStrategies(func):
12
- def wrapper(*args, **kwargs):
13
- retry_number = 0
14
- while retry_number < args[0].retries:
15
- try:
16
- return func(*args, **kwargs)
17
- except Exception as e:
18
- retry_number += 1
19
-
20
- if args[0].retry_strategy == Strategies.NoRetryStrategy:
21
- raise e
22
- elif args[0].retry_strategy == Strategies.LinearRetryStrategy:
23
- time.sleep(args[0].delay)
24
- elif args[0].retry_strategy == Strategies.ExponentialRetryStrategy:
25
- time.sleep(args[0].delay * 2 ** retry_number)
26
-
27
- if retry_number == args[0].retries:
28
- print(f"Failed after {retry_number} retries using {args[0].retry_strategy}")
29
- raise e
30
- return wrapper