Skip to content

Commit 82b7cfe

Browse files
stellamzrStellagadomski
authored
docs: update docstrings and readme pystac-client (#84)
## What I'm changing - replace request with httpx information in docstring - update docstrings - add readme with examples resolves #63 ## Checklist - [x] Tests pass: `uv run pytest` - [x] Checks pass: `uv run pre-commit --all-files` --------- Co-authored-by: Stella <[email protected]> Co-authored-by: Pete Gadomski <[email protected]>
1 parent b9d7951 commit 82b7cfe

File tree

9 files changed

+169
-89
lines changed

9 files changed

+169
-89
lines changed

docs/pystapi-client/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# API
2+
3+
::: pystapi-client

docs/pystapi-client/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# pystapi-client

docs/stapi-client/index.md

Lines changed: 0 additions & 13 deletions
This file was deleted.

docs/stapi-pydantic/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
!!! note
66

77
This repository intentionally has no input/output (IO) functionality.
8-
For making requests to a STAPI API, use **[pystapi-client](../stapi-client/index.md)**.
8+
For making requests to a STAPI API, use **[pystapi-client](../pystapi-client/index.md)**.

mkdocs.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ theme:
1111

1212
nav:
1313
- index.md
14+
- pystapi-client:
15+
- pystapi-client/index.md
16+
- pystapi-client/api.md
1417
- stapi-pydantic:
1518
- stapi-pydantic/index.md
1619
- stapi-pydantic/api.md
17-
- stapi-client:
18-
- stapi-client/index.md
1920

2021
plugins:
2122
- mkdocstrings:

pystapi-client/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,40 @@ A Python client for working with [STAPI](https://stapi-spec.github.io/pystapi/)
1212
Install from PyPi.
1313
Other than [stapi-pydantic](https://stapi-spec.github.io/pystapi/stapi-pydantic/) itself, the only dependencies for **pystapi-client** are the Python [httpx](https://www.python-httpx.org/) and [python-dateutil](https://dateutil.readthedocs.io) libraries.
1414

15+
```shell
16+
python -m pip install pystapi-client
17+
```
18+
1519
## Development
1620

1721
See the instructions in the [pystapi monorepo](https://github.com/stapi-spec/pystapi?tab=readme-ov-file#development).
1822

1923
## Documentation
2024

2125
See the [documentation page](https://stapi-spec.github.io/pystapi/stapi-client/) for the latest docs.
26+
27+
## Usage Example
28+
29+
The `pystapi_client.Client` class is the main interface for working with services that conform to the STAPI API spec.
30+
31+
```python
32+
from pystapi_client import Client
33+
34+
# Initialize client
35+
client = Client.open("https://api.example.com/stapi")
36+
37+
# List all products
38+
products = list(client.get_products())
39+
40+
# Get specific product
41+
product = client.get_product("test-spotlight")
42+
43+
# List all Opportunities for a Product
44+
opportunities = client.get_product_opportunities("test-spotlight")
45+
46+
# List orders
47+
orders = client.get_orders()
48+
49+
# Get specific order
50+
order = client.get_order("test-order")
51+
```

pystapi-client/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ description = "Python library for searching Satellite Tasking API (STAPI) APIs."
55
readme = "README.md"
66
authors = [
77
{ name = "Kaveh Karimi-Asli", email = "[email protected]" },
8-
{ name = "Philip Weiss", email = "[email protected]" }
8+
{ name = "Philip Weiss", email = "[email protected]" },
9+
{ name = "Stella Reinhardt", email = "[email protected]"}
910
]
1011
maintainers = [{ name = "Pete Gadomski", email = "[email protected]" }]
1112
keywords = ["stapi"]

pystapi-client/src/pystapi_client/client.py

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -67,31 +67,33 @@ def open(
6767
request_modifier: Callable[[Request], Request] | None = None,
6868
timeout: TimeoutTypes | None = None,
6969
) -> "Client":
70-
"""Opens a STAPI API client
70+
"""Opens a STAPI API client.
7171
7272
Args:
73-
url : The URL of a STAPI API.
74-
headers : A dictionary of additional headers to use in all requests
75-
made to any part of this STAPI API.
73+
url: The URL of a STAPI API
74+
headers: Optional dictionary of additional headers to use in all requests
75+
made to any part of this STAPI API
7676
parameters: Optional dictionary of query string parameters to
77-
include in all requests.
78-
request_modifier: A callable that either modifies a `Request` instance or
77+
include in all requests
78+
request_modifier: Optional callable that modifies a Request instance or
7979
returns a new one. This can be useful for injecting Authentication
8080
headers and/or signing fully-formed requests (e.g. signing requests
8181
using AWS SigV4).
82-
8382
The callable should expect a single argument, which will be an instance
84-
of :class:`requests.Request`.
85-
86-
If the callable returns a `requests.Request`, that will be used.
83+
of :class:`httpx.Request`.
84+
If the callable returns a `httpx.Request`, that will be used.
8785
Alternately, the callable may simply modify the provided request object
88-
and return `None`.
89-
timeout: Optional float or (float, float) tuple following the semantics
90-
defined by `Requests
91-
<https://requests.readthedocs.io/en/latest/api/#main-interface>`__.
86+
and return `None`
87+
timeout: Optional timeout configuration. Can be:
88+
- None to disable timeouts
89+
- float for a default timeout
90+
- tuple of (connect, read, write, pool) timeouts, each being float or None
91+
- httpx.Timeout instance for fine-grained control
92+
See `httpx timeouts <https://www.python-httpx.org/advanced/timeouts/>`__
93+
for details
9294
93-
Return:
94-
client : A :class:`Client` instance for this STAPI API
95+
Returns:
96+
Client: A :class:`Client` instance for this STAPI API
9597
"""
9698
stapi_io = StapiIO(
9799
root_url=AnyUrl(url),
@@ -118,11 +120,11 @@ def get_single_link(
118120
"""Get a single :class:`~stapi_pydantic.Link` instance associated with this object.
119121
120122
Args:
121-
rel : If set, filter links such that only those
122-
matching this relationship are returned.
123-
media_type: If set, filter the links such that only
124-
those matching media_type are returned. media_type can
125-
be a single value or a list of values.
123+
rel: Optional relationship filter. If set, only links matching this
124+
relationship are considered.
125+
media_type: Optional media type filter. If set, only links matching
126+
this media type are considered. Can be a single value or an
127+
iterable of values.
126128
127129
Returns:
128130
:class:`~stapi_pydantic.Link` | None: First link that matches ``rel``
@@ -161,6 +163,15 @@ def read_links(self) -> None:
161163
]
162164

163165
def read_conformance(self) -> None:
166+
"""Read the API conformance from the root of the STAPI API.
167+
168+
The conformance is stored in `Client.conforms_to`. This method attempts to read
169+
from "/conformance" endpoint first, then falls back to the root endpoint "/".
170+
171+
Note:
172+
This method silently continues if endpoints return APIError, no exceptions
173+
are raised.
174+
"""
164175
conformance: list[str] = []
165176
for endpoint in ["/conformance", "/"]:
166177
try:
@@ -173,22 +184,26 @@ def read_conformance(self) -> None:
173184
self.set_conforms_to(conformance)
174185

175186
def has_conforms_to(self) -> bool:
176-
"""Whether server contains list of ``"conformsTo"`` URIs"""
187+
"""Whether server contains list of ``"conformsTo"`` URIs
188+
189+
Return:
190+
Whether the server contains list of ``"conformsTo"`` URIs
191+
"""
177192
return bool(self.conforms_to)
178193

179194
def get_conforms_to(self) -> list[str]:
180195
"""List of ``"conformsTo"`` URIs
181196
182197
Return:
183-
list[str]: List of URIs that the server conforms to
198+
List of URIs that the server conforms to
184199
"""
185200
return self.conforms_to.copy()
186201

187202
def set_conforms_to(self, conformance_uris: list[str]) -> None:
188203
"""Set list of ``"conformsTo"`` URIs
189204
190205
Args:
191-
conformance_uris : URIs indicating what the server conforms to
206+
conformance_uris: URIs indicating what the server conforms to
192207
"""
193208
self.conforms_to = conformance_uris
194209

@@ -204,7 +219,7 @@ def add_conforms_to(self, name: str) -> None:
204219
"""Add ``"conformsTo"`` by name.
205220
206221
Args:
207-
name : name of :py:class:`ConformanceClasses` keys to add.
222+
name: Name of :py:class:`ConformanceClasses` keys to add.
208223
"""
209224
conformance_class = ConformanceClasses.get_by_name(name)
210225

@@ -215,7 +230,7 @@ def remove_conforms_to(self, name: str) -> None:
215230
"""Remove ``"conformsTo"`` by name.
216231
217232
Args:
218-
name : name of :py:class:`ConformanceClasses` keys to remove.
233+
name: Name of :py:class:`ConformanceClasses` keys to remove.
219234
"""
220235
conformance_class = ConformanceClasses.get_by_name(name)
221236

@@ -230,21 +245,24 @@ def has_conformance(self, conformance_class: ConformanceClasses | str) -> bool:
230245
provides such an endpoint.
231246
232247
Args:
233-
name : name of :py:class:`ConformanceClasses` keys to check
234-
conformance against.
248+
conformance_class: Either a ConformanceClasses enum member or a
249+
string name of a conformance class to check against
250+
235251
236252
Return:
237-
bool: Indicates if the API conforms to the given spec or URI.
253+
Indicates if the API conforms to the given spec or URI.
238254
"""
239255
if isinstance(conformance_class, str):
240256
conformance_class = ConformanceClasses.get_by_name(conformance_class)
241257

242258
return any(re.match(conformance_class.pattern, uri) for uri in self.get_conforms_to())
243259

244260
def _supports_opportunities(self) -> bool:
261+
"""Check if the API supports opportunities"""
245262
return self.has_conformance(ConformanceClasses.OPPORTUNITIES)
246263

247264
def _supports_async_opportunities(self) -> bool:
265+
"""Check if the API supports asynchronous opportunities"""
248266
return self.has_conformance(ConformanceClasses.ASYNC_OPPORTUNITIES)
249267

250268
def get_products(self, limit: int | None = None) -> Iterator[Product]:
@@ -346,6 +364,17 @@ def create_product_order(self, product_id: str, order_parameters: OrderPayload)
346364
return Order.model_validate(product_order_json)
347365

348366
def _get_products_href(self, product_id: str | None = None, subpath: str | None = None) -> str:
367+
"""Get the href for the products endpoint
368+
369+
Args:
370+
product_id: Optional product ID to get the href for
371+
372+
Returns:
373+
str: The href for the products endpoint
374+
375+
Raises:
376+
ValueError: When no products link is found in the API
377+
"""
349378
product_link = self.get_single_link("products")
350379
if product_link is None:
351380
raise ValueError("No products link found")
@@ -368,6 +397,9 @@ def get_orders(self, limit: int | None = None) -> Iterator[Order]: # type: igno
368397
# TODO Update return type after the pydantic model generic type is fixed
369398
"""Get orders from this STAPI API
370399
400+
Args:
401+
limit: Optional limit on the number of orders to return
402+
371403
Returns:
372404
Iterator[Order]: An iterator of STAPI Orders
373405
"""
@@ -395,7 +427,7 @@ def get_order(self, order_id: str) -> Order: # type: ignore[type-arg]
395427
Order: A STAPI Order
396428
397429
Raises:
398-
ValueError if order_id does not exist.
430+
ValueError: When the specified order_id does not exist
399431
"""
400432

401433
order_endpoint = self._get_orders_href(order_id)
@@ -407,6 +439,18 @@ def get_order(self, order_id: str) -> Order: # type: ignore[type-arg]
407439
return Order.model_validate(order_json)
408440

409441
def _get_orders_href(self, order_id: str | None = None) -> str:
442+
"""Get the href for the orders endpoint
443+
444+
Args:
445+
order_id: Optional order ID to get the href for
446+
447+
Returns:
448+
The href for the orders endpoint
449+
450+
Raises:
451+
ValueError: When no orders link is found in the API
452+
"""
453+
410454
order_link = self.get_single_link("orders")
411455
if order_link is None:
412456
raise ValueError("No orders link found")

0 commit comments

Comments
 (0)