hdx-python-country 3.7.1__tar.gz → 3.7.2__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 (37) hide show
  1. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/PKG-INFO +1 -1
  2. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/documentation/main.md +13 -4
  3. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/_version.py +2 -2
  4. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/adminlevel.py +103 -17
  5. hdx_python_country-3.7.2/tests/fixtures/adminlevelparent.yaml +25 -0
  6. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/hdx/location/test_adminlevel.py +138 -3
  7. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/.config/coveragerc +0 -0
  8. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/.config/pre-commit-config.yaml +0 -0
  9. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/.config/pytest.ini +0 -0
  10. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/.config/ruff.toml +0 -0
  11. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/.github/workflows/publish.yaml +0 -0
  12. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/.github/workflows/run-python-tests.yaml +0 -0
  13. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/.gitignore +0 -0
  14. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/CONTRIBUTING.md +0 -0
  15. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/LICENSE +0 -0
  16. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/README.md +0 -0
  17. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/documentation/.readthedocs.yaml +0 -0
  18. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/documentation/pydoc-markdown.yaml +0 -0
  19. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/pyproject.toml +0 -0
  20. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/requirements.txt +0 -0
  21. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/Countries & Territories Taxonomy MVP - C&T Taxonomy with HXL Tags.csv +0 -0
  22. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/__init__.py +0 -0
  23. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/country.py +0 -0
  24. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/currency.py +0 -0
  25. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/names.py +0 -0
  26. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/phonetics.py +0 -0
  27. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/src/hdx/location/wfp_exchangerates.py +0 -0
  28. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/fixtures/adminlevel.yaml +0 -0
  29. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/fixtures/global_pcode_lengths.csv +0 -0
  30. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/fixtures/global_pcodes_adm_1_2.csv +0 -0
  31. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/fixtures/secondary_historic_rates.csv +0 -0
  32. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/fixtures/secondary_rates.json +0 -0
  33. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/hdx/location/Countries_UZB_Deleted.csv +0 -0
  34. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/hdx/location/__init__.py +0 -0
  35. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/hdx/location/test_country.py +0 -0
  36. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/hdx/location/test_currency.py +0 -0
  37. {hdx_python_country-3.7.1 → hdx_python_country-3.7.2}/tests/hdx/location/test_wfp_exchangerates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: hdx-python-country
3
- Version: 3.7.1
3
+ Version: 3.7.2
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
@@ -113,10 +113,19 @@ The administration level mappings takes input configuration dictionary,
113
113
 
114
114
  *admin_config* can have the following optional keys:
115
115
 
116
- *countries_fuzzy_try* are countries (iso3 codes) for which to try fuzzy matching. Default is all countries.
117
- *admin_name_mappings* is a dictionary of mappings from name to pcode (for where fuzzy matching fails)
118
- *admin_name_replacements* is a dictionary of textual replacements to try when fuzzy matching
119
- *admin_fuzzy_dont* is a list of names for which fuzzy matching should not be tried
116
+ *countries_fuzzy_try* are countries (iso3 codes) for which to try fuzzy
117
+ matching. Default is all countries.
118
+ *admin_name_mappings* is a dictionary of mappings from name to pcode. These can
119
+ be global or they can be restricted by country or parent (if the AdminLevel
120
+ object has been set up with parents). Keys take the form "MAPPING",
121
+ "AFG|MAPPING" or "AF01|MAPPING".
122
+ *admin_name_replacements* is a dictionary of textual replacements to try when
123
+ fuzzy matching. It maps from string to string replacement. The replacements can
124
+ be global or they can be restricted by country or parent (if the AdminLevel
125
+ object has been set up with parents). Keys take the form "STRING_TO_REPLACE",
126
+ "AFG|STRING_TO_REPLACE" or "AF01|STRING_TO_REPLACE".
127
+ *admin_fuzzy_dont* is a list of names for which fuzzy matching should not be
128
+ tried
120
129
 
121
130
  Once an AdminLevel object is constructed, one of three setup methods must be
122
131
  called: *setup_from_admin_info*, *setup_from_libhxl_dataset* or
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '3.7.1'
16
- __version_tuple__ = version_tuple = (3, 7, 1)
15
+ __version__ = version = '3.7.2'
16
+ __version_tuple__ = version_tuple = (3, 7, 2)
@@ -306,7 +306,7 @@ class AdminLevel:
306
306
  """Get admin level for country
307
307
 
308
308
  Args:
309
- countryiso3 (str): Iso3 country code
309
+ countryiso3 (str): ISO3 country code
310
310
 
311
311
  Returns:
312
312
  int: Admin level
@@ -320,7 +320,7 @@ class AdminLevel:
320
320
  """Get pcode length for country
321
321
 
322
322
  Args:
323
- countryiso3 (str): Iso3 country code
323
+ countryiso3 (str): ISO3 country code
324
324
 
325
325
  Returns:
326
326
  Optional[int]: Country's pcode length or None
@@ -517,6 +517,41 @@ class AdminLevel:
517
517
  return pcode
518
518
  return None
519
519
 
520
+ def get_admin_name_replacements(
521
+ self, countryiso3: str, parent: Optional[str]
522
+ ) -> Dict[str, str]:
523
+ """Get relevant admin name replacements from admin name replacements
524
+ which is a dictionary of mappings from string to string replacement.
525
+ These can be global or they can be restricted by
526
+ country or parent (if the AdminLevel object has been set up with
527
+ parents). Keys take the form "STRING_TO_REPLACE",
528
+ "AFG|STRING_TO_REPLACE" or "AF01|STRING_TO_REPLACE".
529
+
530
+ Args:
531
+ countryiso3 (str): ISO3 country code
532
+ parent (Optional[str]): Parent admin code
533
+
534
+ Returns:
535
+ Dict[str, str]: Relevant admin name replacements
536
+ """
537
+ relevant_name_replacements = {}
538
+ for key, value in self.admin_name_replacements.items():
539
+ if "|" not in key:
540
+ if key not in relevant_name_replacements:
541
+ relevant_name_replacements[key] = value
542
+ continue
543
+ prefix, name = key.split("|")
544
+ if parent:
545
+ if prefix == parent:
546
+ if name not in relevant_name_replacements:
547
+ relevant_name_replacements[name] = value
548
+ continue
549
+ if prefix == countryiso3:
550
+ if name not in relevant_name_replacements:
551
+ relevant_name_replacements[name] = value
552
+ continue
553
+ return relevant_name_replacements
554
+
520
555
  def fuzzy_pcode(
521
556
  self,
522
557
  countryiso3: str,
@@ -526,7 +561,7 @@ class AdminLevel:
526
561
  """Fuzzy match name to pcode
527
562
 
528
563
  Args:
529
- countryiso3 (str): Iso3 country code
564
+ countryiso3 (str): ISO3 country code
530
565
  name (str): Name to match
531
566
  **kwargs:
532
567
  parent (Optional[str]): Parent admin code
@@ -543,8 +578,17 @@ class AdminLevel:
543
578
  if logname:
544
579
  self.ignored.add((logname, countryiso3))
545
580
  return None
546
- if self.use_parent and "parent" in kwargs:
547
- parent = kwargs["parent"]
581
+ if self.use_parent:
582
+ parent = kwargs.get("parent")
583
+ else:
584
+ parent = None
585
+ if parent is None:
586
+ name_to_pcode = self.name_to_pcode.get(countryiso3)
587
+ if not name_to_pcode:
588
+ if logname:
589
+ self.errors.add((logname, countryiso3))
590
+ return None
591
+ else:
548
592
  name_parent_to_pcode = self.name_parent_to_pcode.get(countryiso3)
549
593
  if not name_parent_to_pcode:
550
594
  if logname:
@@ -555,15 +599,10 @@ class AdminLevel:
555
599
  if logname:
556
600
  self.errors.add((logname, countryiso3, parent))
557
601
  return None
558
- else:
559
- name_to_pcode = self.name_to_pcode.get(countryiso3)
560
- if not name_to_pcode:
561
- if logname:
562
- self.errors.add((logname, countryiso3))
563
- return None
564
602
  adm_name_lookup = clean_name(name)
565
603
  adm_name_lookup2 = multiple_replace(
566
- adm_name_lookup, self.admin_name_replacements
604
+ adm_name_lookup,
605
+ self.get_admin_name_replacements(countryiso3, parent),
567
606
  )
568
607
  pcode = name_to_pcode.get(
569
608
  adm_name_lookup, name_to_pcode.get(adm_name_lookup2)
@@ -643,6 +682,33 @@ class AdminLevel:
643
682
  )
644
683
  return pcode
645
684
 
685
+ def get_name_mapped_pcode(
686
+ self, countryiso3: str, name: str, parent: Optional[str]
687
+ ) -> Optional[str]:
688
+ """Get pcode from admin name mappings which is a dictionary of mappings
689
+ from name to pcode. These can be global or they can be restricted by
690
+ country or parent (if the AdminLevel object has been set up with
691
+ parents). Keys take the form "MAPPING", "AFG|MAPPING" or
692
+ "AF01|MAPPING".
693
+
694
+ Args:
695
+ countryiso3 (str): ISO3 country code
696
+ name (str): Name to match
697
+ parent (Optional[str]): Parent admin code
698
+
699
+ Returns:
700
+ Optional[str]: P code match from admin name mappings or None if no match
701
+ """
702
+ if parent:
703
+ pcode = self.admin_name_mappings.get(f"{parent}|{name}")
704
+ if pcode is None:
705
+ pcode = self.admin_name_mappings.get(f"{countryiso3}|{name}")
706
+ else:
707
+ pcode = self.admin_name_mappings.get(f"{countryiso3}|{name}")
708
+ if pcode is None:
709
+ pcode = self.admin_name_mappings.get(name)
710
+ return pcode
711
+
646
712
  def get_pcode(
647
713
  self,
648
714
  countryiso3: str,
@@ -653,7 +719,7 @@ class AdminLevel:
653
719
  """Get pcode for a given name
654
720
 
655
721
  Args:
656
- countryiso3 (str): Iso3 country code
722
+ countryiso3 (str): ISO3 country code
657
723
  name (str): Name to match
658
724
  fuzzy_match (bool): Whether to try fuzzy matching. Defaults to True.
659
725
  **kwargs:
@@ -663,9 +729,17 @@ class AdminLevel:
663
729
  Returns:
664
730
  Tuple[Optional[str], bool]: (Matched P code or None if no match, True if exact match or False if not)
665
731
  """
666
- pcode = self.admin_name_mappings.get(name)
732
+ if self.use_parent:
733
+ parent = kwargs.get("parent")
734
+ else:
735
+ parent = None
736
+ pcode = self.get_name_mapped_pcode(countryiso3, name, parent)
667
737
  if pcode and self.pcode_to_iso3[pcode] == countryiso3:
668
- return pcode, True
738
+ if parent:
739
+ if self.pcode_to_parent[pcode] == parent:
740
+ return pcode, True
741
+ else:
742
+ return pcode, True
669
743
  if self.looks_like_pcode(name):
670
744
  pcode = name.upper()
671
745
  if pcode in self.pcodes: # name is a p-code
@@ -677,8 +751,7 @@ class AdminLevel:
677
751
  )
678
752
  return pcode, True
679
753
  else:
680
- if self.use_parent and "parent" in kwargs:
681
- parent = kwargs["parent"]
754
+ if parent:
682
755
  name_parent_to_pcode = self.name_parent_to_pcode.get(
683
756
  countryiso3
684
757
  )
@@ -756,3 +829,16 @@ class AdminLevel:
756
829
  logger.info(line)
757
830
  output.append(line)
758
831
  return output
832
+
833
+ def output_admin_name_replacements(self) -> List[str]:
834
+ """Output log of name replacements
835
+
836
+ Returns:
837
+ List[str]: List of name replacements
838
+ """
839
+ output = []
840
+ for name, replacement in self.admin_name_replacements.items():
841
+ line = f"{name}: {replacement}"
842
+ logger.info(line)
843
+ output.append(line)
844
+ return output
@@ -0,0 +1,25 @@
1
+ admin_info_with_parent:
2
+ - {pcode: AF0101, name: Kabul, iso3: AFG, parent: AF01}
3
+ - {pcode: AF0102, name: Paghman, iso3: AFG, parent: AF01}
4
+ - {pcode: AF0201, name: Kabul, iso3: AFG, parent: AF02} # testing purposes
5
+ - {pcode: AF0301, name: Charikar, iso3: AFG, parent: AF03}
6
+ - {pcode: AF0401, name: Maydan Shahr, iso3: AFG, parent: AF04}
7
+ - {pcode: AF0501, name: Pul-e-Alam, iso3: AFG, parent: AF05}
8
+ - {pcode: AF0501, name: Pul-e-Alam, iso3: AFG, parent: AF05}
9
+ - {pcode: CD2013, name: Mbanza-Ngungu, iso3: COD, parent: CD20}
10
+ - {pcode: CD3102, name: Kenge, iso3: COD, parent: CD31}
11
+ - {pcode: MW305, name: Blantyre, iso3: MWI, parent: MW3}
12
+
13
+ admin_name_mappings:
14
+ "MyMapping": "AF0301"
15
+ "AFG|MyMapping2": "AF0401"
16
+ "AF05|MyMapping3": "AF0501"
17
+
18
+ admin_name_replacements:
19
+ " city": ""
20
+
21
+ alt1_admin_name_replacements:
22
+ "COD| city": ""
23
+
24
+ alt2_admin_name_replacements:
25
+ "CD20| city": ""
@@ -14,6 +14,10 @@ class TestAdminLevel:
14
14
  def config(self):
15
15
  return load_yaml(join("tests", "fixtures", "adminlevel.yaml"))
16
16
 
17
+ @pytest.fixture(scope="function")
18
+ def config_parent(self):
19
+ return load_yaml(join("tests", "fixtures", "adminlevelparent.yaml"))
20
+
17
21
  @pytest.fixture(scope="function")
18
22
  def url(self):
19
23
  return "https://raw.githubusercontent.com/OCHA-DAP/hdx-python-country/main/tests/fixtures/global_pcodes_adm_1_2.csv"
@@ -161,6 +165,20 @@ class TestAdminLevel:
161
165
  assert output[31] == "Juba Dhexe: Middle Juba (SO27)"
162
166
  assert output[61] == "CU Niamey: Niamey (NER008)"
163
167
 
168
+ output = adminone.output_admin_name_replacements()
169
+ assert output == [
170
+ " urban: ",
171
+ "sud: south",
172
+ "ouest: west",
173
+ "est: east",
174
+ "nord: north",
175
+ "': ",
176
+ "/: ",
177
+ ".: ",
178
+ " region: ",
179
+ " oblast: ",
180
+ ]
181
+
164
182
  def test_adminlevel_fuzzy(self, config):
165
183
  adminone = AdminLevel(config)
166
184
  adminone.setup_from_admin_info(config["admin_info"])
@@ -184,10 +202,10 @@ class TestAdminLevel:
184
202
  "test - YEM: Matching (fuzzy) Al Dhale'e / الضالع to Ad Dali on map",
185
203
  ]
186
204
 
187
- def test_adminlevel_parent(self, config):
188
- admintwo = AdminLevel(config)
205
+ def test_adminlevel_parent(self, config_parent):
206
+ admintwo = AdminLevel(config_parent)
189
207
  admintwo.countries_fuzzy_try = None
190
- admintwo.setup_from_admin_info(config["admin_info_with_parent"])
208
+ admintwo.setup_from_admin_info(config_parent["admin_info_with_parent"])
191
209
  assert admintwo.use_parent is True
192
210
  assert admintwo.pcode_to_parent["AF0101"] == "AF01"
193
211
  assert admintwo.get_pcode("AFG", "AF0101", logname="test") == (
@@ -223,6 +241,123 @@ class TestAdminLevel:
223
241
  "ABC", "Kabull", parent="AF02", logname="test"
224
242
  ) == (None, False)
225
243
 
244
+ output = admintwo.output_admin_name_mappings()
245
+ assert output == [
246
+ "MyMapping: Charikar (AF0301)",
247
+ "AFG|MyMapping2: Maydan Shahr (AF0401)",
248
+ "AF05|MyMapping3: Pul-e-Alam (AF0501)",
249
+ ]
250
+ assert admintwo.get_pcode("AFG", "MyMapping", logname="test") == (
251
+ "AF0301",
252
+ True,
253
+ )
254
+ assert admintwo.get_pcode(
255
+ "AFG", "MyMapping", parent="AF03", logname="test"
256
+ ) == ("AF0301", True)
257
+ assert admintwo.get_pcode(
258
+ "AFG", "MyMapping", parent="AF04", logname="test"
259
+ ) == (None, False)
260
+
261
+ assert admintwo.get_pcode("AFG", "MyMapping2", logname="test") == (
262
+ "AF0401",
263
+ True,
264
+ )
265
+ assert admintwo.get_pcode(
266
+ "AFG", "MyMapping2", parent="AF04", logname="test"
267
+ ) == ("AF0401", True)
268
+ assert admintwo.get_pcode(
269
+ "AFG", "MyMapping2", parent="AF05", logname="test"
270
+ ) == (None, False)
271
+
272
+ assert admintwo.get_pcode("AFG", "MyMapping3", logname="test") == (
273
+ None,
274
+ False,
275
+ )
276
+ assert admintwo.get_pcode(
277
+ "AFG", "MyMapping3", parent="AF05", logname="test"
278
+ ) == ("AF0501", True)
279
+ assert admintwo.get_pcode(
280
+ "AFG", "MyMapping3", parent="AF04", logname="test"
281
+ ) == (None, False)
282
+
283
+ output = admintwo.output_admin_name_replacements()
284
+ assert output == [" city: "]
285
+ assert admintwo.get_pcode(
286
+ "COD", "Mbanza-Ngungu city", logname="test"
287
+ ) == ("CD2013", False)
288
+ assert admintwo.get_pcode(
289
+ "COD", "Mbanza-Ngungu city", parent="CD20", logname="test"
290
+ ) == ("CD2013", False)
291
+ assert admintwo.get_pcode(
292
+ "COD", "Mbanza-Ngungu city", parent="CD19", logname="test"
293
+ ) == (None, False)
294
+ assert admintwo.get_pcode("COD", "Kenge city", logname="test") == (
295
+ "CD3102",
296
+ False,
297
+ )
298
+ assert admintwo.get_pcode("MWI", "Blantyre city", logname="test") == (
299
+ "MW305",
300
+ False,
301
+ )
302
+
303
+ admintwo.admin_name_replacements = config_parent[
304
+ "alt1_admin_name_replacements"
305
+ ]
306
+ output = admintwo.output_admin_name_replacements()
307
+ assert output == ["COD| city: "]
308
+ assert admintwo.get_pcode(
309
+ "COD", "Mbanza-Ngungu city", logname="test"
310
+ ) == ("CD2013", False)
311
+ assert admintwo.get_pcode(
312
+ "COD", "Mbanza-Ngungu city", parent="CD20", logname="test"
313
+ ) == ("CD2013", False)
314
+ assert admintwo.get_pcode(
315
+ "COD", "Mbanza-Ngungu city", parent="CD19", logname="test"
316
+ ) == (None, False)
317
+ assert admintwo.get_pcode("COD", "Kenge city", logname="test") == (
318
+ "CD3102",
319
+ False,
320
+ )
321
+ assert admintwo.get_pcode(
322
+ "COD", "Kenge city", parent="CD31", logname="test"
323
+ ) == ("CD3102", False)
324
+ assert admintwo.get_pcode("MWI", "Blantyre city", logname="test") == (
325
+ None,
326
+ False,
327
+ )
328
+ assert admintwo.get_pcode(
329
+ "MWI", "Blantyre city", parent="MW3", logname="test"
330
+ ) == (None, False)
331
+
332
+ admintwo.admin_name_replacements = config_parent[
333
+ "alt2_admin_name_replacements"
334
+ ]
335
+ output = admintwo.output_admin_name_replacements()
336
+ assert output == ["CD20| city: "]
337
+ assert admintwo.get_pcode(
338
+ "COD", "Mbanza-Ngungu city", logname="test"
339
+ ) == (None, False)
340
+ assert admintwo.get_pcode(
341
+ "COD", "Mbanza-Ngungu city", parent="CD20", logname="test"
342
+ ) == ("CD2013", False)
343
+ assert admintwo.get_pcode(
344
+ "COD", "Mbanza-Ngungu city", parent="CD19", logname="test"
345
+ ) == (None, False)
346
+ assert admintwo.get_pcode("COD", "Kenge city", logname="test") == (
347
+ None,
348
+ False,
349
+ )
350
+ assert admintwo.get_pcode(
351
+ "COD", "Kenge city", parent="CD31", logname="test"
352
+ ) == (None, False)
353
+ assert admintwo.get_pcode("MWI", "Blantyre city", logname="test") == (
354
+ None,
355
+ False,
356
+ )
357
+ assert admintwo.get_pcode(
358
+ "MWI", "Blantyre city", parent="MW3", logname="test"
359
+ ) == (None, False)
360
+
226
361
  def test_adminlevel_with_url(self, config, url):
227
362
  adminone = AdminLevel(config)
228
363
  with pytest.raises(HXLIOException):