hdx-python-api 6.7.2__tar.gz → 6.7.3__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.7.2 → hdx_python_api-6.7.3}/PKG-INFO +1 -1
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/documentation/index.md +80 -5
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/_version.py +2 -2
- hdx_python_api-6.7.3/src/hdx/api/utilities/hdx_stats.py +47 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/activity.py +3 -1
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/dataset.py +21 -7
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/hdxobject.py +28 -1
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/location.py +1 -7
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/organization.py +1 -7
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/resource.py +163 -18
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/user.py +21 -8
- hdx_python_api-6.7.3/tests/hdx/api/utilities/test_hdx_stats.py +59 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_activity.py +6 -1
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_dataset_noncore.py +38 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_resource.py +210 -1
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_user.py +28 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/.github/workflows/publish.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/.github/workflows/run-python-tests.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/.gitignore +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/.pre-commit-config.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/CLAUDE.md +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/LICENSE +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/README.md +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/documentation/.readthedocs.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/documentation/mkdocs.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/pyproject.toml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/configuration.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/hdx_base_configuration.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/locations.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/remotehdx.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/utilities/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/utilities/date_helper.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/utilities/filestore_helper.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/utilities/hdx_error_handler.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/utilities/hdx_state.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/api/utilities/url_utils.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/resource_matcher.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/resource_view.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/showcase.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/data/vocabulary.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/facades/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/facades/infer_arguments.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/facades/keyword_arguments.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/src/hdx/facades/simple.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/Accepted_Tags.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/CKAN/hdx_dataset_static.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/Tag_Mapping.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/Tag_Mapping_ChainRuleError.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/empty.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_base_config.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_base_config.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_config.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_config.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_dataset_static.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_dataset_static.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_datasource_topline.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_datasource_topline.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_email_configuration.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_missing_site_config.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_organization_static.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_organization_static.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_resource_static.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_resource_static.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_resource_view_static.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_resource_view_static.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_showcase_static.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_showcase_static.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_user_static.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_user_static.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_vocabulary_static.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/hdx_vocabulary_static.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/project_configuration.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/project_configuration.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/user_agent_config.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/user_agent_config2.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/user_agent_config3.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/dataset_search_results.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/download_gen_resource/conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/download_gen_resource/min_qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/download_gen_resource/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/download_gen_resource/test_data_no_data.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/download_gen_resource/test_data_no_years.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/empty.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/gen_resource/conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/gen_resource/min_qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/gen_resource/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/gen_resource/test_data_no_data.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/gen_resource/test_data_no_years.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/location/location_list_all_fields.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/location/location_show_results.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/organization/organization_list_all_fields.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/organization/organization_show_results.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/qc_from_rows/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/qc_from_rows/qc_conflict_data_alg_one_col.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/resource_formats.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/showcase_all_search_results.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/size_hash/ACLED-All-Africa-File_20170101-to-20170708.xlsx +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/state/analysis_dates.txt +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/state/last_build_date.txt +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/test_data.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/test_data.zip +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/dem_data_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/dem_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/expected_resources_to_update.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/opri_data_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/opri_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/opri_metadata_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/qc_sdg_data_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/sdg_data_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/sdg_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/sdg_metadata_zwe.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/unesco_dataset.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_dataset_resources/unesco_update_dataset.json +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_logic/update_logic_resources.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/fixtures/update_logic/update_logic_resources_new.yaml +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/test_ckan.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/test_configuration.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/test_locations.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/utilities/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/utilities/test_filestore_helper.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/utilities/test_hdx_error_handler.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/utilities/test_hdx_state.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/api/utilities/test_url_utils.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/conftest.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_dataset_add_hapi_error.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_dataset_core.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_dataset_resource_generation.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_location.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_organization.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_resource_view.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_showcase.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_update_dataset_resources.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_update_logic.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/data/test_vocabulary.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/facades/__init__.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/facades/test_infer_arguments.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/facades/test_keyword_arguments.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/tests/hdx/facades/test_simple.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/uv.lock +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/workingexample/my_code.py +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/workingexample/my_resource.csv +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/workingexample/my_resource.xlsx +0 -0
- {hdx_python_api-6.7.2 → hdx_python_api-6.7.3}/workingexample/run.py +0 -0
|
@@ -36,6 +36,7 @@ upload your datasets to HDX.
|
|
|
36
36
|
- [Location Management](#location-management)
|
|
37
37
|
- [Vocabulary Management](#vocabulary-management)
|
|
38
38
|
- [Activity Streams](#activity-streams)
|
|
39
|
+
- [HDX Statistics](#hdx-statistics)
|
|
39
40
|
- [Pipeline State](#pipeline-state)
|
|
40
41
|
- [Working Examples](#working-examples)
|
|
41
42
|
- [Project Framework](#project-framework)
|
|
@@ -636,6 +637,16 @@ function, for example:
|
|
|
636
637
|
|
|
637
638
|
dataset.remove_showcase(showcase)
|
|
638
639
|
|
|
640
|
+
HDX runs file structure checks on resources, recording schema changes (eg.
|
|
641
|
+
column names, order or types) and whether sheets have merged cells. You can get
|
|
642
|
+
these checks for all of a dataset's resources from HDX:
|
|
643
|
+
|
|
644
|
+
fs_check_info = dataset.get_fs_check_info()
|
|
645
|
+
|
|
646
|
+
This returns a list of dictionaries, each from a resource name to a list of
|
|
647
|
+
check events with keys such as `state`, `timestamp`, `sheet_changes` and
|
|
648
|
+
`hxl_proxy_response`.
|
|
649
|
+
|
|
639
650
|
### Time Period
|
|
640
651
|
|
|
641
652
|
Time Period is a mandatory field in HDX. It is the earliest start date and
|
|
@@ -947,6 +958,12 @@ If the method **set_file_to_upload** is used to supply a file, the resource
|
|
|
947
958
|
`last_modified` field is set to now automatically regardless of the value of
|
|
948
959
|
`data_updated` or whether **mark_data_updated** has been called.
|
|
949
960
|
|
|
961
|
+
The file structure check events of a resource (see Dataset Specific
|
|
962
|
+
Operations) can be obtained from its `fs_check_info` field without a call to
|
|
963
|
+
HDX:
|
|
964
|
+
|
|
965
|
+
events = resource.get_fs_check_info()
|
|
966
|
+
|
|
950
967
|
### Data Dictionary
|
|
951
968
|
|
|
952
969
|
A resource can have a data dictionary describing the columns in the data it
|
|
@@ -974,11 +991,36 @@ using the getter:
|
|
|
974
991
|
|
|
975
992
|
data_dictionary = resource.get_hdx_data_dictionary()
|
|
976
993
|
|
|
994
|
+
To change the data dictionary of a resource already in HDX without sending
|
|
995
|
+
dataset update notifications, use **update_hdx_data_dictionary**. It patches
|
|
996
|
+
only the `hdx_data_dictionary` field (so `last_modified` is unchanged), then,
|
|
997
|
+
if the resource has a datastore, updates the datastore field labels and
|
|
998
|
+
descriptions without reloading the data. The given columns are merged by
|
|
999
|
+
`field` into the existing data dictionary in the resource metadata, so read
|
|
1000
|
+
the resource from HDX first. Pass `replace=True` to replace the data
|
|
1001
|
+
dictionary instead and `update_datastore=False` to leave the datastore alone:
|
|
1002
|
+
|
|
1003
|
+
resource = Resource.read_from_hdx(RESOURCE_ID)
|
|
1004
|
+
resource.update_hdx_data_dictionary([
|
|
1005
|
+
{
|
|
1006
|
+
"field": "col_name",
|
|
1007
|
+
"label": "Column Label",
|
|
1008
|
+
"description": "New description.",
|
|
1009
|
+
},
|
|
1010
|
+
])
|
|
1011
|
+
|
|
1012
|
+
Columns are matched to datastore fields by `field`. A warning is logged for
|
|
1013
|
+
any column with no matching datastore field.
|
|
1014
|
+
|
|
977
1015
|
### Datastore
|
|
978
1016
|
|
|
979
|
-
A resource's data can be loaded into the HDX datastore and queried. HDX
|
|
980
|
-
|
|
981
|
-
|
|
1017
|
+
A resource's data can be loaded into the HDX datastore and queried. HDX calls this
|
|
1018
|
+
[Tabular Data Endpoints (TDE)](https://docs.humdata.org/build/hdx-apis/tabular-data-endpoints),
|
|
1019
|
+
and it is available only for resources with TDE enabled (their `datastore_active`
|
|
1020
|
+
field is true). Contact hdx@un.org to request TDE for your datasets. HDX requires an
|
|
1021
|
+
API key for all datastore actions and dumps, including reads of public resources,
|
|
1022
|
+
and limits each IP address to 60 requests per minute. To create, update or delete
|
|
1023
|
+
the datastore for a resource:
|
|
982
1024
|
|
|
983
1025
|
resource.create_datastore(
|
|
984
1026
|
[{"id": "code", "type": "text"}, {"id": "value", "type": "numeric"}],
|
|
@@ -988,6 +1030,17 @@ public resources. To create, update or delete the datastore for a resource:
|
|
|
988
1030
|
has_datastore = resource.has_datastore()
|
|
989
1031
|
resource.delete_datastore()
|
|
990
1032
|
|
|
1033
|
+
HDX loads uploaded files into the datastore using DataPusher+. To reload a
|
|
1034
|
+
resource's file into the datastore without uploading it again (so
|
|
1035
|
+
`last_modified` is unchanged), use **submit_to_datapusher**. It returns False
|
|
1036
|
+
if the job was not submitted, eg. because one is already pending. HDX's
|
|
1037
|
+
datastore allowlist and format checks, which apply to automatic submissions
|
|
1038
|
+
on upload, are not applied. **get_datapusher_status** returns the status of
|
|
1039
|
+
the latest job or None if there is none:
|
|
1040
|
+
|
|
1041
|
+
submitted = resource.submit_to_datapusher(ignore_hash=True)
|
|
1042
|
+
status = resource.get_datapusher_status() # eg. status["status"] == "complete"
|
|
1043
|
+
|
|
991
1044
|
To download the whole datastore (rather than the resource's file) use
|
|
992
1045
|
**download_datastore**, which returns the URL and path like **download**.
|
|
993
1046
|
`file_format` can be `csv` (default), `tsv`, `json` or `xml`, and `filename`
|
|
@@ -1008,7 +1061,8 @@ and `include_total`, and passes any other keyword arguments to CKAN:
|
|
|
1008
1061
|
records = result["records"]
|
|
1009
1062
|
|
|
1010
1063
|
**get_datastore_records** takes the same query parameters but pages through
|
|
1011
|
-
all matching rows, yielding each record (`page_size` defaults to 10000)
|
|
1064
|
+
all matching rows, yielding each record (`page_size` defaults to 10000).
|
|
1065
|
+
`records_format` csv and tsv are not supported; use `search_datastore` for those:
|
|
1012
1066
|
|
|
1013
1067
|
for record in resource.get_datastore_records(filters={"code": "A"}):
|
|
1014
1068
|
...
|
|
@@ -1020,7 +1074,9 @@ internal `_id` field:
|
|
|
1020
1074
|
|
|
1021
1075
|
**search_datastore_sql** wraps CKAN's `datastore_search_sql`, running a
|
|
1022
1076
|
SELECT statement where tables are named by double quoted resource id and
|
|
1023
|
-
returning the list of records
|
|
1077
|
+
returning the list of records. HDX returns at most 32,000 rows per query and
|
|
1078
|
+
cuts off larger results, in which case a warning is logged (see
|
|
1079
|
+
[paging in the TDE docs](https://docs.humdata.org/build/hdx-apis/tabular-data-endpoints/troubleshooting-and-error-handling#pagination)):
|
|
1024
1080
|
|
|
1025
1081
|
records = Resource.search_datastore_sql(
|
|
1026
1082
|
'SELECT code, SUM(value) AS total FROM "RESOURCE_ID" GROUP BY code'
|
|
@@ -1067,6 +1123,11 @@ configuration):
|
|
|
1067
1123
|
|
|
1068
1124
|
user = User.get_current_user()
|
|
1069
1125
|
|
|
1126
|
+
You can get the name of the API token in the configuration and the md5 hash
|
|
1127
|
+
of its owner's email:
|
|
1128
|
+
|
|
1129
|
+
token_info = User.get_current_token_info() # {"email_hash": ..., "token_name": ...}
|
|
1130
|
+
|
|
1070
1131
|
You can check that the current user has a particular permission to a specific
|
|
1071
1132
|
organization:
|
|
1072
1133
|
|
|
@@ -1223,6 +1284,20 @@ permission to edit the object the activity relates to for these:
|
|
|
1223
1284
|
data = activity.get_data()
|
|
1224
1285
|
diff = activity.get_diff(diff_type="unified") # or "context" or "html"
|
|
1225
1286
|
|
|
1287
|
+
## HDX Statistics
|
|
1288
|
+
|
|
1289
|
+
General statistics about datasets, organizations and users on HDX can be
|
|
1290
|
+
obtained as follows:
|
|
1291
|
+
|
|
1292
|
+
from hdx.api.utilities.hdx_stats import get_general_statistics, push_general_stats
|
|
1293
|
+
|
|
1294
|
+
stats = get_general_statistics()
|
|
1295
|
+
|
|
1296
|
+
Users with permission to manage basic scheduled tasks can compute the statistics
|
|
1297
|
+
and push them to analytics:
|
|
1298
|
+
|
|
1299
|
+
pushed_stats = push_general_stats()
|
|
1300
|
+
|
|
1226
1301
|
## Pipeline State
|
|
1227
1302
|
|
|
1228
1303
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (6, 7,
|
|
21
|
+
__version__ = version = '6.7.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (6, 7, 3)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,47 @@
|
|
|
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(action: str, configuration: Configuration | None) -> dict:
|
|
8
|
+
"""Call an HDX statistics action
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
action: CKAN action name
|
|
12
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Statistics dictionary
|
|
16
|
+
"""
|
|
17
|
+
if configuration is None:
|
|
18
|
+
configuration = Configuration.read()
|
|
19
|
+
try:
|
|
20
|
+
return configuration.call_remoteckan(action, {})
|
|
21
|
+
except Exception as e:
|
|
22
|
+
raise HDXError(f"Failed when trying to call {action}!") from e
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_general_statistics(configuration: Configuration | None = None) -> dict:
|
|
26
|
+
"""Get general statistics about datasets, organizations and users on HDX
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Dictionary with keys datasets, organizations and users
|
|
33
|
+
"""
|
|
34
|
+
return _call_stats_action("hdx_general_statistics", configuration)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def push_general_stats(configuration: Configuration | None = None) -> dict:
|
|
38
|
+
"""Compute general statistics on HDX and push them to analytics. Requires
|
|
39
|
+
permission to manage basic scheduled tasks.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Statistics that were pushed
|
|
46
|
+
"""
|
|
47
|
+
return _call_stats_action("hdx_push_general_stats", configuration)
|
|
@@ -129,7 +129,9 @@ class Activity(HDXObject):
|
|
|
129
129
|
result = activity._write_to_hdx(action, data)
|
|
130
130
|
return [cls(x, configuration=configuration) for x in result]
|
|
131
131
|
data = dict(data)
|
|
132
|
-
limit
|
|
132
|
+
# HDX silently caps limit at ckan.activity_list_limit_max
|
|
133
|
+
limit = min(data.get("limit", 100), 100)
|
|
134
|
+
data["limit"] = limit
|
|
133
135
|
offset = data.get("offset", 0)
|
|
134
136
|
activities = []
|
|
135
137
|
while True:
|
|
@@ -147,6 +147,7 @@ class Dataset(HDXObject):
|
|
|
147
147
|
"list": "package_list",
|
|
148
148
|
"autocomplete": "package_autocomplete",
|
|
149
149
|
"create_default_views": "package_create_default_resource_views",
|
|
150
|
+
"fs_check_info": "fs_check_info_show",
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
def __setitem__(self, key: Any, value: Any) -> None:
|
|
@@ -1447,13 +1448,7 @@ class Dataset(HDXObject):
|
|
|
1447
1448
|
Returns:
|
|
1448
1449
|
List of Activity objects
|
|
1449
1450
|
"""
|
|
1450
|
-
return
|
|
1451
|
-
"package",
|
|
1452
|
-
self.data.get("id") or self.data.get("name"),
|
|
1453
|
-
self.configuration,
|
|
1454
|
-
all_pages,
|
|
1455
|
-
**kwargs,
|
|
1456
|
-
)
|
|
1451
|
+
return self._get_activities("package", all_pages, **kwargs)
|
|
1457
1452
|
|
|
1458
1453
|
@classmethod
|
|
1459
1454
|
def autocomplete(
|
|
@@ -2333,6 +2328,25 @@ class Dataset(HDXObject):
|
|
|
2333
2328
|
return None
|
|
2334
2329
|
return f"{self.configuration.get_hdx_site_url()}/api/3/action/package_show?id={name_or_id}"
|
|
2335
2330
|
|
|
2331
|
+
def get_fs_check_info(self) -> list[dict]:
|
|
2332
|
+
"""Get file structure check information for the dataset's resources
|
|
2333
|
+
from HDX. Each list item is a dictionary from resource name to a list
|
|
2334
|
+
of check events, each of which can have keys such as state, timestamp,
|
|
2335
|
+
sheet_changes and hxl_proxy_response.
|
|
2336
|
+
|
|
2337
|
+
Returns:
|
|
2338
|
+
List of dictionaries from resource name to file structure checks
|
|
2339
|
+
"""
|
|
2340
|
+
success, result = self._read_from_hdx(
|
|
2341
|
+
"dataset",
|
|
2342
|
+
self.get_name_or_id(prefer_name=False),
|
|
2343
|
+
"id",
|
|
2344
|
+
self.actions()["fs_check_info"],
|
|
2345
|
+
)
|
|
2346
|
+
if success:
|
|
2347
|
+
return result
|
|
2348
|
+
return []
|
|
2349
|
+
|
|
2336
2350
|
def generate_resource(
|
|
2337
2351
|
self,
|
|
2338
2352
|
folder: Path | str,
|
|
@@ -9,7 +9,7 @@ from collections import UserDict
|
|
|
9
9
|
from collections.abc import Sequence
|
|
10
10
|
from os.path import isfile
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Any, Optional, Union
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
13
13
|
|
|
14
14
|
from ckanapi.errors import NotFound
|
|
15
15
|
from hdx.utilities.dictandlist import merge_two_dictionaries
|
|
@@ -20,6 +20,9 @@ from hdx.utilities.loader import (
|
|
|
20
20
|
|
|
21
21
|
from hdx.api.configuration import Configuration
|
|
22
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from hdx.data.activity import Activity
|
|
25
|
+
|
|
23
26
|
logger = logging.getLogger(__name__)
|
|
24
27
|
|
|
25
28
|
|
|
@@ -376,6 +379,30 @@ class HDXObject(UserDict, ABC):
|
|
|
376
379
|
**kwargs,
|
|
377
380
|
)
|
|
378
381
|
|
|
382
|
+
def _get_activities(
|
|
383
|
+
self, object_type: str, all_pages: bool = False, **kwargs: Any
|
|
384
|
+
) -> list["Activity"]:
|
|
385
|
+
"""Helper method to get the activity stream of this object
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
object_type: One of package, organization, group or user
|
|
389
|
+
all_pages: Whether to get all pages of results. Defaults to False.
|
|
390
|
+
**kwargs: Parameters to pass to Activity.get_activities
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
List of Activity objects
|
|
394
|
+
"""
|
|
395
|
+
# Imported here as hdx.data.activity imports this module
|
|
396
|
+
from hdx.data.activity import Activity
|
|
397
|
+
|
|
398
|
+
return Activity.get_activities(
|
|
399
|
+
object_type,
|
|
400
|
+
self.data.get("id") or self.data.get("name"),
|
|
401
|
+
self.configuration,
|
|
402
|
+
all_pages,
|
|
403
|
+
**kwargs,
|
|
404
|
+
)
|
|
405
|
+
|
|
379
406
|
def _write_to_hdx(
|
|
380
407
|
self,
|
|
381
408
|
action: str,
|
|
@@ -232,13 +232,7 @@ class Location(HDXObject):
|
|
|
232
232
|
Returns:
|
|
233
233
|
List of Activity objects
|
|
234
234
|
"""
|
|
235
|
-
return
|
|
236
|
-
"group",
|
|
237
|
-
self.data.get("id") or self.data.get("name"),
|
|
238
|
-
self.configuration,
|
|
239
|
-
all_pages,
|
|
240
|
-
**kwargs,
|
|
241
|
-
)
|
|
235
|
+
return self._get_activities("group", all_pages, **kwargs)
|
|
242
236
|
|
|
243
237
|
@classmethod
|
|
244
238
|
def autocomplete(
|
|
@@ -326,13 +326,7 @@ class Organization(HDXObject):
|
|
|
326
326
|
Returns:
|
|
327
327
|
List of Activity objects
|
|
328
328
|
"""
|
|
329
|
-
return
|
|
330
|
-
"organization",
|
|
331
|
-
self.data.get("id") or self.data.get("name"),
|
|
332
|
-
self.configuration,
|
|
333
|
-
all_pages,
|
|
334
|
-
**kwargs,
|
|
335
|
-
)
|
|
329
|
+
return self._get_activities("organization", all_pages, **kwargs)
|
|
336
330
|
|
|
337
331
|
@classmethod
|
|
338
332
|
def autocomplete(
|
|
@@ -72,6 +72,8 @@ class Resource(HDXObject):
|
|
|
72
72
|
"datastore_upsert": "datastore_upsert",
|
|
73
73
|
"datastore_search": "datastore_search",
|
|
74
74
|
"datastore_search_sql": "datastore_search_sql",
|
|
75
|
+
"datapusher_submit": "datapusher_submit",
|
|
76
|
+
"datapusher_status": "datapusher_status",
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
def update_from_yaml(
|
|
@@ -319,6 +321,69 @@ class Resource(HDXObject):
|
|
|
319
321
|
data_dictionary, separators=(",", ":")
|
|
320
322
|
)
|
|
321
323
|
|
|
324
|
+
def update_hdx_data_dictionary(
|
|
325
|
+
self,
|
|
326
|
+
data_dictionary: Sequence[dict],
|
|
327
|
+
replace: bool = False,
|
|
328
|
+
update_datastore: bool = True,
|
|
329
|
+
) -> None:
|
|
330
|
+
"""Update the resource's data dictionary (column definitions) in HDX
|
|
331
|
+
without changing last_modified, so no dataset update notifications are
|
|
332
|
+
sent. Column definitions are as for set_hdx_data_dictionary. Unless
|
|
333
|
+
replace is True, they are merged by field into the existing data
|
|
334
|
+
dictionary in the resource metadata (so call read_from_hdx first).
|
|
335
|
+
The resource's hdx_data_dictionary field is patched, then if
|
|
336
|
+
update_datastore is True and the resource has a datastore, the
|
|
337
|
+
datastore field labels and descriptions are updated using
|
|
338
|
+
datastore_create without reloading the data.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
data_dictionary: Sequence of column definition dictionaries
|
|
342
|
+
replace: Replace rather than merge into existing data dictionary. Defaults to False.
|
|
343
|
+
update_datastore: Update datastore field labels and descriptions. Defaults to True.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
None
|
|
347
|
+
"""
|
|
348
|
+
if replace:
|
|
349
|
+
merged = [dict(column) for column in data_dictionary]
|
|
350
|
+
else:
|
|
351
|
+
new_columns = {column["field"]: column for column in data_dictionary}
|
|
352
|
+
merged = []
|
|
353
|
+
for column in self.get_hdx_data_dictionary() or []:
|
|
354
|
+
new_column = new_columns.pop(column.get("field"), None)
|
|
355
|
+
merged.append({**column, **new_column} if new_column else column)
|
|
356
|
+
merged.extend(new_columns.values())
|
|
357
|
+
self.set_hdx_data_dictionary(merged)
|
|
358
|
+
self._write_to_hdx(
|
|
359
|
+
"patch",
|
|
360
|
+
{
|
|
361
|
+
"id": self.data["id"],
|
|
362
|
+
"hdx_data_dictionary": self.data["hdx_data_dictionary"],
|
|
363
|
+
},
|
|
364
|
+
"id",
|
|
365
|
+
)
|
|
366
|
+
if not update_datastore or not self.has_datastore():
|
|
367
|
+
return
|
|
368
|
+
columns = {column["field"]: column for column in merged}
|
|
369
|
+
fields = []
|
|
370
|
+
for field in self.get_datastore_fields():
|
|
371
|
+
info = dict(field.get("info") or {})
|
|
372
|
+
column = columns.pop(field["id"], None)
|
|
373
|
+
if column:
|
|
374
|
+
info["label"] = column["label"]
|
|
375
|
+
info["notes"] = column["description"]
|
|
376
|
+
fields.append({"id": field["id"], "type": field["type"], "info": info})
|
|
377
|
+
if columns:
|
|
378
|
+
logger.warning(
|
|
379
|
+
f"Data dictionary fields {', '.join(columns)} not in datastore for resource {self.data['id']}!"
|
|
380
|
+
)
|
|
381
|
+
self._write_to_hdx(
|
|
382
|
+
"datastore_create",
|
|
383
|
+
{"resource_id": self.data["id"], "force": True, "fields": fields},
|
|
384
|
+
"resource_id",
|
|
385
|
+
)
|
|
386
|
+
|
|
322
387
|
def clean_format(self) -> str:
|
|
323
388
|
"""Clean the resource's format, setting it to None if it is invalid and
|
|
324
389
|
cannot be mapped
|
|
@@ -683,6 +748,30 @@ class Resource(HDXObject):
|
|
|
683
748
|
logger.debug(result)
|
|
684
749
|
return resources
|
|
685
750
|
|
|
751
|
+
def _download_file(
|
|
752
|
+
self,
|
|
753
|
+
url: str,
|
|
754
|
+
folder: Path | str | None,
|
|
755
|
+
filename: str,
|
|
756
|
+
retriever: Retrieve | None,
|
|
757
|
+
) -> Path:
|
|
758
|
+
"""Helper method to download a file from HDX
|
|
759
|
+
|
|
760
|
+
Args:
|
|
761
|
+
url: URL to download
|
|
762
|
+
folder: Folder to download file to. None means temporary folder.
|
|
763
|
+
filename: Filename to use
|
|
764
|
+
retriever: Retrieve object to use. None means do not use one.
|
|
765
|
+
|
|
766
|
+
Returns:
|
|
767
|
+
Path to downloaded file
|
|
768
|
+
"""
|
|
769
|
+
session = hdx.api.utilities.url_utils.get_ckan_ready_session(self.configuration)
|
|
770
|
+
with Download(session=session) as downloader:
|
|
771
|
+
if retriever:
|
|
772
|
+
downloader = retriever.clone(downloader)
|
|
773
|
+
return downloader.download_file(url, folder=folder, filename=filename)
|
|
774
|
+
|
|
686
775
|
def download(
|
|
687
776
|
self, folder: Path | str | None = None, retriever: Retrieve | None = None
|
|
688
777
|
) -> tuple[str, Path]:
|
|
@@ -706,12 +795,7 @@ class Resource(HDXObject):
|
|
|
706
795
|
file_format = f".{self.get_format()}"
|
|
707
796
|
if not filename.endswith(file_format):
|
|
708
797
|
filename = f"{filename}{file_format}"
|
|
709
|
-
|
|
710
|
-
with Download(session=session) as downloader:
|
|
711
|
-
if retriever:
|
|
712
|
-
downloader = retriever.clone(downloader)
|
|
713
|
-
path = downloader.download_file(url, folder=folder, filename=filename)
|
|
714
|
-
return url, path
|
|
798
|
+
return url, self._download_file(url, folder, filename, retriever)
|
|
715
799
|
|
|
716
800
|
def get_datastore_dump_url(self, file_format: str = "csv") -> str:
|
|
717
801
|
"""Get the URL from which the resource datastore can be downloaded in
|
|
@@ -755,19 +839,15 @@ class Resource(HDXObject):
|
|
|
755
839
|
Returns:
|
|
756
840
|
(URL downloaded, Path to downloaded file)
|
|
757
841
|
"""
|
|
842
|
+
file_format = file_format.lower()
|
|
758
843
|
url = self.get_datastore_dump_url(file_format)
|
|
759
844
|
logger.debug(f"Downloading {url}")
|
|
760
845
|
if filename is None:
|
|
761
846
|
filename = self.data["name"]
|
|
762
|
-
extension = f".{file_format
|
|
847
|
+
extension = f".{file_format}"
|
|
763
848
|
if not filename.endswith(extension):
|
|
764
849
|
filename = f"{filename}{extension}"
|
|
765
|
-
|
|
766
|
-
with Download(session=session) as downloader:
|
|
767
|
-
if retriever:
|
|
768
|
-
downloader = retriever.clone(downloader)
|
|
769
|
-
path = downloader.download_file(url, folder=folder, filename=filename)
|
|
770
|
-
return url, path
|
|
850
|
+
return url, self._download_file(url, folder, filename, retriever)
|
|
771
851
|
|
|
772
852
|
@staticmethod
|
|
773
853
|
def get_all_resource_ids_in_datastore(
|
|
@@ -930,11 +1010,18 @@ class Resource(HDXObject):
|
|
|
930
1010
|
sort: Comma separated field names with optional asc/desc eg. "code desc". Defaults to None.
|
|
931
1011
|
distinct: Whether to return only distinct rows. Defaults to False.
|
|
932
1012
|
page_size: Number of rows to request per call. Defaults to 10000.
|
|
933
|
-
**kwargs: Other parameters to pass to datastore_search eg. plain, language.
|
|
1013
|
+
**kwargs: Other parameters to pass to datastore_search eg. plain, language. records_format csv and tsv are not supported.
|
|
934
1014
|
|
|
935
1015
|
Returns:
|
|
936
1016
|
Iterator of record dictionaries
|
|
937
1017
|
"""
|
|
1018
|
+
if page_size < 1:
|
|
1019
|
+
raise HDXError("page_size must be at least 1!")
|
|
1020
|
+
records_format = kwargs.get("records_format")
|
|
1021
|
+
if records_format in ("csv", "tsv"):
|
|
1022
|
+
raise HDXError(
|
|
1023
|
+
f"records_format {records_format} is not supported! Use search_datastore instead."
|
|
1024
|
+
)
|
|
938
1025
|
offset = 0
|
|
939
1026
|
while True:
|
|
940
1027
|
result = self.search_datastore(
|
|
@@ -950,9 +1037,11 @@ class Resource(HDXObject):
|
|
|
950
1037
|
)
|
|
951
1038
|
records = result["records"]
|
|
952
1039
|
yield from records
|
|
953
|
-
|
|
1040
|
+
# HDX caps limit at its maximum rows per call and returns the limit applied
|
|
1041
|
+
limit = result["limit"]
|
|
1042
|
+
if len(records) < limit:
|
|
954
1043
|
return
|
|
955
|
-
offset +=
|
|
1044
|
+
offset += limit
|
|
956
1045
|
|
|
957
1046
|
def get_datastore_fields(self) -> list[dict]:
|
|
958
1047
|
"""Get the field definitions of the resource datastore excluding the
|
|
@@ -971,7 +1060,8 @@ class Resource(HDXObject):
|
|
|
971
1060
|
) -> list[dict]:
|
|
972
1061
|
"""Run a read-only SQL query against the HDX datastore using CKAN
|
|
973
1062
|
datastore_search_sql. Tables are named by resource id, which must be
|
|
974
|
-
double quoted eg. SELECT * FROM "RESOURCE_ID".
|
|
1063
|
+
double quoted eg. SELECT * FROM "RESOURCE_ID". Logs a warning if HDX
|
|
1064
|
+
truncates the results.
|
|
975
1065
|
|
|
976
1066
|
Args:
|
|
977
1067
|
sql: SQL SELECT statement
|
|
@@ -982,7 +1072,12 @@ class Resource(HDXObject):
|
|
|
982
1072
|
"""
|
|
983
1073
|
resource = Resource(configuration=configuration)
|
|
984
1074
|
result = resource._write_to_hdx("datastore_search_sql", {"sql": sql})
|
|
985
|
-
|
|
1075
|
+
records = result["records"]
|
|
1076
|
+
if result.get("records_truncated"):
|
|
1077
|
+
logger.warning(
|
|
1078
|
+
f"SQL query results truncated by HDX to {len(records)} rows!"
|
|
1079
|
+
)
|
|
1080
|
+
return records
|
|
986
1081
|
|
|
987
1082
|
def delete_datastore(self) -> None:
|
|
988
1083
|
"""Delete a resource from the HDX datastore
|
|
@@ -1000,6 +1095,41 @@ class Resource(HDXObject):
|
|
|
1000
1095
|
if not success:
|
|
1001
1096
|
logger.debug(result)
|
|
1002
1097
|
|
|
1098
|
+
def submit_to_datapusher(self, ignore_hash: bool = False) -> bool:
|
|
1099
|
+
"""Submit the resource to DataPusher+ to load its file into the
|
|
1100
|
+
datastore. This does not change last_modified. Unlike the automatic
|
|
1101
|
+
submission on file upload, HDX's datastore allowlist and format checks
|
|
1102
|
+
are not applied.
|
|
1103
|
+
|
|
1104
|
+
Args:
|
|
1105
|
+
ignore_hash: Reload even if the file hash is unchanged. Defaults to False.
|
|
1106
|
+
|
|
1107
|
+
Returns:
|
|
1108
|
+
True if submitted, False if not (eg. a job is already pending)
|
|
1109
|
+
"""
|
|
1110
|
+
return self._write_to_hdx(
|
|
1111
|
+
"datapusher_submit",
|
|
1112
|
+
{"resource_id": self.data["id"], "ignore_hash": ignore_hash},
|
|
1113
|
+
"resource_id",
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
def get_datapusher_status(self) -> dict | None:
|
|
1117
|
+
"""Get the status of the resource's latest DataPusher+ job
|
|
1118
|
+
|
|
1119
|
+
Returns:
|
|
1120
|
+
Status dictionary with keys including status and error or None if no job
|
|
1121
|
+
"""
|
|
1122
|
+
success, result = self._read_from_hdx(
|
|
1123
|
+
"datapusher",
|
|
1124
|
+
self.data["id"],
|
|
1125
|
+
"resource_id",
|
|
1126
|
+
self.actions()["datapusher_status"],
|
|
1127
|
+
)
|
|
1128
|
+
if success:
|
|
1129
|
+
return result
|
|
1130
|
+
logger.debug(result)
|
|
1131
|
+
return None
|
|
1132
|
+
|
|
1003
1133
|
def get_resource_views(self) -> list[ResourceView]:
|
|
1004
1134
|
"""Get any resource views in the resource
|
|
1005
1135
|
|
|
@@ -1159,6 +1289,21 @@ class Resource(HDXObject):
|
|
|
1159
1289
|
else:
|
|
1160
1290
|
logger.debug(result)
|
|
1161
1291
|
|
|
1292
|
+
def get_fs_check_info(self) -> list[dict]:
|
|
1293
|
+
"""Get file structure check information for the resource from its
|
|
1294
|
+
fs_check_info field. Each check event can have keys such as state,
|
|
1295
|
+
timestamp, sheet_changes and hxl_proxy_response.
|
|
1296
|
+
|
|
1297
|
+
Returns:
|
|
1298
|
+
List of file structure check events
|
|
1299
|
+
"""
|
|
1300
|
+
fs_check_info = self.data.get("fs_check_info")
|
|
1301
|
+
if not fs_check_info:
|
|
1302
|
+
return []
|
|
1303
|
+
if isinstance(fs_check_info, str):
|
|
1304
|
+
return json.loads(fs_check_info)
|
|
1305
|
+
return fs_check_info
|
|
1306
|
+
|
|
1162
1307
|
def is_marked_data_updated(self) -> bool:
|
|
1163
1308
|
"""Return if the resource's data is marked to be updated
|
|
1164
1309
|
|
|
@@ -45,6 +45,7 @@ class User(HDXObject):
|
|
|
45
45
|
"list": "user_list",
|
|
46
46
|
"listorgs": "organization_list_for_user",
|
|
47
47
|
"token_list": "api_token_list",
|
|
48
|
+
"token_info": "hdx_token_info",
|
|
48
49
|
"autocomplete": "user_autocomplete",
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -334,7 +335,7 @@ class User(HDXObject):
|
|
|
334
335
|
Returns:
|
|
335
336
|
List of organization dicts in HDX that logged in user is a member of
|
|
336
337
|
"""
|
|
337
|
-
user =
|
|
338
|
+
user = cls(configuration=configuration)
|
|
338
339
|
try:
|
|
339
340
|
return user.configuration.call_remoteckan(
|
|
340
341
|
cls.actions()["listorgs"], {"permission": permission}
|
|
@@ -470,13 +471,25 @@ class User(HDXObject):
|
|
|
470
471
|
Returns:
|
|
471
472
|
List of Activity objects
|
|
472
473
|
"""
|
|
473
|
-
return
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
)
|
|
474
|
+
return self._get_activities("user", all_pages, **kwargs)
|
|
475
|
+
|
|
476
|
+
@classmethod
|
|
477
|
+
def get_current_token_info(cls, configuration: Configuration | None = None) -> dict:
|
|
478
|
+
"""Get information about the API token in the configuration: the md5
|
|
479
|
+
hash of the token owner's email (email_hash) and the token name
|
|
480
|
+
(token_name).
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
Dictionary with keys email_hash and token_name
|
|
487
|
+
"""
|
|
488
|
+
user = cls(configuration=configuration)
|
|
489
|
+
try:
|
|
490
|
+
return user.configuration.call_remoteckan(cls.actions()["token_info"], {})
|
|
491
|
+
except Exception as e:
|
|
492
|
+
raise HDXError("Failed when trying to get API token info!") from e
|
|
480
493
|
|
|
481
494
|
@classmethod
|
|
482
495
|
def autocomplete(
|