ecbfx 0.2.0__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.
- ecbfx-0.2.0/.gitignore +11 -0
- ecbfx-0.2.0/CHANGELOG.md +10 -0
- ecbfx-0.2.0/LICENSE +21 -0
- ecbfx-0.2.0/PKG-INFO +226 -0
- ecbfx-0.2.0/README.md +194 -0
- ecbfx-0.2.0/pyproject.toml +58 -0
- ecbfx-0.2.0/src/ecbfx/__init__.py +31 -0
- ecbfx-0.2.0/src/ecbfx/api.py +473 -0
- ecbfx-0.2.0/src/ecbfx/cli.py +490 -0
- ecbfx-0.2.0/tests/test_ecbfx.py +828 -0
ecbfx-0.2.0/.gitignore
ADDED
ecbfx-0.2.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 — 2026-05-01
|
|
4
|
+
- Added `fetch_rates_for_pairs()` — sparse transaction date support
|
|
5
|
+
- Added `fetch_latest()` — most recent available rate
|
|
6
|
+
- Added `validate_currency()` — public input validation
|
|
7
|
+
- Added `--pairs`, `--latest`, `--quiet`, `--decimal`, `--no-gap-fill` CLI flags
|
|
8
|
+
- LOCF gap-filling — always uses prior trading day, never look-ahead
|
|
9
|
+
- HTTP session reuse, exponential backoff with jitter
|
|
10
|
+
- Fixed: `re.fullmatch` for currency validation (trailing newline bypass)
|
ecbfx-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 edvinassvedas-dev
|
|
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.
|
ecbfx-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ecbfx
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: ECB foreign exchange rate lookup — CLI and Python API with gap-fill, strict mode, and scripting support
|
|
5
|
+
Project-URL: Homepage, https://github.com/edvinassvedas-dev/ecbfx
|
|
6
|
+
Project-URL: Repository, https://github.com/edvinassvedas-dev/ecbfx
|
|
7
|
+
Project-URL: Issues, https://github.com/edvinassvedas-dev/ecbfx/issues
|
|
8
|
+
Author: Edvinas Švedas
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,currency,ecb,exchange-rate,finance,forex,fx
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: requests>=2.28
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
30
|
+
Requires-Dist: responses; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# ecbfx
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/ecbfx/)
|
|
36
|
+
[](https://pypi.org/project/ecbfx/)
|
|
37
|
+
[](https://github.com/edvinassvedas-dev/ecbfx/blob/main/LICENSE)
|
|
38
|
+
|
|
39
|
+
A minimal command-line tool and Python library for fetching EUR foreign exchange
|
|
40
|
+
rates directly from the [ECB SDMX API](https://data-api.ecb.europa.eu).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install ecbfx
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**For development** (includes test dependencies):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/edvinassvedas-dev/ecbfx.git
|
|
54
|
+
cd ecbfx
|
|
55
|
+
pip install -e ".[dev]"
|
|
56
|
+
pytest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## CLI usage
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Today's rate for USD (indirect: USD per 1 EUR — ECB native)
|
|
63
|
+
# Note: ECB publishes rates ~16:00 CET on trading days.
|
|
64
|
+
# If today's rate isn't available yet, use --latest instead.
|
|
65
|
+
ecbfx USD
|
|
66
|
+
|
|
67
|
+
# Specific date
|
|
68
|
+
ecbfx USD 2025-01-15
|
|
69
|
+
|
|
70
|
+
# Multiple currencies, specific date
|
|
71
|
+
ecbfx USD GBP CHF 2025-01-15
|
|
72
|
+
|
|
73
|
+
# Date range
|
|
74
|
+
ecbfx USD GBP --from 2025-01-01 --to 2025-03-31
|
|
75
|
+
|
|
76
|
+
# Direct convention: EUR per 1 foreign unit (inverted)
|
|
77
|
+
ecbfx USD 2025-01-15 --direct
|
|
78
|
+
|
|
79
|
+
# Most recent available rate (ignores today being a weekend/holiday)
|
|
80
|
+
ecbfx USD --latest
|
|
81
|
+
|
|
82
|
+
# Single value only — ideal for shell scripting
|
|
83
|
+
ecbfx USD --latest --quiet
|
|
84
|
+
RATE=$(ecbfx USD --quiet)
|
|
85
|
+
|
|
86
|
+
# Control decimal precision (default: 4)
|
|
87
|
+
ecbfx USD 2025-01-15 --decimal 6
|
|
88
|
+
|
|
89
|
+
# Strict mode — error instead of gap-filling on weekends/holidays
|
|
90
|
+
ecbfx USD 2025-01-15 --no-gap-fill
|
|
91
|
+
|
|
92
|
+
# CSV output (pipe-friendly)
|
|
93
|
+
ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv
|
|
94
|
+
ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv > rates.csv
|
|
95
|
+
|
|
96
|
+
# Read pairs from a file — one HTTP call per currency
|
|
97
|
+
ecbfx --pairs transactions.csv --direct --csv
|
|
98
|
+
|
|
99
|
+
# Read pairs from stdin
|
|
100
|
+
cat transactions.csv | ecbfx --pairs - --direct --csv
|
|
101
|
+
|
|
102
|
+
# Combine with date filter
|
|
103
|
+
ecbfx --pairs transactions.csv --from 2025-01-01 --to 2025-03-31 --csv
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Convention
|
|
107
|
+
|
|
108
|
+
| Flag | Formula | Example |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| *(default)* | foreign units per 1 EUR | `1 EUR = 1.0830 USD` |
|
|
111
|
+
| `--direct` | EUR per 1 foreign unit | `1 USD = 0.9234 EUR` |
|
|
112
|
+
|
|
113
|
+
ECB publishes indirect natively. `--direct` inverts the rate.
|
|
114
|
+
The `convention` column in CSV output (`USD/EUR` or `EUR/USD`) makes the
|
|
115
|
+
direction explicit for downstream pipelines.
|
|
116
|
+
|
|
117
|
+
### Flags reference
|
|
118
|
+
|
|
119
|
+
| Flag | Default | Description |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `--direct` | off | EUR per 1 foreign unit instead of ECB native |
|
|
122
|
+
| `--latest` | off | Most recent available rate, regardless of date |
|
|
123
|
+
| `--quiet` / `-q` | off | Print rate value(s) only — ideal for scripting |
|
|
124
|
+
| `--decimal N` | 4 | Decimal places in output rate |
|
|
125
|
+
| `--no-gap-fill` | off | Raise an error on weekends/holidays instead of substituting the nearest rate |
|
|
126
|
+
| `--csv` | off | CSV output instead of formatted table |
|
|
127
|
+
| `--pairs FILE\|-` | — | Read `date,currency` pairs from a file or stdin |
|
|
128
|
+
|
|
129
|
+
### Weekend and holiday gap-filling
|
|
130
|
+
|
|
131
|
+
ECB only publishes rates on trading days. By default, `ecbfx` automatically
|
|
132
|
+
uses the most recent prior trading day's rate (Last Observation Carried Forward)
|
|
133
|
+
when a requested date falls on a weekend or public holiday — including the first
|
|
134
|
+
date in a range that starts on a holiday such as January 1st.
|
|
135
|
+
|
|
136
|
+
Use `--no-gap-fill` to disable this and receive an explicit error instead —
|
|
137
|
+
useful in audit workflows where a substituted rate is not acceptable.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### Exit codes
|
|
143
|
+
|
|
144
|
+
| Code | Meaning |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `0` | Success |
|
|
147
|
+
| `1` | Runtime error (ECB API failure, network issue, no data returned) |
|
|
148
|
+
| `2` | Usage error (invalid arguments, bad date format, missing required flag) |
|
|
149
|
+
|
|
150
|
+
Useful for scripting:
|
|
151
|
+
```bash
|
|
152
|
+
ecbfx USD --quiet || echo "fetch failed, exit $?"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Python API
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from datetime import date
|
|
159
|
+
from ecbfx import fetch_rates, fetch_rates_for_pairs, fetch_latest
|
|
160
|
+
|
|
161
|
+
# Indirect (default) — foreign units per 1 EUR, contiguous range
|
|
162
|
+
rows = fetch_rates(["USD", "GBP"], date(2025, 1, 1), date(2025, 1, 31))
|
|
163
|
+
|
|
164
|
+
# Direct — EUR per 1 foreign unit
|
|
165
|
+
rows = fetch_rates(["USD"], date(2025, 1, 15), date(2025, 1, 15), direct=True)
|
|
166
|
+
|
|
167
|
+
# Most recent available rate
|
|
168
|
+
rows = fetch_latest(["USD", "CHF"])
|
|
169
|
+
|
|
170
|
+
# Sparse transaction dates — one HTTP call per currency regardless of pair count
|
|
171
|
+
pairs = [
|
|
172
|
+
(date(2025, 1, 15), "USD"),
|
|
173
|
+
(date(2025, 1, 20), "GBP"),
|
|
174
|
+
(date(2025, 2, 3), "USD"),
|
|
175
|
+
(date(2025, 2, 3), "CHF"),
|
|
176
|
+
]
|
|
177
|
+
rows = fetch_rates_for_pairs(pairs, direct=True)
|
|
178
|
+
|
|
179
|
+
# Custom decimal precision
|
|
180
|
+
rows = fetch_rates(["USD"], date(2025, 1, 15), date(2025, 1, 15), decimals=6)
|
|
181
|
+
|
|
182
|
+
# Strict mode — raises ECBError on weekends/holidays
|
|
183
|
+
rows = fetch_rates(["USD"], date(2025, 1, 13), date(2025, 1, 13), gap_fill=False)
|
|
184
|
+
rows = fetch_rates_for_pairs([(date(2025, 1, 13), "USD")], gap_fill=False)
|
|
185
|
+
|
|
186
|
+
for r in rows:
|
|
187
|
+
print(r["date"], r["currency"], r["convention"], r["rate"])
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Each row is a dict: `{date, currency, rate, convention}`.
|
|
191
|
+
|
|
192
|
+
### Input validation
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from ecbfx import validate_currency, ECBError
|
|
196
|
+
|
|
197
|
+
# Normalises and validates a currency code — raises ECBError if invalid
|
|
198
|
+
print(validate_currency("usd")) # → "USD"
|
|
199
|
+
print(validate_currency(" GBP ")) # → "GBP"
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
validate_currency("US$")
|
|
203
|
+
except ECBError as e:
|
|
204
|
+
print(e) # Invalid currency code 'US$'. Expected 2–4 ASCII letters.
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Use `ECBError` in `try/except` blocks when calling any `ecbfx` function
|
|
208
|
+
to handle API failures, network errors, or invalid inputs cleanly.
|
|
209
|
+
|
|
210
|
+
### fetch_rates vs fetch_rates_for_pairs
|
|
211
|
+
|
|
212
|
+
| | `fetch_rates` | `fetch_rates_for_pairs` |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| Input | currency list + date range | list of `(date, currency)` tuples |
|
|
215
|
+
| Returns | every calendar day in range | exactly the requested dates |
|
|
216
|
+
| Best for | daily pipelines, backfill | transaction enrichment, broker CSVs |
|
|
217
|
+
| HTTP calls | one per currency | one per currency (full span, regardless of gaps) |
|
|
218
|
+
|
|
219
|
+
> **Note:** `fetch_rates_for_pairs` always fetches the full date span from the earliest to the latest date per currency in a single HTTP call. For very sparse data (e.g. two transactions 10 years apart), this fetches the entire intervening range. A warning is logged when the span exceeds one year.
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
ecbfx-0.2.0/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# ecbfx
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/ecbfx/)
|
|
4
|
+
[](https://pypi.org/project/ecbfx/)
|
|
5
|
+
[](https://github.com/edvinassvedas-dev/ecbfx/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
A minimal command-line tool and Python library for fetching EUR foreign exchange
|
|
8
|
+
rates directly from the [ECB SDMX API](https://data-api.ecb.europa.eu).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install ecbfx
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**For development** (includes test dependencies):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/edvinassvedas-dev/ecbfx.git
|
|
22
|
+
cd ecbfx
|
|
23
|
+
pip install -e ".[dev]"
|
|
24
|
+
pytest
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## CLI usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Today's rate for USD (indirect: USD per 1 EUR — ECB native)
|
|
31
|
+
# Note: ECB publishes rates ~16:00 CET on trading days.
|
|
32
|
+
# If today's rate isn't available yet, use --latest instead.
|
|
33
|
+
ecbfx USD
|
|
34
|
+
|
|
35
|
+
# Specific date
|
|
36
|
+
ecbfx USD 2025-01-15
|
|
37
|
+
|
|
38
|
+
# Multiple currencies, specific date
|
|
39
|
+
ecbfx USD GBP CHF 2025-01-15
|
|
40
|
+
|
|
41
|
+
# Date range
|
|
42
|
+
ecbfx USD GBP --from 2025-01-01 --to 2025-03-31
|
|
43
|
+
|
|
44
|
+
# Direct convention: EUR per 1 foreign unit (inverted)
|
|
45
|
+
ecbfx USD 2025-01-15 --direct
|
|
46
|
+
|
|
47
|
+
# Most recent available rate (ignores today being a weekend/holiday)
|
|
48
|
+
ecbfx USD --latest
|
|
49
|
+
|
|
50
|
+
# Single value only — ideal for shell scripting
|
|
51
|
+
ecbfx USD --latest --quiet
|
|
52
|
+
RATE=$(ecbfx USD --quiet)
|
|
53
|
+
|
|
54
|
+
# Control decimal precision (default: 4)
|
|
55
|
+
ecbfx USD 2025-01-15 --decimal 6
|
|
56
|
+
|
|
57
|
+
# Strict mode — error instead of gap-filling on weekends/holidays
|
|
58
|
+
ecbfx USD 2025-01-15 --no-gap-fill
|
|
59
|
+
|
|
60
|
+
# CSV output (pipe-friendly)
|
|
61
|
+
ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv
|
|
62
|
+
ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv > rates.csv
|
|
63
|
+
|
|
64
|
+
# Read pairs from a file — one HTTP call per currency
|
|
65
|
+
ecbfx --pairs transactions.csv --direct --csv
|
|
66
|
+
|
|
67
|
+
# Read pairs from stdin
|
|
68
|
+
cat transactions.csv | ecbfx --pairs - --direct --csv
|
|
69
|
+
|
|
70
|
+
# Combine with date filter
|
|
71
|
+
ecbfx --pairs transactions.csv --from 2025-01-01 --to 2025-03-31 --csv
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Convention
|
|
75
|
+
|
|
76
|
+
| Flag | Formula | Example |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| *(default)* | foreign units per 1 EUR | `1 EUR = 1.0830 USD` |
|
|
79
|
+
| `--direct` | EUR per 1 foreign unit | `1 USD = 0.9234 EUR` |
|
|
80
|
+
|
|
81
|
+
ECB publishes indirect natively. `--direct` inverts the rate.
|
|
82
|
+
The `convention` column in CSV output (`USD/EUR` or `EUR/USD`) makes the
|
|
83
|
+
direction explicit for downstream pipelines.
|
|
84
|
+
|
|
85
|
+
### Flags reference
|
|
86
|
+
|
|
87
|
+
| Flag | Default | Description |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `--direct` | off | EUR per 1 foreign unit instead of ECB native |
|
|
90
|
+
| `--latest` | off | Most recent available rate, regardless of date |
|
|
91
|
+
| `--quiet` / `-q` | off | Print rate value(s) only — ideal for scripting |
|
|
92
|
+
| `--decimal N` | 4 | Decimal places in output rate |
|
|
93
|
+
| `--no-gap-fill` | off | Raise an error on weekends/holidays instead of substituting the nearest rate |
|
|
94
|
+
| `--csv` | off | CSV output instead of formatted table |
|
|
95
|
+
| `--pairs FILE\|-` | — | Read `date,currency` pairs from a file or stdin |
|
|
96
|
+
|
|
97
|
+
### Weekend and holiday gap-filling
|
|
98
|
+
|
|
99
|
+
ECB only publishes rates on trading days. By default, `ecbfx` automatically
|
|
100
|
+
uses the most recent prior trading day's rate (Last Observation Carried Forward)
|
|
101
|
+
when a requested date falls on a weekend or public holiday — including the first
|
|
102
|
+
date in a range that starts on a holiday such as January 1st.
|
|
103
|
+
|
|
104
|
+
Use `--no-gap-fill` to disable this and receive an explicit error instead —
|
|
105
|
+
useful in audit workflows where a substituted rate is not acceptable.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
### Exit codes
|
|
111
|
+
|
|
112
|
+
| Code | Meaning |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `0` | Success |
|
|
115
|
+
| `1` | Runtime error (ECB API failure, network issue, no data returned) |
|
|
116
|
+
| `2` | Usage error (invalid arguments, bad date format, missing required flag) |
|
|
117
|
+
|
|
118
|
+
Useful for scripting:
|
|
119
|
+
```bash
|
|
120
|
+
ecbfx USD --quiet || echo "fetch failed, exit $?"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Python API
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from datetime import date
|
|
127
|
+
from ecbfx import fetch_rates, fetch_rates_for_pairs, fetch_latest
|
|
128
|
+
|
|
129
|
+
# Indirect (default) — foreign units per 1 EUR, contiguous range
|
|
130
|
+
rows = fetch_rates(["USD", "GBP"], date(2025, 1, 1), date(2025, 1, 31))
|
|
131
|
+
|
|
132
|
+
# Direct — EUR per 1 foreign unit
|
|
133
|
+
rows = fetch_rates(["USD"], date(2025, 1, 15), date(2025, 1, 15), direct=True)
|
|
134
|
+
|
|
135
|
+
# Most recent available rate
|
|
136
|
+
rows = fetch_latest(["USD", "CHF"])
|
|
137
|
+
|
|
138
|
+
# Sparse transaction dates — one HTTP call per currency regardless of pair count
|
|
139
|
+
pairs = [
|
|
140
|
+
(date(2025, 1, 15), "USD"),
|
|
141
|
+
(date(2025, 1, 20), "GBP"),
|
|
142
|
+
(date(2025, 2, 3), "USD"),
|
|
143
|
+
(date(2025, 2, 3), "CHF"),
|
|
144
|
+
]
|
|
145
|
+
rows = fetch_rates_for_pairs(pairs, direct=True)
|
|
146
|
+
|
|
147
|
+
# Custom decimal precision
|
|
148
|
+
rows = fetch_rates(["USD"], date(2025, 1, 15), date(2025, 1, 15), decimals=6)
|
|
149
|
+
|
|
150
|
+
# Strict mode — raises ECBError on weekends/holidays
|
|
151
|
+
rows = fetch_rates(["USD"], date(2025, 1, 13), date(2025, 1, 13), gap_fill=False)
|
|
152
|
+
rows = fetch_rates_for_pairs([(date(2025, 1, 13), "USD")], gap_fill=False)
|
|
153
|
+
|
|
154
|
+
for r in rows:
|
|
155
|
+
print(r["date"], r["currency"], r["convention"], r["rate"])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Each row is a dict: `{date, currency, rate, convention}`.
|
|
159
|
+
|
|
160
|
+
### Input validation
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from ecbfx import validate_currency, ECBError
|
|
164
|
+
|
|
165
|
+
# Normalises and validates a currency code — raises ECBError if invalid
|
|
166
|
+
print(validate_currency("usd")) # → "USD"
|
|
167
|
+
print(validate_currency(" GBP ")) # → "GBP"
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
validate_currency("US$")
|
|
171
|
+
except ECBError as e:
|
|
172
|
+
print(e) # Invalid currency code 'US$'. Expected 2–4 ASCII letters.
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Use `ECBError` in `try/except` blocks when calling any `ecbfx` function
|
|
176
|
+
to handle API failures, network errors, or invalid inputs cleanly.
|
|
177
|
+
|
|
178
|
+
### fetch_rates vs fetch_rates_for_pairs
|
|
179
|
+
|
|
180
|
+
| | `fetch_rates` | `fetch_rates_for_pairs` |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| Input | currency list + date range | list of `(date, currency)` tuples |
|
|
183
|
+
| Returns | every calendar day in range | exactly the requested dates |
|
|
184
|
+
| Best for | daily pipelines, backfill | transaction enrichment, broker CSVs |
|
|
185
|
+
| HTTP calls | one per currency | one per currency (full span, regardless of gaps) |
|
|
186
|
+
|
|
187
|
+
> **Note:** `fetch_rates_for_pairs` always fetches the full date span from the earliest to the latest date per currency in a single HTTP call. For very sparse data (e.g. two transactions 10 years apart), this fetches the entire intervening range. A warning is logged when the span exceeds one year.
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ecbfx"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "ECB foreign exchange rate lookup — CLI and Python API with gap-fill, strict mode, and scripting support"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
keywords = ["ecb", "fx", "forex", "exchange-rate", "currency", "finance", "cli"]
|
|
13
|
+
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "Edvinas Švedas"},
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Office/Business :: Financial",
|
|
31
|
+
"Topic :: Utilities",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
dependencies = [
|
|
35
|
+
"requests>=2.28",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=7",
|
|
41
|
+
"pytest-cov",
|
|
42
|
+
"responses", # HTTP mocking for tests
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
ecbfx = "ecbfx.cli:main"
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/edvinassvedas-dev/ecbfx"
|
|
50
|
+
Repository = "https://github.com/edvinassvedas-dev/ecbfx"
|
|
51
|
+
Issues = "https://github.com/edvinassvedas-dev/ecbfx/issues"
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.wheel]
|
|
54
|
+
packages = ["src/ecbfx"]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
testpaths = ["tests"]
|
|
58
|
+
addopts = "-v --tb=short"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ecbfx — ECB foreign exchange rate lookup tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .api import (
|
|
6
|
+
ECBError,
|
|
7
|
+
validate_currency,
|
|
8
|
+
fetch_rates,
|
|
9
|
+
fetch_rates_for_pairs,
|
|
10
|
+
fetch_latest,
|
|
11
|
+
fetch_ecb_raw,
|
|
12
|
+
parse_ecb_csv,
|
|
13
|
+
resolve_rate,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from importlib.metadata import version as _v
|
|
18
|
+
__version__ = _v("ecbfx")
|
|
19
|
+
except Exception:
|
|
20
|
+
__version__ = "unknown"
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"ECBError",
|
|
24
|
+
"validate_currency",
|
|
25
|
+
"fetch_rates",
|
|
26
|
+
"fetch_rates_for_pairs",
|
|
27
|
+
"fetch_latest",
|
|
28
|
+
"fetch_ecb_raw",
|
|
29
|
+
"parse_ecb_csv",
|
|
30
|
+
"resolve_rate",
|
|
31
|
+
]
|