hdx-python-api 6.7.4__tar.gz → 6.7.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 (152) hide show
  1. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/PKG-INFO +3 -3
  2. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/pyproject.toml +2 -2
  3. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/_version.py +2 -2
  4. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/resource.py +30 -2
  5. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_resource.py +38 -0
  6. hdx_python_api-6.7.6/uv.lock +3203 -0
  7. hdx_python_api-6.7.4/uv.lock +0 -2309
  8. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/.github/workflows/publish.yaml +0 -0
  9. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/.github/workflows/run-python-tests.yaml +0 -0
  10. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/.gitignore +0 -0
  11. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/.pre-commit-config.yaml +0 -0
  12. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/CLAUDE.md +0 -0
  13. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/LICENSE +0 -0
  14. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/README.md +0 -0
  15. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/documentation/.readthedocs.yaml +0 -0
  16. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/documentation/index.md +0 -0
  17. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/documentation/mkdocs.yaml +0 -0
  18. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/__init__.py +0 -0
  19. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/configuration.py +0 -0
  20. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/hdx_base_configuration.yaml +0 -0
  21. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/locations.py +0 -0
  22. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/remotehdx.py +0 -0
  23. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/utilities/__init__.py +0 -0
  24. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/utilities/date_helper.py +0 -0
  25. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/utilities/filestore_helper.py +0 -0
  26. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/utilities/hdx_error_handler.py +0 -0
  27. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/utilities/hdx_state.py +0 -0
  28. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/utilities/hdx_stats.py +0 -0
  29. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/api/utilities/url_utils.py +0 -0
  30. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/__init__.py +0 -0
  31. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/activity.py +0 -0
  32. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/dataset.py +0 -0
  33. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/hdxobject.py +0 -0
  34. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/location.py +0 -0
  35. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/organization.py +0 -0
  36. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/resource_matcher.py +0 -0
  37. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/resource_view.py +0 -0
  38. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/showcase.py +0 -0
  39. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/user.py +0 -0
  40. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/data/vocabulary.py +0 -0
  41. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/facades/__init__.py +0 -0
  42. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/facades/infer_arguments.py +0 -0
  43. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/facades/keyword_arguments.py +0 -0
  44. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/src/hdx/facades/simple.py +0 -0
  45. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/__init__.py +0 -0
  46. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/Accepted_Tags.csv +0 -0
  47. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/CKAN/hdx_dataset_static.yaml +0 -0
  48. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/Tag_Mapping.csv +0 -0
  49. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/Tag_Mapping_ChainRuleError.csv +0 -0
  50. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/empty.yaml +0 -0
  51. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_base_config.json +0 -0
  52. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_base_config.yaml +0 -0
  53. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_config.json +0 -0
  54. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_config.yaml +0 -0
  55. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_dataset_static.json +0 -0
  56. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_dataset_static.yaml +0 -0
  57. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_datasource_topline.json +0 -0
  58. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_datasource_topline.yaml +0 -0
  59. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_email_configuration.json +0 -0
  60. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
  61. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_missing_site_config.json +0 -0
  62. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_organization_static.json +0 -0
  63. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_organization_static.yaml +0 -0
  64. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_resource_static.json +0 -0
  65. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_resource_static.yaml +0 -0
  66. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_resource_view_static.json +0 -0
  67. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_resource_view_static.yaml +0 -0
  68. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_showcase_static.json +0 -0
  69. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_showcase_static.yaml +0 -0
  70. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_user_static.json +0 -0
  71. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_user_static.yaml +0 -0
  72. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_vocabulary_static.json +0 -0
  73. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/hdx_vocabulary_static.yaml +0 -0
  74. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/project_configuration.json +0 -0
  75. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/project_configuration.yaml +0 -0
  76. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/user_agent_config.yaml +0 -0
  77. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/user_agent_config2.yaml +0 -0
  78. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/user_agent_config3.yaml +0 -0
  79. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
  80. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/dataset_search_results.yaml +0 -0
  81. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/download_gen_resource/conflict_data_alg.csv +0 -0
  82. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/download_gen_resource/min_qc_conflict_data_alg.csv +0 -0
  83. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/download_gen_resource/qc_conflict_data_alg.csv +0 -0
  84. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/download_gen_resource/test_data_no_data.csv +0 -0
  85. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/download_gen_resource/test_data_no_years.csv +0 -0
  86. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/empty.csv +0 -0
  87. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/gen_resource/conflict_data_alg.csv +0 -0
  88. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/gen_resource/min_qc_conflict_data_alg.csv +0 -0
  89. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/gen_resource/qc_conflict_data_alg.csv +0 -0
  90. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/gen_resource/test_data_no_data.csv +0 -0
  91. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/gen_resource/test_data_no_years.csv +0 -0
  92. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/location/location_list_all_fields.yaml +0 -0
  93. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/location/location_show_results.yaml +0 -0
  94. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/organization/organization_list_all_fields.yaml +0 -0
  95. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/organization/organization_show_results.yaml +0 -0
  96. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/qc_from_rows/qc_conflict_data_alg.csv +0 -0
  97. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/qc_from_rows/qc_conflict_data_alg_one_col.csv +0 -0
  98. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/resource_formats.json +0 -0
  99. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/showcase_all_search_results.yaml +0 -0
  100. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/size_hash/ACLED-All-Africa-File_20170101-to-20170708.xlsx +0 -0
  101. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/state/analysis_dates.txt +0 -0
  102. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/state/last_build_date.txt +0 -0
  103. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/test_data.csv +0 -0
  104. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/test_data.zip +0 -0
  105. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/dem_data_zwe.csv +0 -0
  106. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/dem_indicatorlist_zwe.csv +0 -0
  107. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/expected_resources_to_update.json +0 -0
  108. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/opri_data_zwe.csv +0 -0
  109. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/opri_indicatorlist_zwe.csv +0 -0
  110. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/opri_metadata_zwe.csv +0 -0
  111. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/qc_sdg_data_zwe.csv +0 -0
  112. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/sdg_data_zwe.csv +0 -0
  113. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/sdg_indicatorlist_zwe.csv +0 -0
  114. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/sdg_metadata_zwe.csv +0 -0
  115. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/unesco_dataset.json +0 -0
  116. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_dataset_resources/unesco_update_dataset.json +0 -0
  117. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_logic/update_logic_resources.yaml +0 -0
  118. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/fixtures/update_logic/update_logic_resources_new.yaml +0 -0
  119. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/__init__.py +0 -0
  120. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/__init__.py +0 -0
  121. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/test_ckan.py +0 -0
  122. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/test_configuration.py +0 -0
  123. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/test_locations.py +0 -0
  124. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/utilities/__init__.py +0 -0
  125. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/utilities/test_filestore_helper.py +0 -0
  126. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/utilities/test_hdx_error_handler.py +0 -0
  127. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/utilities/test_hdx_state.py +0 -0
  128. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/utilities/test_hdx_stats.py +0 -0
  129. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/api/utilities/test_url_utils.py +0 -0
  130. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/conftest.py +0 -0
  131. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/__init__.py +0 -0
  132. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_activity.py +0 -0
  133. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_dataset_add_hapi_error.py +0 -0
  134. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_dataset_core.py +0 -0
  135. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_dataset_noncore.py +0 -0
  136. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_dataset_resource_generation.py +0 -0
  137. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_location.py +0 -0
  138. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_organization.py +0 -0
  139. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_resource_view.py +0 -0
  140. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_showcase.py +0 -0
  141. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_update_dataset_resources.py +0 -0
  142. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_update_logic.py +0 -0
  143. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_user.py +0 -0
  144. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/data/test_vocabulary.py +0 -0
  145. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/facades/__init__.py +0 -0
  146. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/facades/test_infer_arguments.py +0 -0
  147. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/facades/test_keyword_arguments.py +0 -0
  148. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/tests/hdx/facades/test_simple.py +0 -0
  149. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/workingexample/my_code.py +0 -0
  150. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/workingexample/my_resource.csv +0 -0
  151. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/workingexample/my_resource.xlsx +0 -0
  152. {hdx_python_api-6.7.4 → hdx_python_api-6.7.6}/workingexample/run.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hdx-python-api
3
- Version: 6.7.4
3
+ Version: 6.7.6
4
4
  Summary: HDX Python API for interacting with the Humanitarian Data Exchange
5
5
  Project-URL: Homepage, https://github.com/OCHA-DAP/hdx-python-api
6
6
  Author: Michael Rans
@@ -28,8 +28,8 @@ Requires-Python: >=3.10
28
28
  Requires-Dist: ckanapi>=4.11
29
29
  Requires-Dist: defopt>=7.0.0
30
30
  Requires-Dist: email-validator
31
- Requires-Dist: hdx-python-country>=4.2.3
32
- Requires-Dist: hdx-python-utilities>=4.2.1
31
+ Requires-Dist: hdx-python-country>=4.2.5
32
+ Requires-Dist: hdx-python-utilities>=4.2.4
33
33
  Requires-Dist: makefun
34
34
  Requires-Dist: requests
35
35
  Provides-Extra: docs
@@ -39,8 +39,8 @@ dependencies = [
39
39
  "ckanapi>=4.11",
40
40
  "defopt>=7.0.0",
41
41
  "email_validator",
42
- "hdx-python-country>=4.2.3",
43
- "hdx-python-utilities>=4.2.1",
42
+ "hdx-python-country>=4.2.5",
43
+ "hdx-python-utilities>=4.2.4",
44
44
  "makefun",
45
45
  "requests",
46
46
  ]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '6.7.4'
22
- __version_tuple__ = version_tuple = (6, 7, 4)
21
+ __version__ = version = '6.7.6'
22
+ __version_tuple__ = version_tuple = (6, 7, 6)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -4,13 +4,15 @@ import json
4
4
  import logging
5
5
  from collections.abc import Iterator, Sequence
6
6
  from datetime import datetime
7
- from pathlib import Path
7
+ from pathlib import Path, PureWindowsPath
8
8
  from typing import Any, Optional
9
+ from urllib.parse import urlsplit
9
10
 
10
11
  from hdx.utilities.dateparse import now_utc, now_utc_notz, parse_date
11
12
  from hdx.utilities.downloader import Download
12
13
  from hdx.utilities.file_hashing import get_size_and_hash
13
14
  from hdx.utilities.retriever import Retrieve
15
+ from hdx.utilities.session import get_session
14
16
  from hdx.utilities.uuid import is_valid_uuid
15
17
 
16
18
  import hdx.api.utilities.url_utils
@@ -755,6 +757,22 @@ class Resource(HDXObject):
755
757
  logger.debug(result)
756
758
  return resources
757
759
 
760
+ @staticmethod
761
+ def _get_safe_filename(filename: str) -> str:
762
+ """Strip any directory components (/ or \\) from a filename built from
763
+ resource metadata so downloads cannot be written outside the folder
764
+
765
+ Args:
766
+ filename: Filename to make safe
767
+
768
+ Returns:
769
+ Filename without directory components
770
+ """
771
+ safe_filename = PureWindowsPath(filename).name
772
+ if safe_filename in ("", ".", ".."):
773
+ raise HDXError(f"Invalid filename {filename} for download!")
774
+ return safe_filename
775
+
758
776
  def _download_file(
759
777
  self,
760
778
  url: str,
@@ -773,7 +791,17 @@ class Resource(HDXObject):
773
791
  Returns:
774
792
  Path to downloaded file
775
793
  """
776
- session = hdx.api.utilities.url_utils.get_ckan_ready_session(self.configuration)
794
+ # Only send the HDX API key to the HDX site, not to external resource URLs
795
+ hdx_site_url = self.configuration.get_hdx_site_url()
796
+ if urlsplit(url)[:2] == urlsplit(hdx_site_url)[:2]:
797
+ session = hdx.api.utilities.url_utils.get_ckan_ready_session(
798
+ self.configuration
799
+ )
800
+ else:
801
+ session = get_session(
802
+ full_agent=self.configuration.get_user_agent(), use_env=False
803
+ )
804
+ filename = self._get_safe_filename(filename)
777
805
  with Download(session=session) as downloader:
778
806
  if retriever:
779
807
  downloader = retriever.clone(downloader)
@@ -1225,6 +1225,44 @@ class TestResource:
1225
1225
  with pytest.raises(DownloadError):
1226
1226
  resource2.download()
1227
1227
 
1228
+ def test_download_security(self, configuration, read, monkeypatch):
1229
+ downloads = []
1230
+
1231
+ def download_file(self, url, folder=None, filename=None, path=None, **kwargs):
1232
+ downloads.append((self.session.headers.get("Authorization"), filename))
1233
+ return Path(folder or "tmp") / filename
1234
+
1235
+ monkeypatch.setattr(Download, "download_file", download_file)
1236
+ Configuration.read().set_api_key("12345")
1237
+ resource = Resource.read_from_hdx("74b74ae1-df0c-4716-829f-4f939a046811")
1238
+ resource.download()
1239
+ site_url = resource.configuration.get_hdx_site_url()
1240
+ resource["url"] = f"{site_url}/dataset/x/resource/y/download/a.csv"
1241
+ resource.download()
1242
+ resource["url"] = site_url.replace("https://", "http://")
1243
+ resource.download()
1244
+ assert downloads == [
1245
+ (None, "MyResource1.csv"),
1246
+ ("12345", "MyResource1.csv"),
1247
+ (None, "MyResource1.csv"),
1248
+ ]
1249
+
1250
+ downloads.clear()
1251
+ resource["name"] = "/home/user/.bashrc"
1252
+ resource.download()
1253
+ resource["name"] = "..\\..\\evil"
1254
+ resource.download_datastore()
1255
+ resource["name"] = "evil"
1256
+ resource["format"] = "csv/../../x"
1257
+ resource.download()
1258
+ assert [filename for _, filename in downloads] == [
1259
+ ".bashrc.csv",
1260
+ "evil.csv",
1261
+ "x",
1262
+ ]
1263
+ with pytest.raises(HDXError):
1264
+ Resource._get_safe_filename("a/..")
1265
+
1228
1266
  def test_download_datastore(self, configuration, read, monkeypatch):
1229
1267
  downloads = []
1230
1268