hdx-python-country 4.2.6__tar.gz → 4.2.7__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 (49) hide show
  1. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.github/workflows/publish.yaml +2 -2
  2. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.github/workflows/run-python-tests.yaml +4 -4
  3. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.pre-commit-config.yaml +2 -2
  4. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/PKG-INFO +2 -2
  5. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/pyproject.toml +2 -2
  6. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/__init__.py +0 -0
  7. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/_version.py +2 -2
  8. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/adminlevel.py +22 -24
  9. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/country.py +33 -33
  10. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/currency.py +33 -36
  11. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/int_timestamp.py +1 -1
  12. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/wfp_api.py +2 -2
  13. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/__init__.py +0 -0
  14. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/test_adminlevel.py +399 -399
  15. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/test_country.py +0 -0
  16. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/test_currency.py +0 -0
  17. hdx_python_country-4.2.7/tests/hdx/location/test_wfp_exchangerates.py +71 -0
  18. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/uv.lock +41 -42
  19. hdx_python_country-4.2.6/tests/hdx/location/test_wfp_exchangerates.py +0 -69
  20. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.gitignore +0 -0
  21. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/LICENSE +0 -0
  22. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/README.md +0 -0
  23. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/documentation/.readthedocs.yaml +0 -0
  24. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/documentation/index.md +0 -0
  25. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/documentation/mkdocs.yaml +0 -0
  26. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/Countries & Territories Taxonomy MVP - C&T Taxonomy.csv +0 -0
  27. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/wfp_exchangerates.py +0 -0
  28. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/adminlevel.yaml +0 -0
  29. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/adminlevelparent.yaml +0 -0
  30. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/fixtures-download-global-pcode-lengths.csv +0 -0
  31. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/fixtures-download-global-pcodes-adm-1-2.csv +0 -0
  32. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/secondary_historic_rates.csv +0 -0
  33. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/secondary_rates.json +0 -0
  34. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_List_1.json +0 -0
  35. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_List_2.json +0 -0
  36. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_1.json +0 -0
  37. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_10.json +0 -0
  38. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_2.json +0 -0
  39. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_3.json +0 -0
  40. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_4.json +0 -0
  41. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_5.json +0 -0
  42. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_6.json +0 -0
  43. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_7.json +0 -0
  44. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_8.json +0 -0
  45. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_9.json +0 -0
  46. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Markets_List_AFG_1.json +0 -0
  47. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Markets_List_AFG_2.json +0 -0
  48. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/Countries_UZB_Deleted.csv +0 -0
  49. {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/conftest.py +0 -0
@@ -17,7 +17,7 @@ jobs:
17
17
  contents: read
18
18
 
19
19
  steps:
20
- - uses: actions/checkout@v6
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
21
 
22
22
  - name: Get history and tags for versioning to work
23
23
  run: |
@@ -25,7 +25,7 @@ jobs:
25
25
  git fetch --depth=1 origin +refs/tags/*:refs/tags/*
26
26
 
27
27
  - name: Install uv
28
- uses: astral-sh/setup-uv@v7
28
+ uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
29
29
 
30
30
  - name: Build with uv
31
31
  run: uv build
@@ -16,10 +16,10 @@ jobs:
16
16
  pull-requests: write
17
17
 
18
18
  steps:
19
- - uses: actions/checkout@v6
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
20
 
21
21
  - name: Install uv
22
- uses: astral-sh/setup-uv@v7
22
+ uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
23
23
  with:
24
24
  enable-cache: true
25
25
  python-version: "3.13"
@@ -36,13 +36,13 @@ jobs:
36
36
  run: uv run pytest
37
37
 
38
38
  - name: Publish Unit Test Results
39
- uses: EnricoMi/publish-unit-test-result-action@v2
39
+ uses: EnricoMi/publish-unit-test-result-action@7622c5bf3d112a72ab4fa3951c2aa5723d8ab92d # v2.25.0
40
40
  if: success()
41
41
  with:
42
42
  files: test-results.xml
43
43
 
44
44
  - name: Publish in Coveralls
45
- uses: coverallsapp/github-action@v2
45
+ uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8
46
46
  if: always()
47
47
  with:
48
48
  flag-name: tests
@@ -10,7 +10,7 @@ repos:
10
10
  - id: check-ast
11
11
 
12
12
  - repo: https://github.com/astral-sh/ruff-pre-commit
13
- rev: v0.14.13
13
+ rev: v0.17.3
14
14
  hooks:
15
15
  # Run the linter.
16
16
  - id: ruff-check
@@ -19,7 +19,7 @@ repos:
19
19
  - id: ruff-format
20
20
 
21
21
  - repo: https://github.com/astral-sh/uv-pre-commit
22
- rev: 0.9.25
22
+ rev: 0.13.6
23
23
  hooks:
24
24
  # Ensure the lockfile is up-to-date with pyproject.toml
25
25
  - id: uv-lock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hdx-python-country
3
- Version: 4.2.6
3
+ Version: 4.2.7
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
  Project-URL: Repository, https://github.com/OCHA-DAP/hdx-python-country
@@ -26,7 +26,7 @@ Classifier: Programming Language :: Python :: 3.13
26
26
  Classifier: Programming Language :: Python :: 3.14
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
28
  Requires-Python: >=3.11
29
- Requires-Dist: hdx-python-utilities<5,>=4.2.5
29
+ Requires-Dist: hdx-python-utilities<5,>=4.2.6
30
30
  Requires-Dist: tenacity
31
31
  Provides-Extra: docs
32
32
  Requires-Dist: mkapi; extra == 'docs'
@@ -35,7 +35,7 @@ dynamic = ["version"]
35
35
  requires-python = ">=3.11"
36
36
 
37
37
  dependencies = [
38
- "hdx-python-utilities>=4.2.5,<5",
38
+ "hdx-python-utilities>=4.2.6,<5",
39
39
  "tenacity",
40
40
  ]
41
41
 
@@ -48,7 +48,7 @@ dev = [
48
48
  "pytest",
49
49
  "pytest-cov",
50
50
  "pre-commit",
51
- "ruff==0.14.13",
51
+ "ruff==0.17.3",
52
52
  "data-bridges-client>=9.0.0,<10",
53
53
  ]
54
54
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '4.2.6'
22
- __version_tuple__ = version_tuple = (4, 2, 6)
21
+ __version__ = version = '4.2.7'
22
+ __version_tuple__ = version_tuple = (4, 2, 7)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -72,7 +72,7 @@ class AdminLevel:
72
72
  self._countries_fuzzy_try = admin_config.get("countries_fuzzy_try")
73
73
  self._admin_name_mappings = admin_config.get("admin_name_mappings", {})
74
74
  self._admin_name_replacements = admin_config.get("admin_name_replacements", {})
75
- self._admin_fuzzy_dont = admin_config.get("admin_fuzzy_dont", list())
75
+ self._admin_fuzzy_dont = admin_config.get("admin_fuzzy_dont", [])
76
76
  self._use_parent = False
77
77
  self._zeroes = {}
78
78
  self._parent_admins = []
@@ -100,9 +100,7 @@ class AdminLevel:
100
100
  Returns:
101
101
  Whether string looks like a p-code
102
102
  """
103
- if cls.pcode_regex.match(string):
104
- return True
105
- return False
103
+ return bool(cls.pcode_regex.match(string))
106
104
 
107
105
  @classmethod
108
106
  def set_default_admin_url(cls, admin_url: str | None = None) -> None:
@@ -403,19 +401,22 @@ class AdminLevel:
403
401
  admin_changes.append(str(admin_no))
404
402
  else:
405
403
  admin_changes.append(str(admin_no))
406
- elif len_new_pcode > total_length:
407
- if admin_length <= 2 and pcode_part[0] == "0":
408
- pcode_part = pcode_part[1:]
409
- if self._parent_admins and admin_no < self.admin_level:
410
- parent_pcode = [pcode_parts[i] for i in range(admin_no)]
411
- parent_pcode.append(pcode_part[:admin_length])
412
- parent_pcode = "".join(parent_pcode)
413
- if parent_pcode not in self._parent_admins[admin_no - 1]:
414
- pcode_part = f"0{pcode_part}"
415
- else:
416
- admin_changes.append(str(admin_no))
404
+ elif (
405
+ len_new_pcode > total_length
406
+ and admin_length <= 2
407
+ and pcode_part[0] == "0"
408
+ ):
409
+ pcode_part = pcode_part[1:]
410
+ if self._parent_admins and admin_no < self.admin_level:
411
+ parent_pcode = [pcode_parts[i] for i in range(admin_no)]
412
+ parent_pcode.append(pcode_part[:admin_length])
413
+ parent_pcode = "".join(parent_pcode)
414
+ if parent_pcode not in self._parent_admins[admin_no - 1]:
415
+ pcode_part = f"0{pcode_part}"
417
416
  else:
418
417
  admin_changes.append(str(admin_no))
418
+ else:
419
+ admin_changes.append(str(admin_no))
419
420
  pcode_parts[admin_no] = pcode_part[:admin_length]
420
421
  pcode_parts.append(pcode_part[admin_length:])
421
422
  new_pcode = "".join(pcode_parts)
@@ -506,11 +507,10 @@ class AdminLevel:
506
507
  relevant_name_replacements[key] = value
507
508
  continue
508
509
  prefix, name = key.split("|")
509
- if parent:
510
- if prefix == parent:
511
- if name not in relevant_name_replacements:
512
- relevant_name_replacements[name] = value
513
- continue
510
+ if parent and prefix == parent:
511
+ if name not in relevant_name_replacements:
512
+ relevant_name_replacements[name] = value
513
+ continue
514
514
  if prefix == countryiso3:
515
515
  if name not in relevant_name_replacements:
516
516
  relevant_name_replacements[name] = value
@@ -537,10 +537,8 @@ class AdminLevel:
537
537
  relevant_admin_fuzzy_dont.append(value)
538
538
  continue
539
539
  prefix, name = value.split("|")
540
- if parent:
541
- if prefix == parent:
542
- if name not in relevant_admin_fuzzy_dont:
543
- relevant_admin_fuzzy_dont.append(name)
540
+ if parent and prefix == parent and name not in relevant_admin_fuzzy_dont:
541
+ relevant_admin_fuzzy_dont.append(name)
544
542
  if prefix == countryiso3:
545
543
  if name not in relevant_admin_fuzzy_dont:
546
544
  relevant_admin_fuzzy_dont.append(name)
@@ -5,7 +5,7 @@ import os.path
5
5
  import re
6
6
  from collections.abc import Iterator
7
7
  from pathlib import Path
8
- from typing import Any
8
+ from typing import Any, ClassVar
9
9
 
10
10
  from hdx.utilities.base_downloader import BaseDownload, DownloadError
11
11
  from hdx.utilities.downloader import Download
@@ -24,7 +24,7 @@ class Country:
24
24
  region names and codes. Uses OCHA's countries and territories feed.
25
25
  """
26
26
 
27
- abbreviations = {
27
+ abbreviations: ClassVar[dict[str, str]] = {
28
28
  "DEM.": "DEMOCRATIC",
29
29
  "FMR.": "FORMER",
30
30
  "PROV.": "PROVINCE",
@@ -37,7 +37,7 @@ class Country:
37
37
  "W.": "WEST",
38
38
  "K.": "KINGDOM",
39
39
  }
40
- major_differentiators = [
40
+ major_differentiators: ClassVar[list[str]] = [
41
41
  "DEMOCRATIC",
42
42
  "NORTH",
43
43
  "SOUTH",
@@ -45,13 +45,13 @@ class Country:
45
45
  "WEST",
46
46
  "STATES",
47
47
  ]
48
- multiple_abbreviations = {
48
+ multiple_abbreviations: ClassVar[dict[str, list[str]]] = {
49
49
  "FED.": ["FEDERATION", "FEDERAL", "FEDERATED"],
50
50
  "ISL.": ["ISLAND", "ISLANDS"],
51
51
  "S.": ["SOUTH", "STATES"],
52
52
  "TERR.": ["TERRITORY", "TERRITORIES"],
53
53
  }
54
- simplifications = [
54
+ simplifications: ClassVar[list[str]] = [
55
55
  "AND",
56
56
  "THE",
57
57
  "OF",
@@ -80,8 +80,8 @@ class Country:
80
80
  CountryError,
81
81
  )
82
82
  _ochapath = _ochapath_default
83
- _country_name_overrides = {}
84
- _country_name_mappings = {}
83
+ _country_name_overrides: ClassVar[dict] = {}
84
+ _country_name_mappings: ClassVar[dict] = {}
85
85
  _country_name_keys = (
86
86
  "Preferred Term",
87
87
  "ISO Alt Term",
@@ -223,7 +223,7 @@ class Country:
223
223
  def sort_list(colname):
224
224
  for idval in cls._countriesdata[colname]:
225
225
  cls._countriesdata[colname][idval] = sorted(
226
- list(cls._countriesdata[colname][idval])
226
+ cls._countriesdata[colname][idval]
227
227
  )
228
228
 
229
229
  sort_list("regioncodes2countries")
@@ -317,7 +317,7 @@ class Country:
317
317
  cls._use_live = use_live
318
318
 
319
319
  @classmethod
320
- def set_ocha_url(cls, url: str = None) -> None:
320
+ def set_ocha_url(cls, url: str | None = None) -> None:
321
321
  """
322
322
  Set OCHA url from which to retrieve countries data
323
323
 
@@ -376,7 +376,7 @@ class Country:
376
376
  def get_country_info_from_iso3(
377
377
  cls,
378
378
  iso3: str,
379
- use_live: bool = None,
379
+ use_live: bool | None = None,
380
380
  exception: Exception | None = None,
381
381
  downloader: BaseDownload | None = None,
382
382
  ) -> dict[str, str] | None:
@@ -404,7 +404,7 @@ class Country:
404
404
  def get_country_name_from_iso3(
405
405
  cls,
406
406
  iso3: str,
407
- use_live: bool = None,
407
+ use_live: bool | None = None,
408
408
  exception: Exception | None = None,
409
409
  formal: bool = False,
410
410
  downloader: BaseDownload | None = None,
@@ -441,7 +441,7 @@ class Country:
441
441
  def get_currency_from_iso3(
442
442
  cls,
443
443
  iso3: str,
444
- use_live: bool = None,
444
+ use_live: bool | None = None,
445
445
  exception: Exception | None = None,
446
446
  downloader: BaseDownload | None = None,
447
447
  ) -> int | None:
@@ -469,7 +469,7 @@ class Country:
469
469
  def get_iso2_from_iso3(
470
470
  cls,
471
471
  iso3: str,
472
- use_live: bool = None,
472
+ use_live: bool | None = None,
473
473
  exception: Exception | None = None,
474
474
  downloader: BaseDownload | None = None,
475
475
  ) -> str | None:
@@ -497,7 +497,7 @@ class Country:
497
497
  def get_iso3_from_iso2(
498
498
  cls,
499
499
  iso2: str,
500
- use_live: bool = None,
500
+ use_live: bool | None = None,
501
501
  exception: Exception | None = None,
502
502
  downloader: BaseDownload | None = None,
503
503
  ) -> str | None:
@@ -525,7 +525,7 @@ class Country:
525
525
  def get_country_info_from_iso2(
526
526
  cls,
527
527
  iso2: str,
528
- use_live: bool = None,
528
+ use_live: bool | None = None,
529
529
  exception: Exception | None = None,
530
530
  downloader: BaseDownload | None = None,
531
531
  ) -> dict[str, str] | None:
@@ -553,7 +553,7 @@ class Country:
553
553
  def get_country_name_from_iso2(
554
554
  cls,
555
555
  iso2: str,
556
- use_live: bool = None,
556
+ use_live: bool | None = None,
557
557
  exception: Exception | None = None,
558
558
  formal: bool = False,
559
559
  downloader: BaseDownload | None = None,
@@ -583,7 +583,7 @@ class Country:
583
583
  def get_currency_from_iso2(
584
584
  cls,
585
585
  iso2: str,
586
- use_live: bool = None,
586
+ use_live: bool | None = None,
587
587
  exception: Exception | None = None,
588
588
  downloader: BaseDownload | None = None,
589
589
  ) -> str | None:
@@ -611,7 +611,7 @@ class Country:
611
611
  def get_m49_from_iso3(
612
612
  cls,
613
613
  iso3: str,
614
- use_live: bool = None,
614
+ use_live: bool | None = None,
615
615
  exception: Exception | None = None,
616
616
  downloader: BaseDownload | None = None,
617
617
  ) -> int | None:
@@ -639,7 +639,7 @@ class Country:
639
639
  def get_iso3_from_m49(
640
640
  cls,
641
641
  m49: int,
642
- use_live: bool = None,
642
+ use_live: bool | None = None,
643
643
  exception: Exception | None = None,
644
644
  downloader: BaseDownload | None = None,
645
645
  ) -> str | None:
@@ -667,7 +667,7 @@ class Country:
667
667
  def get_country_info_from_m49(
668
668
  cls,
669
669
  m49: int,
670
- use_live: bool = None,
670
+ use_live: bool | None = None,
671
671
  exception: Exception | None = None,
672
672
  downloader: BaseDownload | None = None,
673
673
  ) -> dict[str, str] | None:
@@ -695,7 +695,7 @@ class Country:
695
695
  def get_country_name_from_m49(
696
696
  cls,
697
697
  m49: int,
698
- use_live: bool = None,
698
+ use_live: bool | None = None,
699
699
  exception: Exception | None = None,
700
700
  formal: bool = False,
701
701
  downloader: BaseDownload | None = None,
@@ -725,7 +725,7 @@ class Country:
725
725
  def get_currency_from_m49(
726
726
  cls,
727
727
  m49: int,
728
- use_live: bool = None,
728
+ use_live: bool | None = None,
729
729
  exception: Exception | None = None,
730
730
  downloader: BaseDownload | None = None,
731
731
  ) -> str | None:
@@ -876,7 +876,7 @@ class Country:
876
876
  def get_iso3_country_code(
877
877
  cls,
878
878
  country: str,
879
- use_live: bool = None,
879
+ use_live: bool | None = None,
880
880
  exception: Exception | None = None,
881
881
  downloader: BaseDownload | None = None,
882
882
  ) -> str | None:
@@ -912,13 +912,13 @@ class Country:
912
912
  if iso3 is not None:
913
913
  return iso3
914
914
  elif re.search(r"[\u4e00-\u9fff]+", countryupper):
915
- for country in countriesdata["countries"]:
916
- if countriesdata["countries"][country]["Chinese Short"] == countryupper:
917
- return country
915
+ for iso3 in countriesdata["countries"]:
916
+ if countriesdata["countries"][iso3]["Chinese Short"] == countryupper:
917
+ return iso3
918
918
  elif re.search(r"[\u0600-\u06FF]+", countryupper):
919
- for country in countriesdata["countries"]:
920
- if countriesdata["countries"][country]["Arabic Short"] == countryupper:
921
- return country
919
+ for iso3 in countriesdata["countries"]:
920
+ if countriesdata["countries"][iso3]["Arabic Short"] == countryupper:
921
+ return iso3
922
922
 
923
923
  if exception is not None:
924
924
  raise exception
@@ -928,7 +928,7 @@ class Country:
928
928
  def get_iso3_country_code_fuzzy(
929
929
  cls,
930
930
  country: str,
931
- use_live: bool = None,
931
+ use_live: bool | None = None,
932
932
  exception: Exception | None = None,
933
933
  min_chars: int = 5,
934
934
  downloader: BaseDownload | None = None,
@@ -1023,7 +1023,7 @@ class Country:
1023
1023
  def get_countries_in_region(
1024
1024
  cls,
1025
1025
  region: int | str,
1026
- use_live: bool = None,
1026
+ use_live: bool | None = None,
1027
1027
  exception: Exception | None = None,
1028
1028
  downloader: BaseDownload | None = None,
1029
1029
  ) -> list[str]:
@@ -1056,7 +1056,7 @@ class Country:
1056
1056
  def get_hrp_status_from_iso3(
1057
1057
  cls,
1058
1058
  iso3: str,
1059
- use_live: bool = None,
1059
+ use_live: bool | None = None,
1060
1060
  exception: Exception | None = None,
1061
1061
  downloader: BaseDownload | None = None,
1062
1062
  ) -> bool | None:
@@ -1082,7 +1082,7 @@ class Country:
1082
1082
  def get_gho_status_from_iso3(
1083
1083
  cls,
1084
1084
  iso3: str,
1085
- use_live: bool = None,
1085
+ use_live: bool | None = None,
1086
1086
  exception: Exception | None = None,
1087
1087
  downloader: BaseDownload | None = None,
1088
1088
  ) -> bool | None:
@@ -4,6 +4,7 @@ import logging
4
4
  from copy import deepcopy
5
5
  from datetime import UTC, datetime
6
6
  from pathlib import Path
7
+ from typing import ClassVar
7
8
 
8
9
  from hdx.utilities.dateparse import (
9
10
  now_utc,
@@ -34,11 +35,11 @@ class Currency:
34
35
  _secondary_historic_url = (
35
36
  "https://codeforiati.org/imf-exchangerates/imf_exchangerates.csv"
36
37
  )
37
- _cached_current_rates = {}
38
- _cached_historic_rates = {}
38
+ _cached_current_rates: ClassVar[dict] = {}
39
+ _cached_historic_rates: ClassVar[dict] = {}
39
40
  _rates_api = ""
40
- _secondary_rates = {}
41
- _secondary_historic_rates = {}
41
+ _secondary_rates: ClassVar[dict] = {}
42
+ _secondary_historic_rates: ClassVar[dict] = {}
42
43
  _fallback_to_current = False
43
44
  _no_historic = False
44
45
  _user_agent = "hdx-python-country-rates"
@@ -60,8 +61,8 @@ class Currency:
60
61
  no_historic: bool = False,
61
62
  fixed_now: datetime | None = None,
62
63
  log_level: int = logging.DEBUG,
63
- current_rates_cache: dict = {"USD": 1},
64
- historic_rates_cache: dict = {},
64
+ current_rates_cache: dict | None = None,
65
+ historic_rates_cache: dict | None = None,
65
66
  use_secondary_historic: bool = False,
66
67
  ) -> None:
67
68
  """
@@ -88,6 +89,10 @@ class Currency:
88
89
  None
89
90
  """
90
91
 
92
+ if historic_rates_cache is None:
93
+ historic_rates_cache = {}
94
+ if current_rates_cache is None:
95
+ current_rates_cache = {"USD": 1}
91
96
  cls._cached_current_rates = deepcopy(current_rates_cache)
92
97
  cls._cached_historic_rates = deepcopy(historic_rates_cache)
93
98
  cls._rates_api = primary_rates_url
@@ -194,14 +199,10 @@ class Currency:
194
199
  return None
195
200
 
196
201
  def beyond_threshold(x, y):
197
- if max(x, y) / min(x, y) > cls._threshold:
198
- return True
199
- return False
202
+ return max(x, y) / min(x, y) > cls._threshold
200
203
 
201
204
  def within_threshold(x, y):
202
- if max(x, y) / min(x, y) > cls._threshold:
203
- return False
204
- return True
205
+ return max(x, y) / min(x, y) <= cls._threshold
205
206
 
206
207
  # Compare adjclose to other variables returned by Yahoo API
207
208
  adjclose = adjclose[0]
@@ -232,29 +233,27 @@ class Currency:
232
233
  secondary_fx_rate = cls._get_secondary_historic_rate(currency, timestamp)
233
234
  if not secondary_fx_rate:
234
235
  # compare with high and low to reveal errors from Yahoo feed
235
- if high and low:
236
- if within_threshold(low, high):
237
- return low + (high - low) / 2
236
+ if high and low and within_threshold(low, high):
237
+ return low + (high - low) / 2
238
238
  return None
239
239
 
240
240
  # compare with secondary historic rate
241
241
  if within_threshold(adjclose, secondary_fx_rate):
242
242
  return adjclose
243
243
  # if adjclose is wacky, find another value to return that is ok
244
- if high and low:
245
- if within_threshold(high, secondary_fx_rate) and within_threshold(
246
- low, secondary_fx_rate
247
- ):
248
- return low + (high - low) / 2
249
- if open:
250
- if within_threshold(open, secondary_fx_rate):
251
- return open
252
- if high:
253
- if within_threshold(high, secondary_fx_rate):
254
- return high
255
- if low:
256
- if within_threshold(low, secondary_fx_rate):
257
- return low
244
+ if (
245
+ high
246
+ and low
247
+ and within_threshold(high, secondary_fx_rate)
248
+ and within_threshold(low, secondary_fx_rate)
249
+ ):
250
+ return low + (high - low) / 2
251
+ if open and within_threshold(open, secondary_fx_rate):
252
+ return open
253
+ if high and within_threshold(high, secondary_fx_rate):
254
+ return high
255
+ if low and within_threshold(low, secondary_fx_rate):
256
+ return low
258
257
  return secondary_fx_rate
259
258
 
260
259
  @classmethod
@@ -329,7 +328,7 @@ class Currency:
329
328
  raise CurrencyError(f"Failed to get rate for currency {currency}!")
330
329
 
331
330
  @classmethod
332
- def get_current_value_in_usd(cls, value: int | float, currency: str) -> float:
331
+ def get_current_value_in_usd(cls, value: float, currency: str) -> float:
333
332
  """
334
333
  Get the current USD value of the value in local currency
335
334
 
@@ -347,9 +346,7 @@ class Currency:
347
346
  return value / fx_rate
348
347
 
349
348
  @classmethod
350
- def get_current_value_in_currency(
351
- cls, usdvalue: int | float, currency: str
352
- ) -> float:
349
+ def get_current_value_in_currency(cls, usdvalue: float, currency: str) -> float:
353
350
  """
354
351
  Get the current value in local currency of the value in USD
355
352
 
@@ -389,7 +386,7 @@ class Currency:
389
386
  return fx_rate
390
387
  timestamp1 = None
391
388
  timestamp2 = None
392
- for ts in currency_data.keys():
389
+ for ts in currency_data:
393
390
  if timestamp > ts:
394
391
  timestamp1 = ts
395
392
  else:
@@ -491,7 +488,7 @@ class Currency:
491
488
  @classmethod
492
489
  def get_historic_value_in_usd(
493
490
  cls,
494
- value: int | float,
491
+ value: float,
495
492
  currency: str,
496
493
  date: datetime,
497
494
  ignore_timeinfo: bool = True,
@@ -521,7 +518,7 @@ class Currency:
521
518
  @classmethod
522
519
  def get_historic_value_in_currency(
523
520
  cls,
524
- usdvalue: int | float,
521
+ usdvalue: float,
525
522
  currency: str,
526
523
  date: datetime,
527
524
  ignore_timeinfo: bool = True,
@@ -17,6 +17,6 @@ def get_int_timestamp(date: datetime) -> int:
17
17
  """
18
18
  timestamp = _cache_timestamp_lookup.get(date)
19
19
  if timestamp is None:
20
- timestamp = int(round(get_timestamp_from_datetime(date)))
20
+ timestamp = round(get_timestamp_from_datetime(date))
21
21
  _cache_timestamp_lookup[date] = timestamp
22
22
  return timestamp
@@ -3,7 +3,7 @@ import logging
3
3
  from collections.abc import Callable
4
4
  from datetime import date, datetime
5
5
  from os import getenv
6
- from typing import Any
6
+ from typing import Any, ClassVar
7
7
 
8
8
  from data_bridges_client import (
9
9
  ApiClient,
@@ -60,7 +60,7 @@ class WFPAPI:
60
60
  retriever: Retrieve object for interacting with WFP API
61
61
  """
62
62
 
63
- default_retry_params = {
63
+ default_retry_params: ClassVar[dict[str, Any]] = {
64
64
  "retry": retry_if_exception_type(ApiException),
65
65
  "after": after_log(logger, logging.INFO),
66
66
  }