hdx-python-api 6.7.3__tar.gz → 6.7.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 (152) hide show
  1. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/PKG-INFO +1 -1
  2. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/documentation/index.md +79 -0
  3. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/_version.py +2 -2
  4. hdx_python_api-6.7.4/src/hdx/api/utilities/hdx_stats.py +86 -0
  5. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/dataset.py +137 -0
  6. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/location.py +43 -0
  7. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/resource.py +174 -0
  8. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/utilities/test_hdx_stats.py +32 -2
  9. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_dataset_noncore.py +104 -0
  10. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_location.py +65 -0
  11. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_resource.py +129 -0
  12. hdx_python_api-6.7.3/src/hdx/api/utilities/hdx_stats.py +0 -47
  13. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/.github/workflows/publish.yaml +0 -0
  14. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/.github/workflows/run-python-tests.yaml +0 -0
  15. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/.gitignore +0 -0
  16. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/.pre-commit-config.yaml +0 -0
  17. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/CLAUDE.md +0 -0
  18. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/LICENSE +0 -0
  19. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/README.md +0 -0
  20. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/documentation/.readthedocs.yaml +0 -0
  21. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/documentation/mkdocs.yaml +0 -0
  22. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/pyproject.toml +0 -0
  23. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/__init__.py +0 -0
  24. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/configuration.py +0 -0
  25. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/hdx_base_configuration.yaml +0 -0
  26. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/locations.py +0 -0
  27. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/remotehdx.py +0 -0
  28. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/utilities/__init__.py +0 -0
  29. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/utilities/date_helper.py +0 -0
  30. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/utilities/filestore_helper.py +0 -0
  31. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/utilities/hdx_error_handler.py +0 -0
  32. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/utilities/hdx_state.py +0 -0
  33. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/api/utilities/url_utils.py +0 -0
  34. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/__init__.py +0 -0
  35. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/activity.py +0 -0
  36. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/hdxobject.py +0 -0
  37. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/organization.py +0 -0
  38. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/resource_matcher.py +0 -0
  39. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/resource_view.py +0 -0
  40. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/showcase.py +0 -0
  41. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/user.py +0 -0
  42. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/data/vocabulary.py +0 -0
  43. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/facades/__init__.py +0 -0
  44. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/facades/infer_arguments.py +0 -0
  45. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/facades/keyword_arguments.py +0 -0
  46. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/src/hdx/facades/simple.py +0 -0
  47. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/__init__.py +0 -0
  48. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/Accepted_Tags.csv +0 -0
  49. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/CKAN/hdx_dataset_static.yaml +0 -0
  50. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/Tag_Mapping.csv +0 -0
  51. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/Tag_Mapping_ChainRuleError.csv +0 -0
  52. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/empty.yaml +0 -0
  53. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_base_config.json +0 -0
  54. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_base_config.yaml +0 -0
  55. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_config.json +0 -0
  56. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_config.yaml +0 -0
  57. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_dataset_static.json +0 -0
  58. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_dataset_static.yaml +0 -0
  59. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_datasource_topline.json +0 -0
  60. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_datasource_topline.yaml +0 -0
  61. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_email_configuration.json +0 -0
  62. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
  63. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_missing_site_config.json +0 -0
  64. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_organization_static.json +0 -0
  65. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_organization_static.yaml +0 -0
  66. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_resource_static.json +0 -0
  67. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_resource_static.yaml +0 -0
  68. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_resource_view_static.json +0 -0
  69. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_resource_view_static.yaml +0 -0
  70. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_showcase_static.json +0 -0
  71. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_showcase_static.yaml +0 -0
  72. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_user_static.json +0 -0
  73. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_user_static.yaml +0 -0
  74. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_vocabulary_static.json +0 -0
  75. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/hdx_vocabulary_static.yaml +0 -0
  76. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/project_configuration.json +0 -0
  77. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/project_configuration.yaml +0 -0
  78. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/user_agent_config.yaml +0 -0
  79. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/user_agent_config2.yaml +0 -0
  80. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/user_agent_config3.yaml +0 -0
  81. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
  82. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/dataset_search_results.yaml +0 -0
  83. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/download_gen_resource/conflict_data_alg.csv +0 -0
  84. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/download_gen_resource/min_qc_conflict_data_alg.csv +0 -0
  85. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/download_gen_resource/qc_conflict_data_alg.csv +0 -0
  86. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/download_gen_resource/test_data_no_data.csv +0 -0
  87. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/download_gen_resource/test_data_no_years.csv +0 -0
  88. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/empty.csv +0 -0
  89. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/gen_resource/conflict_data_alg.csv +0 -0
  90. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/gen_resource/min_qc_conflict_data_alg.csv +0 -0
  91. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/gen_resource/qc_conflict_data_alg.csv +0 -0
  92. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/gen_resource/test_data_no_data.csv +0 -0
  93. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/gen_resource/test_data_no_years.csv +0 -0
  94. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/location/location_list_all_fields.yaml +0 -0
  95. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/location/location_show_results.yaml +0 -0
  96. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/organization/organization_list_all_fields.yaml +0 -0
  97. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/organization/organization_show_results.yaml +0 -0
  98. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/qc_from_rows/qc_conflict_data_alg.csv +0 -0
  99. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/qc_from_rows/qc_conflict_data_alg_one_col.csv +0 -0
  100. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/resource_formats.json +0 -0
  101. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/showcase_all_search_results.yaml +0 -0
  102. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/size_hash/ACLED-All-Africa-File_20170101-to-20170708.xlsx +0 -0
  103. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/state/analysis_dates.txt +0 -0
  104. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/state/last_build_date.txt +0 -0
  105. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/test_data.csv +0 -0
  106. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/test_data.zip +0 -0
  107. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/dem_data_zwe.csv +0 -0
  108. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/dem_indicatorlist_zwe.csv +0 -0
  109. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/expected_resources_to_update.json +0 -0
  110. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/opri_data_zwe.csv +0 -0
  111. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/opri_indicatorlist_zwe.csv +0 -0
  112. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/opri_metadata_zwe.csv +0 -0
  113. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/qc_sdg_data_zwe.csv +0 -0
  114. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/sdg_data_zwe.csv +0 -0
  115. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/sdg_indicatorlist_zwe.csv +0 -0
  116. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/sdg_metadata_zwe.csv +0 -0
  117. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/unesco_dataset.json +0 -0
  118. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_dataset_resources/unesco_update_dataset.json +0 -0
  119. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_logic/update_logic_resources.yaml +0 -0
  120. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/fixtures/update_logic/update_logic_resources_new.yaml +0 -0
  121. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/__init__.py +0 -0
  122. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/__init__.py +0 -0
  123. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/test_ckan.py +0 -0
  124. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/test_configuration.py +0 -0
  125. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/test_locations.py +0 -0
  126. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/utilities/__init__.py +0 -0
  127. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/utilities/test_filestore_helper.py +0 -0
  128. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/utilities/test_hdx_error_handler.py +0 -0
  129. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/utilities/test_hdx_state.py +0 -0
  130. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/api/utilities/test_url_utils.py +0 -0
  131. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/conftest.py +0 -0
  132. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/__init__.py +0 -0
  133. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_activity.py +0 -0
  134. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_dataset_add_hapi_error.py +0 -0
  135. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_dataset_core.py +0 -0
  136. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_dataset_resource_generation.py +0 -0
  137. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_organization.py +0 -0
  138. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_resource_view.py +0 -0
  139. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_showcase.py +0 -0
  140. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_update_dataset_resources.py +0 -0
  141. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_update_logic.py +0 -0
  142. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_user.py +0 -0
  143. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/data/test_vocabulary.py +0 -0
  144. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/facades/__init__.py +0 -0
  145. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/facades/test_infer_arguments.py +0 -0
  146. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/facades/test_keyword_arguments.py +0 -0
  147. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/tests/hdx/facades/test_simple.py +0 -0
  148. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/uv.lock +0 -0
  149. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/workingexample/my_code.py +0 -0
  150. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/workingexample/my_resource.csv +0 -0
  151. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/workingexample/my_resource.xlsx +0 -0
  152. {hdx_python_api-6.7.3 → hdx_python_api-6.7.4}/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.3
3
+ Version: 6.7.4
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
@@ -647,6 +647,35 @@ This returns a list of dictionaries, each from a resource name to a list of
647
647
  check events with keys such as `state`, `timestamp`, `sheet_changes` and
648
648
  `hxl_proxy_response`.
649
649
 
650
+ Similarly, you can get the geopreview shape information of a dataset's
651
+ resources:
652
+
653
+ shape_info = dataset.get_shape_info()
654
+
655
+ HDX can recommend tags for a dataset based on its title and organization
656
+ (`owner_org`). Tags are ordered from most to least recommended:
657
+
658
+ tags = dataset.get_recommended_tags()
659
+
660
+ You can check if the dataset or its organization is in HDX's allowlist for
661
+ automatic loading of resources into the datastore:
662
+
663
+ allowed = dataset.is_allowed_for_datastore()
664
+
665
+ HDX sysadmins can submit all of a dataset's CSV resources to DataPusher+
666
+ (returning False if there are none):
667
+
668
+ submitted = dataset.push_to_datastore()
669
+
670
+ Users who are sysadmins or have permission to manage QA can mark QA of a
671
+ dataset as completed, set a QA field of all its resources or clear a file
672
+ structure check field (`fs_check_info` by default) of all its resources. The
673
+ dataset is updated from the result:
674
+
675
+ dataset.set_qa_completed(True)
676
+ dataset.qa_revise_resources("pii", "false")
677
+ dataset.reset_fs_check()
678
+
650
679
  ### Time Period
651
680
 
652
681
  Time Period is a mandatory field in HDX. It is the earliest start date and
@@ -964,6 +993,32 @@ HDX:
964
993
 
965
994
  events = resource.get_fs_check_info()
966
995
 
996
+ Users who are sysadmins or have permission to manage QA can set or clear a
997
+ file structure check field of a resource. If the key is `fs_check_info` and the
998
+ value is a dictionary, it is appended to the existing check events (the last 20
999
+ are kept):
1000
+
1001
+ resource.revise_fs_check("fs_check_info", {"state": "success", ...})
1002
+ resource.reset_fs_check() # key defaults to fs_check_info
1003
+
1004
+ They can also quarantine a resource (which removes its `shape_info` and
1005
+ geopreview) or take it out of quarantine, and patch other QA fields:
1006
+
1007
+ in_quarantine = resource.is_in_quarantine()
1008
+ resource.set_in_quarantine(True)
1009
+ resource.qa_patch(pii="true", sdc_report_flag="false")
1010
+
1011
+ A resource can be flagged as containing p-codes or as being in HAPI. Setting
1012
+ these flags requires sysadmin or permission to manage p-codes or the in HAPI
1013
+ flag respectively:
1014
+
1015
+ p_coded = resource.is_p_coded() # None if not set
1016
+ resource.set_p_coded(True)
1017
+ in_hapi = resource.is_in_hapi()
1018
+ resource.set_in_hapi(True)
1019
+
1020
+ These methods update the resource from the result.
1021
+
967
1022
  ### Data Dictionary
968
1023
 
969
1024
  A resource can have a data dictionary describing the columns in the data it
@@ -1041,6 +1096,11 @@ the latest job or None if there is none:
1041
1096
  submitted = resource.submit_to_datapusher(ignore_hash=True)
1042
1097
  status = resource.get_datapusher_status() # eg. status["status"] == "complete"
1043
1098
 
1099
+ HDX sysadmins can instead use **push_to_datastore**, which submits the
1100
+ resource through HDX's wrapper action:
1101
+
1102
+ submitted = resource.push_to_datastore()
1103
+
1044
1104
  To download the whole datastore (rather than the resource's file) use
1045
1105
  **download_datastore**, which returns the URL and path like **download**.
1046
1106
  `file_format` can be `csv` (default), `tsv`, `json` or `xml`, and `filename`
@@ -1213,6 +1273,17 @@ since Data Grid membership changes over time, rather than a fixed list):
1213
1273
 
1214
1274
  This returns a sorted list of 3-letter location codes.
1215
1275
 
1276
+ HDX sysadmins can get the Data Grid of a country and clear HDX's cached copy of
1277
+ it:
1278
+
1279
+ data_grid = Location.get_data_grid("AFG")
1280
+ Location.invalidate_data_grid("AFG")
1281
+
1282
+ **get_data_grid** returns a dictionary with keys including `title`, `date`,
1283
+ `stats` and `categories` (or None if not found). Each category has
1284
+ `subcategories`, each with a list of `datasets`. Complementary datasets have
1285
+ `is_complementary` set to True.
1286
+
1216
1287
  You can get the datasets belonging to a location as follows:
1217
1288
 
1218
1289
  datasets = location.get_datasets(**kwargs)
@@ -1298,6 +1369,14 @@ and push them to analytics:
1298
1369
 
1299
1370
  pushed_stats = push_general_stats()
1300
1371
 
1372
+ HDX sysadmins can get the number of activities of each active user (optionally
1373
+ between start and end dates) and lists of active and inactive organizations:
1374
+
1375
+ from hdx.api.utilities.hdx_stats import get_organization_statistics, get_user_statistics
1376
+
1377
+ user_stats = get_user_statistics(start_date="2026-01-01", end_date="2026-06-30")
1378
+ org_stats = get_organization_statistics()
1379
+
1301
1380
  ## Pipeline State
1302
1381
 
1303
1382
  The HDXState class allows the reading and writing of state to a given dataset. Input
@@ -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.3'
22
- __version_tuple__ = version_tuple = (6, 7, 3)
21
+ __version__ = version = '6.7.4'
22
+ __version_tuple__ = version_tuple = (6, 7, 4)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,86 @@
1
+ """HDX site-wide statistics"""
2
+
3
+ from hdx.api.configuration import Configuration
4
+ from hdx.data.hdxobject import HDXError
5
+
6
+
7
+ def _call_stats_action(
8
+ action: str, configuration: Configuration | None, data: dict | None = None
9
+ ) -> dict:
10
+ """Call an HDX statistics action
11
+
12
+ Args:
13
+ action: CKAN action name
14
+ configuration: HDX configuration. Defaults to global configuration.
15
+ data: Parameters to pass to the action. Defaults to None.
16
+
17
+ Returns:
18
+ Statistics dictionary
19
+ """
20
+ if configuration is None:
21
+ configuration = Configuration.read()
22
+ try:
23
+ return configuration.call_remoteckan(action, data or {})
24
+ except Exception as e:
25
+ raise HDXError(f"Failed when trying to call {action}!") from e
26
+
27
+
28
+ def get_general_statistics(configuration: Configuration | None = None) -> dict:
29
+ """Get general statistics about datasets, organizations and users on HDX
30
+
31
+ Args:
32
+ configuration: HDX configuration. Defaults to global configuration.
33
+
34
+ Returns:
35
+ Dictionary with keys datasets, organizations and users
36
+ """
37
+ return _call_stats_action("hdx_general_statistics", configuration)
38
+
39
+
40
+ def push_general_stats(configuration: Configuration | None = None) -> dict:
41
+ """Compute general statistics on HDX and push them to analytics. Requires
42
+ permission to manage basic scheduled tasks.
43
+
44
+ Args:
45
+ configuration: HDX configuration. Defaults to global configuration.
46
+
47
+ Returns:
48
+ Statistics that were pushed
49
+ """
50
+ return _call_stats_action("hdx_push_general_stats", configuration)
51
+
52
+
53
+ def get_user_statistics(
54
+ start_date: str | None = None,
55
+ end_date: str | None = None,
56
+ configuration: Configuration | None = None,
57
+ ) -> dict:
58
+ """Get the number of activities of each active user on HDX, optionally
59
+ between start and end dates. Requires sysadmin.
60
+
61
+ Args:
62
+ start_date: Count activities on or after this date eg. 2026-01-01. Defaults to None.
63
+ end_date: Count activities on or before this date. Defaults to None.
64
+ configuration: HDX configuration. Defaults to global configuration.
65
+
66
+ Returns:
67
+ Dictionary from user id to dictionary with keys id, name and no_of_activities
68
+ """
69
+ data = {}
70
+ if start_date:
71
+ data["start_date"] = start_date
72
+ if end_date:
73
+ data["end_date"] = end_date
74
+ return _call_stats_action("hdx_user_statistics", configuration, data)
75
+
76
+
77
+ def get_organization_statistics(configuration: Configuration | None = None) -> dict:
78
+ """Get active and inactive organizations on HDX. Requires sysadmin.
79
+
80
+ Args:
81
+ configuration: HDX configuration. Defaults to global configuration.
82
+
83
+ Returns:
84
+ Dictionary with keys active_organizations and inactive_organizations
85
+ """
86
+ return _call_stats_action("hdx_organization_statistics", configuration)
@@ -148,6 +148,13 @@ class Dataset(HDXObject):
148
148
  "autocomplete": "package_autocomplete",
149
149
  "create_default_views": "package_create_default_resource_views",
150
150
  "fs_check_info": "fs_check_info_show",
151
+ "fs_check_reset": "hdx_fs_check_package_reset",
152
+ "shape_info": "shape_info_show",
153
+ "recommend_tags": "hdx_recommend_tags",
154
+ "allowed_for_datastore": "hdx_is_package_allowed_for_datastore",
155
+ "push_to_datastore": "hdx_push_resource_to_datastore",
156
+ "qa_completed": "hdx_mark_qa_completed",
157
+ "qa_revise_resources": "hdx_qa_package_revise_resource",
151
158
  }
152
159
 
153
160
  def __setitem__(self, key: Any, value: Any) -> None:
@@ -2347,6 +2354,136 @@ class Dataset(HDXObject):
2347
2354
  return result
2348
2355
  return []
2349
2356
 
2357
+ def _set_from_package(self, package: dict) -> None:
2358
+ """Set the dataset's metadata and resources from a package metadata
2359
+ dictionary returned by HDX
2360
+
2361
+ Args:
2362
+ package: Package metadata dictionary
2363
+
2364
+ Returns:
2365
+ None
2366
+ """
2367
+ self.data = package
2368
+ self.init_resources()
2369
+ self.separate_resources()
2370
+
2371
+ def reset_fs_check(self, key: str = "fs_check_info") -> None:
2372
+ """Clear a file structure check field of all the dataset's resources
2373
+ in HDX. Requires sysadmin or permission to manage QA.
2374
+
2375
+ Args:
2376
+ key: Resource field to clear. Defaults to fs_check_info.
2377
+
2378
+ Returns:
2379
+ None
2380
+ """
2381
+ result = self._write_to_hdx(
2382
+ "fs_check_reset", {"package_id": self.data["id"], "key": key}, "package_id"
2383
+ )
2384
+ self._set_from_package(result["package"])
2385
+
2386
+ def set_qa_completed(self, qa_completed: bool) -> None:
2387
+ """Mark QA of the dataset in HDX as completed or not. Requires
2388
+ sysadmin or permission to manage QA.
2389
+
2390
+ Args:
2391
+ qa_completed: Whether QA is completed
2392
+
2393
+ Returns:
2394
+ None
2395
+ """
2396
+ result = self._write_to_hdx(
2397
+ "qa_completed", {"id": self.data["id"], "qa_completed": qa_completed}, "id"
2398
+ )
2399
+ self._set_from_package(result["package"])
2400
+
2401
+ def qa_revise_resources(self, key: str, value: Any) -> None:
2402
+ """Set a QA field of all the dataset's resources in HDX. Requires
2403
+ sysadmin or permission to manage QA.
2404
+
2405
+ Args:
2406
+ key: Resource field eg. pii
2407
+ value: Value to set
2408
+
2409
+ Returns:
2410
+ None
2411
+ """
2412
+ result = self._write_to_hdx(
2413
+ "qa_revise_resources",
2414
+ {"id": self.data["id"], "key": key, "value": value},
2415
+ "id",
2416
+ )
2417
+ self._set_from_package(result["package"])
2418
+
2419
+ def get_shape_info(self) -> list[dict]:
2420
+ """Get geopreview shape information for the dataset's resources from
2421
+ HDX
2422
+
2423
+ Returns:
2424
+ List of dictionaries from resource name to shape information
2425
+ """
2426
+ success, result = self._read_from_hdx(
2427
+ "dataset",
2428
+ self.get_name_or_id(prefer_name=False),
2429
+ "id",
2430
+ self.actions()["shape_info"],
2431
+ )
2432
+ if success:
2433
+ return result
2434
+ return []
2435
+
2436
+ def get_recommended_tags(self) -> list[str]:
2437
+ """Get tags recommended by HDX for the dataset based on its title and
2438
+ organization. Tags are ordered from most to least recommended.
2439
+
2440
+ Returns:
2441
+ List of recommended tags
2442
+ """
2443
+ data = {"title": self.data.get("title")}
2444
+ organization = self.data.get("owner_org")
2445
+ if organization:
2446
+ data["organization"] = organization
2447
+ try:
2448
+ result = self.configuration.call_remoteckan(
2449
+ self.actions()["recommend_tags"], data
2450
+ )
2451
+ except Exception as e:
2452
+ raise HDXError("Failed when trying to get recommended tags!") from e
2453
+ return [tag["name"] for tag in result]
2454
+
2455
+ def is_allowed_for_datastore(self) -> bool:
2456
+ """Return if the dataset or its organization is in HDX's allowlist for
2457
+ automatic loading of resources into the datastore
2458
+
2459
+ Returns:
2460
+ Whether dataset is allowed for datastore
2461
+ """
2462
+ success, result = self._read_from_hdx(
2463
+ "dataset",
2464
+ self.data["id"],
2465
+ "package_id",
2466
+ self.actions()["allowed_for_datastore"],
2467
+ )
2468
+ if success:
2469
+ return result
2470
+ logger.debug(result)
2471
+ return False
2472
+
2473
+ def push_to_datastore(self) -> bool:
2474
+ """Submit all the dataset's CSV resources to DataPusher+ using HDX's
2475
+ wrapper action. Requires sysadmin.
2476
+
2477
+ Returns:
2478
+ True if submitted, False if not (eg. no CSV resources)
2479
+ """
2480
+ result = self._write_to_hdx(
2481
+ "push_to_datastore", {"dataset_id": self.data["id"]}, "dataset_id"
2482
+ )
2483
+ if not result["success"]:
2484
+ logger.debug(result["message"])
2485
+ return result["success"]
2486
+
2350
2487
  def generate_resource(
2351
2488
  self,
2352
2489
  folder: Path | str,
@@ -43,6 +43,8 @@ class Location(HDXObject):
43
43
  "delete": "group_delete",
44
44
  "list": "group_list",
45
45
  "autocomplete": "group_autocomplete",
46
+ "data_grid": "hdx_datagrid_show",
47
+ "invalidate_data_grid": "invalidate_data_completeness_for_location",
46
48
  }
47
49
 
48
50
  @classmethod
@@ -218,6 +220,47 @@ class Location(HDXObject):
218
220
  if len(group["name"]) == 3 and group.get("data_completeness") == "active"
219
221
  )
220
222
 
223
+ @staticmethod
224
+ def get_data_grid(
225
+ iso3: str, configuration: Configuration | None = None
226
+ ) -> dict | None:
227
+ """Get the Data Grid of a country. The dictionary has keys including
228
+ title, date, stats and categories. Each category has subcategories,
229
+ each with a list of datasets. Complementary datasets have
230
+ is_complementary set to True. Requires sysadmin.
231
+
232
+ Args:
233
+ iso3: Country ISO 3-letter code
234
+ configuration: HDX configuration. Defaults to global configuration.
235
+
236
+ Returns:
237
+ Data Grid dictionary or None if not found
238
+ """
239
+ location = Location(configuration=configuration)
240
+ success, result = location._read_from_hdx(
241
+ "location", iso3.lower(), "id", location.actions()["data_grid"]
242
+ )
243
+ if success:
244
+ return result
245
+ logger.debug(result)
246
+ return None
247
+
248
+ @staticmethod
249
+ def invalidate_data_grid(
250
+ iso3: str, configuration: Configuration | None = None
251
+ ) -> None:
252
+ """Clear HDX's cached Data Grid of a country. Requires sysadmin.
253
+
254
+ Args:
255
+ iso3: Country ISO 3-letter code
256
+ configuration: HDX configuration. Defaults to global configuration.
257
+
258
+ Returns:
259
+ None
260
+ """
261
+ location = Location(configuration=configuration)
262
+ location._write_to_hdx("invalidate_data_grid", {"name": iso3.lower()}, "name")
263
+
221
264
  def get_activities(self, all_pages: bool = False, **kwargs: Any) -> list[Activity]:
222
265
  """Get the activity stream of this location. One page of results (whose size
223
266
  is given by limit) is returned unless all_pages is True.
@@ -74,6 +74,13 @@ class Resource(HDXObject):
74
74
  "datastore_search_sql": "datastore_search_sql",
75
75
  "datapusher_submit": "datapusher_submit",
76
76
  "datapusher_status": "datapusher_status",
77
+ "push_to_datastore": "hdx_push_resource_to_datastore",
78
+ "p_coded": "hdx_p_coded_resource_update",
79
+ "in_hapi": "hdx_mark_resource_in_hapi",
80
+ "in_quarantine": "hdx_mark_resource_in_quarantine",
81
+ "qa_patch": "hdx_qa_resource_patch",
82
+ "fs_check_revise": "hdx_fs_check_resource_revise",
83
+ "fs_check_reset": "hdx_fs_check_resource_reset",
77
84
  }
78
85
 
79
86
  def update_from_yaml(
@@ -1113,6 +1120,20 @@ class Resource(HDXObject):
1113
1120
  "resource_id",
1114
1121
  )
1115
1122
 
1123
+ def push_to_datastore(self) -> bool:
1124
+ """Submit the resource to DataPusher+ using HDX's wrapper action.
1125
+ Unlike submit_to_datapusher, this requires sysadmin.
1126
+
1127
+ Returns:
1128
+ True if submitted, False if not
1129
+ """
1130
+ result = self._write_to_hdx(
1131
+ "push_to_datastore", {"resource_id": self.data["id"]}, "resource_id"
1132
+ )
1133
+ if not result["success"]:
1134
+ logger.debug(result["message"])
1135
+ return result["success"]
1136
+
1116
1137
  def get_datapusher_status(self) -> dict | None:
1117
1138
  """Get the status of the resource's latest DataPusher+ job
1118
1139
 
@@ -1304,6 +1325,159 @@ class Resource(HDXObject):
1304
1325
  return json.loads(fs_check_info)
1305
1326
  return fs_check_info
1306
1327
 
1328
+ def _set_from_package(self, package: dict) -> None:
1329
+ """Set the resource's metadata from the matching resource in a package
1330
+ metadata dictionary returned by HDX
1331
+
1332
+ Args:
1333
+ package: Package metadata dictionary
1334
+
1335
+ Returns:
1336
+ None
1337
+ """
1338
+ for resource in package.get("resources", []):
1339
+ if resource["id"] == self.data["id"]:
1340
+ self.data = resource
1341
+ return
1342
+
1343
+ def revise_fs_check(self, key: str, value: Any) -> None:
1344
+ """Set a file structure check field of the resource in HDX. If key is
1345
+ fs_check_info and value is a dictionary, it is appended to the
1346
+ existing check events (the last 20 are kept) rather than replacing
1347
+ them. Requires sysadmin or permission to manage QA.
1348
+
1349
+ Args:
1350
+ key: Resource field eg. fs_check_info
1351
+ value: Value to set
1352
+
1353
+ Returns:
1354
+ None
1355
+ """
1356
+ result = self._write_to_hdx(
1357
+ "fs_check_revise",
1358
+ {
1359
+ "package_id": self.data["package_id"],
1360
+ "id": self.data["id"],
1361
+ "key": key,
1362
+ "value": value,
1363
+ },
1364
+ "id",
1365
+ )
1366
+ self._set_from_package(result["package"])
1367
+
1368
+ def reset_fs_check(self, key: str = "fs_check_info") -> None:
1369
+ """Clear a file structure check field of the resource in HDX. Requires
1370
+ sysadmin or permission to manage QA.
1371
+
1372
+ Args:
1373
+ key: Resource field to clear. Defaults to fs_check_info.
1374
+
1375
+ Returns:
1376
+ None
1377
+ """
1378
+ result = self._write_to_hdx(
1379
+ "fs_check_reset",
1380
+ {"package_id": self.data["package_id"], "id": self.data["id"], "key": key},
1381
+ "id",
1382
+ )
1383
+ self._set_from_package(result["package"])
1384
+
1385
+ def is_p_coded(self) -> bool | None:
1386
+ """Return if the resource is marked as containing p-codes
1387
+
1388
+ Returns:
1389
+ Whether resource is p-coded or None if not set
1390
+ """
1391
+ p_coded = self.data.get("p_coded")
1392
+ if p_coded is None:
1393
+ return None
1394
+ if isinstance(p_coded, str):
1395
+ return p_coded.lower() == "true"
1396
+ return p_coded
1397
+
1398
+ def set_p_coded(self, p_coded: bool) -> None:
1399
+ """Mark the resource in HDX as containing p-codes or not. Requires
1400
+ sysadmin or permission to manage p-codes.
1401
+
1402
+ Args:
1403
+ p_coded: Whether resource is p-coded
1404
+
1405
+ Returns:
1406
+ None
1407
+ """
1408
+ self.data = self._write_to_hdx(
1409
+ "p_coded", {"id": self.data["id"], "p_coded": p_coded}, "id"
1410
+ )
1411
+
1412
+ def is_in_hapi(self) -> bool:
1413
+ """Return if the resource is marked as being in HAPI
1414
+
1415
+ Returns:
1416
+ Whether resource is in HAPI
1417
+ """
1418
+ return self.data.get("in_hapi") == "yes"
1419
+
1420
+ def set_in_hapi(self, in_hapi: bool) -> None:
1421
+ """Mark the resource in HDX as being in HAPI or not. Requires sysadmin
1422
+ or permission to manage the in HAPI flag.
1423
+
1424
+ Args:
1425
+ in_hapi: Whether resource is in HAPI
1426
+
1427
+ Returns:
1428
+ None
1429
+ """
1430
+ self.data = self._write_to_hdx(
1431
+ "in_hapi",
1432
+ {"id": self.data["id"], "in_hapi": "yes" if in_hapi else "no-data"},
1433
+ "id",
1434
+ )
1435
+
1436
+ def is_in_quarantine(self) -> bool:
1437
+ """Return if the resource is in quarantine
1438
+
1439
+ Returns:
1440
+ Whether resource is in quarantine
1441
+ """
1442
+ in_quarantine = self.data.get("in_quarantine", False)
1443
+ if isinstance(in_quarantine, str):
1444
+ return in_quarantine.lower() == "true"
1445
+ return in_quarantine
1446
+
1447
+ def set_in_quarantine(self, in_quarantine: bool) -> None:
1448
+ """Put the resource in HDX in quarantine or take it out. Quarantining
1449
+ also removes the resource's shape_info and geopreview. Requires
1450
+ sysadmin or permission to manage QA.
1451
+
1452
+ Args:
1453
+ in_quarantine: Whether resource is in quarantine
1454
+
1455
+ Returns:
1456
+ None
1457
+ """
1458
+ self.data = self._write_to_hdx(
1459
+ "in_quarantine",
1460
+ {
1461
+ "id": self.data["id"],
1462
+ "in_quarantine": "true" if in_quarantine else "false",
1463
+ },
1464
+ "id",
1465
+ )
1466
+
1467
+ def qa_patch(self, **kwargs: Any) -> None:
1468
+ """Patch QA fields of the resource in HDX eg. pii, sdc_report_flag,
1469
+ broken_link. Requires sysadmin or permission to manage QA.
1470
+
1471
+ Args:
1472
+ **kwargs: Resource fields to patch
1473
+
1474
+ Returns:
1475
+ None
1476
+ """
1477
+ self.data = self._write_to_hdx(
1478
+ "qa_patch", {"id": self.data["id"], **kwargs}, "id"
1479
+ )
1480
+
1307
1481
  def is_marked_data_updated(self) -> bool:
1308
1482
  """Return if the resource's data is marked to be updated
1309
1483
 
@@ -6,7 +6,12 @@ import pytest
6
6
 
7
7
  from ... import MockResponse
8
8
  from hdx.api.configuration import Configuration
9
- from hdx.api.utilities.hdx_stats import get_general_statistics, push_general_stats
9
+ from hdx.api.utilities.hdx_stats import (
10
+ get_general_statistics,
11
+ get_organization_statistics,
12
+ get_user_statistics,
13
+ push_general_stats,
14
+ )
10
15
  from hdx.data.hdxobject import HDXError
11
16
 
12
17
  general_statistics = {
@@ -17,17 +22,30 @@ general_statistics = {
17
22
 
18
23
  pushed_stats = {"mixpanel_meta": {"datasets total": 100, "orgs total": 30}}
19
24
 
25
+ user_statistics = {"USER1": {"id": "USER1", "name": "user1", "no_of_activities": 5}}
26
+
27
+ organization_statistics = {
28
+ "active_organizations": [{"id": "ORG1", "name": "org1"}],
29
+ "inactive_organizations": [],
30
+ }
31
+
20
32
 
21
33
  class TestHDXStats:
22
34
  succeed = True
35
+ data = None
23
36
 
24
37
  @pytest.fixture(scope="function")
25
38
  def post_stats(self):
26
39
  class MockSession:
27
40
  @staticmethod
28
41
  def post(url, data, **kwargs):
42
+ TestHDXStats.data = json.loads(data.decode("utf-8"))
29
43
  if TestHDXStats.succeed:
30
- if "hdx_general_statistics" in url:
44
+ if "hdx_user_statistics" in url:
45
+ result = json.dumps(user_statistics)
46
+ elif "hdx_organization_statistics" in url:
47
+ result = json.dumps(organization_statistics)
48
+ elif "hdx_general_statistics" in url:
31
49
  result = json.dumps(general_statistics)
32
50
  elif "hdx_push_general_stats" in url:
33
51
  result = json.dumps(pushed_stats)
@@ -49,6 +67,18 @@ class TestHDXStats:
49
67
  assert get_general_statistics() == general_statistics
50
68
  assert get_general_statistics(Configuration.read()) == general_statistics
51
69
 
70
+ def test_get_user_statistics(self, configuration, post_stats):
71
+ assert get_user_statistics() == user_statistics
72
+ assert TestHDXStats.data == {}
73
+ assert get_user_statistics("2026-01-01", "2026-06-30") == user_statistics
74
+ assert TestHDXStats.data == {
75
+ "start_date": "2026-01-01",
76
+ "end_date": "2026-06-30",
77
+ }
78
+
79
+ def test_get_organization_statistics(self, configuration, post_stats):
80
+ assert get_organization_statistics() == organization_statistics
81
+
52
82
  def test_push_general_stats(self, configuration, post_stats):
53
83
  assert push_general_stats() == pushed_stats
54
84
  TestHDXStats.succeed = False