data-retrieval-module 1.0.0__tar.gz → 1.0.1__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.
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/CHANGELOG.md +23 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/PKG-INFO +2 -1
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/__init__.py +13 -1
- data_retrieval_module-1.0.1/data_retrieval/foreign_exchange/__init__.py +3 -0
- data_retrieval_module-1.0.1/data_retrieval/foreign_exchange/forex_data_provider_base.py +140 -0
- data_retrieval_module-1.0.1/data_retrieval/foreign_exchange/forex_data_provider_wrapper.py +148 -0
- data_retrieval_module-1.0.1/data_retrieval/foreign_exchange/forex_python_data_provider.py +161 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/model/__init__.py +2 -1
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/model/data_provider.py +0 -1
- data_retrieval_module-1.0.1/data_retrieval/model/data_provider_wrapper.py +428 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/model/exceptions.py +23 -2
- data_retrieval_module-1.0.1/data_retrieval/utils/__init__.py +0 -0
- data_retrieval_module-1.0.1/data_retrieval/utils/date_utils.py +54 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval_module.egg-info/PKG-INFO +2 -1
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval_module.egg-info/SOURCES.txt +7 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval_module.egg-info/requires.txt +1 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/pyproject.toml +3 -2
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/tests/test_basic.py +1 -1
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/LICENSE +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/MANIFEST.in +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/Makefile +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/README.md +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/model/data_module.py +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/py.typed +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval_module.egg-info/dependency_links.txt +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval_module.egg-info/top_level.txt +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/requirements-test.txt +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/setup.cfg +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/setup.py +0 -0
- {data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/tests/__init__.py +0 -0
|
@@ -42,6 +42,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
42
42
|
- CI/CD ready setup
|
|
43
43
|
- Package publishing configuration
|
|
44
44
|
|
|
45
|
+
## [1.0.1] - 2026-01-25
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- Foreign Exchange data provider module
|
|
49
|
+
- `Forex_DataProvider_Base` abstract base class for forex data providers
|
|
50
|
+
- `ForexPython_DataProvider` concrete implementation using Python libraries
|
|
51
|
+
- `Forex_DataProvider_Wrapper` wrapper for enhanced functionality
|
|
52
|
+
- Utility modules
|
|
53
|
+
- `date_utils` module for date/time utilities
|
|
54
|
+
- Enhanced data provider wrapper functionality
|
|
55
|
+
- Examples for forex data sources
|
|
56
|
+
|
|
57
|
+
### Features
|
|
58
|
+
- **Forex Data Support**: Complete foreign exchange data provider framework
|
|
59
|
+
- **Utility Functions**: Date and time utilities for data processing
|
|
60
|
+
- **Enhanced Wrappers**: Improved data provider wrapper capabilities
|
|
61
|
+
- **Example Code**: Forex data source examples and usage patterns
|
|
62
|
+
|
|
63
|
+
### Documentation
|
|
64
|
+
- Added forex provider documentation
|
|
65
|
+
- Updated package exports to include new modules
|
|
66
|
+
- Enhanced examples with forex data sources
|
|
67
|
+
|
|
45
68
|
## [Unreleased]
|
|
46
69
|
|
|
47
70
|
### Planned
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: data-retrieval-module
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
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,6 +28,7 @@ Requires-Python: >=3.8
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
License-File: LICENSE
|
|
30
30
|
Requires-Dist: typing-extensions>=4.0.0; python_version < "3.10"
|
|
31
|
+
Requires-Dist: forex-python>=1.0.0
|
|
31
32
|
Provides-Extra: dev
|
|
32
33
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
34
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
@@ -30,6 +30,13 @@ from data_retrieval.data_provider.rest_api import RestAPI_DataProvider
|
|
|
30
30
|
# Database providers
|
|
31
31
|
from data_retrieval.data_provider.database import Database_DataProvider
|
|
32
32
|
|
|
33
|
+
# Foreign Exchange providers
|
|
34
|
+
from data_retrieval.foreign_exchange import (
|
|
35
|
+
Forex_DataProvider_Base,
|
|
36
|
+
ForexPython_DataProvider,
|
|
37
|
+
Forex_DataProvider_Wrapper
|
|
38
|
+
)
|
|
39
|
+
|
|
33
40
|
#######################################################################
|
|
34
41
|
# Public API
|
|
35
42
|
#######################################################################
|
|
@@ -51,11 +58,16 @@ __all__ = [
|
|
|
51
58
|
|
|
52
59
|
# Database providers
|
|
53
60
|
"Database_DataProvider",
|
|
61
|
+
|
|
62
|
+
# Foreign Exchange providers
|
|
63
|
+
"Forex_DataProvider_Base",
|
|
64
|
+
"ForexPython_DataProvider",
|
|
65
|
+
"Forex_DataProvider_Wrapper",
|
|
54
66
|
]
|
|
55
67
|
|
|
56
68
|
#######################################################################
|
|
57
69
|
# Version Information
|
|
58
70
|
#######################################################################
|
|
59
|
-
__version__ = "1.0.
|
|
71
|
+
__version__ = "1.0.1"
|
|
60
72
|
__author__ = "AbigailWilliams1692"
|
|
61
73
|
__email__ = "abigail.williams@example.com"
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
from data_retrieval.foreign_exchange.forex_data_provider_base import Forex_DataProvider_Base
|
|
2
|
+
from data_retrieval.foreign_exchange.forex_python_data_provider import ForexPython_DataProvider
|
|
3
|
+
from data_retrieval.foreign_exchange.forex_data_provider_wrapper import Forex_DataProvider_Wrapper
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: forex_data_provider_base.py
|
|
4
|
+
# Description: Forex Data Provider Base
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2026-01-24
|
|
7
|
+
# Updated: 2026-01-24
|
|
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 (
|
|
18
|
+
Dict,
|
|
19
|
+
List,
|
|
20
|
+
Optional,
|
|
21
|
+
Union,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Third-Party Packages
|
|
25
|
+
|
|
26
|
+
# Local Packages
|
|
27
|
+
from data_retrieval.model import DataProvider
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
#######################################################################
|
|
31
|
+
# Base Class
|
|
32
|
+
#######################################################################
|
|
33
|
+
class Forex_DataProvider_Base(DataProvider, ABC):
|
|
34
|
+
"""
|
|
35
|
+
Forex Data Provider Base Class
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
#################################################
|
|
39
|
+
# Class Attributes
|
|
40
|
+
#################################################
|
|
41
|
+
__name: str = "Forex_DataProvider_Base"
|
|
42
|
+
__type: str = "DataProvider"
|
|
43
|
+
|
|
44
|
+
#################################################
|
|
45
|
+
# Constructor
|
|
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 data provider.
|
|
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.
|
|
61
|
+
"""
|
|
62
|
+
# Initialize the base DataModule
|
|
63
|
+
super().__init__(
|
|
64
|
+
instance_id=instance_id,
|
|
65
|
+
logger=logger,
|
|
66
|
+
log_level=log_level,
|
|
67
|
+
**config
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Update the data methods
|
|
71
|
+
self.update_data_methods(
|
|
72
|
+
new_methods={
|
|
73
|
+
"exchange_rate_on_spot": self.get_exchange_rate_on_spot,
|
|
74
|
+
"exchange_rate_historical": self.get_exchange_rates_historical,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
#################################################
|
|
79
|
+
# Connection Methods
|
|
80
|
+
#################################################
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def _connect(self) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Connect to the data source.
|
|
85
|
+
"""
|
|
86
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def _disconnect(self) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Disconnect from the data source.
|
|
92
|
+
"""
|
|
93
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
94
|
+
|
|
95
|
+
#################################################
|
|
96
|
+
# Core Instance Methods
|
|
97
|
+
#################################################
|
|
98
|
+
@abstractmethod
|
|
99
|
+
def get_exchange_rate_on_spot(self,
|
|
100
|
+
base_currency: str,
|
|
101
|
+
target_currency: str,
|
|
102
|
+
fx_datetime: Optional[Union[datetime.datetime, datetime.date]] = None,
|
|
103
|
+
*args,
|
|
104
|
+
**kwargs,
|
|
105
|
+
) -> float:
|
|
106
|
+
"""
|
|
107
|
+
Get the exchange rate for a given currency pair on the current date.
|
|
108
|
+
|
|
109
|
+
:param base_currency: The base currency code (e.g., 'USD').
|
|
110
|
+
:param target_currency: The target currency code (e.g., 'EUR').
|
|
111
|
+
:param fx_datetime: The timestamp for the exchange rate. (could be datetime or date)
|
|
112
|
+
:param args: Additional positional arguments.
|
|
113
|
+
:param kwargs: Additional keyword arguments.
|
|
114
|
+
:return: The exchange rate as a float.
|
|
115
|
+
"""
|
|
116
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def get_exchange_rates_historical(
|
|
120
|
+
self,
|
|
121
|
+
base_currency: str,
|
|
122
|
+
target_currencies: List[str],
|
|
123
|
+
fx_datetime_start: Union[datetime.datetime, datetime.date],
|
|
124
|
+
fx_datetime_end: Union[datetime.datetime, datetime.date],
|
|
125
|
+
*args,
|
|
126
|
+
**kwargs,
|
|
127
|
+
) -> Dict[str, Dict[str, float]]:
|
|
128
|
+
"""
|
|
129
|
+
Get the exchange rates for a base currency and multiple target currencies over a time range.
|
|
130
|
+
|
|
131
|
+
:param base_currency: The base currency code (e.g., 'USD').
|
|
132
|
+
:param target_currencies: The target currency codes (e.g., ['EUR', 'GBP']).
|
|
133
|
+
:param fx_datetime_start: The start timestamp for the exchange rate. (could be datetime or date)
|
|
134
|
+
:param fx_datetime_end: The end timestamp for the exchange rate. (could be datetime or date)
|
|
135
|
+
:param args: Additional positional arguments.
|
|
136
|
+
:param kwargs: Additional keyword arguments.
|
|
137
|
+
:return: A dictionary mapping dates to exchange rates.
|
|
138
|
+
"""
|
|
139
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
140
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: forex_data_provider_wrapper.py
|
|
4
|
+
# Description: Forex Data Provider Wrapper
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2026-01-24
|
|
7
|
+
# Updated: 2026-01-24
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
#######################################################################
|
|
11
|
+
# Import Packages
|
|
12
|
+
#######################################################################
|
|
13
|
+
# Standard Packages
|
|
14
|
+
import datetime
|
|
15
|
+
import logging
|
|
16
|
+
from typing import (
|
|
17
|
+
Any,
|
|
18
|
+
Dict,
|
|
19
|
+
List,
|
|
20
|
+
Optional,
|
|
21
|
+
Union,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Third-party Packages
|
|
25
|
+
|
|
26
|
+
# Local Packages
|
|
27
|
+
from data_retrieval.model import DataProvider_Wrapper
|
|
28
|
+
from data_retrieval.foreign_exchange.forex_data_provider_base import Forex_DataProvider_Base
|
|
29
|
+
from data_retrieval.foreign_exchange.forex_python_data_provider import ForexPython_DataProvider
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
#######################################################################
|
|
33
|
+
# Forex Data Provider (Synchronous)
|
|
34
|
+
#######################################################################
|
|
35
|
+
class Forex_DataProvider_Wrapper(DataProvider_Wrapper):
|
|
36
|
+
"""
|
|
37
|
+
Foreign Exchange Data Provider Wrapper.
|
|
38
|
+
|
|
39
|
+
This class provides a standard wrapper interface for retrieving Foreign Exchange data from various sources.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
#################################################
|
|
43
|
+
# Class Attributes
|
|
44
|
+
#################################################
|
|
45
|
+
__name: str = "Forex_DataProvider_Wrapper"
|
|
46
|
+
__type: str = "DataProvider_Wrapper"
|
|
47
|
+
__valid_data_sources = [
|
|
48
|
+
"forex-python",
|
|
49
|
+
"exchange_rate_api",
|
|
50
|
+
]
|
|
51
|
+
__valid_data_source_mapping = {
|
|
52
|
+
"forex-python": ForexPython_DataProvider,
|
|
53
|
+
"exchange_rate_api": None,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#################################################
|
|
57
|
+
# Constructor
|
|
58
|
+
#################################################
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
data_source: str = "forex-python",
|
|
62
|
+
instance_id: Optional[int] = None,
|
|
63
|
+
logger: Optional[logging.Logger] = None,
|
|
64
|
+
log_level: Optional[int] = logging.INFO,
|
|
65
|
+
**config,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Initialize the Forex Data Provider.
|
|
69
|
+
|
|
70
|
+
:param instance_id: Unique identifier for this provider instance.
|
|
71
|
+
:param logger: Logger instance for logging operations.
|
|
72
|
+
:param log_level: Logging level for the provider.
|
|
73
|
+
:param data_methods: Dictionary of data retrieval methods.
|
|
74
|
+
:param config: Additional configuration parameters.
|
|
75
|
+
"""
|
|
76
|
+
# Initialize the base DataProvider
|
|
77
|
+
super().__init__(
|
|
78
|
+
data_source=data_source,
|
|
79
|
+
instance_id=instance_id,
|
|
80
|
+
logger=logger,
|
|
81
|
+
log_level=log_level,
|
|
82
|
+
valid_data_sources=self.__valid_data_sources,
|
|
83
|
+
valid_data_source_mapping=self.__valid_data_source_mapping,
|
|
84
|
+
**config,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
#################################################
|
|
88
|
+
# Core Instance Method: Get Exchange Rate
|
|
89
|
+
#################################################
|
|
90
|
+
def get_exchange_rate_on_spot(
|
|
91
|
+
self,
|
|
92
|
+
base_currency: str,
|
|
93
|
+
target_currency: str,
|
|
94
|
+
fx_datetime: Optional[Union[datetime.datetime, datetime.date]] = None,
|
|
95
|
+
*args,
|
|
96
|
+
**kwargs,
|
|
97
|
+
) -> Any:
|
|
98
|
+
"""
|
|
99
|
+
Get the exchange rate for a given currency pair on the current date.
|
|
100
|
+
|
|
101
|
+
:param base_currency: The base currency code (e.g., 'USD').
|
|
102
|
+
:param target_currency: The target currency code (e.g., 'EUR').
|
|
103
|
+
:param fx_datetime: The timestamp for the exchange rate. (could be datetime or date)
|
|
104
|
+
:param args: Additional positional arguments.
|
|
105
|
+
:param kwargs: Additional keyword arguments.
|
|
106
|
+
:return: The exchange rate as a float.
|
|
107
|
+
"""
|
|
108
|
+
return self.fetch_data(
|
|
109
|
+
data_point="exchange_rate_on_spot",
|
|
110
|
+
return_data_type=float,
|
|
111
|
+
base_currency=base_currency,
|
|
112
|
+
target_currency=target_currency,
|
|
113
|
+
fx_datetime=fx_datetime,
|
|
114
|
+
*args,
|
|
115
|
+
**kwargs,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def get_exchange_rates_historical(
|
|
119
|
+
self,
|
|
120
|
+
base_currency: str,
|
|
121
|
+
target_currencies: List[str],
|
|
122
|
+
fx_datetime_start: Union[datetime.datetime, datetime.date],
|
|
123
|
+
fx_datetime_end: Union[datetime.datetime, datetime.date],
|
|
124
|
+
*args,
|
|
125
|
+
**kwargs,
|
|
126
|
+
) -> Any:
|
|
127
|
+
"""
|
|
128
|
+
Get the exchange rates for a base currency and multiple target currencies over a time range.
|
|
129
|
+
|
|
130
|
+
:param base_currency: The base currency code (e.g., 'USD').
|
|
131
|
+
:param target_currencies: The target currency codes (e.g., ['EUR', 'GBP']).
|
|
132
|
+
:param fx_datetime_start: The start timestamp for the exchange rate. (could be datetime or date)
|
|
133
|
+
:param fx_datetime_end: The end timestamp for the exchange rate. (could be datetime or date)
|
|
134
|
+
:param args: Additional positional arguments.
|
|
135
|
+
:param kwargs: Additional keyword arguments.
|
|
136
|
+
:return: The exchange rate as a float.
|
|
137
|
+
"""
|
|
138
|
+
return self.fetch_data(
|
|
139
|
+
data_point="exchange_rate_historical",
|
|
140
|
+
return_data_type=Dict,
|
|
141
|
+
base_currency=base_currency,
|
|
142
|
+
target_currencies=target_currencies,
|
|
143
|
+
fx_datetime_start=fx_datetime_start,
|
|
144
|
+
fx_datetime_end=fx_datetime_end,
|
|
145
|
+
*args,
|
|
146
|
+
**kwargs,
|
|
147
|
+
)
|
|
148
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: forex_data_provider_base.py
|
|
4
|
+
# Description: Forex Data Provider Base
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2026-01-24
|
|
7
|
+
# Updated: 2026-01-24
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
#######################################################################
|
|
11
|
+
# Import Packages
|
|
12
|
+
#######################################################################
|
|
13
|
+
# Standard Packages
|
|
14
|
+
import datetime
|
|
15
|
+
import logging
|
|
16
|
+
from typing import (
|
|
17
|
+
Dict,
|
|
18
|
+
List,
|
|
19
|
+
Optional,
|
|
20
|
+
Union,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Third-Party Packages
|
|
24
|
+
from forex_python.converter import CurrencyCodes
|
|
25
|
+
from forex_python.converter import CurrencyRates
|
|
26
|
+
from forex_python.bitcoin import BtcConverter
|
|
27
|
+
|
|
28
|
+
# Local Packages
|
|
29
|
+
from data_retrieval.foreign_exchange.forex_data_provider_base import Forex_DataProvider_Base
|
|
30
|
+
from data_retrieval.utils.date_utils import populate_dates_in_between
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#######################################################################
|
|
34
|
+
# Forex Python Data Provider Class
|
|
35
|
+
#######################################################################
|
|
36
|
+
class ForexPython_DataProvider(Forex_DataProvider_Base):
|
|
37
|
+
"""
|
|
38
|
+
Forex Python Data Provider Class
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
#################################################
|
|
42
|
+
# Class Attributes
|
|
43
|
+
#################################################
|
|
44
|
+
__name: str = "ForexPython_DataProvider"
|
|
45
|
+
__type: str = "DataProvider"
|
|
46
|
+
|
|
47
|
+
#################################################
|
|
48
|
+
# Constructor
|
|
49
|
+
#################################################
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
instance_id: Optional[int] = None,
|
|
53
|
+
logger: Optional[logging.Logger] = None,
|
|
54
|
+
log_level: Optional[int] = logging.INFO,
|
|
55
|
+
**config,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Initialize the data provider.
|
|
59
|
+
|
|
60
|
+
:param instance_id: Unique identifier for this provider instance.
|
|
61
|
+
:param logger: Logger instance for logging operations.
|
|
62
|
+
:param log_level: Logging level for the provider.
|
|
63
|
+
:param config: Additional configuration parameters.
|
|
64
|
+
"""
|
|
65
|
+
# Initialize the base DataModule
|
|
66
|
+
super().__init__(
|
|
67
|
+
instance_id=instance_id,
|
|
68
|
+
logger=logger,
|
|
69
|
+
log_level=log_level,
|
|
70
|
+
**config
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Connect to the data source
|
|
74
|
+
self.connect()
|
|
75
|
+
|
|
76
|
+
#################################################
|
|
77
|
+
# Connection Methods
|
|
78
|
+
#################################################
|
|
79
|
+
def _connect(self) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Connect to the data source.
|
|
82
|
+
"""
|
|
83
|
+
self.set_connection(
|
|
84
|
+
{
|
|
85
|
+
"currency_codes": CurrencyCodes(),
|
|
86
|
+
"currency_rates": CurrencyRates(),
|
|
87
|
+
"btc_converter": BtcConverter(),
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _disconnect(self) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Disconnect from the data source.
|
|
94
|
+
"""
|
|
95
|
+
self.set_connection(None)
|
|
96
|
+
|
|
97
|
+
#################################################
|
|
98
|
+
# Core Instance Methods
|
|
99
|
+
#################################################
|
|
100
|
+
def get_exchange_rate_on_spot(
|
|
101
|
+
self,
|
|
102
|
+
base_currency: str,
|
|
103
|
+
target_currency: str,
|
|
104
|
+
fx_datetime: Optional[Union[datetime.datetime, datetime.date]] = None,
|
|
105
|
+
*args,
|
|
106
|
+
**kwargs,
|
|
107
|
+
) -> float:
|
|
108
|
+
"""
|
|
109
|
+
Get the exchange rate for a given currency pair on the current date.
|
|
110
|
+
|
|
111
|
+
:param base_currency: The base currency code (e.g., 'USD').
|
|
112
|
+
:param target_currency: The target currency code (e.g., 'EUR').
|
|
113
|
+
:param fx_datetime: The timestamp for the exchange rate. (could be datetime or date)
|
|
114
|
+
:param args: Additional positional arguments.
|
|
115
|
+
:param kwargs: Additional keyword arguments.
|
|
116
|
+
:return: The exchange rate as a float.
|
|
117
|
+
"""
|
|
118
|
+
# Get the connection
|
|
119
|
+
currency_rates = self.get_connection().get("currency_rates")
|
|
120
|
+
|
|
121
|
+
# Get the exchange rate
|
|
122
|
+
return currency_rates.get_rate(base_cur=base_currency, dest_cur=target_currency, date_obj=fx_datetime)
|
|
123
|
+
|
|
124
|
+
def get_exchange_rates_historical(
|
|
125
|
+
self,
|
|
126
|
+
base_currency: str,
|
|
127
|
+
target_currencies: List[str],
|
|
128
|
+
fx_datetime_start: Union[datetime.datetime, datetime.date],
|
|
129
|
+
fx_datetime_end: Union[datetime.datetime, datetime.date],
|
|
130
|
+
*args,
|
|
131
|
+
**kwargs,
|
|
132
|
+
) -> Dict[datetime.date, Dict[str, Optional[float]]]:
|
|
133
|
+
"""
|
|
134
|
+
Get the exchange rates for a given currency pair on the current date.
|
|
135
|
+
|
|
136
|
+
:param base_currency: The base currency code (e.g., 'USD').
|
|
137
|
+
:param target_currencies: The target currency codes (e.g., ['EUR', 'GBP']).
|
|
138
|
+
:param fx_datetime_start: The start timestamp for the exchange rate. (could be datetime or date)
|
|
139
|
+
:param fx_datetime_end: The end timestamp for the exchange rate. (could be datetime or date)
|
|
140
|
+
:param args: Additional positional arguments.
|
|
141
|
+
:param kwargs: Additional keyword arguments.
|
|
142
|
+
:return: The exchange rates as a dictionary.
|
|
143
|
+
"""
|
|
144
|
+
# Initialize the exchange rates container
|
|
145
|
+
exchange_rates = {}
|
|
146
|
+
|
|
147
|
+
# Get the connection
|
|
148
|
+
currency_rates = self.get_connection().get("currency_rates")
|
|
149
|
+
|
|
150
|
+
# Iterate through the date list and get the exchange rates for each date
|
|
151
|
+
date_list = populate_dates_in_between(start_date=fx_datetime_start, end_date=fx_datetime_end, interval=1)
|
|
152
|
+
for date in date_list:
|
|
153
|
+
raw_record = currency_rates.get_rates(base_cur=base_currency, date_obj=date)
|
|
154
|
+
exchange_rate_record = {
|
|
155
|
+
f"{base_currency}/{target_currency}": raw_record.get(target_currency, None)
|
|
156
|
+
for target_currency in target_currencies
|
|
157
|
+
}
|
|
158
|
+
exchange_rates[date] = exchange_rate_record
|
|
159
|
+
|
|
160
|
+
return exchange_rates
|
|
161
|
+
|
{data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/model/__init__.py
RENAMED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
# Description: Model Package Initialization
|
|
5
5
|
# Author: AbigailWilliams1692
|
|
6
6
|
# Created: 2026-01-14
|
|
7
|
-
# Updated: 2026-01-
|
|
7
|
+
# Updated: 2026-01-24
|
|
8
8
|
#######################################################################
|
|
9
9
|
|
|
10
10
|
from data_retrieval.model.data_module import DataModule
|
|
11
11
|
from data_retrieval.model.data_provider import DataProvider
|
|
12
|
+
from data_retrieval.model.data_provider_wrapper import DataProvider_Wrapper
|
{data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/model/data_provider.py
RENAMED
|
@@ -77,7 +77,6 @@ class DataProvider(DataModule):
|
|
|
77
77
|
:param instance_id: Unique identifier for this provider instance.
|
|
78
78
|
:param logger: Logger instance for logging operations.
|
|
79
79
|
:param log_level: Logging level for the provider.
|
|
80
|
-
:param data_methods: Dictionary of data retrieval methods.
|
|
81
80
|
:param config: Additional configuration parameters.
|
|
82
81
|
"""
|
|
83
82
|
# Initialize the base DataModule
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: data_provider_wrapper.py
|
|
4
|
+
# Description: Wrapper class for managing multiple data providers
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2026-01-24
|
|
7
|
+
# Updated: 2026-01-24
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
#######################################################################
|
|
11
|
+
# Import Packages
|
|
12
|
+
#######################################################################
|
|
13
|
+
# Standard Packages
|
|
14
|
+
import logging
|
|
15
|
+
from typing import (
|
|
16
|
+
Any,
|
|
17
|
+
Optional,
|
|
18
|
+
Dict,
|
|
19
|
+
List,
|
|
20
|
+
Type,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Local Packages
|
|
24
|
+
from data_retrieval.model.data_module import DataModule
|
|
25
|
+
from data_retrieval.model.data_provider import DataProvider
|
|
26
|
+
from data_retrieval.model.exceptions import (
|
|
27
|
+
DataProviderError,
|
|
28
|
+
DataProviderNotFoundError,
|
|
29
|
+
DataProviderInitializationError,
|
|
30
|
+
DataProviderWrapperConnectionError,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#######################################################################
|
|
35
|
+
# Data Provider Wrapper Connection Status
|
|
36
|
+
#######################################################################
|
|
37
|
+
class DataProviderWrapperConnectionStatus:
|
|
38
|
+
"""
|
|
39
|
+
Connection status for DataProvider_Wrapper.
|
|
40
|
+
"""
|
|
41
|
+
CONNECTED = "connected"
|
|
42
|
+
DISCONNECTED = "disconnected"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
#######################################################################
|
|
46
|
+
# Data Provider Wrapper Class
|
|
47
|
+
#######################################################################
|
|
48
|
+
class DataProvider_Wrapper(DataModule):
|
|
49
|
+
"""
|
|
50
|
+
Wrapper class for managing multiple data providers with a unified interface.
|
|
51
|
+
|
|
52
|
+
This class allows switching between different data providers (data sources)
|
|
53
|
+
for fetching the same category of data. It mimics the DataProvider interface
|
|
54
|
+
while providing the flexibility to choose which underlying provider to use.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
#################################################
|
|
58
|
+
# Class Attributes
|
|
59
|
+
#################################################
|
|
60
|
+
__name: str = "DataProvider_Wrapper"
|
|
61
|
+
__type: str = "DataProvider_Wrapper"
|
|
62
|
+
|
|
63
|
+
#################################################
|
|
64
|
+
# Constructor
|
|
65
|
+
#################################################
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
data_source: str,
|
|
69
|
+
instance_id: Optional[int] = None,
|
|
70
|
+
logger: Optional[logging.Logger] = None,
|
|
71
|
+
log_level: Optional[int] = logging.INFO,
|
|
72
|
+
valid_data_sources: Optional[List[str]] = None,
|
|
73
|
+
valid_data_source_mapping: Optional[Dict[str, Type[DataProvider]]] = None,
|
|
74
|
+
**config,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Initialize the data provider wrapper.
|
|
78
|
+
|
|
79
|
+
:param data_source: The data source name to use for fetching data.
|
|
80
|
+
:param instance_id: Unique identifier for this wrapper instance.
|
|
81
|
+
:param logger: Logger instance for logging operations.
|
|
82
|
+
:param log_level: Logging level for the wrapper.
|
|
83
|
+
:param config: Additional configuration parameters.
|
|
84
|
+
"""
|
|
85
|
+
# Initialize the base DataModule
|
|
86
|
+
super().__init__(
|
|
87
|
+
instance_id=instance_id,
|
|
88
|
+
logger=logger,
|
|
89
|
+
log_level=log_level,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Initialize DataProvider_Wrapper attributes
|
|
93
|
+
## Valid data sources and mappings
|
|
94
|
+
self.__valid_data_sources = valid_data_sources
|
|
95
|
+
self.__valid_data_source_mapping = valid_data_source_mapping
|
|
96
|
+
|
|
97
|
+
## Other attributes
|
|
98
|
+
self._config = config or {}
|
|
99
|
+
self._data_source: Optional[str] = data_source
|
|
100
|
+
self._data_provider: Optional[DataProvider] = self._initialize_data_provider_instance(data_source=data_source)
|
|
101
|
+
|
|
102
|
+
# Set initial status for the DataProvider_Wrapper instance
|
|
103
|
+
self.set_status(DataProviderWrapperConnectionStatus.DISCONNECTED)
|
|
104
|
+
|
|
105
|
+
#################################################
|
|
106
|
+
# Getter & Setter Methods
|
|
107
|
+
#################################################
|
|
108
|
+
def get_valid_data_sources(self) -> List[str]:
|
|
109
|
+
"""
|
|
110
|
+
Get the list of valid data sources.
|
|
111
|
+
|
|
112
|
+
:return: The list of valid data sources.
|
|
113
|
+
"""
|
|
114
|
+
return self.__valid_data_sources
|
|
115
|
+
|
|
116
|
+
def set_valid_data_sources(self, valid_data_sources: List[str]) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Set the list of valid data sources.
|
|
119
|
+
|
|
120
|
+
:param valid_data_sources: The list of valid data sources to set.
|
|
121
|
+
:return: None.
|
|
122
|
+
"""
|
|
123
|
+
self.__valid_data_sources = valid_data_sources
|
|
124
|
+
|
|
125
|
+
def add_valid_data_source(self, valid_data_source: str) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Add a valid data source to the list of valid data sources.
|
|
128
|
+
|
|
129
|
+
:param valid_data_source: The valid data source to add.
|
|
130
|
+
:return: None.
|
|
131
|
+
"""
|
|
132
|
+
self.__valid_data_sources.append(valid_data_source)
|
|
133
|
+
|
|
134
|
+
def remove_valid_data_source(self, valid_data_source: str) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Remove a valid data source from the list of valid data sources.
|
|
137
|
+
|
|
138
|
+
:param valid_data_source: The valid data source to remove.
|
|
139
|
+
:return: None.
|
|
140
|
+
"""
|
|
141
|
+
self.__valid_data_sources.remove(valid_data_source)
|
|
142
|
+
|
|
143
|
+
def get_valid_data_source_mapping(self) -> Dict[str, Type[DataProvider]]:
|
|
144
|
+
"""
|
|
145
|
+
Get the mapping of valid data sources to their corresponding DataProvider classes.
|
|
146
|
+
|
|
147
|
+
:return: The mapping of valid data sources to their corresponding DataProvider classes.
|
|
148
|
+
"""
|
|
149
|
+
return self.__valid_data_source_mapping
|
|
150
|
+
|
|
151
|
+
def set_valid_data_source_mapping(self, valid_data_source_mapping: Dict[str, Type[DataProvider]]) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Set the mapping of valid data sources to their corresponding DataProvider classes.
|
|
154
|
+
|
|
155
|
+
:param valid_data_source_mapping: The mapping of valid data sources to their corresponding DataProvider classes.
|
|
156
|
+
:return: None.
|
|
157
|
+
"""
|
|
158
|
+
self.__valid_data_source_mapping = valid_data_source_mapping
|
|
159
|
+
|
|
160
|
+
def update_valid_data_source_mapping(self, valid_data_source: str, data_provider_class: Type[DataProvider]) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Update the mapping of a valid data source to its corresponding DataProvider class.
|
|
163
|
+
|
|
164
|
+
:param valid_data_source: The valid data source to update.
|
|
165
|
+
:param data_provider_class: The DataProvider class to map to the valid data source.
|
|
166
|
+
:return: None.
|
|
167
|
+
"""
|
|
168
|
+
self.__valid_data_source_mapping[valid_data_source] = data_provider_class
|
|
169
|
+
|
|
170
|
+
def get_config(self) -> Dict[str, Any]:
|
|
171
|
+
"""
|
|
172
|
+
Get the configuration dictionary.
|
|
173
|
+
|
|
174
|
+
:return: The configuration dictionary.
|
|
175
|
+
"""
|
|
176
|
+
return self._config
|
|
177
|
+
|
|
178
|
+
def set_config(self, config: Dict[str, Any]) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Set the configuration dictionary.
|
|
181
|
+
|
|
182
|
+
:param config: The configuration dictionary.
|
|
183
|
+
:return: None.
|
|
184
|
+
"""
|
|
185
|
+
self._config = config
|
|
186
|
+
|
|
187
|
+
def update_config(self, key: str, value: Any) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Update a specific configuration key-value pair.
|
|
190
|
+
|
|
191
|
+
:param key: The configuration key to update.
|
|
192
|
+
:param value: The new value for the configuration key.
|
|
193
|
+
:return: None.
|
|
194
|
+
"""
|
|
195
|
+
self._config.update({key: value})
|
|
196
|
+
|
|
197
|
+
def get_data_source(self) -> Optional[str]:
|
|
198
|
+
"""
|
|
199
|
+
Get the current data source.
|
|
200
|
+
|
|
201
|
+
:return: The current data source name.
|
|
202
|
+
"""
|
|
203
|
+
return self._data_source
|
|
204
|
+
|
|
205
|
+
def set_data_source(self, data_source: str) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Validate the data source first and set the data source to use for fetching data.
|
|
208
|
+
|
|
209
|
+
:param data_source: The name of the data source to use.
|
|
210
|
+
:return: None.
|
|
211
|
+
:raises KeyError: If the data source is not registered.
|
|
212
|
+
"""
|
|
213
|
+
if not self.validate_data_source(data_source):
|
|
214
|
+
raise KeyError(f"Data source '{data_source}' is not valid.")
|
|
215
|
+
self._data_source = data_source
|
|
216
|
+
|
|
217
|
+
def get_data_provider(self) -> Optional[DataProvider]:
|
|
218
|
+
"""
|
|
219
|
+
Get the current data provider.
|
|
220
|
+
|
|
221
|
+
:return: The current data provider instance.
|
|
222
|
+
"""
|
|
223
|
+
return self._data_provider
|
|
224
|
+
|
|
225
|
+
def set_data_provider(self, data_provider: DataProvider) -> None:
|
|
226
|
+
"""
|
|
227
|
+
Set the current data provider.
|
|
228
|
+
|
|
229
|
+
:param data_provider: The DataProvider instance to set.
|
|
230
|
+
:return: None.
|
|
231
|
+
"""
|
|
232
|
+
self._data_provider = data_provider
|
|
233
|
+
|
|
234
|
+
#################################################
|
|
235
|
+
# Data Provider Management Methods
|
|
236
|
+
#################################################
|
|
237
|
+
def switch_data_provider(self, data_source: str) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Switch to another data provider that has already been registered.
|
|
240
|
+
|
|
241
|
+
:param data_source: The data source name to register.
|
|
242
|
+
:return: None.
|
|
243
|
+
"""
|
|
244
|
+
if data_source == self.get_data_source():
|
|
245
|
+
raise ValueError(f"Data source '{data_source}' is already the current data source.")
|
|
246
|
+
elif self.validate_data_source(data_source=data_source):
|
|
247
|
+
self.set_data_source(data_source=data_source)
|
|
248
|
+
self.set_data_provider(
|
|
249
|
+
data_provider=self._initialize_data_provider_instance(data_source=data_source)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def register_data_provider(self, data_source: str, data_provider_class: Type[DataProvider]) -> None:
|
|
253
|
+
"""
|
|
254
|
+
Register a new data provider class.
|
|
255
|
+
|
|
256
|
+
:param data_source: The data source name to register.
|
|
257
|
+
:param data_provider_class: The DataProvider class to register.
|
|
258
|
+
:return: None.
|
|
259
|
+
"""
|
|
260
|
+
self.__valid_data_sources[data_source] = data_provider_class
|
|
261
|
+
|
|
262
|
+
def unregister_data_provider(self, data_source: str) -> None:
|
|
263
|
+
"""
|
|
264
|
+
Unregister a data provider class.
|
|
265
|
+
|
|
266
|
+
:param data_source: The data source name to unregister.
|
|
267
|
+
:return: None.
|
|
268
|
+
"""
|
|
269
|
+
if data_source in self.__valid_data_sources:
|
|
270
|
+
del self.__valid_data_sources[data_source]
|
|
271
|
+
|
|
272
|
+
def _initialize_data_provider_instance(self, data_source: Optional[str] = None) -> DataProvider:
|
|
273
|
+
"""
|
|
274
|
+
Initialize the data provider as the data source instance.
|
|
275
|
+
|
|
276
|
+
:param data_source: The data source to use. If None, uses the current data source.
|
|
277
|
+
:return: The initialized data provider instance.
|
|
278
|
+
"""
|
|
279
|
+
# Check if data source is None.
|
|
280
|
+
if data_source is None:
|
|
281
|
+
data_source = self.get_data_source()
|
|
282
|
+
|
|
283
|
+
# Get the data provider class from the mapping
|
|
284
|
+
data_provider_class = self.__valid_data_source_mapping.get(data_source, None)
|
|
285
|
+
|
|
286
|
+
# Check if the data provider class is found
|
|
287
|
+
if data_provider_class is None:
|
|
288
|
+
raise DataProviderNotFoundError(
|
|
289
|
+
f"No data provider found for data source: '{data_source}'."
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Initialize the data provider instance
|
|
293
|
+
try:
|
|
294
|
+
data_provider_instance = data_provider_class(
|
|
295
|
+
log_level=self.get_log_level(),
|
|
296
|
+
**self.get_config(),
|
|
297
|
+
)
|
|
298
|
+
except DataProviderError as e:
|
|
299
|
+
raise e
|
|
300
|
+
except Exception as e:
|
|
301
|
+
raise DataProviderInitializationError(
|
|
302
|
+
f"Failed to initialize data provider for data source: '{data_source}'. Error: {str(e)}"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return data_provider_instance
|
|
306
|
+
|
|
307
|
+
#################################################
|
|
308
|
+
# Connection Methods
|
|
309
|
+
#################################################
|
|
310
|
+
def is_connected(self) -> bool:
|
|
311
|
+
"""
|
|
312
|
+
Check if the specified data provider is connected.
|
|
313
|
+
|
|
314
|
+
:return: True if connected, False otherwise.
|
|
315
|
+
"""
|
|
316
|
+
data_provider = self.get_data_provider()
|
|
317
|
+
if data_provider is None:
|
|
318
|
+
raise DataProviderNotFoundError(
|
|
319
|
+
f"No data provider found for data source: '{self.get_data_source()}'."
|
|
320
|
+
)
|
|
321
|
+
return data_provider.is_connected()
|
|
322
|
+
|
|
323
|
+
def connect(self) -> None:
|
|
324
|
+
"""
|
|
325
|
+
Connect to the specified data provider.
|
|
326
|
+
"""
|
|
327
|
+
try:
|
|
328
|
+
data_provider = self.get_data_provider()
|
|
329
|
+
if data_provider is None:
|
|
330
|
+
raise DataProviderNotFoundError(
|
|
331
|
+
f"No data provider found for data source: '{self.get_data_source()}'."
|
|
332
|
+
)
|
|
333
|
+
data_provider.connect()
|
|
334
|
+
self.set_status(DataProviderWrapperConnectionStatus.CONNECTED)
|
|
335
|
+
except DataProviderError as e:
|
|
336
|
+
raise e
|
|
337
|
+
except Exception as e:
|
|
338
|
+
raise DataProviderWrapperConnectionError(
|
|
339
|
+
f"Failed to connect to data provider for data source: '{self.get_data_source()}'. Error: {str(e)}"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def disconnect(self) -> None:
|
|
343
|
+
"""
|
|
344
|
+
Disconnect from the specified data provider.
|
|
345
|
+
"""
|
|
346
|
+
try:
|
|
347
|
+
data_provider = self.get_data_provider()
|
|
348
|
+
if data_provider is None:
|
|
349
|
+
raise DataProviderNotFoundError(
|
|
350
|
+
f"No data provider found for data source: '{self.get_data_source()}'."
|
|
351
|
+
)
|
|
352
|
+
data_provider.disconnect()
|
|
353
|
+
self.set_status(DataProviderWrapperConnectionStatus.DISCONNECTED)
|
|
354
|
+
except DataProviderError as e:
|
|
355
|
+
raise e
|
|
356
|
+
except Exception as e:
|
|
357
|
+
raise DataProviderWrapperConnectionError(
|
|
358
|
+
f"Failed to disconnect from data provider for data source: '{self.get_data_source()}'. Error: {str(e)}"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
#################################################
|
|
362
|
+
# Context Management
|
|
363
|
+
#################################################
|
|
364
|
+
def __enter__(self) -> "DataProvider":
|
|
365
|
+
"""
|
|
366
|
+
Enter the runtime context related to this object. Automatically connects to the data source.
|
|
367
|
+
"""
|
|
368
|
+
return self
|
|
369
|
+
|
|
370
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
371
|
+
"""
|
|
372
|
+
Exit the runtime context related to this object. Automatically disconnects from the data source.
|
|
373
|
+
"""
|
|
374
|
+
self.disconnect()
|
|
375
|
+
|
|
376
|
+
#################################################
|
|
377
|
+
# Core Instance Method: Fetch Data
|
|
378
|
+
#################################################
|
|
379
|
+
def fetch_data(
|
|
380
|
+
self,
|
|
381
|
+
data_point: str,
|
|
382
|
+
return_data_type: type,
|
|
383
|
+
*args,
|
|
384
|
+
**kwargs,
|
|
385
|
+
) -> Any:
|
|
386
|
+
"""
|
|
387
|
+
Fetch data from the specified data source.
|
|
388
|
+
|
|
389
|
+
:param data_point: The data point to fetch.
|
|
390
|
+
:param return_data_type: The expected return data type.
|
|
391
|
+
:param args: Positional arguments for fetching data.
|
|
392
|
+
:param kwargs: Keyword arguments for fetching data.
|
|
393
|
+
:return: The fetched data.
|
|
394
|
+
:raises DataProviderNotFoundError: If no provider is found for the data source.
|
|
395
|
+
:raises DataMethodNotFoundError: If the data method is not found.
|
|
396
|
+
:raises ReturnDataTypeNotMatchedError: If the return data type does not match.
|
|
397
|
+
"""
|
|
398
|
+
# Get the data provider for the current data source
|
|
399
|
+
data_provider = self.get_data_provider()
|
|
400
|
+
|
|
401
|
+
# Check if the data provider exists
|
|
402
|
+
if data_provider is None:
|
|
403
|
+
raise DataProviderNotFoundError(
|
|
404
|
+
f"No data provider found for data source '{self.get_data_source()}'."
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Fetch the data
|
|
408
|
+
data: Any = data_provider.fetch_data(
|
|
409
|
+
data_point=data_point,
|
|
410
|
+
return_data_type=return_data_type,
|
|
411
|
+
*args,
|
|
412
|
+
**kwargs
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
return data
|
|
416
|
+
|
|
417
|
+
#################################################
|
|
418
|
+
# Utility Methods
|
|
419
|
+
#################################################
|
|
420
|
+
def validate_data_source(self, data_source: str) -> bool:
|
|
421
|
+
"""
|
|
422
|
+
Validate if the provided data source is supported.
|
|
423
|
+
|
|
424
|
+
:param data_source: The data source to validate.
|
|
425
|
+
:return: True if the data source is valid, False otherwise.
|
|
426
|
+
"""
|
|
427
|
+
print(self.get_valid_data_sources())
|
|
428
|
+
return data_source in self.__valid_data_sources
|
{data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/model/exceptions.py
RENAMED
|
@@ -4,17 +4,22 @@
|
|
|
4
4
|
# Description: Exception classes for data providers
|
|
5
5
|
# Author: AbigailWilliams1692
|
|
6
6
|
# Created: 2026-01-14
|
|
7
|
-
# Updated: 2026-01-
|
|
7
|
+
# Updated: 2026-01-24
|
|
8
8
|
#######################################################################
|
|
9
9
|
|
|
10
10
|
#######################################################################
|
|
11
|
-
#
|
|
11
|
+
# Data Provider Exceptions
|
|
12
12
|
#######################################################################
|
|
13
13
|
class DataProviderError(Exception):
|
|
14
14
|
"""Base exception for data provider errors."""
|
|
15
15
|
pass
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
class DataProviderInitializationError(DataProviderError):
|
|
19
|
+
"""Raised when initialization of data provider fails."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
18
23
|
class DataProviderConnectionError(DataProviderError):
|
|
19
24
|
"""Raised when connection to data source fails."""
|
|
20
25
|
pass
|
|
@@ -36,3 +41,19 @@ class ReturnDataTypeNotMatchedError(DataProviderError):
|
|
|
36
41
|
class ValidationError(DataProviderError):
|
|
37
42
|
"""Raised when data validation fails."""
|
|
38
43
|
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#######################################################################
|
|
47
|
+
# Data Provider Wrapper Exceptions
|
|
48
|
+
#######################################################################
|
|
49
|
+
class DataProviderWrapperError(Exception):
|
|
50
|
+
"""Base exception for data provider wrapper errors."""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
class DataProviderNotFoundError(DataProviderWrapperError):
|
|
54
|
+
"""Raised when cannot find the corresponding data provider class."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
class DataProviderWrapperConnectionError(DataProviderWrapperError):
|
|
58
|
+
"""Raised when connection to data provider fails."""
|
|
59
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# Project: Data Retrieval Module
|
|
3
|
+
# File: date_utils.py
|
|
4
|
+
# Description: Util functions for date operations
|
|
5
|
+
# Author: AbigailWilliams1692
|
|
6
|
+
# Created: 2026-01-24
|
|
7
|
+
# Updated: 2026-01-24
|
|
8
|
+
#######################################################################
|
|
9
|
+
|
|
10
|
+
#######################################################################
|
|
11
|
+
# Import Packages
|
|
12
|
+
#######################################################################
|
|
13
|
+
# Standard Packages
|
|
14
|
+
import datetime
|
|
15
|
+
from typing import List, Tuple
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
#######################################################################
|
|
19
|
+
# Util Functions
|
|
20
|
+
#######################################################################
|
|
21
|
+
def populate_dates_in_between(
|
|
22
|
+
start_date: datetime.date,
|
|
23
|
+
end_date: datetime.date,
|
|
24
|
+
interval: int = 1
|
|
25
|
+
) -> List[datetime.date]:
|
|
26
|
+
"""
|
|
27
|
+
Populate a list of dates between start_date and end_date (inclusive).
|
|
28
|
+
|
|
29
|
+
:param start_date: The start date.
|
|
30
|
+
:param end_date: The end date.
|
|
31
|
+
:param interval: The interval between dates in days.
|
|
32
|
+
:return: List of dates between start_date and end_date.
|
|
33
|
+
"""
|
|
34
|
+
if start_date > end_date:
|
|
35
|
+
raise ValueError("start_date must be before or equal to end_date")
|
|
36
|
+
delta = end_date - start_date
|
|
37
|
+
return [start_date + datetime.timedelta(days=i) for i in range(0, delta.days + 1, interval)]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def populate_start_and_end_dates(
|
|
41
|
+
start_date: datetime.date,
|
|
42
|
+
end_date: datetime.date,
|
|
43
|
+
interval: int = 1
|
|
44
|
+
) -> List[Tuple[datetime.date, datetime.date]]:
|
|
45
|
+
"""
|
|
46
|
+
Populate a list of dates between start_date and end_date (inclusive).
|
|
47
|
+
|
|
48
|
+
:param start_date: The start date.
|
|
49
|
+
:param end_date: The end date.
|
|
50
|
+
:param interval: The interval between dates in days.
|
|
51
|
+
:return: List of start date and end date tuples.
|
|
52
|
+
"""
|
|
53
|
+
date_list = populate_dates_in_between(start_date=start_date, end_date=end_date, interval=interval)
|
|
54
|
+
return [(date_list[i], date_list[i+1]) for i in range(0, len(date_list)-1)]
|
{data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval_module.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: data-retrieval-module
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
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,6 +28,7 @@ Requires-Python: >=3.8
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
License-File: LICENSE
|
|
30
30
|
Requires-Dist: typing-extensions>=4.0.0; python_version < "3.10"
|
|
31
|
+
Requires-Dist: forex-python>=1.0.0
|
|
31
32
|
Provides-Extra: dev
|
|
32
33
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
34
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
@@ -8,10 +8,17 @@ requirements-test.txt
|
|
|
8
8
|
setup.py
|
|
9
9
|
data_retrieval/__init__.py
|
|
10
10
|
data_retrieval/py.typed
|
|
11
|
+
data_retrieval/foreign_exchange/__init__.py
|
|
12
|
+
data_retrieval/foreign_exchange/forex_data_provider_base.py
|
|
13
|
+
data_retrieval/foreign_exchange/forex_data_provider_wrapper.py
|
|
14
|
+
data_retrieval/foreign_exchange/forex_python_data_provider.py
|
|
11
15
|
data_retrieval/model/__init__.py
|
|
12
16
|
data_retrieval/model/data_module.py
|
|
13
17
|
data_retrieval/model/data_provider.py
|
|
18
|
+
data_retrieval/model/data_provider_wrapper.py
|
|
14
19
|
data_retrieval/model/exceptions.py
|
|
20
|
+
data_retrieval/utils/__init__.py
|
|
21
|
+
data_retrieval/utils/date_utils.py
|
|
15
22
|
data_retrieval_module.egg-info/PKG-INFO
|
|
16
23
|
data_retrieval_module.egg-info/SOURCES.txt
|
|
17
24
|
data_retrieval_module.egg-info/dependency_links.txt
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "data-retrieval-module"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.1"
|
|
8
8
|
description = "A standardized interface for data providers with sync and async support"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -33,6 +33,7 @@ classifiers = [
|
|
|
33
33
|
requires-python = ">=3.8"
|
|
34
34
|
dependencies = [
|
|
35
35
|
"typing-extensions>=4.0.0; python_version<'3.10'",
|
|
36
|
+
"forex-python>=1.0.0",
|
|
36
37
|
]
|
|
37
38
|
|
|
38
39
|
[project.optional-dependencies]
|
|
@@ -79,7 +80,7 @@ Repository = "https://github.com/AbigailWilliams1692/data-retrieval-module"
|
|
|
79
80
|
Changelog = "https://github.com/AbigailWilliams1692/data-retrieval-module/blob/main/CHANGELOG.md"
|
|
80
81
|
|
|
81
82
|
[tool.setuptools]
|
|
82
|
-
packages = ["data_retrieval", "data_retrieval.model"]
|
|
83
|
+
packages = ["data_retrieval", "data_retrieval.model", "data_retrieval.foreign_exchange", "data_retrieval.utils"]
|
|
83
84
|
|
|
84
85
|
[tool.setuptools.package-data]
|
|
85
86
|
data_retrieval = ["py.typed"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{data_retrieval_module-1.0.0 → data_retrieval_module-1.0.1}/data_retrieval/model/data_module.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|