hdx-python-api 6.7.0__tar.gz → 6.7.1__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.0 → hdx_python_api-6.7.1}/PKG-INFO +3 -3
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/documentation/index.md +65 -5
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/pyproject.toml +2 -2
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/_version.py +2 -2
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/resource.py +180 -1
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_resource.py +164 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/uv.lock +220 -8
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/.github/workflows/publish.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/.github/workflows/run-python-tests.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/.gitignore +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/.pre-commit-config.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/CLAUDE.md +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/LICENSE +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/README.md +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/documentation/.readthedocs.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/documentation/mkdocs.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/configuration.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/hdx_base_configuration.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/locations.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/remotehdx.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/utilities/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/utilities/date_helper.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/utilities/filestore_helper.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/utilities/hdx_error_handler.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/utilities/hdx_state.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/api/utilities/url_utils.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/dataset.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/hdxobject.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/location.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/organization.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/resource_matcher.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/resource_view.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/showcase.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/user.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/data/vocabulary.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/facades/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/facades/infer_arguments.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/facades/keyword_arguments.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/src/hdx/facades/simple.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/Accepted_Tags.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/CKAN/hdx_dataset_static.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/Tag_Mapping.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/Tag_Mapping_ChainRuleError.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/empty.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_base_config.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_base_config.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_config.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_config.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_dataset_static.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_dataset_static.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_datasource_topline.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_datasource_topline.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_email_configuration.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_email_configuration.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_missing_site_config.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_organization_static.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_organization_static.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_resource_static.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_resource_static.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_resource_view_static.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_resource_view_static.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_showcase_static.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_showcase_static.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_user_static.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_user_static.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_vocabulary_static.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/hdx_vocabulary_static.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/project_configuration.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/project_configuration.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/user_agent_config.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/user_agent_config2.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/user_agent_config3.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/config/user_agent_config_wrong.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/dataset_search_results.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/download_gen_resource/conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/download_gen_resource/min_qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/download_gen_resource/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/download_gen_resource/test_data_no_data.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/download_gen_resource/test_data_no_years.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/empty.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/gen_resource/conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/gen_resource/min_qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/gen_resource/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/gen_resource/test_data_no_data.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/gen_resource/test_data_no_years.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/location/location_list_all_fields.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/location/location_show_results.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/organization/organization_list_all_fields.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/organization/organization_show_results.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/qc_from_rows/qc_conflict_data_alg.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/qc_from_rows/qc_conflict_data_alg_one_col.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/resource_formats.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/showcase_all_search_results.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/size_hash/ACLED-All-Africa-File_20170101-to-20170708.xlsx +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/state/analysis_dates.txt +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/state/last_build_date.txt +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/test_data.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/test_data.zip +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/dem_data_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/dem_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/expected_resources_to_update.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/opri_data_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/opri_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/opri_metadata_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/qc_sdg_data_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/sdg_data_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/sdg_indicatorlist_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/sdg_metadata_zwe.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/unesco_dataset.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_dataset_resources/unesco_update_dataset.json +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_logic/update_logic_resources.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/fixtures/update_logic/update_logic_resources_new.yaml +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/test_ckan.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/test_configuration.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/test_locations.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/utilities/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/utilities/test_filestore_helper.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/utilities/test_hdx_error_handler.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/utilities/test_hdx_state.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/api/utilities/test_url_utils.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/conftest.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_dataset_add_hapi_error.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_dataset_core.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_dataset_noncore.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_dataset_resource_generation.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_location.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_organization.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_resource_view.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_showcase.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_update_dataset_resources.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_update_logic.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_user.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/data/test_vocabulary.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/facades/__init__.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/facades/test_infer_arguments.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/facades/test_keyword_arguments.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/tests/hdx/facades/test_simple.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/workingexample/my_code.py +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/workingexample/my_resource.csv +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/workingexample/my_resource.xlsx +0 -0
- {hdx_python_api-6.7.0 → hdx_python_api-6.7.1}/workingexample/run.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hdx-python-api
|
|
3
|
-
Version: 6.7.
|
|
3
|
+
Version: 6.7.1
|
|
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.
|
|
32
|
-
Requires-Dist: hdx-python-utilities>=4.1
|
|
31
|
+
Requires-Dist: hdx-python-country>=4.2.3
|
|
32
|
+
Requires-Dist: hdx-python-utilities>=4.2.1
|
|
33
33
|
Requires-Dist: makefun
|
|
34
34
|
Requires-Dist: requests
|
|
35
35
|
Provides-Extra: docs
|
|
@@ -832,9 +832,13 @@ dictionary. HEADERS is either a row number (rows start counting at 1), or the
|
|
|
832
832
|
actual headers defined as a list of strings. If not set, all rows will be
|
|
833
833
|
treated as containing values:
|
|
834
834
|
|
|
835
|
-
dataset.generate_resource("FOLDER", "FILENAME", ROWS,
|
|
836
|
-
COLUMNS, "FORMAT",
|
|
837
|
-
|
|
835
|
+
dataset.generate_resource("FOLDER", "FILENAME", ROWS, RESOURCE_DATA,
|
|
836
|
+
headers=HEADERS, columns=COLUMNS, format="FORMAT",
|
|
837
|
+
encoding="ENCODING", datecol=DATECOL)
|
|
838
|
+
|
|
839
|
+
To use years or a custom date function instead, replace `datecol=DATECOL` with
|
|
840
|
+
`yearcol=YEARCOL` or `date_function=DATE_FUNCTION`. Supply only one of these
|
|
841
|
+
three arguments, using its keyword.
|
|
838
842
|
|
|
839
843
|
The first 4 parameters are mandatory, the rest are optional. A resource can be generated
|
|
840
844
|
from a given list or tuple or other iterable. The method returns a tuple with a bool
|
|
@@ -868,8 +872,12 @@ and outputs a modified row.
|
|
|
868
872
|
The rest of the arguments are the same as for `generate_resource`.
|
|
869
873
|
|
|
870
874
|
dataset.download_generate_resource(DOWNLOADER, "URL", "FOLDER", "FILENAME",
|
|
871
|
-
RESOURCE_DATA, HEADER_INSERTIONS,
|
|
872
|
-
DATECOL
|
|
875
|
+
RESOURCE_DATA, header_insertions=HEADER_INSERTIONS,
|
|
876
|
+
row_function=ROW_FUNCTION, datecol=DATECOL,
|
|
877
|
+
**KWARGS)
|
|
878
|
+
|
|
879
|
+
As with `generate_resource`, use `yearcol=YEARCOL` or
|
|
880
|
+
`date_function=DATE_FUNCTION` instead of `datecol=DATECOL` when appropriate.
|
|
873
881
|
|
|
874
882
|
## Resource Specific Operations
|
|
875
883
|
|
|
@@ -965,6 +973,58 @@ using the getter:
|
|
|
965
973
|
|
|
966
974
|
data_dictionary = resource.get_hdx_data_dictionary()
|
|
967
975
|
|
|
976
|
+
### Datastore
|
|
977
|
+
|
|
978
|
+
A resource's data can be loaded into the HDX datastore and queried. HDX
|
|
979
|
+
requires an API key for all datastore actions and dumps, including reads of
|
|
980
|
+
public resources. To create, update or delete the datastore for a resource:
|
|
981
|
+
|
|
982
|
+
resource.create_datastore(
|
|
983
|
+
[{"id": "code", "type": "text"}, {"id": "value", "type": "numeric"}],
|
|
984
|
+
primary_key="code",
|
|
985
|
+
)
|
|
986
|
+
resource.update_datastore([{"code": "A", "value": 1.0}]) # upsert by default
|
|
987
|
+
has_datastore = resource.has_datastore()
|
|
988
|
+
resource.delete_datastore()
|
|
989
|
+
|
|
990
|
+
To download the whole datastore (rather than the resource's file) use
|
|
991
|
+
**download_datastore**, which returns the URL and path like **download**.
|
|
992
|
+
`file_format` can be `csv` (default), `tsv`, `json` or `xml`, and `filename`
|
|
993
|
+
defaults to the resource name with that extension. **get_datastore_dump_url**
|
|
994
|
+
returns the URL without downloading:
|
|
995
|
+
|
|
996
|
+
url, path = resource.download_datastore("FOLDER_TO_DOWNLOAD_TO")
|
|
997
|
+
url = resource.get_datastore_dump_url(file_format="json")
|
|
998
|
+
|
|
999
|
+
**search_datastore** wraps CKAN's `datastore_search`, returning the result
|
|
1000
|
+
dictionary (with keys including `records`, `fields` and `total`). It accepts
|
|
1001
|
+
`filters`, `q`, `fields`, `sort`, `distinct`, `limit` (default 100), `offset`
|
|
1002
|
+
and `include_total`, and passes any other keyword arguments to CKAN:
|
|
1003
|
+
|
|
1004
|
+
result = resource.search_datastore(
|
|
1005
|
+
filters={"code": ["A", "B"]}, fields=["code", "value"], sort="value desc"
|
|
1006
|
+
)
|
|
1007
|
+
records = result["records"]
|
|
1008
|
+
|
|
1009
|
+
**get_datastore_records** takes the same query parameters but pages through
|
|
1010
|
+
all matching rows, yielding each record (`page_size` defaults to 10000):
|
|
1011
|
+
|
|
1012
|
+
for record in resource.get_datastore_records(filters={"code": "A"}):
|
|
1013
|
+
...
|
|
1014
|
+
|
|
1015
|
+
**get_datastore_fields** returns the datastore field definitions excluding the
|
|
1016
|
+
internal `_id` field:
|
|
1017
|
+
|
|
1018
|
+
fields = resource.get_datastore_fields()
|
|
1019
|
+
|
|
1020
|
+
**search_datastore_sql** wraps CKAN's `datastore_search_sql`, running a
|
|
1021
|
+
SELECT statement where tables are named by double quoted resource id and
|
|
1022
|
+
returning the list of records:
|
|
1023
|
+
|
|
1024
|
+
records = Resource.search_datastore_sql(
|
|
1025
|
+
'SELECT code, SUM(value) AS total FROM "RESOURCE_ID" GROUP BY code'
|
|
1026
|
+
)
|
|
1027
|
+
|
|
968
1028
|
## Showcase Management
|
|
969
1029
|
|
|
970
1030
|
The **Showcase** class enables you to manage showcases, creating, deleting and updating
|
|
@@ -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.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (6, 7, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
-
from collections.abc import Sequence
|
|
5
|
+
from collections.abc import Iterator, Sequence
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, Optional
|
|
@@ -34,6 +34,7 @@ class Resource(HDXObject):
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
_formats_dict = None
|
|
37
|
+
_valid_datastore_dump_formats = frozenset({"csv", "tsv", "json", "xml"})
|
|
37
38
|
_valid_data_dictionary_types = frozenset(
|
|
38
39
|
{"text", "numeric", "date", "timestamp without time zone"}
|
|
39
40
|
)
|
|
@@ -70,6 +71,7 @@ class Resource(HDXObject):
|
|
|
70
71
|
"datastore_insert": "datastore_insert",
|
|
71
72
|
"datastore_upsert": "datastore_upsert",
|
|
72
73
|
"datastore_search": "datastore_search",
|
|
74
|
+
"datastore_search_sql": "datastore_search_sql",
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
def update_from_yaml(
|
|
@@ -711,6 +713,62 @@ class Resource(HDXObject):
|
|
|
711
713
|
path = downloader.download_file(url, folder=folder, filename=filename)
|
|
712
714
|
return url, path
|
|
713
715
|
|
|
716
|
+
def get_datastore_dump_url(self, file_format: str = "csv") -> str:
|
|
717
|
+
"""Get the URL from which the resource datastore can be downloaded in
|
|
718
|
+
full.
|
|
719
|
+
|
|
720
|
+
Args:
|
|
721
|
+
file_format: Dump format (csv, tsv, json or xml). Defaults to csv.
|
|
722
|
+
|
|
723
|
+
Returns:
|
|
724
|
+
Datastore dump URL
|
|
725
|
+
"""
|
|
726
|
+
file_format = file_format.lower()
|
|
727
|
+
if file_format not in self._valid_datastore_dump_formats:
|
|
728
|
+
valid_formats = ", ".join(sorted(self._valid_datastore_dump_formats))
|
|
729
|
+
raise HDXError(
|
|
730
|
+
f"Invalid datastore dump format '{file_format}'! Must be one of: {valid_formats}"
|
|
731
|
+
)
|
|
732
|
+
url = (
|
|
733
|
+
f"{self.configuration.get_hdx_site_url()}/datastore/dump/{self.data['id']}"
|
|
734
|
+
)
|
|
735
|
+
if file_format != "csv":
|
|
736
|
+
url = f"{url}?format={file_format}"
|
|
737
|
+
return url
|
|
738
|
+
|
|
739
|
+
def download_datastore(
|
|
740
|
+
self,
|
|
741
|
+
folder: Path | str | None = None,
|
|
742
|
+
filename: str | None = None,
|
|
743
|
+
file_format: str = "csv",
|
|
744
|
+
retriever: Retrieve | None = None,
|
|
745
|
+
) -> tuple[str, Path]:
|
|
746
|
+
"""Download the resource datastore in full to provided folder or
|
|
747
|
+
temporary folder if no folder supplied
|
|
748
|
+
|
|
749
|
+
Args:
|
|
750
|
+
folder: Folder to download datastore to. Defaults to None.
|
|
751
|
+
filename: Filename to use. Defaults to None (resource name with file_format extension).
|
|
752
|
+
file_format: Dump format (csv, tsv, json or xml). Defaults to csv.
|
|
753
|
+
retriever: Retrieve object to use. Defaults to None.
|
|
754
|
+
|
|
755
|
+
Returns:
|
|
756
|
+
(URL downloaded, Path to downloaded file)
|
|
757
|
+
"""
|
|
758
|
+
url = self.get_datastore_dump_url(file_format)
|
|
759
|
+
logger.debug(f"Downloading {url}")
|
|
760
|
+
if filename is None:
|
|
761
|
+
filename = self.data["name"]
|
|
762
|
+
extension = f".{file_format.lower()}"
|
|
763
|
+
if not filename.endswith(extension):
|
|
764
|
+
filename = f"{filename}{extension}"
|
|
765
|
+
session = hdx.api.utilities.url_utils.get_ckan_ready_session(self.configuration)
|
|
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
|
|
771
|
+
|
|
714
772
|
@staticmethod
|
|
715
773
|
def get_all_resource_ids_in_datastore(
|
|
716
774
|
configuration: Configuration | None = None,
|
|
@@ -805,6 +863,127 @@ class Resource(HDXObject):
|
|
|
805
863
|
}
|
|
806
864
|
self._write_to_hdx("datastore_upsert", data, "resource_id")
|
|
807
865
|
|
|
866
|
+
def search_datastore(
|
|
867
|
+
self,
|
|
868
|
+
filters: dict | None = None,
|
|
869
|
+
q: str | dict | None = None,
|
|
870
|
+
fields: Sequence[str] | None = None,
|
|
871
|
+
sort: str | None = None,
|
|
872
|
+
distinct: bool = False,
|
|
873
|
+
limit: int = 100,
|
|
874
|
+
offset: int = 0,
|
|
875
|
+
include_total: bool = True,
|
|
876
|
+
**kwargs: Any,
|
|
877
|
+
) -> dict:
|
|
878
|
+
"""Search the resource datastore using CKAN datastore_search.
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
filters: Dictionary of field name to value(s) to match. Defaults to None.
|
|
882
|
+
q: Full text query string or dictionary of field name to query. Defaults to None.
|
|
883
|
+
fields: Fields to return. Defaults to None (all fields).
|
|
884
|
+
sort: Comma separated field names with optional asc/desc eg. "code desc". Defaults to None.
|
|
885
|
+
distinct: Whether to return only distinct rows. Defaults to False.
|
|
886
|
+
limit: Maximum number of rows to return. Defaults to 100.
|
|
887
|
+
offset: Number of rows to skip. Defaults to 0.
|
|
888
|
+
include_total: Whether to include total number of matching rows. Defaults to True.
|
|
889
|
+
**kwargs: Other parameters to pass to datastore_search eg. plain, language.
|
|
890
|
+
|
|
891
|
+
Returns:
|
|
892
|
+
datastore_search result dictionary with keys including records, fields and total
|
|
893
|
+
"""
|
|
894
|
+
data: dict = {
|
|
895
|
+
"resource_id": self.data["id"],
|
|
896
|
+
"limit": limit,
|
|
897
|
+
"offset": offset,
|
|
898
|
+
"include_total": include_total,
|
|
899
|
+
}
|
|
900
|
+
if filters is not None:
|
|
901
|
+
data["filters"] = filters
|
|
902
|
+
if q is not None:
|
|
903
|
+
data["q"] = q
|
|
904
|
+
if fields is not None:
|
|
905
|
+
data["fields"] = ",".join(fields)
|
|
906
|
+
if sort is not None:
|
|
907
|
+
data["sort"] = sort
|
|
908
|
+
if distinct:
|
|
909
|
+
data["distinct"] = True
|
|
910
|
+
data.update(kwargs)
|
|
911
|
+
return self._write_to_hdx("datastore_search", data, "resource_id")
|
|
912
|
+
|
|
913
|
+
def get_datastore_records(
|
|
914
|
+
self,
|
|
915
|
+
filters: dict | None = None,
|
|
916
|
+
q: str | dict | None = None,
|
|
917
|
+
fields: Sequence[str] | None = None,
|
|
918
|
+
sort: str | None = None,
|
|
919
|
+
distinct: bool = False,
|
|
920
|
+
page_size: int = 10000,
|
|
921
|
+
**kwargs: Any,
|
|
922
|
+
) -> Iterator[dict]:
|
|
923
|
+
"""Iterate over all matching records in the resource datastore, paging
|
|
924
|
+
through datastore_search results.
|
|
925
|
+
|
|
926
|
+
Args:
|
|
927
|
+
filters: Dictionary of field name to value(s) to match. Defaults to None.
|
|
928
|
+
q: Full text query string or dictionary of field name to query. Defaults to None.
|
|
929
|
+
fields: Fields to return. Defaults to None (all fields).
|
|
930
|
+
sort: Comma separated field names with optional asc/desc eg. "code desc". Defaults to None.
|
|
931
|
+
distinct: Whether to return only distinct rows. Defaults to False.
|
|
932
|
+
page_size: Number of rows to request per call. Defaults to 10000.
|
|
933
|
+
**kwargs: Other parameters to pass to datastore_search eg. plain, language.
|
|
934
|
+
|
|
935
|
+
Returns:
|
|
936
|
+
Iterator of record dictionaries
|
|
937
|
+
"""
|
|
938
|
+
offset = 0
|
|
939
|
+
while True:
|
|
940
|
+
result = self.search_datastore(
|
|
941
|
+
filters=filters,
|
|
942
|
+
q=q,
|
|
943
|
+
fields=fields,
|
|
944
|
+
sort=sort,
|
|
945
|
+
distinct=distinct,
|
|
946
|
+
limit=page_size,
|
|
947
|
+
offset=offset,
|
|
948
|
+
include_total=False,
|
|
949
|
+
**kwargs,
|
|
950
|
+
)
|
|
951
|
+
records = result["records"]
|
|
952
|
+
yield from records
|
|
953
|
+
if len(records) < page_size:
|
|
954
|
+
return
|
|
955
|
+
offset += page_size
|
|
956
|
+
|
|
957
|
+
def get_datastore_fields(self) -> list[dict]:
|
|
958
|
+
"""Get the field definitions of the resource datastore excluding the
|
|
959
|
+
internal _id field.
|
|
960
|
+
|
|
961
|
+
Returns:
|
|
962
|
+
List of field dictionaries each with id and type keys
|
|
963
|
+
"""
|
|
964
|
+
result = self.search_datastore(limit=0, include_total=False)
|
|
965
|
+
return [field for field in result["fields"] if field["id"] != "_id"]
|
|
966
|
+
|
|
967
|
+
@staticmethod
|
|
968
|
+
def search_datastore_sql(
|
|
969
|
+
sql: str,
|
|
970
|
+
configuration: Configuration | None = None,
|
|
971
|
+
) -> list[dict]:
|
|
972
|
+
"""Run a read-only SQL query against the HDX datastore using CKAN
|
|
973
|
+
datastore_search_sql. Tables are named by resource id, which must be
|
|
974
|
+
double quoted eg. SELECT * FROM "RESOURCE_ID".
|
|
975
|
+
|
|
976
|
+
Args:
|
|
977
|
+
sql: SQL SELECT statement
|
|
978
|
+
configuration: HDX configuration. Defaults to global configuration.
|
|
979
|
+
|
|
980
|
+
Returns:
|
|
981
|
+
List of record dictionaries
|
|
982
|
+
"""
|
|
983
|
+
resource = Resource(configuration=configuration)
|
|
984
|
+
result = resource._write_to_hdx("datastore_search_sql", {"sql": sql})
|
|
985
|
+
return result["records"]
|
|
986
|
+
|
|
808
987
|
def delete_datastore(self) -> None:
|
|
809
988
|
"""Delete a resource from the HDX datastore
|
|
810
989
|
|
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from os import remove
|
|
8
8
|
from os.path import basename
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
import pytest
|
|
11
12
|
from hdx.utilities.dateparse import parse_date
|
|
@@ -338,6 +339,7 @@ def mockresourceview(url, decodedata):
|
|
|
338
339
|
|
|
339
340
|
class TestResource:
|
|
340
341
|
datastore = None
|
|
342
|
+
datastore_request = None
|
|
341
343
|
|
|
342
344
|
@pytest.fixture(scope="class")
|
|
343
345
|
def static_yaml(self, configfolder):
|
|
@@ -600,6 +602,73 @@ class TestResource:
|
|
|
600
602
|
|
|
601
603
|
Configuration.read().remoteckan().session = MockSession()
|
|
602
604
|
|
|
605
|
+
@pytest.fixture(scope="function")
|
|
606
|
+
def post_datastore_query(self):
|
|
607
|
+
all_records = [
|
|
608
|
+
{"_id": 1, "code": "A", "value": 1.0},
|
|
609
|
+
{"_id": 2, "code": "B", "value": 2.0},
|
|
610
|
+
{"_id": 3, "code": "C", "value": 3.0},
|
|
611
|
+
]
|
|
612
|
+
fields = [
|
|
613
|
+
{"id": "_id", "type": "int"},
|
|
614
|
+
{"id": "code", "type": "text"},
|
|
615
|
+
{"id": "value", "type": "numeric"},
|
|
616
|
+
]
|
|
617
|
+
|
|
618
|
+
class MockSession:
|
|
619
|
+
@staticmethod
|
|
620
|
+
def post(url, data, **kwargs):
|
|
621
|
+
datadict = json.loads(data.decode("utf-8"))
|
|
622
|
+
if url.endswith("datastore_search_sql"):
|
|
623
|
+
if "error" in datadict["sql"]:
|
|
624
|
+
return MockResponse(
|
|
625
|
+
409,
|
|
626
|
+
'{"success": false, "error": {"message": "Bad SQL", "__type": "Validation Error"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_search_sql"}',
|
|
627
|
+
)
|
|
628
|
+
result = {"records": all_records[:2], "fields": fields}
|
|
629
|
+
return MockResponse(
|
|
630
|
+
200,
|
|
631
|
+
json.dumps(
|
|
632
|
+
{
|
|
633
|
+
"success": True,
|
|
634
|
+
"result": result,
|
|
635
|
+
"help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_search_sql",
|
|
636
|
+
}
|
|
637
|
+
),
|
|
638
|
+
)
|
|
639
|
+
if url.endswith("datastore_search"):
|
|
640
|
+
if datadict["resource_id"] != "datastore_query_resource":
|
|
641
|
+
return MockResponse(
|
|
642
|
+
404,
|
|
643
|
+
'{"success": false, "error": {"message": "Not found", "__type": "Not Found Error"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_search"}',
|
|
644
|
+
)
|
|
645
|
+
TestResource.datastore_request = datadict
|
|
646
|
+
offset = datadict["offset"]
|
|
647
|
+
limit = datadict["limit"]
|
|
648
|
+
result = {
|
|
649
|
+
"resource_id": datadict["resource_id"],
|
|
650
|
+
"fields": fields,
|
|
651
|
+
"records": all_records[offset : offset + limit],
|
|
652
|
+
}
|
|
653
|
+
if datadict["include_total"]:
|
|
654
|
+
result["total"] = len(all_records)
|
|
655
|
+
return MockResponse(
|
|
656
|
+
200,
|
|
657
|
+
json.dumps(
|
|
658
|
+
{
|
|
659
|
+
"success": True,
|
|
660
|
+
"result": result,
|
|
661
|
+
"help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_search",
|
|
662
|
+
}
|
|
663
|
+
),
|
|
664
|
+
)
|
|
665
|
+
return MockResponse(
|
|
666
|
+
404,
|
|
667
|
+
'{"success": false, "error": {"message": "TEST ERROR: Not datastore search", "__type": "TEST ERROR: Not Datastore Search Error"}, "help": "http://test-data.humdata.org/api/3/action/help_show?name=datastore_search"}',
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
Configuration.read().remoteckan().session = MockSession()
|
|
671
|
+
|
|
603
672
|
@pytest.fixture(scope="function")
|
|
604
673
|
def post_patch(self):
|
|
605
674
|
class MockSession:
|
|
@@ -1031,6 +1100,39 @@ class TestResource:
|
|
|
1031
1100
|
with pytest.raises(DownloadError):
|
|
1032
1101
|
resource2.download()
|
|
1033
1102
|
|
|
1103
|
+
def test_download_datastore(self, configuration, read, monkeypatch):
|
|
1104
|
+
downloads = []
|
|
1105
|
+
|
|
1106
|
+
def download_file(self, url, folder=None, filename=None, path=None, **kwargs):
|
|
1107
|
+
if path is None:
|
|
1108
|
+
path = Path(folder or "tmp") / filename
|
|
1109
|
+
downloads.append(url)
|
|
1110
|
+
return path
|
|
1111
|
+
|
|
1112
|
+
monkeypatch.setattr(Download, "download_file", download_file)
|
|
1113
|
+
resource = Resource.read_from_hdx("74b74ae1-df0c-4716-829f-4f939a046811")
|
|
1114
|
+
site_url = resource.configuration.get_hdx_site_url()
|
|
1115
|
+
dump_url = f"{site_url}/datastore/dump/{resource['id']}"
|
|
1116
|
+
assert resource.get_datastore_dump_url() == dump_url
|
|
1117
|
+
assert resource.get_datastore_dump_url("JSON") == f"{dump_url}?format=json"
|
|
1118
|
+
with pytest.raises(HDXError):
|
|
1119
|
+
resource.get_datastore_dump_url("xlsx")
|
|
1120
|
+
|
|
1121
|
+
url, path = resource.download_datastore("folder")
|
|
1122
|
+
assert url == dump_url
|
|
1123
|
+
assert path == Path("folder", "MyResource1.csv")
|
|
1124
|
+
url, path = resource.download_datastore(
|
|
1125
|
+
filename="builds.tsv", file_format="tsv"
|
|
1126
|
+
)
|
|
1127
|
+
assert url == f"{dump_url}?format=tsv"
|
|
1128
|
+
assert path == Path("tmp", "builds.tsv")
|
|
1129
|
+
tempdir = get_temp_dir()
|
|
1130
|
+
retriever = Retrieve(Download(user_agent="test"), tempdir, tempdir, tempdir)
|
|
1131
|
+
url, path = resource.download_datastore(retriever=retriever)
|
|
1132
|
+
assert url == dump_url
|
|
1133
|
+
assert path.name == "MyResource1.csv"
|
|
1134
|
+
assert len(downloads) == 3
|
|
1135
|
+
|
|
1034
1136
|
def test_datastore(self, configuration, post_datastore, topline_yaml, topline_json):
|
|
1035
1137
|
resource_ids = Resource.get_all_resource_ids_in_datastore()
|
|
1036
1138
|
assert resource_ids == [
|
|
@@ -1073,6 +1175,68 @@ class TestResource:
|
|
|
1073
1175
|
resource.update_datastore(records, method="insert")
|
|
1074
1176
|
assert TestResource.datastore == "upsert"
|
|
1075
1177
|
|
|
1178
|
+
def test_datastore_queries(self, configuration, post_datastore_query):
|
|
1179
|
+
resource = Resource({"id": "datastore_query_resource"})
|
|
1180
|
+
result = resource.search_datastore()
|
|
1181
|
+
assert result["total"] == 3
|
|
1182
|
+
assert len(result["records"]) == 3
|
|
1183
|
+
assert TestResource.datastore_request == {
|
|
1184
|
+
"resource_id": "datastore_query_resource",
|
|
1185
|
+
"limit": 100,
|
|
1186
|
+
"offset": 0,
|
|
1187
|
+
"include_total": True,
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
resource.search_datastore(
|
|
1191
|
+
filters={"code": ["A", "B"]},
|
|
1192
|
+
q="A",
|
|
1193
|
+
fields=["code", "value"],
|
|
1194
|
+
sort="value desc",
|
|
1195
|
+
distinct=True,
|
|
1196
|
+
limit=2,
|
|
1197
|
+
offset=1,
|
|
1198
|
+
plain=False,
|
|
1199
|
+
)
|
|
1200
|
+
assert TestResource.datastore_request == {
|
|
1201
|
+
"resource_id": "datastore_query_resource",
|
|
1202
|
+
"limit": 2,
|
|
1203
|
+
"offset": 1,
|
|
1204
|
+
"include_total": True,
|
|
1205
|
+
"filters": {"code": ["A", "B"]},
|
|
1206
|
+
"q": "A",
|
|
1207
|
+
"fields": "code,value",
|
|
1208
|
+
"sort": "value desc",
|
|
1209
|
+
"distinct": True,
|
|
1210
|
+
"plain": False,
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
records = list(resource.get_datastore_records(sort="code", page_size=2))
|
|
1214
|
+
assert [record["code"] for record in records] == ["A", "B", "C"]
|
|
1215
|
+
assert TestResource.datastore_request["offset"] == 2
|
|
1216
|
+
assert TestResource.datastore_request["sort"] == "code"
|
|
1217
|
+
assert TestResource.datastore_request["include_total"] is False
|
|
1218
|
+
|
|
1219
|
+
records = list(resource.get_datastore_records(page_size=3))
|
|
1220
|
+
assert len(records) == 3
|
|
1221
|
+
assert TestResource.datastore_request["offset"] == 3
|
|
1222
|
+
|
|
1223
|
+
assert resource.get_datastore_fields() == [
|
|
1224
|
+
{"id": "code", "type": "text"},
|
|
1225
|
+
{"id": "value", "type": "numeric"},
|
|
1226
|
+
]
|
|
1227
|
+
assert TestResource.datastore_request["limit"] == 0
|
|
1228
|
+
|
|
1229
|
+
records = Resource.search_datastore_sql(
|
|
1230
|
+
'SELECT * FROM "datastore_query_resource" LIMIT 2'
|
|
1231
|
+
)
|
|
1232
|
+
assert [record["code"] for record in records] == ["A", "B"]
|
|
1233
|
+
with pytest.raises(HDXError):
|
|
1234
|
+
Resource.search_datastore_sql("error")
|
|
1235
|
+
|
|
1236
|
+
resource = Resource({"id": "datastore_unknown_resource"})
|
|
1237
|
+
with pytest.raises(HDXError):
|
|
1238
|
+
resource.search_datastore()
|
|
1239
|
+
|
|
1076
1240
|
def test_resource_views(self, configuration, post_resourceview):
|
|
1077
1241
|
resource = Resource({"id": "25982d1c-f45a-45e1-b14e-87d367413045"})
|
|
1078
1242
|
with pytest.raises(HDXError):
|