ecbfx 0.2.0__tar.gz → 0.3.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.
@@ -0,0 +1,76 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ test:
11
+ # One job per advertised Python. The classifiers in pyproject.toml claim
12
+ # 3.9-3.14; until this matrix went green the claim was never checked, and
13
+ # date.fromisoformat had already been shown to behave differently across it.
14
+ # Keep the matrix and the classifier list in sync — a version in one and not
15
+ # the other is the bug this job exists to prevent.
16
+ name: test (py${{ matrix.python-version }})
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ # Report every failing version, not just the first: knowing whether 3.9
20
+ # alone breaks or the whole lower half does is the point of the matrix.
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+
31
+ - name: Install
32
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
33
+
34
+ - name: Test
35
+ run: python -m pytest
36
+
37
+ lint:
38
+ # Ruff's result does not vary by interpreter, so it runs once rather than
39
+ # six times. The pinned version is the linter's runtime, not the package's.
40
+ name: lint
41
+ runs-on: ubuntu-latest
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.13"
48
+
49
+ - name: Install
50
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
51
+
52
+ - name: Ruff
53
+ run: python -m ruff check src/ tests/
54
+
55
+ build:
56
+ # Catches packaging breakage before a tag rather than after: the wheel must
57
+ # install and the console script must run in an environment that never saw
58
+ # the source tree.
59
+ name: build + smoke-test artifacts
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+
64
+ - uses: actions/setup-python@v5
65
+ with:
66
+ python-version: "3.13"
67
+
68
+ - name: Build
69
+ run: python -m pip install --upgrade pip build && python -m build
70
+
71
+ - name: Install the wheel into a clean environment
72
+ run: |
73
+ python -m venv /tmp/smoke
74
+ /tmp/smoke/bin/pip install dist/*.whl
75
+ /tmp/smoke/bin/python -c "import ecbfx; print(ecbfx.__version__)"
76
+ /tmp/smoke/bin/ecbfx --version
@@ -9,3 +9,8 @@ htmlcov/
9
9
  .mypy_cache/
10
10
  .pytest_cache/
11
11
  *.pyc
12
+ CLAUDE.md
13
+ audit.md
14
+ TODO.md
15
+ .ruff_cache
16
+ github/
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 — 2026-08-01
4
+ - Removed `_validate_currency`, the deprecated alias for `validate_currency`
5
+ - Added `--timeout N`, and a `timeout` parameter on the three fetch functions
6
+ - Added `--decimals` as an alias for `--decimal`
7
+ - Added `py.typed` — annotations are now visible to type checkers
8
+ - Added Python 3.14 classifier; CI runs the suite on 3.9–3.14
9
+ - Added `User-Agent: ecbfx/<version>` on outgoing requests
10
+ - `--pairs` accepts quoted fields — spreadsheet exports work as-is
11
+ - Currency codes must now be exactly three letters (`US`, `USDD` were accepted)
12
+ - Dates must now be `YYYY-MM-DD` — `20250115` and ISO week dates parsed only on 3.11+
13
+ - `fetch_rates()` collapses duplicate currencies, matching `fetch_rates_for_pairs()`
14
+ - Library installs a `logging.NullHandler`; skipped non-positive rates are logged
15
+ - Fixed: CLI was unusable on Python 3.9 — argparse rejected the `nargs="*"` positional
16
+ - Fixed: `--direct` lost precision above ratio 10 — KRW gave `0.0007`, not `0.0006656`
17
+ - Fixed: table output misaligned in both tty and non-tty modes
18
+ - Fixed: mistyped dates reported as invalid currency codes, exit 1 instead of 2
19
+ - Fixed: `USD 2025-01-15 --latest` exited 1 — the unreachable `DATE` positional is gone
20
+ - Fixed: `--pairs` with a malformed `--from`/`--to` crashed with a traceback
21
+ - Fixed: `fetch_latest()` rejected padded codes (`" usd "`)
22
+ - Fixed: `fetch_rates()` with `start` after `end` fetched, then returned `[]`
23
+ - Fixed: HTML error pages from the ECB are retried instead of failing outright
24
+ - Fixed: a UTF-8 BOM could be reported as a bad payload
25
+ - Fixed: HTTP session is closed on every exit path (`ResourceWarning` under `-W error`)
26
+ - Fixed: gap-fill error says "calendar days"; `resolve_rate()` docstring matches the code
27
+
28
+ Minor bump, not a patch: currency and date validation both narrowed, duplicate
29
+ currencies collapse, an inverted range raises instead of returning `[]`, and
30
+ direct-mode output changes for rates above 10 (JPY, HUF, KRW). Rates below 10 —
31
+ USD, GBP, CHF and the rest — are byte-identical to 0.2.0.
32
+
33
+ ## 0.2.0 — 2026-05-01
34
+ - Added `fetch_rates_for_pairs()` — sparse transaction date support
35
+ - Added `fetch_latest()` — most recent available rate
36
+ - Added `validate_currency()` — public input validation
37
+ - Added `--pairs`, `--latest`, `--quiet`, `--decimal`, `--no-gap-fill` CLI flags
38
+ - LOCF gap-filling — always uses prior trading day, never look-ahead
39
+ - HTTP session reuse, exponential backoff with jitter
40
+ - Fixed: `re.fullmatch` for currency validation (trailing newline bypass)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecbfx
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: ECB foreign exchange rate lookup — CLI and Python API with gap-fill, strict mode, and scripting support
5
5
  Project-URL: Homepage, https://github.com/edvinassvedas-dev/ecbfx
6
6
  Project-URL: Repository, https://github.com/edvinassvedas-dev/ecbfx
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
23
24
  Classifier: Topic :: Office/Business :: Financial
24
25
  Classifier: Topic :: Utilities
25
26
  Requires-Python: >=3.9
@@ -28,6 +29,7 @@ Provides-Extra: dev
28
29
  Requires-Dist: pytest-cov; extra == 'dev'
29
30
  Requires-Dist: pytest>=7; extra == 'dev'
30
31
  Requires-Dist: responses; extra == 'dev'
32
+ Requires-Dist: ruff; extra == 'dev'
31
33
  Description-Content-Type: text/markdown
32
34
 
33
35
  # ecbfx
@@ -47,12 +49,11 @@ rates directly from the [ECB SDMX API](https://data-api.ecb.europa.eu).
47
49
  pip install ecbfx
48
50
  ```
49
51
 
50
- **For development** (includes test dependencies):
52
+ Working on `ecbfx`:
51
53
 
52
54
  ```bash
53
55
  git clone https://github.com/edvinassvedas-dev/ecbfx.git
54
- cd ecbfx
55
- pip install -e ".[dev]"
56
+ cd ecbfx && pip install -e ".[dev]"
56
57
  pytest
57
58
  ```
58
59
 
@@ -64,10 +65,8 @@ pytest
64
65
  # If today's rate isn't available yet, use --latest instead.
65
66
  ecbfx USD
66
67
 
67
- # Specific date
68
+ # Specific date, and multiple currencies at once
68
69
  ecbfx USD 2025-01-15
69
-
70
- # Multiple currencies, specific date
71
70
  ecbfx USD GBP CHF 2025-01-15
72
71
 
73
72
  # Date range
@@ -80,27 +79,14 @@ ecbfx USD 2025-01-15 --direct
80
79
  ecbfx USD --latest
81
80
 
82
81
  # Single value only — ideal for shell scripting
83
- ecbfx USD --latest --quiet
84
82
  RATE=$(ecbfx USD --quiet)
85
83
 
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
84
  # CSV output (pipe-friendly)
93
- ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv
94
85
  ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv > rates.csv
95
86
 
96
- # Read pairs from a file — one HTTP call per currency
87
+ # Read (date, currency) pairs from a file or stdin — one HTTP call per currency
97
88
  ecbfx --pairs transactions.csv --direct --csv
98
-
99
- # Read pairs from stdin
100
89
  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
90
  ```
105
91
 
106
92
  ### Convention
@@ -118,13 +104,17 @@ direction explicit for downstream pipelines.
118
104
 
119
105
  | Flag | Default | Description |
120
106
  |---|---|---|
107
+ | `--from YYYY-MM-DD` | — | Start of date range (inclusive). Also filters `--pairs` input |
108
+ | `--to YYYY-MM-DD` | — | End of date range (inclusive). Requires `--from` in range mode |
121
109
  | `--direct` | off | EUR per 1 foreign unit instead of ECB native |
122
110
  | `--latest` | off | Most recent available rate, regardless of date |
123
111
  | `--quiet` / `-q` | off | Print rate value(s) only — ideal for scripting |
124
- | `--decimal N` | 4 | Decimal places in output rate |
112
+ | `--decimal N` / `--decimals N` | 4 | Output precision. Decimal places normally; with `--direct` a floor on *significant* digits, so high-ratio currencies (JPY, HUF, KRW) keep full precision instead of collapsing to `0.0007` |
113
+ | `--timeout N` | 30 | HTTP timeout in seconds, per attempt (3 attempts with backoff) |
125
114
  | `--no-gap-fill` | off | Raise an error on weekends/holidays instead of substituting the nearest rate |
126
115
  | `--csv` | off | CSV output instead of formatted table |
127
- | `--pairs FILE\|-` | — | Read `date,currency` pairs from a file or stdin |
116
+ | `--pairs FILE\|-` | — | Read `date,currency` pairs from a file or stdin. Comma, tab or space separated; surrounding quotes are tolerated, so spreadsheet exports work as-is |
117
+ | `--version` | — | Print the installed version and exit |
128
118
 
129
119
  ### Weekend and holiday gap-filling
130
120
 
@@ -133,11 +123,14 @@ uses the most recent prior trading day's rate (Last Observation Carried Forward)
133
123
  when a requested date falls on a weekend or public holiday — including the first
134
124
  date in a range that starts on a holiday such as January 1st.
135
125
 
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
- ---
126
+ A future date is never substituted, so a rate is never influenced by information
127
+ that did not exist on the date requested. If no trading day exists within 5
128
+ calendar days before the target — the widest real gap in the ECB calendar — the
129
+ lookup fails rather than reaching further back for a staler rate.
140
130
 
131
+ Use `--no-gap-fill` to disable substitution entirely and receive an explicit
132
+ error instead — useful in audit workflows where a substituted rate is not
133
+ acceptable.
141
134
 
142
135
  ### Exit codes
143
136
 
@@ -152,6 +145,8 @@ Useful for scripting:
152
145
  ecbfx USD --quiet || echo "fetch failed, exit $?"
153
146
  ```
154
147
 
148
+ ---
149
+
155
150
  ## Python API
156
151
 
157
152
  ```python
@@ -172,22 +167,28 @@ pairs = [
172
167
  (date(2025, 1, 15), "USD"),
173
168
  (date(2025, 1, 20), "GBP"),
174
169
  (date(2025, 2, 3), "USD"),
175
- (date(2025, 2, 3), "CHF"),
176
170
  ]
177
171
  rows = fetch_rates_for_pairs(pairs, direct=True)
178
172
 
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
173
+ # Strict mode — raises ECBError on weekends/holidays instead of substituting
183
174
  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
175
 
186
176
  for r in rows:
187
177
  print(r["date"], r["currency"], r["convention"], r["rate"])
188
178
  ```
189
179
 
190
- Each row is a dict: `{date, currency, rate, convention}`.
180
+ Both fetch functions accept `direct`, `decimals`, `gap_fill`, `timeout` and an
181
+ optional `session` for connection reuse.
182
+
183
+ ### Behaviour worth knowing
184
+
185
+ - **`fetch_rates` is all-or-nothing across currencies.** A malformed code raises
186
+ before any request. A well-formed code the ECB will not serve raises partway
187
+ through: rows already fetched are discarded and later currencies are never
188
+ requested. Call once per currency if you need per-currency isolation.
189
+ - **Duplicate currencies collapse.** `["USD", "usd"]` is one HTTP call and one
190
+ row per date.
191
+ - **Output order differs between the two functions** — see the table below.
191
192
 
192
193
  ### Input validation
193
194
 
@@ -195,13 +196,14 @@ Each row is a dict: `{date, currency, rate, convention}`.
195
196
  from ecbfx import validate_currency, ECBError
196
197
 
197
198
  # Normalises and validates a currency code — raises ECBError if invalid
198
- print(validate_currency("usd")) # → "USD"
199
- print(validate_currency(" GBP ")) # → "GBP"
199
+ print(validate_currency("usd")) # → "USD"
200
+ print(validate_currency(" GBP ")) # → "GBP"
200
201
 
201
202
  try:
202
203
  validate_currency("US$")
203
204
  except ECBError as e:
204
- print(e) # Invalid currency code 'US$'. Expected 2–4 ASCII letters.
205
+ print(e)
206
+ # Invalid currency code 'US$'. Expected a 3-letter ISO 4217 code, e.g. USD, GBP, CHF.
205
207
  ```
206
208
 
207
209
  Use `ECBError` in `try/except` blocks when calling any `ecbfx` function
@@ -213,11 +215,15 @@ to handle API failures, network errors, or invalid inputs cleanly.
213
215
  |---|---|---|
214
216
  | Input | currency list + date range | list of `(date, currency)` tuples |
215
217
  | Returns | every calendar day in range | exactly the requested dates |
218
+ | Order | sorted by `(date, currency)` | matches input order, repeats included |
216
219
  | Best for | daily pipelines, backfill | transaction enrichment, broker CSVs |
217
- | HTTP calls | one per currency | one per currency (full span, regardless of gaps) |
218
220
 
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.
221
+ Both make one HTTP call per currency.
220
222
 
223
+ > **Note:** `fetch_rates_for_pairs` fetches the full span from the earliest to
224
+ > the latest date per currency in that single call. For very sparse data (e.g.
225
+ > two transactions 10 years apart) this pulls the entire intervening range; a
226
+ > warning is logged when the span exceeds one year.
221
227
 
222
228
  ---
223
229
 
@@ -15,12 +15,11 @@ rates directly from the [ECB SDMX API](https://data-api.ecb.europa.eu).
15
15
  pip install ecbfx
16
16
  ```
17
17
 
18
- **For development** (includes test dependencies):
18
+ Working on `ecbfx`:
19
19
 
20
20
  ```bash
21
21
  git clone https://github.com/edvinassvedas-dev/ecbfx.git
22
- cd ecbfx
23
- pip install -e ".[dev]"
22
+ cd ecbfx && pip install -e ".[dev]"
24
23
  pytest
25
24
  ```
26
25
 
@@ -32,10 +31,8 @@ pytest
32
31
  # If today's rate isn't available yet, use --latest instead.
33
32
  ecbfx USD
34
33
 
35
- # Specific date
34
+ # Specific date, and multiple currencies at once
36
35
  ecbfx USD 2025-01-15
37
-
38
- # Multiple currencies, specific date
39
36
  ecbfx USD GBP CHF 2025-01-15
40
37
 
41
38
  # Date range
@@ -48,27 +45,14 @@ ecbfx USD 2025-01-15 --direct
48
45
  ecbfx USD --latest
49
46
 
50
47
  # Single value only — ideal for shell scripting
51
- ecbfx USD --latest --quiet
52
48
  RATE=$(ecbfx USD --quiet)
53
49
 
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
50
  # CSV output (pipe-friendly)
61
- ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv
62
51
  ecbfx USD --from 2025-01-01 --to 2025-01-31 --csv > rates.csv
63
52
 
64
- # Read pairs from a file — one HTTP call per currency
53
+ # Read (date, currency) pairs from a file or stdin — one HTTP call per currency
65
54
  ecbfx --pairs transactions.csv --direct --csv
66
-
67
- # Read pairs from stdin
68
55
  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
56
  ```
73
57
 
74
58
  ### Convention
@@ -86,13 +70,17 @@ direction explicit for downstream pipelines.
86
70
 
87
71
  | Flag | Default | Description |
88
72
  |---|---|---|
73
+ | `--from YYYY-MM-DD` | — | Start of date range (inclusive). Also filters `--pairs` input |
74
+ | `--to YYYY-MM-DD` | — | End of date range (inclusive). Requires `--from` in range mode |
89
75
  | `--direct` | off | EUR per 1 foreign unit instead of ECB native |
90
76
  | `--latest` | off | Most recent available rate, regardless of date |
91
77
  | `--quiet` / `-q` | off | Print rate value(s) only — ideal for scripting |
92
- | `--decimal N` | 4 | Decimal places in output rate |
78
+ | `--decimal N` / `--decimals N` | 4 | Output precision. Decimal places normally; with `--direct` a floor on *significant* digits, so high-ratio currencies (JPY, HUF, KRW) keep full precision instead of collapsing to `0.0007` |
79
+ | `--timeout N` | 30 | HTTP timeout in seconds, per attempt (3 attempts with backoff) |
93
80
  | `--no-gap-fill` | off | Raise an error on weekends/holidays instead of substituting the nearest rate |
94
81
  | `--csv` | off | CSV output instead of formatted table |
95
- | `--pairs FILE\|-` | — | Read `date,currency` pairs from a file or stdin |
82
+ | `--pairs FILE\|-` | — | Read `date,currency` pairs from a file or stdin. Comma, tab or space separated; surrounding quotes are tolerated, so spreadsheet exports work as-is |
83
+ | `--version` | — | Print the installed version and exit |
96
84
 
97
85
  ### Weekend and holiday gap-filling
98
86
 
@@ -101,11 +89,14 @@ uses the most recent prior trading day's rate (Last Observation Carried Forward)
101
89
  when a requested date falls on a weekend or public holiday — including the first
102
90
  date in a range that starts on a holiday such as January 1st.
103
91
 
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
- ---
92
+ A future date is never substituted, so a rate is never influenced by information
93
+ that did not exist on the date requested. If no trading day exists within 5
94
+ calendar days before the target — the widest real gap in the ECB calendar — the
95
+ lookup fails rather than reaching further back for a staler rate.
108
96
 
97
+ Use `--no-gap-fill` to disable substitution entirely and receive an explicit
98
+ error instead — useful in audit workflows where a substituted rate is not
99
+ acceptable.
109
100
 
110
101
  ### Exit codes
111
102
 
@@ -120,6 +111,8 @@ Useful for scripting:
120
111
  ecbfx USD --quiet || echo "fetch failed, exit $?"
121
112
  ```
122
113
 
114
+ ---
115
+
123
116
  ## Python API
124
117
 
125
118
  ```python
@@ -140,22 +133,28 @@ pairs = [
140
133
  (date(2025, 1, 15), "USD"),
141
134
  (date(2025, 1, 20), "GBP"),
142
135
  (date(2025, 2, 3), "USD"),
143
- (date(2025, 2, 3), "CHF"),
144
136
  ]
145
137
  rows = fetch_rates_for_pairs(pairs, direct=True)
146
138
 
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
139
+ # Strict mode — raises ECBError on weekends/holidays instead of substituting
151
140
  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
141
 
154
142
  for r in rows:
155
143
  print(r["date"], r["currency"], r["convention"], r["rate"])
156
144
  ```
157
145
 
158
- Each row is a dict: `{date, currency, rate, convention}`.
146
+ Both fetch functions accept `direct`, `decimals`, `gap_fill`, `timeout` and an
147
+ optional `session` for connection reuse.
148
+
149
+ ### Behaviour worth knowing
150
+
151
+ - **`fetch_rates` is all-or-nothing across currencies.** A malformed code raises
152
+ before any request. A well-formed code the ECB will not serve raises partway
153
+ through: rows already fetched are discarded and later currencies are never
154
+ requested. Call once per currency if you need per-currency isolation.
155
+ - **Duplicate currencies collapse.** `["USD", "usd"]` is one HTTP call and one
156
+ row per date.
157
+ - **Output order differs between the two functions** — see the table below.
159
158
 
160
159
  ### Input validation
161
160
 
@@ -163,13 +162,14 @@ Each row is a dict: `{date, currency, rate, convention}`.
163
162
  from ecbfx import validate_currency, ECBError
164
163
 
165
164
  # Normalises and validates a currency code — raises ECBError if invalid
166
- print(validate_currency("usd")) # → "USD"
167
- print(validate_currency(" GBP ")) # → "GBP"
165
+ print(validate_currency("usd")) # → "USD"
166
+ print(validate_currency(" GBP ")) # → "GBP"
168
167
 
169
168
  try:
170
169
  validate_currency("US$")
171
170
  except ECBError as e:
172
- print(e) # Invalid currency code 'US$'. Expected 2–4 ASCII letters.
171
+ print(e)
172
+ # Invalid currency code 'US$'. Expected a 3-letter ISO 4217 code, e.g. USD, GBP, CHF.
173
173
  ```
174
174
 
175
175
  Use `ECBError` in `try/except` blocks when calling any `ecbfx` function
@@ -181,11 +181,15 @@ to handle API failures, network errors, or invalid inputs cleanly.
181
181
  |---|---|---|
182
182
  | Input | currency list + date range | list of `(date, currency)` tuples |
183
183
  | Returns | every calendar day in range | exactly the requested dates |
184
+ | Order | sorted by `(date, currency)` | matches input order, repeats included |
184
185
  | Best for | daily pipelines, backfill | transaction enrichment, broker CSVs |
185
- | HTTP calls | one per currency | one per currency (full span, regardless of gaps) |
186
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.
187
+ Both make one HTTP call per currency.
188
188
 
189
+ > **Note:** `fetch_rates_for_pairs` fetches the full span from the earliest to
190
+ > the latest date per currency in that single call. For very sparse data (e.g.
191
+ > two transactions 10 years apart) this pulls the entire intervening range; a
192
+ > warning is logged when the span exceeds one year.
189
193
 
190
194
  ---
191
195
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ecbfx"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "ECB foreign exchange rate lookup — CLI and Python API with gap-fill, strict mode, and scripting support"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -27,6 +27,7 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3.11",
28
28
  "Programming Language :: Python :: 3.12",
29
29
  "Programming Language :: Python :: 3.13",
30
+ "Programming Language :: Python :: 3.14",
30
31
  "Topic :: Office/Business :: Financial",
31
32
  "Topic :: Utilities",
32
33
  ]
@@ -40,6 +41,7 @@ dev = [
40
41
  "pytest>=7",
41
42
  "pytest-cov",
42
43
  "responses", # HTTP mocking for tests
44
+ "ruff", # linter — see [tool.ruff] below
43
45
  ]
44
46
 
45
47
  [project.scripts]
@@ -53,6 +55,22 @@ Issues = "https://github.com/edvinassvedas-dev/ecbfx/issues"
53
55
  [tool.hatch.build.targets.wheel]
54
56
  packages = ["src/ecbfx"]
55
57
 
58
+
56
59
  [tool.pytest.ini_options]
57
60
  testpaths = ["tests"]
58
61
  addopts = "-v --tb=short"
62
+
63
+ [tool.ruff]
64
+ # 88 is Black's default; this code is written to roughly 95, so the default
65
+ # would report ~9 pure-formatting hits that say nothing about correctness.
66
+ line-length = 100
67
+
68
+ [tool.ruff.lint]
69
+ # Bug-shaped rules only. The `UP`/`I` families were deliberately left out —
70
+ # they would flag ~37 "use `list` instead of `List`" style rewrites, and
71
+ # requires-python is >=3.9 where the typing imports are still needed.
72
+ select = ["E", "F", "B"]
73
+ ignore = [
74
+ "E731", # the DIM/BOLD/CYAN/... colour helpers read better as an aligned
75
+ # block of lambdas than as six separate defs
76
+ ]
@@ -13,11 +13,9 @@ from .api import (
13
13
  resolve_rate,
14
14
  )
15
15
 
16
- try:
17
- from importlib.metadata import version as _v
18
- __version__ = _v("ecbfx")
19
- except Exception:
20
- __version__ = "unknown"
16
+ from .api import package_version
17
+
18
+ __version__ = package_version()
21
19
 
22
20
  __all__ = [
23
21
  "ECBError",