hdx-python-utilities 3.9.3__tar.gz → 3.9.4__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 (129) hide show
  1. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/PKG-INFO +1 -1
  2. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/documentation/index.md +25 -3
  3. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/requirements.txt +14 -15
  4. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/_version.py +2 -2
  5. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/saver.py +90 -1
  6. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_saver.py +154 -1
  7. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/.coveragerc +0 -0
  8. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/.github/workflows/publish-test.yaml +0 -0
  9. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/.github/workflows/publish.yaml +0 -0
  10. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/.github/workflows/run-python-tests.yaml +0 -0
  11. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/.gitignore +0 -0
  12. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/.pre-commit-config.yaml +0 -0
  13. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/CONTRIBUTING.md +0 -0
  14. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/LICENSE +0 -0
  15. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/README.md +0 -0
  16. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/documentation/.readthedocs.yaml +0 -0
  17. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/documentation/mkdocs.yaml +0 -0
  18. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/hatch.toml +0 -0
  19. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/pyproject.toml +0 -0
  20. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/pytest.ini +0 -0
  21. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/ruff.toml +0 -0
  22. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/__init__.py +0 -0
  23. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/base_downloader.py +0 -0
  24. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/compare.py +0 -0
  25. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/dateparse.py +0 -0
  26. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/dictandlist.py +0 -0
  27. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/downloader.py +0 -0
  28. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/easy_logging.py +0 -0
  29. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/email.py +0 -0
  30. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/encoding.py +0 -0
  31. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/error_handler.py +0 -0
  32. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/errors_onexit.py +0 -0
  33. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/frictionless_wrapper.py +0 -0
  34. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/html.py +0 -0
  35. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/loader.py +0 -0
  36. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/matching.py +0 -0
  37. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/path.py +0 -0
  38. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/retriever.py +0 -0
  39. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/session.py +0 -0
  40. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/state.py +0 -0
  41. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/text.py +0 -0
  42. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/typehint.py +0 -0
  43. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/useragent.py +0 -0
  44. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/src/hdx/utilities/uuid.py +0 -0
  45. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/compare/test_csv_processing.csv +0 -0
  46. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/compare/test_csv_processing2.csv +0 -0
  47. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/empty.yaml +0 -0
  48. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/hdx_config.json +0 -0
  49. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/hdx_config.yaml +0 -0
  50. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/hdx_email_configuration.json +0 -0
  51. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
  52. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/json_csv.yaml +0 -0
  53. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/logging_config.json +0 -0
  54. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/logging_config.yaml +0 -0
  55. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/project_configuration.json +0 -0
  56. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/project_configuration.yaml +0 -0
  57. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/smtp_config.json +0 -0
  58. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/smtp_config.yaml +0 -0
  59. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/user_agent_config.yaml +0 -0
  60. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/user_agent_config2.yaml +0 -0
  61. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/user_agent_config3.yaml +0 -0
  62. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
  63. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/basicauth.txt +0 -0
  64. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/bearertoken.txt +0 -0
  65. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/extra_params.json +0 -0
  66. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/extra_params.yaml +0 -0
  67. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/extra_params_tree.yaml +0 -0
  68. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_csv_processing.csv +0 -0
  69. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_csv_processing_blanks.csv +0 -0
  70. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_data.csv +0 -0
  71. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_data.xlsx +0 -0
  72. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_data1.csv/empty.txt +0 -0
  73. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_data2.csv +0 -0
  74. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_json_processing.json +0 -0
  75. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_xls_processing.xls +0 -0
  76. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/downloader/test_xlsx_processing.xlsx +0 -0
  77. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/html/response.html +0 -0
  78. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/loader/empty.json +0 -0
  79. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/loader/empty.yaml +0 -0
  80. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/loader/empty_list.json +0 -0
  81. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/fallbacks/test.csv +0 -0
  82. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/fallbacks/test.json +0 -0
  83. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/fallbacks/test.txt +0 -0
  84. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/fallbacks/test.yaml +0 -0
  85. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/retriever-test.csv +0 -0
  86. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/test.csv +0 -0
  87. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/test.json +0 -0
  88. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/test.txt +0 -0
  89. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/test.yaml +0 -0
  90. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/retriever/test_hxl.csv +0 -0
  91. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out.csv +0 -0
  92. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out.json +0 -0
  93. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out2.csv +0 -0
  94. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out2.json +0 -0
  95. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out5.json +0 -0
  96. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out6.json +0 -0
  97. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out7.json +0 -0
  98. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out8.csv +0 -0
  99. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/out8.json +0 -0
  100. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/pretty-false_sortkeys-false.json +0 -0
  101. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/pretty-false_sortkeys-false.yaml +0 -0
  102. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/pretty-false_sortkeys-true.json +0 -0
  103. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/pretty-false_sortkeys-true.yaml +0 -0
  104. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/pretty-true_sortkeys-false.json +0 -0
  105. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/pretty-true_sortkeys-false.yaml +0 -0
  106. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/pretty-true_sortkeys-true.json +0 -0
  107. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/saver/pretty-true_sortkeys-true.yaml +0 -0
  108. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/state/analysis_dates.txt +0 -0
  109. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/state/last_build_date.txt +0 -0
  110. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/fixtures/test_data.csv +0 -0
  111. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/conftest.py +0 -0
  112. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_compare.py +0 -0
  113. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_dateparse.py +0 -0
  114. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_dictandlist.py +0 -0
  115. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_downloader.py +0 -0
  116. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_easy_logging.py +0 -0
  117. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_email.py +0 -0
  118. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_encoding.py +0 -0
  119. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_error_handler.py +0 -0
  120. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_html.py +0 -0
  121. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_loader.py +0 -0
  122. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_matching.py +0 -0
  123. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_path.py +0 -0
  124. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_retriever.py +0 -0
  125. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_state.py +0 -0
  126. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_text.py +0 -0
  127. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_useragent.py +0 -0
  128. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/test_uuid.py +0 -0
  129. {hdx_python_utilities-3.9.3 → hdx_python_utilities-3.9.4}/tests/hdx/utilities/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hdx-python-utilities
3
- Version: 3.9.3
3
+ Version: 3.9.4
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>
@@ -386,6 +386,24 @@ Examples:
386
386
  # sorting the keys
387
387
  save_json(mydict, "mypath.json", pretty=False, sortkeys=False)
388
388
 
389
+ ## Saving iterable to file
390
+
391
+ `save_iterable` is a utility to save an iterable of dict, list or tuple to a file such
392
+ as a csv or Excel.
393
+
394
+ # Save iterable to file
395
+ l = [[1, 2, 3, "a"],
396
+ [4, 5, 6, "b"],
397
+ [7, 8, 9, "c"]]
398
+ save_iterable(filepath, l, headers=["h1", "h2", "h3", "h4"])
399
+ newll = read_list_from_csv(filepath)
400
+ newld = read_list_from_csv(filepath, headers=1, dict_form=True)
401
+ assert newll == [["h1", "h2", "h3", "h4"], ["1", "2", "3", "a"], ["4", "5", "6", "b"], ["7", "8", "9", "c"]]
402
+ assert newld == [{"h1": "1", "h2": "2", "h4": "a", "h3": "3"},
403
+ {"h1": "4", "h2": "5", "h4": "b", "h3": "6"},
404
+ {"h1": "7", "h2": "8", "h4": "c", "h3": "9"}]
405
+ save_iterable(xlfilepath, list_of_lists, headers=["h1", "h2", "h3", "h4"], format="xlsx")
406
+
389
407
  ## Loading and saving HXLated csv and/or JSON
390
408
 
391
409
  `save_hxlated_output` is a utility to save HXLated output (currently JSON and/or csv are
@@ -555,6 +573,10 @@ Examples:
555
573
  d2 = {1: 2, 2: 2.0, 3: 5, 4: 4, 7: 3}
556
574
  assert avg_dicts(d1, d2) == {1: 1.5, 2: 1.5, 3: 4, 4: 4}
557
575
 
576
+ ## Convert command line arguments to dictionary
577
+ args = "a=1,big=hello,1=3"
578
+ assert args_to_dict(args) == {"a": "1", "big": "hello", "1": "3"}
579
+
558
580
  # Read and write lists to csv
559
581
  l = [[1, 2, 3, "a"],
560
582
  [4, 5, 6, "b"],
@@ -567,9 +589,9 @@ Examples:
567
589
  {"h1": "4", "h2": "5", "h4": "b", "h3": "6"},
568
590
  {"h1": "7", "h2": "8", "h4": "c", "h3": "9"}]
569
591
 
570
- ## Convert command line arguments to dictionary
571
- args = "a=1,big=hello,1=3"
572
- assert args_to_dict(args) == {"a": "1", "big": "hello", "1": "3"}
592
+ Note that `get_tabular_rows` in the `Download` class is a more powerful and streaming
593
+ version of `read_list_from_csv` and `save_iterable` is a generally more efficient
594
+ version of`write_list_to_csv` as it uses iteration instead of list manipulation.
573
595
 
574
596
  ## HTML utilities
575
597
 
@@ -4,7 +4,7 @@ annotated-types==0.7.0
4
4
  # via pydantic
5
5
  astdoc==1.3.2
6
6
  # via mkapi
7
- attrs==25.3.0
7
+ attrs==25.4.0
8
8
  # via
9
9
  # frictionless
10
10
  # jsonlines
@@ -14,9 +14,9 @@ babel==2.17.0
14
14
  # via mkdocs-material
15
15
  backrefs==5.9
16
16
  # via mkdocs-material
17
- beautifulsoup4==4.13.5
17
+ beautifulsoup4==4.14.2
18
18
  # via hdx-python-utilities (pyproject.toml)
19
- certifi==2025.8.3
19
+ certifi==2025.10.5
20
20
  # via requests
21
21
  cfgv==3.4.0
22
22
  # via pre-commit
@@ -24,10 +24,9 @@ chardet==5.2.0
24
24
  # via frictionless
25
25
  charset-normalizer==3.4.3
26
26
  # via requests
27
- click==8.2.1
27
+ click==8.3.0
28
28
  # via
29
29
  # mkdocs
30
- # mkdocs-material
31
30
  # typer
32
31
  colorama==0.4.6
33
32
  # via mkdocs-material
@@ -43,7 +42,7 @@ email-validator==2.3.0
43
42
  # via hdx-python-utilities (pyproject.toml)
44
43
  et-xmlfile==2.0.0
45
44
  # via openpyxl
46
- filelock==3.19.1
45
+ filelock==3.20.0
47
46
  # via virtualenv
48
47
  frictionless==5.18.1
49
48
  # via hdx-python-utilities (pyproject.toml)
@@ -53,7 +52,7 @@ html5lib==1.1
53
52
  # via hdx-python-utilities (pyproject.toml)
54
53
  humanize==4.13.0
55
54
  # via frictionless
56
- identify==2.6.14
55
+ identify==2.6.15
57
56
  # via pre-commit
58
57
  idna==3.10
59
58
  # via
@@ -92,7 +91,7 @@ markdown-it-py==4.0.0
92
91
  # via rich
93
92
  marko==2.2.0
94
93
  # via frictionless
95
- markupsafe==3.0.2
94
+ markupsafe==3.0.3
96
95
  # via
97
96
  # jinja2
98
97
  # mkdocs
@@ -110,7 +109,7 @@ mkdocs==1.6.1
110
109
  # mkdocs-material
111
110
  mkdocs-get-deps==0.2.0
112
111
  # via mkdocs
113
- mkdocs-material==9.6.20
112
+ mkdocs-material==9.6.21
114
113
  # via mkapi
115
114
  mkdocs-material-extensions==1.3.1
116
115
  # via mkdocs-material
@@ -128,7 +127,7 @@ pathspec==0.12.1
128
127
  # via mkdocs
129
128
  petl==1.7.17
130
129
  # via frictionless
131
- platformdirs==4.4.0
130
+ platformdirs==4.5.0
132
131
  # via
133
132
  # mkdocs-get-deps
134
133
  # virtualenv
@@ -138,9 +137,9 @@ pluggy==1.6.0
138
137
  # pytest-cov
139
138
  pre-commit==4.3.0
140
139
  # via hdx-python-utilities (pyproject.toml)
141
- pydantic==2.11.9
140
+ pydantic==2.12.0
142
141
  # via frictionless
143
- pydantic-core==2.33.2
142
+ pydantic-core==2.41.1
144
143
  # via pydantic
145
144
  pygments==2.19.2
146
145
  # via
@@ -166,7 +165,7 @@ python-dateutil==2.9.0.post0
166
165
  # ghp-import
167
166
  python-slugify==8.0.4
168
167
  # via frictionless
169
- pyyaml==6.0.2
168
+ pyyaml==6.0.3
170
169
  # via
171
170
  # frictionless
172
171
  # mkdocs
@@ -218,7 +217,7 @@ tabulate==0.9.0
218
217
  # via frictionless
219
218
  text-unidecode==1.3
220
219
  # via python-slugify
221
- typer==0.19.1
220
+ typer==0.19.2
222
221
  # via frictionless
223
222
  typing-extensions==4.15.0
224
223
  # via
@@ -228,7 +227,7 @@ typing-extensions==4.15.0
228
227
  # pydantic-core
229
228
  # typer
230
229
  # typing-inspection
231
- typing-inspection==0.4.1
230
+ typing-inspection==0.4.2
232
231
  # via pydantic
233
232
  unidecode==1.4.0
234
233
  # via pyphonetics
@@ -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 = '3.9.3'
32
- __version_tuple__ = version_tuple = (3, 9, 3)
31
+ __version__ = version = '3.9.4'
32
+ __version_tuple__ = version_tuple = (3, 9, 4)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -4,7 +4,7 @@ import csv
4
4
  import json
5
5
  from collections import OrderedDict
6
6
  from os.path import join
7
- from typing import Any, Dict
7
+ from typing import Any, Dict, Iterable, Optional, Union
8
8
 
9
9
  from ruamel.yaml import (
10
10
  YAML,
@@ -13,6 +13,7 @@ from ruamel.yaml import (
13
13
  add_representer,
14
14
  )
15
15
 
16
+ from .frictionless_wrapper import get_frictionless_tableresource
16
17
  from .matching import match_template_variables
17
18
  from .typehint import ListTuple, ListTupleDict
18
19
 
@@ -268,3 +269,91 @@ def save_hxlated_output(
268
269
  output_json.close()
269
270
  if csv_file:
270
271
  csv_file.close()
272
+
273
+
274
+ def save_iterable(
275
+ filepath: str,
276
+ rows: Iterable[ListTupleDict],
277
+ headers: Union[int, ListTuple[str], None] = None,
278
+ columns: Union[ListTuple[int], ListTuple[str], None] = None,
279
+ format: str = "csv",
280
+ encoding: Optional[str] = None,
281
+ ) -> bool:
282
+ """Save an iterable of rows in dict or list form to a csv. (The headers
283
+ argument is either a row number (rows start counting at 1), or the actual
284
+ headers defined as a list of strings. If not set, all rows will be treated
285
+ as containing values.)
286
+
287
+ Args:
288
+ filepath (str): Path to write to
289
+ rows (Iterable[ListTupleDict]): List of rows in dict or list form
290
+ headers (Union[int, ListTuple[str], None]): Headers to write. Defaults to None.
291
+ columns (Union[ListTuple[int], ListTuple[str], None]): Columns to write. Defaults to all.
292
+ format (str): Format to write. Defaults to csv.
293
+ encoding (Optional[str]): Encoding to use. Defaults to None (infer encoding).
294
+
295
+ Returns:
296
+ bool: Whether iterable was saved
297
+ """
298
+ rows = iter(rows)
299
+ try:
300
+ row = next(rows)
301
+ except StopIteration:
302
+ return False
303
+ newrows = []
304
+ if isinstance(row, dict):
305
+ has_header = True
306
+ if columns:
307
+ newrow = {}
308
+ for column in columns:
309
+ if column in row:
310
+ newrow[column] = row[column]
311
+ newrows.append(newrow)
312
+ for row in rows:
313
+ newrow = {}
314
+ for column in columns:
315
+ if column in row:
316
+ newrow[column] = row[column]
317
+ newrows.append(newrow)
318
+ if headers is None:
319
+ headers = columns
320
+ else:
321
+ newrows.append(row)
322
+ for row in rows:
323
+ newrows.append(row)
324
+ else:
325
+ if headers is None:
326
+ headers = 1
327
+ if isinstance(headers, int):
328
+ headers_rowno = headers
329
+ headers = row
330
+ for i in range(headers_rowno - 1):
331
+ headers = next(rows)
332
+ else:
333
+ headers_rowno = None
334
+ has_header = False
335
+ if columns:
336
+ if headers_rowno is None:
337
+ newrow = []
338
+ for column in columns:
339
+ newrow.append(row[column - 1])
340
+ newrows.append(newrow)
341
+ for row in rows:
342
+ newrow = []
343
+ for column in columns:
344
+ newrow.append(row[column - 1])
345
+ newrows.append(newrow)
346
+ else:
347
+ if headers_rowno is None:
348
+ newrows.append(row)
349
+ for row in rows:
350
+ newrows.append(row)
351
+ resource = get_frictionless_tableresource(
352
+ data=newrows,
353
+ has_header=has_header,
354
+ headers=headers,
355
+ encoding=encoding,
356
+ )
357
+ resource.write(filepath, format=format, encoding=encoding)
358
+ resource.close()
359
+ return True
@@ -2,13 +2,16 @@
2
2
 
3
3
  import json
4
4
  from collections import OrderedDict
5
+ from os import remove
5
6
  from os.path import exists, join
6
7
 
7
8
  import pytest
8
9
 
9
10
  from hdx.utilities.compare import assert_files_same
11
+ from hdx.utilities.dictandlist import read_list_from_csv
10
12
  from hdx.utilities.loader import load_yaml
11
- from hdx.utilities.saver import save_hxlated_output, save_json, save_yaml
13
+ from hdx.utilities.path import temp_dir
14
+ from hdx.utilities.saver import save_hxlated_output, save_iterable, save_json, save_yaml
12
15
 
13
16
 
14
17
  class TestLoader:
@@ -290,3 +293,153 @@ class TestLoader:
290
293
  assert_files_same(join(saverfolder, filename), join(output_dir, filename))
291
294
  filename = "out8.json"
292
295
  assert_files_same(join(saverfolder, filename), join(output_dir, filename))
296
+
297
+ def test_save_iterable(self):
298
+ list_of_tuples = [(1, 2, 3, "a"), (4, 5, 6, "b"), (7, 8, 9, "c")]
299
+ list_of_lists = [[1, 2, 3, "a"], [4, 5, 6, "b"], [7, 8, 9, "c"]]
300
+ with temp_dir(
301
+ "TestSaveIterable",
302
+ delete_on_success=True,
303
+ delete_on_failure=False,
304
+ ) as tempdir:
305
+ filename = "test_save_iterable_to_csv.csv"
306
+ filepath = join(tempdir, filename)
307
+ res = save_iterable(
308
+ filepath, list_of_lists, headers=["h1", "h2", "h3", "h4"]
309
+ )
310
+ assert res is True
311
+ newll = read_list_from_csv(filepath)
312
+ newld = read_list_from_csv(filepath, headers=1, dict_form=True)
313
+ remove(filepath)
314
+ assert newll == [
315
+ ["h1", "h2", "h3", "h4"],
316
+ ["1", "2", "3", "a"],
317
+ ["4", "5", "6", "b"],
318
+ ["7", "8", "9", "c"],
319
+ ]
320
+ assert newld == [
321
+ {"h1": "1", "h2": "2", "h4": "a", "h3": "3"},
322
+ {"h1": "4", "h2": "5", "h4": "b", "h3": "6"},
323
+ {"h1": "7", "h2": "8", "h4": "c", "h3": "9"},
324
+ ]
325
+ xlfilepath = filepath.replace("csv", "xlsx")
326
+ res = save_iterable(
327
+ xlfilepath,
328
+ list_of_lists,
329
+ headers=["h1", "h2", "h3", "h4"],
330
+ format="xlsx",
331
+ )
332
+ assert res is True
333
+ assert exists(xlfilepath), "File should exist"
334
+
335
+ save_iterable(filepath, list_of_tuples, headers=("h1", "h2", "h3", "h4"))
336
+ newll = read_list_from_csv(filepath)
337
+ newld = read_list_from_csv(filepath, headers=1, dict_form=True)
338
+ remove(filepath)
339
+ assert newll == [
340
+ ["h1", "h2", "h3", "h4"],
341
+ ["1", "2", "3", "a"],
342
+ ["4", "5", "6", "b"],
343
+ ["7", "8", "9", "c"],
344
+ ]
345
+ assert newld == [
346
+ {"h1": "1", "h2": "2", "h4": "a", "h3": "3"},
347
+ {"h1": "4", "h2": "5", "h4": "b", "h3": "6"},
348
+ {"h1": "7", "h2": "8", "h4": "c", "h3": "9"},
349
+ ]
350
+ save_iterable(filepath, list_of_lists, headers=["h1", "h2", "h3"])
351
+ newll = read_list_from_csv(filepath)
352
+ remove(filepath)
353
+ assert newll == [
354
+ ["h1", "h2", "h3"],
355
+ ["1", "2", "3"],
356
+ ["4", "5", "6"],
357
+ ["7", "8", "9"],
358
+ ]
359
+ save_iterable(
360
+ filepath,
361
+ list_of_lists,
362
+ headers=["h1", "h3", "h4"],
363
+ columns=[1, 3, 4],
364
+ )
365
+ newll = read_list_from_csv(filepath)
366
+ remove(filepath)
367
+ assert newll == [
368
+ ["h1", "h3", "h4"],
369
+ ["1", "3", "a"],
370
+ ["4", "6", "b"],
371
+ ["7", "9", "c"],
372
+ ]
373
+
374
+ list_of_lists = [
375
+ ["1", "2", "3", "a"],
376
+ ["4", "5", "6", "b"],
377
+ [7, 8, 9, "c"],
378
+ ]
379
+ save_iterable(filepath, list_of_lists)
380
+ newll = read_list_from_csv(filepath)
381
+ remove(filepath)
382
+ assert newll == [
383
+ ["1", "2", "3", "a"],
384
+ ["4", "5", "6", "b"],
385
+ ["7", "8", "9", "c"],
386
+ ]
387
+ save_iterable(filepath, list_of_lists, headers=2)
388
+ newll = read_list_from_csv(filepath)
389
+ remove(filepath)
390
+ assert newll == [
391
+ ["4", "5", "6", "b"],
392
+ ["7", "8", "9", "c"],
393
+ ]
394
+
395
+ list_of_dicts = [
396
+ {"h1": 1, "h2": 2, "h3": 3, "h4": "a"},
397
+ {"h1": 4, "h2": 5, "h3": 6, "h4": "b"},
398
+ {"h1": 7, "h2": 8, "h3": 9, "h4": "c"},
399
+ ]
400
+ save_iterable(filepath, list_of_dicts, headers=["h1", "h2", "h3", "h4"])
401
+ newll = read_list_from_csv(filepath)
402
+ remove(filepath)
403
+ assert newll == [
404
+ ["h1", "h2", "h3", "h4"],
405
+ ["1", "2", "3", "a"],
406
+ ["4", "5", "6", "b"],
407
+ ["7", "8", "9", "c"],
408
+ ]
409
+ save_iterable(filepath, list_of_dicts)
410
+ newll = read_list_from_csv(filepath)
411
+ remove(filepath)
412
+ assert newll == [
413
+ ["h1", "h2", "h3", "h4"],
414
+ ["1", "2", "3", "a"],
415
+ ["4", "5", "6", "b"],
416
+ ["7", "8", "9", "c"],
417
+ ]
418
+ save_iterable(filepath, list_of_dicts, headers=2)
419
+ newll = read_list_from_csv(filepath)
420
+ remove(filepath)
421
+ assert newll == [
422
+ ["1", "2", "3", "a"],
423
+ ["4", "5", "6", "b"],
424
+ ["7", "8", "9", "c"],
425
+ ]
426
+ save_iterable(
427
+ filepath,
428
+ list_of_dicts,
429
+ headers=["h1", "h3", "h4"],
430
+ columns=["h1", "h3", "h4"],
431
+ )
432
+ newll = read_list_from_csv(filepath)
433
+ remove(filepath)
434
+ assert newll == [
435
+ ["h1", "h3", "h4"],
436
+ ["1", "3", "a"],
437
+ ["4", "6", "b"],
438
+ ["7", "9", "c"],
439
+ ]
440
+
441
+ with pytest.raises(ValueError):
442
+ read_list_from_csv(filepath, dict_form=True)
443
+
444
+ res = save_iterable(filepath, [], headers=["h1", "h3", "h4"])
445
+ assert res is False