data-retrieval-module 1.0.0__py3-none-any.whl
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.
- data_retrieval/__init__.py +61 -0
- data_retrieval/model/__init__.py +11 -0
- data_retrieval/model/data_module.py +150 -0
- data_retrieval/model/data_provider.py +313 -0
- data_retrieval/model/exceptions.py +38 -0
- data_retrieval/py.typed +2 -0
- data_retrieval_module-1.0.0.dist-info/METADATA +350 -0
- data_retrieval_module-1.0.0.dist-info/RECORD +11 -0
- data_retrieval_module-1.0.0.dist-info/WHEEL +5 -0
- data_retrieval_module-1.0.0.dist-info/licenses/LICENSE +21 -0
- data_retrieval_module-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: __init__.py
|
|
4
|
+
# Description: Data Retrieval Module package initialization
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2026-01-14
|
|
7
|
+
# Updated: 2026-01-18
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
#######################################################################
|
|
11
|
+
# Import Core Classes
|
|
12
|
+
#######################################################################
|
|
13
|
+
from data_retrieval.model.data_provider import DataProvider
|
|
14
|
+
from data_retrieval.model.data_module import DataModule
|
|
15
|
+
from data_retrieval.model.exceptions import (
|
|
16
|
+
DataProviderError,
|
|
17
|
+
DataProviderConnectionError,
|
|
18
|
+
DataFetchError,
|
|
19
|
+
DataMethodNotFoundError,
|
|
20
|
+
ReturnDataTypeNotMatchedError,
|
|
21
|
+
ValidationError
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
#######################################################################
|
|
25
|
+
# Import Data Provider Implementations
|
|
26
|
+
#######################################################################
|
|
27
|
+
# REST API providers
|
|
28
|
+
from data_retrieval.data_provider.rest_api import RestAPI_DataProvider
|
|
29
|
+
|
|
30
|
+
# Database providers
|
|
31
|
+
from data_retrieval.data_provider.database import Database_DataProvider
|
|
32
|
+
|
|
33
|
+
#######################################################################
|
|
34
|
+
# Public API
|
|
35
|
+
#######################################################################
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Core classes
|
|
38
|
+
"DataProvider",
|
|
39
|
+
"DataModule",
|
|
40
|
+
|
|
41
|
+
# Exceptions
|
|
42
|
+
"DataProviderError",
|
|
43
|
+
"DataProviderConnectionError",
|
|
44
|
+
"DataFetchError",
|
|
45
|
+
"DataMethodNotFoundError",
|
|
46
|
+
"ReturnDataTypeNotMatchedError",
|
|
47
|
+
"ValidationError",
|
|
48
|
+
|
|
49
|
+
# REST API providers
|
|
50
|
+
"RestAPI_DataProvider",
|
|
51
|
+
|
|
52
|
+
# Database providers
|
|
53
|
+
"Database_DataProvider",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
#######################################################################
|
|
57
|
+
# Version Information
|
|
58
|
+
#######################################################################
|
|
59
|
+
__version__ = "1.0.0"
|
|
60
|
+
__author__ = "AbigailWilliams1692"
|
|
61
|
+
__email__ = "abigail.williams@example.com"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: __init__.py
|
|
4
|
+
# Description: Model Package Initialization
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2026-01-14
|
|
7
|
+
# Updated: 2026-01-18
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
from data_retrieval.model.data_module import DataModule
|
|
11
|
+
from data_retrieval.model.data_provider import DataProvider
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: data_module.py
|
|
4
|
+
# Description: Abstract base class for aall data modules
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2025-11-13
|
|
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 typing import Any, Optional
|
|
17
|
+
|
|
18
|
+
# Local Packages
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#################################################
|
|
22
|
+
# Class Definition
|
|
23
|
+
#################################################
|
|
24
|
+
class DataModule(ABC):
|
|
25
|
+
"""
|
|
26
|
+
Data Module Abstract Base Class. Provides the interface for all data modules.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
#################################################
|
|
30
|
+
# Class Attributes
|
|
31
|
+
#################################################
|
|
32
|
+
__name: str = "BaseDataModule"
|
|
33
|
+
__type: str = "DataModule"
|
|
34
|
+
|
|
35
|
+
#################################################
|
|
36
|
+
# Constructor
|
|
37
|
+
#################################################
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
instance_id: Optional[int] = None,
|
|
41
|
+
logger: Optional[logging.Logger] = None,
|
|
42
|
+
log_level: Optional[int] = logging.INFO,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Constructor method.
|
|
46
|
+
|
|
47
|
+
:param instance_id: str: Unique identifier for the data module instance.
|
|
48
|
+
:param logger: logging.Logger: Logger instance for logging.
|
|
49
|
+
"""
|
|
50
|
+
# DataProvider ID
|
|
51
|
+
self._instance_id = instance_id or id(self)
|
|
52
|
+
|
|
53
|
+
# Log Level
|
|
54
|
+
self._log_level = log_level
|
|
55
|
+
|
|
56
|
+
# Logger
|
|
57
|
+
self._logger = logger or self.refresh_logger()
|
|
58
|
+
|
|
59
|
+
# Status
|
|
60
|
+
self._status = None
|
|
61
|
+
|
|
62
|
+
#################################################
|
|
63
|
+
# Getter & Setter Methods
|
|
64
|
+
#################################################
|
|
65
|
+
def get_name(self) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Get the name of the data module.
|
|
68
|
+
|
|
69
|
+
:return: str: Name of the data module.
|
|
70
|
+
"""
|
|
71
|
+
return self.__name
|
|
72
|
+
|
|
73
|
+
def get_type(self) -> str:
|
|
74
|
+
"""
|
|
75
|
+
Get the type of the data module.
|
|
76
|
+
|
|
77
|
+
:return: str: Type of the data module.
|
|
78
|
+
"""
|
|
79
|
+
return self.__type
|
|
80
|
+
|
|
81
|
+
def get_instance_id(self) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Get the unique identifier of the data module instance.
|
|
84
|
+
|
|
85
|
+
:return: str: Unique identifier of the data module instance.
|
|
86
|
+
"""
|
|
87
|
+
return self._instance_id
|
|
88
|
+
|
|
89
|
+
def set_instance_id(self, instance_id: str) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Set the unique identifier of the data module instance.
|
|
92
|
+
|
|
93
|
+
:param instance_id: str: Unique identifier of the data module instance.
|
|
94
|
+
"""
|
|
95
|
+
self._instance_id = instance_id
|
|
96
|
+
|
|
97
|
+
def get_logger(self) -> logging.Logger:
|
|
98
|
+
"""
|
|
99
|
+
Get the logger instance.
|
|
100
|
+
|
|
101
|
+
:return: logging.Logger: Logger instance.
|
|
102
|
+
"""
|
|
103
|
+
return self._logger
|
|
104
|
+
|
|
105
|
+
def set_logger(self, logger: logging.Logger) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Set the logger instance.
|
|
108
|
+
|
|
109
|
+
:param logger: logging.Logger: Logger instance.
|
|
110
|
+
"""
|
|
111
|
+
self._logger = logger
|
|
112
|
+
|
|
113
|
+
def refresh_logger(self) -> logging.Logger:
|
|
114
|
+
"""
|
|
115
|
+
Refresh the logger instance.
|
|
116
|
+
"""
|
|
117
|
+
logger = logging.getLogger(name=self.__class__.__name__)
|
|
118
|
+
logger.setLevel(self._log_level)
|
|
119
|
+
return logger
|
|
120
|
+
|
|
121
|
+
def get_log_level(self) -> int:
|
|
122
|
+
"""
|
|
123
|
+
Get the log level of the logger instance.
|
|
124
|
+
|
|
125
|
+
:return: int: Log level of the logger instance.
|
|
126
|
+
"""
|
|
127
|
+
return self._log_level
|
|
128
|
+
|
|
129
|
+
def set_log_level(self, log_level: int) -> None:
|
|
130
|
+
"""
|
|
131
|
+
Set the log level of the logger instance.
|
|
132
|
+
|
|
133
|
+
:param log_level: int: Log level of the logger instance.
|
|
134
|
+
"""
|
|
135
|
+
self._logger.setLevel(level=log_level)
|
|
136
|
+
|
|
137
|
+
def get_status(self) -> Any:
|
|
138
|
+
"""Get the preoccupation status of the data module.
|
|
139
|
+
|
|
140
|
+
:return: Any: Preoccupation status of the data module.
|
|
141
|
+
"""
|
|
142
|
+
return self._status
|
|
143
|
+
|
|
144
|
+
def set_status(self, status: Any) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Set the preoccupation status of the data module.
|
|
147
|
+
|
|
148
|
+
:param status: Any: Preoccupation status of the data module.
|
|
149
|
+
"""
|
|
150
|
+
self._status = status
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: data_provider.py
|
|
4
|
+
# Description: Abstract base class for data providers
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2025-11-13
|
|
7
|
+
# Updated: 2026-01-18
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
#######################################################################
|
|
11
|
+
# Import Packages
|
|
12
|
+
#######################################################################
|
|
13
|
+
# Standard Packages
|
|
14
|
+
import logging
|
|
15
|
+
from abc import abstractmethod
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import (
|
|
19
|
+
Any,
|
|
20
|
+
Optional,
|
|
21
|
+
Dict,
|
|
22
|
+
Callable,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Local Packages
|
|
26
|
+
from data_retrieval.model.data_module import DataModule
|
|
27
|
+
from data_retrieval.model.exceptions import (
|
|
28
|
+
DataProviderConnectionError,
|
|
29
|
+
DataMethodNotFoundError,
|
|
30
|
+
ReturnDataTypeNotMatchedError,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#######################################################################
|
|
35
|
+
# Enums & Data Classes
|
|
36
|
+
#######################################################################
|
|
37
|
+
class DataProviderConnectionStatus(Enum):
|
|
38
|
+
"""
|
|
39
|
+
Enumeration of provider connection states.
|
|
40
|
+
"""
|
|
41
|
+
DISCONNECTED = "disconnected"
|
|
42
|
+
CONNECTED = "connected"
|
|
43
|
+
ERROR = "error"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#######################################################################
|
|
47
|
+
# Data Provider Class
|
|
48
|
+
#######################################################################
|
|
49
|
+
class DataProvider(DataModule):
|
|
50
|
+
"""
|
|
51
|
+
Abstract base class for data providers with standardized synchronous API interface.
|
|
52
|
+
|
|
53
|
+
This class provides a unified interface for retrieving data from various sources
|
|
54
|
+
(APIs, databases, files, etc.). Subclasses must implement the abstract methods
|
|
55
|
+
to provide source-specific data retrieval logic.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
#################################################
|
|
59
|
+
# Class Attributes
|
|
60
|
+
#################################################
|
|
61
|
+
__name: str = "DataProvider"
|
|
62
|
+
__type: str = "DataProvider"
|
|
63
|
+
|
|
64
|
+
#################################################
|
|
65
|
+
# Constructor
|
|
66
|
+
#################################################
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
instance_id: Optional[int] = None,
|
|
70
|
+
logger: Optional[logging.Logger] = None,
|
|
71
|
+
log_level: Optional[int] = logging.INFO,
|
|
72
|
+
**config,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Initialize the data provider.
|
|
76
|
+
|
|
77
|
+
:param instance_id: Unique identifier for this provider instance.
|
|
78
|
+
:param logger: Logger instance for logging operations.
|
|
79
|
+
:param log_level: Logging level for the provider.
|
|
80
|
+
:param data_methods: Dictionary of data retrieval methods.
|
|
81
|
+
:param config: Additional configuration parameters.
|
|
82
|
+
"""
|
|
83
|
+
# Initialize the base DataModule
|
|
84
|
+
super().__init__(
|
|
85
|
+
instance_id=instance_id,
|
|
86
|
+
logger=logger,
|
|
87
|
+
log_level=log_level,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Intialize DataProvider attributes
|
|
91
|
+
self._config = config or {}
|
|
92
|
+
self._connection = None
|
|
93
|
+
self._data_methods = {}
|
|
94
|
+
|
|
95
|
+
# Set initial status for the DataProvider instance
|
|
96
|
+
self.set_status(DataProviderConnectionStatus.DISCONNECTED)
|
|
97
|
+
|
|
98
|
+
#################################################
|
|
99
|
+
# Getter & Setter Methods
|
|
100
|
+
#################################################
|
|
101
|
+
def get_config(self) -> Dict[str, Any]:
|
|
102
|
+
"""
|
|
103
|
+
Get the configuration dictionary.
|
|
104
|
+
|
|
105
|
+
:return: The configuration dictionary.
|
|
106
|
+
"""
|
|
107
|
+
return self._config
|
|
108
|
+
|
|
109
|
+
def set_config(self, config: Dict[str, Any]) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Set the configuration dictionary.
|
|
112
|
+
|
|
113
|
+
:param config: The configuration dictionary.
|
|
114
|
+
:return: None.
|
|
115
|
+
"""
|
|
116
|
+
self._config = config
|
|
117
|
+
|
|
118
|
+
def update_config(self, key: str, value: Any) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Update a specific configuration key-value pair.
|
|
121
|
+
|
|
122
|
+
:param key: The configuration key to update.
|
|
123
|
+
:param value: The new value for the configuration key.
|
|
124
|
+
:return: None.
|
|
125
|
+
"""
|
|
126
|
+
self._config.update({key: value})
|
|
127
|
+
|
|
128
|
+
def get_connection(self) -> Any:
|
|
129
|
+
"""
|
|
130
|
+
Get the connection object.
|
|
131
|
+
|
|
132
|
+
:return: The connection object.
|
|
133
|
+
"""
|
|
134
|
+
return self._connection
|
|
135
|
+
|
|
136
|
+
def set_connection(self, connection: Any) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Set the connection object.
|
|
139
|
+
|
|
140
|
+
:param connection: The connection object.
|
|
141
|
+
:return: None.
|
|
142
|
+
"""
|
|
143
|
+
self._connection = connection
|
|
144
|
+
|
|
145
|
+
def get_data_methods(self) -> Dict[str, Any]:
|
|
146
|
+
"""
|
|
147
|
+
Get the data methods dictionary.
|
|
148
|
+
|
|
149
|
+
:return: The data methods dictionary.
|
|
150
|
+
"""
|
|
151
|
+
return self._data_methods
|
|
152
|
+
|
|
153
|
+
def get_data_method(self, data_point: str) -> Optional[Callable]:
|
|
154
|
+
"""
|
|
155
|
+
Get a data method from the data methods dictionary.
|
|
156
|
+
|
|
157
|
+
:param data_point: The name of the data method.
|
|
158
|
+
:return: The data method.
|
|
159
|
+
"""
|
|
160
|
+
return self._data_methods.get(data_point)
|
|
161
|
+
|
|
162
|
+
def update_data_methods(self, new_methods: Dict[str, Callable]) -> None:
|
|
163
|
+
"""
|
|
164
|
+
Update the data methods with new methods, merging with existing ones.
|
|
165
|
+
|
|
166
|
+
:param new_methods: Dictionary of new data methods to add/update.
|
|
167
|
+
:return: None.
|
|
168
|
+
"""
|
|
169
|
+
self._data_methods.update(new_methods)
|
|
170
|
+
|
|
171
|
+
def set_data_methods(self, data_methods: Dict[str, Any]) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Set the data methods dictionary.
|
|
174
|
+
|
|
175
|
+
:param data_methods: The data methods dictionary.
|
|
176
|
+
:return: None.
|
|
177
|
+
"""
|
|
178
|
+
self._data_methods = data_methods
|
|
179
|
+
|
|
180
|
+
def add_data_method(self, data_point: str, method: Any) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Add a data method to the data methods dictionary.
|
|
183
|
+
|
|
184
|
+
:param data_point: The name of the data method.
|
|
185
|
+
:param method: The data method.
|
|
186
|
+
:return: None.
|
|
187
|
+
"""
|
|
188
|
+
self._data_methods.update(
|
|
189
|
+
{
|
|
190
|
+
data_point: method,
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def delete_data_method(self, data_point: str) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Delete a data method from the data methods dictionary.
|
|
197
|
+
|
|
198
|
+
:param data_point: The name of the data method to delete.
|
|
199
|
+
:return: None.
|
|
200
|
+
"""
|
|
201
|
+
if data_point in self._data_methods:
|
|
202
|
+
del self._data_methods[data_point]
|
|
203
|
+
|
|
204
|
+
#################################################
|
|
205
|
+
# Connection Methods
|
|
206
|
+
#################################################
|
|
207
|
+
def is_connected(self) -> bool:
|
|
208
|
+
"""
|
|
209
|
+
Check if the data provider is connected to the data source.
|
|
210
|
+
|
|
211
|
+
:return: True if connected, False otherwise.
|
|
212
|
+
"""
|
|
213
|
+
return self._connection is not None
|
|
214
|
+
|
|
215
|
+
def connect(self, *args, **kwargs) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Safely Connect to the data source.
|
|
218
|
+
|
|
219
|
+
:param args: Positional arguments for connection.
|
|
220
|
+
:param kwargs: Keyword arguments for connection.
|
|
221
|
+
:return: None.
|
|
222
|
+
:raise ConnectionError: If connection fails.
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
self._connect(*args, **kwargs)
|
|
226
|
+
self.set_status(status=DataProviderConnectionStatus.CONNECTED)
|
|
227
|
+
except Exception as e:
|
|
228
|
+
raise DataProviderConnectionError(f"Failed to connect to data source: {e}")
|
|
229
|
+
|
|
230
|
+
@abstractmethod
|
|
231
|
+
def _connect(self, *args, **kwargs):
|
|
232
|
+
"""
|
|
233
|
+
Abstract method to connect to the data source.
|
|
234
|
+
"""
|
|
235
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
236
|
+
|
|
237
|
+
def disconnect(self, *args, **kwargs) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Safely disconnect from the data source.
|
|
240
|
+
|
|
241
|
+
:param args: Positional arguments for connection.
|
|
242
|
+
:param kwargs: Keyword arguments for connection.
|
|
243
|
+
:return: None.
|
|
244
|
+
:raise ConnectionError: If connection fails.
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
self._disconnect(*args, **kwargs)
|
|
248
|
+
self.set_status(status=DataProviderConnectionStatus.DISCONNECTED)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
raise DataProviderConnectionError(f"Failed to disconnect from data source: {e}")
|
|
251
|
+
|
|
252
|
+
@abstractmethod
|
|
253
|
+
def _disconnect(self, *args, **kwargs):
|
|
254
|
+
"""
|
|
255
|
+
Abstract method to disconnect from the data source.
|
|
256
|
+
"""
|
|
257
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
258
|
+
|
|
259
|
+
def refresh_connection(self, *args, **kwargs) -> None:
|
|
260
|
+
"""
|
|
261
|
+
Refresh the connection.
|
|
262
|
+
|
|
263
|
+
:param args: Positional arguments for connection.
|
|
264
|
+
:param kwargs: Keyword arguments for connection.
|
|
265
|
+
:return: None.
|
|
266
|
+
:raise ConnectionError: If connection fails.
|
|
267
|
+
"""
|
|
268
|
+
self.disconnect(*args, **kwargs)
|
|
269
|
+
self.connect(*args, **kwargs)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
#################################################
|
|
273
|
+
# Context Management
|
|
274
|
+
#################################################
|
|
275
|
+
def __enter__(self) -> "DataProvider":
|
|
276
|
+
"""
|
|
277
|
+
Enter the runtime context related to this object. Automatically connects to the data source.
|
|
278
|
+
"""
|
|
279
|
+
return self
|
|
280
|
+
|
|
281
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
282
|
+
"""
|
|
283
|
+
Exit the runtime context related to this object. Automatically disconnects from the data source.
|
|
284
|
+
"""
|
|
285
|
+
self.disconnect()
|
|
286
|
+
|
|
287
|
+
#################################################
|
|
288
|
+
# Core Instance Method: Fetch Data
|
|
289
|
+
#################################################
|
|
290
|
+
def fetch_data(self, data_point: str, return_data_type: type, *args, **kwargs) -> Any:
|
|
291
|
+
"""
|
|
292
|
+
Base method to fetch data based on the data point and return type.
|
|
293
|
+
|
|
294
|
+
:param data_point: str: The data point to fetch.
|
|
295
|
+
:param return_data_type: type: The type of data to return.
|
|
296
|
+
:param args: Tuple: Positional arguments for fetching data.
|
|
297
|
+
:param kwargs: Dict: Keyword arguments for fetching data.
|
|
298
|
+
:return: The fetched data.
|
|
299
|
+
"""
|
|
300
|
+
# Extract the corresponding data method
|
|
301
|
+
data_method = self.get_data_method(data_point=data_point)
|
|
302
|
+
if data_method is None:
|
|
303
|
+
raise DataMethodNotFoundError(f"Data method for data point '{data_point}' not found.")
|
|
304
|
+
|
|
305
|
+
# Retrieve the data
|
|
306
|
+
data = data_method(*args, **kwargs)
|
|
307
|
+
|
|
308
|
+
# Check the return data type
|
|
309
|
+
if not isinstance(data, return_data_type):
|
|
310
|
+
raise ReturnDataTypeNotMatchedError(f"Data type mismatch. Expected {return_data_type}, got {type(data)}.")
|
|
311
|
+
|
|
312
|
+
return data
|
|
313
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: exceptions.py
|
|
4
|
+
# Description: Exception classes for data providers
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2026-01-14
|
|
7
|
+
# Updated: 2026-01-19
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
#######################################################################
|
|
11
|
+
# Exception Classes
|
|
12
|
+
#######################################################################
|
|
13
|
+
class DataProviderError(Exception):
|
|
14
|
+
"""Base exception for data provider errors."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DataProviderConnectionError(DataProviderError):
|
|
19
|
+
"""Raised when connection to data source fails."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DataFetchError(DataProviderError):
|
|
24
|
+
"""Raised when a query operation fails."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
class DataMethodNotFoundError(DataProviderError):
|
|
28
|
+
"""Raised when a requested data method is not found."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
class ReturnDataTypeNotMatchedError(DataProviderError):
|
|
32
|
+
"""Raised when the retrieved data type does not match the expected type."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ValidationError(DataProviderError):
|
|
37
|
+
"""Raised when data validation fails."""
|
|
38
|
+
pass
|
data_retrieval/py.typed
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-retrieval-module
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A standardized interface for data providers with sync and async support
|
|
5
|
+
Author-email: AbigailWilliams1692 <abigail.williams@example.com>
|
|
6
|
+
Maintainer-email: AbigailWilliams1692 <abigail.williams@example.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/AbigailWilliams1692/data-retrieval-module
|
|
9
|
+
Project-URL: Documentation, https://github.com/AbigailWilliams1692/data-retrieval-module/wiki
|
|
10
|
+
Project-URL: Repository, https://github.com/AbigailWilliams1692/data-retrieval-module
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/AbigailWilliams1692/data-retrieval-module/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/AbigailWilliams1692/data-retrieval-module/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: data,retrieval,provider,async,sync,database,api
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: typing-extensions>=4.0.0; python_version < "3.10"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
36
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
40
|
+
Provides-Extra: async
|
|
41
|
+
Requires-Dist: aiohttp>=3.8.0; extra == "async"
|
|
42
|
+
Requires-Dist: aiosqlite>=0.19.0; extra == "async"
|
|
43
|
+
Requires-Dist: asyncpg>=0.28.0; extra == "async"
|
|
44
|
+
Provides-Extra: database
|
|
45
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "database"
|
|
46
|
+
Requires-Dist: PyMySQL>=1.0.0; extra == "database"
|
|
47
|
+
Provides-Extra: all
|
|
48
|
+
Requires-Dist: pytest>=7.0.0; extra == "all"
|
|
49
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
|
|
50
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "all"
|
|
51
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "all"
|
|
52
|
+
Requires-Dist: black>=23.0.0; extra == "all"
|
|
53
|
+
Requires-Dist: flake8>=6.0.0; extra == "all"
|
|
54
|
+
Requires-Dist: mypy>=1.0.0; extra == "all"
|
|
55
|
+
Requires-Dist: isort>=5.12.0; extra == "all"
|
|
56
|
+
Requires-Dist: aiohttp>=3.8.0; extra == "all"
|
|
57
|
+
Requires-Dist: aiosqlite>=0.19.0; extra == "all"
|
|
58
|
+
Requires-Dist: asyncpg>=0.28.0; extra == "all"
|
|
59
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
|
|
60
|
+
Requires-Dist: PyMySQL>=1.0.0; extra == "all"
|
|
61
|
+
Dynamic: license-file
|
|
62
|
+
|
|
63
|
+
# Data Retrieval Module
|
|
64
|
+
|
|
65
|
+
A standardized interface for data providers with both synchronous and asynchronous support. This module provides abstract base classes that enable consistent data retrieval patterns across different data sources (APIs, databases, files, etc.).
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- 🔄 **Dual API Support**: Both sync and async interfaces
|
|
70
|
+
- 🏗️ **Abstract Base Classes**: Standardized patterns for data providers
|
|
71
|
+
- 🔌 **Connection Management**: Built-in connection handling with context managers
|
|
72
|
+
- 🔄 **Retry Logic**: Automatic retry with configurable parameters
|
|
73
|
+
- 📊 **Pagination Support**: Standardized pagination with QueryResult
|
|
74
|
+
- 🎣 **Hook Methods**: Customizable validation and transformation
|
|
75
|
+
- 🧪 **Type Safety**: Full type hints and generic support
|
|
76
|
+
- ✅ **Well Tested**: Comprehensive unit test coverage
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
### Basic Installation
|
|
81
|
+
```bash
|
|
82
|
+
pip install data-retrieval-module
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### With Async Support
|
|
86
|
+
```bash
|
|
87
|
+
pip install data-retrieval-module[async]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Development Installation
|
|
91
|
+
```bash
|
|
92
|
+
pip install data-retrieval-module[dev]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### All Features
|
|
96
|
+
```bash
|
|
97
|
+
pip install data-retrieval-module[all]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Quick Start
|
|
101
|
+
|
|
102
|
+
### Synchronous Data Provider
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from data_retrieval import DataProvider, QueryResult
|
|
106
|
+
from data_retrieval.model import ProviderStatus
|
|
107
|
+
|
|
108
|
+
class UserProvider(DataProvider[User]):
|
|
109
|
+
def _connect(self) -> None:
|
|
110
|
+
self._db = Database.connect(...)
|
|
111
|
+
|
|
112
|
+
def _disconnect(self) -> None:
|
|
113
|
+
self._db.close()
|
|
114
|
+
|
|
115
|
+
def fetch(self, *args, **kwargs) -> QueryResult[User]:
|
|
116
|
+
filters = kwargs.get("filters", {})
|
|
117
|
+
users = self._db.users.find(filters)
|
|
118
|
+
return QueryResult(
|
|
119
|
+
data=users,
|
|
120
|
+
total_count=len(users),
|
|
121
|
+
metadata={"source": "database"}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Usage
|
|
125
|
+
provider = UserProvider()
|
|
126
|
+
with provider.connection(host="localhost", port=5432):
|
|
127
|
+
result = provider.fetch(filters={"active": True})
|
|
128
|
+
for user in result.data:
|
|
129
|
+
print(user.name)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Asynchronous Data Provider
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from data_retrieval import AsyncDataProvider
|
|
136
|
+
|
|
137
|
+
class AsyncUserProvider(AsyncDataProvider[User]):
|
|
138
|
+
async def _connect(self) -> None:
|
|
139
|
+
self._db = await Database.connect(...)
|
|
140
|
+
|
|
141
|
+
async def _disconnect(self) -> None:
|
|
142
|
+
await self._db.close()
|
|
143
|
+
|
|
144
|
+
async def fetch(self, *args, **kwargs) -> QueryResult[User]:
|
|
145
|
+
filters = kwargs.get("filters", {})
|
|
146
|
+
users = await self._db.users.find(filters)
|
|
147
|
+
return QueryResult(
|
|
148
|
+
data=users,
|
|
149
|
+
total_count=len(users),
|
|
150
|
+
metadata={"source": "database"}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Usage
|
|
154
|
+
async def main():
|
|
155
|
+
provider = AsyncUserProvider()
|
|
156
|
+
async with provider.async_connection(host="localhost", port=5432) as p:
|
|
157
|
+
result = await p.fetch(filters={"active": True})
|
|
158
|
+
for user in result.data:
|
|
159
|
+
print(user.name)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Core Classes
|
|
163
|
+
|
|
164
|
+
### DataProvider (Synchronous)
|
|
165
|
+
|
|
166
|
+
Abstract base class for synchronous data providers.
|
|
167
|
+
|
|
168
|
+
**Key Methods:**
|
|
169
|
+
- `connect(**config)` - Establish connection
|
|
170
|
+
- `disconnect()` - Close connection
|
|
171
|
+
- `fetch(*args, **kwargs)` - Retrieve data
|
|
172
|
+
- `fetch_or_raise(*args, **kwargs)` - Fetch with error handling
|
|
173
|
+
- `with_retry(operation, max_retries, retry_delay)` - Retry logic
|
|
174
|
+
|
|
175
|
+
**Hook Methods:**
|
|
176
|
+
- `validate(data)` - Validate data
|
|
177
|
+
- `transform(data)` - Transform data
|
|
178
|
+
- `health_check()` - Health status
|
|
179
|
+
|
|
180
|
+
### AsyncDataProvider (Asynchronous)
|
|
181
|
+
|
|
182
|
+
Abstract base class for asynchronous data providers.
|
|
183
|
+
|
|
184
|
+
**Key Methods:**
|
|
185
|
+
- `async connect(**config)` - Establish connection
|
|
186
|
+
- `async disconnect()` - Close connection
|
|
187
|
+
- `async fetch(*args, **kwargs)` - Retrieve data
|
|
188
|
+
- `async fetch_or_raise(*args, **kwargs)` - Fetch with error handling
|
|
189
|
+
- `async with_retry(operation, max_retries, retry_delay)` - Retry logic
|
|
190
|
+
|
|
191
|
+
### QueryResult
|
|
192
|
+
|
|
193
|
+
Standardized container for query results.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
@dataclass
|
|
197
|
+
class QueryResult[T]:
|
|
198
|
+
data: List[T]
|
|
199
|
+
total_count: int
|
|
200
|
+
metadata: Dict[str, Any]
|
|
201
|
+
|
|
202
|
+
def is_empty(self) -> bool:
|
|
203
|
+
return self.total_count == 0
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Advanced Usage
|
|
207
|
+
|
|
208
|
+
### Custom Validation
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
class ValidatedProvider(DataProvider[User]):
|
|
212
|
+
def validate(self, data: User) -> bool:
|
|
213
|
+
# Custom validation logic
|
|
214
|
+
return data.email and "@" in data.email
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Data Transformation
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
class TransformingProvider(DataProvider[User]):
|
|
221
|
+
def transform(self, data: dict) -> User:
|
|
222
|
+
# Convert raw data to User object
|
|
223
|
+
return User(**data)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Retry Logic
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
provider = MyProvider()
|
|
230
|
+
|
|
231
|
+
# Retry with custom parameters
|
|
232
|
+
result = provider.with_retry(
|
|
233
|
+
operation=lambda: provider.fetch(filters={"id": "123"}),
|
|
234
|
+
max_retries=5,
|
|
235
|
+
retry_delay=2.0,
|
|
236
|
+
parameters={}
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Context Managers
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
# Automatic connection management
|
|
244
|
+
with provider.connection(host="localhost") as p:
|
|
245
|
+
data = p.fetch()
|
|
246
|
+
|
|
247
|
+
# Async version
|
|
248
|
+
async with provider.async_connection(host="localhost") as p:
|
|
249
|
+
data = await p.fetch()
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Error Handling
|
|
253
|
+
|
|
254
|
+
The module provides specific exception types:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from data_retrieval.model.exceptions import (
|
|
258
|
+
DataProviderError,
|
|
259
|
+
ConnectionError,
|
|
260
|
+
QueryError,
|
|
261
|
+
ValidationError
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
result = provider.fetch(filters={"invalid": "field"})
|
|
266
|
+
except ConnectionError as e:
|
|
267
|
+
print(f"Connection failed: {e}")
|
|
268
|
+
except QueryError as e:
|
|
269
|
+
print(f"Query failed: {e}")
|
|
270
|
+
except DataProviderError as e:
|
|
271
|
+
print(f"General error: {e}")
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Development
|
|
275
|
+
|
|
276
|
+
### Setup Development Environment
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Clone repository
|
|
280
|
+
git clone https://github.com/AbigailWilliams1692/data-retrieval-module.git
|
|
281
|
+
cd data-retrieval-module
|
|
282
|
+
|
|
283
|
+
# Create virtual environment
|
|
284
|
+
python -m venv venv
|
|
285
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
286
|
+
|
|
287
|
+
# Install development dependencies
|
|
288
|
+
pip install -e .[dev]
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Running Tests
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Run all tests
|
|
295
|
+
pytest
|
|
296
|
+
|
|
297
|
+
# Run with coverage
|
|
298
|
+
pytest --cov=data_retrieval --cov-report=html
|
|
299
|
+
|
|
300
|
+
# Run specific test file
|
|
301
|
+
pytest tests/test_data_provider.py
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Code Quality
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
# Format code
|
|
308
|
+
black data_retrieval/ tests/
|
|
309
|
+
|
|
310
|
+
# Sort imports
|
|
311
|
+
isort data_retrieval/ tests/
|
|
312
|
+
|
|
313
|
+
# Type checking
|
|
314
|
+
mypy data_retrieval/
|
|
315
|
+
|
|
316
|
+
# Linting
|
|
317
|
+
flake8 data_retrieval/ tests/
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Contributing
|
|
321
|
+
|
|
322
|
+
1. Fork the repository
|
|
323
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
324
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
325
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
326
|
+
5. Open a Pull Request
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
331
|
+
|
|
332
|
+
## Changelog
|
|
333
|
+
|
|
334
|
+
See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history.
|
|
335
|
+
|
|
336
|
+
## Support
|
|
337
|
+
|
|
338
|
+
- 📖 [Documentation](https://github.com/AbigailWilliams1692/data-retrieval-module/wiki)
|
|
339
|
+
- 🐛 [Bug Reports](https://github.com/AbigailWilliams1692/data-retrieval-module/issues)
|
|
340
|
+
- 💬 [Discussions](https://github.com/AbigailWilliams1692/data-retrieval-module/discussions)
|
|
341
|
+
|
|
342
|
+
## Related Projects
|
|
343
|
+
|
|
344
|
+
- [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) - SQL toolkit and ORM
|
|
345
|
+
- [Django ORM](https://github.com/django/django) - Django's database ORM
|
|
346
|
+
- [Tortoise ORM](https://github.com/tortoise/tortoise-orm) - Async ORM for Python
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
**Made with ❤️ by [AbigailWilliams1692](https://github.com/AbigailWilliams1692)**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
data_retrieval/__init__.py,sha256=uJboXyByW_UUD1UF7Si1APOxUGmGPAZMSBcVO2m2wuo,2000
|
|
2
|
+
data_retrieval/py.typed,sha256=-NTLJWHpx_8vs__Ouvm4nvtZ1-l6FqRKyJ2iqfxmutE,158
|
|
3
|
+
data_retrieval/model/__init__.py,sha256=ZVqtFkRxqTCiqyOfKEeJ7XVNBreQLsN5GRnf-bOOJNk,432
|
|
4
|
+
data_retrieval/model/data_module.py,sha256=CWBRh35_4UV7PQ0pz0bl2xgi45Otd_z5rgi1rBlIlYI,4281
|
|
5
|
+
data_retrieval/model/data_provider.py,sha256=i5D12nwYOVGopVemBlAoX6BhC0wrw3ftNQyifESQjII,10315
|
|
6
|
+
data_retrieval/model/exceptions.py,sha256=ySs--qJ9fJV-cprAb69peZhQx1ppv5RbzK5nbjDQjUc,1169
|
|
7
|
+
data_retrieval_module-1.0.0.dist-info/licenses/LICENSE,sha256=Mcf9NKkIfu5xuYsMLyvzhYXnm9OwZ8jko8scfkocBBU,1073
|
|
8
|
+
data_retrieval_module-1.0.0.dist-info/METADATA,sha256=uAY7libj5VgumIrLjGVYc797MbFcXUCJqgLr4PxKjzo,10150
|
|
9
|
+
data_retrieval_module-1.0.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
10
|
+
data_retrieval_module-1.0.0.dist-info/top_level.txt,sha256=02EwIYUSaidyp_6eSOKa1TTYSE71RmhFlpkgI5OQ6nk,15
|
|
11
|
+
data_retrieval_module-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Abigail Williams
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
data_retrieval
|