hdx-python-api 6.6.8__tar.gz → 6.7.0__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.
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/.gitignore +1 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/PKG-INFO +4 -4
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/documentation/index.md +70 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/pyproject.toml +2 -2
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/_version.py +2 -2
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/hdx_base_configuration.yaml +4 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/locations.py +3 -4
- hdx_python_api-6.7.0/src/hdx/data/location.py +237 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/resource.py +54 -0
- hdx_python_api-6.7.0/tests/fixtures/location/location_list_all_fields.yaml +31 -0
- hdx_python_api-6.7.0/tests/fixtures/location/location_show_results.yaml +11 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/__init__.py +5 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/test_configuration.py +18 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/test_locations.py +4 -2
- hdx_python_api-6.7.0/tests/hdx/data/test_location.py +381 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_resource.py +64 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/uv.lock +132 -103
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/.github/workflows/publish.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/.github/workflows/run-python-tests.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/.pre-commit-config.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/CLAUDE.md +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/LICENSE +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/README.md +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/documentation/.readthedocs.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/documentation/mkdocs.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/configuration.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/remotehdx.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/utilities/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/utilities/date_helper.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/utilities/filestore_helper.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/utilities/hdx_error_handler.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/utilities/hdx_state.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/api/utilities/url_utils.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/dataset.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/hdxobject.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/organization.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/resource_matcher.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/resource_view.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/showcase.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/user.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/data/vocabulary.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/facades/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/facades/infer_arguments.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/facades/keyword_arguments.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/src/hdx/facades/simple.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/Accepted_Tags.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/CKAN/hdx_dataset_static.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/Tag_Mapping.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/Tag_Mapping_ChainRuleError.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/empty.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_base_config.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_base_config.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_config.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_config.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_dataset_static.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_dataset_static.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_datasource_topline.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_datasource_topline.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_email_configuration.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_missing_site_config.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_organization_static.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_organization_static.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_resource_static.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_resource_static.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_resource_view_static.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_resource_view_static.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_showcase_static.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_showcase_static.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_user_static.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_user_static.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_vocabulary_static.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/hdx_vocabulary_static.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/project_configuration.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/project_configuration.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/user_agent_config.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/user_agent_config2.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/user_agent_config3.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/dataset_search_results.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/download_gen_resource/conflict_data_alg.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/download_gen_resource/min_qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/download_gen_resource/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/download_gen_resource/test_data_no_data.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/download_gen_resource/test_data_no_years.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/empty.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/gen_resource/conflict_data_alg.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/gen_resource/min_qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/gen_resource/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/gen_resource/test_data_no_data.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/gen_resource/test_data_no_years.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/organization/organization_list_all_fields.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/organization/organization_show_results.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/qc_from_rows/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/qc_from_rows/qc_conflict_data_alg_one_col.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/resource_formats.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/showcase_all_search_results.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/size_hash/ACLED-All-Africa-File_20170101-to-20170708.xlsx +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/state/analysis_dates.txt +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/state/last_build_date.txt +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/test_data.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/test_data.zip +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/dem_data_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/dem_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/expected_resources_to_update.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/opri_data_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/opri_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/opri_metadata_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/qc_sdg_data_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/sdg_data_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/sdg_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/sdg_metadata_zwe.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/unesco_dataset.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_dataset_resources/unesco_update_dataset.json +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_logic/update_logic_resources.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/fixtures/update_logic/update_logic_resources_new.yaml +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/test_ckan.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/utilities/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/utilities/test_filestore_helper.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/utilities/test_hdx_error_handler.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/utilities/test_hdx_state.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/api/utilities/test_url_utils.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/conftest.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_dataset_add_hapi_error.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_dataset_core.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_dataset_noncore.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_dataset_resource_generation.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_organization.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_resource_view.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_showcase.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_update_dataset_resources.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_update_logic.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_user.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/data/test_vocabulary.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/facades/__init__.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/facades/test_infer_arguments.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/facades/test_keyword_arguments.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/tests/hdx/facades/test_simple.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/workingexample/my_code.py +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/workingexample/my_resource.csv +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/workingexample/my_resource.xlsx +0 -0
- {hdx_python_api-6.6.8 → hdx_python_api-6.7.0}/workingexample/run.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: hdx-python-api
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.7.0
|
|
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.
|
|
32
|
-
Requires-Dist: hdx-python-utilities>=4.
|
|
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
|
|
@@ -33,6 +33,7 @@ upload your datasets to HDX.
|
|
|
33
33
|
- [Showcase Management](#showcase-management)
|
|
34
34
|
- [User Management](#user-management)
|
|
35
35
|
- [Organization Management](#organization-management)
|
|
36
|
+
- [Location Management](#location-management)
|
|
36
37
|
- [Vocabulary Management](#vocabulary-management)
|
|
37
38
|
- [Pipeline State](#pipeline-state)
|
|
38
39
|
- [Working Examples](#working-examples)
|
|
@@ -937,6 +938,33 @@ If the method **set_file_to_upload** is used to supply a file, the resource
|
|
|
937
938
|
`last_modified` field is set to now automatically regardless of the value of
|
|
938
939
|
`data_updated` or whether **mark_data_updated** has been called.
|
|
939
940
|
|
|
941
|
+
### Data Dictionary
|
|
942
|
+
|
|
943
|
+
A resource can have a data dictionary describing the columns in the data it
|
|
944
|
+
contains. It is a list of dictionaries, each with the keys `field` (the
|
|
945
|
+
column name as it appears in the file), `label` (a human-readable label) and
|
|
946
|
+
`description` (a human-readable description of the column). Set it using
|
|
947
|
+
**set_hdx_data_dictionary**:
|
|
948
|
+
|
|
949
|
+
resource.set_hdx_data_dictionary([
|
|
950
|
+
{
|
|
951
|
+
"field": "col_name",
|
|
952
|
+
"label": "Column Label",
|
|
953
|
+
"description": "Human-readable description.",
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"field": "another_col",
|
|
957
|
+
"label": "Another Label",
|
|
958
|
+
"description": "Second column description.",
|
|
959
|
+
},
|
|
960
|
+
])
|
|
961
|
+
|
|
962
|
+
`field`, `label` and `description` must all be non-empty strings for every
|
|
963
|
+
column, otherwise an `HDXError` is raised. Read the data dictionary back
|
|
964
|
+
using the getter:
|
|
965
|
+
|
|
966
|
+
data_dictionary = resource.get_hdx_data_dictionary()
|
|
967
|
+
|
|
940
968
|
## Showcase Management
|
|
941
969
|
|
|
942
970
|
The **Showcase** class enables you to manage showcases, creating, deleting and updating
|
|
@@ -1036,6 +1064,48 @@ You can delete a user from an organization:
|
|
|
1036
1064
|
|
|
1037
1065
|
organization.delete_user("USER ID")
|
|
1038
1066
|
|
|
1067
|
+
## Location Management
|
|
1068
|
+
|
|
1069
|
+
The **Location** class wraps HDX locations (CKAN groups eg. countries, "world"). Reading
|
|
1070
|
+
locations does not require any special permissions, so these are the operations you will
|
|
1071
|
+
typically use. Creating, updating and deleting locations is restricted to HDX sysadmins
|
|
1072
|
+
and works the same way as for other HDX objects (see
|
|
1073
|
+
[Operations on HDX Objects](#operations-on-hdx-objects)).
|
|
1074
|
+
|
|
1075
|
+
You can read a single location:
|
|
1076
|
+
|
|
1077
|
+
location = Location.read_from_hdx("LOCATION_ID_OR_NAME")
|
|
1078
|
+
|
|
1079
|
+
You can get all location names in HDX:
|
|
1080
|
+
|
|
1081
|
+
location_names = Location.get_all_location_names(**kwargs)
|
|
1082
|
+
|
|
1083
|
+
Passing `all_fields=True` (plus optionally `include_extras=True` etc.) returns the full
|
|
1084
|
+
location dictionaries instead of just names. Various additional arguments (`**kwargs`)
|
|
1085
|
+
can be supplied and are detailed in the API documentation.
|
|
1086
|
+
|
|
1087
|
+
You can fetch the countries that are currently active on HDX's Data Grid (a live lookup,
|
|
1088
|
+
since Data Grid membership changes over time, rather than a fixed list):
|
|
1089
|
+
|
|
1090
|
+
country_codes = Location.get_data_grid_countries()
|
|
1091
|
+
|
|
1092
|
+
This returns a sorted list of 3-letter location codes.
|
|
1093
|
+
|
|
1094
|
+
You can get the datasets belonging to a location as follows:
|
|
1095
|
+
|
|
1096
|
+
datasets = location.get_datasets(**kwargs)
|
|
1097
|
+
|
|
1098
|
+
Various additional arguments (`**kwargs`) can be supplied. These are detailed in the API
|
|
1099
|
+
documentation.
|
|
1100
|
+
|
|
1101
|
+
You can autocomplete a location name:
|
|
1102
|
+
|
|
1103
|
+
matches = Location.autocomplete("NAME")
|
|
1104
|
+
|
|
1105
|
+
For simple name/code lookups against a cached list of valid locations (eg. converting
|
|
1106
|
+
between a country name and its HDX code) rather than the full **Location** object, see
|
|
1107
|
+
the **Locations** helper class instead.
|
|
1108
|
+
|
|
1039
1109
|
## Vocabulary Management
|
|
1040
1110
|
|
|
1041
1111
|
The **Vocabulary** class enables you to manage CKAN vocabularies, creating, deleting and
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (6,
|
|
21
|
+
__version__ = version = '6.7.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (6, 7, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
|
|
5
5
|
from hdx.api.configuration import Configuration
|
|
6
|
+
from hdx.data.location import Location
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class Locations:
|
|
@@ -22,10 +23,8 @@ class Locations:
|
|
|
22
23
|
A list of valid locations
|
|
23
24
|
"""
|
|
24
25
|
if cls._validlocations is None:
|
|
25
|
-
|
|
26
|
-
configuration =
|
|
27
|
-
cls._validlocations = configuration.call_remoteckan(
|
|
28
|
-
"group_list", {"all_fields": True}
|
|
26
|
+
cls._validlocations = Location.get_all_location_names(
|
|
27
|
+
configuration=configuration, all_fields=True
|
|
29
28
|
)
|
|
30
29
|
return cls._validlocations
|
|
31
30
|
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Location class containing all logic for creating, checking, and updating locations."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from hdx.api.configuration import Configuration
|
|
8
|
+
from hdx.data.hdxobject import HDXObject
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Location(HDXObject):
|
|
14
|
+
"""Location class containing all logic for creating, checking, and updating locations.
|
|
15
|
+
A location is a CKAN group.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
initial_data: Initial location metadata dictionary. Defaults to None.
|
|
19
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
initial_data: dict | None = None,
|
|
25
|
+
configuration: Configuration | None = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
if not initial_data:
|
|
28
|
+
initial_data = {}
|
|
29
|
+
super().__init__(initial_data, configuration=configuration)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def actions() -> dict[str, str]:
|
|
33
|
+
"""Dictionary of actions that can be performed on object
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dictionary of actions that can be performed on object
|
|
37
|
+
"""
|
|
38
|
+
return {
|
|
39
|
+
"show": "group_show",
|
|
40
|
+
"update": "group_update",
|
|
41
|
+
"create": "group_create",
|
|
42
|
+
"delete": "group_delete",
|
|
43
|
+
"list": "group_list",
|
|
44
|
+
"autocomplete": "group_autocomplete",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def read_from_hdx(
|
|
49
|
+
cls, identifier: str, configuration: Configuration | None = None
|
|
50
|
+
) -> Optional["Location"]:
|
|
51
|
+
"""Reads the location given by identifier from HDX and returns Location object
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
identifier: Identifier of location
|
|
55
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Location object if successful read, None if not
|
|
59
|
+
"""
|
|
60
|
+
return cls._read_from_hdx_class("group", identifier, configuration)
|
|
61
|
+
|
|
62
|
+
def check_required_fields(self, ignore_fields: Sequence[str] = ()) -> None:
|
|
63
|
+
"""Check that metadata for location is complete. The parameter ignore_fields should
|
|
64
|
+
be set if required to any fields that should be ignored for the particular operation.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
ignore_fields: Fields to ignore. Default is ().
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
None
|
|
71
|
+
"""
|
|
72
|
+
self._check_required_fields("group", ignore_fields)
|
|
73
|
+
|
|
74
|
+
def update_in_hdx(self, **kwargs: Any) -> None:
|
|
75
|
+
"""Check if location exists in HDX and if so, update location
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
None
|
|
79
|
+
"""
|
|
80
|
+
self._update_in_hdx("group", "id", **kwargs)
|
|
81
|
+
|
|
82
|
+
def create_in_hdx(self, **kwargs: Any) -> None:
|
|
83
|
+
"""Check if location exists in HDX and if so, update it, otherwise create location
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
None
|
|
87
|
+
"""
|
|
88
|
+
self._create_in_hdx("group", "id", "name", **kwargs)
|
|
89
|
+
|
|
90
|
+
def delete_from_hdx(self) -> None:
|
|
91
|
+
"""Deletes a location from HDX.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
None
|
|
95
|
+
"""
|
|
96
|
+
self._delete_from_hdx("group", "id")
|
|
97
|
+
|
|
98
|
+
def get_datasets(self, query: str = "*:*", **kwargs: Any) -> list["Dataset"]: # noqa: F821
|
|
99
|
+
"""Get list of datasets in location
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
query: Restrict datasets returned to this query (in Solr format). Defaults to '*:*'.
|
|
103
|
+
**kwargs: See below
|
|
104
|
+
sort (string): Sorting of the search results. Defaults to 'relevance asc, metadata_modified desc'.
|
|
105
|
+
rows (int): Number of matching rows to return. Defaults to all datasets (sys.maxsize).
|
|
106
|
+
start (int): Offset in the complete result for where the set of returned datasets should begin
|
|
107
|
+
facet (string): Whether to enable faceted results. Default to True.
|
|
108
|
+
facet.mincount (int): Minimum counts for facet fields should be included in the results
|
|
109
|
+
facet.limit (int): Maximum number of values the facet fields return (- = unlimited). Defaults to 50.
|
|
110
|
+
facet.field (list[str]): Fields to facet upon. Default is empty.
|
|
111
|
+
use_default_schema (bool): Use default package schema instead of custom schema. Defaults to False.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
List of datasets in location
|
|
115
|
+
"""
|
|
116
|
+
import hdx.data.dataset # avoid circular import
|
|
117
|
+
|
|
118
|
+
return hdx.data.dataset.Dataset.search_in_hdx(
|
|
119
|
+
query=query,
|
|
120
|
+
configuration=self.configuration,
|
|
121
|
+
fq=f"groups:{self.data['name']}",
|
|
122
|
+
**kwargs,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def get_all_location_names(
|
|
127
|
+
configuration: Configuration | None = None, **kwargs: Any
|
|
128
|
+
) -> list[str]:
|
|
129
|
+
"""Get all location names in HDX
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
133
|
+
**kwargs: See below
|
|
134
|
+
sort (str): Sort the search results according to field name and sort-order. Allowed fields are ‘name’, ‘package_count’ and ‘title’. Defaults to 'name asc'.
|
|
135
|
+
groups (list[str]): List of names of the groups to return.
|
|
136
|
+
all_fields (bool): Return group dictionaries instead of just names. Only core fields are returned - get some more using the include_* options. Defaults to False.
|
|
137
|
+
include_extras (bool): If all_fields, include the group extra fields. Defaults to False.
|
|
138
|
+
include_tags (bool): If all_fields, include the group tags. Defaults to False.
|
|
139
|
+
include_groups (bool): If all_fields, include the groups the groups are in. Defaults to False.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
List of all location names in HDX
|
|
143
|
+
"""
|
|
144
|
+
location = Location(configuration=configuration)
|
|
145
|
+
|
|
146
|
+
# Early return for the standard, non-paginated case
|
|
147
|
+
if not kwargs.get("all_fields"):
|
|
148
|
+
return location._write_to_hdx("list", kwargs)
|
|
149
|
+
|
|
150
|
+
compiled_locations = {}
|
|
151
|
+
# Extract limit and offset to dictate our paging sizes, defaulting to 400 and 0
|
|
152
|
+
page_size = kwargs.pop("limit", 400)
|
|
153
|
+
current_offset = kwargs.pop("offset", 0)
|
|
154
|
+
|
|
155
|
+
# 1. Fetch in pages using the provided/default page size
|
|
156
|
+
while True:
|
|
157
|
+
page_kwargs = kwargs.copy()
|
|
158
|
+
page_kwargs["limit"] = page_size
|
|
159
|
+
page_kwargs["offset"] = current_offset
|
|
160
|
+
|
|
161
|
+
page_data = location._write_to_hdx("list", page_kwargs)
|
|
162
|
+
|
|
163
|
+
if not page_data:
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
# Store by name to automatically deduplicate offset shifts
|
|
167
|
+
for loc in page_data:
|
|
168
|
+
compiled_locations[loc["name"]] = loc
|
|
169
|
+
|
|
170
|
+
if len(page_data) < page_size:
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
current_offset += page_size
|
|
174
|
+
|
|
175
|
+
# 2. Establish Ground Truth LAST (without limit/offset constraints)
|
|
176
|
+
truth_kwargs = kwargs.copy()
|
|
177
|
+
truth_kwargs["all_fields"] = False
|
|
178
|
+
|
|
179
|
+
ground_truth_names = location._write_to_hdx("list", truth_kwargs)
|
|
180
|
+
|
|
181
|
+
# 3 & 4. Check for missing IDs, fetch individually, and implicitly prune deletes
|
|
182
|
+
final_locations = []
|
|
183
|
+
for name in ground_truth_names:
|
|
184
|
+
if name in compiled_locations:
|
|
185
|
+
final_locations.append(compiled_locations[name])
|
|
186
|
+
else:
|
|
187
|
+
try:
|
|
188
|
+
missing_location = location._write_to_hdx("show", {"id": name})
|
|
189
|
+
if missing_location:
|
|
190
|
+
final_locations.append(missing_location)
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
return final_locations
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def get_data_grid_countries(
|
|
198
|
+
configuration: Configuration | None = None,
|
|
199
|
+
) -> list[str]:
|
|
200
|
+
"""Fetch HDX's active Data Grid countries (3-letter group names).
|
|
201
|
+
|
|
202
|
+
Treated as a live/short-TTL lookup rather than a frozen static list, since Data Grid
|
|
203
|
+
membership changes over time.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Sorted list of active Data Grid country (group) names
|
|
210
|
+
"""
|
|
211
|
+
groups = Location.get_all_location_names(
|
|
212
|
+
configuration=configuration, all_fields=True, include_extras=True
|
|
213
|
+
)
|
|
214
|
+
return sorted(
|
|
215
|
+
group["name"]
|
|
216
|
+
for group in groups
|
|
217
|
+
if len(group["name"]) == 3 and group.get("data_completeness") == "active"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def autocomplete(
|
|
222
|
+
cls,
|
|
223
|
+
name: str,
|
|
224
|
+
limit: int = 20,
|
|
225
|
+
configuration: Configuration | None = None,
|
|
226
|
+
) -> list:
|
|
227
|
+
"""Autocomplete a location name and return matches
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
name: Name to autocomplete
|
|
231
|
+
limit: Maximum number of matches to return
|
|
232
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Autocomplete matches
|
|
236
|
+
"""
|
|
237
|
+
return cls._autocomplete(name, limit, configuration)
|
|
@@ -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
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
- id: afg
|
|
2
|
+
name: afg
|
|
3
|
+
title: Afghanistan
|
|
4
|
+
display_name: Afghanistan
|
|
5
|
+
type: group
|
|
6
|
+
is_organization: false
|
|
7
|
+
state: active
|
|
8
|
+
data_completeness: active
|
|
9
|
+
description: ""
|
|
10
|
+
package_count: 480
|
|
11
|
+
|
|
12
|
+
- id: zmb
|
|
13
|
+
name: zmb
|
|
14
|
+
title: Zambia
|
|
15
|
+
display_name: Zambia
|
|
16
|
+
type: group
|
|
17
|
+
is_organization: false
|
|
18
|
+
state: active
|
|
19
|
+
data_completeness: inactive
|
|
20
|
+
description: ""
|
|
21
|
+
package_count: 120
|
|
22
|
+
|
|
23
|
+
- id: world
|
|
24
|
+
name: world
|
|
25
|
+
title: World
|
|
26
|
+
display_name: World
|
|
27
|
+
type: group
|
|
28
|
+
is_organization: false
|
|
29
|
+
state: active
|
|
30
|
+
description: ""
|
|
31
|
+
package_count: 12
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
id: "f8b0e58f-9be5-4b25-9d5f-1c8b6d8b6a0a"
|
|
2
|
+
name: gnq
|
|
3
|
+
title: "Equatorial Guinea"
|
|
4
|
+
display_name: "Equatorial Guinea"
|
|
5
|
+
type: group
|
|
6
|
+
is_organization: false
|
|
7
|
+
state: active
|
|
8
|
+
data_completeness: active
|
|
9
|
+
description: ""
|
|
10
|
+
package_count: 5
|
|
11
|
+
created: "2015-01-09T14:44:54.006612"
|
|
@@ -487,6 +487,12 @@ hello there"""
|
|
|
487
487
|
"description",
|
|
488
488
|
]
|
|
489
489
|
},
|
|
490
|
+
"group": {
|
|
491
|
+
"required_fields": [
|
|
492
|
+
"name",
|
|
493
|
+
"title",
|
|
494
|
+
]
|
|
495
|
+
},
|
|
490
496
|
"resource view": {
|
|
491
497
|
"required_fields": [
|
|
492
498
|
"resource_id",
|
|
@@ -617,6 +623,12 @@ hello there"""
|
|
|
617
623
|
"description",
|
|
618
624
|
]
|
|
619
625
|
},
|
|
626
|
+
"group": {
|
|
627
|
+
"required_fields": [
|
|
628
|
+
"name",
|
|
629
|
+
"title",
|
|
630
|
+
]
|
|
631
|
+
},
|
|
620
632
|
"resource view": {
|
|
621
633
|
"required_fields": [
|
|
622
634
|
"resource_id",
|
|
@@ -739,6 +751,12 @@ hello there"""
|
|
|
739
751
|
"description",
|
|
740
752
|
]
|
|
741
753
|
},
|
|
754
|
+
"group": {
|
|
755
|
+
"required_fields": [
|
|
756
|
+
"name",
|
|
757
|
+
"title",
|
|
758
|
+
]
|
|
759
|
+
},
|
|
742
760
|
"resource view": {
|
|
743
761
|
"required_fields": [
|
|
744
762
|
"resource_id",
|
|
@@ -7,8 +7,10 @@ from hdx.api.locations import Locations
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class MyConfiguration:
|
|
10
|
-
def call_remoteckan(self, a, b):
|
|
11
|
-
|
|
10
|
+
def call_remoteckan(self, a, b, files=None):
|
|
11
|
+
if b.get("all_fields"):
|
|
12
|
+
return [{"name": "zaf", "title": "South Africa"}]
|
|
13
|
+
return ["zaf"]
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class TestHDXLocations:
|