diff --git a/planet/clients/features.py b/planet/clients/features.py index 4aa3483b..ec5742a2 100644 --- a/planet/clients/features.py +++ b/planet/clients/features.py @@ -114,6 +114,11 @@ async def list_items( self, collection_id: str, limit: int = 10, + bbox: Optional[list[float]] = None, + datetime: Optional[str] = None, + id: Optional[str] = None, + hashid: Optional[str] = None, + sort: Optional[str] = None, ) -> AsyncIterator[Feature]: """ List features in `collection_id`. @@ -124,6 +129,19 @@ async def list_items( APIs. Within the Python SDK, the entire Feature can generally be passed to functions that accept a geometry. + Parameters: + collection_id: ID of the collection to list features from. + limit: Maximum number of features to return. 0 means no limit. + bbox: Only return features intersecting this bounding box, given + as [xmin, ymin, xmax, ymax]. + datetime: Only return features matching this datetime. Accepts a + single RFC 3339 datetime or an interval, e.g. + "2024-01-01T00:00:00Z/2024-12-31T23:59:59Z". + id: Only return features whose id matches this value. Prefix the + value with "~" for substring (fuzzy) matching. + hashid: Only return the feature with this hashid. + sort: Sort order for the returned features, e.g. "id" or "-id". + example: ``` @@ -154,7 +172,21 @@ def _next_link(self, page): url = f'{self._base_url}/collections/{collection_id}/items' - resp = await self._session.request(method='GET', url=url) + params: dict[str, Any] = {} + if bbox is not None: + params['bbox'] = ','.join(str(coord) for coord in bbox) + if datetime is not None: + params['datetime'] = datetime + if id is not None: + params['id'] = id + if hashid is not None: + params['hashid'] = hashid + if sort is not None: + params['sort'] = sort + + resp = await self._session.request(method='GET', + url=url, + params=params) async for feat in _FeaturesPager(resp, self._session.request, limit=limit): diff --git a/planet/sync/features.py b/planet/sync/features.py index c4cbee69..c48424fa 100644 --- a/planet/sync/features.py +++ b/planet/sync/features.py @@ -97,7 +97,12 @@ def delete_collection(self, collection_id: str) -> None: def list_items(self, collection_id: str, - limit: int = 0) -> Iterator[Feature]: + limit: int = 0, + bbox: Optional[list[float]] = None, + datetime: Optional[str] = None, + id: Optional[str] = None, + hashid: Optional[str] = None, + sort: Optional[str] = None) -> Iterator[Feature]: """ List features in `collection_id`. @@ -122,7 +127,13 @@ def list_items(self, ``` """ return self._client._aiter_to_iter( - self._client.list_items(collection_id, limit=limit)) + self._client.list_items(collection_id, + limit=limit, + bbox=bbox, + datetime=datetime, + id=id, + hashid=hashid, + sort=sort)) def get_item(self, collection_id: str, feature_id: str) -> Feature: """ diff --git a/tests/integration/test_features_api.py b/tests/integration/test_features_api.py index 4e620282..9c59aea5 100644 --- a/tests/integration/test_features_api.py +++ b/tests/integration/test_features_api.py @@ -228,6 +228,38 @@ def assertf(resp): assertf(list(cl_sync.list_items(collection_id))) +@respx.mock +async def test_list_items_with_filters(): + collection_id = "test" + items_url = f"{TEST_URL}/collections/{collection_id}/items" + + mock_response(items_url, + list_features_response(collection_id, num_features=1)) + + filters = dict( + bbox=[1.0, 2.0, 3.0, 4.0], + datetime="2024-01-01T00:00:00Z/2024-12-31T23:59:59Z", + id="~abc", + hashid="deadbeef", + sort="-id", + ) + + # async client forwards every filter as a query parameter + [feat async for feat in cl_async.list_items(collection_id, **filters)] + sent = respx.calls[-1].request.url.params + assert sent["bbox"] == "1.0,2.0,3.0,4.0" + assert sent["datetime"] == filters["datetime"] + assert sent["id"] == "~abc" + assert sent["hashid"] == "deadbeef" + assert sent["sort"] == "-id" + + # sync wrapper forwards them too + list(cl_sync.list_items(collection_id, **filters)) + sent_sync = respx.calls[-1].request.url.params + assert sent_sync["bbox"] == "1.0,2.0,3.0,4.0" + assert sent_sync["sort"] == "-id" + + @respx.mock @pytest.mark.parametrize( "feature, expected_body",