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.
Files changed (51) hide show
  1. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/PKG-INFO +1 -1
  2. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/documentation/index.md +7 -3
  3. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/requirements.txt +1 -1
  4. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/_version.py +2 -2
  5. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/currency.py +106 -105
  6. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/wfp_exchangerates.py +1 -1
  7. hdx_python_country-3.9.2/tests/hdx/location/conftest.py +26 -0
  8. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_adminlevel.py +0 -4
  9. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_currency.py +27 -20
  10. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_wfp_exchangerates.py +20 -22
  11. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.github/workflows/publish.yaml +0 -0
  12. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.github/workflows/run-python-tests.yaml +0 -0
  13. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.gitignore +0 -0
  14. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/.pre-commit-config.yaml +0 -0
  15. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/CONTRIBUTING.md +0 -0
  16. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/LICENSE +0 -0
  17. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/README.md +0 -0
  18. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/coveragerc +0 -0
  19. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/documentation/.readthedocs.yaml +0 -0
  20. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/documentation/mkdocs.yaml +0 -0
  21. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/hatch.toml +0 -0
  22. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/pyproject.toml +0 -0
  23. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/pytest.ini +0 -0
  24. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/ruff.toml +0 -0
  25. {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
  26. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/__init__.py +0 -0
  27. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/adminlevel.py +0 -0
  28. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/country.py +0 -0
  29. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/int_timestamp.py +0 -0
  30. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/src/hdx/location/wfp_api.py +0 -0
  31. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/adminlevel.yaml +0 -0
  32. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/adminlevelparent.yaml +0 -0
  33. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/download-global-pcode-lengths.csv +0 -0
  34. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/download-global-pcodes-adm-1-2.csv +0 -0
  35. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/secondary_historic_rates.csv +0 -0
  36. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/secondary_rates.json +0 -0
  37. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_List_1.json +0 -0
  38. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_List_2.json +0 -0
  39. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_1.json +0 -0
  40. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_10.json +0 -0
  41. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_2.json +0 -0
  42. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_3.json +0 -0
  43. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_4.json +0 -0
  44. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_5.json +0 -0
  45. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_6.json +0 -0
  46. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_7.json +0 -0
  47. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_8.json +0 -0
  48. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_9.json +0 -0
  49. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/Countries_UZB_Deleted.csv +0 -0
  50. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/__init__.py +0 -0
  51. {hdx_python_country-3.9.1 → hdx_python_country-3.9.2}/tests/hdx/location/test_country.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hdx-python-country
3
- Version: 3.9.1
3
+ Version: 3.9.2
4
4
  Summary: HDX Python country code and exchange rate (fx) utilities
5
5
  Project-URL: Homepage, https://github.com/OCHA-DAP/hdx-python-country
6
6
  Author: Michael Rans
@@ -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. Note that the
195
- returned values are cached to reduce network usage which means that the library is
196
- unsuited for use where rates are expected to update while the program is running:
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"
@@ -42,7 +42,7 @@ ghp-import==2.1.0
42
42
  # via mkdocs
43
43
  hdx-python-utilities==3.8.5
44
44
  # via hdx-python-country (pyproject.toml)
45
- humanize==4.12.1
45
+ humanize==4.12.2
46
46
  # via frictionless
47
47
  identify==2.6.9
48
48
  # via pre-commit
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '3.9.1'
21
- __version_tuple__ = version_tuple = (3, 9, 1)
20
+ __version__ = version = '3.9.2'
21
+ __version_tuple__ = version_tuple = (3, 9, 2)
@@ -1,7 +1,7 @@
1
1
  """Currency conversion"""
2
2
 
3
3
  import logging
4
- from copy import copy
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 = None
37
- _cached_historic_rates = None
38
- _rates_api = None
39
- _secondary_rates = None
40
- _secondary_historic = None
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 = copy(current_rates_cache)
87
- cls._cached_historic_rates = copy(historic_rates_cache)
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 = None
90
- cls._secondary_historic = None
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
- try:
121
- _, iterator = retriever.get_tabular_rows(
122
- secondary_historic_url,
123
- dict_form=True,
124
- filename="historic_rates.csv",
125
- logstr="secondary historic exchange rates",
126
- )
127
- cls._secondary_historic = {}
128
- for row in iterator:
129
- currency = row["Currency"]
130
- date = get_int_timestamp(parse_date(row["Date"]))
131
- rate = float(row["Rate"])
132
- dict_of_dicts_add(cls._secondary_historic, currency, date, rate)
133
- except (DownloadError, OSError):
134
- logger.exception("Error getting secondary historic rates!")
135
- cls._secondary_historic = "FAIL"
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 is None:
153
- Currency.setup()
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 secondary fx rate for currency on a particular date
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
- if cls._secondary_historic is None:
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
- return cls._get_interpolated_rate(
430
- timestamp1,
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
- if currency_data is not None:
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(
@@ -58,7 +58,7 @@ class WFPExchangeRates:
58
58
  parameters={"currencyName": currency},
59
59
  )
60
60
  historic_rates = {}
61
- for quote in quotes:
61
+ for quote in reversed(quotes):
62
62
  if not quote["isOfficial"]:
63
63
  continue
64
64
  date = parse_date(quote["date"])
@@ -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 fixtures(self):
19
- return join("tests", "fixtures")
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 secondary_rates_url(self, fixtures):
23
- return join(fixtures, "secondary_rates.json")
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, fixtures):
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 = fixtures
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(self, retrievers, secondary_rates_url):
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(self, retrievers, secondary_rates_url):
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(self, retrievers, secondary_historic_url):
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._secondary_historic = None
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(self, retrievers, secondary_historic_url):
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
@@ -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
- @pytest.fixture(scope="class")
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(historic_rates_cache=all_historic_rates)
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
+ )