ecb-rate 0.5.2__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.
ecb_rate-0.5.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PythBuster
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,3 @@
1
+ include pyproject.toml
2
+ include README.md
3
+ include LICENSE
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: ecb-rate
3
+ Version: 0.5.2
4
+ Summary: CLI for ECB JSON exchange rates.
5
+ Author: PythBuster
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/PythBuster/ecb-rate
8
+ Project-URL: Repository, https://github.com/PythBuster/ecb-rate
9
+ Project-URL: Issues, https://github.com/PythBuster/ecb-rate/issues
10
+ Keywords: ecb,exchange-rates,currency,cli,forex
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Office/Business :: Financial
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: <3.15,>=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: aiohttp>=3.13.5
27
+ Requires-Dist: pydantic>=2.12.5
28
+ Dynamic: license-file
29
+
30
+ # ecb-rate
31
+
32
+ Simple CLI tool to fetch EUR-based exchange rates from the European Central Bank (ECB) API.
33
+
34
+ Note: ECB euro foreign exchange reference rates are published for information purposes only.
35
+ Using these rates for transaction purposes is strongly discouraged by the ECB.
36
+
37
+ Official ECB reference:
38
+ https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html
39
+
40
+ ---
41
+
42
+ ## Installation
43
+
44
+ Using `uv` (recommended):
45
+
46
+ uv sync
47
+
48
+ Or with pip:
49
+
50
+ pip install .
51
+
52
+ ---
53
+
54
+ ## Usage
55
+
56
+ ### Basic
57
+
58
+ ecb_rate TRY
59
+
60
+ Output:
61
+
62
+ 51.2795
63
+
64
+ ---
65
+
66
+ ### With specific date
67
+
68
+ ecb_rate TRY --specific-date 2025-06-06
69
+
70
+ ---
71
+
72
+ ### Pretty output
73
+
74
+ ecb_rate TRY --pretty
75
+
76
+ Output:
77
+
78
+ Base currency: EUR
79
+ Target currency: TRY
80
+
81
+ 2025-06-06: 1 EUR = 43.1234 TRY
82
+
83
+ ---
84
+
85
+ ### Version
86
+
87
+ ecb_rate --version
88
+
89
+ Output:
90
+
91
+ ecb_rate 0.4.1
92
+
93
+ Use this option to print the installed CLI version and exit immediately.
94
+
95
+ ---
96
+
97
+ ## Supported currencies
98
+
99
+ The CLI supports all currencies defined by the ECB reference exchange rate dataset (EXR).
100
+
101
+ Currently implemented currencies:
102
+
103
+ - AUD – Australian dollar (Australia)
104
+ - BRL – Brazilian real (Brazil)
105
+ - CAD – Canadian dollar (Canada)
106
+ - CHF – Swiss franc (Switzerland)
107
+ - CNY – Chinese yuan (China)
108
+ - CZK – Czech koruna (Czech Republic)
109
+ - DKK – Danish krone (Denmark)
110
+ - EUR – Euro (Eurozone)
111
+ - GBP – Pound sterling (United Kingdom)
112
+ - HKD – Hong Kong dollar (Hong Kong)
113
+ - HUF – Hungarian forint (Hungary)
114
+ - IDR – Indonesian rupiah (Indonesia)
115
+ - ILS – Israeli shekel (Israel)
116
+ - INR – Indian rupee (India)
117
+ - ISK – Icelandic krona (Iceland)
118
+ - JPY – Japanese yen (Japan)
119
+ - KRW – South Korean won (South Korea)
120
+ - MXN – Mexican peso (Mexico)
121
+ - MYR – Malaysian ringgit (Malaysia)
122
+ - NOK – Norwegian krone (Norway)
123
+ - NZD – New Zealand dollar (New Zealand)
124
+ - PHP – Philippine peso (Philippines)
125
+ - PLN – Polish zloty (Poland)
126
+ - RON – Romanian leu (Romania)
127
+ - SEK – Swedish krona (Sweden)
128
+ - SGD – Singapore dollar (Singapore)
129
+ - THB – Thai baht (Thailand)
130
+ - TRY – Turkish lira (Turkey)
131
+ - USD – US dollar (United States)
132
+ - ZAR – South African rand (South Africa)
133
+
134
+ Source:
135
+ https://data-api.ecb.europa.eu/service/data/EXR
136
+
137
+ Currency support in this tool is defined via the `CurrencyType` enum.
138
+
139
+ ---
140
+
141
+ ## API
142
+
143
+ Uses the official ECB Data Portal:
144
+
145
+ https://data-api.ecb.europa.eu/service/data/EXR
146
+
147
+ Format:
148
+
149
+ - jsondata (SDMX JSON)
150
+
151
+ Reference rates are intended for informational use and should not be treated as executable market prices.
152
+
153
+ ---
154
+
155
+ ## Project structure
156
+
157
+ ecb_rate/
158
+ ├─ cli.py
159
+ ├─ client.py
160
+ ├─ custom_types.py
161
+ ├─ models.py
162
+ ├─ service.py
163
+ └─ utils.py
164
+
165
+ ---
166
+
167
+ ## Development
168
+
169
+ Install dev dependencies:
170
+
171
+ uv sync --dev
172
+
173
+ Run tests:
174
+
175
+ pytest
@@ -0,0 +1,146 @@
1
+ # ecb-rate
2
+
3
+ Simple CLI tool to fetch EUR-based exchange rates from the European Central Bank (ECB) API.
4
+
5
+ Note: ECB euro foreign exchange reference rates are published for information purposes only.
6
+ Using these rates for transaction purposes is strongly discouraged by the ECB.
7
+
8
+ Official ECB reference:
9
+ https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html
10
+
11
+ ---
12
+
13
+ ## Installation
14
+
15
+ Using `uv` (recommended):
16
+
17
+ uv sync
18
+
19
+ Or with pip:
20
+
21
+ pip install .
22
+
23
+ ---
24
+
25
+ ## Usage
26
+
27
+ ### Basic
28
+
29
+ ecb_rate TRY
30
+
31
+ Output:
32
+
33
+ 51.2795
34
+
35
+ ---
36
+
37
+ ### With specific date
38
+
39
+ ecb_rate TRY --specific-date 2025-06-06
40
+
41
+ ---
42
+
43
+ ### Pretty output
44
+
45
+ ecb_rate TRY --pretty
46
+
47
+ Output:
48
+
49
+ Base currency: EUR
50
+ Target currency: TRY
51
+
52
+ 2025-06-06: 1 EUR = 43.1234 TRY
53
+
54
+ ---
55
+
56
+ ### Version
57
+
58
+ ecb_rate --version
59
+
60
+ Output:
61
+
62
+ ecb_rate 0.4.1
63
+
64
+ Use this option to print the installed CLI version and exit immediately.
65
+
66
+ ---
67
+
68
+ ## Supported currencies
69
+
70
+ The CLI supports all currencies defined by the ECB reference exchange rate dataset (EXR).
71
+
72
+ Currently implemented currencies:
73
+
74
+ - AUD – Australian dollar (Australia)
75
+ - BRL – Brazilian real (Brazil)
76
+ - CAD – Canadian dollar (Canada)
77
+ - CHF – Swiss franc (Switzerland)
78
+ - CNY – Chinese yuan (China)
79
+ - CZK – Czech koruna (Czech Republic)
80
+ - DKK – Danish krone (Denmark)
81
+ - EUR – Euro (Eurozone)
82
+ - GBP – Pound sterling (United Kingdom)
83
+ - HKD – Hong Kong dollar (Hong Kong)
84
+ - HUF – Hungarian forint (Hungary)
85
+ - IDR – Indonesian rupiah (Indonesia)
86
+ - ILS – Israeli shekel (Israel)
87
+ - INR – Indian rupee (India)
88
+ - ISK – Icelandic krona (Iceland)
89
+ - JPY – Japanese yen (Japan)
90
+ - KRW – South Korean won (South Korea)
91
+ - MXN – Mexican peso (Mexico)
92
+ - MYR – Malaysian ringgit (Malaysia)
93
+ - NOK – Norwegian krone (Norway)
94
+ - NZD – New Zealand dollar (New Zealand)
95
+ - PHP – Philippine peso (Philippines)
96
+ - PLN – Polish zloty (Poland)
97
+ - RON – Romanian leu (Romania)
98
+ - SEK – Swedish krona (Sweden)
99
+ - SGD – Singapore dollar (Singapore)
100
+ - THB – Thai baht (Thailand)
101
+ - TRY – Turkish lira (Turkey)
102
+ - USD – US dollar (United States)
103
+ - ZAR – South African rand (South Africa)
104
+
105
+ Source:
106
+ https://data-api.ecb.europa.eu/service/data/EXR
107
+
108
+ Currency support in this tool is defined via the `CurrencyType` enum.
109
+
110
+ ---
111
+
112
+ ## API
113
+
114
+ Uses the official ECB Data Portal:
115
+
116
+ https://data-api.ecb.europa.eu/service/data/EXR
117
+
118
+ Format:
119
+
120
+ - jsondata (SDMX JSON)
121
+
122
+ Reference rates are intended for informational use and should not be treated as executable market prices.
123
+
124
+ ---
125
+
126
+ ## Project structure
127
+
128
+ ecb_rate/
129
+ ├─ cli.py
130
+ ├─ client.py
131
+ ├─ custom_types.py
132
+ ├─ models.py
133
+ ├─ service.py
134
+ └─ utils.py
135
+
136
+ ---
137
+
138
+ ## Development
139
+
140
+ Install dev dependencies:
141
+
142
+ uv sync --dev
143
+
144
+ Run tests:
145
+
146
+ pytest
File without changes
@@ -0,0 +1,133 @@
1
+ """
2
+ CLI entry point for ecb_rate.
3
+
4
+ Examples:
5
+ ecb_rate TRY
6
+ ecb_rate try
7
+ ecb_rate TRY --specific-date 2025-06-06
8
+ ecb_rate TRY --pretty
9
+ """
10
+
11
+ import argparse
12
+ import asyncio
13
+ import sys
14
+ from datetime import date
15
+
16
+ from pydantic import ValidationError
17
+ from importlib.metadata import metadata
18
+
19
+ from ecb_rate.client import ECBJsonClient
20
+ from ecb_rate.models import CliInputError, EcbRateError, QueryParams, RatePoint
21
+ from ecb_rate.service import EcbJsonParser, ExchangeRateService
22
+
23
+
24
+ class CliApplication:
25
+ """
26
+ Application entry point coordinating argparse and the service layer.
27
+ """
28
+
29
+ def __init__(self) -> None:
30
+ client = ECBJsonClient(timeout_seconds=10)
31
+ parser = EcbJsonParser()
32
+ self._service = ExchangeRateService(
33
+ client=client,
34
+ parser=parser,
35
+ )
36
+
37
+ def run(self, argv: list[str] | None = None) -> int:
38
+ try:
39
+ args = self._parse_args(argv)
40
+ query = self._build_query(args)
41
+ result = asyncio.run(self._service.get_rate(query))
42
+ self._print_result(result, pretty=args.pretty)
43
+ return 0
44
+ except (CliInputError, EcbRateError) as exc:
45
+ print(f"Error: {exc}", file=sys.stderr)
46
+ return 1
47
+
48
+ @staticmethod
49
+ def _parse_args(argv: list[str] | None) -> argparse.Namespace:
50
+ package_metadata = metadata("ecb-rate")
51
+
52
+ project_name = package_metadata["Name"].replace("-", "_")
53
+ project_version = package_metadata["Version"]
54
+ project_description = package_metadata["Summary"]
55
+
56
+ parser = argparse.ArgumentParser(
57
+ prog=project_name,
58
+ description=project_description,
59
+ )
60
+
61
+ parser.add_argument(
62
+ "--version",
63
+ action="version",
64
+ version=f"%(prog)s {project_version}",
65
+ help="Show the installed CLI version and exit.",
66
+ )
67
+ parser.add_argument(
68
+ "target_currency",
69
+ nargs="?",
70
+ help="Target currency code, for example TRY or USD.",
71
+ )
72
+ parser.add_argument(
73
+ "--specific-date",
74
+ dest="specific_date",
75
+ default=None,
76
+ help="Specific date in YYYY-MM-DD format. Defaults to today.",
77
+ )
78
+ parser.add_argument(
79
+ "--pretty",
80
+ action="store_true",
81
+ help="Print formatted output with base currency, target currency, and rate date.",
82
+ )
83
+
84
+ args = parser.parse_args(argv)
85
+
86
+ if args.target_currency is None:
87
+ parser.error("the following arguments are required: target_currency")
88
+
89
+ return args
90
+
91
+ @staticmethod
92
+ def _build_query(args: argparse.Namespace) -> QueryParams:
93
+ if args.specific_date is None:
94
+ specific_date = date.today()
95
+ else:
96
+ try:
97
+ specific_date = date.fromisoformat(args.specific_date)
98
+ except ValueError as exc:
99
+ raise CliInputError(
100
+ f"Invalid --specific-date: {args.specific_date}. Expected YYYY-MM-DD."
101
+ ) from exc
102
+
103
+ try:
104
+ return QueryParams(
105
+ target_currency=args.target_currency,
106
+ specific_date=specific_date,
107
+ )
108
+ except (ValidationError, ValueError) as exc:
109
+ raise CliInputError(str(exc)) from exc
110
+
111
+ @staticmethod
112
+ def _print_result(rate_point: RatePoint, pretty: bool = False) -> None:
113
+ if not pretty:
114
+ print(f"{rate_point.rate:.4f}")
115
+ return
116
+
117
+ print(f"Base currency: {rate_point.base_currency.code}")
118
+ print(f"Target currency: {rate_point.target_currency.code}")
119
+ print()
120
+
121
+ print(
122
+ f"{rate_point.date.isoformat()}: "
123
+ f"1 {rate_point.base_currency.code} = {rate_point.rate:.4f} {rate_point.target_currency.code}"
124
+ )
125
+
126
+
127
+ def main() -> int:
128
+ app = CliApplication()
129
+ return app.run()
130
+
131
+
132
+ if __name__ == "__main__": # pragma: no cover
133
+ sys.exit(main())
@@ -0,0 +1,93 @@
1
+ """
2
+ ECB-specific async JSON API client.
3
+ """
4
+
5
+ from datetime import date
6
+ from typing import Any
7
+
8
+ import aiohttp
9
+
10
+ from ecb_rate.custom_types import CurrencyType
11
+ from ecb_rate.models import EcbApiError
12
+
13
+
14
+ class ECBJsonClient:
15
+ """
16
+ Async HTTP client for the ECB Data Portal API.
17
+
18
+ This client is responsible for:
19
+ - building ECB-specific URLs
20
+ - executing HTTP requests
21
+ - returning parsed JSON responses
22
+ """
23
+
24
+ BASE_URL = "https://data-api.ecb.europa.eu/service/data/EXR"
25
+
26
+ def __init__(self, timeout_seconds: int = 10) -> None:
27
+ self._timeout = aiohttp.ClientTimeout(total=timeout_seconds)
28
+
29
+ async def fetch(
30
+ self,
31
+ currency: CurrencyType,
32
+ specific_date: date,
33
+ ) -> dict[str, Any]:
34
+ """
35
+ Fetch ECB time series data in jsondata format for a specific date.
36
+
37
+ Uses:
38
+ D.<CURRENCY>.EUR.SP00.A
39
+ format=jsondata
40
+ lastNObservations=1
41
+ endPeriod=<date>
42
+ details=dataonly
43
+
44
+ This returns the latest available observation up to the given date.
45
+ """
46
+ series_key = f"D.{currency.code}.EUR.SP00.A"
47
+
48
+ iso_formatted_date = specific_date.isoformat()
49
+ params = {
50
+ "format": "jsondata",
51
+ "lastNObservations": 1,
52
+ "endPeriod": iso_formatted_date,
53
+ "details": "dataonly",
54
+ }
55
+
56
+ headers = {
57
+ "Accept": "application/json",
58
+ "User-Agent": "ecb_rate/0.1.0",
59
+ }
60
+
61
+ url = f"{self.BASE_URL}/{series_key}"
62
+
63
+ try:
64
+ async with aiohttp.ClientSession(
65
+ timeout=self._timeout,
66
+ headers=headers,
67
+ ) as session:
68
+ async with session.get(url, params=params) as response:
69
+ if response.status >= 400:
70
+ text = await response.text()
71
+ raise EcbApiError(
72
+ f"HTTP error: {response.status} {response.reason}. "
73
+ f"Response: {text}"
74
+ )
75
+
76
+ try:
77
+ data = await response.json()
78
+
79
+ except aiohttp.ContentTypeError as exc:
80
+ text = await response.text()
81
+ raise EcbApiError(
82
+ f"Expected JSON but got different content: {text}"
83
+ ) from exc
84
+
85
+ if not isinstance(data, dict):
86
+ raise EcbApiError("Expected JSON object as top-level response.")
87
+
88
+ return data
89
+
90
+ except aiohttp.ClientError as exc:
91
+ raise EcbApiError(f"Network error: {exc}") from exc
92
+ except TimeoutError as exc:
93
+ raise EcbApiError("Request timeout.") from exc
@@ -0,0 +1,111 @@
1
+ """
2
+ Custom shared types for ecb_rate.
3
+ """
4
+
5
+ from enum import StrEnum
6
+
7
+
8
+ class CurrencyType(StrEnum):
9
+ """
10
+ Supported currencies for the CLI.
11
+
12
+ Values are stored in lowercase. Use ``code`` for the ECB-compatible
13
+ uppercase representation.
14
+ """
15
+
16
+ AUD = "aud"
17
+ # Australian dollar (Australia)
18
+
19
+ BRL = "brl"
20
+ # Brazilian real (Brazil)
21
+
22
+ CAD = "cad"
23
+ # Canadian dollar (Canada)
24
+
25
+ CHF = "chf"
26
+ # Swiss franc (Switzerland)
27
+
28
+ CNY = "cny"
29
+ # Chinese yuan (China)
30
+
31
+ CZK = "czk"
32
+ # Czech koruna (Czech Republic)
33
+
34
+ DKK = "dkk"
35
+ # Danish krone (Denmark)
36
+
37
+ EUR = "eur"
38
+ # Euro (Eurozone)
39
+
40
+ GBP = "gbp"
41
+ # Pound sterling (United Kingdom)
42
+
43
+ HKD = "hkd"
44
+ # Hong Kong dollar (Hong Kong)
45
+
46
+ HUF = "huf"
47
+ # Hungarian forint (Hungary)
48
+
49
+ IDR = "idr"
50
+ # Indonesian rupiah (Indonesia)
51
+
52
+ ILS = "ils"
53
+ # Israeli shekel (Israel)
54
+
55
+ INR = "inr"
56
+ # Indian rupee (India)
57
+
58
+ ISK = "isk"
59
+ # Icelandic krona (Iceland)
60
+
61
+ JPY = "jpy"
62
+ # Japanese yen (Japan)
63
+
64
+ KRW = "krw"
65
+ # South Korean won (South Korea)
66
+
67
+ MXN = "mxn"
68
+ # Mexican peso (Mexico)
69
+
70
+ MYR = "myr"
71
+ # Malaysian ringgit (Malaysia)
72
+
73
+ NOK = "nok"
74
+ # Norwegian krone (Norway)
75
+
76
+ NZD = "nzd"
77
+ # New Zealand dollar (New Zealand)
78
+
79
+ PHP = "php"
80
+ # Philippine peso (Philippines)
81
+
82
+ PLN = "pln"
83
+ # Polish zloty (Poland)
84
+
85
+ RON = "ron"
86
+ # Romanian leu (Romania)
87
+
88
+ SEK = "sek"
89
+ # Swedish krona (Sweden)
90
+
91
+ SGD = "sgd"
92
+ # Singapore dollar (Singapore)
93
+
94
+ THB = "thb"
95
+ # Thai baht (Thailand)
96
+
97
+ TRY = "try"
98
+ # Turkish lira (Turkey)
99
+
100
+ USD = "usd"
101
+ # US dollar (United States)
102
+
103
+ ZAR = "zar"
104
+ # South African rand (South Africa)
105
+
106
+ @property
107
+ def code(self) -> str:
108
+ """
109
+ Return the ECB-compatible uppercase currency code.
110
+ """
111
+ return self.value.upper()