diff --git a/stapi-fastapi/src/stapi_fastapi/backends/product_backend.py b/stapi-fastapi/src/stapi_fastapi/backends/product_backend.py index 1a96b7a..b554f79 100644 --- a/stapi-fastapi/src/stapi_fastapi/backends/product_backend.py +++ b/stapi-fastapi/src/stapi_fastapi/backends/product_backend.py @@ -42,8 +42,8 @@ - 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. + Backends must validate search queryables and return + returns.result.Failure[stapi_fastapi.exceptions.QueryablesException] if not valid. """ SearchOpportunitiesAsync = Callable[ @@ -63,8 +63,8 @@ - Should return returns.result.Success[OpportunitySearchRecord] - 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. +Backends must validate search queryables and return +returns.result.Failure[stapi_fastapi.exceptions.QueryablesException] if not valid. """ GetOpportunityCollection = Callable[ @@ -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.exceptions.QueryablesException] if not valid. """ diff --git a/stapi-fastapi/src/stapi_fastapi/exceptions.py b/stapi-fastapi/src/stapi_fastapi/exceptions.py index 32d512b..bee04f5 100644 --- a/stapi-fastapi/src/stapi_fastapi/exceptions.py +++ b/stapi-fastapi/src/stapi_fastapi/exceptions.py @@ -7,7 +7,7 @@ class StapiException(HTTPException): pass -class ConstraintsException(StapiException): +class QueryablesException(StapiException): def __init__(self, detail: Any) -> None: super().__init__(status.HTTP_422_UNPROCESSABLE_ENTITY, detail) diff --git a/stapi-fastapi/src/stapi_fastapi/models/product.py b/stapi-fastapi/src/stapi_fastapi/models/product.py index 4fe505d..fd17bcd 100644 --- a/stapi-fastapi/src/stapi_fastapi/models/product.py +++ b/stapi-fastapi/src/stapi_fastapi/models/product.py @@ -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: @@ -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] @@ -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, @@ -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 @@ -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]: diff --git a/stapi-fastapi/src/stapi_fastapi/routers/product_router.py b/stapi-fastapi/src/stapi_fastapi/routers/product_router.py index 756dc07..52e6d6b 100644 --- a/stapi-fastapi/src/stapi_fastapi/routers/product_router.py +++ b/stapi-fastapi/src/stapi_fastapi/routers/product_router.py @@ -34,16 +34,16 @@ ) from stapi_fastapi.constants import TYPE_JSON -from stapi_fastapi.exceptions import ConstraintsException, NotFoundException +from stapi_fastapi.exceptions import NotFoundException, QueryablesException 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, ) @@ -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"], ) @@ -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 @@ -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( @@ -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 ( @@ -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, QueryablesException): raise e case Failure(e): logger.error( @@ -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, QueryablesException): raise e case Failure(e): logger.error( @@ -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 @@ -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, QueryablesException): raise e case Failure(e): logger.error( diff --git a/stapi-fastapi/src/stapi_fastapi/routers/route_names.py b/stapi-fastapi/src/stapi_fastapi/routers/route_names.py index cbde946..3283c57 100644 --- a/stapi-fastapi/src/stapi_fastapi/routers/route_names.py +++ b/stapi-fastapi/src/stapi_fastapi/routers/route_names.py @@ -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 diff --git a/stapi-fastapi/tests/shared.py b/stapi-fastapi/tests/shared.py index 58aa897..10af058 100644 --- a/stapi-fastapi/tests/shared.py +++ b/stapi-fastapi/tests/shared.py @@ -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 @@ -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, ) @@ -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, ) @@ -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, ) @@ -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, ) @@ -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, ) diff --git a/stapi-fastapi/tests/test_product.py b/stapi-fastapi/tests/test_product.py index cb80023..1fee345 100644 --- a/stapi-fastapi/tests/test_product.py +++ b/stapi-fastapi/tests/test_product.py @@ -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") @@ -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" @@ -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" @@ -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", }, { diff --git a/stapi-pydantic/CHANGELOG.md b/stapi-pydantic/CHANGELOG.md index 35dda24..9621f6b 100644 --- a/stapi-pydantic/CHANGELOG.md +++ b/stapi-pydantic/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Changed +- `s/constraints/queryables/` ([#74](https://github.com/stapi-spec/pystapi/pull/74)) - `s/canceled/cancelled/` ([#75](https://github.com/stapi-spec/pystapi/pull/75)) ## [0.0.2] - 2025-04-02 diff --git a/stapi-pydantic/src/stapi_pydantic/__init__.py b/stapi-pydantic/src/stapi_pydantic/__init__.py index 0ee1f63..44ecbd0 100644 --- a/stapi-pydantic/src/stapi_pydantic/__init__.py +++ b/stapi-pydantic/src/stapi_pydantic/__init__.py @@ -1,5 +1,4 @@ from .conformance import Conformance -from .constraints import Constraints from .datetime_interval import DatetimeInterval from .filter import CQL2Filter from .json_schema_model import JsonSchemaModel @@ -26,12 +25,12 @@ OrderStatuses, ) from .product import Product, ProductsCollection, Provider, ProviderRole +from .queryables import Queryables from .root import RootResponse from .shared import Link __all__ = [ "Conformance", - "Constraints", "CQL2Filter", "DatetimeInterval", "JsonSchemaModel", @@ -58,5 +57,6 @@ "ProductsCollection", "Provider", "ProviderRole", + "Queryables", "RootResponse", ] diff --git a/stapi-pydantic/src/stapi_pydantic/product.py b/stapi-pydantic/src/stapi_pydantic/product.py index d6dfd78..54b946f 100644 --- a/stapi-pydantic/src/stapi_pydantic/product.py +++ b/stapi-pydantic/src/stapi_pydantic/product.py @@ -1,13 +1,11 @@ from enum import StrEnum -from typing import Any, Literal, Self, TypeAlias +from typing import Any, Literal, Self from pydantic import AnyHttpUrl, BaseModel, Field from .constants import STAPI_VERSION from .shared import Link -Constraints: TypeAlias = BaseModel - class ProviderRole(StrEnum): licensor = "licensor" diff --git a/stapi-pydantic/src/stapi_pydantic/constraints.py b/stapi-pydantic/src/stapi_pydantic/queryables.py similarity index 75% rename from stapi-pydantic/src/stapi_pydantic/constraints.py rename to stapi-pydantic/src/stapi_pydantic/queryables.py index ad3e6de..2bb5c5c 100644 --- a/stapi-pydantic/src/stapi_pydantic/constraints.py +++ b/stapi-pydantic/src/stapi_pydantic/queryables.py @@ -1,5 +1,5 @@ from pydantic import BaseModel, ConfigDict -class Constraints(BaseModel): +class Queryables(BaseModel): model_config = ConfigDict(extra="allow")