hdx-python-country 3.9.1__tar.gz → 3.9.3__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.3}/.github/workflows/publish.yaml +1 -1
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/PKG-INFO +90 -2
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/README.md +88 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/documentation/index.md +7 -3
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/pyproject.toml +1 -1
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/requirements.txt +36 -36
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/_version.py +2 -2
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/country.py +1 -1
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/currency.py +106 -105
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/wfp_exchangerates.py +1 -1
- hdx_python_country-3.9.3/tests/hdx/location/conftest.py +26 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/test_adminlevel.py +0 -4
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/test_country.py +8 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/test_currency.py +35 -20
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/test_wfp_exchangerates.py +20 -22
- hdx_python_country-3.9.1/CONTRIBUTING.md +0 -53
- /hdx_python_country-3.9.1/coveragerc → /hdx_python_country-3.9.3/.coveragerc +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/.github/workflows/run-python-tests.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/.gitignore +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/.pre-commit-config.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/LICENSE +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/documentation/.readthedocs.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/documentation/mkdocs.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/hatch.toml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/pytest.ini +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/ruff.toml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/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.3}/src/hdx/location/__init__.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/adminlevel.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/int_timestamp.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/wfp_api.py +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/adminlevel.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/adminlevelparent.yaml +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/download-global-pcode-lengths.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/download-global-pcodes-adm-1-2.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/secondary_historic_rates.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/secondary_rates.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_List_1.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_List_2.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_1.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_10.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_2.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_3.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_4.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_5.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_6.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_7.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_8.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_9.json +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/Countries_UZB_Deleted.csv +0 -0
- {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hdx-python-country
|
|
3
|
-
Version: 3.9.
|
|
3
|
+
Version: 3.9.3
|
|
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
|
|
@@ -25,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
27
|
Requires-Python: >=3.8
|
|
28
|
-
Requires-Dist: hdx-python-utilities>=3.8.
|
|
28
|
+
Requires-Dist: hdx-python-utilities>=3.8.6
|
|
29
29
|
Requires-Dist: libhxl>=5.2.2
|
|
30
30
|
Requires-Dist: tenacity
|
|
31
31
|
Provides-Extra: dev
|
|
@@ -69,3 +69,91 @@ For more information, please read the [documentation](https://hdx-python-country
|
|
|
69
69
|
This library is part of the [Humanitarian Data Exchange](https://data.humdata.org/)
|
|
70
70
|
(HDX) project. If you have humanitarian related data, please upload your datasets to
|
|
71
71
|
HDX.
|
|
72
|
+
|
|
73
|
+
## Development
|
|
74
|
+
|
|
75
|
+
### Environment
|
|
76
|
+
|
|
77
|
+
Development is currently done using Python 3.12. We recommend using a virtual
|
|
78
|
+
environment such as ``venv``:
|
|
79
|
+
|
|
80
|
+
```shell
|
|
81
|
+
python -m venv venv
|
|
82
|
+
source venv/bin/activate
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
In your virtual environment, install all packages for development by running:
|
|
86
|
+
|
|
87
|
+
```shell
|
|
88
|
+
pip install -r requirements.txt
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Pre-commit
|
|
92
|
+
|
|
93
|
+
Be sure to install `pre-commit`, which is run every time you make a git commit:
|
|
94
|
+
|
|
95
|
+
```shell
|
|
96
|
+
pip install pre-commit
|
|
97
|
+
pre-commit install
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
With pre-commit, all code is formatted according to
|
|
101
|
+
[ruff](https://docs.astral.sh/ruff/) guidelines.
|
|
102
|
+
|
|
103
|
+
To check if your changes pass pre-commit without committing, run:
|
|
104
|
+
|
|
105
|
+
```shell
|
|
106
|
+
pre-commit run --all-files
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Testing
|
|
110
|
+
|
|
111
|
+
Ensure you have the required packages to run the tests:
|
|
112
|
+
|
|
113
|
+
```shell
|
|
114
|
+
pip install -r requirements.txt
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
To run the tests and view coverage, execute:
|
|
118
|
+
|
|
119
|
+
```shell
|
|
120
|
+
pytest -c --cov hdx
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Packages
|
|
124
|
+
|
|
125
|
+
[uv](https://github.com/astral-sh/uv) is used for package management. If
|
|
126
|
+
you’ve introduced a new package to the source code (i.e. anywhere in `src/`),
|
|
127
|
+
please add it to the `project.dependencies` section of `pyproject.toml` with
|
|
128
|
+
any known version constraints.
|
|
129
|
+
|
|
130
|
+
To add packages required only for testing, add them to the `test` section under
|
|
131
|
+
`[project.optional-dependencies]`.
|
|
132
|
+
|
|
133
|
+
Any changes to the dependencies will be automatically reflected in
|
|
134
|
+
`requirements.txt` with `pre-commit`, but you can re-generate the file without
|
|
135
|
+
committing by executing:
|
|
136
|
+
|
|
137
|
+
```shell
|
|
138
|
+
pre-commit run pip-compile --all-files
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Project
|
|
142
|
+
|
|
143
|
+
[Hatch](https://hatch.pypa.io/) is used for project management. The project can be built using:
|
|
144
|
+
|
|
145
|
+
```shell
|
|
146
|
+
hatch build
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Linting and syntax checking can be run with:
|
|
150
|
+
|
|
151
|
+
```shell
|
|
152
|
+
hatch fmt --check
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Tests can be executed using:
|
|
156
|
+
|
|
157
|
+
```shell
|
|
158
|
+
hatch test
|
|
159
|
+
```
|
|
@@ -30,3 +30,91 @@ For more information, please read the [documentation](https://hdx-python-country
|
|
|
30
30
|
This library is part of the [Humanitarian Data Exchange](https://data.humdata.org/)
|
|
31
31
|
(HDX) project. If you have humanitarian related data, please upload your datasets to
|
|
32
32
|
HDX.
|
|
33
|
+
|
|
34
|
+
## Development
|
|
35
|
+
|
|
36
|
+
### Environment
|
|
37
|
+
|
|
38
|
+
Development is currently done using Python 3.12. We recommend using a virtual
|
|
39
|
+
environment such as ``venv``:
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
python -m venv venv
|
|
43
|
+
source venv/bin/activate
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
In your virtual environment, install all packages for development by running:
|
|
47
|
+
|
|
48
|
+
```shell
|
|
49
|
+
pip install -r requirements.txt
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Pre-commit
|
|
53
|
+
|
|
54
|
+
Be sure to install `pre-commit`, which is run every time you make a git commit:
|
|
55
|
+
|
|
56
|
+
```shell
|
|
57
|
+
pip install pre-commit
|
|
58
|
+
pre-commit install
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
With pre-commit, all code is formatted according to
|
|
62
|
+
[ruff](https://docs.astral.sh/ruff/) guidelines.
|
|
63
|
+
|
|
64
|
+
To check if your changes pass pre-commit without committing, run:
|
|
65
|
+
|
|
66
|
+
```shell
|
|
67
|
+
pre-commit run --all-files
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Testing
|
|
71
|
+
|
|
72
|
+
Ensure you have the required packages to run the tests:
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
pip install -r requirements.txt
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
To run the tests and view coverage, execute:
|
|
79
|
+
|
|
80
|
+
```shell
|
|
81
|
+
pytest -c --cov hdx
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Packages
|
|
85
|
+
|
|
86
|
+
[uv](https://github.com/astral-sh/uv) is used for package management. If
|
|
87
|
+
you’ve introduced a new package to the source code (i.e. anywhere in `src/`),
|
|
88
|
+
please add it to the `project.dependencies` section of `pyproject.toml` with
|
|
89
|
+
any known version constraints.
|
|
90
|
+
|
|
91
|
+
To add packages required only for testing, add them to the `test` section under
|
|
92
|
+
`[project.optional-dependencies]`.
|
|
93
|
+
|
|
94
|
+
Any changes to the dependencies will be automatically reflected in
|
|
95
|
+
`requirements.txt` with `pre-commit`, but you can re-generate the file without
|
|
96
|
+
committing by executing:
|
|
97
|
+
|
|
98
|
+
```shell
|
|
99
|
+
pre-commit run pip-compile --all-files
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Project
|
|
103
|
+
|
|
104
|
+
[Hatch](https://hatch.pypa.io/) is used for project management. The project can be built using:
|
|
105
|
+
|
|
106
|
+
```shell
|
|
107
|
+
hatch build
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Linting and syntax checking can be run with:
|
|
111
|
+
|
|
112
|
+
```shell
|
|
113
|
+
hatch fmt --check
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Tests can be executed using:
|
|
117
|
+
|
|
118
|
+
```shell
|
|
119
|
+
hatch test
|
|
120
|
+
```
|
|
@@ -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"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# uv pip compile pyproject.toml --resolver=backtracking --all-extras -o requirements.txt
|
|
3
3
|
annotated-types==0.7.0
|
|
4
4
|
# via pydantic
|
|
5
|
-
astdoc==1.1
|
|
5
|
+
astdoc==1.2.1
|
|
6
6
|
# via mkapi
|
|
7
7
|
attrs==25.3.0
|
|
8
8
|
# via
|
|
@@ -14,13 +14,13 @@ babel==2.17.0
|
|
|
14
14
|
# via mkdocs-material
|
|
15
15
|
backrefs==5.8
|
|
16
16
|
# via mkdocs-material
|
|
17
|
-
certifi==2025.
|
|
17
|
+
certifi==2025.4.26
|
|
18
18
|
# via requests
|
|
19
19
|
cfgv==3.4.0
|
|
20
20
|
# via pre-commit
|
|
21
21
|
chardet==5.2.0
|
|
22
22
|
# via frictionless
|
|
23
|
-
charset-normalizer==3.4.
|
|
23
|
+
charset-normalizer==3.4.2
|
|
24
24
|
# via requests
|
|
25
25
|
click==8.1.8
|
|
26
26
|
# via
|
|
@@ -28,23 +28,25 @@ click==8.1.8
|
|
|
28
28
|
# typer
|
|
29
29
|
colorama==0.4.6
|
|
30
30
|
# via mkdocs-material
|
|
31
|
-
coverage==7.
|
|
31
|
+
coverage==7.8.0
|
|
32
32
|
# via pytest-cov
|
|
33
|
+
cydifflib==1.2.0
|
|
34
|
+
# via hdx-python-utilities
|
|
33
35
|
distlib==0.3.9
|
|
34
36
|
# via virtualenv
|
|
35
37
|
et-xmlfile==2.0.0
|
|
36
38
|
# via openpyxl
|
|
37
39
|
filelock==3.18.0
|
|
38
40
|
# via virtualenv
|
|
39
|
-
frictionless==5.18.
|
|
41
|
+
frictionless==5.18.1
|
|
40
42
|
# via hdx-python-utilities
|
|
41
43
|
ghp-import==2.1.0
|
|
42
44
|
# via mkdocs
|
|
43
|
-
hdx-python-utilities==3.8.
|
|
45
|
+
hdx-python-utilities==3.8.6
|
|
44
46
|
# via hdx-python-country (pyproject.toml)
|
|
45
|
-
humanize==4.12.
|
|
47
|
+
humanize==4.12.3
|
|
46
48
|
# via frictionless
|
|
47
|
-
identify==2.6.
|
|
49
|
+
identify==2.6.10
|
|
48
50
|
# via pre-commit
|
|
49
51
|
idna==3.10
|
|
50
52
|
# via requests
|
|
@@ -68,21 +70,20 @@ jsonschema==4.23.0
|
|
|
68
70
|
# via
|
|
69
71
|
# frictionless
|
|
70
72
|
# tableschema-to-template
|
|
71
|
-
jsonschema-specifications==
|
|
73
|
+
jsonschema-specifications==2025.4.1
|
|
72
74
|
# via jsonschema
|
|
73
75
|
libhxl==5.2.2
|
|
74
76
|
# via hdx-python-country (pyproject.toml)
|
|
75
77
|
loguru==0.7.3
|
|
76
78
|
# via hdx-python-utilities
|
|
77
|
-
markdown==3.
|
|
79
|
+
markdown==3.8
|
|
78
80
|
# via
|
|
79
|
-
# astdoc
|
|
80
81
|
# mkdocs
|
|
81
82
|
# mkdocs-material
|
|
82
83
|
# pymdown-extensions
|
|
83
84
|
markdown-it-py==3.0.0
|
|
84
85
|
# via rich
|
|
85
|
-
marko==2.1.
|
|
86
|
+
marko==2.1.3
|
|
86
87
|
# via frictionless
|
|
87
88
|
markupsafe==3.0.2
|
|
88
89
|
# via
|
|
@@ -94,7 +95,7 @@ mergedeep==1.3.4
|
|
|
94
95
|
# via
|
|
95
96
|
# mkdocs
|
|
96
97
|
# mkdocs-get-deps
|
|
97
|
-
mkapi==4.
|
|
98
|
+
mkapi==4.3.2
|
|
98
99
|
# via hdx-python-country (pyproject.toml)
|
|
99
100
|
mkdocs==1.6.1
|
|
100
101
|
# via
|
|
@@ -102,7 +103,7 @@ mkdocs==1.6.1
|
|
|
102
103
|
# mkdocs-material
|
|
103
104
|
mkdocs-get-deps==0.2.0
|
|
104
105
|
# via mkdocs
|
|
105
|
-
mkdocs-material==9.6.
|
|
106
|
+
mkdocs-material==9.6.12
|
|
106
107
|
# via mkapi
|
|
107
108
|
mkdocs-material-extensions==1.3.1
|
|
108
109
|
# via mkdocs-material
|
|
@@ -110,7 +111,7 @@ nodeenv==1.9.1
|
|
|
110
111
|
# via pre-commit
|
|
111
112
|
openpyxl==3.1.5
|
|
112
113
|
# via hdx-python-utilities
|
|
113
|
-
packaging==
|
|
114
|
+
packaging==25.0
|
|
114
115
|
# via
|
|
115
116
|
# mkdocs
|
|
116
117
|
# pytest
|
|
@@ -118,7 +119,7 @@ paginate==0.5.7
|
|
|
118
119
|
# via mkdocs-material
|
|
119
120
|
pathspec==0.12.1
|
|
120
121
|
# via mkdocs
|
|
121
|
-
petl==1.7.
|
|
122
|
+
petl==1.7.16
|
|
122
123
|
# via frictionless
|
|
123
124
|
platformdirs==4.3.7
|
|
124
125
|
# via
|
|
@@ -132,15 +133,15 @@ ply==3.11
|
|
|
132
133
|
# libhxl
|
|
133
134
|
pre-commit==4.2.0
|
|
134
135
|
# via hdx-python-country (pyproject.toml)
|
|
135
|
-
pydantic==2.
|
|
136
|
+
pydantic==2.11.4
|
|
136
137
|
# via frictionless
|
|
137
|
-
pydantic-core==2.
|
|
138
|
+
pydantic-core==2.33.2
|
|
138
139
|
# via pydantic
|
|
139
140
|
pygments==2.19.1
|
|
140
141
|
# via
|
|
141
142
|
# mkdocs-material
|
|
142
143
|
# rich
|
|
143
|
-
pymdown-extensions==10.
|
|
144
|
+
pymdown-extensions==10.15
|
|
144
145
|
# via mkdocs-material
|
|
145
146
|
pyphonetics==0.5.3
|
|
146
147
|
# via hdx-python-utilities
|
|
@@ -148,7 +149,7 @@ pytest==8.3.5
|
|
|
148
149
|
# via
|
|
149
150
|
# hdx-python-country (pyproject.toml)
|
|
150
151
|
# pytest-cov
|
|
151
|
-
pytest-cov==6.
|
|
152
|
+
pytest-cov==6.1.1
|
|
152
153
|
# via hdx-python-country (pyproject.toml)
|
|
153
154
|
python-dateutil==2.9.0.post0
|
|
154
155
|
# via
|
|
@@ -187,11 +188,9 @@ requests-file==2.1.0
|
|
|
187
188
|
# via hdx-python-utilities
|
|
188
189
|
rfc3986==2.0.0
|
|
189
190
|
# via frictionless
|
|
190
|
-
rich==
|
|
191
|
-
# via
|
|
192
|
-
|
|
193
|
-
# typer
|
|
194
|
-
rpds-py==0.23.1
|
|
191
|
+
rich==14.0.0
|
|
192
|
+
# via typer
|
|
193
|
+
rpds-py==0.24.0
|
|
195
194
|
# via
|
|
196
195
|
# jsonschema
|
|
197
196
|
# referencing
|
|
@@ -205,38 +204,39 @@ simpleeval==1.0.3
|
|
|
205
204
|
# via frictionless
|
|
206
205
|
six==1.17.0
|
|
207
206
|
# via python-dateutil
|
|
208
|
-
|
|
209
|
-
# via frictionless
|
|
210
|
-
structlog==25.2.0
|
|
207
|
+
structlog==25.3.0
|
|
211
208
|
# via libhxl
|
|
212
209
|
tableschema-to-template==0.0.13
|
|
213
210
|
# via hdx-python-utilities
|
|
214
211
|
tabulate==0.9.0
|
|
215
212
|
# via frictionless
|
|
216
|
-
tenacity==9.
|
|
213
|
+
tenacity==9.1.2
|
|
217
214
|
# via hdx-python-country (pyproject.toml)
|
|
218
215
|
text-unidecode==1.3
|
|
219
216
|
# via python-slugify
|
|
220
|
-
typer==0.15.
|
|
217
|
+
typer==0.15.3
|
|
221
218
|
# via frictionless
|
|
222
|
-
typing-extensions==4.
|
|
219
|
+
typing-extensions==4.13.2
|
|
223
220
|
# via
|
|
224
221
|
# frictionless
|
|
225
222
|
# pydantic
|
|
226
223
|
# pydantic-core
|
|
227
224
|
# referencing
|
|
228
225
|
# typer
|
|
229
|
-
|
|
226
|
+
# typing-inspection
|
|
227
|
+
typing-inspection==0.4.0
|
|
228
|
+
# via pydantic
|
|
229
|
+
unidecode==1.4.0
|
|
230
230
|
# via
|
|
231
231
|
# libhxl
|
|
232
232
|
# pyphonetics
|
|
233
|
-
urllib3==2.
|
|
233
|
+
urllib3==2.4.0
|
|
234
234
|
# via
|
|
235
235
|
# libhxl
|
|
236
236
|
# requests
|
|
237
|
-
validators==0.
|
|
237
|
+
validators==0.35.0
|
|
238
238
|
# via frictionless
|
|
239
|
-
virtualenv==20.
|
|
239
|
+
virtualenv==20.31.1
|
|
240
240
|
# via pre-commit
|
|
241
241
|
watchdog==6.0.0
|
|
242
242
|
# via mkdocs
|
|
@@ -248,7 +248,7 @@ xlrd3==1.1.0
|
|
|
248
248
|
# via libhxl
|
|
249
249
|
xlsx2csv==0.8.4
|
|
250
250
|
# via hdx-python-utilities
|
|
251
|
-
xlsxwriter==3.2.
|
|
251
|
+
xlsxwriter==3.2.3
|
|
252
252
|
# via tableschema-to-template
|
|
253
253
|
xlwt==1.3.0
|
|
254
254
|
# via hdx-python-utilities
|
|
@@ -709,7 +709,7 @@ class Country:
|
|
|
709
709
|
for simplification2 in simplifications:
|
|
710
710
|
remove.append(simplification2)
|
|
711
711
|
remove = "|".join(remove)
|
|
712
|
-
regex = re.compile(r"\b(" + remove + r")\b", flags=re.IGNORECASE)
|
|
712
|
+
regex = re.compile(r"[^\w\s]|\b(" + remove + r")\b", flags=re.IGNORECASE)
|
|
713
713
|
countryupper = regex.sub("", countryupper)
|
|
714
714
|
countryupper = countryupper.strip()
|
|
715
715
|
countryupper_words = get_words_in_sentence(countryupper)
|
|
@@ -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"))
|
|
@@ -528,6 +528,14 @@ class TestCountry:
|
|
|
528
528
|
assert Country.simplify_countryname(
|
|
529
529
|
"Democratic People's Republic of Korea"
|
|
530
530
|
) == ("KOREA", ["DEMOCRATIC", "PEOPLE'S", "REPUBLIC", "OF"])
|
|
531
|
+
assert Country.simplify_countryname("Korea (the Republic of))") == (
|
|
532
|
+
"KOREA",
|
|
533
|
+
["THE", "REPUBLIC", "OF"],
|
|
534
|
+
)
|
|
535
|
+
assert Country.simplify_countryname("Korea (the Republic of") == (
|
|
536
|
+
"KOREA",
|
|
537
|
+
["THE", "REPUBLIC", "OF"],
|
|
538
|
+
)
|
|
531
539
|
assert Country.simplify_countryname(
|
|
532
540
|
"The former Yugoslav Republic of Macedonia"
|
|
533
541
|
) == ("MACEDONIA", ["THE", "FORMER", "YUGOSLAV", "REPUBLIC", "OF"])
|
|
@@ -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,7 +104,18 @@ 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
109
|
assert Currency.get_current_rate("usd") == 1
|
|
110
|
+
|
|
111
|
+
Currency._cached_current_rates = {}
|
|
112
|
+
Currency._cached_historic_rates = {}
|
|
113
|
+
Currency._rates_api = ""
|
|
114
|
+
Currency._secondary_rates = {}
|
|
115
|
+
Currency._secondary_historic_rates = {}
|
|
116
|
+
Currency._fallback_to_current = False
|
|
117
|
+
Currency._no_historic = False
|
|
118
|
+
Currency.setup(no_historic=True)
|
|
110
119
|
rate1gbp = Currency.get_current_rate("gbp")
|
|
111
120
|
assert rate1gbp != 1
|
|
112
121
|
rate1xdr = Currency.get_current_rate("xdr")
|
|
@@ -114,27 +123,25 @@ class TestCurrency:
|
|
|
114
123
|
Currency.setup(
|
|
115
124
|
retriever=retrievers[1],
|
|
116
125
|
primary_rates_url="fail",
|
|
117
|
-
secondary_rates_url="fail",
|
|
118
126
|
fallback_current_to_static=False,
|
|
119
127
|
no_historic=True,
|
|
120
128
|
)
|
|
121
|
-
Currency._secondary_rates = None
|
|
122
129
|
rate2gbp = Currency.get_current_rate("gbp")
|
|
123
130
|
assert rate2gbp != 1
|
|
124
131
|
assert abs(rate1gbp - rate2gbp) / rate1gbp < 0.008
|
|
125
132
|
Currency.setup(
|
|
126
133
|
retriever=retrievers[1],
|
|
127
134
|
primary_rates_url="fail",
|
|
128
|
-
secondary_rates_url="fail",
|
|
129
135
|
fallback_current_to_static=False,
|
|
130
136
|
no_historic=True,
|
|
131
137
|
)
|
|
132
|
-
Currency._secondary_rates = None
|
|
133
138
|
rate2xdr = Currency.get_current_rate("xdr")
|
|
134
139
|
assert rate2xdr != 1
|
|
135
140
|
assert abs(rate1xdr - rate2xdr) / rate1xdr < 0.1
|
|
136
141
|
|
|
137
|
-
def test_get_current_value_in_usd_fixednnow(
|
|
142
|
+
def test_get_current_value_in_usd_fixednnow(
|
|
143
|
+
self, reset_currency, retrievers, secondary_rates_url
|
|
144
|
+
):
|
|
138
145
|
date = parse_date("2020-02-20")
|
|
139
146
|
Currency.setup(
|
|
140
147
|
no_historic=True,
|
|
@@ -148,7 +155,9 @@ class TestCurrency:
|
|
|
148
155
|
# falls back to secondary current rates
|
|
149
156
|
assert Currency.get_current_rate("xdr") == 0.76479065
|
|
150
157
|
|
|
151
|
-
def test_get_historic_value_in_usd(
|
|
158
|
+
def test_get_historic_value_in_usd(
|
|
159
|
+
self, reset_currency, retrievers, secondary_historic_url
|
|
160
|
+
):
|
|
152
161
|
Currency._no_historic = False
|
|
153
162
|
Currency.setup(secondary_historic_url=secondary_historic_url)
|
|
154
163
|
date = parse_date("2020-02-20")
|
|
@@ -229,13 +238,17 @@ class TestCurrency:
|
|
|
229
238
|
fallback_historic_to_current=False,
|
|
230
239
|
)
|
|
231
240
|
Currency.get_historic_value_in_usd(10, "gbp", date)
|
|
232
|
-
Currency.
|
|
241
|
+
Currency.setup(
|
|
242
|
+
retriever=retriever,
|
|
243
|
+
primary_rates_url="fail",
|
|
244
|
+
fallback_historic_to_current=False,
|
|
245
|
+
)
|
|
233
246
|
# Interpolation
|
|
234
247
|
# 0.761817697025102 + (0.776276975624903 - 0.761817697025102) * 20 / 29
|
|
235
248
|
# 0.761817697025102 + (0.776276975624903 - 0.761817697025102) * (1582156800-1580428800) / (1582934400 - 1580428800)
|
|
236
249
|
assert Currency.get_historic_rate("gbp", date) == 0.7717896133008268
|
|
237
250
|
|
|
238
|
-
def test_broken_rates_no_secondary(self, retrievers):
|
|
251
|
+
def test_broken_rates_no_secondary(self, reset_currency, retrievers):
|
|
239
252
|
Currency._no_historic = False
|
|
240
253
|
Currency.setup(secondary_historic_url="fail")
|
|
241
254
|
# Without the checking against high and low returned by Yahoo API, this
|
|
@@ -265,7 +278,9 @@ class TestCurrency:
|
|
|
265
278
|
== 601.632568359375
|
|
266
279
|
)
|
|
267
280
|
|
|
268
|
-
def test_broken_rates_with_secondary(
|
|
281
|
+
def test_broken_rates_with_secondary(
|
|
282
|
+
self, reset_currency, retrievers, secondary_historic_url
|
|
283
|
+
):
|
|
269
284
|
Currency._no_historic = False
|
|
270
285
|
Currency.setup(secondary_historic_url=secondary_historic_url)
|
|
271
286
|
# Without the checking against secondary historic rate, this
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/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
|
+
)
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Development
|
|
2
|
-
|
|
3
|
-
## Environment
|
|
4
|
-
|
|
5
|
-
Development is currently done using Python 3.12. We recommend using a virtual
|
|
6
|
-
environment such as ``venv``:
|
|
7
|
-
|
|
8
|
-
python3.12 -m venv venv
|
|
9
|
-
source venv/bin/activate
|
|
10
|
-
|
|
11
|
-
In your virtual environment, please install all packages for
|
|
12
|
-
development by running:
|
|
13
|
-
|
|
14
|
-
pip install -r requirements.txt
|
|
15
|
-
|
|
16
|
-
## Pre-Commit
|
|
17
|
-
|
|
18
|
-
Also be sure to install `pre-commit`, which is run every time
|
|
19
|
-
you make a git commit:
|
|
20
|
-
|
|
21
|
-
pre-commit install
|
|
22
|
-
|
|
23
|
-
With pre-commit, all code is formatted according to
|
|
24
|
-
[ruff](https://github.com/astral-sh/ruff) guidelines.
|
|
25
|
-
|
|
26
|
-
To check if your changes pass pre-commit without committing, run:
|
|
27
|
-
|
|
28
|
-
pre-commit run --all-files
|
|
29
|
-
|
|
30
|
-
## Testing
|
|
31
|
-
|
|
32
|
-
To run the tests and view coverage, execute:
|
|
33
|
-
|
|
34
|
-
pytest --cov hdx
|
|
35
|
-
|
|
36
|
-
Follow the example set out already in ``documentation/main.md`` as you write the documentation.
|
|
37
|
-
|
|
38
|
-
## Packages
|
|
39
|
-
|
|
40
|
-
[pip-tools](https://github.com/jazzband/pip-tools) is used for
|
|
41
|
-
package management. If you’ve introduced a new package to the
|
|
42
|
-
source code (i.e.anywhere in `src/`), please add it to the
|
|
43
|
-
`project.dependencies` section of
|
|
44
|
-
`pyproject.toml` with any known version constraints.
|
|
45
|
-
|
|
46
|
-
For adding packages for testing or development, add them to
|
|
47
|
-
the `test` or `dev` sections under `[project.optional-dependencies]`.
|
|
48
|
-
|
|
49
|
-
Any changes to the dependencies will be automatically reflected in
|
|
50
|
-
`requirements.txt` with `pre-commit`, but you can re-generate
|
|
51
|
-
the file without committing by executing:
|
|
52
|
-
|
|
53
|
-
pre-commit run pip-compile --all-files
|
|
File without changes
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/.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
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/secondary_historic_rates.csv
RENAMED
|
File without changes
|
|
File without changes
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_List_1.json
RENAMED
|
File without changes
|
{hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/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.3}/tests/hdx/location/Countries_UZB_Deleted.csv
RENAMED
|
File without changes
|
|
File without changes
|