data-retrieval-module 1.0.1__tar.gz → 1.0.3__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.
Files changed (36) hide show
  1. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/CHANGELOG.md +17 -0
  2. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/PKG-INFO +1 -1
  3. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/__init__.py +8 -4
  4. data_retrieval_module-1.0.3/data_retrieval/data_provider/__init__.py +5 -0
  5. data_retrieval_module-1.0.3/data_retrieval/data_provider/database/__init__.py +4 -0
  6. data_retrieval_module-1.0.3/data_retrieval/data_provider/database/database_data_provider.py +121 -0
  7. data_retrieval_module-1.0.3/data_retrieval/data_provider/database/sqlite3_data_provider.py +388 -0
  8. data_retrieval_module-1.0.3/data_retrieval/data_provider/rest_api/__init__.py +12 -0
  9. data_retrieval_module-1.0.3/data_retrieval/data_provider/rest_api/rest_api_data_provider.py +265 -0
  10. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval_module.egg-info/PKG-INFO +1 -1
  11. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval_module.egg-info/SOURCES.txt +6 -0
  12. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/pyproject.toml +2 -2
  13. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/tests/test_basic.py +1 -1
  14. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/LICENSE +0 -0
  15. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/MANIFEST.in +0 -0
  16. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/Makefile +0 -0
  17. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/README.md +0 -0
  18. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/foreign_exchange/__init__.py +0 -0
  19. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/foreign_exchange/forex_data_provider_base.py +0 -0
  20. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/foreign_exchange/forex_data_provider_wrapper.py +0 -0
  21. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/foreign_exchange/forex_python_data_provider.py +0 -0
  22. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/model/__init__.py +0 -0
  23. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/model/data_module.py +0 -0
  24. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/model/data_provider.py +0 -0
  25. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/model/data_provider_wrapper.py +0 -0
  26. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/model/exceptions.py +0 -0
  27. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/py.typed +0 -0
  28. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/utils/__init__.py +0 -0
  29. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval/utils/date_utils.py +0 -0
  30. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval_module.egg-info/dependency_links.txt +0 -0
  31. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval_module.egg-info/requires.txt +0 -0
  32. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/data_retrieval_module.egg-info/top_level.txt +0 -0
  33. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/requirements-test.txt +0 -0
  34. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/setup.cfg +0 -0
  35. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/setup.py +0 -0
  36. {data_retrieval_module-1.0.1 → data_retrieval_module-1.0.3}/tests/__init__.py +0 -0
@@ -65,6 +65,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
65
65
  - Updated package exports to include new modules
66
66
  - Enhanced examples with forex data sources
67
67
 
68
+ ## [1.0.3] - 2026-06-13
69
+
70
+ ### Fixed
71
+ - `RestAPI_DataProvider.set_base_url` no longer strips trailing slashes from the provided URL
72
+
73
+ ### Changed
74
+ - Updated `.gitignore` to exclude `*.db` database files
75
+
76
+ ## [1.0.2] - 2026-01-19
77
+
78
+ ### Added
79
+ - REST API data provider module
80
+ - `RestAPI_DataProvider` synchronous base class for REST API data providers
81
+ - Built-in retry strategy with configurable backoff factor and status force list
82
+ - HTTP Basic Authentication support via `generate_http_authentication`
83
+ - Utility method `partition_list_into_chunks` for paginated request handling
84
+
68
85
  ## [Unreleased]
69
86
 
70
87
  ### Planned
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: data-retrieval-module
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: A standardized interface for data providers with sync and async support
5
5
  Author-email: AbigailWilliams1692 <abigail.williams@example.com>
6
6
  Maintainer-email: AbigailWilliams1692 <abigail.williams@example.com>
@@ -28,13 +28,16 @@ from data_retrieval.model.exceptions import (
28
28
  from data_retrieval.data_provider.rest_api import RestAPI_DataProvider
29
29
 
30
30
  # Database providers
31
- from data_retrieval.data_provider.database import Database_DataProvider
31
+ from data_retrieval.data_provider.database import (
32
+ Database_DataProvider,
33
+ SQLite3_DataProvider,
34
+ )
32
35
 
33
36
  # Foreign Exchange providers
34
37
  from data_retrieval.foreign_exchange import (
35
38
  Forex_DataProvider_Base,
36
39
  ForexPython_DataProvider,
37
- Forex_DataProvider_Wrapper
40
+ Forex_DataProvider_Wrapper,
38
41
  )
39
42
 
40
43
  #######################################################################
@@ -58,6 +61,7 @@ __all__ = [
58
61
 
59
62
  # Database providers
60
63
  "Database_DataProvider",
64
+ "SQLite3_DataProvider",
61
65
 
62
66
  # Foreign Exchange providers
63
67
  "Forex_DataProvider_Base",
@@ -68,6 +72,6 @@ __all__ = [
68
72
  #######################################################################
69
73
  # Version Information
70
74
  #######################################################################
71
- __version__ = "1.0.1"
75
+ __version__ = "1.0.2"
72
76
  __author__ = "AbigailWilliams1692"
73
- __email__ = "abigail.williams@example.com"
77
+ __email__ = "alfred.xy1020@gmail.com"
@@ -0,0 +1,5 @@
1
+ #######################################################################
2
+ # Project: Data Retrieval Module
3
+ # File: __init__.py
4
+ # Description: Data provider package initialization
5
+ #######################################################################
@@ -0,0 +1,4 @@
1
+ from .database_data_provider import Database_DataProvider
2
+ from .sqlite3_data_provider import SQLite3_DataProvider
3
+
4
+ __all__ = ["Database_DataProvider", "SQLite3_DataProvider"]
@@ -0,0 +1,121 @@
1
+ #######################################################################
2
+ # Project: Data Retrieval Module
3
+ # File: db_provider.py
4
+ # Description: Database data provider implementations
5
+ # Author: AbigailWilliams1692
6
+ # Created: 2026-01-14
7
+ # Updated: 2026-01-19
8
+ #######################################################################
9
+
10
+ #######################################################################
11
+ # Import Packages
12
+ #######################################################################
13
+ # Standard Packages
14
+ import datetime
15
+ import logging
16
+ from abc import ABC, abstractmethod
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ # Third-party Packages
20
+
21
+ # Local Packages
22
+ from data_retrieval.model.data_provider import DataProvider
23
+
24
+
25
+ #######################################################################
26
+ # Database Data Provider (Synchronous)
27
+ #######################################################################
28
+ class Database_DataProvider(DataProvider, ABC):
29
+ """
30
+ Base class for database data providers.
31
+
32
+ This class extends the DataProvider base class to provide database-specific
33
+ functionality for synchronous data retrieval operations. It serves as a
34
+ foundation for implementing various database connection types and query
35
+ operations.
36
+ """
37
+
38
+ ###################################################################
39
+ # Class Attributes
40
+ ###################################################################
41
+ __name = "Database_DataProvider"
42
+ __type = "Database_DataProvider"
43
+
44
+ ###################################################################
45
+ # Constructor Method
46
+ ###################################################################
47
+ def __init__(
48
+ self,
49
+ instance_id: Optional[int] = None,
50
+ logger: Optional[logging.Logger] = None,
51
+ log_level: Optional[int] = logging.INFO,
52
+ **config,
53
+ ) -> None:
54
+ """
55
+ Initialize the Database_DataProvider with the given parameters.
56
+
57
+ :param instance_id: Unique identifier for this provider instance.
58
+ :param logger: Logger instance for logging operations.
59
+ :param log_level: Logging level for the provider.
60
+ :param config: Additional configuration parameters for the database connection.
61
+ """
62
+ # Super Initialize
63
+ super().__init__(
64
+ instance_id=instance_id,
65
+ logger=logger,
66
+ log_level=log_level,
67
+ **config
68
+ )
69
+
70
+ ###################################################################
71
+ # Utility Methods
72
+ ###################################################################
73
+ def format_sql_query(self, sql: str, params: Dict[str, Any]) -> str:
74
+ """
75
+ Format the SQL query string with the given key-value pairs.
76
+
77
+ :param sql: the SQL query template string with placeholders.
78
+ :param params: Dict[str, Any]: a Dictionary of key-value pairs used to parametrize the SQL query.
79
+ :return: the formatted SQL query string.
80
+ """
81
+ for key, value in params.items():
82
+ placeholder = ":" + key
83
+
84
+ # Covert the value to string based on its type
85
+ if isinstance(value, str):
86
+ value_to_replace = "'" + str(value) + "'"
87
+ elif isinstance(value, (int, float)):
88
+ value_to_replace = str(value)
89
+ elif isinstance(value, datetime.date):
90
+ value_to_replace = "'" + value.strftime("%Y-%m-%d") + "'"
91
+ elif isinstance(value, List):
92
+ value_to_replace = "'" + self.stringify_a_list_of_items_with_apostrophe(item_list=value) + "'"
93
+ else:
94
+ raise TypeError(f"Unsupported value type: {type(value)} for parameter '{key}'")
95
+
96
+ # Replace all the placeholders in the SQL string
97
+ sql = sql.replace(placeholder, value_to_replace, count=-1)
98
+
99
+ return sql
100
+
101
+ @staticmethod
102
+ def stringify_a_list_of_items_with_apostrophe(item_list: List[Any]) -> str:
103
+ """
104
+ Convert a list of items into a comma-separated string with each item wrapped in apostrophes.
105
+
106
+ :param item_list: List of items to convert.
107
+ :return: String representation of items wrapped in apostrophes and separated by commas.
108
+ """
109
+ item_str_list = [f"'{str(item)}'" for item in item_list]
110
+ return ",".join(item_str_list)
111
+
112
+ @staticmethod
113
+ def generate_markers(size: int, marker: str = "?") -> str:
114
+ """
115
+ Generate placeholder markers joined by ','.
116
+
117
+ :param size: Number of markers to generate.
118
+ :param marker: Marker string to use (default is "?").
119
+ :return: String of markers joined by commas.
120
+ """
121
+ return ",".join([marker] * size)
@@ -0,0 +1,388 @@
1
+ #!/usr/bin/env python3
2
+ ########################################################################
3
+ # File: database_service.py
4
+ # Description: SQLite3 database data provider
5
+ # Author: AbigailWilliams1692
6
+ # Creation Date: 2026-01-29
7
+ # Version: 1.0.0
8
+ # License: MIT License
9
+ ########################################################################
10
+
11
+ ########################################################################
12
+ # Import Libraries
13
+ ########################################################################
14
+ # Standard Packages
15
+ import logging
16
+ import sqlite3
17
+ from enum import Enum
18
+ from typing import Any, Dict, List, Optional, Tuple, Union
19
+
20
+ # Third-party Packages
21
+ from data_retrieval.data_provider.database.database_data_provider import (
22
+ Database_DataProvider,
23
+ )
24
+
25
+
26
+ ########################################################################
27
+ # SQLite3 Data Provider Class
28
+ ########################################################################
29
+ class SQLite3FetchMode(str, Enum):
30
+ """
31
+ Enum for fetch modes.
32
+ """
33
+ ALL = "all"
34
+ ONE = "one"
35
+ MANY = "many"
36
+ LAST_ID = "last_id"
37
+ NONE = "none"
38
+
39
+
40
+ class SQLite3_DataProvider(Database_DataProvider):
41
+ """
42
+ SQLite3 data provider class.
43
+ """
44
+
45
+ ###################################################################
46
+ # Class Attributes
47
+ ###################################################################
48
+ __name = "SQLite3_DataProvider"
49
+
50
+ ###################################################################
51
+ # Constructor Method
52
+ ###################################################################
53
+ def __init__(
54
+ self,
55
+ db_file_path: str,
56
+ instance_id: Optional[int] = None,
57
+ logger: Optional[logging.Logger] = None,
58
+ log_level: Optional[int] = logging.INFO,
59
+ **config,
60
+ ) -> None:
61
+ """
62
+ Initialize the DatabaseService with the given parameters.
63
+
64
+ :param db_file_path: Path to the SQLite database file (.db).
65
+ :param instance_id: Unique identifier for this provider instance.
66
+ :param logger: Logger instance for logging operations.
67
+ :param log_level: Logging level for the provider.
68
+ :param config: Additional configuration parameters.
69
+ """
70
+ # Super Initialize
71
+ super().__init__(
72
+ instance_id=instance_id,
73
+ logger=logger,
74
+ log_level=log_level,
75
+ **config,
76
+ )
77
+
78
+ # Initialize the DatabaseService attributes
79
+ ## Store the database path
80
+ self._db_file_path = db_file_path
81
+ ## Initialize the cursor
82
+ self._cursor: Optional[sqlite3.Cursor] = None
83
+ ## Connect to the datbase file
84
+ self.connect()
85
+
86
+ # Update data methods
87
+ self.update_data_methods(
88
+ new_methods={
89
+ "execute": self.execute,
90
+ "execute_many": self.execute_many,
91
+ "fetch_one": self.fetch_one,
92
+ "fetch_many": self.fetch_many,
93
+ "fetch_all": self.fetch_all,
94
+ }
95
+ )
96
+
97
+ ###################################################################
98
+ # Getter & Setter Methods
99
+ ###################################################################
100
+ def get_db_file_path(self) -> str:
101
+ """
102
+ Get the database file path.
103
+
104
+ :return: The path to the SQLite database file.
105
+ """
106
+ return self._db_file_path
107
+
108
+ def set_db_file_path(self, db_file_path: str) -> None:
109
+ """
110
+ Set the database file path.
111
+
112
+ :param db_file_path: The path to the SQLite database file.
113
+ :return: None.
114
+ """
115
+ self._db_file_path = db_file_path
116
+
117
+ def get_cursor(self) -> Optional[sqlite3.Cursor]:
118
+ """
119
+ Get the database cursor.
120
+
121
+ :return: The SQLite cursor object.
122
+ """
123
+ return self._cursor
124
+
125
+ def set_cursor(self, cursor: sqlite3.Cursor) -> None:
126
+ """
127
+ Set the database cursor.
128
+
129
+ :param cursor: The SQLite cursor object.
130
+ :return: None.
131
+ """
132
+ self._cursor = cursor
133
+
134
+ ###################################################################
135
+ # Connection Methods
136
+ ###################################################################
137
+ def _connect(self, *args, **kwargs) -> None:
138
+ """
139
+ Connect to the SQLite database.
140
+
141
+ Creates a connection to the SQLite database file specified by db_file_path.
142
+ If the file does not exist, SQLite will create it.
143
+
144
+ :param args: Positional arguments (unused).
145
+ :param kwargs: Keyword arguments (unused).
146
+ :return: None.
147
+ :raises sqlite3.Error: If connection fails.
148
+ """
149
+ # Connect to the database
150
+ conn = sqlite3.connect(self.get_db_file_path())
151
+
152
+ # Set row factory to return rows as sqlite3.Row objects
153
+ conn.row_factory = sqlite3.Row
154
+
155
+ # Set connection and cursor
156
+ self.set_connection(connection=conn)
157
+ self.set_cursor(conn.cursor())
158
+
159
+ def _disconnect(self) -> None:
160
+ """
161
+ Disconnect from the SQLite database.
162
+
163
+ Closes the cursor and connection to the database.
164
+
165
+ :param args: Positional arguments (unused).
166
+ :param kwargs: Keyword arguments (unused).
167
+ :return: None.
168
+ :raises sqlite3.Error: If disconnection fails.
169
+ """
170
+ if self._cursor is not None:
171
+ self._cursor.close()
172
+ self._cursor = None
173
+
174
+ conn = self.get_connection()
175
+ if conn is not None:
176
+ conn.close()
177
+ self.set_connection(connection=None)
178
+
179
+ ###################################################################
180
+ # Core Instance Method: Execute
181
+ ###################################################################
182
+ def execute(
183
+ self,
184
+ sql: str,
185
+ params: Optional[Union[Tuple, Dict[str, Any]]] = None,
186
+ fetch_mode: str = SQLite3FetchMode.ALL,
187
+ commit: bool = True,
188
+ **kwargs,
189
+ ) -> Any:
190
+ """
191
+ Execute a SQL query on the SQLite database.
192
+
193
+ :param sql: The SQL query string to execute.
194
+ :param params: Optional parameters for parameterized queries.
195
+ Can be a tuple for positional params or dict for named params.
196
+ :param fetch: Fetch mode - "all", "one", "many", or "none".
197
+ - "all": Fetch all results (fetchall).
198
+ - "one": Fetch single result (fetchone).
199
+ - "many": Fetch specified number of results (fetchmany).
200
+ - "none": Don't fetch results (for INSERT/UPDATE/DELETE).
201
+ :param commit: Whether to commit the transaction after execution.
202
+ :param kwargs: Additional keyword arguments.
203
+ - fetch_size: Number of rows to fetch when fetch="many".
204
+ :return: Query results based on fetch mode, or lastrowid for INSERT operations.
205
+ :raises sqlite3.Error: If query execution fails.
206
+ :raises ValueError: If invalid fetch mode is specified.
207
+ """
208
+ # Check if the database is connected
209
+ self.check_db_connection()
210
+
211
+ # Get the connection and the cursor
212
+ conn = self.get_connection()
213
+ cursor = self.get_cursor()
214
+
215
+ # Execute the query with or without parameters
216
+ if params is not None:
217
+ cursor.execute(sql, params)
218
+ else:
219
+ cursor.execute(sql)
220
+
221
+ # Commit if requested
222
+ if commit:
223
+ conn.commit()
224
+
225
+ # Fetch results based on mode
226
+ if fetch_mode == "all":
227
+ return cursor.fetchall()
228
+ elif fetch_mode == "one":
229
+ return cursor.fetchone()
230
+ elif fetch_mode == "many":
231
+ fetch_size = kwargs.get("fetch_size", 100)
232
+ return cursor.fetchmany(fetch_size)
233
+ elif fetch_mode == "last_id":
234
+ return cursor.lastrowid
235
+ elif fetch_mode == "none":
236
+ return None
237
+ else:
238
+ raise ValueError(f"Invalid fetch mode: {fetch_mode}. Must be 'all', 'one', 'many', or 'none'.")
239
+
240
+ def execute_many(
241
+ self,
242
+ sql: str,
243
+ params_list: List[Union[Tuple, Dict[str, Any]]],
244
+ fetch_mode: str = "all",
245
+ commit: bool = True,
246
+ ) -> None:
247
+ """
248
+ Execute a SQL statement on the SQLite database multiple times with different parameters.
249
+
250
+ :param sql: The SQL statement to execute.
251
+ :param params_list: List of parameter tuples or dicts.
252
+ :param commit: Whether to commit the transaction (default: True).
253
+ :return: None.
254
+ """
255
+ # Check if the database is connected
256
+ self.check_db_connection()
257
+
258
+ # Get the connection and the cursor
259
+ conn = self.get_connection()
260
+ cursor = self.get_cursor()
261
+
262
+ # Execute the query with multiple parameter groups
263
+ cursor.executemany(sql, params_list)
264
+
265
+ # Commit if requested
266
+ if commit:
267
+ conn.commit()
268
+
269
+ # Fetch results based on mode
270
+ if fetch_mode == "all":
271
+ return cursor.fetchall()
272
+ elif fetch_mode == "one":
273
+ return cursor.fetchone()
274
+ elif fetch_mode == "many":
275
+ fetch_size = kwargs.get("fetch_size", 100)
276
+ return cursor.fetchmany(fetch_size)
277
+ elif fetch_mode == "last_id":
278
+ return cursor.lastrowid
279
+ elif fetch_mode == "none":
280
+ return None
281
+ else:
282
+ raise ValueError(f"Invalid fetch mode: {fetch_mode}. Must be 'all', 'one', 'many', or 'none'.")
283
+
284
+ ###################################################################
285
+ # Core Instance Methods: Fetch One/Many/All
286
+ ###################################################################
287
+ def fetch_one(
288
+ self,
289
+ sql: str,
290
+ params: Optional[Union[Tuple, Dict[str, Any]]] = None,
291
+ ) -> Optional[sqlite3.Row]:
292
+ """
293
+ Execute a SELECT query and fetch a single result.
294
+
295
+ :param sql: The SQL SELECT query.
296
+ :param params: Optional parameters for parameterized queries.
297
+ :return: Single result row or None.
298
+ """
299
+ return self.execute(sql=sql, params=params, fetch="one")
300
+
301
+ def fetch_many(
302
+ self,
303
+ sql: str,
304
+ params: Optional[Union[Tuple, Dict[str, Any]]] = None,
305
+ fetch_size: int = 100,
306
+ ) -> List[sqlite3.Row]:
307
+ """
308
+ Execute a SELECT query and fetch multiple results.
309
+
310
+ :param sql: The SQL SELECT query.
311
+ :param params: Optional parameters for parameterized queries.
312
+ :param fetch_size: Number of rows to fetch (default: 100).
313
+ :return: List of result rows.
314
+ """
315
+ return self.execute(sql=sql, params=params, fetch="many", fetch_size=fetch_size)
316
+
317
+ def fetch_all(
318
+ self,
319
+ sql: str,
320
+ params: Optional[Union[Tuple, Dict[str, Any]]] = None,
321
+ ) -> List[sqlite3.Row]:
322
+ """
323
+ Execute a SELECT query and fetch all results.
324
+
325
+ :param sql: The SQL SELECT query.
326
+ :param params: Optional parameters for parameterized queries.
327
+ :return: List of all result rows.
328
+ """
329
+ return self.execute(sql=sql, params=params, fetch="all")
330
+
331
+ ###################################################################
332
+ # Core Instance Method: Commit
333
+ ###################################################################
334
+ def commit(self) -> None:
335
+ """
336
+ Commit the current transaction.
337
+ """
338
+ self.check_db_connection()
339
+ self.get_connection().commit()
340
+
341
+ ###################################################################
342
+ # Core Instance Method: Rollback
343
+ ###################################################################
344
+ def rollback(self) -> None:
345
+ """
346
+ Rollback the current transaction.
347
+
348
+ :return: None.
349
+ """
350
+ self.check_db_connection()
351
+ self.get_connection().rollback()
352
+
353
+ ###################################################################
354
+ # Utility Methods
355
+ ###################################################################
356
+ def check_db_connection(self) -> None:
357
+ """
358
+ Check connection and raise sqlite3.OperationalError if not connected.
359
+ """
360
+ if not self.is_connected():
361
+ raise sqlite3.OperationalError("Database is not connected. Call connect() first.")
362
+
363
+ def table_exists(self, table_name: str) -> bool:
364
+ """
365
+ Check if a table exists in the database.
366
+
367
+ :param table_name: Name of the table to check.
368
+ :return: True if table exists, False otherwise.
369
+ """
370
+ sql = """
371
+ SELECT name
372
+ FROM sqlite_master
373
+ WHERE type='table'
374
+ AND name=?
375
+ """
376
+ result = self.fetch_one(sql=sql, params=(table_name,))
377
+ return result is not None
378
+
379
+ def get_table_info(self, table_name: str) -> List[sqlite3.Row]:
380
+ """
381
+ Get column information for a table.
382
+
383
+ :param table_name: Name of the table.
384
+ :return: List of column information rows.
385
+ """
386
+ self.check_db_connection()
387
+ sql = f"PRAGMA table_info({table_name})"
388
+ return self.fetch_all(sql=sql)
@@ -0,0 +1,12 @@
1
+ #######################################################################
2
+ # Project: Data Retrieval Module
3
+ # File: __init__.py
4
+ # Description: REST API data provider package initialization
5
+ # Author: AbigailWilliams1692
6
+ # Created: 2026-01-14
7
+ # Updated: 2026-01-18
8
+ #######################################################################
9
+
10
+ from .rest_api_data_provider import RestAPI_DataProvider
11
+
12
+ __all__ = ["RestAPI_DataProvider"]
@@ -0,0 +1,265 @@
1
+ #######################################################################
2
+ # Project: Data Retrieval Module
3
+ # File: rest_api_provider.py
4
+ # Description: REST API data provider implementations
5
+ # Author: AbigailWilliams1692
6
+ # Created: 2026-01-14
7
+ # Updated: 2026-01-19
8
+ #######################################################################
9
+
10
+ #######################################################################
11
+ # Import Packages
12
+ #######################################################################
13
+ # Standard Packages
14
+ import logging
15
+ from abc import ABC
16
+ from collections.abc import Generator
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ # Third-party Packages
20
+ import requests
21
+ from requests import Response
22
+ from requests.adapters import HTTPAdapter
23
+ from requests.auth import HTTPBasicAuth
24
+ from urllib3.util import Retry
25
+
26
+ # Local Packages
27
+ from data_retrieval.model.data_provider import DataProvider
28
+
29
+
30
+ #######################################################################
31
+ # REST API Data Provider (Synchronous)
32
+ #######################################################################
33
+ class RestAPI_DataProvider(DataProvider, ABC):
34
+ """
35
+ Synchronous REST API data provider.
36
+
37
+ Provides standardized interface for interacting with REST APIs.
38
+ Supports authentication, pagination, error handling, and retry logic.
39
+ """
40
+
41
+ ###################################################################
42
+ # Class Attributes
43
+ ###################################################################
44
+ __name = "RestAPI_DataProvider"
45
+ __type = "RestAPI_DataProvider"
46
+ __base_url: str = ""
47
+
48
+ ###################################################################
49
+ # Constructor Method
50
+ ###################################################################
51
+ def __init__(
52
+ self,
53
+ instance_id: Optional[int] = None,
54
+ logger: Optional[logging.Logger] = None,
55
+ log_level: Optional[int] = logging.INFO,
56
+ base_url: Optional[str] = None,
57
+ timeout: int = 30,
58
+ max_retries: int = 3,
59
+ retry_backoff_factor: float = 0.3,
60
+ **config
61
+ ) -> None:
62
+ """
63
+ Initialize the REST API data provider.
64
+
65
+ :param instance_id: Unique identifier for this provider instance.
66
+ :param logger: Logger instance for logging operations.
67
+ :param log_level: Logging level for the data provider.
68
+ :param data_methods: Dictionary of data retrieval methods.
69
+ :param base_url: Base URL for the REST API.
70
+ :param timeout: Request timeout in seconds.
71
+ :param max_retries: Maximum number of retry attempts.
72
+ :param retry_backoff_factor: Backoff factor for retry delays.
73
+ :param config: Additional configuration parameters.
74
+ """
75
+ # Initialize the base DataProvider
76
+ super().__init__(
77
+ instance_id=instance_id,
78
+ logger=logger,
79
+ log_level=log_level,
80
+ **config
81
+ )
82
+
83
+ # Prepare the data methods
84
+ self.update_data_methods(
85
+ {
86
+ "http_request": self._make_request,
87
+ }
88
+ )
89
+
90
+ # Initialize REST API specific attributes
91
+ self._base_url = base_url or self.__base_url
92
+ self._timeout = timeout
93
+ self._max_retries = max_retries
94
+ self._retry_backoff_factor = retry_backoff_factor
95
+
96
+ # Connect to the Session
97
+ self.connect()
98
+
99
+ # Initialize session with retry strategy
100
+ retry_strategy = Retry(
101
+ total=max_retries,
102
+ backoff_factor=retry_backoff_factor,
103
+ status_forcelist=[429, 500, 502, 503, 504],
104
+ allowed_methods=["HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
105
+ )
106
+ adapter = HTTPAdapter(max_retries=retry_strategy)
107
+ self.get_connection().mount("http://", adapter)
108
+ self.get_connection().mount("https://", adapter)
109
+
110
+ ###################################################################
111
+ # Getter & Setter Methods
112
+ ###################################################################
113
+ def get_base_url(self) -> str:
114
+ """
115
+ Get the base URL of the API server.
116
+
117
+ :return: The base URL of the API server.
118
+ """
119
+ return self.__base_url
120
+
121
+ def set_base_url(self, base_url: str) -> None:
122
+ """
123
+ Set the base URL of the API server.
124
+
125
+ :param base_url: The base URL of the API server.
126
+ """
127
+ self.__base_url = base_url
128
+
129
+ ###################################################################
130
+ # Core Instance Method: Make Request to REST API Server
131
+ ###################################################################
132
+ def _make_request(
133
+ self,
134
+ url: str,
135
+ method: str,
136
+ params: Optional[Dict] = None,
137
+ data: Optional[Dict] = None,
138
+ json: Optional[Dict] = None,
139
+ headers: Optional[Dict] = None,
140
+ authentication: Optional[Any] = None,
141
+ ) -> Dict:
142
+ """
143
+ Make HTTP request to REST API endpoint.
144
+
145
+ :param url: Endpoint URL (relative to base_url)
146
+ :param method: HTTP method (GET, POST, PUT, DELETE, etc.)
147
+ :param params: Query parameters
148
+ :param data: Form data to send
149
+ :param json: JSON data to send
150
+ :param headers: Additional headers
151
+ :param authentication: Override default authentication
152
+
153
+ :return: Response data as dictionary.
154
+ :raises: HTTPError if request fails.
155
+ """
156
+ # Logging the Request
157
+ self.get_logger().debug(f"Making {method.upper()} request to {url}")
158
+
159
+ # Make the request
160
+ try:
161
+ response: Response = self.get_connection().request(
162
+ method=method.upper(),
163
+ url=url,
164
+ params=params,
165
+ data=data,
166
+ json=json,
167
+ headers=headers,
168
+ auth=authentication,
169
+ )
170
+
171
+ ## Raise an error for bad response (4xx and 5xx)
172
+ response.raise_for_status()
173
+
174
+ ## If the response is successful, return the JSON content
175
+ self.get_logger().debug(f"Request succeeded: {response.status_code}.")
176
+ return response.json()
177
+
178
+ except requests.exceptions.RequestException as e:
179
+ self.get_logger().error(f"Request failed: {e}")
180
+ return {}
181
+
182
+ ###################################################################
183
+ # Core Instance Method: Fetch Data
184
+ ##################################################################
185
+ def fetch_data(self, data_point: str, return_data_type: type, *args, **kwargs) -> Any:
186
+ """
187
+ Base method to fetch data based on the data point and return type.
188
+
189
+ :param data_point: str: The data point to fetch.
190
+ :param return_data_type: type: The type of data to return.
191
+ :param args: Tuple: Positional arguments for fetching data.
192
+ :param kwargs: Dict: Keyword arguments for fetching data.
193
+ :return: The fetched data.
194
+ """
195
+ return super().fetch_data(data_point=data_point, return_data_type=return_data_type, *args, **kwargs)
196
+
197
+ ###################################################################
198
+ # Connection Methods
199
+ ###################################################################
200
+ def _connect(self) -> None:
201
+ """
202
+ Connect to Aladdin API server.
203
+ """
204
+ self.set_connection(connection=requests.Session())
205
+
206
+ def _disconnect(self, *args, **kwargs):
207
+ if self.get_connection() is not None:
208
+ self.get_connection().close()
209
+ self.set_connection(connection=None)
210
+
211
+ ###################################################################
212
+ # Utility Methods
213
+ ###################################################################
214
+ @staticmethod
215
+ def generate_headers(*args, **kwargs) -> Dict:
216
+ """
217
+ Generate default HTTP headers for API requests.
218
+
219
+ :param args: Additional positional arguments (unused).
220
+ :param kwargs: Additional keyword arguments (unused).
221
+ :return: Dictionary containing default HTTP headers.
222
+ """
223
+ headers = {
224
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
225
+ "Accept": "application/json, text/plain, */*",
226
+ "Accept-Language": "en-US,en;q=0.9",
227
+ "Content-Type": "application/json",
228
+ "Connection": "keep-alive",
229
+ }
230
+ return headers
231
+
232
+ def generate_authentication(self, authentication_type: str, *args, **kwargs) -> Any:
233
+ """
234
+ Generate the authentication for the request.
235
+
236
+ :param args: Additional positional arguments (unused).
237
+ :param kwargs: Additional keyword arguments (unused).
238
+ :return: the Authentication.
239
+ """
240
+ if authentication_type == "HTTPBasicAuth":
241
+ return self.generate_http_authentication(*args, **kwargs)
242
+ else:
243
+ return None
244
+
245
+ @staticmethod
246
+ def generate_http_authentication(username: str, password: str) -> Any:
247
+ """
248
+ Generate HTTP authentication for the request.
249
+
250
+ :param username: The username for authentication.
251
+ :param password: The password for authentication.
252
+ :return: The HTTP authentication.
253
+ """
254
+ return HTTPBasicAuth(username=username, password=password)
255
+
256
+ @staticmethod
257
+ def partition_list_into_chunks(item_list: List[Any], chunk_length: int = 100) -> Generator[List[Any], None, None]:
258
+ """
259
+ Return a generator that yields successive n-size chunks of list of items.
260
+
261
+ :param item_list: a list of items.
262
+ :param chunk_length: the length of each chunk.
263
+ """
264
+ for i in range(0, len(item_list), chunk_length):
265
+ yield item_list[i:i + chunk_length]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: data-retrieval-module
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: A standardized interface for data providers with sync and async support
5
5
  Author-email: AbigailWilliams1692 <abigail.williams@example.com>
6
6
  Maintainer-email: AbigailWilliams1692 <abigail.williams@example.com>
@@ -8,6 +8,12 @@ requirements-test.txt
8
8
  setup.py
9
9
  data_retrieval/__init__.py
10
10
  data_retrieval/py.typed
11
+ data_retrieval/data_provider/__init__.py
12
+ data_retrieval/data_provider/database/__init__.py
13
+ data_retrieval/data_provider/database/database_data_provider.py
14
+ data_retrieval/data_provider/database/sqlite3_data_provider.py
15
+ data_retrieval/data_provider/rest_api/__init__.py
16
+ data_retrieval/data_provider/rest_api/rest_api_data_provider.py
11
17
  data_retrieval/foreign_exchange/__init__.py
12
18
  data_retrieval/foreign_exchange/forex_data_provider_base.py
13
19
  data_retrieval/foreign_exchange/forex_data_provider_wrapper.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "data-retrieval-module"
7
- version = "1.0.1"
7
+ version = "1.0.3"
8
8
  description = "A standardized interface for data providers with sync and async support"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -80,7 +80,7 @@ Repository = "https://github.com/AbigailWilliams1692/data-retrieval-module"
80
80
  Changelog = "https://github.com/AbigailWilliams1692/data-retrieval-module/blob/main/CHANGELOG.md"
81
81
 
82
82
  [tool.setuptools]
83
- packages = ["data_retrieval", "data_retrieval.model", "data_retrieval.foreign_exchange", "data_retrieval.utils"]
83
+ packages = ["data_retrieval", "data_retrieval.model", "data_retrieval.foreign_exchange", "data_retrieval.utils", "data_retrieval.data_provider", "data_retrieval.data_provider.database", "data_retrieval.data_provider.rest_api"]
84
84
 
85
85
  [tool.setuptools.package-data]
86
86
  data_retrieval = ["py.typed"]
@@ -26,7 +26,7 @@ def test_version():
26
26
  import data_retrieval
27
27
 
28
28
  assert hasattr(data_retrieval, '__version__')
29
- assert data_retrieval.__version__ == "1.0.1"
29
+ assert data_retrieval.__version__ == "1.0.2"
30
30
 
31
31
  # Test package structure
32
32
  def test_package_structure():