Skip to content
This repository was archived by the owner on Apr 2, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
33 changes: 19 additions & 14 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,28 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- run: pipx install poetry==1.7.1
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: poetry
- name: Install dependencies
run: poetry install --with=dev
- name: Lint code
if: ${{ matrix.python-version == 3.12 }}
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.cache/pip
.venv
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
${{ runner.os }}-pip-
- name: Install
run: |
python -m pip install poetry==1.7.1
poetry install --with=dev
- name: Lint
run: |
python -m pip install pre-commit
pre-commit run --all-files
- name: Type-check code
if: ${{ matrix.python-version == 3.12 }}
run: poetry run mypy src
- name: Run tests
run: poetry run nox
poetry run pre-commit run --all-files
- name: Test
run: poetry run pytest
53 changes: 26 additions & 27 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
name: Build Python Package
name: PR Checks

on:
push:
branches:
- main
pull_request:
branches:
- main
release:
types:
- published
branches: ["main"]

jobs:
build-package:
test:
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/stapi-fastapi
permissions:
id-token: write
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
- uses: actions/setup-python@v5
with:
python-version: "3.12.x"
- name: Install dependencies
python-version: ${{ matrix.python-version }}
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.cache/pip
.venv
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
${{ runner.os }}-pip-
- name: Install
run: |
python -m pip install poetry==1.7.1
poetry install --with=dev
- name: Lint
run: |
python -m pip install --upgrade pip
pip install build
pip install .
- name: Build package
run: python -m build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
if: startsWith(github.ref, 'refs/tags')
poetry run pre-commit run --all-files
- name: Test
run: poetry run pytest
21 changes: 18 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Added token-based pagination to `GET /orders`, `GET /products`,
`GET /orders/{order_id}/statuses`, and `POST /products/{product_id}/opportunities`.

### Changed

- Replaced the root and product backend Protocol classes with Callable type aliases to
enable future changes to make product opportunity searching, product ordering, and/or
asynchronous (stateful) product opportunity searching optional.
- Backend methods that support pagination now return tuples to include the pagination
token.
- Moved `OrderCollection` construction from the root backend to the `RootRouter`
`get_orders` method.

## [v0.5.0] - 2025-01-08

Expand Down Expand Up @@ -64,7 +80,6 @@ none

none


## [v0.3.0] - 2024-12-6

### Added
Expand All @@ -75,7 +90,7 @@ none

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

### Deprecated
Expand Down Expand Up @@ -140,7 +155,7 @@ Initial release
- Add link `create-order` to OpportunityCollection

[unreleased]: https://github.com/stapi-spec/stapi-fastapi/compare/v0.5.0...main
[v0.4.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.5.0
[v0.5.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.5.0
[v0.4.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.4.0
[v0.3.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.3.0
[v0.2.0]: https://github.com/stapi-spec/stapi-fastapi/tree/v0.2.0
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ STAPI FastAPI provides an `fastapi.APIRouter` which must be included in
`GET`: `'/orders`, `/products`, `/orders/{order_id}/statuses`
`POST`: `/opportunities`.

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.
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 as
separate key/value pairs for `POST` requests.

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.
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.

`limit` defaults to 10 and maxes at 100.


## ADRs

ADRs can be found in in the [adrs](./adrs/README.md) directory.
Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import nox


@nox.session(python=["3.12"])
@nox.session(python=["3.12", "3.13"])
def tests(session):
session.run("poetry", "install", external=True)
session.run("poetry", "run", "pytest", external=True)
6 changes: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
packages = [{include = "stapi_fastapi", from="src"}]

[tool.poetry.dependencies]
python = "3.12.*"
python = "^3.12.0"
fastapi = "^0.115.0"
pydantic = "^2.10.1"
geojson-pydantic = "^1.1.1"
Expand Down
3 changes: 0 additions & 3 deletions src/stapi_fastapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .backends import ProductBackend, RootBackend
from .models import (
Link,
OpportunityProperties,
Expand All @@ -12,10 +11,8 @@
"Link",
"OpportunityProperties",
"Product",
"ProductBackend",
"ProductRouter",
"Provider",
"ProviderRole",
"RootBackend",
"RootRouter",
]
11 changes: 7 additions & 4 deletions src/stapi_fastapi/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from .product_backend import ProductBackend
from .root_backend import RootBackend
from .product_backend import CreateOrder, SearchOpportunities
from .root_backend import GetOrder, GetOrders, GetOrderStatuses

__all__ = [
"ProductBackend",
"RootBackend",
"CreateOrder",
"GetOrder",
"GetOrders",
"GetOrderStatuses",
"SearchOpportunities",
]
77 changes: 47 additions & 30 deletions src/stapi_fastapi/backends/product_backend.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import Protocol
from typing import Any, Callable, Coroutine

from fastapi import Request
from returns.maybe import Maybe
Expand All @@ -10,32 +10,49 @@
from stapi_fastapi.models.order import Order, OrderPayload
from stapi_fastapi.routers.product_router import ProductRouter


class ProductBackend(Protocol): # pragma: nocover
async def search_opportunities(
self,
product_router: ProductRouter,
search: OpportunityRequest,
request: Request,
next: str | None,
limit: int,
) -> ResultE[tuple[list[Opportunity], Maybe[str]]]:
"""
Search for ordering opportunities for the given search parameters and return pagination token if applicable.

Backends must validate search constraints and return
`stapi_fastapi.exceptions.ConstraintsException` if not valid.
"""

async def create_order(
self,
product_router: ProductRouter,
search: OrderPayload,
request: Request,
) -> ResultE[Order]:
"""
Create a new order.

Backends must validate order payload and return
`stapi_fastapi.exceptions.ConstraintsException` if not valid.
"""
SearchOpportunities = Callable[
[ProductRouter, OpportunityRequest, Request, str | None, int],
Coroutine[Any, Any, ResultE[tuple[list[Opportunity], Maybe[str]]]],
]
"""
Type alias for an async function that searches for ordering opportunities for the given
search parameters.

Args:
product_router (ProductRouter): The product router.
search (OpportunityRequest): The search parameters.
request (Request): FastAPI's Request object.
next (str | None): A pagination token.
limit (int): The maximum number of opportunities to return in a page.

Returns:
A tuple containing a list of opportunities and a pagination token.

- Should return returns.result.Success[tuple[list[Opportunity], returns.maybe.Some[str]]] if including a pagination token
- Should return returns.result.Success[tuple[list[Opportunity], returns.maybe.Nothing]] if not including a pagination token
- Returning returns.result.Failure[Exception] will result in a 500.

Note:
Backends must validate search constraints and return
returns.result.Failure[stapi_fastapi.exceptions.ConstraintsException] if not valid.
"""

CreateOrder = Callable[
[ProductRouter, OrderPayload, Request], Coroutine[Any, Any, ResultE[Order]]
]
"""
Type alias for an async function that creates a new order.

Args:
product_router (ProductRouter): The product router.
payload (OrderPayload): The order payload.
request (Request): FastAPI's Request object.

Returns:
- Should return returns.result.Success[Order]
- Returning returns.result.Failure[Exception] will result in a 500.

Note:
Backends must validate order payload and return
returns.result.Failure[stapi_fastapi.exceptions.ConstraintsException] if not valid.
"""
Loading