Skip to content

Commit 25a2743

Browse files
authored
feat: enable retries by default, add docs (#532)
1 parent a641174 commit 25a2743

File tree

3 files changed

+116
-45
lines changed

3 files changed

+116
-45
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1515
- Support for fetching and merging a selection of queryables [#511](https://github.com/stac-utils/pystac-client/pull/511)
1616
- Better error messages for the CLI [#531](https://github.com/stac-utils/pystac-client/pull/531)
1717
- `Modifiable` to our public API [#534](https://github.com/stac-utils/pystac-client/pull/534)
18+
- `max_retries` parameter to `StacApiIO` [#532](https://github.com/stac-utils/pystac-client/pull/532)
1819

1920
### Changed
2021

docs/usage.rst

Lines changed: 108 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,114 @@ there are no ``"conformsTo"`` uris set at all. But they can be explicitly set:
117117
Note, updating ``"conformsTo"`` does not change what the server supports, it just
118118
changes PySTAC client's understanding of what the server supports.
119119

120+
Configuring retry behavior
121+
--------------------------
122+
123+
By default, **pystac-client** will retry requests that fail DNS lookup or have timeouts.
124+
If you'd like to configure this behavior, e.g. to retry on some ``50x`` responses, you can configure the StacApiIO's session:
125+
126+
.. code-block:: python
127+
128+
from requests.adapters import HTTPAdapter
129+
from urllib3 import Retry
130+
131+
from pystac_client import Client
132+
from pystac_client.stac_api_io import StacApiIO
133+
134+
retry = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504])
135+
stac_api_io = StacApiIO()
136+
stac_api_io.session.mount("https://", HTTPAdapter(max_retries=retry))
137+
client = Client.open(
138+
"https://planetarycomputer.microsoft.com/api/stac/v1", stac_io=stac_api_io
139+
)
140+
141+
Automatically modifying results
142+
-------------------------------
143+
144+
Some systems, like the `Microsoft Planetary Computer <http://planetarycomputer.microsoft.com/>`__,
145+
have public STAC metadata but require some `authentication <https://planetarycomputer.microsoft.com/docs/concepts/sas/>`__
146+
to access the actual assets.
147+
148+
``pystac-client`` provides a ``modifier`` keyword that can automatically
149+
modify the STAC objects returned by the STAC API.
150+
151+
.. code-block:: python
152+
153+
>>> from pystac_client import Client
154+
>>> import planetary_computer, requests
155+
>>> catalog = Client.open(
156+
... 'https://planetarycomputer.microsoft.com/api/stac/v1',
157+
... modifier=planetary_computer.sign_inplace,
158+
... )
159+
>>> item = next(catalog.get_collection("sentinel-2-l2a").get_all_items())
160+
>>> requests.head(item.assets["B02"].href).status_code
161+
200
162+
163+
Without the modifier, we would have received a 404 error because the asset
164+
is in a private storage container.
165+
166+
``pystac-client`` expects that the ``modifier`` callable modifies the result
167+
object in-place and returns no result. A warning is emitted if your
168+
``modifier`` returns a non-None result that is not the same object as the
169+
input.
170+
171+
Here's an example of creating your own modifier.
172+
Because :py:class:`~pystac_client.Modifiable` is a union, the modifier function must handle a few different types of input objects, and care must be taken to ensure that you are modifying the input object (rather than a copy).
173+
Simplifying this interface is a space for future improvement.
174+
175+
.. code-block:: python
176+
177+
import urllib.parse
178+
179+
import pystac
180+
181+
from pystac_client import Client, Modifiable
182+
183+
184+
def modifier(modifiable: Modifiable) -> None:
185+
if isinstance(modifiable, dict):
186+
if modifiable["type"] == "FeatureCollection":
187+
new_features = list()
188+
for item_dict in modifiable["features"]:
189+
modifier(item_dict)
190+
new_features.append(item_dict)
191+
modifiable["features"] = new_features
192+
else:
193+
stac_object = pystac.read_dict(modifiable)
194+
modifier(stac_object)
195+
modifiable.update(stac_object.to_dict())
196+
else:
197+
for key, asset in modifiable.assets.items():
198+
url = urllib.parse.urlparse(asset.href)
199+
if not url.query:
200+
asset.href = urllib.parse.urlunparse(url._replace(query="foo=bar"))
201+
modifiable.assets[key] = asset
202+
203+
204+
client = Client.open(
205+
"https://planetarycomputer.microsoft.com/api/stac/v1", modifier=modifier
206+
)
207+
item_search = client.search(collections=["landsat-c2-l2"], max_items=1)
208+
item = next(item_search.items())
209+
asset = item.assets["red"]
210+
assert urllib.parse.urlparse(asset.href).query == "foo=bar"
211+
212+
213+
Using custom certificates
214+
-------------------------
215+
216+
If you need to use custom certificates in your ``pystac-client`` requests, you can
217+
customize the :class:`StacApiIO<pystac_client.stac_api_io.StacApiIO>` instance before
218+
creating your :class:`Client<pystac_client.Client>`.
219+
220+
.. code-block:: python
221+
222+
>>> from pystac_client.stac_api_io import StacApiIO
223+
>>> from pystac_client.client import Client
224+
>>> stac_api_io = StacApiIO()
225+
>>> stac_api_io.session.verify = "/path/to/certfile"
226+
>>> client = Client.open("https://planetarycomputer.microsoft.com/api/stac/v1", stac_io=stac_api_io)
227+
120228
CollectionClient
121229
++++++++++++++++
122230

@@ -307,51 +415,6 @@ descending sort and a ``+`` prefix or no prefix means an ascending sort.
307415
]
308416
... )
309417
310-
Automatically modifying results
311-
-------------------------------
312-
313-
Some systems, like the `Microsoft Planetary Computer <http://planetarycomputer.microsoft.com/>`__,
314-
have public STAC metadata but require some `authentication <https://planetarycomputer.microsoft.com/docs/concepts/sas/>`__
315-
to access the actual assets.
316-
317-
``pystac-client`` provides a ``modifier`` keyword that can automatically
318-
modify the STAC objects returned by the STAC API.
319-
320-
.. code-block:: python
321-
322-
>>> from pystac_client import Client
323-
>>> import planetary_computer, requests
324-
>>> catalog = Client.open(
325-
... 'https://planetarycomputer.microsoft.com/api/stac/v1',
326-
... modifier=planetary_computer.sign_inplace,
327-
... )
328-
>>> item = next(catalog.get_collection("sentinel-2-l2a").get_all_items())
329-
>>> requests.head(item.assets["B02"].href).status_code
330-
200
331-
332-
Without the modifier, we would have received a 404 error because the asset
333-
is in a private storage container.
334-
335-
``pystac-client`` expects that the ``modifier`` callable modifies the result
336-
object in-place and returns no result. A warning is emitted if your
337-
``modifier`` returns a non-None result that is not the same object as the
338-
input.
339-
340-
Using custom certificates
341-
-------------------------
342-
343-
If you need to use custom certificates in your ``pystac-client`` requests, you can
344-
customize the :class:`StacApiIO<pystac_client.stac_api_io.StacApiIO>` instance before
345-
creating your :class:`Client<pystac_client.Client>`.
346-
347-
.. code-block:: python
348-
349-
>>> from pystac_client.stac_api_io import StacApiIO
350-
>>> from pystac_client.client import Client
351-
>>> stac_api_io = StacApiIO()
352-
>>> stac_api_io.session.verify = "/path/to/certfile"
353-
>>> client = Client.open("https://planetarycomputer.microsoft.com/api/stac/v1", stac_io=stac_api_io)
354-
355418
Loading data
356419
++++++++++++
357420

pystac_client/stac_api_io.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
)
2626
from pystac.stac_io import DefaultStacIO
2727
from requests import Request, Session
28+
from requests.adapters import HTTPAdapter
2829
from typing_extensions import TypeAlias
2930

3031
import pystac_client
@@ -49,6 +50,7 @@ def __init__(
4950
parameters: Optional[Dict[str, Any]] = None,
5051
request_modifier: Optional[Callable[[Request], Union[Request, None]]] = None,
5152
timeout: Timeout = None,
53+
max_retries: Optional[int] = 5,
5254
):
5355
"""Initialize class for API IO
5456
@@ -69,6 +71,8 @@ def __init__(
6971
timeout: Optional float or (float, float) tuple following the semantics
7072
defined by `Requests
7173
<https://requests.readthedocs.io/en/latest/api/#main-interface>`__.
74+
max_retries: The number of times to retry requests. Set to ``None`` to
75+
disable retries.
7276
7377
Return:
7478
StacApiIO : StacApiIO instance
@@ -87,6 +91,9 @@ def __init__(
8791
)
8892

8993
self.session = Session()
94+
if max_retries:
95+
self.session.mount("http://", HTTPAdapter(max_retries=max_retries))
96+
self.session.mount("https://", HTTPAdapter(max_retries=max_retries))
9097
self.timeout = timeout
9198
self.update(
9299
headers=headers, parameters=parameters, request_modifier=request_modifier

0 commit comments

Comments
 (0)