Skip to content
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
55 changes: 15 additions & 40 deletions stapi-fastapi/src/stapi_fastapi/routers/product_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,50 +199,34 @@ async def _create_order(
tags=["Products"],
)

@staticmethod
def url_for(request: Request, name: str, /, **path_params: Any) -> str:
return str(request.url_for(name, **path_params))

def get_product(self, request: Request) -> ProductPydantic:
links = [
Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{GET_PRODUCT}",
),
),
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{GET_PRODUCT}"),
rel="self",
type=TYPE_JSON,
),
Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{CONFORMANCE}",
),
),
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{CONFORMANCE}"),
rel="conformance",
type=TYPE_JSON,
),
Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{GET_QUERYABLES}",
),
),
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{GET_QUERYABLES}"),
rel="queryables",
type=TYPE_JSON,
),
Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{GET_ORDER_PARAMETERS}",
),
),
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{GET_ORDER_PARAMETERS}"),
rel="order-parameters",
type=TYPE_JSON,
),
Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}",
),
),
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}"),
rel="create-order",
type=TYPE_JSON,
method="POST",
Expand All @@ -254,11 +238,7 @@ def get_product(self, request: Request) -> ProductPydantic:
):
links.append(
Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{SEARCH_OPPORTUNITIES}",
),
),
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{SEARCH_OPPORTUNITIES}"),
rel="opportunities",
type=TYPE_JSON,
),
Expand Down Expand Up @@ -420,11 +400,7 @@ async def create_order(self, payload: OrderPayload, request: Request, response:

def order_link(self, request: Request, opp_req: OpportunityPayload) -> Link:
return Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}",
),
),
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}"),
rel="create-order",
type=TYPE_JSON,
method="POST",
Expand Down Expand Up @@ -456,11 +432,10 @@ async def get_opportunity_collection(
case Success(Some(opportunity_collection)):
opportunity_collection.links.append(
Link(
href=str(
request.url_for(
f"{self.root_router.name}:{self.product.id}:{GET_OPPORTUNITY_COLLECTION}",
opportunity_collection_id=opportunity_collection_id,
),
href=self.url_for(
request,
f"{self.root_router.name}:{self.product.id}:{GET_OPPORTUNITY_COLLECTION}",
opportunity_collection_id=opportunity_collection_id,
),
rel="self",
type=TYPE_JSON,
Expand Down
49 changes: 26 additions & 23 deletions stapi-fastapi/src/stapi_fastapi/routers/root_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from typing import Any

from fastapi import APIRouter, HTTPException, Request, status
from fastapi.datastructures import URL
from returns.maybe import Maybe, Some
from returns.result import Failure, Success
from stapi_pydantic import (
Expand Down Expand Up @@ -171,35 +170,39 @@ def __init__(

self.conformances = list(_conformances)

@staticmethod
def url_for(request: Request, name: str, /, **path_params: Any) -> str:
return str(request.url_for(name, **path_params))

def get_root(self, request: Request) -> RootResponse:
links = [
Link(
href=str(request.url_for(f"{self.name}:{ROOT}")),
href=self.url_for(request, f"{self.name}:{ROOT}"),
rel="self",
type=TYPE_JSON,
),
Link(
href=str(request.url_for(self.openapi_endpoint_name)),
href=self.url_for(request, self.openapi_endpoint_name),
rel="service-description",
type=TYPE_JSON,
),
Link(
href=str(request.url_for(self.docs_endpoint_name)),
href=self.url_for(request, self.docs_endpoint_name),
rel="service-docs",
type="text/html",
),
Link(
href=str(request.url_for(f"{self.name}:{CONFORMANCE}")),
href=self.url_for(request, f"{self.name}:{CONFORMANCE}"),
rel="conformance",
type=TYPE_JSON,
),
Link(
href=str(request.url_for(f"{self.name}:{LIST_PRODUCTS}")),
href=self.url_for(request, f"{self.name}:{LIST_PRODUCTS}"),
rel="products",
type=TYPE_JSON,
),
Link(
href=str(request.url_for(f"{self.name}:{LIST_ORDERS}")),
href=self.url_for(request, f"{self.name}:{LIST_ORDERS}"),
rel="orders",
type=TYPE_GEOJSON,
),
Expand All @@ -208,7 +211,7 @@ def get_root(self, request: Request) -> RootResponse:
if self.supports_async_opportunity_search:
links.append(
Link(
href=str(request.url_for(f"{self.name}:{LIST_OPPORTUNITY_SEARCH_RECORDS}")),
href=self.url_for(request, f"{self.name}:{LIST_OPPORTUNITY_SEARCH_RECORDS}"),
rel="opportunity-search-records",
type=TYPE_JSON,
),
Expand Down Expand Up @@ -236,7 +239,7 @@ def get_products(self, request: Request, next: str | None = None, limit: int = 1
ids = self.product_ids[start:end]
links = [
Link(
href=str(request.url_for(f"{self.name}:{LIST_PRODUCTS}")),
href=self.url_for(request, f"{self.name}:{LIST_PRODUCTS}"),
rel="self",
type=TYPE_JSON,
),
Expand Down Expand Up @@ -339,33 +342,32 @@ def add_product(self, product: Product, *args: Any, **kwargs: Any) -> None:
self.product_routers[product.id] = product_router
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)
def generate_order_href(self, request: Request, order_id: str) -> str:
return self.url_for(request, 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)
def generate_order_statuses_href(self, request: Request, order_id: str) -> str:
return self.url_for(request, f"{self.name}:{LIST_ORDER_STATUSES}", order_id=order_id)

def order_links(self, order: Order[OrderStatus], request: Request) -> list[Link]:
return [
Link(
href=str(self.generate_order_href(request, order.id)),
href=self.generate_order_href(request, order.id),
rel="self",
type=TYPE_GEOJSON,
),
Link(
href=str(self.generate_order_statuses_href(request, order.id)),
href=self.generate_order_statuses_href(request, order.id),
rel="monitor",
type=TYPE_JSON,
),
]

def order_statuses_link(self, request: Request, order_id: str) -> Link:
return Link(
href=str(
request.url_for(
f"{self.name}:{LIST_ORDER_STATUSES}",
order_id=order_id,
)
href=self.url_for(
request,
f"{self.name}:{LIST_ORDER_STATUSES}",
order_id=order_id,
),
rel="self",
type=TYPE_JSON,
Expand Down Expand Up @@ -453,8 +455,9 @@ async def get_opportunity_search_record_statuses(
case _:
raise AssertionError("Expected code to be unreachable")

def generate_opportunity_search_record_href(self, request: Request, search_record_id: str) -> URL:
return request.url_for(
def generate_opportunity_search_record_href(self, request: Request, search_record_id: str) -> str:
return self.url_for(
request,
f"{self.name}:{GET_OPPORTUNITY_SEARCH_RECORD}",
search_record_id=search_record_id,
)
Expand All @@ -463,7 +466,7 @@ def opportunity_search_record_self_link(
self, opportunity_search_record: OpportunitySearchRecord, request: Request
) -> Link:
return Link(
href=str(self.generate_opportunity_search_record_href(request, opportunity_search_record.id)),
href=self.generate_opportunity_search_record_href(request, opportunity_search_record.id),
rel="self",
type=TYPE_JSON,
)
Expand Down
5 changes: 5 additions & 0 deletions stapi-pydantic/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

## [Unreleased]

### Added

- ProductRouter and RootRouter now have a method `url_for` that makes the link generation code slightly cleaner and
allows for overridding in child classes, to support proxy rewrite of the links.

## [0.0.4] - 2025-07-17

### Added
Expand Down