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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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`
34 changes: 21 additions & 13 deletions src/stapi_fastapi/routers/product_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"],
Expand All @@ -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"],
Expand All @@ -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"],
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
),
),
Expand Down
48 changes: 30 additions & 18 deletions src/stapi_fastapi/routers/root_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -84,31 +94,31 @@ def __init__(
"/",
self.get_root,
methods=["GET"],
name=f"{self.name}:root",
name=f"{self.name}:{ROOT}",
tags=["Root"],
)

self.add_api_route(
"/conformance",
self.get_conformance,
methods=["GET"],
name=f"{self.name}:conformance",
name=f"{self.name}:{CONFORMANCE}",
tags=["Conformance"],
)

self.add_api_route(
"/products",
self.get_products,
methods=["GET"],
name=f"{self.name}:list-products",
name=f"{self.name}:{LIST_PRODUCTS}",
tags=["Products"],
)

self.add_api_route(
"/orders",
self.get_orders,
methods=["GET"],
name=f"{self.name}:list-orders",
name=f"{self.name}:{LIST_ORDERS}",
response_class=GeoJSONResponse,
tags=["Orders"],
)
Expand All @@ -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"],
)
Expand All @@ -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"],
)

Expand All @@ -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"],
)
Expand All @@ -144,15 +154,15 @@ 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"],
)

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,
),
Expand All @@ -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,
),
Expand All @@ -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,
Expand Down Expand Up @@ -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,
),
Expand Down Expand Up @@ -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 [
Expand All @@ -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,
)
),
Expand Down Expand Up @@ -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,
)

Expand Down
22 changes: 22 additions & 0 deletions src/stapi_fastapi/routers/route_names.py
Original file line number Diff line number Diff line change
@@ -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"