Skip to content
This repository was archived by the owner on Apr 2, 2025. It is now read-only.

Commit 2ec540d

Browse files
author
Phil Varner
authored
Merge branch 'main' into pv/add-additional-status-codes
2 parents efcfd49 + f3adb87 commit 2ec540d

File tree

21 files changed

+680
-675
lines changed

21 files changed

+680
-675
lines changed

.github/workflows/pr.yml

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,28 @@ jobs:
99
runs-on: ubuntu-latest
1010
strategy:
1111
matrix:
12-
python-version: ["3.12"]
12+
python-version: ["3.12", "3.13"]
1313
steps:
1414
- uses: actions/checkout@v4
15-
- run: pipx install poetry==1.7.1
1615
- uses: actions/setup-python@v5
1716
with:
1817
python-version: ${{ matrix.python-version }}
19-
cache: poetry
20-
- name: Install dependencies
21-
run: poetry install --with=dev
22-
- name: Lint code
23-
if: ${{ matrix.python-version == 3.12 }}
18+
- name: Cache dependencies
19+
uses: actions/cache@v3
20+
with:
21+
path: |
22+
~/.cache/pip
23+
.venv
24+
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
25+
restore-keys: |
26+
${{ runner.os }}-pip-${{ matrix.python-version }}-
27+
${{ runner.os }}-pip-
28+
- name: Install
29+
run: |
30+
python -m pip install poetry==1.7.1
31+
poetry install --with=dev
32+
- name: Lint
2433
run: |
25-
python -m pip install pre-commit
26-
pre-commit run --all-files
27-
- name: Type-check code
28-
if: ${{ matrix.python-version == 3.12 }}
29-
run: poetry run mypy src
30-
- name: Run tests
31-
run: poetry run nox
34+
poetry run pre-commit run --all-files
35+
- name: Test
36+
run: poetry run pytest

CHANGELOG.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,31 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased
8+
## [unreleased]
99

1010
### Added
1111

12+
- Added token-based pagination to `GET /orders`, `GET /products`,
13+
`GET /orders/{order_id}/statuses`, and `POST /products/{product_id}/opportunities`.
1214
- Optional and Extension STAPI Status Codes "scheduled", "held", "processing", "reserved", "tasked",
1315
and "user_cancelled"
1416

17+
### Changed
18+
19+
- Replaced the root and product backend Protocol classes with Callable type aliases to
20+
enable future changes to make product opportunity searching, product ordering, and/or
21+
asynchronous (stateful) product opportunity searching optional.
22+
- Backend methods that support pagination now return tuples to include the pagination
23+
token.
24+
- Moved `OrderCollection` construction from the root backend to the `RootRouter`
25+
`get_orders` method.
26+
- Renamed `OpportunityRequest` to `OpportunityPayload` so that would not be confused as
27+
being a subclass of the Starlette/FastAPI Request class.
28+
29+
### Fixed
30+
31+
- Opportunities Search result now has the search body in the `create-order` link.
32+
1533
## [v0.5.0] - 2025-01-08
1634

1735
### Added
@@ -70,7 +88,6 @@ none
7088

7189
none
7290

73-
7491
## [v0.3.0] - 2024-12-6
7592

7693
### Added
@@ -81,7 +98,7 @@ none
8198

8299
- OrderStatusCode and ProviderRole are now StrEnum instead of (str, Enum)
83100
- All types using `Result[A, Exception]` have been replace with the equivalent type `ResultE[A]`
84-
- Order and OrderCollection extend _GeoJsonBase instead of Feature and FeatureCollection, to allow for tighter
101+
- Order and OrderCollection extend \_GeoJsonBase instead of Feature and FeatureCollection, to allow for tighter
85102
constraints on fields
86103

87104
### Deprecated
@@ -146,7 +163,7 @@ Initial release
146163
- Add link `create-order` to OpportunityCollection
147164

148165
[unreleased]: https://github.com/stapi-spec/stapi-fastapi/compare/v0.5.0...main
149-
[v0.4.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.5.0
166+
[v0.5.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.5.0
150167
[v0.4.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.4.0
151168
[v0.3.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.3.0
152169
[v0.2.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.2.0

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ STAPI FastAPI provides an `fastapi.APIRouter` which must be included in
1414
`GET`: `'/orders`, `/products`, `/orders/{order_id}/statuses`
1515
`POST`: `/opportunities`.
1616

17-
Pagination is token based and follows recommendations in the [STAC API pagination]. Limit and token are passed in as query params for `GET` endpoints, and via the body aas separte key/value pairs for `POST` requests.
17+
Pagination is token based and follows recommendations in the [STAC API pagination].
18+
Limit and token are passed in as query params for `GET` endpoints, and via the body as
19+
separate key/value pairs for `POST` requests.
1820

19-
If pagination is available and more records remain the response object will contain a `next` link object that can be used to get the next page of results. No `next` `Link` returned indicates there are no further records available.
21+
If pagination is available and more records remain the response object will contain a
22+
`next` link object that can be used to get the next page of results. No `next` `Link`
23+
returned indicates there are no further records available.
2024

2125
`limit` defaults to 10 and maxes at 100.
2226

23-
2427
## ADRs
2528

2629
ADRs can be found in in the [adrs](./adrs/README.md) directory.

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import nox
22

33

4-
@nox.session(python=["3.12"])
4+
@nox.session(python=["3.12", "3.13"])
55
def tests(session):
66
session.run("poetry", "install", external=True)
77
session.run("poetry", "run", "pytest", external=True)

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ readme = "README.md"
99
packages = [{include = "stapi_fastapi", from="src"}]
1010

1111
[tool.poetry.dependencies]
12-
python = "3.12.*"
12+
python = "^3.12.0"
1313
fastapi = "^0.115.0"
1414
pydantic = "^2.10.1"
1515
geojson-pydantic = "^1.1.1"

src/stapi_fastapi/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from .backends import ProductBackend, RootBackend
21
from .models import (
32
Link,
43
OpportunityProperties,
@@ -12,10 +11,8 @@
1211
"Link",
1312
"OpportunityProperties",
1413
"Product",
15-
"ProductBackend",
1614
"ProductRouter",
1715
"Provider",
1816
"ProviderRole",
19-
"RootBackend",
2017
"RootRouter",
2118
]
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
from .product_backend import ProductBackend
2-
from .root_backend import RootBackend
1+
from .product_backend import CreateOrder, SearchOpportunities
2+
from .root_backend import GetOrder, GetOrders, GetOrderStatuses
33

44
__all__ = [
5-
"ProductBackend",
6-
"RootBackend",
5+
"CreateOrder",
6+
"GetOrder",
7+
"GetOrders",
8+
"GetOrderStatuses",
9+
"SearchOpportunities",
710
]
Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,58 @@
11
from __future__ import annotations
22

3-
from typing import Protocol
3+
from typing import Any, Callable, Coroutine
44

55
from fastapi import Request
66
from returns.maybe import Maybe
77
from returns.result import ResultE
88

9-
from stapi_fastapi.models.opportunity import Opportunity, OpportunityRequest
9+
from stapi_fastapi.models.opportunity import Opportunity, OpportunityPayload
1010
from stapi_fastapi.models.order import Order, OrderPayload
1111
from stapi_fastapi.routers.product_router import ProductRouter
1212

13-
14-
class ProductBackend(Protocol): # pragma: nocover
15-
async def search_opportunities(
16-
self,
17-
product_router: ProductRouter,
18-
search: OpportunityRequest,
19-
request: Request,
20-
next: str | None,
21-
limit: int,
22-
) -> ResultE[tuple[list[Opportunity], Maybe[str]]]:
23-
"""
24-
Search for ordering opportunities for the given search parameters and return pagination token if applicable.
25-
26-
Backends must validate search constraints and return
27-
`stapi_fastapi.exceptions.ConstraintsException` if not valid.
28-
"""
29-
30-
async def create_order(
31-
self,
32-
product_router: ProductRouter,
33-
search: OrderPayload,
34-
request: Request,
35-
) -> ResultE[Order]:
36-
"""
37-
Create a new order.
38-
39-
Backends must validate order payload and return
40-
`stapi_fastapi.exceptions.ConstraintsException` if not valid.
41-
"""
13+
SearchOpportunities = Callable[
14+
[ProductRouter, OpportunityPayload, str | None, int, Request],
15+
Coroutine[Any, Any, ResultE[tuple[list[Opportunity], Maybe[str]]]],
16+
]
17+
"""
18+
Type alias for an async function that searches for ordering opportunities for the given
19+
search parameters.
20+
21+
Args:
22+
product_router (ProductRouter): The product router.
23+
search (OpportunityRequest): The search parameters.
24+
next (str | None): A pagination token.
25+
limit (int): The maximum number of opportunities to return in a page.
26+
request (Request): FastAPI's Request object.
27+
28+
Returns:
29+
A tuple containing a list of opportunities and a pagination token.
30+
31+
- Should return returns.result.Success[tuple[list[Opportunity], returns.maybe.Some[str]]] if including a pagination token
32+
- Should return returns.result.Success[tuple[list[Opportunity], returns.maybe.Nothing]] if not including a pagination token
33+
- Returning returns.result.Failure[Exception] will result in a 500.
34+
35+
Note:
36+
Backends must validate search constraints and return
37+
returns.result.Failure[stapi_fastapi.exceptions.ConstraintsException] if not valid.
38+
"""
39+
40+
CreateOrder = Callable[
41+
[ProductRouter, OrderPayload, Request], Coroutine[Any, Any, ResultE[Order]]
42+
]
43+
"""
44+
Type alias for an async function that creates a new order.
45+
46+
Args:
47+
product_router (ProductRouter): The product router.
48+
payload (OrderPayload): The order payload.
49+
request (Request): FastAPI's Request object.
50+
51+
Returns:
52+
- Should return returns.result.Success[Order]
53+
- Returning returns.result.Failure[Exception] will result in a 500.
54+
55+
Note:
56+
Backends must validate order payload and return
57+
returns.result.Failure[stapi_fastapi.exceptions.ConstraintsException] if not valid.
58+
"""
Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Protocol
1+
from typing import Any, Callable, Coroutine, TypeVar
22

33
from fastapi import Request
44
from returns.maybe import Maybe
@@ -9,40 +9,63 @@
99
OrderStatus,
1010
)
1111

12+
GetOrders = Callable[
13+
[str | None, int, Request],
14+
Coroutine[Any, Any, ResultE[tuple[list[Order], Maybe[str]]]],
15+
]
16+
"""
17+
Type alias for an async function that returns a list of existing Orders.
1218
13-
class RootBackend[T: OrderStatus](Protocol): # pragma: nocover
14-
async def get_orders(
15-
self, request: Request, next: str | None, limit: int
16-
) -> ResultE[tuple[list[Order], Maybe[str]]]:
17-
"""
18-
Return a list of existing orders and pagination token if applicable.
19-
"""
20-
...
19+
Args:
20+
next (str | None): A pagination token.
21+
limit (int): The maximum number of orders to return in a page.
22+
request (Request): FastAPI's Request object.
2123
22-
async def get_order(self, order_id: str, request: Request) -> ResultE[Maybe[Order]]:
23-
"""
24-
Get details for order with `order_id`.
24+
Returns:
25+
A tuple containing a list of orders and a pagination token.
2526
26-
Should return returns.results.Success[Order] if order is found.
27+
- Should return returns.result.Success[tuple[list[Order], returns.maybe.Some[str]]] if including a pagination token
28+
- Should return returns.result.Success[tuple[list[Order], returns.maybe.Nothing]] if not including a pagination token
29+
- Returning returns.result.Failure[Exception] will result in a 500.
30+
"""
2731

28-
Should return returns.results.Failure[returns.maybe.Nothing] if the
29-
order is not found or if access is denied.
32+
GetOrder = Callable[[str, Request], Coroutine[Any, Any, ResultE[Maybe[Order]]]]
33+
"""
34+
Type alias for an async function that gets details for the order with `order_id`.
3035
31-
A Failure[Exception] will result in a 500.
32-
"""
33-
...
36+
Args:
37+
order_id (str): The order ID.
38+
request (Request): FastAPI's Request object.
3439
35-
async def get_order_statuses(
36-
self, order_id: str, request: Request, next: str | None, limit: int
37-
) -> ResultE[tuple[list[T], Maybe[str]]]:
38-
"""
39-
Get statuses for order with `order_id` and return pagination token if applicable
40+
Returns:
41+
- Should return returns.result.Success[returns.maybe.Some[Order]] if order is found.
42+
- Should return returns.result.Success[returns.maybe.Nothing] if the order is not
43+
found or if access is denied.
44+
- Returning returns.result.Failure[Exception] will result in a 500.
45+
"""
4046

41-
Should return returns.results.Success[list[OrderStatus]] if order is found.
4247

43-
Should return returns.results.Failure[Exception] if the order is
44-
not found or if access is denied.
48+
T = TypeVar("T", bound=OrderStatus)
4549

46-
A Failure[Exception] will result in a 500.
47-
"""
48-
...
50+
51+
GetOrderStatuses = Callable[
52+
[str, str | None, int, Request],
53+
Coroutine[Any, Any, ResultE[tuple[list[T], Maybe[str]]]],
54+
]
55+
"""
56+
Type alias for an async function that gets statuses for the order with `order_id`.
57+
58+
Args:
59+
order_id (str): The order ID.
60+
next (str | None): A pagination token.
61+
limit (int): The maximum number of statuses to return in a page.
62+
request (Request): FastAPI's Request object.
63+
64+
Returns:
65+
A tuple containing a list of order statuses and a pagination token.
66+
67+
- Should return returns.result.Success[tuple[list[OrderStatus], returns.maybe.Some[str]] if order is found and including a pagination token.
68+
- Should return returns.result.Success[tuple[list[OrderStatus], returns.maybe.Nothing]] if order is found and not including a pagination token.
69+
- Should return returns.result.Failure[Exception] if the order is not found or if access is denied.
70+
- Returning returns.result.Failure[Exception] will result in a 500.
71+
"""

0 commit comments

Comments
 (0)