hdx-python-country 3.6.4__tar.gz → 3.6.6__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 (38) hide show
  1. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/.config/pre-commit-config.yaml +9 -10
  2. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/.config/ruff.toml +8 -5
  3. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/.github/workflows/run-python-tests.yaml +3 -0
  4. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/PKG-INFO +7 -7
  5. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/README.md +4 -4
  6. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/documentation/main.md +9 -8
  7. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/pyproject.toml +10 -14
  8. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/requirements.txt +25 -26
  9. hdx_python_country-3.6.6/src/hdx/location/__init__.py +17 -0
  10. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/src/hdx/location/_version.py +2 -2
  11. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/src/hdx/location/adminlevel.py +4 -4
  12. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/src/hdx/location/country.py +28 -27
  13. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/src/hdx/location/currency.py +14 -21
  14. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/src/hdx/location/names.py +1 -1
  15. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/src/hdx/location/phonetics.py +1 -1
  16. hdx_python_country-3.6.6/src/hdx/location/wfp_exchangerates.py +102 -0
  17. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/hdx/location/test_adminlevel.py +1 -0
  18. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/hdx/location/test_country.py +1 -0
  19. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/hdx/location/test_currency.py +17 -15
  20. hdx_python_country-3.6.6/tests/hdx/location/test_wfp_exchangerates.py +42 -0
  21. hdx_python_country-3.6.4/.config/black.toml +0 -2
  22. hdx_python_country-3.6.4/src/hdx/location/__init__.py +0 -1
  23. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/.config/coveragerc +0 -0
  24. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/.config/pytest.ini +0 -0
  25. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/.github/workflows/publish.yaml +0 -0
  26. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/.gitignore +0 -0
  27. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/CONTRIBUTING.md +0 -0
  28. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/LICENSE +0 -0
  29. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/documentation/.readthedocs.yaml +0 -0
  30. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/documentation/pydoc-markdown.yaml +0 -0
  31. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/src/hdx/location/Countries & Territories Taxonomy MVP - C&T Taxonomy with HXL Tags.csv +0 -0
  32. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/fixtures/adminlevel.yaml +0 -0
  33. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/fixtures/global_pcode_lengths.csv +0 -0
  34. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/fixtures/global_pcodes_adm_1_2.csv +0 -0
  35. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/fixtures/secondary_historic_rates.csv +0 -0
  36. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/fixtures/secondary_rates.json +0 -0
  37. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/hdx/location/Countries_UZB_Deleted.csv +0 -0
  38. {hdx_python_country-3.6.4 → hdx_python_country-3.6.6}/tests/hdx/location/__init__.py +0 -0
@@ -2,23 +2,22 @@ default_language_version:
2
2
  python: python3.11
3
3
  repos:
4
4
  - repo: https://github.com/pre-commit/pre-commit-hooks
5
- rev: v4.4.0
5
+ rev: v4.5.0
6
6
  hooks:
7
7
  - id: trailing-whitespace
8
8
  - id: end-of-file-fixer
9
9
  - id: check-ast
10
- - repo: https://github.com/psf/black
11
- rev: 23.3.0
12
- hooks:
13
- - id: black
14
- args: [--config, .config/black.toml]
15
- - repo: https://github.com/charliermarsh/ruff-pre-commit
16
- rev: v0.0.267
10
+ - repo: https://github.com/astral-sh/ruff-pre-commit
11
+ rev: v0.3.0
17
12
  hooks:
13
+ # Run the linter.
18
14
  - id: ruff
19
- args: [--config, .config/ruff.toml, --fix, --exit-non-zero-on-fix]
15
+ args: [--config, .config/ruff.toml, --fix]
16
+ # Run the formatter.
17
+ - id: ruff-format
18
+ args: [--config, .config/ruff.toml]
20
19
  - repo: https://github.com/jazzband/pip-tools
21
- rev: 6.13.0
20
+ rev: 7.4.0
22
21
  hooks:
23
22
  - id: pip-compile
24
23
  name: pip-compile requirements.txt
@@ -1,14 +1,17 @@
1
1
  line-length = 79
2
2
  exclude = ["_version.py"]
3
- ignore = [
4
- "E501" # Line too long
5
- ]
6
- # List of rules: https://beta.ruff.rs/docs/rules/
3
+
4
+ [lint]
5
+ # List of rules: https://docs.astral.sh/ruff/rules/
7
6
  select = [
8
7
  "E", # pycodestyle - default
9
8
  "F", # pyflakes - default
10
9
  "I" # isort
11
10
  ]
11
+ ignore = [
12
+ "E501" # Line too long
13
+ ]
14
+
12
15
 
13
- [isort]
16
+ [lint.isort]
14
17
  known-local-folder = ["hdx"]
@@ -28,6 +28,9 @@ jobs:
28
28
  python -m pip install --upgrade pip
29
29
  pip install --upgrade hatch
30
30
  - name: Test with hatch/pytest
31
+ env:
32
+ WFP_KEY: ${{ secrets.WFP_KEY }}
33
+ WFP_SECRET: ${{ secrets.WFP_SECRET }}
31
34
  run: |
32
35
  hatch run test:test
33
36
  - name: Check styling
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hdx-python-country
3
- Version: 3.6.4
3
+ Version: 3.6.6
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-email: Michael Rans <rans@email.com>
@@ -25,8 +25,8 @@ 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.6.4
29
- Requires-Dist: libhxl>=5.1
28
+ Requires-Dist: hdx-python-utilities>=3.6.5
29
+ Requires-Dist: libhxl>=5.2
30
30
  Requires-Dist: pyphonetics
31
31
  Provides-Extra: dev
32
32
  Requires-Dist: pre-commit; extra == 'dev'
@@ -58,10 +58,10 @@ Dem. for Democratic and Rep. for Republic.
58
58
  Mapping administration level names from a source to a given base set is also handled
59
59
  including phonetic fuzzy name matching.
60
60
 
61
- It also provides foreign exchange rates and conversion from amounts in local currency to
62
- USD and vice-versa. The conversion relies on Yahoo Finance, falling back on
63
- [currency-api](https://github.com/fawazahmed0/currency-api) for current rates,
64
- and Yahoo Finance falling back on IMF data via IATI for historic rates.
61
+ It also provides foreign exchange rates and conversion from amounts in local
62
+ currency to USD and vice-versa. The conversion relies on Yahoo Finance, falling
63
+ back on [currency-api](https://github.com/fawazahmed0/currency-api) for current rates, and Yahoo Finance falling back
64
+ on IMF data via IATI (with interpolation) for historic daily rates.
65
65
 
66
66
  For more information, please read the [documentation](https://hdx-python-country.readthedocs.io/en/latest/).
67
67
 
@@ -21,10 +21,10 @@ Dem. for Democratic and Rep. for Republic.
21
21
  Mapping administration level names from a source to a given base set is also handled
22
22
  including phonetic fuzzy name matching.
23
23
 
24
- It also provides foreign exchange rates and conversion from amounts in local currency to
25
- USD and vice-versa. The conversion relies on Yahoo Finance, falling back on
26
- [currency-api](https://github.com/fawazahmed0/currency-api) for current rates,
27
- and Yahoo Finance falling back on IMF data via IATI for historic rates.
24
+ It also provides foreign exchange rates and conversion from amounts in local
25
+ currency to USD and vice-versa. The conversion relies on Yahoo Finance, falling
26
+ back on [currency-api](https://github.com/fawazahmed0/currency-api) for current rates, and Yahoo Finance falling back
27
+ on IMF data via IATI (with interpolation) for historic daily rates.
28
28
 
29
29
  For more information, please read the [documentation](https://hdx-python-country.readthedocs.io/en/latest/).
30
30
 
@@ -29,10 +29,10 @@ Dem. for Democratic and Rep. for Republic.
29
29
  Mapping administration level names from a source to a given base set is also handled
30
30
  including phonetic fuzzy name matching.
31
31
 
32
- It also provides foreign exchange rates and conversion from amounts in local currency to
33
- USD and vice-versa. The conversion relies on Yahoo Finance, falling back on
34
- [currency-api](https://github.com/fawazahmed0/currency-api) for current rates,
35
- and Yahoo Finance falling back on IMF data via IATI for historic rates.
32
+ It also provides foreign exchange rates and conversion from amounts in local
33
+ currency to USD and vice-versa. The conversion relies on Yahoo Finance, falling
34
+ back on [currency-api](https://github.com/fawazahmed0/currency-api) for current rates, and Yahoo Finance falling back
35
+ on IMF data via IATI (with interpolation) for historic daily rates.
36
36
 
37
37
  This library is part of the [Humanitarian Data Exchange](https://data.humdata.org/)
38
38
  (HDX) project. If you have humanitarian related data, please upload your datasets to
@@ -185,9 +185,10 @@ unsuited for use where rates are expected to update while the program is running
185
185
  timezone_handling=2), ignore_timeinfo=False)
186
186
  # == 0.76910001039505
187
187
 
188
- Historic rates can be made to fall back to current rates if desired (this is not the
189
- default). It is possible to pass in a Retrieve object to Currency.setup() to allow the
190
- downloaded files from the secondary sources to be saved or previously downloaded files
191
- to be reused and to allow fallbacks from current rates to a static file eg.
188
+ Historic daily rates can be made to fall back to current rates if desired (this
189
+ is not the default). It is possible to pass in a Retrieve object to
190
+ Currency.setup() to allow the downloaded files from the secondary sources to be
191
+ saved or previously downloaded files to be reused and to allow fallbacks from
192
+ current rates to a static file eg.
192
193
 
193
194
  Currency.setup(retriever, ..., fallback_historic_to_current=True, fallback_current_to_static=True)
@@ -34,8 +34,8 @@ classifiers = [
34
34
  requires-python = ">=3.8"
35
35
 
36
36
  dependencies = [
37
- "hdx-python-utilities>=3.6.4",
38
- "libhxl>=5.1",
37
+ "hdx-python-utilities>=3.6.5",
38
+ "libhxl>=5.2",
39
39
  "pyphonetics",
40
40
  ]
41
41
  dynamic = ["version"]
@@ -50,12 +50,16 @@ Homepage = "https://github.com/OCHA-DAP/hdx-python-country"
50
50
  [project.optional-dependencies]
51
51
  test = ["pytest", "pytest-cov"]
52
52
  dev = ["pre-commit"]
53
+ #wfp = ["data-bridges-client@git+https://github.com/WFP-VAM/DataBridgesAPI@dev#egg=data-bridges-client"]
53
54
 
54
55
 
55
56
  #########
56
57
  # Hatch #
57
58
  #########
58
59
 
60
+ #[tool.hatch.metadata]
61
+ #allow-direct-references = true
62
+
59
63
  # Build
60
64
 
61
65
  [tool.hatch.build.targets.wheel]
@@ -76,7 +80,7 @@ version_scheme = "python-simplified-semver"
76
80
  # Tests
77
81
 
78
82
  [tool.hatch.envs.test]
79
- features = ["test"]
83
+ features = ["test"]#, "wfp"]
80
84
 
81
85
  [tool.hatch.envs.test.scripts]
82
86
  test = """
@@ -90,18 +94,10 @@ python = ["3.12"]
90
94
 
91
95
  [tool.hatch.envs.lint]
92
96
  detached = true
93
- dependencies = [
94
- "black",
95
- "ruff",
96
- ]
97
+ dependencies = ["ruff"]
97
98
 
98
99
  [tool.hatch.envs.lint.scripts]
99
100
  style = [
100
- "ruff --config .config/ruff.toml {args:.}",
101
- "black --config .config/black.toml --check --diff {args:.}",
102
- ]
103
- # Not used for anything at the moment
104
- fmt = [
105
- "black --config .config/black.toml {args:.}",
106
- "ruff --config .config/ruff.toml --fix {args:.}",
101
+ "ruff check --config .config/ruff.toml --diff {args:.}",
102
+ "ruff format --config .config/ruff.toml --diff {args:.}",
107
103
  ]
@@ -2,7 +2,7 @@
2
2
  # This file is autogenerated by pip-compile with Python 3.11
3
3
  # by the following command:
4
4
  #
5
- # pip-compile --all-extras --output-file=requirements.txt --resolver=backtracking pyproject.toml
5
+ # pip-compile --all-extras --output-file=requirements.txt pyproject.toml
6
6
  #
7
7
  annotated-types==0.6.0
8
8
  # via pydantic
@@ -11,7 +11,7 @@ attrs==23.2.0
11
11
  # frictionless
12
12
  # jsonlines
13
13
  # jsonschema
14
- certifi==2023.11.17
14
+ certifi==2024.2.2
15
15
  # via requests
16
16
  cfgv==3.4.0
17
17
  # via pre-commit
@@ -23,7 +23,7 @@ click==8.1.7
23
23
  # via typer
24
24
  colorama==0.4.6
25
25
  # via typer
26
- coverage[toml]==7.4.0
26
+ coverage[toml]==7.4.3
27
27
  # via pytest-cov
28
28
  distlib==0.3.8
29
29
  # via virtualenv
@@ -31,13 +31,13 @@ et-xmlfile==1.1.0
31
31
  # via openpyxl
32
32
  filelock==3.13.1
33
33
  # via virtualenv
34
- frictionless==5.16.0
34
+ frictionless==5.16.1
35
35
  # via hdx-python-utilities
36
- hdx-python-utilities==3.6.4
36
+ hdx-python-utilities==3.6.5
37
37
  # via hdx-python-country (pyproject.toml)
38
38
  humanize==4.9.0
39
39
  # via frictionless
40
- identify==2.5.33
40
+ identify==2.5.35
41
41
  # via pre-commit
42
42
  idna==3.6
43
43
  # via requests
@@ -51,21 +51,21 @@ jinja2==3.1.3
51
51
  # via frictionless
52
52
  jsonlines==4.0.0
53
53
  # via hdx-python-utilities
54
- jsonpath-ng==1.6.0
54
+ jsonpath-ng==1.6.1
55
55
  # via libhxl
56
56
  jsonschema==4.17.3
57
57
  # via
58
58
  # frictionless
59
59
  # tableschema-to-template
60
- libhxl==5.2
60
+ libhxl==5.2.1
61
61
  # via hdx-python-country (pyproject.toml)
62
62
  loguru==0.7.2
63
63
  # via hdx-python-utilities
64
64
  markdown-it-py==3.0.0
65
65
  # via rich
66
- marko==2.0.2
66
+ marko==2.0.3
67
67
  # via frictionless
68
- markupsafe==2.1.3
68
+ markupsafe==2.1.5
69
69
  # via jinja2
70
70
  mdurl==0.1.2
71
71
  # via markdown-it-py
@@ -77,19 +77,19 @@ packaging==23.2
77
77
  # via pytest
78
78
  petl==1.7.14
79
79
  # via frictionless
80
- platformdirs==4.1.0
80
+ platformdirs==4.2.0
81
81
  # via virtualenv
82
- pluggy==1.3.0
82
+ pluggy==1.4.0
83
83
  # via pytest
84
84
  ply==3.11
85
85
  # via
86
86
  # jsonpath-ng
87
87
  # libhxl
88
- pre-commit==3.6.0
88
+ pre-commit==3.6.2
89
89
  # via hdx-python-country (pyproject.toml)
90
- pydantic==2.5.3
90
+ pydantic==2.6.3
91
91
  # via frictionless
92
- pydantic-core==2.14.6
92
+ pydantic-core==2.16.3
93
93
  # via pydantic
94
94
  pygments==2.17.2
95
95
  # via rich
@@ -97,7 +97,7 @@ pyphonetics==0.5.3
97
97
  # via hdx-python-country (pyproject.toml)
98
98
  pyrsistent==0.20.0
99
99
  # via jsonschema
100
- pytest==7.4.4
100
+ pytest==8.1.0
101
101
  # via
102
102
  # hdx-python-country (pyproject.toml)
103
103
  # pytest-cov
@@ -110,7 +110,7 @@ python-dateutil==2.8.2
110
110
  # libhxl
111
111
  python-io-wrapper==0.3.1
112
112
  # via libhxl
113
- python-slugify==8.0.1
113
+ python-slugify==8.0.4
114
114
  # via frictionless
115
115
  pyyaml==6.0.1
116
116
  # via
@@ -124,13 +124,13 @@ requests==2.31.0
124
124
  # frictionless
125
125
  # libhxl
126
126
  # requests-file
127
- requests-file==1.5.1
127
+ requests-file==2.0.0
128
128
  # via hdx-python-utilities
129
129
  rfc3986==2.0.0
130
130
  # via frictionless
131
- rich==13.7.0
131
+ rich==13.7.1
132
132
  # via typer
133
- ruamel-yaml==0.18.5
133
+ ruamel-yaml==0.18.6
134
134
  # via hdx-python-utilities
135
135
  ruamel-yaml-clib==0.2.8
136
136
  # via ruamel-yaml
@@ -142,7 +142,6 @@ six==1.16.0
142
142
  # via
143
143
  # isodate
144
144
  # python-dateutil
145
- # requests-file
146
145
  stringcase==1.2.0
147
146
  # via frictionless
148
147
  structlog==24.1.0
@@ -155,23 +154,23 @@ text-unidecode==1.3
155
154
  # via python-slugify
156
155
  typer[all]==0.9.0
157
156
  # via frictionless
158
- typing-extensions==4.9.0
157
+ typing-extensions==4.10.0
159
158
  # via
160
159
  # frictionless
161
160
  # pydantic
162
161
  # pydantic-core
163
162
  # typer
164
- unidecode==1.3.7
163
+ unidecode==1.3.8
165
164
  # via
166
165
  # libhxl
167
166
  # pyphonetics
168
- urllib3==2.1.0
167
+ urllib3==2.2.1
169
168
  # via
170
169
  # libhxl
171
170
  # requests
172
171
  validators==0.22.0
173
172
  # via frictionless
174
- virtualenv==20.25.0
173
+ virtualenv==20.25.1
175
174
  # via pre-commit
176
175
  wheel==0.42.0
177
176
  # via libhxl
@@ -179,7 +178,7 @@ xlrd==2.0.1
179
178
  # via hdx-python-utilities
180
179
  xlrd3==1.1.0
181
180
  # via libhxl
182
- xlsxwriter==3.1.9
181
+ xlsxwriter==3.2.0
183
182
  # via tableschema-to-template
184
183
  xlwt==1.3.0
185
184
  # via hdx-python-utilities
@@ -0,0 +1,17 @@
1
+ from datetime import datetime
2
+
3
+ from ._version import version as __version__ # noqa: F401
4
+ from hdx.utilities.dateparse import get_timestamp_from_datetime
5
+
6
+
7
+ def get_int_timestamp(date: datetime) -> int:
8
+ """
9
+ Get integer timestamp from datetime object
10
+
11
+ Args:
12
+ date (datetime): datetime object
13
+
14
+ Returns:
15
+ int: Integer timestamp
16
+ """
17
+ return int(round(get_timestamp_from_datetime(date)))
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '3.6.4'
16
- __version_tuple__ = version_tuple = (3, 6, 4)
15
+ __version__ = version = '3.6.6'
16
+ __version_tuple__ = version_tuple = (3, 6, 6)
@@ -58,7 +58,7 @@ class AdminLevel:
58
58
  "admin_name_replacements", {}
59
59
  )
60
60
  self.admin_fuzzy_dont = admin_config.get("admin_fuzzy_dont", list())
61
- self.pcodes = list()
61
+ self.pcodes = []
62
62
  self.pcode_lengths = {}
63
63
  self.name_to_pcode = {}
64
64
  self.pcode_to_name = {}
@@ -634,7 +634,7 @@ class AdminLevel:
634
634
  Returns:
635
635
  List[str]: List of matches
636
636
  """
637
- output = list()
637
+ output = []
638
638
  for match in sorted(self.matches):
639
639
  line = f"{match[0]} - {match[1]}: Matching ({match[4]}) {match[2]} to {match[3]} on map"
640
640
  logger.info(line)
@@ -647,7 +647,7 @@ class AdminLevel:
647
647
  Returns:
648
648
  List[str]: List of ignored
649
649
  """
650
- output = list()
650
+ output = []
651
651
  for ignored in sorted(self.ignored):
652
652
  if len(ignored) == 2:
653
653
  line = f"{ignored[0]} - Ignored {ignored[1]}!"
@@ -663,7 +663,7 @@ class AdminLevel:
663
663
  Returns:
664
664
  List[str]: List of errors
665
665
  """
666
- output = list()
666
+ output = []
667
667
  for error in sorted(self.errors):
668
668
  if len(error) == 2:
669
669
  line = f"{error[0]} - Could not find {error[1]} in map names!"
@@ -1,4 +1,5 @@
1
1
  """Country location"""
2
+
2
3
  import copy
3
4
  import logging
4
5
  import re
@@ -69,8 +70,8 @@ class Country:
69
70
  _countriesdata = None
70
71
  _ochaurl_default = "https://docs.google.com/spreadsheets/d/1NjSI2LaS3SqbgYc0HdD8oIb7lofGtiHgoKKATCpwVdY/export?format=csv&gid=1088874596"
71
72
  _ochaurl = _ochaurl_default
72
- _country_name_overrides = dict()
73
- _country_name_mappings = dict()
73
+ _country_name_overrides = {}
74
+ _country_name_mappings = {}
74
75
 
75
76
  @classmethod
76
77
  def _add_countriesdata(cls, iso3: str, hxlcountry: hxl.Row) -> Dict:
@@ -131,24 +132,24 @@ class Country:
131
132
  if regionname:
132
133
  add_country_to_set("regioncodes2countries", regionid, iso3)
133
134
  cls._countriesdata["regioncodes2names"][regionid] = regionname
134
- cls._countriesdata["regionnames2codes"][
135
- regionname.upper()
136
- ] = regionid
135
+ cls._countriesdata["regionnames2codes"][regionname.upper()] = (
136
+ regionid
137
+ )
137
138
  if sub_regionname:
138
139
  add_country_to_set("regioncodes2countries", sub_regionid, iso3)
139
- cls._countriesdata["regioncodes2names"][
140
+ cls._countriesdata["regioncodes2names"][sub_regionid] = (
141
+ sub_regionname
142
+ )
143
+ cls._countriesdata["regionnames2codes"][sub_regionname.upper()] = (
140
144
  sub_regionid
141
- ] = sub_regionname
142
- cls._countriesdata["regionnames2codes"][
143
- sub_regionname.upper()
144
- ] = sub_regionid
145
+ )
145
146
  if intermediate_regionname:
146
147
  add_country_to_set(
147
148
  "regioncodes2countries", intermediate_regionid, iso3
148
149
  )
149
- cls._countriesdata["regioncodes2names"][
150
- intermediate_regionid
151
- ] = intermediate_regionname
150
+ cls._countriesdata["regioncodes2names"][intermediate_regionid] = (
151
+ intermediate_regionname
152
+ )
152
153
  cls._countriesdata["regionnames2codes"][
153
154
  intermediate_regionname.upper()
154
155
  ] = intermediate_regionid
@@ -167,21 +168,21 @@ class Country:
167
168
  Returns:
168
169
  None
169
170
  """
170
- cls._countriesdata = dict()
171
- cls._countriesdata["countries"] = dict()
172
- cls._countriesdata["iso2iso3"] = dict()
173
- cls._countriesdata["m49iso3"] = dict()
174
- cls._countriesdata["countrynames2iso3"] = dict()
175
- cls._countriesdata["regioncodes2countries"] = dict()
176
- cls._countriesdata["regioncodes2names"] = dict()
177
- cls._countriesdata["regionnames2codes"] = dict()
178
- cls._countriesdata["aliases"] = dict()
179
- cls._countriesdata["currencies"] = dict()
171
+ cls._countriesdata = {}
172
+ cls._countriesdata["countries"] = {}
173
+ cls._countriesdata["iso2iso3"] = {}
174
+ cls._countriesdata["m49iso3"] = {}
175
+ cls._countriesdata["countrynames2iso3"] = {}
176
+ cls._countriesdata["regioncodes2countries"] = {}
177
+ cls._countriesdata["regioncodes2names"] = {}
178
+ cls._countriesdata["regionnames2codes"] = {}
179
+ cls._countriesdata["aliases"] = {}
180
+ cls._countriesdata["currencies"] = {}
180
181
 
181
182
  for key, value in cls._country_name_mappings.items():
182
- cls._countriesdata["countrynames2iso3"][
183
- key.upper()
184
- ] = value.upper()
183
+ cls._countriesdata["countrynames2iso3"][key.upper()] = (
184
+ value.upper()
185
+ )
185
186
 
186
187
  for country in countries:
187
188
  iso3 = country.get("#country+code+v_iso3")
@@ -891,4 +892,4 @@ class Country:
891
892
 
892
893
  if exception is not None:
893
894
  raise exception
894
- return list()
895
+ return []
@@ -1,10 +1,12 @@
1
1
  """Currency conversion"""
2
+
2
3
  import logging
4
+ from copy import copy
3
5
  from datetime import datetime, timezone
4
6
  from typing import Dict, Optional, Union
5
7
 
8
+ from . import get_int_timestamp
6
9
  from hdx.utilities.dateparse import (
7
- get_timestamp_from_datetime,
8
10
  now_utc,
9
11
  parse_date,
10
12
  )
@@ -27,7 +29,7 @@ class Currency:
27
29
  """
28
30
 
29
31
  _primary_rates_url = "https://query2.finance.yahoo.com/v8/finance/chart/{currency}=X?period1={date}&period2={date}&interval=1d&events=div%2Csplit&formatted=false&lang=en-US&region=US&corsDomain=finance.yahoo.com"
30
- _secondary_rates_url = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/usd.min.json"
32
+ _secondary_rates_url = "https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/usd.min.json"
31
33
  _secondary_historic_url = (
32
34
  "https://codeforiati.org/imf-exchangerates/imf_exchangerates.csv"
33
35
  )
@@ -44,19 +46,6 @@ class Currency:
44
46
  _fixed_now = None
45
47
  _threshold = 1.3
46
48
 
47
- @classmethod
48
- def _get_int_timestamp(cls, date: datetime) -> int:
49
- """
50
- Get integer timestamp from datetime object
51
-
52
- Args:
53
- date (datetime): datetime object
54
-
55
- Returns:
56
- int: Integer timestamp
57
- """
58
- return int(round(get_timestamp_from_datetime(date)))
59
-
60
49
  @classmethod
61
50
  def setup(
62
51
  cls,
@@ -69,6 +58,8 @@ class Currency:
69
58
  no_historic: bool = False,
70
59
  fixed_now: Optional[datetime] = None,
71
60
  log_level: int = logging.DEBUG,
61
+ current_rates_cache: Dict = {"USD": 1},
62
+ historic_rates_cache: Dict = {},
72
63
  ) -> None:
73
64
  """
74
65
  Setup the sources. If you wish to use a static fallback file by setting
@@ -85,13 +76,15 @@ class Currency:
85
76
  no_historic (bool): Do not set up historic rates. Defaults to False.
86
77
  fixed_now (Optional[datetime]): Use a fixed datetime for now. Defaults to None (use datetime.now()).
87
78
  log_level (int): Level at which to log messages. Defaults to logging.DEBUG.
79
+ current_rates_cache (Dict): Pre-populate current rates cache with given values. Defaults to {"USD": 1}.
80
+ historic_rates_cache (Dict): Pre-populate historic rates cache with given values. Defaults to {}.
88
81
 
89
82
  Returns:
90
83
  None
91
84
  """
92
85
 
93
- cls._cached_current_rates = {"USD": 1}
94
- cls._cached_historic_rates = dict()
86
+ cls._cached_current_rates = copy(current_rates_cache)
87
+ cls._cached_historic_rates = copy(historic_rates_cache)
95
88
  cls._rates_api = primary_rates_url
96
89
  cls._secondary_rates = None
97
90
  cls._secondary_historic = None
@@ -131,10 +124,10 @@ class Currency:
131
124
  filename="historic_rates.csv",
132
125
  logstr="secondary historic exchange rates",
133
126
  )
134
- cls._secondary_historic = dict()
127
+ cls._secondary_historic = {}
135
128
  for row in iterator:
136
129
  currency = row["Currency"]
137
- date = cls._get_int_timestamp(parse_date(row["Date"]))
130
+ date = get_int_timestamp(parse_date(row["Date"]))
138
131
  rate = float(row["Rate"])
139
132
  dict_of_dicts_add(
140
133
  cls._secondary_historic, currency, date, rate
@@ -281,7 +274,7 @@ class Currency:
281
274
  else:
282
275
  now = now_utc()
283
276
  get_close = False
284
- timestamp = cls._get_int_timestamp(now)
277
+ timestamp = get_int_timestamp(now)
285
278
  else:
286
279
  get_close = True
287
280
  data = cls._get_primary_rates_data(currency, timestamp)
@@ -479,7 +472,7 @@ class Currency:
479
472
  )
480
473
  else:
481
474
  date = date.astimezone(timezone.utc)
482
- timestamp = cls._get_int_timestamp(date)
475
+ timestamp = get_int_timestamp(date)
483
476
  if currency_data is not None:
484
477
  fx_rate = currency_data.get(timestamp)
485
478
  if fx_rate is not None:
@@ -3,7 +3,7 @@ import unicodedata
3
3
 
4
4
  from unidecode import unidecode
5
5
 
6
- non_ascii = "([^\x00-\x7F])+"
6
+ non_ascii = "([^\x00-\x7f])+"
7
7
 
8
8
 
9
9
  def clean_name(name: str) -> str:
@@ -11,7 +11,7 @@ class Phonetics(pyphonetics.RefinedSoundex):
11
11
  possible_names: ListTuple,
12
12
  name: str,
13
13
  alternative_name: Optional[str] = None,
14
- transform_possible_names: ListTuple[Callable] = list(),
14
+ transform_possible_names: ListTuple[Callable] = [],
15
15
  threshold: int = 2,
16
16
  ) -> Optional[int]:
17
17
  """
@@ -0,0 +1,102 @@
1
+ import logging
2
+ from datetime import timezone
3
+ from typing import Dict, List
4
+
5
+ from . import get_int_timestamp
6
+ from hdx.utilities.typehint import ListTuple
7
+
8
+ try:
9
+ from data_bridges_client import ApiClient, Configuration, CurrencyApi
10
+ from data_bridges_client.exceptions import ApiException
11
+ from data_bridges_client.token import WfpApiToken
12
+ except ImportError:
13
+ WfpApiToken = None
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class WFPExchangeRates:
19
+ """Obtain WFP official exchange rates. Requires WFP credentials amd
20
+ installation of the WFP extra eg. `pip install hdx-python-country[wfp]`
21
+
22
+ Args:
23
+ key: WFP API key
24
+ secret: WFP API secret
25
+ """
26
+
27
+ def __init__(self, key: str, secret: str):
28
+ # Configure OAuth2 access token for authorization: default
29
+ self.token = WfpApiToken(api_key=key, api_secret=secret)
30
+ configuration = Configuration()
31
+ configuration.access_token = self.token.refresh()
32
+ api_client = ApiClient(configuration)
33
+ self.api_instance = CurrencyApi(api_client)
34
+
35
+ def get_currencies(self) -> List[str]:
36
+ """Get list of currencies in WFP API
37
+
38
+ Returns:
39
+ List[str]: List of currencies in WFP API
40
+ """
41
+ currencies = []
42
+ for currencydto in self.api_instance.currency_list_get().items:
43
+ currencies.append(currencydto.name)
44
+ return currencies
45
+
46
+ def get_currency_historic_rates(self, currency: str) -> Dict[int, float]:
47
+ """Get historic rates for currency from WFP API
48
+
49
+ Args:
50
+ currency (str): Currency
51
+
52
+ Returns:
53
+ Dict[int, float]: Mapping from timestamp to rate
54
+ """
55
+ historic_rates = {}
56
+ page = 1
57
+ while True:
58
+ try:
59
+ usdquotations = (
60
+ self.api_instance.currency_usd_indirect_quotation_get(
61
+ currency_name=currency, page=page
62
+ ).items
63
+ )
64
+ except ApiException as ex:
65
+ if ex.status not in (104, 401, 403):
66
+ raise
67
+ self.token.refresh()
68
+ usdquotations = (
69
+ self.api_instance.currency_usd_indirect_quotation_get(
70
+ currency_name=currency, page=page
71
+ ).items
72
+ )
73
+ if not usdquotations:
74
+ break
75
+ for usdquotation in usdquotations:
76
+ if not usdquotation.is_official:
77
+ continue
78
+ date = usdquotation.var_date.replace(tzinfo=timezone.utc)
79
+ timestamp = get_int_timestamp(date)
80
+ historic_rates[timestamp] = usdquotation.value
81
+ page = page + 1
82
+ return historic_rates
83
+
84
+ def get_historic_rates(
85
+ self, currencies: ListTuple[str]
86
+ ) -> Dict[str, Dict]:
87
+ """Get historic rates for a list of currencies from WFP API
88
+
89
+ Args:
90
+ currencies (List[str]): List of currencies
91
+
92
+ Returns:
93
+ Dict[str, Dict]: Mapping from currency to mapping from timestamp to rate
94
+ """
95
+ historic_rates = {}
96
+ for currency in currencies:
97
+ logger.info(f"Getting WFP historic rates for {currency}")
98
+ currency_historic_rates = self.get_currency_historic_rates(
99
+ currency
100
+ )
101
+ historic_rates[currency.upper()] = currency_historic_rates
102
+ return historic_rates
@@ -1,4 +1,5 @@
1
1
  """location Tests"""
2
+
2
3
  from os.path import join
3
4
 
4
5
  import pytest
@@ -1,4 +1,5 @@
1
1
  """location Tests"""
2
+
2
3
  import hxl
3
4
  import pytest
4
5
  from hxl import InputOptions
@@ -1,8 +1,10 @@
1
1
  """Currency Tests"""
2
+
2
3
  from os.path import join
3
4
 
4
5
  import pytest
5
6
 
7
+ from hdx.location import get_int_timestamp
6
8
  from hdx.location.currency import Currency, CurrencyError
7
9
  from hdx.utilities.dateparse import parse_date
8
10
  from hdx.utilities.downloader import Download
@@ -333,7 +335,7 @@ class TestCurrency:
333
335
  }
334
336
  ],
335
337
  }
336
- timestamp = Currency._get_int_timestamp(parse_date("2017-02-15"))
338
+ timestamp = get_int_timestamp(parse_date("2017-02-15"))
337
339
  assert Currency._get_adjclose(indicators, "NGN", timestamp) == 314.5
338
340
  indicators = {
339
341
  "adjclose": [{"adjclose": [33.13999938964844]}],
@@ -347,7 +349,7 @@ class TestCurrency:
347
349
  }
348
350
  ],
349
351
  }
350
- timestamp = Currency._get_int_timestamp(parse_date("2015-12-15"))
352
+ timestamp = get_int_timestamp(parse_date("2015-12-15"))
351
353
  assert Currency._get_adjclose(indicators, "COP", timestamp) is None
352
354
  indicators = {
353
355
  "adjclose": [{"adjclose": [605.5509643554688]}],
@@ -361,7 +363,7 @@ class TestCurrency:
361
363
  }
362
364
  ],
363
365
  }
364
- timestamp = Currency._get_int_timestamp(parse_date("2022-04-14"))
366
+ timestamp = get_int_timestamp(parse_date("2022-04-14"))
365
367
  assert (
366
368
  Currency._get_adjclose(indicators, "XAF", timestamp)
367
369
  == 605.5509643554688
@@ -378,7 +380,7 @@ class TestCurrency:
378
380
  }
379
381
  ],
380
382
  }
381
- timestamp = Currency._get_int_timestamp(parse_date("2022-04-15"))
383
+ timestamp = get_int_timestamp(parse_date("2022-04-15"))
382
384
  assert (
383
385
  Currency._get_adjclose(indicators, "XAF", timestamp)
384
386
  == 601.632568359375
@@ -395,7 +397,7 @@ class TestCurrency:
395
397
  }
396
398
  ],
397
399
  }
398
- timestamp = Currency._get_int_timestamp(parse_date("2017-02-15"))
400
+ timestamp = get_int_timestamp(parse_date("2017-02-15"))
399
401
  assert Currency._get_adjclose(indicators, "XXX", timestamp) == 3.145
400
402
 
401
403
  Currency.setup(secondary_historic_url=secondary_historic_url)
@@ -411,7 +413,7 @@ class TestCurrency:
411
413
  }
412
414
  ],
413
415
  }
414
- timestamp = Currency._get_int_timestamp(parse_date("2017-02-15"))
416
+ timestamp = get_int_timestamp(parse_date("2017-02-15"))
415
417
  assert Currency._get_adjclose(indicators, "NGN", timestamp) == 314.5
416
418
  indicators = {
417
419
  "adjclose": [{"adjclose": [33.13999938964844]}],
@@ -425,7 +427,7 @@ class TestCurrency:
425
427
  }
426
428
  ],
427
429
  }
428
- timestamp = Currency._get_int_timestamp(parse_date("2015-12-15"))
430
+ timestamp = get_int_timestamp(parse_date("2015-12-15"))
429
431
  assert (
430
432
  Currency._get_adjclose(indicators, "COP", timestamp)
431
433
  == 3269.199951171875
@@ -442,7 +444,7 @@ class TestCurrency:
442
444
  }
443
445
  ],
444
446
  }
445
- timestamp = Currency._get_int_timestamp(parse_date("2022-04-14"))
447
+ timestamp = get_int_timestamp(parse_date("2022-04-14"))
446
448
  assert (
447
449
  Currency._get_adjclose(indicators, "XAF", timestamp)
448
450
  == 605.5509643554688
@@ -459,7 +461,7 @@ class TestCurrency:
459
461
  }
460
462
  ],
461
463
  }
462
- timestamp = Currency._get_int_timestamp(parse_date("2022-04-15"))
464
+ timestamp = get_int_timestamp(parse_date("2022-04-15"))
463
465
  assert (
464
466
  Currency._get_adjclose(indicators, "XAF", timestamp)
465
467
  == 601.632568359375
@@ -476,7 +478,7 @@ class TestCurrency:
476
478
  }
477
479
  ],
478
480
  }
479
- timestamp = Currency._get_int_timestamp(parse_date("2017-02-15"))
481
+ timestamp = get_int_timestamp(parse_date("2017-02-15"))
480
482
  assert Currency._get_adjclose(indicators, "XXX", timestamp) == 3.145
481
483
  indicators = {
482
484
  "adjclose": [{"adjclose": [33.13999938964844]}],
@@ -490,7 +492,7 @@ class TestCurrency:
490
492
  }
491
493
  ],
492
494
  }
493
- timestamp = Currency._get_int_timestamp(parse_date("2015-12-15"))
495
+ timestamp = get_int_timestamp(parse_date("2015-12-15"))
494
496
  assert Currency._get_adjclose(indicators, "COP", timestamp) == 3320.0
495
497
  indicators = {
496
498
  "adjclose": [{"adjclose": [33.13999938964844]}],
@@ -504,7 +506,7 @@ class TestCurrency:
504
506
  }
505
507
  ],
506
508
  }
507
- timestamp = Currency._get_int_timestamp(parse_date("2015-12-15"))
509
+ timestamp = get_int_timestamp(parse_date("2015-12-15"))
508
510
  assert (
509
511
  Currency._get_adjclose(indicators, "COP", timestamp)
510
512
  == 3313.999938964844
@@ -523,7 +525,7 @@ class TestCurrency:
523
525
  }
524
526
  ],
525
527
  }
526
- timestamp = Currency._get_int_timestamp(parse_date("2015-12-15"))
528
+ timestamp = get_int_timestamp(parse_date("2015-12-15"))
527
529
  assert (
528
530
  Currency._get_adjclose(indicators, "COP", timestamp)
529
531
  == 33.13999938964844
@@ -542,7 +544,7 @@ class TestCurrency:
542
544
  }
543
545
  ],
544
546
  }
545
- timestamp = Currency._get_int_timestamp(parse_date("2015-12-15"))
547
+ timestamp = get_int_timestamp(parse_date("2015-12-15"))
546
548
  assert (
547
549
  Currency._get_adjclose(indicators, "COP", timestamp)
548
550
  == 3124.504838709677
@@ -561,7 +563,7 @@ class TestCurrency:
561
563
  }
562
564
  ],
563
565
  }
564
- timestamp = Currency._get_int_timestamp(parse_date("2015-12-15"))
566
+ timestamp = get_int_timestamp(parse_date("2015-12-15"))
565
567
  assert Currency._get_adjclose(indicators, "COP", timestamp) == 3270
566
568
 
567
569
  Currency._no_historic = True
@@ -0,0 +1,42 @@
1
+ from os import environ
2
+
3
+ import pytest
4
+
5
+ from hdx.location import get_int_timestamp
6
+ from hdx.location.currency import Currency
7
+ from hdx.location.wfp_exchangerates import WfpApiToken, WFPExchangeRates
8
+ from hdx.utilities.dateparse import parse_date
9
+
10
+ if WfpApiToken is not None:
11
+
12
+ class TestWFPExchangeRates:
13
+ @pytest.fixture(scope="class")
14
+ def wfp_fx(self):
15
+ key = environ.get("WFP_KEY")
16
+ secret = environ.get("WFP_SECRET")
17
+ return WFPExchangeRates(key, secret)
18
+
19
+ @pytest.fixture(scope="class")
20
+ def currency(self):
21
+ return "afn"
22
+
23
+ @pytest.fixture(scope="class")
24
+ def date(self):
25
+ return parse_date("2020-02-20")
26
+
27
+ def test_get_currencies(self, wfp_fx):
28
+ currencies = wfp_fx.get_currencies()
29
+ assert len(currencies) == 126
30
+
31
+ def test_get_historic_rates(self, wfp_fx, currency, date):
32
+ assert (
33
+ Currency.get_historic_rate(currency, date) == 76.80000305175781
34
+ )
35
+ timestamp = get_int_timestamp(date)
36
+ historic_rates = wfp_fx.get_currency_historic_rates(currency)
37
+ assert historic_rates[timestamp] == 77.01
38
+
39
+ def test_get_all_historic_rates(self, wfp_fx, currency, date):
40
+ all_historic_rates = wfp_fx.get_historic_rates([currency])
41
+ Currency.setup(historic_rates_cache=all_historic_rates)
42
+ assert Currency.get_historic_rate(currency, date) == 77.01
@@ -1,2 +0,0 @@
1
- [tool.black]
2
- line-length = 79
@@ -1 +0,0 @@
1
- from ._version import version as __version__ # noqa: F401