hdx-python-utilities 4.0.6__tar.gz → 4.0.8__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 (138) hide show
  1. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/PKG-INFO +1 -1
  2. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/documentation/index.md +3 -0
  3. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/_version.py +2 -2
  4. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/saver.py +9 -3
  5. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/text.py +62 -0
  6. hdx_python_utilities-4.0.8/tests/fixtures/text/text1.txt +1 -0
  7. hdx_python_utilities-4.0.8/tests/fixtures/text/text2.txt +1 -0
  8. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/conftest.py +8 -0
  9. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_downloader.py +0 -7
  10. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_retriever.py +0 -7
  11. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_saver.py +54 -1
  12. hdx_python_utilities-4.0.8/tests/hdx/utilities/test_text.py +199 -0
  13. hdx_python_utilities-4.0.6/tests/hdx/utilities/test_text.py +0 -115
  14. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/.github/workflows/publish-test.yaml +0 -0
  15. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/.github/workflows/publish.yaml +0 -0
  16. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/.github/workflows/run-python-tests.yaml +0 -0
  17. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/.gitignore +0 -0
  18. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/.pre-commit-config.yaml +0 -0
  19. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/LICENSE +0 -0
  20. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/README.md +0 -0
  21. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/documentation/.readthedocs.yaml +0 -0
  22. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/documentation/mkdocs.yaml +0 -0
  23. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/pyproject.toml +0 -0
  24. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/__init__.py +0 -0
  25. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/base_downloader.py +0 -0
  26. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/compare.py +0 -0
  27. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/dateparse.py +0 -0
  28. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/dictandlist.py +0 -0
  29. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/downloader.py +0 -0
  30. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/easy_logging.py +0 -0
  31. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/email.py +0 -0
  32. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/encoding.py +0 -0
  33. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/error_handler.py +0 -0
  34. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/errors_onexit.py +0 -0
  35. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/file_hashing.py +0 -0
  36. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/frictionless_wrapper.py +0 -0
  37. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/html.py +0 -0
  38. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/loader.py +0 -0
  39. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/matching.py +0 -0
  40. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/path.py +0 -0
  41. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/retriever.py +0 -0
  42. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/session.py +0 -0
  43. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/state.py +0 -0
  44. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/url.py +0 -0
  45. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/useragent.py +0 -0
  46. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/uuid.py +0 -0
  47. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/src/hdx/utilities/zip_crc.py +0 -0
  48. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/compare/test_csv_processing.csv +0 -0
  49. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/compare/test_csv_processing2.csv +0 -0
  50. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/empty.yaml +0 -0
  51. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/hdx_config.json +0 -0
  52. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/hdx_config.yaml +0 -0
  53. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/hdx_email_configuration.json +0 -0
  54. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
  55. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/json_csv.yaml +0 -0
  56. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/logging_config.json +0 -0
  57. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/logging_config.yaml +0 -0
  58. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/project_configuration.json +0 -0
  59. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/project_configuration.yaml +0 -0
  60. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/smtp_config.json +0 -0
  61. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/smtp_config.yaml +0 -0
  62. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/user_agent_config.yaml +0 -0
  63. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/user_agent_config2.yaml +0 -0
  64. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/user_agent_config3.yaml +0 -0
  65. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
  66. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/basicauth.txt +0 -0
  67. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/bearertoken.txt +0 -0
  68. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/extra_params.json +0 -0
  69. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/extra_params.yaml +0 -0
  70. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/extra_params_tree.yaml +0 -0
  71. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_csv_processing.csv +0 -0
  72. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_csv_processing_blanks.csv +0 -0
  73. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_data.csv +0 -0
  74. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_data.xlsx +0 -0
  75. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_data1.csv/empty.txt +0 -0
  76. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_data2.csv +0 -0
  77. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_json_processing.json +0 -0
  78. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_xls_processing.xls +0 -0
  79. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/downloader/test_xlsx_processing.xlsx +0 -0
  80. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/file_hashing/bad_header.zip +0 -0
  81. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/file_hashing/bad_index.xlsx +0 -0
  82. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/file_hashing/empty.zip +0 -0
  83. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/file_hashing/test.xlsx +0 -0
  84. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/file_hashing/test_shapefile.zip +0 -0
  85. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/file_hashing/valid_sig_invalid_body.zip +0 -0
  86. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/html/response.html +0 -0
  87. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/loader/empty.json +0 -0
  88. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/loader/empty.yaml +0 -0
  89. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/loader/empty_list.json +0 -0
  90. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/fallbacks/test.csv +0 -0
  91. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/fallbacks/test.json +0 -0
  92. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/fallbacks/test.txt +0 -0
  93. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/fallbacks/test.yaml +0 -0
  94. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/retriever-test.csv +0 -0
  95. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/test.csv +0 -0
  96. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/test.json +0 -0
  97. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/test.txt +0 -0
  98. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/test.yaml +0 -0
  99. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/retriever/test_hxl.csv +0 -0
  100. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out.csv +0 -0
  101. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out.json +0 -0
  102. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out2.csv +0 -0
  103. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out2.json +0 -0
  104. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out5.json +0 -0
  105. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out6.json +0 -0
  106. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out7.json +0 -0
  107. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out8.csv +0 -0
  108. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/out8.json +0 -0
  109. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/pretty-false_sortkeys-false.json +0 -0
  110. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/pretty-false_sortkeys-false.yaml +0 -0
  111. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/pretty-false_sortkeys-true.json +0 -0
  112. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/pretty-false_sortkeys-true.yaml +0 -0
  113. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/pretty-true_sortkeys-false.json +0 -0
  114. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/pretty-true_sortkeys-false.yaml +0 -0
  115. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/pretty-true_sortkeys-true.json +0 -0
  116. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/saver/pretty-true_sortkeys-true.yaml +0 -0
  117. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/state/analysis_dates.txt +0 -0
  118. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/state/last_build_date.txt +0 -0
  119. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/fixtures/test_data.csv +0 -0
  120. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_compare.py +0 -0
  121. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_dateparse.py +0 -0
  122. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_dictandlist.py +0 -0
  123. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_easy_logging.py +0 -0
  124. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_email.py +0 -0
  125. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_encoding.py +0 -0
  126. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_error_handler.py +0 -0
  127. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_file_hashing.py +0 -0
  128. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_html.py +0 -0
  129. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_loader.py +0 -0
  130. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_matching.py +0 -0
  131. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_path.py +0 -0
  132. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_state.py +0 -0
  133. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_url.py +0 -0
  134. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_useragent.py +0 -0
  135. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_uuid.py +0 -0
  136. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/test_zip_crc.py +0 -0
  137. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/tests/hdx/utilities/utils.py +0 -0
  138. {hdx_python_utilities-4.0.6 → hdx_python_utilities-4.0.8}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hdx-python-utilities
3
- Version: 4.0.6
3
+ Version: 4.0.8
4
4
  Summary: HDX Python Utilities for streaming tabular data, date and time handling and other helpful functions
5
5
  Project-URL: Homepage, https://github.com/OCHA-DAP/hdx-python-utilities
6
6
  Author-email: Michael Rans <rans@email.com>
@@ -941,6 +941,9 @@ Examples:
941
941
  result = get_words_in_sentence("Korea (Democratic People's Republic of)")
942
942
  assert result == ["Korea", "Democratic", "People's", "Republic", "of"]
943
943
 
944
+ # Split wall of text into paragraphs in a smart and efficient way
945
+ result = smart_split("MY WALL OF TEXT")
946
+
944
947
  ## Stable file hashing
945
948
 
946
949
  Efficient Hashing of files that produces a stable hash even for zip and xlsx files eg.
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '4.0.6'
32
- __version_tuple__ = version_tuple = (4, 0, 6)
31
+ __version__ = version = '4.0.8'
32
+ __version_tuple__ = version_tuple = (4, 0, 8)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -330,9 +330,15 @@ def save_iterable(
330
330
  if isinstance(row, dict):
331
331
  has_header = True
332
332
  row = row_function(row)
333
- while row is None:
334
- row = next(rows)
335
- row = row_function(row)
333
+ try:
334
+ while row is None:
335
+ row = next(rows)
336
+ row = row_function(row)
337
+ except StopIteration:
338
+ if not no_empty and headers:
339
+ newrows = [headers]
340
+ write_rows(newrows, None, None)
341
+ return newrows
336
342
  if columns:
337
343
  row = {k: row[k] for k in columns if k in row}
338
344
  newrows.append(row)
@@ -261,3 +261,65 @@ def get_numeric_if_possible(value: Any) -> Any:
261
261
  val = val.replace(",", ".")
262
262
  return float(val) / denominator
263
263
  return value
264
+
265
+
266
+ def smart_split(text: str, target_length: int = 400) -> str:
267
+ """
268
+ Splits text into paragraphs based on sentence length and structure.
269
+
270
+ Args:
271
+ text: The input text.
272
+ target_length: Approximate characters per paragraph. Defaults to 400.
273
+
274
+ Returns:
275
+ The output text
276
+ """
277
+
278
+ # 1. Clean up and split into sentences
279
+ # Look for (.!?) followed by a space and a capital letter
280
+ sentences = re.split(r"(?<=[.!?])\s+(?=[A-Z])", text.strip())
281
+
282
+ paragraphs = []
283
+ current_para = []
284
+ current_length = 0
285
+
286
+ for i, sentence in enumerate(sentences):
287
+ sentence = sentence.strip()
288
+
289
+ # 2. Analyze the sentence start
290
+ first_word = sentence.split()[0].replace(",", "")
291
+
292
+ # Score: How likely is this a new paragraph starter?
293
+ score = 0
294
+
295
+ # Rule A: Proper Nouns/Acronyms often start new topics
296
+ # (e.g., "The WDPCA", "UNEP-WCMC", "Data")
297
+ if first_word[0].isupper() and len(first_word) > 1:
298
+ score += 1
299
+
300
+ # Rule B: Weak Starters (Conjunctions) -> Force stay
301
+ if first_word.lower() in ["and", "but", "or", "so", "because"]:
302
+ score -= 5
303
+
304
+ # Rule C: Length Pressure
305
+ # If we are way over the target length, force a split on any decent sentence
306
+ if current_length > target_length:
307
+ score += 3
308
+ elif current_length < (target_length / 2):
309
+ score -= 5 # Too short to split yet
310
+
311
+ # 3. Decision Time
312
+ # If score is high enough (and we aren't at the very first sentence)
313
+ if score >= 2 and current_para:
314
+ paragraphs.append(" ".join(current_para))
315
+ current_para = [sentence]
316
+ current_length = len(sentence)
317
+ else:
318
+ current_para.append(sentence)
319
+ current_length += len(sentence)
320
+
321
+ # Append whatever is left
322
+ if current_para:
323
+ paragraphs.append(" ".join(current_para))
324
+
325
+ return "\n\n".join(paragraphs)
@@ -0,0 +1 @@
1
+ The World Database on Protected and Conserved Areas (WDPCA) combines the formerly separate World Database on Protected Areas (WDPA) and World Database on Other Effective Area-based Conservation Measures (WD-OECM). The WDPCA is the most comprehensive global database of marine and terrestrial protected areas and other effective area-based conservation measures, updated on a monthly basis, and is one of the key global biodiversity datasets being widely used by scientists, businesses, governments, international secretariats, and others to inform planning, policy decisions, and management. The WDPCA is part of the Protected Planet Initiative, a joint product of the UN Environment Programme and the International Union for Conservation of Nature (IUCN). The compilation and management of the WDPCA is carried out by the UN Environment Programme World Conservation Monitoring Centre (UNEP-WCMC), in collaboration with governments and other stakeholders. Data and information on the world's protected and conserved areas compiled in the WDPCA is used for reporting on progress towards reaching Target 3 of the Kunming-Montreal Global Biodiversity Framework, which calls for 30% of the world’s land and waters to be effectively conserved by 2030. Additionally, the WDPCA is used for reporting to the UN to track progress towards the 2030 Sustainable Development Goals, tracking of core indicators of the Intergovernmental Science-Policy Platform on Biodiversity and Ecosystem Services (IPBES), and providing information for other international assessments and reports including the Global Biodiversity Outlook. UNEP-WCMC and IUCN periodically release the Protected Planet Report on the status of the world's protected and conserved areas. Many platforms are incorporating the WDPCA to provide integrated information to diverse users, including businesses and governments, in a range of sectors. For example, the WDPCA is included in the Integrated Biodiversity Assessment Tool (IBAT), an innovative decision support tool that gives commercial users easy access to up-to-date information that allows them to identify biodiversity risks and opportunities within a project boundary. The reach of the WDPCA is further enhanced by the UN Biodiversity Lab as well as services developed by other parties, such as the Global Forest Watch and the Digital Observatory for Protected Areas, which provide decision makers with access to monitoring and alert systems that allow whole landscapes to be managed better. Together, these applications of the WDPCA demonstrate the growing value and significance of the Protected Planet initiative.
@@ -0,0 +1 @@
1
+ UNHCR, the UN Refugee Agency, is a global organization dedicated to saving lives, protecting rights and building a better future for people forced to flee their homes because of conflict and persecution. We lead international action to protect refugees, forcibly displaced communities and stateless people. Our vision is a world where every person forced to flee can build a better future. Formally known as the Office of the High Commissioner for Refugees, UNHCR was established by the General Assembly of the United Nations in 1950 in the aftermath of the Second World War to help the millions of people who had lost their homes. Today, UNHCR works in 128 countries. We provide life-saving assistance, including shelter, food, water and medical care for people forced to flee conflict and persecution, many of whom have nobody left to turn to. We defend their right to reach safety and help them find a place to call home so they can rebuild their lives. Long term, we work with countries to improve and monitor refugee and asylum laws and policies, ensuring human rights are upheld. In everything we do UNHCR considers refugees and those forced to flee as partners, putting those most affected at the centre of planning and decision-making.
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  import pytest
7
7
 
8
8
  from hdx.utilities.downloader import Download
9
+ from hdx.utilities.useragent import UserAgent
9
10
 
10
11
 
11
12
  @pytest.fixture(scope="session")
@@ -23,6 +24,13 @@ def fixtureurl():
23
24
  return "https://raw.githubusercontent.com/OCHA-DAP/hdx-python-utilities/master/tests/fixtures/test_data.csv"
24
25
 
25
26
 
27
+ @pytest.fixture(scope="session", autouse=True)
28
+ def useragent():
29
+ UserAgent.set_global("test")
30
+ yield
31
+ UserAgent.clear_global()
32
+
33
+
26
34
  @pytest.fixture(scope="function")
27
35
  def mocksmtp(monkeypatch):
28
36
  class MockSMTPBase:
@@ -17,7 +17,6 @@ from hdx.utilities.base_downloader import DownloadError
17
17
  from hdx.utilities.downloader import Download
18
18
  from hdx.utilities.session import SessionError
19
19
  from hdx.utilities.url import get_path_for_url
20
- from hdx.utilities.useragent import UserAgent
21
20
 
22
21
 
23
22
  @contextmanager
@@ -34,12 +33,6 @@ def not_raises(ExpectedException):
34
33
  class TestDownloader:
35
34
  downloaderfoldername = "downloader"
36
35
 
37
- @pytest.fixture(scope="class", autouse=True)
38
- def useragent(self):
39
- UserAgent.set_global("test")
40
- yield
41
- UserAgent.clear_global()
42
-
43
36
  @pytest.fixture(scope="class")
44
37
  def downloaderfolder(self, fixturesfolder):
45
38
  return fixturesfolder / self.downloaderfoldername
@@ -10,18 +10,11 @@ from utils import assert_downloaders
10
10
 
11
11
  from hdx.utilities.downloader import Download, DownloadError
12
12
  from hdx.utilities.retriever import Retrieve
13
- from hdx.utilities.useragent import UserAgent
14
13
 
15
14
 
16
15
  class TestRetriever:
17
16
  retrieverfoldername = "retriever"
18
17
 
19
- @pytest.fixture(scope="class", autouse=True)
20
- def useragent(self):
21
- UserAgent.set_global("test")
22
- yield
23
- UserAgent.clear_global()
24
-
25
18
  @pytest.fixture(scope="class")
26
19
  def retrieverfolder(self, fixturesfolder):
27
20
  return fixturesfolder / self.retrieverfoldername
@@ -296,7 +296,7 @@ class TestLoader:
296
296
  filename = "out8.json"
297
297
  assert_files_same(saverfolder / filename, output_dir / filename)
298
298
 
299
- def test_save_iterable(self):
299
+ def test_save_iterable_lists(self):
300
300
  list_of_tuples = [(1, 2, 3, "a"), (4, 5, 6, "b"), (7, 8, 9, "c")]
301
301
  list_of_lists = [[1, 2, 3, "a"], [4, 5, 6, "b"], [7, 8, 9, "c"]]
302
302
  list_of_dicts = [
@@ -457,6 +457,15 @@ class TestLoader:
457
457
  ["7", "8", "9", "c"],
458
458
  ]
459
459
 
460
+ def test_save_iterable_dicts(self):
461
+ with temp_dir(
462
+ "TestSaveIterable",
463
+ delete_on_success=True,
464
+ delete_on_failure=False,
465
+ ) as tempdir:
466
+ filename = "test_save_iterable_to_csv.csv"
467
+ filepath = tempdir / filename
468
+
460
469
  list_of_dicts = [
461
470
  {"h1": 1, "h2": 2, "h3": 3, "h4": "a"},
462
471
  {"h1": 4, "h2": 5, "h3": 6, "h4": "b"},
@@ -554,3 +563,47 @@ class TestLoader:
554
563
  newll = read_list_from_csv(str(filepath))
555
564
  remove(filepath)
556
565
  assert newll == [["h1", "h3", "h4"]]
566
+
567
+ def process_row(row):
568
+ return None
569
+
570
+ rows = save_iterable(
571
+ filepath,
572
+ list_of_dicts,
573
+ headers=["h1", "h2", "h3", "h4"],
574
+ row_function=process_row,
575
+ )
576
+ assert rows == []
577
+
578
+ rows = save_iterable(
579
+ filepath,
580
+ list_of_dicts,
581
+ headers=["h1", "h2", "h3", "h4"],
582
+ row_function=process_row,
583
+ no_empty=False,
584
+ )
585
+ assert rows == [["h1", "h2", "h3", "h4"]]
586
+ newll = read_list_from_csv(filepath)
587
+ remove(filepath)
588
+ assert newll == [
589
+ ["h1", "h2", "h3", "h4"],
590
+ ]
591
+
592
+ def process_row(row):
593
+ if 5 in row.values():
594
+ return None
595
+ return row
596
+
597
+ save_iterable(
598
+ filepath,
599
+ list_of_dicts,
600
+ headers=["h1", "h2", "h3", "h4"],
601
+ row_function=process_row,
602
+ )
603
+ newll = read_list_from_csv(filepath)
604
+ remove(filepath)
605
+ assert newll == [
606
+ ["h1", "h2", "h3", "h4"],
607
+ ["1", "2", "4", "a"],
608
+ ["7", "8", "10", "c"],
609
+ ]
@@ -0,0 +1,199 @@
1
+ """Text Processing Tests"""
2
+
3
+ from string import punctuation, whitespace
4
+
5
+ import pytest
6
+ from pytest import approx
7
+
8
+ from hdx.utilities.downloader import Download
9
+ from hdx.utilities.text import (
10
+ PUNCTUATION_MINUS_BRACKETS,
11
+ get_fraction_str,
12
+ get_numeric_if_possible,
13
+ get_words_in_sentence,
14
+ normalise,
15
+ number_format,
16
+ only_allowed_in_str,
17
+ remove_end_characters,
18
+ remove_from_end,
19
+ remove_string,
20
+ smart_split,
21
+ )
22
+
23
+
24
+ class TestText:
25
+ @pytest.fixture(scope="function")
26
+ def text1(self, fixturesfolder):
27
+ with Download() as downloader:
28
+ return downloader.download_text(fixturesfolder / "text" / "text1.txt")
29
+
30
+ @pytest.fixture(scope="function")
31
+ def text2(self, fixturesfolder):
32
+ with Download() as downloader:
33
+ return downloader.download_text(fixturesfolder / "text" / "text2.txt")
34
+
35
+ def test_normalise(self):
36
+ assert (
37
+ normalise("£^*& ()+-[]<>?|\ Al DhaleZ'eÉ / الضالع,,..1234''#~~### ")
38
+ == "al dhalezee 1234"
39
+ )
40
+
41
+ def test_remove_end_characters(self):
42
+ assert remove_end_characters('lalala,.,"') == "lalala"
43
+ assert (
44
+ remove_end_characters('lalala, .\t/,"', f"{punctuation}{whitespace}")
45
+ == "lalala"
46
+ )
47
+
48
+ def test_remove_from_end(self):
49
+ a = "The quick brown fox jumped over the lazy dog. It was so fast!"
50
+ result = remove_from_end(
51
+ a, ["fast!", "so", "hello", "as"], "Transforming %s -> %s"
52
+ )
53
+ assert result == "The quick brown fox jumped over the lazy dog. It was"
54
+ result = remove_from_end(
55
+ a,
56
+ ["fast!", "so", "hello", "as"],
57
+ "Transforming %s -> %s",
58
+ False,
59
+ )
60
+ assert result == "The quick brown fox jumped over the lazy dog. It w"
61
+
62
+ def test_remove_string(self):
63
+ assert remove_string("lala, 01/02/2020 ", "01/02/2020") == "lala "
64
+ assert remove_string("lala,(01/02/2020) ", "01/02/2020") == "lala) "
65
+ assert (
66
+ remove_string("lala, 01/02/2020 ", "01/02/2020", PUNCTUATION_MINUS_BRACKETS)
67
+ == "lala "
68
+ )
69
+ assert (
70
+ remove_string(
71
+ "lala,(01/02/2020) ", "01/02/2020", PUNCTUATION_MINUS_BRACKETS
72
+ )
73
+ == "lala,() "
74
+ )
75
+
76
+ def test_get_words_in_sentence(self):
77
+ result = get_words_in_sentence("Korea (Democratic People's Republic of)")
78
+ assert result == ["Korea", "Democratic", "People's", "Republic", "of"]
79
+ result = get_words_in_sentence("Serbia and Kosovo: S/RES/1244 (1999)")
80
+ assert result == [
81
+ "Serbia",
82
+ "and",
83
+ "Kosovo",
84
+ "S",
85
+ "RES",
86
+ "1244",
87
+ "1999",
88
+ ]
89
+
90
+ def test_number_format(self):
91
+ assert number_format(1234.56789) == "1234.5679"
92
+ assert number_format("") == ""
93
+ assert number_format(None) == ""
94
+ assert number_format(1234.5, "%.4f") == "1234.5000"
95
+ assert number_format(1234.5, "%.4f", False) == "1234.5"
96
+ assert number_format(1234, "%.4f", False) == "1234"
97
+
98
+ def test_get_fraction_str(self):
99
+ assert get_fraction_str("abc", 345) == ""
100
+ assert get_fraction_str(123, 345) == "0.3565"
101
+ assert get_fraction_str(123, 0) == ""
102
+
103
+ def test_only_allowed_in_str(self):
104
+ assert only_allowed_in_str("1234a", {"1", "2", "3", "a"}) is False
105
+ assert only_allowed_in_str("1234a", {"1", "2", "3", "4", "a"}) is True
106
+
107
+ def test_get_numeric_if_possible(self):
108
+ assert get_numeric_if_possible(123) == 123
109
+ assert get_numeric_if_possible(-123) == -123
110
+ assert get_numeric_if_possible(123.45) == 123.45
111
+ assert get_numeric_if_possible(-123.45) == -123.45
112
+ assert get_numeric_if_possible("") == ""
113
+ assert get_numeric_if_possible("hello") == "hello"
114
+ assert get_numeric_if_possible("123") == 123
115
+ assert get_numeric_if_possible("-123") == -123
116
+ assert get_numeric_if_possible("123.45") == 123.45
117
+ assert get_numeric_if_possible("-123.45") == -123.45
118
+ assert get_numeric_if_possible("123,123,123.45") == 123123123.45
119
+ assert get_numeric_if_possible("123.123.123,45") == 123123123.45
120
+ assert get_numeric_if_possible("123,123,123") == 123123123
121
+ assert get_numeric_if_possible("123.123.123") == 123123123
122
+ assert get_numeric_if_possible("12.3%") == approx(0.123)
123
+ assert get_numeric_if_possible("10%") == 0.1
124
+ assert get_numeric_if_possible("-10%") == -0.1
125
+ assert get_numeric_if_possible("10-") == "10-"
126
+ assert get_numeric_if_possible("123,123.45%") == 1231.2345
127
+ assert get_numeric_if_possible("-123,123.45%") == -1231.2345
128
+ assert get_numeric_if_possible("123.123,45%") == 1231.2345
129
+
130
+ def test_smart_split(self, text1, text2):
131
+ result = smart_split(text1)
132
+ assert result == (
133
+ "The World Database on Protected and Conserved Areas (WDPCA) combines the "
134
+ "formerly separate World Database on Protected Areas (WDPA) and World "
135
+ "Database on Other Effective Area-based Conservation Measures (WD-OECM). The "
136
+ "WDPCA is the most comprehensive global database of marine and terrestrial "
137
+ "protected areas and other effective area-based conservation measures, "
138
+ "updated on a monthly basis, and is one of the key global biodiversity "
139
+ "datasets being widely used by scientists, businesses, governments, "
140
+ "international secretariats, and others to inform planning, policy decisions, "
141
+ "and management.\n"
142
+ "\n"
143
+ "The WDPCA is part of the Protected Planet Initiative, a joint product of the "
144
+ "UN Environment Programme and the International Union for Conservation of "
145
+ "Nature (IUCN). The compilation and management of the WDPCA is carried out by "
146
+ "the UN Environment Programme World Conservation Monitoring Centre "
147
+ "(UNEP-WCMC), in collaboration with governments and other stakeholders. Data "
148
+ "and information on the world's protected and conserved areas compiled in the "
149
+ "WDPCA is used for reporting on progress towards reaching Target 3 of the "
150
+ "Kunming-Montreal Global Biodiversity Framework, which calls for 30% of the "
151
+ "world’s land and waters to be effectively conserved by 2030.\n"
152
+ "\n"
153
+ "Additionally, the WDPCA is used for reporting to the UN to track progress "
154
+ "towards the 2030 Sustainable Development Goals, tracking of core indicators "
155
+ "of the Intergovernmental Science-Policy Platform on Biodiversity and "
156
+ "Ecosystem Services (IPBES), and providing information for other "
157
+ "international assessments and reports including the Global Biodiversity "
158
+ "Outlook. UNEP-WCMC and IUCN periodically release the Protected Planet Report "
159
+ "on the status of the world's protected and conserved areas.\n"
160
+ "\n"
161
+ "Many platforms are incorporating the WDPCA to provide integrated information "
162
+ "to diverse users, including businesses and governments, in a range of "
163
+ "sectors. For example, the WDPCA is included in the Integrated Biodiversity "
164
+ "Assessment Tool (IBAT), an innovative decision support tool that gives "
165
+ "commercial users easy access to up-to-date information that allows them to "
166
+ "identify biodiversity risks and opportunities within a project boundary.\n"
167
+ "\n"
168
+ "The reach of the WDPCA is further enhanced by the UN Biodiversity Lab as "
169
+ "well as services developed by other parties, such as the Global Forest Watch "
170
+ "and the Digital Observatory for Protected Areas, which provide decision "
171
+ "makers with access to monitoring and alert systems that allow whole "
172
+ "landscapes to be managed better. Together, these applications of the WDPCA "
173
+ "demonstrate the growing value and significance of the Protected Planet "
174
+ "initiative."
175
+ )
176
+ result = smart_split(text2)
177
+ assert result == (
178
+ "UNHCR, the UN Refugee Agency, is a global organization dedicated to saving "
179
+ "lives, protecting rights and building a better future for people forced to "
180
+ "flee their homes because of conflict and persecution. We lead international "
181
+ "action to protect refugees, forcibly displaced communities and stateless "
182
+ "people. Our vision is a world where every person forced to flee can build a "
183
+ "better future. Formally known as the Office of the High Commissioner for "
184
+ "Refugees, UNHCR was established by the General Assembly of the United "
185
+ "Nations in 1950 in the aftermath of the Second World War to help the "
186
+ "millions of people who had lost their homes.\n"
187
+ "\n"
188
+ "Today, UNHCR works in 128 countries. We provide life-saving assistance, "
189
+ "including shelter, food, water and medical care for people forced to flee "
190
+ "conflict and persecution, many of whom have nobody left to turn to. We "
191
+ "defend their right to reach safety and help them find a place to call home "
192
+ "so they can rebuild their lives. Long term, we work with countries to "
193
+ "improve and monitor refugee and asylum laws and policies, ensuring human "
194
+ "rights are upheld.\n"
195
+ "\n"
196
+ "In everything we do UNHCR considers refugees and those forced to flee as "
197
+ "partners, putting those most affected at the centre of planning and "
198
+ "decision-making."
199
+ )
@@ -1,115 +0,0 @@
1
- """Text Processing Tests"""
2
-
3
- from string import punctuation, whitespace
4
-
5
- from pytest import approx
6
-
7
- from hdx.utilities.text import (
8
- PUNCTUATION_MINUS_BRACKETS,
9
- get_fraction_str,
10
- get_numeric_if_possible,
11
- get_words_in_sentence,
12
- normalise,
13
- number_format,
14
- only_allowed_in_str,
15
- remove_end_characters,
16
- remove_from_end,
17
- remove_string,
18
- )
19
-
20
-
21
- class TestText:
22
- def test_normalise(self):
23
- assert (
24
- normalise("£^*& ()+-[]<>?|\ Al DhaleZ'eÉ / الضالع,,..1234''#~~### ")
25
- == "al dhalezee 1234"
26
- )
27
-
28
- def test_remove_end_characters(self):
29
- assert remove_end_characters('lalala,.,"') == "lalala"
30
- assert (
31
- remove_end_characters('lalala, .\t/,"', f"{punctuation}{whitespace}")
32
- == "lalala"
33
- )
34
-
35
- def test_remove_from_end(self):
36
- a = "The quick brown fox jumped over the lazy dog. It was so fast!"
37
- result = remove_from_end(
38
- a, ["fast!", "so", "hello", "as"], "Transforming %s -> %s"
39
- )
40
- assert result == "The quick brown fox jumped over the lazy dog. It was"
41
- result = remove_from_end(
42
- a,
43
- ["fast!", "so", "hello", "as"],
44
- "Transforming %s -> %s",
45
- False,
46
- )
47
- assert result == "The quick brown fox jumped over the lazy dog. It w"
48
-
49
- def test_remove_string(self):
50
- assert remove_string("lala, 01/02/2020 ", "01/02/2020") == "lala "
51
- assert remove_string("lala,(01/02/2020) ", "01/02/2020") == "lala) "
52
- assert (
53
- remove_string("lala, 01/02/2020 ", "01/02/2020", PUNCTUATION_MINUS_BRACKETS)
54
- == "lala "
55
- )
56
- assert (
57
- remove_string(
58
- "lala,(01/02/2020) ", "01/02/2020", PUNCTUATION_MINUS_BRACKETS
59
- )
60
- == "lala,() "
61
- )
62
-
63
- def test_get_words_in_sentence(self):
64
- result = get_words_in_sentence("Korea (Democratic People's Republic of)")
65
- assert result == ["Korea", "Democratic", "People's", "Republic", "of"]
66
- result = get_words_in_sentence("Serbia and Kosovo: S/RES/1244 (1999)")
67
- assert result == [
68
- "Serbia",
69
- "and",
70
- "Kosovo",
71
- "S",
72
- "RES",
73
- "1244",
74
- "1999",
75
- ]
76
-
77
- def test_number_format(self):
78
- assert number_format(1234.56789) == "1234.5679"
79
- assert number_format("") == ""
80
- assert number_format(None) == ""
81
- assert number_format(1234.5, "%.4f") == "1234.5000"
82
- assert number_format(1234.5, "%.4f", False) == "1234.5"
83
- assert number_format(1234, "%.4f", False) == "1234"
84
-
85
- def test_get_fraction_str(self):
86
- assert get_fraction_str("abc", 345) == ""
87
- assert get_fraction_str(123, 345) == "0.3565"
88
- assert get_fraction_str(123, 0) == ""
89
-
90
- def test_only_allowed_in_str(self):
91
- assert only_allowed_in_str("1234a", {"1", "2", "3", "a"}) is False
92
- assert only_allowed_in_str("1234a", {"1", "2", "3", "4", "a"}) is True
93
-
94
- def test_get_numeric_if_possible(self):
95
- assert get_numeric_if_possible(123) == 123
96
- assert get_numeric_if_possible(-123) == -123
97
- assert get_numeric_if_possible(123.45) == 123.45
98
- assert get_numeric_if_possible(-123.45) == -123.45
99
- assert get_numeric_if_possible("") == ""
100
- assert get_numeric_if_possible("hello") == "hello"
101
- assert get_numeric_if_possible("123") == 123
102
- assert get_numeric_if_possible("-123") == -123
103
- assert get_numeric_if_possible("123.45") == 123.45
104
- assert get_numeric_if_possible("-123.45") == -123.45
105
- assert get_numeric_if_possible("123,123,123.45") == 123123123.45
106
- assert get_numeric_if_possible("123.123.123,45") == 123123123.45
107
- assert get_numeric_if_possible("123,123,123") == 123123123
108
- assert get_numeric_if_possible("123.123.123") == 123123123
109
- assert get_numeric_if_possible("12.3%") == approx(0.123)
110
- assert get_numeric_if_possible("10%") == 0.1
111
- assert get_numeric_if_possible("-10%") == -0.1
112
- assert get_numeric_if_possible("10-") == "10-"
113
- assert get_numeric_if_possible("123,123.45%") == 1231.2345
114
- assert get_numeric_if_possible("-123,123.45%") == -1231.2345
115
- assert get_numeric_if_possible("123.123,45%") == 1231.2345