hdx-python-country 3.9.1__tar.gz → 3.9.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.
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/PKG-INFO +1 -1
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/documentation/index.md +7 -3
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/requirements.txt +1 -1
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/_version.py +2 -2
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/currency.py +106 -105
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/wfp_exchangerates.py +1 -1
- hdx_python_country-3.9.2/tests/hdx/location/conftest.py +26 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_adminlevel.py +0 -4
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_currency.py +27 -20
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_wfp_exchangerates.py +20 -22
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.github/workflows/publish.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.github/workflows/run-python-tests.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.gitignore +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.pre-commit-config.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/CONTRIBUTING.md +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/LICENSE +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/README.md +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/coveragerc +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/documentation/.readthedocs.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/documentation/mkdocs.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/hatch.toml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/pyproject.toml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/pytest.ini +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/ruff.toml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/Countries & Territories Taxonomy MVP - C&T Taxonomy with HXL Tags.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/__init__.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/adminlevel.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/country.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/int_timestamp.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/wfp_api.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/adminlevel.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/adminlevelparent.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/download-global-pcode-lengths.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/download-global-pcodes-adm-1-2.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/secondary_historic_rates.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/secondary_rates.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_List_1.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_List_2.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_1.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_10.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_2.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_3.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_4.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_5.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_6.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_7.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_8.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_9.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/Countries_UZB_Deleted.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/__init__.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_country.py +0 -0
|
@@ -42,6 +42,8 @@ The code for the library is [here](https://github.com/OCHA-DAP/hdx-python-countr
|
|
|
42
42
|
The library has detailed API documentation which can be found in the menu at the top.
|
|
43
43
|
|
|
44
44
|
## Breaking Changes
|
|
45
|
+
From 3.9.2, must call Currency.setup before using Currency methods.
|
|
46
|
+
|
|
45
47
|
From 3.7.5, removed clean_name function. There is now a function normalise in
|
|
46
48
|
HDX Python Utilities.
|
|
47
49
|
|
|
@@ -191,9 +193,11 @@ AdminLevel objects in a list or lists of p-codes per parent admin level:
|
|
|
191
193
|
|
|
192
194
|
## Currencies
|
|
193
195
|
|
|
194
|
-
Various functions support the conversion of monetary amounts to USD.
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
Various functions support the conversion of monetary amounts to USD. The setup
|
|
197
|
+
method must be called once before using any other methods. Note that the
|
|
198
|
+
returned values are cached to reduce network usage which means that the
|
|
199
|
+
library is unsuited for use where rates are expected to update while the
|
|
200
|
+
program is running:
|
|
197
201
|
|
|
198
202
|
Currency.setup(fallback_historic_to_current=True, fallback_current_to_static=True, log_level=logging.INFO)
|
|
199
203
|
currency = Country.get_currency_from_iso3("usa") # returns "USD"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Currency conversion"""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from copy import
|
|
4
|
+
from copy import deepcopy
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
6
|
from typing import Dict, Optional, Union
|
|
7
7
|
|
|
@@ -33,11 +33,11 @@ class Currency:
|
|
|
33
33
|
_secondary_historic_url = (
|
|
34
34
|
"https://codeforiati.org/imf-exchangerates/imf_exchangerates.csv"
|
|
35
35
|
)
|
|
36
|
-
_cached_current_rates =
|
|
37
|
-
_cached_historic_rates =
|
|
38
|
-
_rates_api =
|
|
39
|
-
_secondary_rates =
|
|
40
|
-
|
|
36
|
+
_cached_current_rates = {}
|
|
37
|
+
_cached_historic_rates = {}
|
|
38
|
+
_rates_api = ""
|
|
39
|
+
_secondary_rates = {}
|
|
40
|
+
_secondary_historic_rates = {}
|
|
41
41
|
_fallback_to_current = False
|
|
42
42
|
_no_historic = False
|
|
43
43
|
_user_agent = "hdx-python-country-rates"
|
|
@@ -52,7 +52,8 @@ class Currency:
|
|
|
52
52
|
retriever: Optional[Retrieve] = None,
|
|
53
53
|
primary_rates_url: str = _primary_rates_url,
|
|
54
54
|
secondary_rates_url: str = _secondary_rates_url,
|
|
55
|
-
secondary_historic_url: str = _secondary_historic_url,
|
|
55
|
+
secondary_historic_url: Optional[str] = _secondary_historic_url,
|
|
56
|
+
secondary_historic_rates: Optional[Dict] = None,
|
|
56
57
|
fallback_historic_to_current: bool = False,
|
|
57
58
|
fallback_current_to_static: bool = False,
|
|
58
59
|
no_historic: bool = False,
|
|
@@ -60,6 +61,7 @@ class Currency:
|
|
|
60
61
|
log_level: int = logging.DEBUG,
|
|
61
62
|
current_rates_cache: Dict = {"USD": 1},
|
|
62
63
|
historic_rates_cache: Dict = {},
|
|
64
|
+
use_secondary_historic: bool = False,
|
|
63
65
|
) -> None:
|
|
64
66
|
"""
|
|
65
67
|
Setup the sources. If you wish to use a static fallback file by setting
|
|
@@ -70,7 +72,8 @@ class Currency:
|
|
|
70
72
|
retriever (Optional[Retrieve]): Retrieve object to use for downloading. Defaults to None (generate a new one).
|
|
71
73
|
primary_rates_url (str): Primary rates url to use. Defaults to Yahoo API.
|
|
72
74
|
secondary_rates_url (str): Current rates url to use. Defaults to currency-api.
|
|
73
|
-
secondary_historic_url (str): Historic rates url to use. Defaults to IMF (via IATI).
|
|
75
|
+
secondary_historic_url (Optional[str]): Historic rates url to use. Defaults to IMF (via IATI).
|
|
76
|
+
secondary_historic_rates (Optional[Dict]): Historic rates to use. Defaults to None.
|
|
74
77
|
fallback_historic_to_current (bool): If historic unavailable, fallback to current. Defaults to False.
|
|
75
78
|
fallback_current_to_static (bool): Use static file as final fallback. Defaults to False.
|
|
76
79
|
no_historic (bool): Do not set up historic rates. Defaults to False.
|
|
@@ -78,16 +81,20 @@ class Currency:
|
|
|
78
81
|
log_level (int): Level at which to log messages. Defaults to logging.DEBUG.
|
|
79
82
|
current_rates_cache (Dict): Pre-populate current rates cache with given values. Defaults to {"USD": 1}.
|
|
80
83
|
historic_rates_cache (Dict): Pre-populate historic rates cache with given values. Defaults to {}.
|
|
84
|
+
use_secondary_historic (bool): Use secondary historic first. Defaults to False.
|
|
81
85
|
|
|
82
86
|
Returns:
|
|
83
87
|
None
|
|
84
88
|
"""
|
|
85
89
|
|
|
86
|
-
cls._cached_current_rates =
|
|
87
|
-
cls._cached_historic_rates =
|
|
90
|
+
cls._cached_current_rates = deepcopy(current_rates_cache)
|
|
91
|
+
cls._cached_historic_rates = deepcopy(historic_rates_cache)
|
|
88
92
|
cls._rates_api = primary_rates_url
|
|
89
|
-
cls._secondary_rates =
|
|
90
|
-
|
|
93
|
+
cls._secondary_rates = {}
|
|
94
|
+
if secondary_historic_rates is not None:
|
|
95
|
+
cls._secondary_historic_rates = secondary_historic_rates
|
|
96
|
+
else:
|
|
97
|
+
cls._secondary_historic_rates = {}
|
|
91
98
|
if retriever is None:
|
|
92
99
|
downloader = Download(user_agent=cls._user_agent)
|
|
93
100
|
temp_dir = get_temp_dir(cls._user_agent)
|
|
@@ -110,30 +117,33 @@ class Currency:
|
|
|
110
117
|
cls._secondary_rates = secondary_rates["usd"]
|
|
111
118
|
except (DownloadError, OSError):
|
|
112
119
|
logger.exception("Error getting secondary current rates!")
|
|
113
|
-
cls._secondary_rates = "FAIL"
|
|
114
120
|
cls._fixed_now = fixed_now
|
|
115
121
|
cls._log_level = log_level
|
|
116
122
|
if no_historic:
|
|
117
|
-
cls._no_historic = True
|
|
118
|
-
if cls._no_historic:
|
|
119
123
|
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
cls._no_historic = no_historic
|
|
125
|
+
if secondary_historic_url:
|
|
126
|
+
try:
|
|
127
|
+
_, iterator = retriever.get_tabular_rows(
|
|
128
|
+
secondary_historic_url,
|
|
129
|
+
dict_form=True,
|
|
130
|
+
filename="historic_rates.csv",
|
|
131
|
+
logstr="secondary historic exchange rates",
|
|
132
|
+
)
|
|
133
|
+
for row in iterator:
|
|
134
|
+
currency = row["Currency"]
|
|
135
|
+
date = get_int_timestamp(parse_date(row["Date"]))
|
|
136
|
+
rate = float(row["Rate"])
|
|
137
|
+
dict_of_dicts_add(
|
|
138
|
+
cls._secondary_historic_rates, currency, date, rate
|
|
139
|
+
)
|
|
140
|
+
except (DownloadError, OSError):
|
|
141
|
+
logger.exception("Error getting secondary historic rates!")
|
|
136
142
|
cls._fallback_to_current = fallback_historic_to_current
|
|
143
|
+
if use_secondary_historic:
|
|
144
|
+
cls._get_historic_rate = cls._get_historic_rate_secondary
|
|
145
|
+
else:
|
|
146
|
+
cls._get_historic_rate = cls._get_historic_rate_primary
|
|
137
147
|
|
|
138
148
|
@classmethod
|
|
139
149
|
def _get_primary_rates_data(
|
|
@@ -149,8 +159,8 @@ class Currency:
|
|
|
149
159
|
Returns:
|
|
150
160
|
Optional[float]: fx rate or None
|
|
151
161
|
"""
|
|
152
|
-
if cls._rates_api
|
|
153
|
-
|
|
162
|
+
if not cls._rates_api:
|
|
163
|
+
return None
|
|
154
164
|
url = cls._rates_api.format(currency=currency, date=str(timestamp))
|
|
155
165
|
if downloader is None:
|
|
156
166
|
downloader = cls._retriever
|
|
@@ -289,10 +299,6 @@ class Currency:
|
|
|
289
299
|
Returns:
|
|
290
300
|
Optional[float]: fx rate or None
|
|
291
301
|
"""
|
|
292
|
-
if cls._secondary_rates is None:
|
|
293
|
-
Currency.setup()
|
|
294
|
-
if cls._secondary_rates == "FAIL":
|
|
295
|
-
return None
|
|
296
302
|
return cls._secondary_rates.get(currency.lower())
|
|
297
303
|
|
|
298
304
|
@classmethod
|
|
@@ -307,8 +313,6 @@ class Currency:
|
|
|
307
313
|
float: fx rate
|
|
308
314
|
"""
|
|
309
315
|
currency = currency.upper()
|
|
310
|
-
if cls._cached_current_rates is None:
|
|
311
|
-
Currency.setup()
|
|
312
316
|
fx_rate = cls._cached_current_rates.get(currency)
|
|
313
317
|
if fx_rate is not None:
|
|
314
318
|
return fx_rate
|
|
@@ -361,39 +365,13 @@ class Currency:
|
|
|
361
365
|
fx_rate = cls.get_current_rate(currency)
|
|
362
366
|
return usdvalue * fx_rate
|
|
363
367
|
|
|
364
|
-
@classmethod
|
|
365
|
-
def _get_interpolated_rate(
|
|
366
|
-
cls,
|
|
367
|
-
timestamp1: int,
|
|
368
|
-
rate1: float,
|
|
369
|
-
timestamp2: int,
|
|
370
|
-
rate2: float,
|
|
371
|
-
desired_timestamp: int,
|
|
372
|
-
) -> float:
|
|
373
|
-
"""
|
|
374
|
-
Return a rate for a desired timestamp based on linearly interpolating between
|
|
375
|
-
two timestamp/rate pairs.
|
|
376
|
-
|
|
377
|
-
Args:
|
|
378
|
-
timestamp1 (int): First timestamp to use for fx conversion
|
|
379
|
-
rate1 (float): Rate at first timestamp
|
|
380
|
-
timestamp2 (int): Second timestamp to use for fx conversion
|
|
381
|
-
rate2 (float): Rate at second timestamp
|
|
382
|
-
desired_timestamp (int): Timestamp at which rate is desired
|
|
383
|
-
|
|
384
|
-
Returns:
|
|
385
|
-
float: Rate at desired timestamp
|
|
386
|
-
"""
|
|
387
|
-
return rate1 + (desired_timestamp - timestamp1) * (
|
|
388
|
-
(rate2 - rate1) / (timestamp2 - timestamp1)
|
|
389
|
-
)
|
|
390
|
-
|
|
391
368
|
@classmethod
|
|
392
369
|
def _get_secondary_historic_rate(
|
|
393
370
|
cls, currency: str, timestamp: int
|
|
394
371
|
) -> Optional[float]:
|
|
395
372
|
"""
|
|
396
|
-
Get the
|
|
373
|
+
Get the historic fx rate for currency on a particular date using
|
|
374
|
+
interpolation if needed.
|
|
397
375
|
|
|
398
376
|
Args:
|
|
399
377
|
currency (str): Currency
|
|
@@ -402,11 +380,7 @@ class Currency:
|
|
|
402
380
|
Returns:
|
|
403
381
|
Optional[float]: fx rate or None
|
|
404
382
|
"""
|
|
405
|
-
|
|
406
|
-
Currency.setup()
|
|
407
|
-
if cls._secondary_historic == "FAIL":
|
|
408
|
-
return None
|
|
409
|
-
currency_data = cls._secondary_historic.get(currency)
|
|
383
|
+
currency_data = cls._secondary_historic_rates.get(currency)
|
|
410
384
|
if currency_data is None:
|
|
411
385
|
return None
|
|
412
386
|
fx_rate = currency_data.get(timestamp)
|
|
@@ -426,14 +400,67 @@ class Currency:
|
|
|
426
400
|
return currency_data[timestamp2]
|
|
427
401
|
if timestamp2 is None:
|
|
428
402
|
return currency_data[timestamp1]
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
currency_data[timestamp1
|
|
432
|
-
timestamp2,
|
|
433
|
-
currency_data[timestamp2],
|
|
434
|
-
timestamp,
|
|
403
|
+
rate1 = currency_data[timestamp1]
|
|
404
|
+
return rate1 + (timestamp - timestamp1) * (
|
|
405
|
+
(currency_data[timestamp2] - rate1) / (timestamp2 - timestamp1)
|
|
435
406
|
)
|
|
436
407
|
|
|
408
|
+
@classmethod
|
|
409
|
+
def _get_historic_rate_primary(cls, currency: str, timestamp: int) -> float:
|
|
410
|
+
currency_data = cls._cached_historic_rates.get(currency)
|
|
411
|
+
if currency_data is not None:
|
|
412
|
+
fx_rate = currency_data.get(timestamp)
|
|
413
|
+
if fx_rate is not None:
|
|
414
|
+
return fx_rate
|
|
415
|
+
fx_rate = cls._get_primary_rate(currency, timestamp)
|
|
416
|
+
if fx_rate is not None:
|
|
417
|
+
dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
|
|
418
|
+
return fx_rate
|
|
419
|
+
fx_rate = cls._get_secondary_historic_rate(currency, timestamp)
|
|
420
|
+
if fx_rate is not None:
|
|
421
|
+
dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
|
|
422
|
+
return fx_rate
|
|
423
|
+
if cls._fallback_to_current:
|
|
424
|
+
fx_rate = cls.get_current_rate(currency)
|
|
425
|
+
if fx_rate:
|
|
426
|
+
logger.debug(
|
|
427
|
+
f"Falling back to current rate for currency {currency} on timestamp {timestamp}!"
|
|
428
|
+
)
|
|
429
|
+
return fx_rate
|
|
430
|
+
raise CurrencyError(
|
|
431
|
+
f"Failed to get rate for currency {currency} on timestamp {timestamp}!"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
@classmethod
|
|
435
|
+
def _get_historic_rate_secondary(cls, currency: str, timestamp: int) -> float:
|
|
436
|
+
currency_data = cls._cached_historic_rates.get(currency)
|
|
437
|
+
if currency_data is not None:
|
|
438
|
+
fx_rate = currency_data.get(timestamp)
|
|
439
|
+
if fx_rate is not None:
|
|
440
|
+
return fx_rate
|
|
441
|
+
fx_rate = cls._get_secondary_historic_rate(currency, timestamp)
|
|
442
|
+
if fx_rate is None:
|
|
443
|
+
fx_rate = cls._get_primary_rate(currency, timestamp)
|
|
444
|
+
if fx_rate is None:
|
|
445
|
+
if cls._fallback_to_current:
|
|
446
|
+
fx_rate = cls.get_current_rate(currency)
|
|
447
|
+
if fx_rate:
|
|
448
|
+
logger.debug(
|
|
449
|
+
f"Falling back to current rate for currency {currency} on timestamp {timestamp}!"
|
|
450
|
+
)
|
|
451
|
+
return fx_rate
|
|
452
|
+
raise CurrencyError(
|
|
453
|
+
f"Failed to get rate for currency {currency} on timestamp {timestamp}!"
|
|
454
|
+
)
|
|
455
|
+
else:
|
|
456
|
+
dict_of_dicts_add(
|
|
457
|
+
cls._cached_historic_rates, currency, timestamp, fx_rate
|
|
458
|
+
)
|
|
459
|
+
return fx_rate
|
|
460
|
+
else:
|
|
461
|
+
dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
|
|
462
|
+
return fx_rate
|
|
463
|
+
|
|
437
464
|
@classmethod
|
|
438
465
|
def get_historic_rate(
|
|
439
466
|
cls, currency: str, date: datetime, ignore_timeinfo: bool = True
|
|
@@ -455,38 +482,12 @@ class Currency:
|
|
|
455
482
|
currency = currency.upper()
|
|
456
483
|
if currency == "USD":
|
|
457
484
|
return 1
|
|
458
|
-
if cls._cached_historic_rates is None:
|
|
459
|
-
Currency.setup()
|
|
460
|
-
currency_data = cls._cached_historic_rates.get(currency)
|
|
461
485
|
if ignore_timeinfo:
|
|
462
486
|
date = date.replace(
|
|
463
487
|
hour=0, minute=0, second=0, microsecond=0, tzinfo=timezone.utc
|
|
464
488
|
)
|
|
465
|
-
else:
|
|
466
|
-
date = date.astimezone(timezone.utc)
|
|
467
489
|
timestamp = get_int_timestamp(date)
|
|
468
|
-
|
|
469
|
-
fx_rate = currency_data.get(timestamp)
|
|
470
|
-
if fx_rate is not None:
|
|
471
|
-
return fx_rate
|
|
472
|
-
fx_rate = cls._get_primary_rate(currency, timestamp)
|
|
473
|
-
if fx_rate is not None:
|
|
474
|
-
dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
|
|
475
|
-
return fx_rate
|
|
476
|
-
fx_rate = cls._get_secondary_historic_rate(currency, timestamp)
|
|
477
|
-
if fx_rate is not None:
|
|
478
|
-
dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
|
|
479
|
-
return fx_rate
|
|
480
|
-
if cls._fallback_to_current:
|
|
481
|
-
fx_rate = cls.get_current_rate(currency)
|
|
482
|
-
if fx_rate:
|
|
483
|
-
logger.debug(
|
|
484
|
-
f"Falling back to current rate for currency {currency} on date {date.isoformat()}!"
|
|
485
|
-
)
|
|
486
|
-
return fx_rate
|
|
487
|
-
raise CurrencyError(
|
|
488
|
-
f"Failed to get rate for currency {currency} on date {date.isoformat()}!"
|
|
489
|
-
)
|
|
490
|
+
return cls._get_historic_rate(currency, timestamp)
|
|
490
491
|
|
|
491
492
|
@classmethod
|
|
492
493
|
def get_historic_value_in_usd(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from os.path import join
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from hdx.location.currency import Currency
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture(scope="session")
|
|
9
|
+
def fixtures_dir():
|
|
10
|
+
return join("tests", "fixtures")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(scope="session")
|
|
14
|
+
def input_dir(fixtures_dir):
|
|
15
|
+
return join(fixtures_dir, "wfp")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture(scope="function")
|
|
19
|
+
def reset_currency():
|
|
20
|
+
Currency._cached_current_rates = {}
|
|
21
|
+
Currency._cached_historic_rates = {}
|
|
22
|
+
Currency._rates_api = ""
|
|
23
|
+
Currency._secondary_rates = {}
|
|
24
|
+
Currency._secondary_historic_rates = {}
|
|
25
|
+
Currency._fallback_to_current = False
|
|
26
|
+
Currency._no_historic = False
|
|
@@ -13,10 +13,6 @@ from hdx.utilities.retriever import Retrieve
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class TestAdminLevel:
|
|
16
|
-
@pytest.fixture(scope="class")
|
|
17
|
-
def fixtures_dir(self):
|
|
18
|
-
return join("tests", "fixtures")
|
|
19
|
-
|
|
20
16
|
@pytest.fixture(scope="function")
|
|
21
17
|
def config(self, fixtures_dir):
|
|
22
18
|
return load_yaml(join(fixtures_dir, "adminlevel.yaml"))
|
|
@@ -15,23 +15,19 @@ from hdx.utilities.useragent import UserAgent
|
|
|
15
15
|
|
|
16
16
|
class TestCurrency:
|
|
17
17
|
@pytest.fixture(scope="class")
|
|
18
|
-
def
|
|
19
|
-
return join(
|
|
18
|
+
def secondary_rates_url(self, fixtures_dir):
|
|
19
|
+
return join(fixtures_dir, "secondary_rates.json")
|
|
20
20
|
|
|
21
21
|
@pytest.fixture(scope="class")
|
|
22
|
-
def
|
|
23
|
-
return join(
|
|
24
|
-
|
|
25
|
-
@pytest.fixture(scope="class")
|
|
26
|
-
def secondary_historic_url(self, fixtures):
|
|
27
|
-
return join(fixtures, "secondary_historic_rates.csv")
|
|
22
|
+
def secondary_historic_url(self, fixtures_dir):
|
|
23
|
+
return join(fixtures_dir, "secondary_historic_rates.csv")
|
|
28
24
|
|
|
29
25
|
@pytest.fixture(scope="class", autouse=True)
|
|
30
|
-
def retrievers(self,
|
|
26
|
+
def retrievers(self, fixtures_dir):
|
|
31
27
|
name = "hdx-python-country-rates"
|
|
32
28
|
UserAgent.set_global(name)
|
|
33
29
|
downloader = Download()
|
|
34
|
-
fallback_dir =
|
|
30
|
+
fallback_dir = fixtures_dir
|
|
35
31
|
temp_dir = get_temp_dir(name)
|
|
36
32
|
retriever = Retrieve(
|
|
37
33
|
downloader,
|
|
@@ -52,7 +48,9 @@ class TestCurrency:
|
|
|
52
48
|
yield retriever, retriever_broken
|
|
53
49
|
UserAgent.clear_global()
|
|
54
50
|
|
|
55
|
-
def test_get_current_value_in_usd(
|
|
51
|
+
def test_get_current_value_in_usd(
|
|
52
|
+
self, reset_currency, retrievers, secondary_rates_url
|
|
53
|
+
):
|
|
56
54
|
Currency.setup(no_historic=True)
|
|
57
55
|
assert Currency.get_current_value_in_usd(10, "usd") == 10
|
|
58
56
|
assert Currency.get_current_value_in_currency(10, "usd") == 10
|
|
@@ -106,6 +104,9 @@ class TestCurrency:
|
|
|
106
104
|
)
|
|
107
105
|
Currency.get_current_value_in_currency(10, "gbp")
|
|
108
106
|
Currency._rates_api = None
|
|
107
|
+
with pytest.raises(CurrencyError):
|
|
108
|
+
Currency.get_current_rate("gbp")
|
|
109
|
+
Currency._rates_api = Currency._primary_rates_url
|
|
109
110
|
assert Currency.get_current_rate("usd") == 1
|
|
110
111
|
rate1gbp = Currency.get_current_rate("gbp")
|
|
111
112
|
assert rate1gbp != 1
|
|
@@ -114,27 +115,25 @@ class TestCurrency:
|
|
|
114
115
|
Currency.setup(
|
|
115
116
|
retriever=retrievers[1],
|
|
116
117
|
primary_rates_url="fail",
|
|
117
|
-
secondary_rates_url="fail",
|
|
118
118
|
fallback_current_to_static=False,
|
|
119
119
|
no_historic=True,
|
|
120
120
|
)
|
|
121
|
-
Currency._secondary_rates = None
|
|
122
121
|
rate2gbp = Currency.get_current_rate("gbp")
|
|
123
122
|
assert rate2gbp != 1
|
|
124
123
|
assert abs(rate1gbp - rate2gbp) / rate1gbp < 0.008
|
|
125
124
|
Currency.setup(
|
|
126
125
|
retriever=retrievers[1],
|
|
127
126
|
primary_rates_url="fail",
|
|
128
|
-
secondary_rates_url="fail",
|
|
129
127
|
fallback_current_to_static=False,
|
|
130
128
|
no_historic=True,
|
|
131
129
|
)
|
|
132
|
-
Currency._secondary_rates = None
|
|
133
130
|
rate2xdr = Currency.get_current_rate("xdr")
|
|
134
131
|
assert rate2xdr != 1
|
|
135
132
|
assert abs(rate1xdr - rate2xdr) / rate1xdr < 0.1
|
|
136
133
|
|
|
137
|
-
def test_get_current_value_in_usd_fixednnow(
|
|
134
|
+
def test_get_current_value_in_usd_fixednnow(
|
|
135
|
+
self, reset_currency, retrievers, secondary_rates_url
|
|
136
|
+
):
|
|
138
137
|
date = parse_date("2020-02-20")
|
|
139
138
|
Currency.setup(
|
|
140
139
|
no_historic=True,
|
|
@@ -148,7 +147,9 @@ class TestCurrency:
|
|
|
148
147
|
# falls back to secondary current rates
|
|
149
148
|
assert Currency.get_current_rate("xdr") == 0.76479065
|
|
150
149
|
|
|
151
|
-
def test_get_historic_value_in_usd(
|
|
150
|
+
def test_get_historic_value_in_usd(
|
|
151
|
+
self, reset_currency, retrievers, secondary_historic_url
|
|
152
|
+
):
|
|
152
153
|
Currency._no_historic = False
|
|
153
154
|
Currency.setup(secondary_historic_url=secondary_historic_url)
|
|
154
155
|
date = parse_date("2020-02-20")
|
|
@@ -229,13 +230,17 @@ class TestCurrency:
|
|
|
229
230
|
fallback_historic_to_current=False,
|
|
230
231
|
)
|
|
231
232
|
Currency.get_historic_value_in_usd(10, "gbp", date)
|
|
232
|
-
Currency.
|
|
233
|
+
Currency.setup(
|
|
234
|
+
retriever=retriever,
|
|
235
|
+
primary_rates_url="fail",
|
|
236
|
+
fallback_historic_to_current=False,
|
|
237
|
+
)
|
|
233
238
|
# Interpolation
|
|
234
239
|
# 0.761817697025102 + (0.776276975624903 - 0.761817697025102) * 20 / 29
|
|
235
240
|
# 0.761817697025102 + (0.776276975624903 - 0.761817697025102) * (1582156800-1580428800) / (1582934400 - 1580428800)
|
|
236
241
|
assert Currency.get_historic_rate("gbp", date) == 0.7717896133008268
|
|
237
242
|
|
|
238
|
-
def test_broken_rates_no_secondary(self, retrievers):
|
|
243
|
+
def test_broken_rates_no_secondary(self, reset_currency, retrievers):
|
|
239
244
|
Currency._no_historic = False
|
|
240
245
|
Currency.setup(secondary_historic_url="fail")
|
|
241
246
|
# Without the checking against high and low returned by Yahoo API, this
|
|
@@ -265,7 +270,9 @@ class TestCurrency:
|
|
|
265
270
|
== 601.632568359375
|
|
266
271
|
)
|
|
267
272
|
|
|
268
|
-
def test_broken_rates_with_secondary(
|
|
273
|
+
def test_broken_rates_with_secondary(
|
|
274
|
+
self, reset_currency, retrievers, secondary_historic_url
|
|
275
|
+
):
|
|
269
276
|
Currency._no_historic = False
|
|
270
277
|
Currency.setup(secondary_historic_url=secondary_historic_url)
|
|
271
278
|
# Without the checking against secondary historic rate, this
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_wfp_exchangerates.py
RENAMED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
from os.path import join
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
1
|
from hdx.location.currency import Currency
|
|
6
2
|
from hdx.location.int_timestamp import get_int_timestamp
|
|
7
3
|
from hdx.location.wfp_api import WFPAPI
|
|
@@ -13,23 +9,7 @@ from hdx.utilities.retriever import Retrieve
|
|
|
13
9
|
|
|
14
10
|
|
|
15
11
|
class TestWFPExchangeRates:
|
|
16
|
-
|
|
17
|
-
def currency(self):
|
|
18
|
-
return "afn"
|
|
19
|
-
|
|
20
|
-
@pytest.fixture(scope="class")
|
|
21
|
-
def date(self):
|
|
22
|
-
return parse_date("2020-02-20")
|
|
23
|
-
|
|
24
|
-
@pytest.fixture(scope="class")
|
|
25
|
-
def fixtures_dir(self):
|
|
26
|
-
return join("tests", "fixtures")
|
|
27
|
-
|
|
28
|
-
@pytest.fixture(scope="class")
|
|
29
|
-
def input_dir(self, fixtures_dir):
|
|
30
|
-
return join(fixtures_dir, "wfp")
|
|
31
|
-
|
|
32
|
-
def test_wfp_exchangerates(self, input_dir, currency, date):
|
|
12
|
+
def test_wfp_exchangerates(self, reset_currency, input_dir):
|
|
33
13
|
with temp_dir(
|
|
34
14
|
"TestWFPExchangeRates",
|
|
35
15
|
delete_on_success=True,
|
|
@@ -44,6 +24,8 @@ class TestWFPExchangeRates:
|
|
|
44
24
|
save=False,
|
|
45
25
|
use_saved=True,
|
|
46
26
|
)
|
|
27
|
+
currency = "afn"
|
|
28
|
+
date = parse_date("2020-02-20")
|
|
47
29
|
wfp_api = WFPAPI(downloader, retriever)
|
|
48
30
|
wfp_api.update_retry_params(attempts=5, wait=5)
|
|
49
31
|
wfp_fx = WFPExchangeRates(wfp_api)
|
|
@@ -55,11 +37,27 @@ class TestWFPExchangeRates:
|
|
|
55
37
|
currencies = wfp_fx.get_currencies()
|
|
56
38
|
assert len(currencies) == 127
|
|
57
39
|
|
|
40
|
+
Currency.setup()
|
|
58
41
|
assert Currency.get_historic_rate(currency, date) == 76.80000305175781
|
|
59
42
|
timestamp = get_int_timestamp(date)
|
|
60
43
|
historic_rates = wfp_fx.get_currency_historic_rates(currency)
|
|
44
|
+
keys = list(historic_rates.keys())
|
|
45
|
+
sorted_keys = sorted(keys)
|
|
46
|
+
assert keys == sorted_keys
|
|
61
47
|
assert historic_rates[timestamp] == 77.01
|
|
62
48
|
|
|
63
49
|
all_historic_rates = wfp_fx.get_historic_rates([currency])
|
|
64
|
-
Currency.setup(
|
|
50
|
+
Currency.setup(
|
|
51
|
+
historic_rates_cache=all_historic_rates,
|
|
52
|
+
secondary_historic_rates=all_historic_rates,
|
|
53
|
+
use_secondary_historic=True,
|
|
54
|
+
)
|
|
55
|
+
assert Currency.get_historic_rate(currency, date) == 77.01
|
|
56
|
+
date = parse_date("2020-02-21")
|
|
65
57
|
assert Currency.get_historic_rate(currency, date) == 77.01
|
|
58
|
+
date = parse_date("2020-02-20 12:00:00")
|
|
59
|
+
assert Currency.get_historic_rate(currency, date) == 77.01
|
|
60
|
+
assert (
|
|
61
|
+
Currency.get_historic_rate(currency, date, ignore_timeinfo=False)
|
|
62
|
+
== 77.01
|
|
63
|
+
)
|
|
File without changes
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.github/workflows/run-python-tests.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/secondary_historic_rates.csv
RENAMED
|
File without changes
|
|
File without changes
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_List_1.json
RENAMED
|
File without changes
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_List_2.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/Countries_UZB_Deleted.csv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|