diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e93d7..b661e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +- Add constants for route names to be used in link href generation + ## [v0.6.0] - 2025-02-11 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9fcd02c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# Contributing + +TODO: Move most of the readme into here. + +## Design Principles + +### Route Names and Links + +The route names used in route defintions should be constants in `stapi_fastapi.routers.route_names`. This +makes it easier to populate these links in numerous places, including in apps that use this library. + +The general scheme for route names should follow: + +- `create-{x}` - create a resource `x` +- `create-{x}-for-{y}` - create a resource `x` as a sub-resource or associated resource of `y` +- `get-{x}` - retrieve a resource `x` +- `list-{xs}` - retrieve a list of resources of type `x` +- `list-{xs}-for-{y}` - retrieve a list of subresources of type `x` of a resource `y` +- `set-{x}` - update an existing resource `x` +- `set-{x}-for-{y}` - set a sub-resource `x` of a resource `y`, e.g., `set-status-for-order` diff --git a/src/stapi_fastapi/routers/product_router.py b/src/stapi_fastapi/routers/product_router.py index 4bacd77..0d98753 100644 --- a/src/stapi_fastapi/routers/product_router.py +++ b/src/stapi_fastapi/routers/product_router.py @@ -30,6 +30,14 @@ from stapi_fastapi.models.product import Product from stapi_fastapi.models.shared import Link from stapi_fastapi.responses import GeoJSONResponse +from stapi_fastapi.routers.route_names import ( + CREATE_ORDER, + GET_CONSTRAINTS, + GET_OPPORTUNITY_COLLECTION, + GET_ORDER_PARAMETERS, + GET_PRODUCT, + SEARCH_OPPORTUNITIES, +) from stapi_fastapi.types.json_schema_model import JsonSchemaModel if TYPE_CHECKING: @@ -76,7 +84,7 @@ def __init__( self.add_api_route( path="", endpoint=self.get_product, - name=f"{self.root_router.name}:{self.product.id}:get-product", + name=f"{self.root_router.name}:{self.product.id}:{GET_PRODUCT}", methods=["GET"], summary="Retrieve this product", tags=["Products"], @@ -85,7 +93,7 @@ def __init__( self.add_api_route( path="/constraints", endpoint=self.get_product_constraints, - name=f"{self.root_router.name}:{self.product.id}:get-constraints", + name=f"{self.root_router.name}:{self.product.id}:{GET_CONSTRAINTS}", methods=["GET"], summary="Get constraints for the product", tags=["Products"], @@ -94,7 +102,7 @@ def __init__( self.add_api_route( path="/order-parameters", endpoint=self.get_product_order_parameters, - name=f"{self.root_router.name}:{self.product.id}:get-order-parameters", + name=f"{self.root_router.name}:{self.product.id}:{GET_ORDER_PARAMETERS}", methods=["GET"], summary="Get order parameters for the product", tags=["Products"], @@ -121,7 +129,7 @@ async def _create_order( self.add_api_route( path="/orders", endpoint=_create_order, - name=f"{self.root_router.name}:{self.product.id}:create-order", + name=f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}", methods=["POST"], response_class=GeoJSONResponse, status_code=status.HTTP_201_CREATED, @@ -136,7 +144,7 @@ async def _create_order( self.add_api_route( path="/opportunities", endpoint=self.search_opportunities, - name=f"{self.root_router.name}:{self.product.id}:search-opportunities", + 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 @@ -158,7 +166,7 @@ async def _create_order( self.add_api_route( path="/opportunities/{opportunity_collection_id}", endpoint=self.get_opportunity_collection, - name=f"{self.root_router.name}:{self.product.id}:get-opportunity-collection", + name=f"{self.root_router.name}:{self.product.id}:{GET_OPPORTUNITY_COLLECTION}", methods=["GET"], response_class=GeoJSONResponse, summary="Get an Opportunity Collection by ID", @@ -170,7 +178,7 @@ def get_product(self, request: Request) -> Product: Link( href=str( request.url_for( - f"{self.root_router.name}:{self.product.id}:get-product", + f"{self.root_router.name}:{self.product.id}:{GET_PRODUCT}", ), ), rel="self", @@ -179,7 +187,7 @@ def get_product(self, request: Request) -> Product: 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_CONSTRAINTS}", ), ), rel="constraints", @@ -188,7 +196,7 @@ def get_product(self, request: Request) -> Product: Link( href=str( request.url_for( - f"{self.root_router.name}:{self.product.id}:get-order-parameters", + f"{self.root_router.name}:{self.product.id}:{GET_ORDER_PARAMETERS}", ), ), rel="order-parameters", @@ -197,7 +205,7 @@ def get_product(self, request: Request) -> Product: Link( href=str( request.url_for( - f"{self.root_router.name}:{self.product.id}:create-order", + f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}", ), ), rel="create-order", @@ -213,7 +221,7 @@ def get_product(self, request: Request) -> Product: Link( href=str( request.url_for( - f"{self.root_router.name}:{self.product.id}:search-opportunities", + f"{self.root_router.name}:{self.product.id}:{SEARCH_OPPORTUNITIES}", ), ), rel="opportunities", @@ -381,7 +389,7 @@ def order_link(self, request: Request, opp_req: OpportunityPayload): return Link( href=str( request.url_for( - f"{self.root_router.name}:{self.product.id}:create-order", + f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}", ), ), rel="create-order", @@ -419,7 +427,7 @@ async def get_opportunity_collection( Link( href=str( request.url_for( - f"{self.root_router.name}:{self.product.id}:get-opportunity-collection", + f"{self.root_router.name}:{self.product.id}:{GET_OPPORTUNITY_COLLECTION}", opportunity_collection_id=opportunity_collection_id, ), ), diff --git a/src/stapi_fastapi/routers/root_router.py b/src/stapi_fastapi/routers/root_router.py index af0571c..0605daf 100644 --- a/src/stapi_fastapi/routers/root_router.py +++ b/src/stapi_fastapi/routers/root_router.py @@ -34,6 +34,16 @@ from stapi_fastapi.models.shared import Link from stapi_fastapi.responses import GeoJSONResponse from stapi_fastapi.routers.product_router import ProductRouter +from stapi_fastapi.routers.route_names import ( + CONFORMANCE, + GET_OPPORTUNITY_SEARCH_RECORD, + GET_ORDER, + LIST_OPPORTUNITY_SEARCH_RECORDS, + LIST_ORDER_STATUSES, + LIST_ORDERS, + LIST_PRODUCTS, + ROOT, +) logger = logging.getLogger(__name__) @@ -84,7 +94,7 @@ def __init__( "/", self.get_root, methods=["GET"], - name=f"{self.name}:root", + name=f"{self.name}:{ROOT}", tags=["Root"], ) @@ -92,7 +102,7 @@ def __init__( "/conformance", self.get_conformance, methods=["GET"], - name=f"{self.name}:conformance", + name=f"{self.name}:{CONFORMANCE}", tags=["Conformance"], ) @@ -100,7 +110,7 @@ def __init__( "/products", self.get_products, methods=["GET"], - name=f"{self.name}:list-products", + name=f"{self.name}:{LIST_PRODUCTS}", tags=["Products"], ) @@ -108,7 +118,7 @@ def __init__( "/orders", self.get_orders, methods=["GET"], - name=f"{self.name}:list-orders", + name=f"{self.name}:{LIST_ORDERS}", response_class=GeoJSONResponse, tags=["Orders"], ) @@ -117,7 +127,7 @@ def __init__( "/orders/{order_id}", self.get_order, methods=["GET"], - name=f"{self.name}:get-order", + name=f"{self.name}:{GET_ORDER}", response_class=GeoJSONResponse, tags=["Orders"], ) @@ -126,7 +136,7 @@ def __init__( "/orders/{order_id}/statuses", self.get_order_statuses, methods=["GET"], - name=f"{self.name}:list-order-statuses", + name=f"{self.name}:{LIST_ORDER_STATUSES}", tags=["Orders"], ) @@ -135,7 +145,7 @@ def __init__( "/searches/opportunities", self.get_opportunity_search_records, methods=["GET"], - name=f"{self.name}:list-opportunity-search-records", + name=f"{self.name}:{LIST_OPPORTUNITY_SEARCH_RECORDS}", summary="List all Opportunity Search Records", tags=["Opportunities"], ) @@ -144,7 +154,7 @@ def __init__( "/searches/opportunities/{search_record_id}", self.get_opportunity_search_record, methods=["GET"], - name=f"{self.name}:get-opportunity-search-record", + name=f"{self.name}:{GET_OPPORTUNITY_SEARCH_RECORD}", summary="Get an Opportunity Search Record by ID", tags=["Opportunities"], ) @@ -152,7 +162,7 @@ def __init__( def get_root(self, request: Request) -> RootResponse: links = [ Link( - href=str(request.url_for(f"{self.name}:root")), + href=str(request.url_for(f"{self.name}:{ROOT}")), rel="self", type=TYPE_JSON, ), @@ -167,17 +177,17 @@ def get_root(self, request: Request) -> RootResponse: type="text/html", ), Link( - href=str(request.url_for(f"{self.name}:conformance")), + href=str(request.url_for(f"{self.name}:{CONFORMANCE}")), rel="conformance", type=TYPE_JSON, ), Link( - href=str(request.url_for(f"{self.name}:list-products")), + href=str(request.url_for(f"{self.name}:{LIST_PRODUCTS}")), rel="products", type=TYPE_JSON, ), Link( - href=str(request.url_for(f"{self.name}:list-orders")), + href=str(request.url_for(f"{self.name}:{LIST_ORDERS}")), rel="orders", type=TYPE_GEOJSON, ), @@ -187,7 +197,9 @@ def get_root(self, request: Request) -> RootResponse: links.append( Link( href=str( - request.url_for(f"{self.name}:list-opportunity-search-records") + request.url_for( + f"{self.name}:{LIST_OPPORTUNITY_SEARCH_RECORDS}" + ) ), rel="opportunity-search-records", type=TYPE_JSON, @@ -220,7 +232,7 @@ def get_products( ids = self.product_ids[start:end] links = [ Link( - href=str(request.url_for(f"{self.name}:list-products")), + href=str(request.url_for(f"{self.name}:{LIST_PRODUCTS}")), rel="self", type=TYPE_JSON, ), @@ -327,10 +339,10 @@ def add_product(self, product: Product, *args, **kwargs) -> None: self.product_ids = [*self.product_routers.keys()] def generate_order_href(self, request: Request, order_id: str) -> URL: - return request.url_for(f"{self.name}:get-order", order_id=order_id) + return request.url_for(f"{self.name}:{GET_ORDER}", order_id=order_id) def generate_order_statuses_href(self, request: Request, order_id: str) -> URL: - return request.url_for(f"{self.name}:list-order-statuses", order_id=order_id) + return request.url_for(f"{self.name}:{LIST_ORDER_STATUSES}", order_id=order_id) def order_links(self, order: Order, request: Request) -> list[Link]: return [ @@ -350,7 +362,7 @@ def order_statuses_link(self, request: Request, order_id: str): return Link( href=str( request.url_for( - f"{self.name}:list-order-statuses", + f"{self.name}:{LIST_ORDER_STATUSES}", order_id=order_id, ) ), @@ -428,7 +440,7 @@ def generate_opportunity_search_record_href( self, request: Request, search_record_id: str ) -> URL: return request.url_for( - f"{self.name}:get-opportunity-search-record", + f"{self.name}:{GET_OPPORTUNITY_SEARCH_RECORD}", search_record_id=search_record_id, ) diff --git a/src/stapi_fastapi/routers/route_names.py b/src/stapi_fastapi/routers/route_names.py new file mode 100644 index 0000000..292bc54 --- /dev/null +++ b/src/stapi_fastapi/routers/route_names.py @@ -0,0 +1,22 @@ +# Root +ROOT = "root" +CONFORMANCE = "conformance" + +# Product +LIST_PRODUCTS = "list-products" +LIST_PRODUCTS = "list-products" +GET_PRODUCT = "get-product" +GET_CONSTRAINTS = "get-constraints" +GET_ORDER_PARAMETERS = "get-order-parameters" + +# Opportunity +LIST_OPPORTUNITY_SEARCH_RECORDS = "list-opportunity-search-records" +GET_OPPORTUNITY_SEARCH_RECORD = "get-opportunity-search-record" +SEARCH_OPPORTUNITIES = "search-opportunities" +GET_OPPORTUNITY_COLLECTION = "get-opportunity-collection" + +# Order +LIST_ORDERS = "list-orders" +GET_ORDER = "get-order" +LIST_ORDER_STATUSES = "list-order-statuses" +CREATE_ORDER = "create-order"