ecb-rate 0.5.2__py3-none-any.whl

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/__init__.py ADDED
File without changes
ecb_rate/cli.py ADDED
@@ -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())
ecb_rate/client.py ADDED
@@ -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()
ecb_rate/models.py ADDED
@@ -0,0 +1,55 @@
1
+ """
2
+ Shared models and exceptions for ecb_rate.
3
+ """
4
+
5
+ from datetime import date
6
+ from decimal import Decimal
7
+
8
+ from pydantic import BaseModel, field_validator
9
+
10
+ from ecb_rate.custom_types import CurrencyType
11
+
12
+
13
+ class EcbRateError(Exception):
14
+ """Base exception for the application."""
15
+
16
+
17
+ class CliInputError(EcbRateError):
18
+ """Raised when CLI input is invalid."""
19
+
20
+
21
+ class EcbApiError(EcbRateError):
22
+ """Raised when the ECB API request or response is invalid."""
23
+
24
+
25
+ class QueryParams(BaseModel):
26
+ """
27
+ Query parameters for the currency conversion request.
28
+
29
+ EUR is always the base currency.
30
+ """
31
+
32
+ target_currency: CurrencyType
33
+ specific_date: date
34
+
35
+ @field_validator("target_currency", mode="before")
36
+ @classmethod
37
+ def normalize_currency(cls, value: object) -> object:
38
+ """
39
+ Normalize currency input to lowercase before enum validation.
40
+ """
41
+ if isinstance(value, str) and not isinstance(value, CurrencyType):
42
+ return value.lower()
43
+
44
+ return value
45
+
46
+
47
+ class RatePoint(BaseModel):
48
+ """
49
+ A single exchange-rate result for a specific date.
50
+ """
51
+
52
+ date: date
53
+ rate: Decimal
54
+ target_currency: CurrencyType
55
+ base_currency: CurrencyType = CurrencyType.EUR
ecb_rate/service.py ADDED
@@ -0,0 +1,89 @@
1
+ """
2
+ Business logic for building EUR exchange rates from ECB JSON series.
3
+ """
4
+
5
+ from datetime import date
6
+ from decimal import Decimal
7
+ from typing import Any
8
+
9
+ from ecb_rate.client import ECBJsonClient
10
+ from ecb_rate.custom_types import CurrencyType
11
+ from ecb_rate.models import EcbApiError, QueryParams, RatePoint
12
+
13
+
14
+ class EcbJsonParser:
15
+ """
16
+ Parser for ECB JSON responses.
17
+ """
18
+
19
+ def extract_ecb_rate(
20
+ self,
21
+ payload: dict[str, Any],
22
+ ) -> Decimal:
23
+ """
24
+ Extract the currency rate from ECB JSON payload.
25
+ """
26
+
27
+ try:
28
+ result = list(payload["dataSets"][0]["series"].values())[0]["observations"][
29
+ "0"
30
+ ][0]
31
+
32
+ if result is None:
33
+ raise EcbApiError("No exchange rate found.")
34
+ except (KeyError, IndexError, TypeError) as exc:
35
+ raise EcbApiError("Unexpected ECB JSON structure.") from exc
36
+
37
+ return Decimal(str(result))
38
+
39
+
40
+ class ExchangeRateService:
41
+ """
42
+ High-level service that fetches ECB series and computes:
43
+
44
+ 1 EUR = X target_currency
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ client: ECBJsonClient,
50
+ parser: EcbJsonParser,
51
+ ) -> None:
52
+ self._client = client
53
+ self._parser = parser
54
+
55
+ async def get_rate(self, query: QueryParams) -> RatePoint:
56
+ """
57
+ Return the EUR -> target currency rate for the requested date.
58
+ """
59
+
60
+ if query.target_currency == CurrencyType.EUR:
61
+ return RatePoint(
62
+ target_currency=query.target_currency,
63
+ date=query.specific_date,
64
+ rate=Decimal("1"),
65
+ )
66
+
67
+ rate = await self._fetch_eur_to_currency(
68
+ currency=query.target_currency,
69
+ specific_date=query.specific_date,
70
+ )
71
+
72
+ return RatePoint(
73
+ target_currency=query.target_currency,
74
+ date=query.specific_date,
75
+ rate=rate,
76
+ )
77
+
78
+ async def _fetch_eur_to_currency(
79
+ self,
80
+ currency: CurrencyType,
81
+ specific_date: date,
82
+ ) -> Decimal:
83
+ payload = await self._client.fetch(
84
+ currency=currency,
85
+ specific_date=specific_date,
86
+ )
87
+ return self._parser.extract_ecb_rate(
88
+ payload=payload,
89
+ )
@@ -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,12 @@
1
+ ecb_rate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ ecb_rate/cli.py,sha256=1Ot79OjscUWuC-_u9Uu3ZYZe0oHQ68HkSPNbdhOJEsg,4148
3
+ ecb_rate/client.py,sha256=HZxy-OZjnLKfADKeNlOsOW21sQieErZNk2zfxWMZl1g,2888
4
+ ecb_rate/custom_types.py,sha256=Jrj73KOhaqDMUUPF6Q1O78pjtZiqwNPdYcz59bsHdGo,1991
5
+ ecb_rate/models.py,sha256=1p4cH3G3NNBqQfcPYviww0WWJy6WkUvVy_cuGjeJU9Y,1297
6
+ ecb_rate/service.py,sha256=Rg98iz3v6RpuNlAcyavSmjuthmtyDyQWoBvtFL0lLII,2390
7
+ ecb_rate-0.5.2.dist-info/licenses/LICENSE,sha256=ZYPOZtJBYetn0L8ZRA4enYpb1TPQfe1b8nJcqhAPU5I,1086
8
+ ecb_rate-0.5.2.dist-info/METADATA,sha256=hXQJL4OvelGlyIMb7MGAOQCgMFD6tenQ27QAzHxkntQ,4117
9
+ ecb_rate-0.5.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ ecb_rate-0.5.2.dist-info/entry_points.txt,sha256=gcVdhCrILbpRPW-mLQHaLBUQ_uV3e5p-Z4ZF5ki444Y,47
11
+ ecb_rate-0.5.2.dist-info/top_level.txt,sha256=VcYA2ySxOquKBOdsla5uzQNYZ9HIRQGhoYbRJw2zPF0,9
12
+ ecb_rate-0.5.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ecb_rate = ecb_rate.cli:main
@@ -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 @@
1
+ ecb_rate