From c946bfe041582f0ab091588ce2fac782d1a5671f Mon Sep 17 00:00:00 2001 From: wietzesuijker Date: Sat, 27 Aug 2022 17:07:51 +0200 Subject: [PATCH 1/6] add ItemSearch.url_with_parameters --- CHANGELOG.md | 2 ++ pystac_client/item_search.py | 9 +++++++++ tests/test_item_search.py | 9 +++++++++ 3 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40edc369..437c53a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +- Added add ItemSearch.url_with_parameters to get a formatted search url` [#259](https://github.com/stac-utils/pystac-client/issues/299) + ### Changed ### Fixed diff --git a/pystac_client/item_search.py b/pystac_client/item_search.py index 7c7e40cd..549ce832 100644 --- a/pystac_client/item_search.py +++ b/pystac_client/item_search.py @@ -328,6 +328,15 @@ def get_parameters(self) -> Dict[str, Any]: else: raise Exception(f"Unsupported method {self.method}") + def url_with_parameters(self) -> str: + params_list = [ + f'{k}={",".join(map(str, v))}' + if isinstance(v, List) or isinstance(v, Tuple) + else f'{k}={v}' + for k, v in self._parameters.items() + ] + return f'{self.url}{"&".join(params_list)}' + def _format_query(self, value: Optional[QueryLike]) -> Optional[Dict[str, Any]]: if value is None: return None diff --git a/tests/test_item_search.py b/tests/test_item_search.py index fad84482..e59d0390 100644 --- a/tests/test_item_search.py +++ b/tests/test_item_search.py @@ -90,6 +90,15 @@ def bboxer() -> Iterator[float]: search = ItemSearch(url=SEARCH_URL, bbox=bboxer()) assert search.get_parameters()["bbox"] == (-104.5, 44.0, -104.0, 45.0) + def test_url_with_parameters(self) -> None: + # Single timestamp input + search = ItemSearch( + url=SEARCH_URL, + datetime="2020-02-01T00:00:00Z", + bbox=[-104.5, 44.0, -104.0, 45.0], + ) + assert "bbox=-104.5,44.0,-104.0,45.0" in search.url_with_parameters() + def test_single_string_datetime(self) -> None: # Single timestamp input search = ItemSearch(url=SEARCH_URL, datetime="2020-02-01T00:00:00Z") From a1a22c057426f3cd2d7f710e533fe11334e2aa14 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 30 Aug 2022 12:49:00 -0600 Subject: [PATCH 2/6] fix: types, formatting for url_with_parameters --- pystac_client/item_search.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pystac_client/item_search.py b/pystac_client/item_search.py index 549ce832..3b8c2c74 100644 --- a/pystac_client/item_search.py +++ b/pystac_client/item_search.py @@ -330,13 +330,11 @@ def get_parameters(self) -> Dict[str, Any]: def url_with_parameters(self) -> str: params_list = [ - f'{k}={",".join(map(str, v))}' - if isinstance(v, List) or isinstance(v, Tuple) - else f'{k}={v}' + f'{k}={",".join(map(str, v))}' if isinstance(v, Iterable) else f"{k}={v}" for k, v in self._parameters.items() ] return f'{self.url}{"&".join(params_list)}' - + def _format_query(self, value: Optional[QueryLike]) -> Optional[Dict[str, Any]]: if value is None: return None From 421589d0d9119dd5c7dd17542afe2ddb89fad7ad Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 30 Aug 2022 12:54:55 -0600 Subject: [PATCH 3/6] fix, docs: fix CHANGELOG entry for 304 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 437c53a4..99876547 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Added add ItemSearch.url_with_parameters to get a formatted search url` [#259](https://github.com/stac-utils/pystac-client/issues/299) +- Added `ItemSearch.url_with_parameters` to get a formatted search url [#304](https://github.com/stac-utils/pystac-client/issues/304) ### Changed From fa7171e04fb6b8336261c36193c30a59e68a8344 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 30 Aug 2022 14:36:29 -0600 Subject: [PATCH 4/6] feat: add motivating example test, and fix impl --- pystac_client/item_search.py | 2 +- tests/test_item_search.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pystac_client/item_search.py b/pystac_client/item_search.py index 3b8c2c74..6e9ae6f2 100644 --- a/pystac_client/item_search.py +++ b/pystac_client/item_search.py @@ -333,7 +333,7 @@ def url_with_parameters(self) -> str: f'{k}={",".join(map(str, v))}' if isinstance(v, Iterable) else f"{k}={v}" for k, v in self._parameters.items() ] - return f'{self.url}{"&".join(params_list)}' + return f'{self.url}?{"&".join(params_list)}' def _format_query(self, value: Optional[QueryLike]) -> Optional[Dict[str, Any]]: if value is None: diff --git a/tests/test_item_search.py b/tests/test_item_search.py index e59d0390..fa8038d7 100644 --- a/tests/test_item_search.py +++ b/tests/test_item_search.py @@ -99,6 +99,18 @@ def test_url_with_parameters(self) -> None: ) assert "bbox=-104.5,44.0,-104.0,45.0" in search.url_with_parameters() + # Motivating example: https://github.com/stac-utils/pystac-client/issues/299 + search = ItemSearch( + url="https://planetarycomputer.microsoft.com/api/stac/v1/search", + collections=["cop-dem-glo-30"], + bbox=[88.214, 27.927, 88.302, 28.034], + ) + assert ( + search.url_with_parameters() + == "https://planetarycomputer.microsoft.com/api/stac/v1/search?" + "limit=100&bbox=88.214,27.927,88.302,28.034&collections=cop-dem-glo-30" + ) + def test_single_string_datetime(self) -> None: # Single timestamp input search = ItemSearch(url=SEARCH_URL, datetime="2020-02-01T00:00:00Z") From e61d471fab05b16d7f149cfca754cd487ed74d0c Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 30 Aug 2022 15:13:26 -0600 Subject: [PATCH 5/6] refactor: use request for url construction --- pystac_client/item_search.py | 44 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/pystac_client/item_search.py b/pystac_client/item_search.py index 6e9ae6f2..dc3a6518 100644 --- a/pystac_client/item_search.py +++ b/pystac_client/item_search.py @@ -1,5 +1,6 @@ import json import re +import urllib.parse import warnings from collections.abc import Iterable, Mapping from copy import deepcopy @@ -22,6 +23,7 @@ from dateutil.relativedelta import relativedelta from dateutil.tz import tzutc from pystac import Collection, Item, ItemCollection +from requests import Request from pystac_client._utils import Modifiable, call_modifier from pystac_client.conformance import ConformanceClasses @@ -311,29 +313,33 @@ def get_parameters(self) -> Dict[str, Any]: if self.method == "POST": return self._parameters elif self.method == "GET": - params = deepcopy(self._parameters) - if "bbox" in params: - params["bbox"] = ",".join(map(str, params["bbox"])) - if "ids" in params: - params["ids"] = ",".join(params["ids"]) - if "collections" in params: - params["collections"] = ",".join(params["collections"]) - if "intersects" in params: - params["intersects"] = json.dumps(params["intersects"]) - if "sortby" in params: - params["sortby"] = self._sortby_dict_to_str(params["sortby"]) - if "fields" in params: - params["fields"] = self._fields_dict_to_str(params["fields"]) - return params + return self._clean_params_for_get_request() else: raise Exception(f"Unsupported method {self.method}") + def _clean_params_for_get_request(self) -> Dict[str, Any]: + params = deepcopy(self._parameters) + if "bbox" in params: + params["bbox"] = ",".join(map(str, params["bbox"])) + if "ids" in params: + params["ids"] = ",".join(params["ids"]) + if "collections" in params: + params["collections"] = ",".join(params["collections"]) + if "intersects" in params: + params["intersects"] = json.dumps(params["intersects"]) + if "sortby" in params: + params["sortby"] = self._sortby_dict_to_str(params["sortby"]) + if "fields" in params: + params["fields"] = self._fields_dict_to_str(params["fields"]) + return params + def url_with_parameters(self) -> str: - params_list = [ - f'{k}={",".join(map(str, v))}' if isinstance(v, Iterable) else f"{k}={v}" - for k, v in self._parameters.items() - ] - return f'{self.url}?{"&".join(params_list)}' + params = self._clean_params_for_get_request() + request = Request("GET", self.url, params=params) + url = request.prepare().url + if url is None: + raise ValueError("Could not construct a full url") + return urllib.parse.unquote(url) def _format_query(self, value: Optional[QueryLike]) -> Optional[Dict[str, Any]]: if value is None: From 8d2acc2382dcba8e36a28ea662e4938f27bda0e0 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 1 Sep 2022 10:36:20 -0600 Subject: [PATCH 6/6] docs: add docstring for url_with_parameters --- pystac_client/item_search.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pystac_client/item_search.py b/pystac_client/item_search.py index dc3a6518..4c14370a 100644 --- a/pystac_client/item_search.py +++ b/pystac_client/item_search.py @@ -334,6 +334,24 @@ def _clean_params_for_get_request(self) -> Dict[str, Any]: return params def url_with_parameters(self) -> str: + """Returns this item search url with parameters, appropriate for a GET request. + + Examples: + + >>> search = ItemSearch( + ... url="https://planetarycomputer.microsoft.com/api/stac/v1/search", + ... collections=["cop-dem-glo-30"], + ... bbox=[88.214, 27.927, 88.302, 28.034], + ... ) + >>> assert ( + ... search.url_with_parameters() + ... == "https://planetarycomputer.microsoft.com/api/stac/v1/search?" + ... "limit=100&bbox=88.214,27.927,88.302,28.034&collections=cop-dem-glo-30" + ... ) + + Returns: + str: The search url with parameters. + """ params = self._clean_params_for_get_request() request = Request("GET", self.url, params=params) url = request.prepare().url