forexrates 0.0.1.dev0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Debmalya Pramanik
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,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: forexrates
3
+ Version: 0.0.1.dev0
4
+ Summary: A unified codebase for fetching FOREX rates.
5
+ Home-page: https://github.com/sharkutilities/forexrates
6
+ Author: shark-utilities developers
7
+ Author-email: shark-utilities developers <neuralNOD@outlook.com>
8
+ License: MIT License
9
+ Project-URL: Homepage, https://github.com/sharkutilities/forexrates
10
+ Project-URL: Issue Tracker, https://github.com/sharkutilities/forexrates/issues
11
+ Project-URL: Org. Homepage, https://github.com/sharkutilities
12
+ Keywords: forex,api,exchange rates,foreign exchange rates,wrappers,data science,data analysis,data scientist,data analyst
13
+ Classifier: Development Status :: 1 - Planning
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: Operating System :: Unix
20
+ Classifier: Operating System :: POSIX
21
+ Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Programming Language :: Python
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: License :: OSI Approved :: MIT License
28
+ Requires-Python: >=3.11
29
+ License-File: LICENSE
30
+ Requires-Dist: numpy==2.4.2
31
+ Requires-Dist: pandas==3.0.0
32
+ Requires-Dist: psycopg==3.3.2
33
+ Requires-Dist: psycopg-binary==3.3.2
34
+ Requires-Dist: requests==2.32.5
35
+ Requires-Dist: SQLAlchemy==2.0.0
36
+ Requires-Dist: tqdm==4.67.3
37
+ Dynamic: author
38
+ Dynamic: home-page
39
+ Dynamic: license-file
40
+ Dynamic: requires-python
@@ -0,0 +1,16 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ A Python Package to Fetch FOREX Rates from API Sources
5
+
6
+ An one place that integrates different paid/open source API sources
7
+ to fetch foreign exchange rates.
8
+ """
9
+
10
+ # ? package follows https://peps.python.org/pep-0440/
11
+ # ? https://python-semver.readthedocs.io/en/latest/advanced/convert-pypi-to-semver.html
12
+ __version__ = "v0.0.1.dev0"
13
+
14
+ # init-time options registrations
15
+ from forexrates import io
16
+ from forexrates import api
@@ -0,0 +1 @@
1
+ from forexrates.api.exchangerates import ExchangeRatesAPI
@@ -0,0 +1,142 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ Base API Class Wrapper and Structure for API Integration
5
+
6
+ The base class is designed to be uniform for all integrated external
7
+ data sources with abstract class methods and properties.
8
+ """
9
+
10
+ import time
11
+ import requests
12
+ import warnings
13
+
14
+ from typing import Iterable
15
+ from abc import ABC, abstractmethod
16
+
17
+ # GH#2 disable SSL warnings for self-signed certificates
18
+ from urllib3.exceptions import InsecureRequestWarning
19
+
20
+ class BaseAPI(ABC):
21
+ def __init__(self, apikey : str) -> None:
22
+ self.apikey = apikey
23
+
24
+ # create request sessions object for further use
25
+ self._session = requests.Session()
26
+
27
+
28
+ @property
29
+ @abstractmethod
30
+ def uri(self) -> str:
31
+ pass
32
+
33
+
34
+ @property
35
+ @abstractmethod
36
+ def headers(self) -> dict:
37
+ pass
38
+
39
+
40
+ @property
41
+ @abstractmethod
42
+ def endpoint(self) -> str:
43
+ pass
44
+
45
+
46
+ @property
47
+ @abstractmethod
48
+ def params(self) -> dict:
49
+ pass
50
+
51
+
52
+ def __format_error__(self, e : requests.exceptions.RequestException) -> str:
53
+ """
54
+ Format the Base Error Response Structure to Hide Sensitive Data
55
+
56
+ The make request exposes some sensitive information in the URL
57
+ string, which is not desired for security reasons. Referring
58
+ to an existing issue, the problem is not yet resolved and may
59
+ require an alternate approach.
60
+
61
+ Override the method in the child class to preserve the sensitive
62
+ information from leaking, or leave it to the parent class to
63
+ return the value as is.
64
+
65
+ :type e: requests.exceptions.RequestException
66
+ :param e: Exception to be formatted, to hide sensitive
67
+ information from the URL string.
68
+ """
69
+
70
+ return e
71
+
72
+
73
+ def make_request(self, method : str, **kwargs) -> requests.Response:
74
+ sleep = kwargs.get("sleep", 2) # sleep b/w requests, in seconds
75
+ retries = kwargs.get("retries", 3) # no. of retry attempts
76
+
77
+ # GH#2 give user the ability to disable SSL verification
78
+ # ? this is not recommended, but can be useful for testing
79
+ verify = kwargs.get("verify", True) # verify SSL certificate
80
+
81
+ if _ := kwargs.get("suppresswarning", False):
82
+ warnings.filterwarnings(
83
+ "ignore", category = InsecureRequestWarning
84
+ )
85
+
86
+ requests.packages.urllib3.disable_warnings(
87
+ InsecureRequestWarning
88
+ )
89
+
90
+ # create uri and append the endpoint to the url::
91
+ uri = self.uri + self.endpoint
92
+
93
+ # create url load like headers and params
94
+ dataload = {"headers" : self.headers, "params" : self.params}
95
+
96
+ # retry on the uri with header and param loads
97
+ for attempt in range(retries):
98
+ try:
99
+ response = self._session.request(
100
+ method, uri, **dataload, verify = verify
101
+ )
102
+ response.raise_for_status()
103
+
104
+ return response # data received, return response
105
+
106
+ except requests.exceptions.RequestException as e:
107
+ e = self.__format_error__(e)
108
+ print(f"Request Failed (Attempt: {attempt + 1}) : {e}")
109
+
110
+ if attempt == retries - 1:
111
+ print("Max Retries Reached.")
112
+ raise e
113
+
114
+ time.sleep(sleep)
115
+ continue # continue until retries are exhausted
116
+
117
+ except requests.exceptions.Timeout as e:
118
+ print(f"Request Timed Out (Attempt: {attempt + 1}) : {e}")
119
+
120
+ if attempt == retries - 1:
121
+ print("Max Retries Reached.")
122
+ raise e
123
+
124
+ time.sleep(sleep)
125
+ continue # continue until retries are exhausted
126
+
127
+
128
+ def get(self, **kwargs) -> Iterable:
129
+ """
130
+ Get the Base Response from an External API Source
131
+
132
+ The base response is fetched and the data is returned as
133
+ a JSON/Dictionary object to the enduser. The response can be
134
+ further processed and formatted into a desired structure using
135
+ the IO module.
136
+ """
137
+
138
+ return self.make_request("GET", **kwargs).json()
139
+
140
+
141
+ def close(self) -> None:
142
+ self._session.close()
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ API Handling for the Exchangerates API (https://exchangeratesapi.io/)
5
+
6
+ Exchange Rates API provides historic and real-time exchange rates and
7
+ currency conversions. More information about the API is available at
8
+ https://exchangeratesapi.io/documentation/.
9
+ """
10
+
11
+ import requests
12
+
13
+ import datetime as dt
14
+ from typing import Iterable
15
+ from forexrates.api.base import BaseAPI
16
+
17
+ class ExchangeRatesAPI(BaseAPI):
18
+ def __init__(self, apikey : str, **kwargs) -> None:
19
+ super().__init__(apikey)
20
+
21
+ # ! override default endpoint with endpoint
22
+ # ? the endpoint can be date (%Y-%m-%d) for historic rates
23
+ self._endpoint = kwargs.get("endpoint", "latest")
24
+
25
+ # ? each base class accepts parameters as keyword arguments
26
+ self._base = kwargs.get("base", "EUR")
27
+
28
+
29
+ @property
30
+ def uri(self) -> str:
31
+ return "https://api.exchangeratesapi.io/v1/"
32
+
33
+
34
+ @property
35
+ def endpoint(self) -> str:
36
+ # ? endpoint can be date (%Y-%m-%d) for historic rates
37
+ retval = self._endpoint
38
+
39
+ # if date object, then parse as string
40
+ if isinstance(retval, dt.date):
41
+ retval = retval.strftime("%Y-%m-%d")
42
+
43
+ return retval
44
+
45
+
46
+ @property
47
+ def params(self) -> dict:
48
+ return {
49
+ "base" : self._base,
50
+ "access_key" : self.apikey
51
+ }
52
+
53
+
54
+ @property
55
+ def headers(self) -> None:
56
+ pass
57
+
58
+
59
+ def get(self, **kwargs) -> Iterable:
60
+ return super().get(**kwargs)
61
+
62
+
63
+ def __format_error__(self, e : requests.exceptions.RequestException) -> str:
64
+ server_message = e.response.json()["error"]["message"]
65
+ return f"{e.response} : {e.response.reason} - {server_message}"
@@ -0,0 +1,10 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ A Short Module to Handle Input/Output for JSON Response
5
+
6
+ The response received from the external API sources are parsed and
7
+ returned as per user requirements.
8
+ """
9
+
10
+ from forexrates.io import dataframe
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ Base Class Wrapper and Structure for IO Integration
5
+
6
+ The base class is designed to be uniform for all integrated external
7
+ data sources with abstract class methods and properties.
8
+ """
9
+
10
+ import pandas as pd
11
+
12
+ from typing import Iterable
13
+ from abc import ABC, abstractmethod
14
+
15
+ class BaseIO(ABC):
16
+ def __init__(self, data : dict | Iterable[dict]) -> None:
17
+ self.data = self.__parse_data__(data)
18
+
19
+
20
+ @staticmethod
21
+ def __parse_data__(data : dict | Iterable[dict]) -> pd.DataFrame:
22
+ assert isinstance(data, (dict, list)), \
23
+ "Data must be a dictionary or a list of dictionaries."
24
+
25
+ return [data] if isinstance(data, dict) else data
26
+
27
+
28
+ @abstractmethod
29
+ def dataframe(self, *args, **kwargs) -> pd.DataFrame:
30
+ pass
@@ -0,0 +1,137 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ Operations to Parse Data as :mod:``pandas.DataFrame`` Object
5
+ """
6
+
7
+ import datetime as dt
8
+
9
+ from tqdm import tqdm as TQ
10
+ from typing import Iterable
11
+
12
+ import pandas as pd
13
+
14
+ from forexrates.io.base import BaseIO
15
+
16
+ class ExchangeRatesIO(BaseIO):
17
+ """
18
+ An IO Parser for the API Response from ExchangeRatesAPI
19
+
20
+ The IO parser transforms the API response into a desired format
21
+ by calling any internal methods or processes.
22
+
23
+ A single response is received as a JSON (dictionary equivalent)
24
+ from the API and is parsed in a desired format.A typical response
25
+ structure is like:
26
+
27
+ .. code-block:: json
28
+
29
+ {
30
+ "base" : "EUR",
31
+ "date" : "2020-01-01",
32
+ "rates" : {
33
+ "USD" : 1.2345,
34
+ "INR" : 1.2345,
35
+ [...]
36
+ }
37
+ }
38
+
39
+ In case of multiple response parsed under a single call, the
40
+ function is adjusted with parameter to handle such a response. A
41
+ sample response is like:
42
+
43
+ .. code-block:: json
44
+
45
+ [
46
+ {
47
+ "base" : "EUR",
48
+ "date" : "2020-01-01",
49
+ "rates" : {
50
+ "USD" : 1.2345,
51
+ "INR" : 1.2345,
52
+ [...]
53
+ }
54
+ },
55
+ [...]
56
+ ]
57
+
58
+ :type data: dict
59
+ :param data: A single JSON response from the ExchangeRatesAPI.
60
+ More information about the reponse is available here:
61
+ https://exchangeratesapi.io/documentation/.
62
+ """
63
+
64
+ def __init__(self, data : dict | Iterable[dict]) -> None:
65
+ super().__init__(data)
66
+
67
+
68
+ def dataframe(self, verbose : bool = False, **kwargs) -> pd.DataFrame:
69
+ """
70
+ Parse a JSON (DICTIONARY) Response from ExchangeRatesAPI
71
+
72
+ The function provide method to parse a single/multiple
73
+ responses of the module and return a single
74
+ :mod:``pandas.DataFrame`` object.
75
+
76
+ :type verbose: bool
77
+ :param verbose: If set to ``True``, the function will print
78
+ additional information about the parsed dataframe.
79
+
80
+ Keyword Arguments
81
+ -----------------
82
+ Currently only setting the column and index names are supported
83
+ using keyword arguments. More control is available in the future
84
+ release and changes over dataframe.
85
+
86
+ * **index** (*str*): Name of the index column for parsed
87
+ dataframe. Defaults to ``foreign_exchange_rate``.
88
+ * **column** (*str*): Name of the column for parsed dataframe.
89
+ Defaults to ``target_currency_code``.
90
+ * **basecolumn** (*str*): Name of the base currency column for
91
+ parsed dataframe. Defaults to ``base_currency_code``.
92
+ * **datecolumn** (*str*): Name of the date column for parsed
93
+ dataframe. Defaults to ``effective_date``.
94
+
95
+
96
+ :rtype: :mod:``pandas.DataFrame``
97
+ :return: A :mod:``pandas.DataFrame`` object with parsed data.
98
+ """
99
+
100
+ index = kwargs.get("index", "foreign_exchange_rate")
101
+ column = kwargs.get("column", "target_currency_code")
102
+ basecolumn = kwargs.get("basecolumn", "base_currency_code")
103
+ datecolumn = kwargs.get("datecolumn", "effective_date")
104
+
105
+ frames = []
106
+ for data in TQ(self.data, desc = "Parsing Records..."):
107
+ base = data["base"]
108
+ date = data["date"]
109
+
110
+ # the data["date"] is a string in the format "%Y-%m-%d"
111
+ # convert it to a datetime object and return parsed frame
112
+ date = dt.datetime.strptime(date, "%Y-%m-%d")
113
+
114
+ frame = pd.DataFrame(
115
+ data["rates"], index = [index]
116
+ ).T.reset_index().rename(columns = {"index" : column})
117
+
118
+ frame[basecolumn] = base
119
+ frame[datecolumn] = date
120
+
121
+ frames.append(frame[[datecolumn, basecolumn, column, index]])
122
+
123
+ frames = pd.concat(frames, ignore_index = True)
124
+
125
+ if verbose:
126
+ print(f"Total Parsed Records: {frames.shape[0]:,}")
127
+
128
+ min_ = frames[datecolumn].min().date()
129
+ max_ = frames[datecolumn].max().date()
130
+ print(f" >> Date Range: {min_} - {max_}")
131
+
132
+ unique_bases_= frames[basecolumn].unique()
133
+ n_unique_targets_ = frames[column].nunique()
134
+ print(f" >> Base Currency: {unique_bases_}")
135
+ print(f" >> # UQ Targets: {n_unique_targets_:,}")
136
+
137
+ return frames
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: forexrates
3
+ Version: 0.0.1.dev0
4
+ Summary: A unified codebase for fetching FOREX rates.
5
+ Home-page: https://github.com/sharkutilities/forexrates
6
+ Author: shark-utilities developers
7
+ Author-email: shark-utilities developers <neuralNOD@outlook.com>
8
+ License: MIT License
9
+ Project-URL: Homepage, https://github.com/sharkutilities/forexrates
10
+ Project-URL: Issue Tracker, https://github.com/sharkutilities/forexrates/issues
11
+ Project-URL: Org. Homepage, https://github.com/sharkutilities
12
+ Keywords: forex,api,exchange rates,foreign exchange rates,wrappers,data science,data analysis,data scientist,data analyst
13
+ Classifier: Development Status :: 1 - Planning
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: Operating System :: Unix
20
+ Classifier: Operating System :: POSIX
21
+ Classifier: Operating System :: Microsoft :: Windows
22
+ Classifier: Programming Language :: Python
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: License :: OSI Approved :: MIT License
28
+ Requires-Python: >=3.11
29
+ License-File: LICENSE
30
+ Requires-Dist: numpy==2.4.2
31
+ Requires-Dist: pandas==3.0.0
32
+ Requires-Dist: psycopg==3.3.2
33
+ Requires-Dist: psycopg-binary==3.3.2
34
+ Requires-Dist: requests==2.32.5
35
+ Requires-Dist: SQLAlchemy==2.0.0
36
+ Requires-Dist: tqdm==4.67.3
37
+ Dynamic: author
38
+ Dynamic: home-page
39
+ Dynamic: license-file
40
+ Dynamic: requires-python
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ pyproject.toml
3
+ setup.py
4
+ forexrates/__init__.py
5
+ forexrates.egg-info/PKG-INFO
6
+ forexrates.egg-info/SOURCES.txt
7
+ forexrates.egg-info/dependency_links.txt
8
+ forexrates.egg-info/requires.txt
9
+ forexrates.egg-info/top_level.txt
10
+ forexrates/api/__init__.py
11
+ forexrates/api/base.py
12
+ forexrates/api/exchangerates.py
13
+ forexrates/io/__init__.py
14
+ forexrates/io/base.py
15
+ forexrates/io/dataframe.py
16
+ main/config.py
17
+ main/erapi.py
@@ -0,0 +1,7 @@
1
+ numpy==2.4.2
2
+ pandas==3.0.0
3
+ psycopg==3.3.2
4
+ psycopg-binary==3.3.2
5
+ requests==2.32.5
6
+ SQLAlchemy==2.0.0
7
+ tqdm==4.67.3
@@ -0,0 +1,4 @@
1
+ dist
2
+ docs
3
+ forexrates
4
+ main
@@ -0,0 +1,49 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ A Set of Configuration Function(s) for Main Files
5
+ """
6
+
7
+ import logging
8
+ import logging.handlers
9
+
10
+ def setLogger(
11
+ name : str = None,
12
+ level : int = logging.DEBUG,
13
+ filename : str = "./status.log"
14
+ ) -> object:
15
+ """
16
+ Setup a logger with pre-built configurations that can be used and
17
+ extended by any endpoints to capture data update, deletion and
18
+ other maintenance logs, using the :mod:`logger` module.
19
+
20
+ :type name: str
21
+ :param name: Name of the logger, else defaults to file name as the
22
+ name (``__name__`` of the execution script).
23
+
24
+ :type level: int
25
+ :param level: Any one of the allowed levels from the :mod:`logger`
26
+ can be used, defaults to debugging mode.
27
+
28
+ :type filename: str
29
+ :param filename: Full absolute/relative path of the logger file to
30
+ store the message, defaults to ``./status.log`` file.
31
+ """
32
+
33
+ logger = logging.getLogger(name or __name__)
34
+ logger.setLevel(level)
35
+
36
+ filehandler = logging.handlers.RotatingFileHandler(
37
+ filename,
38
+ maxBytes = 1024 * 1024,
39
+ backupCount = 1,
40
+ encoding = "utf8"
41
+ )
42
+
43
+ formatter = logging.Formatter(
44
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
45
+ )
46
+
47
+ filehandler.setFormatter(formatter)
48
+ logger.addHandler(filehandler)
49
+ return logger
@@ -0,0 +1,96 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ """
4
+ A Script to Update Data for ExchangeRatesIO on MacroDB Schema
5
+
6
+ Given a database schema with keys and other definitions intact as in
7
+ https://github.com/aivenio/macrodb, the query can be used to update
8
+ the data using CRON schedulers, or GitHub actions.
9
+ """
10
+
11
+ import os
12
+ import sys
13
+
14
+ import pandas as pd
15
+ import datetime as dt
16
+ import sqlalchemy as sa
17
+
18
+ # append additional files, check actions for more information
19
+ sys.path.append(
20
+ os.path.join(os.path.dirname(__file__), "..")
21
+ )
22
+ sys.path.append("dtutils")
23
+
24
+ import forexrates # get the module from repository root
25
+
26
+ # https://ds-gringotts.readthedocs.io/en/latest/modules/utils/dtutils.html
27
+ import datetime_ as dt_ # cloned using git, ./dtutils
28
+
29
+ from config import setLogger # already exists in the current path
30
+
31
+ if __name__ == "__main__":
32
+ API_KEY = os.environ["EXCHANGERATES_IO_API_KEY"]
33
+
34
+ # create a logger for the erapi module
35
+ logger = setLogger(name = "ERAPI")
36
+
37
+ # get configurations for database connection elements, and build
38
+ DATABASE = os.environ["AIVENIO_MACRODB_DATABASE"]
39
+ HOSTNAME = os.environ["AIVENIO_MACRODB_HOSTNAME"]
40
+ PASSWORD = os.environ["AIVENIO_MACRODB_PASSWORD"]
41
+ PORTNAME = os.environ["AIVENIO_MACRODB_PORTNAME"]
42
+ USERNAME = os.environ["AIVENIO_MACRODB_USERNAME"]
43
+
44
+ engine = sa.create_engine(
45
+ f"postgresql+psycopg://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORTNAME}/{DATABASE}"
46
+ )
47
+
48
+ try:
49
+ engine.connect()
50
+ except Exception as err:
51
+ logger.critical(f"Cannot connect to database. Error: {err}")
52
+ else:
53
+ logger.info(f"Connection established to {DATABASE}")
54
+
55
+
56
+ # get the last available date from the database, and then set a
57
+ # context to fetch the date period for which data is required
58
+ statement = """
59
+ SELECT MAX(effective_date)::DATE AS last_date
60
+ FROM common.forex_rate_tx
61
+ """
62
+
63
+ with engine.connect() as connection:
64
+ start_date = connection.execute(
65
+ sa.text(statement)
66
+ ).fetchone()[0]
67
+ start_date += dt.timedelta(days = 1)
68
+
69
+ last_date = dt.datetime.now().date() - dt.timedelta(days = 1)
70
+ dates = list(dt_.date_range(start = start_date, end = last_date))
71
+
72
+ logger.info(f"Trying to fetch data from {start_date} to {last_date}")
73
+ logger.info(f"This will consume {len(dates):,} calls for the API")
74
+
75
+ data = [
76
+ forexrates.api.ExchangeRatesAPI(
77
+ apikey = API_KEY, endpoint = date.strftime("%Y-%m-%d")
78
+ ).get(
79
+ verify = False, suppresswarning = True
80
+ ) for date in dates
81
+ ]
82
+
83
+ parser = forexrates.io.dataframe.ExchangeRatesIO(data)
84
+ dataframe = parser.dataframe(
85
+ index = "exchange_rate", verbose = True
86
+ )
87
+ dataframe["data_source_id"] = "ERAPI"
88
+
89
+ with engine.connect() as connection:
90
+ metadata = sa.Table(
91
+ "forex_rate_tx", sa.MetaData(schema = "common"),
92
+ autoload_with = connection
93
+ )
94
+
95
+ connection.execute(metadata.insert(), dataframe.to_dict(orient = "records"))
96
+ connection.commit()
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = [
3
+ "numpy==2.4.2",
4
+ "pandas==3.0.0",
5
+ "psycopg==3.3.2",
6
+ "psycopg-binary==3.3.2",
7
+ "requests==2.32.5",
8
+ "setuptools==82.0.0",
9
+ "SQLAlchemy==2.0.0",
10
+ "tqdm==4.67.3",
11
+ "wheel==0.46.3",
12
+ ]
13
+ build-backend = "setuptools.build_meta"
14
+
15
+ [project]
16
+ name = "forexrates"
17
+ dynamic = ["version"]
18
+ authors = [
19
+ { name = "shark-utilities developers", email = "neuralNOD@outlook.com" }
20
+ ]
21
+ description = "A unified codebase for fetching FOREX rates."
22
+ # readme = "README.md" # Uncomment if you have a README.md
23
+ requires-python = ">=3.11"
24
+ license = { text = "MIT License" }
25
+ keywords = [
26
+ "forex", "api", "exchange rates", "foreign exchange rates",
27
+ "wrappers", "data science", "data analysis", "data scientist", "data analyst"
28
+ ]
29
+ classifiers = [
30
+ "Development Status :: 1 - Planning",
31
+ "Intended Audience :: Developers",
32
+ "Intended Audience :: Education",
33
+ "Intended Audience :: End Users/Desktop",
34
+ "Intended Audience :: Information Technology",
35
+ "Intended Audience :: Science/Research",
36
+ "Operating System :: Unix",
37
+ "Operating System :: POSIX",
38
+ "Operating System :: Microsoft :: Windows",
39
+ "Programming Language :: Python",
40
+ "Programming Language :: Python :: 3",
41
+ "Programming Language :: Python :: 3 :: Only",
42
+ "Programming Language :: Python :: 3.11",
43
+ "Programming Language :: Python :: 3.12",
44
+ "License :: OSI Approved :: MIT License",
45
+ ]
46
+ dependencies = [
47
+ "numpy==2.4.2",
48
+ "pandas==3.0.0",
49
+ "psycopg==3.3.2",
50
+ "psycopg-binary==3.3.2",
51
+ "requests==2.32.5",
52
+ "SQLAlchemy==2.0.0",
53
+ "tqdm==4.67.3",
54
+ ]
55
+
56
+ [project.urls]
57
+ "Homepage" = "https://github.com/sharkutilities/forexrates"
58
+ "Issue Tracker" = "https://github.com/sharkutilities/forexrates/issues"
59
+ "Org. Homepage" = "https://github.com/sharkutilities"
60
+
61
+ [tool.setuptools]
62
+ py-modules = []
63
+
64
+ [tool.setuptools.packages.find]
65
+ exclude = ["tests*", "examples*"]
66
+
67
+ [tool.setuptools.dynamic]
68
+ version = {attr = "forexrates.__version__"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python
2
+ #
3
+ # Copywright (C) 2024 Debmalya Pramanik <neuralNOD@gmail.com>
4
+ # LICENSE: MIT License
5
+
6
+ import os
7
+ import sys
8
+
9
+ from setuptools import setup
10
+ from setuptools import find_packages
11
+
12
+ sys.path.append(
13
+ os.path.join(os.path.dirname(__file__))
14
+ )
15
+
16
+ import forexrates
17
+
18
+ setup(
19
+ name = "forexrates",
20
+ version = forexrates.__version__,
21
+ author = "shark-utilities developers",
22
+ author_email = "neuralNOD@outlook.com",
23
+ description = "A unified codebase for fetching FOREX rates.",
24
+ # long_description = open("README.md", "r").read(),
25
+ # long_description_content_type = "text/markdown",
26
+ url = "https://github.com/sharkutilities/forexrates",
27
+ packages = find_packages(
28
+ exclude = ["tests*", "examples*"]
29
+ ),
30
+ install_requires = [
31
+ "numpy==2.4.2",
32
+ "pandas==3.0.0",
33
+ "psycopg==3.3.2",
34
+ "psycopg-binary==3.3.2",
35
+ "requests==2.32.5",
36
+ "SQLAlchemy==2.0.0",
37
+ "tqdm==4.67.3",
38
+ ],
39
+ classifiers = [
40
+ "Development Status :: 1 - Planning",
41
+ "Intended Audience :: Developers",
42
+ "Intended Audience :: Education",
43
+ "Intended Audience :: End Users/Desktop",
44
+ "Intended Audience :: Information Technology",
45
+ "Intended Audience :: Science/Research",
46
+ "Operating System :: Unix",
47
+ "Operating System :: POSIX",
48
+ "Operating System :: Microsoft :: Windows",
49
+ "Programming Language :: Python",
50
+ "Programming Language :: Python :: 3",
51
+ "Programming Language :: Python :: 3 :: Only",
52
+ "Programming Language :: Python :: 3.10",
53
+ "Programming Language :: Python :: 3.11",
54
+ "Programming Language :: Python :: 3.12",
55
+ "License :: OSI Approved :: MIT License"
56
+ ],
57
+ project_urls = {
58
+ "Issue Tracker" : "https://github.com/sharkutilities/forexrates/issues",
59
+ # "Code Documentations" : "https://.readthedocs.io/en/latest/index.html",
60
+ "Org. Homepage" : "https://github.com/sharkutilities"
61
+ },
62
+ keywords = [
63
+ # keywords for finding the package::
64
+ "forex", "api", "exchange rates", "foreign exchange rates",
65
+ # keywords for finding the package relevant to usecases::
66
+ "wrappers", "data science", "data analysis", "data scientist", "data analyst"
67
+ ],
68
+ python_requires = ">=3.11"
69
+ )