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.
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.github/workflows/publish.yaml +2 -2
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.github/workflows/run-python-tests.yaml +4 -4
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.pre-commit-config.yaml +2 -2
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/PKG-INFO +2 -2
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/pyproject.toml +2 -2
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/__init__.py +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/_version.py +2 -2
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/adminlevel.py +22 -24
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/country.py +33 -33
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/currency.py +33 -36
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/int_timestamp.py +1 -1
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/wfp_api.py +2 -2
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/__init__.py +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/test_adminlevel.py +399 -399
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/test_country.py +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/test_currency.py +0 -0
- hdx_python_country-4.2.7/tests/hdx/location/test_wfp_exchangerates.py +71 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/uv.lock +41 -42
- hdx_python_country-4.2.6/tests/hdx/location/test_wfp_exchangerates.py +0 -69
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.gitignore +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/LICENSE +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/README.md +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/documentation/.readthedocs.yaml +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/documentation/index.md +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/documentation/mkdocs.yaml +0 -0
- {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
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/src/hdx/location/wfp_exchangerates.py +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/adminlevel.yaml +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/adminlevelparent.yaml +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/fixtures-download-global-pcode-lengths.csv +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/fixtures-download-global-pcodes-adm-1-2.csv +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/secondary_historic_rates.csv +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/secondary_rates.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_List_1.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_List_2.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_1.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_10.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_2.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_3.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_4.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_5.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_6.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_7.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_8.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_9.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Markets_List_AFG_1.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/fixtures/wfp/Markets_List_AFG_2.json +0 -0
- {hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/tests/hdx/location/Countries_UZB_Deleted.csv +0 -0
- {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@
|
|
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@
|
|
28
|
+
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
|
29
29
|
|
|
30
30
|
- name: Build with uv
|
|
31
31
|
run: uv build
|
{hdx_python_country-4.2.6 → hdx_python_country-4.2.7}/.github/workflows/run-python-tests.yaml
RENAMED
|
@@ -16,10 +16,10 @@ jobs:
|
|
|
16
16
|
pull-requests: write
|
|
17
17
|
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
20
|
|
|
21
21
|
- name: Install uv
|
|
22
|
-
uses: astral-sh/setup-uv@
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
51
|
+
"ruff==0.17.3",
|
|
52
52
|
"data-bridges-client>=9.0.0,<10",
|
|
53
53
|
]
|
|
54
54
|
|
|
File without changes
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (4, 2,
|
|
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",
|
|
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
|
-
|
|
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
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
916
|
-
if countriesdata["countries"][
|
|
917
|
-
return
|
|
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
|
|
920
|
-
if countriesdata["countries"][
|
|
921
|
-
return
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if low:
|
|
256
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
}
|
|
File without changes
|