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.
Files changed (51) hide show
  1. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/.github/workflows/publish.yaml +1 -1
  2. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/PKG-INFO +90 -2
  3. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/README.md +88 -0
  4. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/documentation/index.md +7 -3
  5. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/pyproject.toml +1 -1
  6. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/requirements.txt +36 -36
  7. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/_version.py +2 -2
  8. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/country.py +1 -1
  9. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/currency.py +106 -105
  10. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/wfp_exchangerates.py +1 -1
  11. hdx_python_country-3.9.3/tests/hdx/location/conftest.py +26 -0
  12. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/test_adminlevel.py +0 -4
  13. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/test_country.py +8 -0
  14. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/test_currency.py +35 -20
  15. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/test_wfp_exchangerates.py +20 -22
  16. hdx_python_country-3.9.1/CONTRIBUTING.md +0 -53
  17. /hdx_python_country-3.9.1/coveragerc → /hdx_python_country-3.9.3/.coveragerc +0 -0
  18. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/.github/workflows/run-python-tests.yaml +0 -0
  19. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/.gitignore +0 -0
  20. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/.pre-commit-config.yaml +0 -0
  21. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/LICENSE +0 -0
  22. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/documentation/.readthedocs.yaml +0 -0
  23. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/documentation/mkdocs.yaml +0 -0
  24. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/hatch.toml +0 -0
  25. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/pytest.ini +0 -0
  26. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/ruff.toml +0 -0
  27. {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
  28. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/__init__.py +0 -0
  29. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/adminlevel.py +0 -0
  30. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/int_timestamp.py +0 -0
  31. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/src/hdx/location/wfp_api.py +0 -0
  32. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/adminlevel.yaml +0 -0
  33. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/adminlevelparent.yaml +0 -0
  34. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/download-global-pcode-lengths.csv +0 -0
  35. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/download-global-pcodes-adm-1-2.csv +0 -0
  36. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/secondary_historic_rates.csv +0 -0
  37. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/secondary_rates.json +0 -0
  38. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_List_1.json +0 -0
  39. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_List_2.json +0 -0
  40. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_1.json +0 -0
  41. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_10.json +0 -0
  42. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_2.json +0 -0
  43. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_3.json +0 -0
  44. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_4.json +0 -0
  45. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_5.json +0 -0
  46. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_6.json +0 -0
  47. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_7.json +0 -0
  48. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_8.json +0 -0
  49. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/fixtures/wfp/Currency_UsdIndirectQuotation_9.json +0 -0
  50. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/Countries_UZB_Deleted.csv +0 -0
  51. {hdx_python_country-3.9.1 → hdx_python_country-3.9.3}/tests/hdx/location/__init__.py +0 -0
@@ -10,7 +10,7 @@ jobs:
10
10
 
11
11
  environment:
12
12
  name: pypi
13
- url: https://pypi.org/p/hdx-python-utilities
13
+ url: https://pypi.org/p/hdx-python-country
14
14
 
15
15
  permissions:
16
16
  id-token: write # IMPORTANT: mandatory for trusted publishing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hdx-python-country
3
- Version: 3.9.1
3
+ Version: 3.9.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.5
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. Note that the
195
- returned values are cached to reduce network usage which means that the library is
196
- unsuited for use where rates are expected to update while the program is running:
196
+ Various functions support the conversion of monetary amounts to USD. The setup
197
+ method must be called once before using any other methods. Note that the
198
+ returned values are cached to reduce network usage which means that the
199
+ library is unsuited for use where rates are expected to update while the
200
+ program is running:
197
201
 
198
202
  Currency.setup(fallback_historic_to_current=True, fallback_current_to_static=True, log_level=logging.INFO)
199
203
  currency = Country.get_currency_from_iso3("usa") # returns "USD"
@@ -34,7 +34,7 @@ classifiers = [
34
34
  requires-python = ">=3.8"
35
35
 
36
36
  dependencies = [
37
- "hdx-python-utilities>=3.8.5",
37
+ "hdx-python-utilities>=3.8.6",
38
38
  "libhxl>=5.2.2",
39
39
  "tenacity",
40
40
  ]
@@ -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
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.1.31
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.1
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.7.1
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.0
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.5
45
+ hdx-python-utilities==3.8.6
44
46
  # via hdx-python-country (pyproject.toml)
45
- humanize==4.12.1
47
+ humanize==4.12.3
46
48
  # via frictionless
47
- identify==2.6.9
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==2024.10.1
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.7
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.2
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.1.3
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.9
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==24.2
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.15
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.10.6
136
+ pydantic==2.11.4
136
137
  # via frictionless
137
- pydantic-core==2.27.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.14.3
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.0.0
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==13.9.4
191
- # via
192
- # mkapi
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
- stringcase==1.2.0
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.0.0
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.2
217
+ typer==0.15.3
221
218
  # via frictionless
222
- typing-extensions==4.12.2
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
- unidecode==1.3.8
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.3.0
233
+ urllib3==2.4.0
234
234
  # via
235
235
  # libhxl
236
236
  # requests
237
- validators==0.34.0
237
+ validators==0.35.0
238
238
  # via frictionless
239
- virtualenv==20.29.3
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.2
251
+ xlsxwriter==3.2.3
252
252
  # via tableschema-to-template
253
253
  xlwt==1.3.0
254
254
  # via hdx-python-utilities
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '3.9.1'
21
- __version_tuple__ = version_tuple = (3, 9, 1)
20
+ __version__ = version = '3.9.3'
21
+ __version_tuple__ = version_tuple = (3, 9, 3)
@@ -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 copy
4
+ from copy import deepcopy
5
5
  from datetime import datetime, timezone
6
6
  from typing import Dict, Optional, Union
7
7
 
@@ -33,11 +33,11 @@ class Currency:
33
33
  _secondary_historic_url = (
34
34
  "https://codeforiati.org/imf-exchangerates/imf_exchangerates.csv"
35
35
  )
36
- _cached_current_rates = None
37
- _cached_historic_rates = None
38
- _rates_api = None
39
- _secondary_rates = None
40
- _secondary_historic = None
36
+ _cached_current_rates = {}
37
+ _cached_historic_rates = {}
38
+ _rates_api = ""
39
+ _secondary_rates = {}
40
+ _secondary_historic_rates = {}
41
41
  _fallback_to_current = False
42
42
  _no_historic = False
43
43
  _user_agent = "hdx-python-country-rates"
@@ -52,7 +52,8 @@ class Currency:
52
52
  retriever: Optional[Retrieve] = None,
53
53
  primary_rates_url: str = _primary_rates_url,
54
54
  secondary_rates_url: str = _secondary_rates_url,
55
- secondary_historic_url: str = _secondary_historic_url,
55
+ secondary_historic_url: Optional[str] = _secondary_historic_url,
56
+ secondary_historic_rates: Optional[Dict] = None,
56
57
  fallback_historic_to_current: bool = False,
57
58
  fallback_current_to_static: bool = False,
58
59
  no_historic: bool = False,
@@ -60,6 +61,7 @@ class Currency:
60
61
  log_level: int = logging.DEBUG,
61
62
  current_rates_cache: Dict = {"USD": 1},
62
63
  historic_rates_cache: Dict = {},
64
+ use_secondary_historic: bool = False,
63
65
  ) -> None:
64
66
  """
65
67
  Setup the sources. If you wish to use a static fallback file by setting
@@ -70,7 +72,8 @@ class Currency:
70
72
  retriever (Optional[Retrieve]): Retrieve object to use for downloading. Defaults to None (generate a new one).
71
73
  primary_rates_url (str): Primary rates url to use. Defaults to Yahoo API.
72
74
  secondary_rates_url (str): Current rates url to use. Defaults to currency-api.
73
- secondary_historic_url (str): Historic rates url to use. Defaults to IMF (via IATI).
75
+ secondary_historic_url (Optional[str]): Historic rates url to use. Defaults to IMF (via IATI).
76
+ secondary_historic_rates (Optional[Dict]): Historic rates to use. Defaults to None.
74
77
  fallback_historic_to_current (bool): If historic unavailable, fallback to current. Defaults to False.
75
78
  fallback_current_to_static (bool): Use static file as final fallback. Defaults to False.
76
79
  no_historic (bool): Do not set up historic rates. Defaults to False.
@@ -78,16 +81,20 @@ class Currency:
78
81
  log_level (int): Level at which to log messages. Defaults to logging.DEBUG.
79
82
  current_rates_cache (Dict): Pre-populate current rates cache with given values. Defaults to {"USD": 1}.
80
83
  historic_rates_cache (Dict): Pre-populate historic rates cache with given values. Defaults to {}.
84
+ use_secondary_historic (bool): Use secondary historic first. Defaults to False.
81
85
 
82
86
  Returns:
83
87
  None
84
88
  """
85
89
 
86
- cls._cached_current_rates = copy(current_rates_cache)
87
- cls._cached_historic_rates = copy(historic_rates_cache)
90
+ cls._cached_current_rates = deepcopy(current_rates_cache)
91
+ cls._cached_historic_rates = deepcopy(historic_rates_cache)
88
92
  cls._rates_api = primary_rates_url
89
- cls._secondary_rates = None
90
- cls._secondary_historic = None
93
+ cls._secondary_rates = {}
94
+ if secondary_historic_rates is not None:
95
+ cls._secondary_historic_rates = secondary_historic_rates
96
+ else:
97
+ cls._secondary_historic_rates = {}
91
98
  if retriever is None:
92
99
  downloader = Download(user_agent=cls._user_agent)
93
100
  temp_dir = get_temp_dir(cls._user_agent)
@@ -110,30 +117,33 @@ class Currency:
110
117
  cls._secondary_rates = secondary_rates["usd"]
111
118
  except (DownloadError, OSError):
112
119
  logger.exception("Error getting secondary current rates!")
113
- cls._secondary_rates = "FAIL"
114
120
  cls._fixed_now = fixed_now
115
121
  cls._log_level = log_level
116
122
  if no_historic:
117
- cls._no_historic = True
118
- if cls._no_historic:
119
123
  return
120
- try:
121
- _, iterator = retriever.get_tabular_rows(
122
- secondary_historic_url,
123
- dict_form=True,
124
- filename="historic_rates.csv",
125
- logstr="secondary historic exchange rates",
126
- )
127
- cls._secondary_historic = {}
128
- for row in iterator:
129
- currency = row["Currency"]
130
- date = get_int_timestamp(parse_date(row["Date"]))
131
- rate = float(row["Rate"])
132
- dict_of_dicts_add(cls._secondary_historic, currency, date, rate)
133
- except (DownloadError, OSError):
134
- logger.exception("Error getting secondary historic rates!")
135
- cls._secondary_historic = "FAIL"
124
+ cls._no_historic = no_historic
125
+ if secondary_historic_url:
126
+ try:
127
+ _, iterator = retriever.get_tabular_rows(
128
+ secondary_historic_url,
129
+ dict_form=True,
130
+ filename="historic_rates.csv",
131
+ logstr="secondary historic exchange rates",
132
+ )
133
+ for row in iterator:
134
+ currency = row["Currency"]
135
+ date = get_int_timestamp(parse_date(row["Date"]))
136
+ rate = float(row["Rate"])
137
+ dict_of_dicts_add(
138
+ cls._secondary_historic_rates, currency, date, rate
139
+ )
140
+ except (DownloadError, OSError):
141
+ logger.exception("Error getting secondary historic rates!")
136
142
  cls._fallback_to_current = fallback_historic_to_current
143
+ if use_secondary_historic:
144
+ cls._get_historic_rate = cls._get_historic_rate_secondary
145
+ else:
146
+ cls._get_historic_rate = cls._get_historic_rate_primary
137
147
 
138
148
  @classmethod
139
149
  def _get_primary_rates_data(
@@ -149,8 +159,8 @@ class Currency:
149
159
  Returns:
150
160
  Optional[float]: fx rate or None
151
161
  """
152
- if cls._rates_api is None:
153
- Currency.setup()
162
+ if not cls._rates_api:
163
+ return None
154
164
  url = cls._rates_api.format(currency=currency, date=str(timestamp))
155
165
  if downloader is None:
156
166
  downloader = cls._retriever
@@ -289,10 +299,6 @@ class Currency:
289
299
  Returns:
290
300
  Optional[float]: fx rate or None
291
301
  """
292
- if cls._secondary_rates is None:
293
- Currency.setup()
294
- if cls._secondary_rates == "FAIL":
295
- return None
296
302
  return cls._secondary_rates.get(currency.lower())
297
303
 
298
304
  @classmethod
@@ -307,8 +313,6 @@ class Currency:
307
313
  float: fx rate
308
314
  """
309
315
  currency = currency.upper()
310
- if cls._cached_current_rates is None:
311
- Currency.setup()
312
316
  fx_rate = cls._cached_current_rates.get(currency)
313
317
  if fx_rate is not None:
314
318
  return fx_rate
@@ -361,39 +365,13 @@ class Currency:
361
365
  fx_rate = cls.get_current_rate(currency)
362
366
  return usdvalue * fx_rate
363
367
 
364
- @classmethod
365
- def _get_interpolated_rate(
366
- cls,
367
- timestamp1: int,
368
- rate1: float,
369
- timestamp2: int,
370
- rate2: float,
371
- desired_timestamp: int,
372
- ) -> float:
373
- """
374
- Return a rate for a desired timestamp based on linearly interpolating between
375
- two timestamp/rate pairs.
376
-
377
- Args:
378
- timestamp1 (int): First timestamp to use for fx conversion
379
- rate1 (float): Rate at first timestamp
380
- timestamp2 (int): Second timestamp to use for fx conversion
381
- rate2 (float): Rate at second timestamp
382
- desired_timestamp (int): Timestamp at which rate is desired
383
-
384
- Returns:
385
- float: Rate at desired timestamp
386
- """
387
- return rate1 + (desired_timestamp - timestamp1) * (
388
- (rate2 - rate1) / (timestamp2 - timestamp1)
389
- )
390
-
391
368
  @classmethod
392
369
  def _get_secondary_historic_rate(
393
370
  cls, currency: str, timestamp: int
394
371
  ) -> Optional[float]:
395
372
  """
396
- Get the secondary fx rate for currency on a particular date
373
+ Get the historic fx rate for currency on a particular date using
374
+ interpolation if needed.
397
375
 
398
376
  Args:
399
377
  currency (str): Currency
@@ -402,11 +380,7 @@ class Currency:
402
380
  Returns:
403
381
  Optional[float]: fx rate or None
404
382
  """
405
- if cls._secondary_historic is None:
406
- Currency.setup()
407
- if cls._secondary_historic == "FAIL":
408
- return None
409
- currency_data = cls._secondary_historic.get(currency)
383
+ currency_data = cls._secondary_historic_rates.get(currency)
410
384
  if currency_data is None:
411
385
  return None
412
386
  fx_rate = currency_data.get(timestamp)
@@ -426,14 +400,67 @@ class Currency:
426
400
  return currency_data[timestamp2]
427
401
  if timestamp2 is None:
428
402
  return currency_data[timestamp1]
429
- return cls._get_interpolated_rate(
430
- timestamp1,
431
- currency_data[timestamp1],
432
- timestamp2,
433
- currency_data[timestamp2],
434
- timestamp,
403
+ rate1 = currency_data[timestamp1]
404
+ return rate1 + (timestamp - timestamp1) * (
405
+ (currency_data[timestamp2] - rate1) / (timestamp2 - timestamp1)
435
406
  )
436
407
 
408
+ @classmethod
409
+ def _get_historic_rate_primary(cls, currency: str, timestamp: int) -> float:
410
+ currency_data = cls._cached_historic_rates.get(currency)
411
+ if currency_data is not None:
412
+ fx_rate = currency_data.get(timestamp)
413
+ if fx_rate is not None:
414
+ return fx_rate
415
+ fx_rate = cls._get_primary_rate(currency, timestamp)
416
+ if fx_rate is not None:
417
+ dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
418
+ return fx_rate
419
+ fx_rate = cls._get_secondary_historic_rate(currency, timestamp)
420
+ if fx_rate is not None:
421
+ dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
422
+ return fx_rate
423
+ if cls._fallback_to_current:
424
+ fx_rate = cls.get_current_rate(currency)
425
+ if fx_rate:
426
+ logger.debug(
427
+ f"Falling back to current rate for currency {currency} on timestamp {timestamp}!"
428
+ )
429
+ return fx_rate
430
+ raise CurrencyError(
431
+ f"Failed to get rate for currency {currency} on timestamp {timestamp}!"
432
+ )
433
+
434
+ @classmethod
435
+ def _get_historic_rate_secondary(cls, currency: str, timestamp: int) -> float:
436
+ currency_data = cls._cached_historic_rates.get(currency)
437
+ if currency_data is not None:
438
+ fx_rate = currency_data.get(timestamp)
439
+ if fx_rate is not None:
440
+ return fx_rate
441
+ fx_rate = cls._get_secondary_historic_rate(currency, timestamp)
442
+ if fx_rate is None:
443
+ fx_rate = cls._get_primary_rate(currency, timestamp)
444
+ if fx_rate is None:
445
+ if cls._fallback_to_current:
446
+ fx_rate = cls.get_current_rate(currency)
447
+ if fx_rate:
448
+ logger.debug(
449
+ f"Falling back to current rate for currency {currency} on timestamp {timestamp}!"
450
+ )
451
+ return fx_rate
452
+ raise CurrencyError(
453
+ f"Failed to get rate for currency {currency} on timestamp {timestamp}!"
454
+ )
455
+ else:
456
+ dict_of_dicts_add(
457
+ cls._cached_historic_rates, currency, timestamp, fx_rate
458
+ )
459
+ return fx_rate
460
+ else:
461
+ dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
462
+ return fx_rate
463
+
437
464
  @classmethod
438
465
  def get_historic_rate(
439
466
  cls, currency: str, date: datetime, ignore_timeinfo: bool = True
@@ -455,38 +482,12 @@ class Currency:
455
482
  currency = currency.upper()
456
483
  if currency == "USD":
457
484
  return 1
458
- if cls._cached_historic_rates is None:
459
- Currency.setup()
460
- currency_data = cls._cached_historic_rates.get(currency)
461
485
  if ignore_timeinfo:
462
486
  date = date.replace(
463
487
  hour=0, minute=0, second=0, microsecond=0, tzinfo=timezone.utc
464
488
  )
465
- else:
466
- date = date.astimezone(timezone.utc)
467
489
  timestamp = get_int_timestamp(date)
468
- if currency_data is not None:
469
- fx_rate = currency_data.get(timestamp)
470
- if fx_rate is not None:
471
- return fx_rate
472
- fx_rate = cls._get_primary_rate(currency, timestamp)
473
- if fx_rate is not None:
474
- dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
475
- return fx_rate
476
- fx_rate = cls._get_secondary_historic_rate(currency, timestamp)
477
- if fx_rate is not None:
478
- dict_of_dicts_add(cls._cached_historic_rates, currency, timestamp, fx_rate)
479
- return fx_rate
480
- if cls._fallback_to_current:
481
- fx_rate = cls.get_current_rate(currency)
482
- if fx_rate:
483
- logger.debug(
484
- f"Falling back to current rate for currency {currency} on date {date.isoformat()}!"
485
- )
486
- return fx_rate
487
- raise CurrencyError(
488
- f"Failed to get rate for currency {currency} on date {date.isoformat()}!"
489
- )
490
+ return cls._get_historic_rate(currency, timestamp)
490
491
 
491
492
  @classmethod
492
493
  def get_historic_value_in_usd(
@@ -58,7 +58,7 @@ class WFPExchangeRates:
58
58
  parameters={"currencyName": currency},
59
59
  )
60
60
  historic_rates = {}
61
- for quote in quotes:
61
+ for quote in reversed(quotes):
62
62
  if not quote["isOfficial"]:
63
63
  continue
64
64
  date = parse_date(quote["date"])
@@ -0,0 +1,26 @@
1
+ from os.path import join
2
+
3
+ import pytest
4
+
5
+ from hdx.location.currency import Currency
6
+
7
+
8
+ @pytest.fixture(scope="session")
9
+ def fixtures_dir():
10
+ return join("tests", "fixtures")
11
+
12
+
13
+ @pytest.fixture(scope="session")
14
+ def input_dir(fixtures_dir):
15
+ return join(fixtures_dir, "wfp")
16
+
17
+
18
+ @pytest.fixture(scope="function")
19
+ def reset_currency():
20
+ Currency._cached_current_rates = {}
21
+ Currency._cached_historic_rates = {}
22
+ Currency._rates_api = ""
23
+ Currency._secondary_rates = {}
24
+ Currency._secondary_historic_rates = {}
25
+ Currency._fallback_to_current = False
26
+ Currency._no_historic = False
@@ -13,10 +13,6 @@ from hdx.utilities.retriever import Retrieve
13
13
 
14
14
 
15
15
  class TestAdminLevel:
16
- @pytest.fixture(scope="class")
17
- def fixtures_dir(self):
18
- return join("tests", "fixtures")
19
-
20
16
  @pytest.fixture(scope="function")
21
17
  def config(self, fixtures_dir):
22
18
  return load_yaml(join(fixtures_dir, "adminlevel.yaml"))
@@ -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 fixtures(self):
19
- return join("tests", "fixtures")
18
+ def secondary_rates_url(self, fixtures_dir):
19
+ return join(fixtures_dir, "secondary_rates.json")
20
20
 
21
21
  @pytest.fixture(scope="class")
22
- def secondary_rates_url(self, fixtures):
23
- return join(fixtures, "secondary_rates.json")
24
-
25
- @pytest.fixture(scope="class")
26
- def secondary_historic_url(self, fixtures):
27
- return join(fixtures, "secondary_historic_rates.csv")
22
+ def secondary_historic_url(self, fixtures_dir):
23
+ return join(fixtures_dir, "secondary_historic_rates.csv")
28
24
 
29
25
  @pytest.fixture(scope="class", autouse=True)
30
- def retrievers(self, fixtures):
26
+ def retrievers(self, fixtures_dir):
31
27
  name = "hdx-python-country-rates"
32
28
  UserAgent.set_global(name)
33
29
  downloader = Download()
34
- fallback_dir = fixtures
30
+ fallback_dir = fixtures_dir
35
31
  temp_dir = get_temp_dir(name)
36
32
  retriever = Retrieve(
37
33
  downloader,
@@ -52,7 +48,9 @@ class TestCurrency:
52
48
  yield retriever, retriever_broken
53
49
  UserAgent.clear_global()
54
50
 
55
- def test_get_current_value_in_usd(self, retrievers, secondary_rates_url):
51
+ def test_get_current_value_in_usd(
52
+ self, reset_currency, retrievers, secondary_rates_url
53
+ ):
56
54
  Currency.setup(no_historic=True)
57
55
  assert Currency.get_current_value_in_usd(10, "usd") == 10
58
56
  assert Currency.get_current_value_in_currency(10, "usd") == 10
@@ -106,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(self, retrievers, secondary_rates_url):
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(self, retrievers, secondary_historic_url):
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._secondary_historic = None
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(self, retrievers, secondary_historic_url):
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
@@ -1,7 +1,3 @@
1
- from os.path import join
2
-
3
- import pytest
4
-
5
1
  from hdx.location.currency import Currency
6
2
  from hdx.location.int_timestamp import get_int_timestamp
7
3
  from hdx.location.wfp_api import WFPAPI
@@ -13,23 +9,7 @@ from hdx.utilities.retriever import Retrieve
13
9
 
14
10
 
15
11
  class TestWFPExchangeRates:
16
- @pytest.fixture(scope="class")
17
- def currency(self):
18
- return "afn"
19
-
20
- @pytest.fixture(scope="class")
21
- def date(self):
22
- return parse_date("2020-02-20")
23
-
24
- @pytest.fixture(scope="class")
25
- def fixtures_dir(self):
26
- return join("tests", "fixtures")
27
-
28
- @pytest.fixture(scope="class")
29
- def input_dir(self, fixtures_dir):
30
- return join(fixtures_dir, "wfp")
31
-
32
- def test_wfp_exchangerates(self, input_dir, currency, date):
12
+ def test_wfp_exchangerates(self, reset_currency, input_dir):
33
13
  with temp_dir(
34
14
  "TestWFPExchangeRates",
35
15
  delete_on_success=True,
@@ -44,6 +24,8 @@ class TestWFPExchangeRates:
44
24
  save=False,
45
25
  use_saved=True,
46
26
  )
27
+ currency = "afn"
28
+ date = parse_date("2020-02-20")
47
29
  wfp_api = WFPAPI(downloader, retriever)
48
30
  wfp_api.update_retry_params(attempts=5, wait=5)
49
31
  wfp_fx = WFPExchangeRates(wfp_api)
@@ -55,11 +37,27 @@ class TestWFPExchangeRates:
55
37
  currencies = wfp_fx.get_currencies()
56
38
  assert len(currencies) == 127
57
39
 
40
+ Currency.setup()
58
41
  assert Currency.get_historic_rate(currency, date) == 76.80000305175781
59
42
  timestamp = get_int_timestamp(date)
60
43
  historic_rates = wfp_fx.get_currency_historic_rates(currency)
44
+ keys = list(historic_rates.keys())
45
+ sorted_keys = sorted(keys)
46
+ assert keys == sorted_keys
61
47
  assert historic_rates[timestamp] == 77.01
62
48
 
63
49
  all_historic_rates = wfp_fx.get_historic_rates([currency])
64
- Currency.setup(historic_rates_cache=all_historic_rates)
50
+ Currency.setup(
51
+ historic_rates_cache=all_historic_rates,
52
+ secondary_historic_rates=all_historic_rates,
53
+ use_secondary_historic=True,
54
+ )
55
+ assert Currency.get_historic_rate(currency, date) == 77.01
56
+ date = parse_date("2020-02-21")
65
57
  assert Currency.get_historic_rate(currency, date) == 77.01
58
+ date = parse_date("2020-02-20 12:00:00")
59
+ assert Currency.get_historic_rate(currency, date) == 77.01
60
+ assert (
61
+ Currency.get_historic_rate(currency, date, ignore_timeinfo=False)
62
+ == 77.01
63
+ )
@@ -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