hdx-python-api 6.7.0__py3-none-any.whl → 6.7.1__py3-none-any.whl

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/api/_version.py CHANGED
@@ -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.0'
22
- __version_tuple__ = version_tuple = (6, 7, 0)
21
+ __version__ = version = '6.7.1'
22
+ __version_tuple__ = version_tuple = (6, 7, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
hdx/data/resource.py CHANGED
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hdx-python-api
3
- Version: 6.7.0
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.1.3
32
- Requires-Dist: hdx-python-utilities>=4.1.2
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
@@ -1,5 +1,5 @@
1
1
  hdx/api/__init__.py,sha256=11ozJKiUsqDCZ3_mcAHhGYUyGK_Unl54djVSBBExFB4,59
2
- hdx/api/_version.py,sha256=ySRWjy-IYGDZdNB2wR2G-LZ8vwxqmQqgeGjL_1f7wkc,520
2
+ hdx/api/_version.py,sha256=7uXJAam46P0GVJ-CiBxd6tVnUUkt-epJdnvQ6m2fpb4,520
3
3
  hdx/api/configuration.py,sha256=9WvuVI-3th2Dv70_t_4VoyrlJwB_9qUSN5_zUKbp4sQ,23805
4
4
  hdx/api/hdx_base_configuration.yaml,sha256=jj2t5b7NnI499rHVwOjj3Q5BAjE8i7NDGVKnWZ5P5-4,2222
5
5
  hdx/api/locations.py,sha256=bFl_cKz_AEQzkE_xWOkrRlUQ0GT2LMPSk-G_LsbV-Ns,4099
@@ -15,7 +15,7 @@ hdx/data/dataset.py,sha256=0gS7tQSswTgG0g8YeXg0FL8pFVkocBQSRPFpQAurN00,99455
15
15
  hdx/data/hdxobject.py,sha256=_jlwYjb_CcjaXsTXObknMfq-OiNOcpJfLJ9b1vKV1Zo,27494
16
16
  hdx/data/location.py,sha256=WIQRdY1boy6xPweIWRczWNiJLFrp4w0DVUwppHeNccU,8765
17
17
  hdx/data/organization.py,sha256=HGopFsCYOWrR7sUtZzZSXq6wLFw4djus2gzg6yNXOqc,12296
18
- hdx/data/resource.py,sha256=SBAEou9Fj5sgpfoDmQ6j33W0srMMmhlsj00hiTuW86Y,38097
18
+ hdx/data/resource.py,sha256=iVARfA3_8gGsGjXgNxomm7umAadvxzFsGGqxTpBB3WQ,45196
19
19
  hdx/data/resource_matcher.py,sha256=Yzk9pWa4zk1p09m6zcZMj0XZf5Y16Pa3KToOdUt7rP8,4247
20
20
  hdx/data/resource_view.py,sha256=GcYkh0iDAe6_m2ErY_A6i3Z7ZFX949eGt1TLnLZv_8s,7346
21
21
  hdx/data/showcase.py,sha256=vnRZ45xd9Ct65eFQkWZtMIJPFifZfKOrH4obO5D5Pvc,14062
@@ -25,7 +25,7 @@ hdx/facades/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  hdx/facades/infer_arguments.py,sha256=DvyDuozfOPmoaV7HjhQySojGIrMWW9nos31auQBVVl8,4032
26
26
  hdx/facades/keyword_arguments.py,sha256=aVapHCfqBO6H7xxBgTdK9ISdgrBFsNypPDWihfQ3edU,1125
27
27
  hdx/facades/simple.py,sha256=QWsBkaHG_UM6X3Zu2u3LWVOQzeR24q-EUClrfsL_mkM,1064
28
- hdx_python_api-6.7.0.dist-info/METADATA,sha256=RYuk0RvwKnxNcRzipF3cYH0_Vk-16d0XRt7lbt8x6yo,4946
29
- hdx_python_api-6.7.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
30
- hdx_python_api-6.7.0.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
31
- hdx_python_api-6.7.0.dist-info/RECORD,,
28
+ hdx_python_api-6.7.1.dist-info/METADATA,sha256=7SaqX2zZKJ828e94hbPjvjJ_3q5obaL0NU9Ghut_86A,4946
29
+ hdx_python_api-6.7.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
30
+ hdx_python_api-6.7.1.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
31
+ hdx_python_api-6.7.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any