Skip to content
1 change: 1 addition & 0 deletions stapi-fastapi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a

## Changed

- Renamed all exceptions to errors ([#41](https://github.com/stapi-spec/pystapi/pull/41))
- stapi-fastapi is now using stapi-pydantic models, deduplicating code
- Product in stapi-fastapi is now subclass of Product from stapi-pydantic

Expand Down
6 changes: 3 additions & 3 deletions stapi-fastapi/src/stapi_fastapi/backends/product_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

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

SearchOpportunitiesAsync = Callable[
Expand All @@ -64,7 +64,7 @@
- Returning returns.result.Failure[Exception] will result in a 500.

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

GetOpportunityCollection = Callable[
Expand Down Expand Up @@ -105,5 +105,5 @@

Note:
Backends must validate order payload and return
returns.result.Failure[stapi_fastapi.exceptions.ConstraintsException] if not valid.
returns.result.Failure[stapi_fastapi.errors.QueryablesError] if not valid.
"""
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
from fastapi import HTTPException, status


class StapiException(HTTPException):
class StapiError(HTTPException):
pass


class ConstraintsException(StapiException):
class QueryablesError(StapiError):
def __init__(self, detail: Any) -> None:
super().__init__(status.HTTP_422_UNPROCESSABLE_ENTITY, detail)


class NotFoundException(StapiException):
class NotFoundError(StapiError):
def __init__(self, detail: Any | None = None) -> None:
super().__init__(status.HTTP_404_NOT_FOUND, detail)
12 changes: 6 additions & 6 deletions stapi-fastapi/src/stapi_fastapi/models/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any

from stapi_pydantic import Constraints, OpportunityProperties, OrderParameters
from stapi_pydantic import OpportunityProperties, OrderParameters, Queryables
from stapi_pydantic import Product as BaseProduct

if TYPE_CHECKING:
Expand All @@ -21,7 +21,7 @@ class Product(BaseProduct):
_get_opportunity_collection: GetOpportunityCollection | None

# we don't want to include these in the model fields
_constraints: type[Constraints]
_queryables: type[Queryables]
_opportunity_properties: type[OpportunityProperties]
_order_parameters: type[OrderParameters]

Expand All @@ -32,7 +32,7 @@ def __init__(
search_opportunities: SearchOpportunities | None = None,
search_opportunities_async: SearchOpportunitiesAsync | None = None,
get_opportunity_collection: GetOpportunityCollection | None = None,
constraints: type[Constraints],
queryables: type[Queryables],
opportunity_properties: type[OpportunityProperties],
order_parameters: type[OrderParameters],
**kwargs: Any,
Expand All @@ -49,7 +49,7 @@ def __init__(
self._search_opportunities = search_opportunities
self._search_opportunities_async = search_opportunities_async
self._get_opportunity_collection = get_opportunity_collection
self._constraints = constraints
self._queryables = queryables
self._opportunity_properties = opportunity_properties
self._order_parameters = order_parameters

Expand Down Expand Up @@ -84,8 +84,8 @@ def supports_async_opportunity_search(self) -> bool:
return self._search_opportunities_async is not None and self._get_opportunity_collection is not None

@property
def constraints(self) -> type[Constraints]:
return self._constraints
def queryables(self) -> type[Queryables]:
return self._queryables

@property
def opportunity_properties(self) -> type[OpportunityProperties]:
Expand Down
36 changes: 18 additions & 18 deletions stapi-fastapi/src/stapi_fastapi/routers/product_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@
)

from stapi_fastapi.constants import TYPE_JSON
from stapi_fastapi.exceptions import ConstraintsException, NotFoundException
from stapi_fastapi.errors import QueryablesError, NotFoundError
from stapi_fastapi.models.product import Product
from stapi_fastapi.responses import GeoJSONResponse
from stapi_fastapi.routers.route_names import (
CONFORMANCE,
CREATE_ORDER,
GET_CONSTRAINTS,
GET_OPPORTUNITY_COLLECTION,
GET_ORDER_PARAMETERS,
GET_PRODUCT,
GET_QUERYABLES,
SEARCH_OPPORTUNITIES,
)

Expand Down Expand Up @@ -103,11 +103,11 @@ def __init__(
)

self.add_api_route(
path="/constraints",
endpoint=self.get_product_constraints,
name=f"{self.root_router.name}:{self.product.id}:{GET_CONSTRAINTS}",
path="/queryables",
endpoint=self.get_product_queryables,
name=f"{self.root_router.name}:{self.product.id}:{GET_QUERYABLES}",
methods=["GET"],
summary="Get constraints for the product",
summary="Get queryables for the product",
tags=["Products"],
)

Expand Down Expand Up @@ -156,7 +156,7 @@ async def _create_order(
name=f"{self.root_router.name}:{self.product.id}:{SEARCH_OPPORTUNITIES}",
methods=["POST"],
response_class=GeoJSONResponse,
# unknown why mypy can't see the constraints property on Product, ignoring
# unknown why mypy can't see the queryables property on Product, ignoring
response_model=OpportunityCollection[
Geometry,
self.product.opportunity_properties, # type: ignore
Expand Down Expand Up @@ -205,10 +205,10 @@ def get_product(self, request: Request) -> ProductPydantic:
Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{GET_CONSTRAINTS}",
f"{self.root_router.name}:{self.product.id}:{GET_QUERYABLES}",
),
),
rel="constraints",
rel="queryables",
type=TYPE_JSON,
),
Link(
Expand Down Expand Up @@ -255,7 +255,7 @@ async def search_opportunities(
prefer: Prefer | None = Depends(get_prefer),
) -> OpportunityCollection | Response: # type: ignore
"""
Explore the opportunities available for a particular set of constraints
Explore the opportunities available for a particular set of queryables
"""
# sync
if not self.root_router.supports_async_opportunity_search or (
Expand Down Expand Up @@ -300,7 +300,7 @@ async def search_opportunities_sync(
links.append(self.pagination_link(request, search, x))
case Maybe.empty:
pass
case Failure(e) if isinstance(e, ConstraintsException):
case Failure(e) if isinstance(e, QueryablesError):
raise e
case Failure(e):
logger.error(
Expand Down Expand Up @@ -339,7 +339,7 @@ async def search_opportunities_async(
content=search_record.model_dump(mode="json"),
headers=headers,
)
case Failure(e) if isinstance(e, ConstraintsException):
case Failure(e) if isinstance(e, QueryablesError):
raise e
case Failure(e):
logger.error(
Expand All @@ -359,15 +359,15 @@ def get_product_conformance(self) -> Conformance:
"""
return Conformance.model_validate({"conforms_to": self.product.conformsTo})

def get_product_constraints(self) -> JsonSchemaModel:
def get_product_queryables(self) -> JsonSchemaModel:
"""
Return supported constraints of a specific product
Return supported queryables of a specific product
"""
return self.product.constraints
return self.product.queryables

def get_product_order_parameters(self) -> JsonSchemaModel:
"""
Return supported constraints of a specific product
Return supported order parameters of a specific product
"""
return self.product.order_parameters

Expand All @@ -385,7 +385,7 @@ async def create_order(self, payload: OrderPayload, request: Request, response:
location = str(self.root_router.generate_order_href(request, order.id))
response.headers["Location"] = location
return order # type: ignore
case Failure(e) if isinstance(e, ConstraintsException):
case Failure(e) if isinstance(e, QueryablesError):
raise e
case Failure(e):
logger.error(
Expand Down Expand Up @@ -449,7 +449,7 @@ async def get_opportunity_collection(
)
return opportunity_collection # type: ignore
case Success(Maybe.empty):
raise NotFoundException("Opportunity Collection not found")
raise NotFoundError("Opportunity Collection not found")
case Failure(e):
logger.error(
"An error occurred while fetching opportunity collection: '%s': %s",
Expand Down
16 changes: 8 additions & 8 deletions stapi-fastapi/src/stapi_fastapi/routers/root_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
)
from stapi_fastapi.conformance import ASYNC_OPPORTUNITIES, CORE
from stapi_fastapi.constants import TYPE_GEOJSON, TYPE_JSON
from stapi_fastapi.exceptions import NotFoundException
from stapi_fastapi.errors import NotFoundError
from stapi_fastapi.models.product import Product
from stapi_fastapi.responses import GeoJSONResponse
from stapi_fastapi.routers.product_router import ProductRouter
Expand Down Expand Up @@ -231,7 +231,7 @@ def get_products(self, request: Request, next: str | None = None, limit: int = 1
start = self.product_ids.index(next)
except ValueError:
logger.exception("An error occurred while retrieving products")
raise NotFoundException(detail="Error finding pagination token for products") from None
raise NotFoundError(detail="Error finding pagination token for products") from None
end = start + limit
ids = self.product_ids[start:end]
links = [
Expand Down Expand Up @@ -262,7 +262,7 @@ async def get_orders(
case Maybe.empty:
pass
case Failure(ValueError()):
raise NotFoundException(detail="Error finding pagination token")
raise NotFoundError(detail="Error finding pagination token")
case Failure(e):
logger.error(
"An error occurred while retrieving orders: %s",
Expand All @@ -285,7 +285,7 @@ async def get_order(self, order_id: str, request: Request) -> Order[OrderStatus]
order.links.extend(self.order_links(order, request))
return order # type: ignore
case Success(Maybe.empty):
raise NotFoundException("Order not found")
raise NotFoundError("Order not found")
case Failure(e):
logger.error(
"An error occurred while retrieving order '%s': %s",
Expand Down Expand Up @@ -316,9 +316,9 @@ async def get_order_statuses(
case Maybe.empty:
pass
case Success(Maybe.empty):
raise NotFoundException("Order not found")
raise NotFoundError("Order not found")
case Failure(ValueError()):
raise NotFoundException("Error finding pagination token")
raise NotFoundError("Error finding pagination token")
case Failure(e):
logger.error(
"An error occurred while retrieving order statuses: %s",
Expand Down Expand Up @@ -392,7 +392,7 @@ async def get_opportunity_search_records(
case Maybe.empty:
pass
case Failure(ValueError()):
raise NotFoundException(detail="Error finding pagination token")
raise NotFoundError(detail="Error finding pagination token")
case Failure(e):
logger.error(
"An error occurred while retrieving opportunity search records: %s",
Expand All @@ -415,7 +415,7 @@ async def get_opportunity_search_record(self, search_record_id: str, request: Re
search_record.links.append(self.opportunity_search_record_self_link(search_record, request))
return search_record # type: ignore
case Success(Maybe.empty):
raise NotFoundException("Opportunity Search Record not found")
raise NotFoundError("Opportunity Search Record not found")
case Failure(e):
logger.error(
"An error occurred while retrieving opportunity search record '%s': %s",
Expand Down
2 changes: 1 addition & 1 deletion stapi-fastapi/src/stapi_fastapi/routers/route_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
LIST_PRODUCTS = "list-products"
LIST_PRODUCTS = "list-products"
GET_PRODUCT = "get-product"
GET_CONSTRAINTS = "get-constraints"
GET_QUERYABLES = "get-queryables"
GET_ORDER_PARAMETERS = "get-order-parameters"

# Opportunity
Expand Down
12 changes: 6 additions & 6 deletions stapi-fastapi/tests/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def put_opportunity_collection(self, collection: OpportunityCollection) -> None:
self._collections[collection.id] = deepcopy(collection)


class MyProductConstraints(BaseModel):
class MyProductQueryables(BaseModel):
off_nadir: int


Expand Down Expand Up @@ -134,7 +134,7 @@ class MyOrderParameters(OrderParameters):
search_opportunities=None,
search_opportunities_async=None,
get_opportunity_collection=None,
constraints=MyProductConstraints,
queryables=MyProductQueryables,
opportunity_properties=MyOpportunityProperties,
order_parameters=MyOrderParameters,
)
Expand All @@ -151,7 +151,7 @@ class MyOrderParameters(OrderParameters):
search_opportunities=mock_search_opportunities,
search_opportunities_async=None,
get_opportunity_collection=None,
constraints=MyProductConstraints,
queryables=MyProductQueryables,
opportunity_properties=MyOpportunityProperties,
order_parameters=MyOrderParameters,
)
Expand All @@ -169,7 +169,7 @@ class MyOrderParameters(OrderParameters):
search_opportunities=None,
search_opportunities_async=mock_search_opportunities_async,
get_opportunity_collection=mock_get_opportunity_collection,
constraints=MyProductConstraints,
queryables=MyProductQueryables,
opportunity_properties=MyOpportunityProperties,
order_parameters=MyOrderParameters,
)
Expand All @@ -186,7 +186,7 @@ class MyOrderParameters(OrderParameters):
search_opportunities=mock_search_opportunities,
search_opportunities_async=mock_search_opportunities_async,
get_opportunity_collection=mock_get_opportunity_collection,
constraints=MyProductConstraints,
queryables=MyProductQueryables,
opportunity_properties=MyOpportunityProperties,
order_parameters=MyOrderParameters,
)
Expand All @@ -203,7 +203,7 @@ class MyOrderParameters(OrderParameters):
search_opportunities=mock_search_opportunities,
search_opportunities_async=None,
get_opportunity_collection=None,
constraints=MyProductConstraints,
queryables=MyProductQueryables,
opportunity_properties=MyOpportunityProperties,
order_parameters=MyOrderParameters,
)
Expand Down
12 changes: 6 additions & 6 deletions stapi-fastapi/tests/test_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_product_response_links(
assert_link(
url, body, "conformance", f"/products/{product_id}/conformance"
) # https://github.com/stapi-spec/stapi-spec/issues/253
assert_link(url, body, "constraints", f"/products/{product_id}/constraints")
assert_link(url, body, "queryables", f"/products/{product_id}/queryables")
assert_link(url, body, "order-parameters", f"/products/{product_id}/order-parameters")
assert_link(url, body, "opportunities", f"/products/{product_id}/opportunities")
assert_link(url, body, "create-order", f"/products/{product_id}/orders", method="POST")
Expand All @@ -47,7 +47,7 @@ def test_product_conformance_response(
product_id: str,
stapi_client: TestClient,
):
res = stapi_client.get(f"/products/{product_id}/constraints")
res = stapi_client.get(f"/products/{product_id}/conformance")
assert res.status_code == status.HTTP_200_OK
assert res.headers["Content-Type"] == "application/json"

Expand All @@ -56,11 +56,11 @@ def test_product_conformance_response(


@pytest.mark.parametrize("product_id", ["test-spotlight"])
def test_product_constraints_response(
def test_product_queryables_response(
product_id: str,
stapi_client: TestClient,
):
res = stapi_client.get(f"/products/{product_id}/constraints")
res = stapi_client.get(f"/products/{product_id}/queryables")
assert res.status_code == status.HTTP_200_OK
assert res.headers["Content-Type"] == "application/json"

Expand Down Expand Up @@ -106,8 +106,8 @@ def test_get_products_pagination(
"type": "application/json",
},
{
"href": f"http://stapiserver/products/{product_id}/constraints",
"rel": "constraints",
"href": f"http://stapiserver/products/{product_id}/queryables",
"rel": "queryables",
"type": "application/json",
},
{
Expand Down
Loading