hdx-python-api 6.6.8__tar.gz → 6.6.9__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 (143) hide show
  1. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/PKG-INFO +4 -4
  2. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/documentation/index.md +27 -0
  3. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/pyproject.toml +2 -2
  4. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/_version.py +2 -2
  5. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/resource.py +54 -0
  6. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_resource.py +64 -0
  7. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/uv.lock +132 -103
  8. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/.github/workflows/publish.yaml +0 -0
  9. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/.github/workflows/run-python-tests.yaml +0 -0
  10. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/.gitignore +0 -0
  11. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/.pre-commit-config.yaml +0 -0
  12. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/CLAUDE.md +0 -0
  13. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/LICENSE +0 -0
  14. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/README.md +0 -0
  15. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/documentation/.readthedocs.yaml +0 -0
  16. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/documentation/mkdocs.yaml +0 -0
  17. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/__init__.py +0 -0
  18. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/configuration.py +0 -0
  19. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/hdx_base_configuration.yaml +0 -0
  20. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/locations.py +0 -0
  21. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/remotehdx.py +0 -0
  22. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/utilities/__init__.py +0 -0
  23. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/utilities/date_helper.py +0 -0
  24. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/utilities/filestore_helper.py +0 -0
  25. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/utilities/hdx_error_handler.py +0 -0
  26. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/utilities/hdx_state.py +0 -0
  27. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/api/utilities/url_utils.py +0 -0
  28. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/__init__.py +0 -0
  29. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/dataset.py +0 -0
  30. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/hdxobject.py +0 -0
  31. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/organization.py +0 -0
  32. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/resource_matcher.py +0 -0
  33. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/resource_view.py +0 -0
  34. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/showcase.py +0 -0
  35. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/user.py +0 -0
  36. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/data/vocabulary.py +0 -0
  37. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/facades/__init__.py +0 -0
  38. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/facades/infer_arguments.py +0 -0
  39. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/facades/keyword_arguments.py +0 -0
  40. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/src/hdx/facades/simple.py +0 -0
  41. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/__init__.py +0 -0
  42. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/Accepted_Tags.csv +0 -0
  43. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/CKAN/hdx_dataset_static.yaml +0 -0
  44. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/Tag_Mapping.csv +0 -0
  45. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/Tag_Mapping_ChainRuleError.csv +0 -0
  46. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/empty.yaml +0 -0
  47. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_base_config.json +0 -0
  48. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_base_config.yaml +0 -0
  49. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_config.json +0 -0
  50. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_config.yaml +0 -0
  51. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_dataset_static.json +0 -0
  52. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_dataset_static.yaml +0 -0
  53. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_datasource_topline.json +0 -0
  54. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_datasource_topline.yaml +0 -0
  55. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_email_configuration.json +0 -0
  56. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
  57. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_missing_site_config.json +0 -0
  58. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_organization_static.json +0 -0
  59. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_organization_static.yaml +0 -0
  60. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_resource_static.json +0 -0
  61. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_resource_static.yaml +0 -0
  62. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_resource_view_static.json +0 -0
  63. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_resource_view_static.yaml +0 -0
  64. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_showcase_static.json +0 -0
  65. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_showcase_static.yaml +0 -0
  66. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_user_static.json +0 -0
  67. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_user_static.yaml +0 -0
  68. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_vocabulary_static.json +0 -0
  69. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/hdx_vocabulary_static.yaml +0 -0
  70. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/project_configuration.json +0 -0
  71. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/project_configuration.yaml +0 -0
  72. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/user_agent_config.yaml +0 -0
  73. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/user_agent_config2.yaml +0 -0
  74. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/user_agent_config3.yaml +0 -0
  75. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
  76. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/dataset_search_results.yaml +0 -0
  77. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/download_gen_resource/conflict_data_alg.csv +0 -0
  78. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/download_gen_resource/min_qc_conflict_data_alg.csv +0 -0
  79. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/download_gen_resource/qc_conflict_data_alg.csv +0 -0
  80. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/download_gen_resource/test_data_no_data.csv +0 -0
  81. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/download_gen_resource/test_data_no_years.csv +0 -0
  82. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/empty.csv +0 -0
  83. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/gen_resource/conflict_data_alg.csv +0 -0
  84. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/gen_resource/min_qc_conflict_data_alg.csv +0 -0
  85. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/gen_resource/qc_conflict_data_alg.csv +0 -0
  86. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/gen_resource/test_data_no_data.csv +0 -0
  87. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/gen_resource/test_data_no_years.csv +0 -0
  88. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/organization/organization_list_all_fields.yaml +0 -0
  89. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/organization/organization_show_results.yaml +0 -0
  90. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/qc_from_rows/qc_conflict_data_alg.csv +0 -0
  91. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/qc_from_rows/qc_conflict_data_alg_one_col.csv +0 -0
  92. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/resource_formats.json +0 -0
  93. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/showcase_all_search_results.yaml +0 -0
  94. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/size_hash/ACLED-All-Africa-File_20170101-to-20170708.xlsx +0 -0
  95. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/state/analysis_dates.txt +0 -0
  96. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/state/last_build_date.txt +0 -0
  97. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/test_data.csv +0 -0
  98. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/test_data.zip +0 -0
  99. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/dem_data_zwe.csv +0 -0
  100. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/dem_indicatorlist_zwe.csv +0 -0
  101. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/expected_resources_to_update.json +0 -0
  102. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/opri_data_zwe.csv +0 -0
  103. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/opri_indicatorlist_zwe.csv +0 -0
  104. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/opri_metadata_zwe.csv +0 -0
  105. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/qc_sdg_data_zwe.csv +0 -0
  106. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/sdg_data_zwe.csv +0 -0
  107. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/sdg_indicatorlist_zwe.csv +0 -0
  108. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/sdg_metadata_zwe.csv +0 -0
  109. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/unesco_dataset.json +0 -0
  110. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_dataset_resources/unesco_update_dataset.json +0 -0
  111. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_logic/update_logic_resources.yaml +0 -0
  112. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/fixtures/update_logic/update_logic_resources_new.yaml +0 -0
  113. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/__init__.py +0 -0
  114. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/__init__.py +0 -0
  115. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/test_ckan.py +0 -0
  116. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/test_configuration.py +0 -0
  117. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/test_locations.py +0 -0
  118. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/utilities/__init__.py +0 -0
  119. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/utilities/test_filestore_helper.py +0 -0
  120. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/utilities/test_hdx_error_handler.py +0 -0
  121. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/utilities/test_hdx_state.py +0 -0
  122. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/api/utilities/test_url_utils.py +0 -0
  123. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/conftest.py +0 -0
  124. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/__init__.py +0 -0
  125. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_dataset_add_hapi_error.py +0 -0
  126. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_dataset_core.py +0 -0
  127. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_dataset_noncore.py +0 -0
  128. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_dataset_resource_generation.py +0 -0
  129. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_organization.py +0 -0
  130. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_resource_view.py +0 -0
  131. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_showcase.py +0 -0
  132. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_update_dataset_resources.py +0 -0
  133. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_update_logic.py +0 -0
  134. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_user.py +0 -0
  135. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/data/test_vocabulary.py +0 -0
  136. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/facades/__init__.py +0 -0
  137. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/facades/test_infer_arguments.py +0 -0
  138. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/facades/test_keyword_arguments.py +0 -0
  139. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/tests/hdx/facades/test_simple.py +0 -0
  140. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/workingexample/my_code.py +0 -0
  141. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/workingexample/my_resource.csv +0 -0
  142. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/workingexample/my_resource.xlsx +0 -0
  143. {hdx_python_api-6.6.8 → hdx_python_api-6.6.9}/workingexample/run.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: hdx-python-api
3
- Version: 6.6.8
3
+ Version: 6.6.9
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.1.1
32
- Requires-Dist: hdx-python-utilities>=4.0.8
31
+ Requires-Dist: hdx-python-country>=4.1.3
32
+ Requires-Dist: hdx-python-utilities>=4.1.2
33
33
  Requires-Dist: makefun
34
34
  Requires-Dist: requests
35
35
  Provides-Extra: docs
@@ -937,6 +937,33 @@ If the method **set_file_to_upload** is used to supply a file, the resource
937
937
  `last_modified` field is set to now automatically regardless of the value of
938
938
  `data_updated` or whether **mark_data_updated** has been called.
939
939
 
940
+ ### Data Dictionary
941
+
942
+ A resource can have a data dictionary describing the columns in the data it
943
+ contains. It is a list of dictionaries, each with the keys `field` (the
944
+ column name as it appears in the file), `label` (a human-readable label) and
945
+ `description` (a human-readable description of the column). Set it using
946
+ **set_hdx_data_dictionary**:
947
+
948
+ resource.set_hdx_data_dictionary([
949
+ {
950
+ "field": "col_name",
951
+ "label": "Column Label",
952
+ "description": "Human-readable description.",
953
+ },
954
+ {
955
+ "field": "another_col",
956
+ "label": "Another Label",
957
+ "description": "Second column description.",
958
+ },
959
+ ])
960
+
961
+ `field`, `label` and `description` must all be non-empty strings for every
962
+ column, otherwise an `HDXError` is raised. Read the data dictionary back
963
+ using the getter:
964
+
965
+ data_dictionary = resource.get_hdx_data_dictionary()
966
+
940
967
  ## Showcase Management
941
968
 
942
969
  The **Showcase** class enables you to manage showcases, creating, deleting and updating
@@ -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.1.1",
43
- "hdx-python-utilities>=4.0.8",
42
+ "hdx-python-country>=4.1.3",
43
+ "hdx-python-utilities>=4.1.2",
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.6.8'
22
- __version_tuple__ = version_tuple = (6, 6, 8)
21
+ __version__ = version = '6.6.9'
22
+ __version_tuple__ = version_tuple = (6, 6, 9)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1,5 +1,6 @@
1
1
  """Resource class containing all logic for creating, checking, and updating resources."""
2
2
 
3
+ import json
3
4
  import logging
4
5
  from collections.abc import Sequence
5
6
  from datetime import datetime
@@ -33,6 +34,9 @@ class Resource(HDXObject):
33
34
  """
34
35
 
35
36
  _formats_dict = None
37
+ _valid_data_dictionary_types = frozenset(
38
+ {"text", "numeric", "date", "timestamp without time zone"}
39
+ )
36
40
 
37
41
  def __init__(
38
42
  self,
@@ -263,6 +267,56 @@ class Resource(HDXObject):
263
267
  self.data["format"] = file_format
264
268
  return file_format
265
269
 
270
+ def get_hdx_data_dictionary(self) -> list[dict] | None:
271
+ """Get the resource's data dictionary (column definitions)
272
+
273
+ Returns:
274
+ List of column definition dictionaries or None if not set
275
+ """
276
+ data_dictionary = self.data.get("hdx_data_dictionary")
277
+ if data_dictionary is None:
278
+ return None
279
+ if isinstance(data_dictionary, str):
280
+ return json.loads(data_dictionary)
281
+ return data_dictionary
282
+
283
+ def set_hdx_data_dictionary(self, data_dictionary: Sequence[dict]) -> None:
284
+ """Set the resource's data dictionary (column definitions). Each column
285
+ definition must be a dict with non-empty string values for field
286
+ (column name in the CSV file), label (human-readable column label) and
287
+ description (human-readable description of the column). If present,
288
+ data_type must be one of the PostgreSQL types produced by DataPusher+
289
+ (text, numeric, date, timestamp without time zone).
290
+
291
+ Args:
292
+ data_dictionary: Sequence of column definition dictionaries
293
+
294
+ Returns:
295
+ None
296
+ """
297
+ if not data_dictionary:
298
+ raise HDXError("hdx_data_dictionary must be a non-empty list!")
299
+ for i, column in enumerate(data_dictionary):
300
+ if not isinstance(column, dict):
301
+ raise HDXError(f"hdx_data_dictionary[{i}] must be a dict!")
302
+ for required_key in ("field", "label", "description"):
303
+ value = column.get(required_key)
304
+ if not isinstance(value, str) or not value.strip():
305
+ raise HDXError(
306
+ f"hdx_data_dictionary[{i}] is missing a non-empty '{required_key}'!"
307
+ )
308
+ if "data_type" in column:
309
+ data_type = column["data_type"]
310
+ if data_type not in self._valid_data_dictionary_types:
311
+ valid_types = ", ".join(sorted(self._valid_data_dictionary_types))
312
+ raise HDXError(
313
+ f"hdx_data_dictionary[{i}] has invalid 'data_type' "
314
+ f"'{data_type}'! Must be one of: {valid_types}"
315
+ )
316
+ self.data["hdx_data_dictionary"] = json.dumps(
317
+ data_dictionary, separators=(",", ":")
318
+ )
319
+
266
320
  def clean_format(self) -> str:
267
321
  """Clean the resource's format, setting it to None if it is invalid and
268
322
  cannot be mapped
@@ -734,6 +734,70 @@ class TestResource:
734
734
  "ongoing": False,
735
735
  }
736
736
 
737
+ def test_get_set_hdx_data_dictionary(self, configuration):
738
+ resource = Resource({})
739
+ assert resource.get_hdx_data_dictionary() is None
740
+
741
+ data_dictionary = [
742
+ {"field": "col_name", "label": "Column Label", "description": "Desc."},
743
+ {
744
+ "field": "another_col",
745
+ "label": "Another Label",
746
+ "description": "Second column description.",
747
+ },
748
+ ]
749
+ resource.set_hdx_data_dictionary(data_dictionary)
750
+ assert resource.data["hdx_data_dictionary"] == json.dumps(
751
+ data_dictionary, separators=(",", ":")
752
+ )
753
+ assert resource.get_hdx_data_dictionary() == data_dictionary
754
+
755
+ resource2 = Resource({"hdx_data_dictionary": json.dumps(data_dictionary)})
756
+ assert resource2.get_hdx_data_dictionary() == data_dictionary
757
+
758
+ with pytest.raises(HDXError):
759
+ resource.set_hdx_data_dictionary([])
760
+ with pytest.raises(HDXError):
761
+ resource.set_hdx_data_dictionary([{"field": "col_name", "label": "Label"}])
762
+ with pytest.raises(HDXError):
763
+ resource.set_hdx_data_dictionary(
764
+ [{"field": "", "label": "Label", "description": "Desc."}]
765
+ )
766
+ with pytest.raises(HDXError):
767
+ resource.set_hdx_data_dictionary(
768
+ [{"field": "col_name", "label": " ", "description": "Desc."}]
769
+ )
770
+ with pytest.raises(HDXError):
771
+ resource.set_hdx_data_dictionary(["not a dict"])
772
+ with pytest.raises(HDXError):
773
+ resource.set_hdx_data_dictionary(
774
+ [
775
+ {
776
+ "field": "col_name",
777
+ "label": "Label",
778
+ "description": "Desc.",
779
+ "data_type": "varchar",
780
+ }
781
+ ]
782
+ )
783
+
784
+ data_dictionary_with_types = [
785
+ {
786
+ "field": "col_name",
787
+ "label": "Column Label",
788
+ "description": "Desc.",
789
+ "data_type": "numeric",
790
+ },
791
+ {
792
+ "field": "another_col",
793
+ "label": "Another Label",
794
+ "description": "Second column description.",
795
+ "data_type": "timestamp without time zone",
796
+ },
797
+ ]
798
+ resource.set_hdx_data_dictionary(data_dictionary_with_types)
799
+ assert resource.get_hdx_data_dictionary() == data_dictionary_with_types
800
+
737
801
  def test_check_required_fields(self, configuration):
738
802
  resource_data_copy = copy.deepcopy(resource_data)
739
803
  resource = Resource(resource_data_copy)