Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new ItemSearch result consumers #284

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed

- Fix type annotation of `Client._stac_io` and avoid implicit re-exports in `pystac_client.__init__.py` [#249](https://github.com/stac-utils/pystac-client/pull/249)
- Added `ItemSearch.item_collection()` as a replacement for the deprecated `ItemSearch.get_all_items()` [#237](https://github.com/stac-utils/pystac-client/issues/237)
- Added `ItemSearch.pages`, `ItemSearch.pages_as_dicts`, `ItemSearch.item_collection`, and `ItemSearch.item_collection_as_dict`
as replacements for various deprecated methods [#237](https://github.com/stac-utils/pystac-client/issues/237)

## [v0.4.0] - 2022-06-08

Expand Down
32 changes: 18 additions & 14 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,19 +156,23 @@ requests to a service's "search" endpoint. This method returns a
... datetime=['2019-01-01T00:00:00Z', '2019-01-02T00:00:00Z'],
... )

Instances of :class:`~pystac_client.ItemSearch` have 2 methods for iterating
over results:

* :meth:`ItemSearch.item_collections
<pystac_client.ItemSearch.item_collections>`: iterator over *pages* of results,
yielding an :class:`~pystac.ItemCollection` for each page of results.
* :meth:`ItemSearch.items <pystac_client.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 <pystac_client.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
Instances of :class:`~pystac_client.ItemSearch` have a handful of methods for
getting matching items into Python objects. The right method to use depends on
how many of the matches you want to consume (a single item at a time, a
page at a time, or everything) and whether you want plain Python dictionaries
representing the items, or proper ``pystac`` objects.

The following table shows the :class:`~pystac_client.ItemSearch` methods for fetching
matches, according to which set of matches to return and whether to return them as
``pystac`` objects or plain dictionaries.

================= ================================================= =========================================================
Matches to return PySTAC objects Plain dictionaries
================= ================================================= =========================================================
**Single items** :meth:`~pystac_client.ItemSearch.items` :meth:`~pystac_client.ItemSearch.items_as_dicts`
**Pages** :meth:`~pystac_client.ItemSearch.pages` :meth:`~pystac_client.ItemSearch.pages_as_dicts`
**Everything** :meth:`~pystac_client.ItemSearch.item_collection` :meth:`~pystac_client.ItemSearch.item_collection_as_dict`
================= ================================================= =========================================================

Additionally, the ``matched`` method can be used to access result metadata about
how many total items matched the query:
Expand Down Expand Up @@ -203,7 +207,7 @@ ItemCollection is one page of results retrieved from search:

.. code-block:: python

>>> for ic in results.item_collections():
>>> for ic in results.pages():
... for item in ic.items:
... print(item.id)
S2B_OPER_MSI_L2A_TL_SGS__20190101T200120_A009518_T18TXP_N02.11
Expand Down
2 changes: 1 addition & 1 deletion pystac_client/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def search(
if matched:
print(f"{result.matched()} items matched")
else:
feature_collection = result.get_all_items_as_dict()
feature_collection = result.item_collection_as_dict()
if save:
with open(save, "w") as f:
f.write(json.dumps(feature_collection))
Expand Down
194 changes: 119 additions & 75 deletions pystac_client/item_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,71 +590,24 @@ def matched(self) -> Optional[int]:
warnings.warn("numberMatched or context.matched not in response")
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:
ItemCollection : a group of Items matching the search criteria within an
ItemCollection
"""
if isinstance(self._stac_io, StacApiIO):
for page in self._stac_io.get_pages(
self.url, self.method, self.get_parameters()
):
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()
# ------------------------------------------------------------------------
# Result sets
# ------------------------------------------------------------------------
# By item

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
:attr:`ItemCollection.features <pystac_client.ItemCollection.features>` for
each page of results.
the given search parameters.

Yields:
Item : each Item matching the search criteria
"""
nitems = 0
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
for item in self.items_as_dicts():
yield Item.from_dict(item, root=self.client, preserve_dict=False)

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 <pystac_client.ItemCollection.features>` for
each page of results.
the given search parameters.

Yields:
Item : each Item matching the search criteria
Expand All @@ -669,22 +622,68 @@ def items_as_dicts(self) -> Iterator[Dict[str, Any]]:
if self._max_items and nitems >= self._max_items:
return

# ------------------------------------------------------------------------
# By Page
def pages(self) -> Iterator[ItemCollection]:
"""Iterator that yields ItemCollection objects. Each ItemCollection is
a page of results from the search.

Yields:
ItemCollection : a group of Items matching the search criteria within an
ItemCollection
"""
if isinstance(self._stac_io, StacApiIO):
for page in self.pages_as_dicts():
yield ItemCollection.from_dict(
page, preserve_dict=False, root=self.client
)

def pages_as_dicts(self) -> Iterator[Dict[str, Any]]:
"""Iterator that yields :class:`dict` instances for each page
of results from the search.

Yields:
dict : a group of items matching the search
criteria as a feature-collection-like dictionary.
"""
if isinstance(self._stac_io, StacApiIO):
for page in self._stac_io.get_pages(
self.url, self.method, self.get_parameters()
):
yield page

# ------------------------------------------------------------------------
# Everything

@lru_cache(1)
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.
def item_collection(self) -> ItemCollection:
"""
Get the matching items as a :ref:`pystac.ItemCollection`.

Return:
Dict : A GeoJSON FeatureCollection
item_collection: ItemCollection
"""
warnings.warn(
"get_all_items_as_dict is deprecated, use get_items or"
" get_item_collections instead",
DeprecationWarning,
stacklevel=2,
# Bypass the cache here, so that we can pass __preserve_dict__
# without mutating what's in the cache.
feature_collection = self.item_collection_as_dict.__wrapped__(self)
return ItemCollection.from_dict(
feature_collection, preserve_dict=False, root=self.client
)

@lru_cache(1)
def item_collection_as_dict(self) -> Dict[str, Any]:
"""
Get the matching items as an item-collection-like dict.

The dictionary will have two keys:

1. ``'type'`` with the value ``'FeatureCollection'``
2. ``'features'`` with the value being a list of dictionaries
for the matching items.

Return:
item_collection : dict
"""
features = []
for page in self._stac_io.get_pages(
self.url, self.method, self.get_parameters()
Expand All @@ -695,7 +694,48 @@ def get_all_items_as_dict(self) -> Dict[str, Any]:
return {"type": "FeatureCollection", "features": features}
return {"type": "FeatureCollection", "features": features}

@lru_cache(1)
# Deprecated methods
# not caching these, since they're cached in the implementation

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 pages() instead",
DeprecationWarning,
)
return self.pages()

def item_collections(self) -> Iterator[ItemCollection]:
"""Iterator that yields ItemCollection objects. Each ItemCollection is
a page of results from the search.

Yields:
ItemCollection : a group of Items matching the search criteria within an
ItemCollection
"""
warnings.warn(
"'item_collections()' is deprecated, use 'pages()' instead",
DeprecationWarning,
)
return self.pages()

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 get_all_items(self) -> ItemCollection:
"""
Get the matching items as a :ref:`pystac.ItemCollection`.
Expand All @@ -715,15 +755,19 @@ def get_all_items(self) -> ItemCollection:
feature_collection, preserve_dict=False, root=self.client
)

@lru_cache(1)
def item_collection(self) -> ItemCollection:
"""
Get the matching items as a :ref:`pystac.ItemCollection`.
def get_all_items_as_dict(self) -> Dict[str, Any]:
"""Get items as a FeatureCollection dictionary.

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:
item_collection: ItemCollection
Dict : A GeoJSON FeatureCollection
"""
feature_collection = self.get_all_items_as_dict()
return ItemCollection.from_dict(
feature_collection, preserve_dict=False, root=self.client
warnings.warn(
"'get_all_items_as_dict' is deprecated, use 'item_collection_as_dict' "
"instead.",
DeprecationWarning,
)
return self.item_collection_as_dict()
2 changes: 1 addition & 1 deletion scripts/test
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
./scripts/lint
./scripts/format
# Test suite with coverage enabled
pytest -s --block-network --cov pystac_client --cov-report term-missing
pytest -Werror -s --block-network --cov pystac_client --cov-report term-missing
coverage xml
fi
fi
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ profile=black

[doc8]
ignore-path=docs/_build,docs/tutorials
max-line-length=88
max-line-length=130

[flake8]
max-line-length = 88
Expand Down
Loading