diff --git a/CHANGELOG.md b/CHANGELOG.md index e71254ec..d9397d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] - TBD +### Deprecated + +- Item Search methods `get_items()` and `get_item_collections()` have been renamed to + `items()` and `item_collections()`. The original methods are now deprecated + and may be removed as early as v0.5.0. [#206](https://github.com/stac-utils/pystac-client/pull/206) +- Item Search methods `get_all_items()` and `get_all_items_as_dict()` are now deprecated, + and may be removed as early as v0.5.0. + These have been deprecated because they have the potential to perform a large number + of requests to the server and instantiate a large number of objects in memory. + To a user, this is only visible as a large delay in the method call and/or the + exhaustion of all available memory. The iterator methods `items()` or + `item_collections()` should be used instead. [#206](https://github.com/stac-utils/pystac-client/pull/206) + ### Added - lru_cache to several methods [#167](https://github.com/stac-utils/pystac-client/pull/167) @@ -15,9 +28,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Better error message when trying to search a non-item-search-conforming catalog [#164](https://github.com/stac-utils/pystac-client/pull/164) -- Search `filter-lang` defaults to `cql2-json` instead of `cql-json` -- Search `filter-lang` will be set to `cql2-json` if the `filter` is a dict, or `cql2-text` if it is a string -- Search parameter `intersects` is now typed to only accept a str, dict, or object that implements `__geo_interface__` +- Search `filter-lang` defaults to `cql2-json` instead of `cql-json` [#169](https://github.com/stac-utils/pystac-client/pull/169) +- Search `filter-lang` will be set to `cql2-json` if the `filter` is a dict, or `cql2-text` if it is a string [#169](https://github.com/stac-utils/pystac-client/pull/169) +- Search parameter `intersects` is now typed to only accept a str, dict, or object that implements `__geo_interface__` [#169](https://github.com/stac-utils/pystac-client/pull/169) ### Fixed @@ -27,7 +40,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Removed -- Client parameter `require_geojson_link` has been removed. +- Client parameter `require_geojson_link` has been removed. [#169](https://github.com/stac-utils/pystac-client/pull/169) ## [v0.3.5] - 2022-05-26 @@ -122,7 +135,7 @@ are in a single HTTP session, handle pagination and respects conformance - Update to use PySTAC 1.1.0 - IO changed to use PySTAC's new StacIO base class. - `Search.item_collections()` renamed to `Search.get_item_collections()` -- `Search.item_collections()` renamed to `Search.get_items()` +- `Search.item()` renamed to `Search.get_items()` - Conformance is checked by each individual function that requires a particular conformance - STAC API testing URLs changed to updated APIs - `ItemSearch.get_pages()` function moved to StacApiIO class for general use diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8daf209b..f21e6ee0 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -13,7 +13,7 @@ The ``--matched`` switch performs a search with limit=1 so does not get any Items, but gets the total number of matches which will be output to the screen (if supported by the STAC API). -:: +.. code-block:: console $ stac-client search https://earth-search.aws.element84.com/v0 -c sentinel-s2-l2a-cogs --bbox -72.5 40.5 -72 41 --matched 2179 items matched @@ -21,7 +21,7 @@ the screen (if supported by the STAC API). If the same URL is to be used over and over, define an environment variable to be used in the CLI call: -:: +.. code-block:: console $ export STAC_API_URL=https://earth-search.aws.element84.com/v0 $ stac-client search ${STAC_API_URL} -c sentinel-s2-l2a-cogs --bbox -72.5 40.5 -72 41 --datetime 2020-01-01/2020-01-31 --matched @@ -36,7 +36,7 @@ another process such as `geojsonio-cli `__, or `jq `__. -:: +.. code-block:: console $ stac-client search ${STAC_API_URL} -c sentinel-s2-l2a-cogs --bbox -72.5 40.5 -72 41 --datetime 2020-01-01/2020-01-31 | stacterm cal --label platform @@ -46,7 +46,7 @@ another process such as If the ``--save`` switch is provided instead, the results will not be output to stdout, but instead will be saved to the specified file. -:: +.. code-block:: console $ stac-client search ${STAC_API_URL} -c sentinel-s2-l2a-cogs --bbox -72.5 40.5 -72 41 --datetime 2020-01-01/2020-01-31 --save items.json @@ -77,12 +77,12 @@ The query filter will also accept complete JSON as per the specification. Any number of properties can be included, and each can be included more than once to use additional operators. -:: +.. code-block:: console $ stac-client search ${STAC_API_URL} -c sentinel-s2-l2a-cogs --bbox -72.5 40.5 -72 41 --datetime 2020-01-01/2020-01-31 -q "eo:cloud_cover<10" --matched 10 items matched -:: +.. code-block:: console $ stac-client search ${STAC_API_URL} -c sentinel-s2-l2a-cogs --bbox -72.5 40.5 -72 41 --datetime 2020-01-01/2020-01-31 -q "eo:cloud_cover<10" "eo:cloud_cover>5" --matched 4 items matched @@ -91,37 +91,39 @@ Python ~~~~~~ To use the Python library, first a Client instance is created for a -specific STAC API (use the root URL) +specific STAC API (use the root URL): -:: +.. code-block:: python from pystac_client import Client - catalog = Client.open("https://earth-search.aws.element84.com/v0") + catalog_client = Client.open("https://earth-search.aws.element84.com/v0") -Create a search +Create a search: -:: +.. code-block:: python - mysearch = catalog.search(collections=['sentinel-s2-l2a-cogs'], bbox=[-72.5,40.5,-72,41], max_items=10) + my_search = catalog_client.search(collections=['sentinel-s2-l2a-cogs'], bbox=[-72.5,40.5,-72,41], max_items=10) print(f"{mysearch.matched()} items found") -The ``get_items`` function returns an iterator for looping through he -returned items. +The ``items()`` generator method can be used to iterate through all resulting items. -:: +.. code-block:: python - for item in mysearch.get_items(): + for item in my_search.items(): print(item.id) -To get all of Items from a search as a single `PySTAC -ItemCollection `__ -use the ``get_all_items`` function. The ``ItemCollection`` can then be -saved as a GeoJSON FeatureCollection. +To convert all of Items from a search as a single `PySTAC +ItemCollection `__, +you must first do a limited iteration on the iterator to get a list of Items, and then +create an ItemCollection with that. The ``ItemCollection`` can then be saved as a +GeoJSON FeatureCollection. -Save all found items as a single FeatureCollection +Save all found items as a single FeatureCollection: -:: +.. code-block:: python + + from pystac import ItemCollection - items = mysearch.get_all_items() - items.save_object('items.json') + my_itemcollection = ItemCollection(items = list(my_search.items())) + my_itemcollection.save_object('my_itemcollection.json') diff --git a/docs/tutorials/cql2-filter.ipynb b/docs/tutorials/cql2-filter.ipynb index d55f2260..9e5a5f08 100644 --- a/docs/tutorials/cql2-filter.ipynb +++ b/docs/tutorials/cql2-filter.ipynb @@ -5,7 +5,7 @@ "id": "e06a27bf", "metadata": {}, "source": [ - "# pystac-client CQL Filtering\n", + "# pystac-client CQL2 Filtering\n", "\n", "This notebook demonstrates the use of pystac-client to use [CQL2 filtering](https://github.com/radiantearth/stac-api-spec/tree/master/fragments/filter). The server needs to support this and advertise conformance as the `https://api.stacspec.org/v1.0.0-rc.1/item-search#filter` class in the `conformsTo` attribute of the root API.\n", "\n", @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "b65de617", "metadata": {}, "outputs": [], @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "98942e75", "metadata": {}, "outputs": [], @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "d8af6334", "metadata": {}, "outputs": [], @@ -125,9 +125,9 @@ " # limit sets the # of items per page so we can see multiple pages getting fetched\n", " params['filter'] = filt\n", " search = cat.search(**params)\n", - " items_json = search.get_all_items_as_dict()\n", + " items = list(search.items_as_dicts()) # safe b/c we set max_items = 100\n", " # DataFrame\n", - " items_df = pd.DataFrame(items_to_geodataframe(items_json['features']))\n", + " items_df = pd.DataFrame(items_to_geodataframe(items))\n", " print(f\"{len(items_df.index)} items found\")\n", " field = 'properties.eo:cloud_cover'\n", " return items_df.hvplot(y=field, label=json.dumps(filt), frame_height=500, frame_width=800) " @@ -145,67 +145,10 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "dfc0e759", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "33 items found\n" - ] - }, - { - "data": {}, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ], - "text/plain": [ - ":Curve [properties.datetime] (properties.eo:cloud_cover)" - ] - }, - "execution_count": 18, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "2290" - } - }, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "filt = {\n", " \"op\": \"lte\",\n", @@ -217,67 +160,10 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "9c2f9ca1", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "92 items found\n" - ] - }, - { - "data": {}, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ], - "text/plain": [ - ":Curve [properties.datetime] (properties.eo:cloud_cover)" - ] - }, - "execution_count": 19, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "2474" - } - }, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "filt = {\n", " \"op\": \"gte\",\n", @@ -289,67 +175,10 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "109f673c", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "41 items found\n" - ] - }, - { - "data": {}, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.holoviews_exec.v0+json": "", - "text/html": [ - "
\n", - "
\n", - "
\n", - "" - ], - "text/plain": [ - ":Curve [properties.datetime] (properties.eo:cloud_cover)" - ] - }, - "execution_count": 20, - "metadata": { - "application/vnd.holoviews_exec.v0+json": { - "id": "2658" - } - }, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "filt = { \n", " \"op\": \"and\",\n", diff --git a/docs/usage.rst b/docs/usage.rst index 644a5922..79f4f57f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -159,30 +159,27 @@ requests to a service's "search" endpoint. This method returns a Instances of :class:`~pystac_client.ItemSearch` have 2 methods for iterating over results: -* :meth:`ItemSearch.get_item_collections `: - iterator over *pages* of results, +* :meth:`ItemSearch.item_collections + `: iterator over *pages* of results, yielding an :class:`~pystac.ItemCollection` for each page of results. -* :meth:`ItemSearch.get_items `: an iterator over - individual Item objects, yielding a :class:`pystac.Item` instance for Item - that matches the search criteria. +* :meth:`ItemSearch.items `: an iterator over + individual Item objects, yielding a :class:`pystac.Item` instance for all Items + that match the search criteria. +* :meth:`ItemSearch.items_as_dicts `: + iterate over individual results, yielding a :class:`dict` instance representing each + item that matches the search criteria. This eliminates the overhead of creating + class:`pystac.Item` objects -In addition three additional convenience methods are provided: +Additionally, the ``matched`` method can be used to access result metadata about +how many total items matched the query: * :meth:`ItemSearch.matched `: returns the number of hits (items) for this search if the API supports the STAC API Context Extension. Not all APIs support returning a total count, in which case a warning will be issued. -* :meth:`ItemSearch.get_all_items `: Rather - than return an iterator, this function will - fetch all items and return them as a single :class:`~pystac.ItemCollection`. -* :meth:`ItemSearch.get_all_items_as_dict - ` : Like `get_all_items` this fetches - all items but returns them as a GeoJSON FeatureCollection dictionary rather than a - PySTAC object. This can be more efficient if only a dictionary of the results is - needed. .. code-block:: python - >>> for item in results.get_items(): + >>> for item in results.items(): ... print(item.id) S2B_OPER_MSI_L2A_TL_SGS__20190101T200120_A009518_T18TXP_N02.11 MCD43A4.A2019010.h12v04.006.2019022234410 @@ -190,7 +187,7 @@ In addition three additional convenience methods are provided: MYD11A1.A2019002.h12v04.006.2019003174703 MYD11A1.A2019001.h12v04.006.2019002165238 -The :meth:`~pystac_client.ItemSearch.get_items` and related methods handle retrieval of +The :meth:`~pystac_client.ItemSearch.items` and related methods handle retrieval of successive pages of results by finding links with a ``"rel"`` type of ``"next"`` and parsing them to construct the next request. The default @@ -201,6 +198,35 @@ described in the section. See the :mod:`Paging ` docs for details on how to customize this behavior. +Alternatively, the Items can be returned within ItemCollections, where each +ItemCollection is one page of results retrieved from search: + +.. code-block:: python + + >>> for ic in results.item_collections(): + ... for item in ic.items: + ... print(item.id) + S2B_OPER_MSI_L2A_TL_SGS__20190101T200120_A009518_T18TXP_N02.11 + MCD43A4.A2019010.h12v04.006.2019022234410 + MCD43A4.A2019009.h12v04.006.2019022222645 + MYD11A1.A2019002.h12v04.006.2019003174703 + MYD11A1.A2019001.h12v04.006.2019002165238 + +If you do not need the :class:`pystac.Item` instances, you can instead use +:meth:`ItemSearch.items_as_dicts ` +to retrieve dictionary representation of the items, without incurring the cost of +creating the Item objects. + +.. code-block:: python + + >>> for item_dict in results.items_as_dicts(): + ... print(item_dict["id"]) + S2B_OPER_MSI_L2A_TL_SGS__20190101T200120_A009518_T18TXP_N02.11 + MCD43A4.A2019010.h12v04.006.2019022234410 + MCD43A4.A2019009.h12v04.006.2019022222645 + MYD11A1.A2019002.h12v04.006.2019003174703 + MYD11A1.A2019001.h12v04.006.2019002165238 + Query Extension --------------- diff --git a/pystac_client/client.py b/pystac_client/client.py index 3578b4ec..dd1c0dde 100644 --- a/pystac_client/client.py +++ b/pystac_client/client.py @@ -142,7 +142,7 @@ def get_items(self) -> Iterable["Item_Type"]: """ if self._stac_io.conforms_to(ConformanceClasses.ITEM_SEARCH): search = self.search() - yield from search.get_items() + yield from search.items() else: return super().get_items() diff --git a/pystac_client/collection_client.py b/pystac_client/collection_client.py index e217e3d9..cee3cbbe 100644 --- a/pystac_client/collection_client.py +++ b/pystac_client/collection_client.py @@ -30,7 +30,7 @@ def get_items(self) -> Iterable["Item_Type"]: root = self.get_root() if link is not None and root is not None: search = ItemSearch(link.href, method="GET", stac_io=root._stac_io) - yield from search.get_items() + yield from search.items() else: yield from super().get_items() diff --git a/pystac_client/item_search.py b/pystac_client/item_search.py index 286bd79f..f4750534 100644 --- a/pystac_client/item_search.py +++ b/pystac_client/item_search.py @@ -69,7 +69,9 @@ # from https://gist.github.com/angstwad/bf22d1822c38a92ec0a9#gistcomment-2622319 -def dict_merge(dct: Dict, merge_dct: Dict, add_keys: bool = True) -> Dict: +def dict_merge( + dct: Dict[Any, Any], merge_dct: Dict[Any, Any], add_keys: bool = True +) -> Dict[Any, Any]: """Recursive dict merge. Inspired by :meth:``dict.update()``, instead of @@ -191,7 +193,7 @@ class ItemSearch: sortby: A single field or list of fields to sort the response by fields: A list of fields to include in the response. Note this may result in invalid STAC objects, as they may not have required fields. - Use `get_all_items_as_dict` to avoid object unmarshalling errors. + Use `items_as_dicts` to avoid object unmarshalling errors. max_items: The maximum number of items to get, even if there are more matched items. method: The http method, 'GET' or 'POST' @@ -540,7 +542,7 @@ def _format_intersects(value: Optional[IntersectsLike]) -> Optional[Intersects]: ) @lru_cache(1) - def matched(self) -> int: + def matched(self) -> Optional[int]: """Return number matched for search Returns the value from the `numberMatched` or `context.matched` field. @@ -562,11 +564,25 @@ def matched(self) -> int: return found def get_item_collections(self) -> Iterator[ItemCollection]: + """DEPRECATED. Use :meth:`ItemSearch.item_collections` instead. + + Yields: + ItemCollection : a group of Items matching the search criteria within an + ItemCollection + """ + warnings.warn( + "get_item_collections() is deprecated, use item_collections() instead", + DeprecationWarning, + ) + return self.item_collections() + + def item_collections(self) -> Iterator[ItemCollection]: """Iterator that yields ItemCollection objects. Each ItemCollection is a page of results from the search. Yields: - Iterable[Item] : pystac_client.ItemCollection + ItemCollection : a group of Items matching the search criteria within an + ItemCollection """ for page in self._stac_io.get_pages( self.url, self.method, self.get_parameters() @@ -574,31 +590,70 @@ def get_item_collections(self) -> Iterator[ItemCollection]: yield ItemCollection.from_dict(page, preserve_dict=False, root=self.client) def get_items(self) -> Iterator[Item]: + """DEPRECATED. Use :meth:`ItemSearch.items` instead. + + Yields: + Item : each Item matching the search criteria + """ + warnings.warn( + "get_items() is deprecated, use items() instead", + DeprecationWarning, + ) + return self.items() + + def items(self) -> Iterator[Item]: """Iterator that yields :class:`pystac.Item` instances for each item matching the given search parameters. Calls - :meth:`ItemSearch.item_collections()` internally and yields from + :meth:`ItemSearch.item_collections` internally and yields from :attr:`ItemCollection.features ` for each page of results. - Return: - Iterable[Item] : Iterate through resulting Items + Yields: + Item : each Item matching the search criteria """ nitems = 0 - for item_collection in self.get_item_collections(): + for item_collection in self.item_collections(): for item in item_collection: yield item nitems += 1 if self._max_items and nitems >= self._max_items: return + def items_as_dicts(self) -> Iterator[Dict[str, Any]]: + """Iterator that yields :class:`dict` instances for each item matching + the given search parameters. Calls + :meth:`ItemSearch.item_collections` internally and yields from + :attr:`ItemCollection.features ` for + each page of results. + + Yields: + Item : each Item matching the search criteria + """ + nitems = 0 + for page in self._stac_io.get_pages( + self.url, self.method, self.get_parameters() + ): + for item in page.get("features", []): + yield item + nitems += 1 + if self._max_items and nitems >= self._max_items: + return + @lru_cache(1) - def get_all_items_as_dict(self) -> Dict: - """Convenience method that gets all items from all pages, up to self._max_items, - and returns an array of dictionaries + def get_all_items_as_dict(self) -> Dict[str, Any]: + """DEPRECATED. Use :meth:`get_items` or :meth:`get_item_collections` instead. + Convenience method that gets all items from all pages, up to + the number provided by the max_items parameter, and returns an array of + dictionaries. Return: Dict : A GeoJSON FeatureCollection """ + warnings.warn( + "get_all_items_as_dict is deprecated, use get_items or" + " get_item_collections instead", + DeprecationWarning, + ) features = [] for page in self._stac_io.get_pages( self.url, self.method, self.get_parameters() @@ -611,12 +666,18 @@ def get_all_items_as_dict(self) -> Dict: @lru_cache(1) def get_all_items(self) -> ItemCollection: - """Convenience method that builds an :class:`ItemCollection` from all items + """DEPRECATED. Use :meth:`get_items` or :meth:`get_item_collections` instead. + Convenience method that builds an :class:`ItemCollection` from all items matching the given search parameters. Return: item_collection : ItemCollection """ + warnings.warn( + "get_all_items is deprecated, use get_items or " + "get_item_collections instead", + DeprecationWarning, + ) feature_collection = self.get_all_items_as_dict() return ItemCollection.from_dict( feature_collection, preserve_dict=False, root=self.client diff --git a/tests/cassettes/test_item_search/TestItemSearch.test_items_as_dicts.yaml b/tests/cassettes/test_item_search/TestItemSearch.test_items_as_dicts.yaml new file mode 100644 index 00000000..98b5b710 --- /dev/null +++ b/tests/cassettes/test_item_search/TestItemSearch.test_items_as_dicts.yaml @@ -0,0 +1,160 @@ +interactions: +- request: + body: '{"limit": 10, "bbox": [-73.21, 43.99, -73.12, 44.05], "collections": ["naip"]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '78' + Content-Type: + - application/json + User-Agent: + - python-requests/2.27.1 + method: POST + uri: https://planetarycomputer.microsoft.com/api/stac/v1/search + response: + body: + string: !!binary | + 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 + headers: + Content-Encoding: + - gzip + Content-Length: + - '2143' + Content-Type: + - application/geo+json + Date: + - Wed, 01 Jun 2022 19:30:46 GMT + Strict-Transport-Security: + - max-age=15724800; includeSubDomains + Vary: + - Accept-Encoding + X-Azure-Ref: + - 0Zr6XYgAAAABsVlagafu2RqCzig51hgH7TU5aMjIxMDYwNjE0MDI3ADkyN2FiZmE2LTE5ZjYtNGFmMS1hMDlkLWM5NTlkOWExZTY0NA== + X-Cache: + - CONFIG_NOCACHE + status: + code: 200 + message: OK +- request: + body: '{"limit": 10, "bbox": [-73.21, 43.99, -73.12, 44.05], "collections": ["naip"], + "token": "next:vt_m_4407363_sw_18_h_20160804"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '125' + Content-Type: + - application/json + User-Agent: + - python-requests/2.27.1 + method: POST + uri: https://planetarycomputer.microsoft.com/api/stac/v1/search + response: + body: + string: !!binary | + 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 + headers: + Content-Encoding: + - gzip + Content-Length: + - '2222' + Content-Type: + - application/geo+json + Date: + - Wed, 01 Jun 2022 19:30:47 GMT + Strict-Transport-Security: + - max-age=15724800; includeSubDomains + Vary: + - Accept-Encoding + X-Azure-Ref: + - 0Z76XYgAAAACu5+MprhBqQrVcK11NG1t4TU5aMjIxMDYwNjE0MDI3ADkyN2FiZmE2LTE5ZjYtNGFmMS1hMDlkLWM5NTlkOWExZTY0NA== + X-Cache: + - CONFIG_NOCACHE + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_item_search.py b/tests/test_item_search.py index 702d2dc3..f26b5b18 100644 --- a/tests/test_item_search.py +++ b/tests/test_item_search.py @@ -507,7 +507,7 @@ def test_results(self) -> None: max_items=20, limit=10, ) - results = search.get_items() + results = search.items() assert all(isinstance(item, pystac.Item) for item in results) @@ -521,7 +521,7 @@ def test_ids_results(self) -> None: url=SEARCH_URL, ids=ids, ) - results = list(search.get_items()) + results = list(search.items()) assert len(results) == 1 assert all(item.id in ids for item in results) @@ -531,13 +531,13 @@ def test_datetime_results(self) -> None: # Datetime range string datetime_ = "2019-01-01T00:00:01Z/2019-01-01T00:00:10Z" search = ItemSearch(url=SEARCH_URL, datetime=datetime_) - results = list(search.get_items()) + results = list(search.items()) assert len(results) == 33 min_datetime = datetime(2019, 1, 1, 0, 0, 1, tzinfo=tzutc()) max_datetime = datetime(2019, 1, 1, 0, 0, 10, tzinfo=tzutc()) search = ItemSearch(url=SEARCH_URL, datetime=(min_datetime, max_datetime)) - results = search.get_items() + results = search.items() assert all( min_datetime <= item.datetime <= (max_datetime + timedelta(seconds=1)) for item in results @@ -561,7 +561,7 @@ def test_intersects_results(self) -> None: search = ItemSearch( url=SEARCH_URL, intersects=intersects_dict, collections="naip" ) - results = list(search.get_items()) + results = list(search.items()) assert len(results) == 30 # Geo-interface object @@ -574,7 +574,7 @@ def __geo_interface__(self) -> None: search = ItemSearch( url=SEARCH_URL, intersects=intersects_obj, collections="naip" ) - results = search.get_items() + results = search.items() assert all(isinstance(item, pystac.Item) for item in results) @pytest.mark.vcr @@ -588,7 +588,7 @@ def test_result_paging(self) -> None: ) # Check that the current page changes on the ItemSearch instance when a new page is requested - pages = list(search.get_item_collections()) + pages = list(search.item_collections()) assert pages[1] != pages[2] assert pages[1].items != pages[2].items @@ -605,6 +605,17 @@ def test_get_all_items(self) -> None: item_collection = search.get_all_items() assert len(item_collection.items) == 20 + @pytest.mark.vcr + def test_items_as_dicts(self) -> None: + search = ItemSearch( + url=SEARCH_URL, + bbox=(-73.21, 43.99, -73.12, 44.05), + collections="naip", + limit=10, + max_items=20, + ) + assert len(list(search.items_as_dicts())) == 20 + class TestItemSearchQuery: @pytest.mark.vcr @@ -615,7 +626,7 @@ def test_query_shortcut_syntax(self) -> None: query=["gsd=10"], max_items=1, ) - items1 = list(search.get_items()) + items1 = list(search.items()) search = ItemSearch( url=SEARCH_URL, @@ -623,7 +634,7 @@ def test_query_shortcut_syntax(self) -> None: query={"gsd": {"eq": 10}}, max_items=1, ) - items2 = list(search.get_items()) + items2 = list(search.items()) assert len(items1) == 1 assert len(items2) == 1