api-to-dataframe 1.2.3__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,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: api-to-dataframe
3
- Version: 1.2.3
3
+ Version: 1.3.0
4
4
  Summary: A package to convert API responses to pandas dataframe
5
5
  License: MIT
6
6
  Author: IvanildoBarauna
7
7
  Author-email: ivanildo.jnr@outlook.com
8
8
  Requires-Python: >=3.9,<4.0
9
- Classifier: Development Status :: 4 - Beta
9
+ Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Programming Language :: Python :: 3
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.8
18
+ Requires-Dist: logging (>=0.4.9.6,<0.5.0.0)
18
19
  Requires-Dist: pandas (>=2.2.2,<3.0.0)
19
20
  Requires-Dist: requests (>=2.32.3,<3.0.0)
20
21
  Project-URL: Documentation, https://github.com/IvanildoBarauna/api-to-dataframe/blob/main/README.md
@@ -46,7 +47,7 @@ Python library that simplifies obtaining data from API endpoints by converting t
46
47
  ![Python](https://img.shields.io/badge/-Python-05122A?style=flat&logo=python)&nbsp;
47
48
  ![Docker](https://img.shields.io/badge/-Docker-05122A?style=flat&logo=docker)&nbsp;
48
49
  ![Poetry](https://img.shields.io/badge/-Poetry-05122A?style=flat&logo=poetry)&nbsp;
49
- ![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;
50
51
  ![CodeCov](https://img.shields.io/badge/-CodeCov-05122A?style=flat&logo=codecov)&nbsp;
51
52
  ![pypi](https://img.shields.io/badge/-pypi-05122A?style=flat&logo=pypi)&nbsp;
52
53
  ![pandas](https://img.shields.io/badge/-pandas-05122A?style=flat&logo=pandas)&nbsp;
@@ -76,29 +77,28 @@ from api_to_dataframe import ClientBuilder, RetryStrategies
76
77
  # Create a client for simple ingest data from API (timeout 1 second)
77
78
  client = ClientBuilder(endpoint="https://api.example.com")
78
79
 
79
- # if you can define timeout with LinearStrategy and set headers:
80
+ # if you can define timeout with LINEAR_RETRY_STRATEGY and set headers:
80
81
  headers = {
81
82
  "application_name": "api_to_dataframe"
82
83
  }
83
84
  client = ClientBuilder(endpoint="https://api.example.com"
84
- ,retry_strategy=RetryStrategies.LinearStrategy
85
+ ,retry_strategy=RetryStrategies.LINEAR_RETRY_STRATEGY
85
86
  ,connection_timeout=2
86
87
  ,headers=headers)
87
88
 
88
89
  """
89
- NOTE: by default the quantity of retries is 3 and the time between retries is 1 second, but you can define manually, like this:
90
-
90
+ NOTE: by default the quantity of retries is 3 and the time between retries is 1 second, but you can define manually.
91
91
  """
92
92
 
93
93
  client = ClientBuilder(endpoint="https://api.example.com"
94
- ,retry_strategy=RetryStrategies.LinearStrategy
94
+ ,retry_strategy=RetryStrategies.LINEAR_RETRY_STRATEGY
95
95
  ,connection_timeout=10
96
96
  ,headers=headers
97
97
  ,retries=5
98
98
  ,initial_delay=10)
99
-
100
99
 
101
- ### timeout, retry_strategy and headers are opcionals parameters
100
+
101
+ ### timeout, retry_strategy and headers are opcionals parameters
102
102
 
103
103
  # Get data from the API
104
104
  data = client.get_api_data()
@@ -19,7 +19,7 @@ Python library that simplifies obtaining data from API endpoints by converting t
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;
@@ -49,29 +49,28 @@ from api_to_dataframe import ClientBuilder, RetryStrategies
49
49
  # Create a client for simple ingest data from API (timeout 1 second)
50
50
  client = ClientBuilder(endpoint="https://api.example.com")
51
51
 
52
- # if you can define timeout with LinearStrategy and set headers:
52
+ # if you can define timeout with LINEAR_RETRY_STRATEGY and set headers:
53
53
  headers = {
54
54
  "application_name": "api_to_dataframe"
55
55
  }
56
56
  client = ClientBuilder(endpoint="https://api.example.com"
57
- ,retry_strategy=RetryStrategies.LinearStrategy
57
+ ,retry_strategy=RetryStrategies.LINEAR_RETRY_STRATEGY
58
58
  ,connection_timeout=2
59
59
  ,headers=headers)
60
60
 
61
61
  """
62
- NOTE: by default the quantity of retries is 3 and the time between retries is 1 second, but you can define manually, like this:
63
-
62
+ NOTE: by default the quantity of retries is 3 and the time between retries is 1 second, but you can define manually.
64
63
  """
65
64
 
66
65
  client = ClientBuilder(endpoint="https://api.example.com"
67
- ,retry_strategy=RetryStrategies.LinearStrategy
66
+ ,retry_strategy=RetryStrategies.LINEAR_RETRY_STRATEGY
68
67
  ,connection_timeout=10
69
68
  ,headers=headers
70
69
  ,retries=5
71
70
  ,initial_delay=10)
72
-
73
71
 
74
- ### timeout, retry_strategy and headers are opcionals parameters
72
+
73
+ ### timeout, retry_strategy and headers are opcionals parameters
75
74
 
76
75
  # Get data from the API
77
76
  data = client.get_api_data()
@@ -1,12 +1,12 @@
1
1
  [tool.poetry]
2
2
  name = "api-to-dataframe"
3
- version = "1.2.3"
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 :: 4 - Beta",
9
+ "Development Status :: 5 - Production/Stable",
10
10
  "Intended Audience :: Developers",
11
11
  "License :: OSI Approved :: MIT License",
12
12
  "Programming Language :: Python :: 3",
@@ -26,12 +26,15 @@ Repository = "https://github.com/IvanildoBarauna/api-to-dataframe"
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