hdx-python-utilities 4.0.4__tar.gz → 4.0.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 (136) hide show
  1. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/PKG-INFO +76 -1
  2. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/README.md +75 -0
  3. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/_version.py +2 -2
  4. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/base_downloader.py +5 -5
  5. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/dictandlist.py +1 -1
  6. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/downloader.py +17 -15
  7. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/frictionless_wrapper.py +2 -1
  8. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/retriever.py +6 -6
  9. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/saver.py +19 -25
  10. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/url.py +1 -1
  11. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_downloader.py +12 -5
  12. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_saver.py +24 -0
  13. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/uv.lock +453 -2
  14. hdx_python_utilities-4.0.4/CONTRIBUTING.md +0 -68
  15. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/.github/workflows/publish-test.yaml +0 -0
  16. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/.github/workflows/publish.yaml +0 -0
  17. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/.github/workflows/run-python-tests.yaml +0 -0
  18. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/.gitignore +0 -0
  19. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/.pre-commit-config.yaml +0 -0
  20. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/LICENSE +0 -0
  21. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/documentation/.readthedocs.yaml +0 -0
  22. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/documentation/index.md +0 -0
  23. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/documentation/mkdocs.yaml +0 -0
  24. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/pyproject.toml +0 -0
  25. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/__init__.py +0 -0
  26. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/compare.py +0 -0
  27. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/dateparse.py +0 -0
  28. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/easy_logging.py +0 -0
  29. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/email.py +0 -0
  30. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/encoding.py +0 -0
  31. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/error_handler.py +0 -0
  32. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/errors_onexit.py +0 -0
  33. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/file_hashing.py +0 -0
  34. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/html.py +0 -0
  35. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/loader.py +0 -0
  36. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/matching.py +0 -0
  37. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/path.py +0 -0
  38. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/session.py +0 -0
  39. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/state.py +0 -0
  40. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/text.py +0 -0
  41. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/useragent.py +0 -0
  42. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/uuid.py +0 -0
  43. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/src/hdx/utilities/zip_crc.py +0 -0
  44. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/compare/test_csv_processing.csv +0 -0
  45. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/compare/test_csv_processing2.csv +0 -0
  46. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/empty.yaml +0 -0
  47. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/hdx_config.json +0 -0
  48. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/hdx_config.yaml +0 -0
  49. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/hdx_email_configuration.json +0 -0
  50. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
  51. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/json_csv.yaml +0 -0
  52. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/logging_config.json +0 -0
  53. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/logging_config.yaml +0 -0
  54. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/project_configuration.json +0 -0
  55. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/project_configuration.yaml +0 -0
  56. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/smtp_config.json +0 -0
  57. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/smtp_config.yaml +0 -0
  58. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/user_agent_config.yaml +0 -0
  59. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/user_agent_config2.yaml +0 -0
  60. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/user_agent_config3.yaml +0 -0
  61. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
  62. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/basicauth.txt +0 -0
  63. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/bearertoken.txt +0 -0
  64. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/extra_params.json +0 -0
  65. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/extra_params.yaml +0 -0
  66. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/extra_params_tree.yaml +0 -0
  67. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_csv_processing.csv +0 -0
  68. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_csv_processing_blanks.csv +0 -0
  69. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_data.csv +0 -0
  70. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_data.xlsx +0 -0
  71. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_data1.csv/empty.txt +0 -0
  72. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_data2.csv +0 -0
  73. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_json_processing.json +0 -0
  74. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_xls_processing.xls +0 -0
  75. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/downloader/test_xlsx_processing.xlsx +0 -0
  76. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/file_hashing/bad_header.zip +0 -0
  77. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/file_hashing/bad_index.xlsx +0 -0
  78. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/file_hashing/empty.zip +0 -0
  79. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/file_hashing/test.xlsx +0 -0
  80. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/file_hashing/test_shapefile.zip +0 -0
  81. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/file_hashing/valid_sig_invalid_body.zip +0 -0
  82. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/html/response.html +0 -0
  83. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/loader/empty.json +0 -0
  84. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/loader/empty.yaml +0 -0
  85. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/loader/empty_list.json +0 -0
  86. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/fallbacks/test.csv +0 -0
  87. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/fallbacks/test.json +0 -0
  88. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/fallbacks/test.txt +0 -0
  89. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/fallbacks/test.yaml +0 -0
  90. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/retriever-test.csv +0 -0
  91. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/test.csv +0 -0
  92. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/test.json +0 -0
  93. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/test.txt +0 -0
  94. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/test.yaml +0 -0
  95. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/retriever/test_hxl.csv +0 -0
  96. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out.csv +0 -0
  97. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out.json +0 -0
  98. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out2.csv +0 -0
  99. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out2.json +0 -0
  100. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out5.json +0 -0
  101. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out6.json +0 -0
  102. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out7.json +0 -0
  103. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out8.csv +0 -0
  104. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/out8.json +0 -0
  105. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/pretty-false_sortkeys-false.json +0 -0
  106. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/pretty-false_sortkeys-false.yaml +0 -0
  107. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/pretty-false_sortkeys-true.json +0 -0
  108. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/pretty-false_sortkeys-true.yaml +0 -0
  109. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/pretty-true_sortkeys-false.json +0 -0
  110. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/pretty-true_sortkeys-false.yaml +0 -0
  111. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/pretty-true_sortkeys-true.json +0 -0
  112. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/saver/pretty-true_sortkeys-true.yaml +0 -0
  113. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/state/analysis_dates.txt +0 -0
  114. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/state/last_build_date.txt +0 -0
  115. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/fixtures/test_data.csv +0 -0
  116. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/conftest.py +0 -0
  117. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_compare.py +0 -0
  118. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_dateparse.py +0 -0
  119. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_dictandlist.py +0 -0
  120. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_easy_logging.py +0 -0
  121. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_email.py +0 -0
  122. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_encoding.py +0 -0
  123. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_error_handler.py +0 -0
  124. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_file_hashing.py +0 -0
  125. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_html.py +0 -0
  126. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_loader.py +0 -0
  127. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_matching.py +0 -0
  128. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_path.py +0 -0
  129. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_retriever.py +0 -0
  130. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_state.py +0 -0
  131. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_text.py +0 -0
  132. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_url.py +0 -0
  133. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_useragent.py +0 -0
  134. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_uuid.py +0 -0
  135. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/tests/hdx/utilities/test_zip_crc.py +0 -0
  136. {hdx_python_utilities-4.0.4 → hdx_python_utilities-4.0.6}/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: 4.0.4
3
+ Version: 4.0.6
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>
@@ -83,3 +83,78 @@ For more information, please read the [documentation](https://hdx-python-utiliti
83
83
 
84
84
  This library is part of the [Humanitarian Data Exchange](https://data.humdata.org/) (HDX) project. If you have
85
85
  humanitarian related data, please upload your datasets to HDX.
86
+
87
+ # Development
88
+
89
+ ## Environment
90
+
91
+ Development is currently done using Python 3.13. The environment can be created with:
92
+
93
+ ```shell
94
+ uv sync
95
+ ```
96
+
97
+ This creates a .venv folder with the versions specified in the project's uv.lock file.
98
+
99
+ ### Pre-commit
100
+
101
+ pre-commit will be installed when syncing uv. It is run every time you make a git
102
+ commit if you call it like this:
103
+
104
+ ```shell
105
+ pre-commit install
106
+ ```
107
+
108
+ With pre-commit, all code is formatted according to
109
+ [ruff](https://docs.astral.sh/ruff/) guidelines.
110
+
111
+ To check if your changes pass pre-commit without committing, run:
112
+
113
+ ```shell
114
+ pre-commit run --all-files
115
+ ```
116
+
117
+ ## Packages
118
+
119
+ [uv](https://github.com/astral-sh/uv) is used for package management. If
120
+ you’ve introduced a new package to the source code (i.e. anywhere in `src/`),
121
+ please add it to the `project.dependencies` section of `pyproject.toml` with
122
+ any known version constraints.
123
+
124
+ To add packages required only for testing, add them to the
125
+ `[dependency-groups]`.
126
+
127
+ Any changes to the dependencies will be automatically reflected in
128
+ `uv.lock` with `pre-commit`, but you can re-generate the files without committing by
129
+ executing:
130
+
131
+ ```shell
132
+ uv lock --upgrade
133
+ ```
134
+
135
+ ## Project
136
+
137
+ [uv](https://github.com/astral-sh/uv) is used for project management. The project can be
138
+ built using:
139
+
140
+ ```shell
141
+ uv build
142
+ ```
143
+
144
+ Linting and syntax checking can be run with:
145
+
146
+ ```shell
147
+ uv run ruff check
148
+ ```
149
+
150
+ To run the tests and view coverage, execute:
151
+
152
+ ```shell
153
+ uv run pytest
154
+ ```
155
+
156
+ ## Documentation
157
+
158
+ The documentation, including API documentation, is generated using ReadtheDocs and
159
+ MkDocs with Material. As you change the source code, remember to update the
160
+ documentation at `documentation/index.md`.
@@ -30,3 +30,78 @@ For more information, please read the [documentation](https://hdx-python-utiliti
30
30
 
31
31
  This library is part of the [Humanitarian Data Exchange](https://data.humdata.org/) (HDX) project. If you have
32
32
  humanitarian related data, please upload your datasets to HDX.
33
+
34
+ # Development
35
+
36
+ ## Environment
37
+
38
+ Development is currently done using Python 3.13. The environment can be created with:
39
+
40
+ ```shell
41
+ uv sync
42
+ ```
43
+
44
+ This creates a .venv folder with the versions specified in the project's uv.lock file.
45
+
46
+ ### Pre-commit
47
+
48
+ pre-commit will be installed when syncing uv. It is run every time you make a git
49
+ commit if you call it like this:
50
+
51
+ ```shell
52
+ pre-commit install
53
+ ```
54
+
55
+ With pre-commit, all code is formatted according to
56
+ [ruff](https://docs.astral.sh/ruff/) guidelines.
57
+
58
+ To check if your changes pass pre-commit without committing, run:
59
+
60
+ ```shell
61
+ pre-commit run --all-files
62
+ ```
63
+
64
+ ## Packages
65
+
66
+ [uv](https://github.com/astral-sh/uv) is used for package management. If
67
+ you’ve introduced a new package to the source code (i.e. anywhere in `src/`),
68
+ please add it to the `project.dependencies` section of `pyproject.toml` with
69
+ any known version constraints.
70
+
71
+ To add packages required only for testing, add them to the
72
+ `[dependency-groups]`.
73
+
74
+ Any changes to the dependencies will be automatically reflected in
75
+ `uv.lock` with `pre-commit`, but you can re-generate the files without committing by
76
+ executing:
77
+
78
+ ```shell
79
+ uv lock --upgrade
80
+ ```
81
+
82
+ ## Project
83
+
84
+ [uv](https://github.com/astral-sh/uv) is used for project management. The project can be
85
+ built using:
86
+
87
+ ```shell
88
+ uv build
89
+ ```
90
+
91
+ Linting and syntax checking can be run with:
92
+
93
+ ```shell
94
+ uv run ruff check
95
+ ```
96
+
97
+ To run the tests and view coverage, execute:
98
+
99
+ ```shell
100
+ uv run pytest
101
+ ```
102
+
103
+ ## Documentation
104
+
105
+ The documentation, including API documentation, is generated using ReadtheDocs and
106
+ MkDocs with Material. As you change the source code, remember to update the
107
+ documentation at `documentation/index.md`.
@@ -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.4'
32
- __version_tuple__ = version_tuple = (4, 0, 4)
31
+ __version__ = version = '4.0.6'
32
+ __version_tuple__ = version_tuple = (4, 0, 6)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -35,7 +35,7 @@ class BaseDownload(ABC):
35
35
  """
36
36
 
37
37
  @abstractmethod
38
- def download_file(self, url: str, *args: Any, **kwargs: Any) -> Path:
38
+ def download_file(self, url: Path | str, *args: Any, **kwargs: Any) -> Path:
39
39
  """Download file from url.
40
40
 
41
41
  Args:
@@ -48,7 +48,7 @@ class BaseDownload(ABC):
48
48
  """
49
49
 
50
50
  @abstractmethod
51
- def download_text(self, url: str, *args: Any, **kwargs: Any) -> str:
51
+ def download_text(self, url: Path | str, *args: Any, **kwargs: Any) -> str:
52
52
  """Download text from url.
53
53
 
54
54
  Args:
@@ -61,7 +61,7 @@ class BaseDownload(ABC):
61
61
  """
62
62
 
63
63
  @abstractmethod
64
- def download_yaml(self, url: str, *args: Any, **kwargs: Any) -> Any:
64
+ def download_yaml(self, url: Path | str, *args: Any, **kwargs: Any) -> Any:
65
65
  """Download YAML from url.
66
66
 
67
67
  Args:
@@ -74,7 +74,7 @@ class BaseDownload(ABC):
74
74
  """
75
75
 
76
76
  @abstractmethod
77
- def download_json(self, url: str, *args: Any, **kwargs: Any) -> Any:
77
+ def download_json(self, url: Path | str, *args: Any, **kwargs: Any) -> Any:
78
78
  """Download JSON from url.
79
79
 
80
80
  Args:
@@ -89,7 +89,7 @@ class BaseDownload(ABC):
89
89
  @abstractmethod
90
90
  def get_tabular_rows(
91
91
  self,
92
- url: str | Sequence[str],
92
+ url: Path | str | Sequence[str],
93
93
  has_hxl: bool = False,
94
94
  headers: int | Sequence[int] | Sequence[str] = 1,
95
95
  dict_form: bool = False,
@@ -374,7 +374,7 @@ def avg_dicts(
374
374
 
375
375
 
376
376
  def read_list_from_csv(
377
- url: str,
377
+ url: Path | str,
378
378
  headers: int | Sequence[int] | Sequence[str] | None = None,
379
379
  dict_form: bool = False,
380
380
  **kwargs: Any,
@@ -129,7 +129,7 @@ class Download(BaseDownload):
129
129
  """
130
130
  self.close()
131
131
 
132
- def get_full_url(self, url: str) -> str:
132
+ def get_full_url(self, url: Path | str) -> str:
133
133
  """Get full url including any additional parameters added to the session.
134
134
 
135
135
  Args:
@@ -193,14 +193,16 @@ class Download(BaseDownload):
193
193
  self.close_response()
194
194
  self.response = None
195
195
  try:
196
- url = str(url)
197
- spliturl = urlsplit(url)
198
- if not spliturl.scheme:
199
- if isfile(url):
200
- url = Path(url).resolve().as_uri()
201
- else:
202
- spliturl = spliturl._replace(scheme="https")
203
- url = urlunsplit(spliturl)
196
+ if isinstance(url, Path):
197
+ url = url.resolve().as_uri()
198
+ else:
199
+ spliturl = urlsplit(url)
200
+ if not spliturl.scheme:
201
+ if isfile(url):
202
+ url = Path(url).resolve().as_uri()
203
+ else:
204
+ spliturl = spliturl._replace(scheme="https")
205
+ url = urlunsplit(spliturl)
204
206
  if post:
205
207
  full_url, parameters = get_url_params_for_post(url, parameters)
206
208
  if json_string:
@@ -671,7 +673,7 @@ class Download(BaseDownload):
671
673
 
672
674
  def get_tabular_rows(
673
675
  self,
674
- url: str | Sequence[str],
676
+ url: Path | str | Sequence[str],
675
677
  has_hxl: bool = False,
676
678
  headers: int | Sequence[int] | Sequence[str] = 1,
677
679
  dict_form: bool = False,
@@ -778,7 +780,7 @@ class Download(BaseDownload):
778
780
 
779
781
  def get_tabular_rows_as_list(
780
782
  self,
781
- url: str | Sequence[str],
783
+ url: Path | str | Sequence[str],
782
784
  has_hxl: bool = False,
783
785
  headers: int | Sequence[int] | Sequence[str] = 1,
784
786
  include_headers: bool = True,
@@ -854,7 +856,7 @@ class Download(BaseDownload):
854
856
 
855
857
  def get_tabular_rows_as_dict(
856
858
  self,
857
- url: str | Sequence[str],
859
+ url: Path | str | Sequence[str],
858
860
  has_hxl: bool = False,
859
861
  headers: int | Sequence[int] | Sequence[str] = 1,
860
862
  ignore_blank_rows: bool = True,
@@ -928,7 +930,7 @@ class Download(BaseDownload):
928
930
 
929
931
  def download_tabular_key_value(
930
932
  self,
931
- url: str | Sequence[str],
933
+ url: Path | str | Sequence[str],
932
934
  has_hxl: bool = False,
933
935
  headers: int | Sequence[int] | Sequence[str] = 1,
934
936
  include_headers: bool = True,
@@ -1008,7 +1010,7 @@ class Download(BaseDownload):
1008
1010
 
1009
1011
  def download_tabular_rows_as_dicts(
1010
1012
  self,
1011
- url: str | Sequence[str],
1013
+ url: Path | str | Sequence[str],
1012
1014
  has_hxl: bool = False,
1013
1015
  headers: int | Sequence[int] | Sequence[str] = 1,
1014
1016
  keycolumn: int = 1,
@@ -1092,7 +1094,7 @@ class Download(BaseDownload):
1092
1094
 
1093
1095
  def download_tabular_cols_as_dicts(
1094
1096
  self,
1095
- url: str | Sequence[str],
1097
+ url: Path | str | Sequence[str],
1096
1098
  has_hxl: bool = False,
1097
1099
  headers: int | Sequence[int] | Sequence[str] = 1,
1098
1100
  keycolumn: int = 1,
@@ -1,5 +1,6 @@
1
1
  """Frictionless wrapper"""
2
2
 
3
+ from pathlib import Path
3
4
  from typing import Any
4
5
 
5
6
  import requests
@@ -119,7 +120,7 @@ def get_frictionless_dialect(
119
120
 
120
121
 
121
122
  def get_frictionless_tableresource(
122
- url: str | None = None,
123
+ url: Path | str | None = None,
123
124
  ignore_blank_rows: bool = True,
124
125
  infer_types: bool = False,
125
126
  session: requests.Session | None = None,
@@ -120,7 +120,7 @@ class Retrieve(BaseDownload):
120
120
 
121
121
  def get_filename(
122
122
  self,
123
- url: str,
123
+ url: Path | str,
124
124
  filename: str | None = None,
125
125
  possible_extensions: tuple[str, ...] = tuple(),
126
126
  **kwargs: Any,
@@ -180,7 +180,7 @@ class Retrieve(BaseDownload):
180
180
 
181
181
  def download_file(
182
182
  self,
183
- url: str,
183
+ url: Path | str,
184
184
  filename: str | None = None,
185
185
  logstr: str | None = None,
186
186
  fallback: bool = False,
@@ -231,7 +231,7 @@ class Retrieve(BaseDownload):
231
231
 
232
232
  def download_text(
233
233
  self,
234
- url: str,
234
+ url: Path | str,
235
235
  filename: str | None = None,
236
236
  logstr: str | None = None,
237
237
  fallback: bool = False,
@@ -282,7 +282,7 @@ class Retrieve(BaseDownload):
282
282
 
283
283
  def download_yaml(
284
284
  self,
285
- url: str,
285
+ url: Path | str,
286
286
  filename: str | None = None,
287
287
  logstr: str | None = None,
288
288
  fallback: bool = False,
@@ -333,7 +333,7 @@ class Retrieve(BaseDownload):
333
333
 
334
334
  def download_json(
335
335
  self,
336
- url: str,
336
+ url: Path | str,
337
337
  filename: str | None = None,
338
338
  logstr: str | None = None,
339
339
  fallback: bool = False,
@@ -384,7 +384,7 @@ class Retrieve(BaseDownload):
384
384
 
385
385
  def get_tabular_rows(
386
386
  self,
387
- url: str | Sequence[str],
387
+ url: Path | str | Sequence[str],
388
388
  has_hxl: bool = False,
389
389
  headers: int | Sequence[int] | Sequence[str] = 1,
390
390
  dict_form: bool = False,
@@ -281,10 +281,13 @@ def save_iterable(
281
281
  row_function: Callable[[dict], dict | None] | None = None,
282
282
  no_empty: bool = True,
283
283
  ) -> list:
284
- """Save an iterable of rows in dict or list form to a csv. (The headers
284
+ """Save an iterable of rows in dict or list form to a csv. The headers
285
285
  argument is either a row number (rows start counting at 1), or the actual
286
- headers defined as a list of strings. If not set, all rows will be treated
287
- as containing values.)
286
+ headers defined as a list of strings, the order of which defines the column order.
287
+ Columns not named in that list of strings will be dropped. If headers is not set,
288
+ all rows will be treated as containing values. The columns argument defines which
289
+ columns will be output. It can be used in conjunction with an integer headers with
290
+ columns selecting the columns and hedders selecting the starting row.
288
291
 
289
292
  Args:
290
293
  filepath: Path to write to
@@ -327,32 +330,23 @@ def save_iterable(
327
330
  if isinstance(row, dict):
328
331
  has_header = True
329
332
  row = row_function(row)
333
+ while row is None:
334
+ row = next(rows)
335
+ row = row_function(row)
330
336
  if columns:
331
- if row is not None:
332
- newrow = {}
333
- for column in columns:
334
- if column in row:
335
- newrow[column] = row[column]
336
- newrows.append(newrow)
337
- for row in rows:
338
- row = row_function(row)
339
- if row is None:
340
- continue
341
- newrow = {}
342
- for column in columns:
343
- if column in row:
344
- newrow[column] = row[column]
345
- newrows.append(newrow)
337
+ row = {k: row[k] for k in columns if k in row}
338
+ newrows.append(row)
346
339
  if headers is None:
347
340
  headers = columns
348
341
  else:
349
- if row is not None:
350
- newrows.append(row)
351
- for row in rows:
352
- row = row_function(row)
353
- if row is None:
354
- continue
355
- newrows.append(row)
342
+ if headers and isinstance(headers, list):
343
+ row = {k: row.get(k) for k in headers}
344
+ newrows.append(row)
345
+ for row in rows:
346
+ row = row_function(row)
347
+ if row is None:
348
+ continue
349
+ newrows.append(row)
356
350
  else:
357
351
  if headers is None:
358
352
  headers = 1
@@ -62,7 +62,7 @@ def get_filename_from_url(
62
62
 
63
63
 
64
64
  def get_path_for_url(
65
- url: str,
65
+ url: Path | str,
66
66
  folder: Path | str | None = None,
67
67
  filename: str | None = None,
68
68
  path: Path | str | None = None,
@@ -48,6 +48,10 @@ class TestDownloader:
48
48
  def fixturefile(self, downloaderfolder):
49
49
  return downloaderfolder / "extra_params_tree.yaml"
50
50
 
51
+ @pytest.fixture(scope="class")
52
+ def fixturepath(self, downloaderfolder):
53
+ return downloaderfolder / "test_csv_processing.csv"
54
+
51
55
  @pytest.fixture(scope="class")
52
56
  def fixtureurlexcel(self):
53
57
  return "https://raw.githubusercontent.com/OCHA-DAP/hdx-python-utilities/main/tests/fixtures/downloader/test_data.xlsx"
@@ -595,7 +599,9 @@ class TestDownloader:
595
599
  {"la1": "gas", "ha1": "2", "ba1": "6.5", "ma1": "'n/a'"},
596
600
  ]
597
601
 
598
- def test_get_tabular_rows(self, fixtureprocessurl, fixtureprocessurlblank):
602
+ def test_get_tabular_rows(
603
+ self, fixturepath, fixtureprocessurl, fixtureprocessurlblank
604
+ ):
599
605
  with Download() as downloader:
600
606
  expected = [
601
607
  ["la1", "ha1", "ba1", "ma1"],
@@ -604,6 +610,9 @@ class TestDownloader:
604
610
  ["gas", "2", "6.5", "'n/a'"],
605
611
  ]
606
612
  expected_headers = expected[0]
613
+ headers, iterator = downloader.get_tabular_rows(fixturepath)
614
+ assert headers == expected_headers
615
+ assert list(iterator) == expected[1:]
607
616
  headers, iterator = downloader.get_tabular_rows(fixtureprocessurl)
608
617
  assert headers == expected_headers
609
618
  assert list(iterator) == expected[1:]
@@ -1271,11 +1280,9 @@ class TestDownloader:
1271
1280
  ["AFG", "Afghanistan", 38041754],
1272
1281
  ]
1273
1282
 
1274
- def test_download_tabular_rows_as_dicts(self, fixtureprocessurl):
1283
+ def test_download_tabular_rows_as_dicts(self, fixturepath, fixtureprocessurl):
1275
1284
  with Download() as downloader:
1276
- result = downloader.download_tabular_rows_as_dicts(
1277
- fixtureprocessurl, headers=2
1278
- )
1285
+ result = downloader.download_tabular_rows_as_dicts(fixturepath, headers=2)
1279
1286
  assert result == {
1280
1287
  "coal": {
1281
1288
  "header2": "3",
@@ -353,6 +353,12 @@ class TestLoader:
353
353
  ["5", "6", "4"],
354
354
  ["8", "9", "7"],
355
355
  ]
356
+ save_iterable(
357
+ str(filepath), list_of_dicts, headers=2, columns=["h2", "h3", "h1"]
358
+ )
359
+ newll = read_list_from_csv(filepath)
360
+ remove(filepath)
361
+ assert newll == [["2", "3", "1"], ["5", "6", "4"], ["8", "9", "7"]]
356
362
 
357
363
  xlfilepath = filepath.with_suffix(".xlsx")
358
364
  rows = save_iterable(
@@ -465,6 +471,24 @@ class TestLoader:
465
471
  ["4", "5", "6", "b"],
466
472
  ["7", "8", "9", "c"],
467
473
  ]
474
+ save_iterable(filepath, list_of_dicts, headers=["h2", "h1", "h4", "h3"])
475
+ newll = read_list_from_csv(filepath)
476
+ remove(filepath)
477
+ assert newll == [
478
+ ["h2", "h1", "h4", "h3"],
479
+ ["2", "1", "a", "3"],
480
+ ["5", "4", "b", "6"],
481
+ ["8", "7", "c", "9"],
482
+ ]
483
+ save_iterable(filepath, list_of_dicts, headers=["h2", "h1", "h4"])
484
+ newll = read_list_from_csv(filepath)
485
+ remove(filepath)
486
+ assert newll == [
487
+ ["h2", "h1", "h4"],
488
+ ["2", "1", "a"],
489
+ ["5", "4", "b"],
490
+ ["8", "7", "c"],
491
+ ]
468
492
  save_iterable(filepath, list_of_dicts)
469
493
  newll = read_list_from_csv(filepath)
470
494
  remove(filepath)