Skip to content

Commit 6a42083

Browse files
philvarnerPhil Varner
andauthored
refactor method url_for in ProductRouter and RootRouter (#120)
## What I'm changing - refactor use of Request#url_for into a static method in RootRouter and ProductRouter ## How I did it I created a new static method that all of the generated links use. The intention of this is to allow child class implementations for projects that use this library to override this if they want to change the urls. A key use case for this is when the API is being proxied from another API, and there is the desire for the links to be correct wrt the URLs provided by the reverse proxy. ## Checklist - [X] Tests pass: `uv run pytest` - [X] Checks pass: `uv run pre-commit run --all-files` - [X] CHANGELOG is updated (if necessary) --------- Co-authored-by: Phil Varner <phil.varner@orbitalsidekick.com>
1 parent 9afccdc commit 6a42083

File tree

3 files changed

+46
-63
lines changed

3 files changed

+46
-63
lines changed

stapi-fastapi/src/stapi_fastapi/routers/product_router.py

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -199,50 +199,34 @@ async def _create_order(
199199
tags=["Products"],
200200
)
201201

202+
@staticmethod
203+
def url_for(request: Request, name: str, /, **path_params: Any) -> str:
204+
return str(request.url_for(name, **path_params))
205+
202206
def get_product(self, request: Request) -> ProductPydantic:
203207
links = [
204208
Link(
205-
href=str(
206-
request.url_for(
207-
f"{self.root_router.name}:{self.product.id}:{GET_PRODUCT}",
208-
),
209-
),
209+
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{GET_PRODUCT}"),
210210
rel="self",
211211
type=TYPE_JSON,
212212
),
213213
Link(
214-
href=str(
215-
request.url_for(
216-
f"{self.root_router.name}:{self.product.id}:{CONFORMANCE}",
217-
),
218-
),
214+
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{CONFORMANCE}"),
219215
rel="conformance",
220216
type=TYPE_JSON,
221217
),
222218
Link(
223-
href=str(
224-
request.url_for(
225-
f"{self.root_router.name}:{self.product.id}:{GET_QUERYABLES}",
226-
),
227-
),
219+
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{GET_QUERYABLES}"),
228220
rel="queryables",
229221
type=TYPE_JSON,
230222
),
231223
Link(
232-
href=str(
233-
request.url_for(
234-
f"{self.root_router.name}:{self.product.id}:{GET_ORDER_PARAMETERS}",
235-
),
236-
),
224+
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{GET_ORDER_PARAMETERS}"),
237225
rel="order-parameters",
238226
type=TYPE_JSON,
239227
),
240228
Link(
241-
href=str(
242-
request.url_for(
243-
f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}",
244-
),
245-
),
229+
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}"),
246230
rel="create-order",
247231
type=TYPE_JSON,
248232
method="POST",
@@ -254,11 +238,7 @@ def get_product(self, request: Request) -> ProductPydantic:
254238
):
255239
links.append(
256240
Link(
257-
href=str(
258-
request.url_for(
259-
f"{self.root_router.name}:{self.product.id}:{SEARCH_OPPORTUNITIES}",
260-
),
261-
),
241+
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{SEARCH_OPPORTUNITIES}"),
262242
rel="opportunities",
263243
type=TYPE_JSON,
264244
),
@@ -420,11 +400,7 @@ async def create_order(self, payload: OrderPayload, request: Request, response:
420400

421401
def order_link(self, request: Request, opp_req: OpportunityPayload) -> Link:
422402
return Link(
423-
href=str(
424-
request.url_for(
425-
f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}",
426-
),
427-
),
403+
href=self.url_for(request, f"{self.root_router.name}:{self.product.id}:{CREATE_ORDER}"),
428404
rel="create-order",
429405
type=TYPE_JSON,
430406
method="POST",
@@ -456,11 +432,10 @@ async def get_opportunity_collection(
456432
case Success(Some(opportunity_collection)):
457433
opportunity_collection.links.append(
458434
Link(
459-
href=str(
460-
request.url_for(
461-
f"{self.root_router.name}:{self.product.id}:{GET_OPPORTUNITY_COLLECTION}",
462-
opportunity_collection_id=opportunity_collection_id,
463-
),
435+
href=self.url_for(
436+
request,
437+
f"{self.root_router.name}:{self.product.id}:{GET_OPPORTUNITY_COLLECTION}",
438+
opportunity_collection_id=opportunity_collection_id,
464439
),
465440
rel="self",
466441
type=TYPE_JSON,

stapi-fastapi/src/stapi_fastapi/routers/root_router.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from typing import Any
44

55
from fastapi import APIRouter, HTTPException, Request, status
6-
from fastapi.datastructures import URL
76
from returns.maybe import Maybe, Some
87
from returns.result import Failure, Success
98
from stapi_pydantic import (
@@ -171,35 +170,39 @@ def __init__(
171170

172171
self.conformances = list(_conformances)
173172

173+
@staticmethod
174+
def url_for(request: Request, name: str, /, **path_params: Any) -> str:
175+
return str(request.url_for(name, **path_params))
176+
174177
def get_root(self, request: Request) -> RootResponse:
175178
links = [
176179
Link(
177-
href=str(request.url_for(f"{self.name}:{ROOT}")),
180+
href=self.url_for(request, f"{self.name}:{ROOT}"),
178181
rel="self",
179182
type=TYPE_JSON,
180183
),
181184
Link(
182-
href=str(request.url_for(self.openapi_endpoint_name)),
185+
href=self.url_for(request, self.openapi_endpoint_name),
183186
rel="service-description",
184187
type=TYPE_JSON,
185188
),
186189
Link(
187-
href=str(request.url_for(self.docs_endpoint_name)),
190+
href=self.url_for(request, self.docs_endpoint_name),
188191
rel="service-docs",
189192
type="text/html",
190193
),
191194
Link(
192-
href=str(request.url_for(f"{self.name}:{CONFORMANCE}")),
195+
href=self.url_for(request, f"{self.name}:{CONFORMANCE}"),
193196
rel="conformance",
194197
type=TYPE_JSON,
195198
),
196199
Link(
197-
href=str(request.url_for(f"{self.name}:{LIST_PRODUCTS}")),
200+
href=self.url_for(request, f"{self.name}:{LIST_PRODUCTS}"),
198201
rel="products",
199202
type=TYPE_JSON,
200203
),
201204
Link(
202-
href=str(request.url_for(f"{self.name}:{LIST_ORDERS}")),
205+
href=self.url_for(request, f"{self.name}:{LIST_ORDERS}"),
203206
rel="orders",
204207
type=TYPE_GEOJSON,
205208
),
@@ -208,7 +211,7 @@ def get_root(self, request: Request) -> RootResponse:
208211
if self.supports_async_opportunity_search:
209212
links.append(
210213
Link(
211-
href=str(request.url_for(f"{self.name}:{LIST_OPPORTUNITY_SEARCH_RECORDS}")),
214+
href=self.url_for(request, f"{self.name}:{LIST_OPPORTUNITY_SEARCH_RECORDS}"),
212215
rel="opportunity-search-records",
213216
type=TYPE_JSON,
214217
),
@@ -236,7 +239,7 @@ def get_products(self, request: Request, next: str | None = None, limit: int = 1
236239
ids = self.product_ids[start:end]
237240
links = [
238241
Link(
239-
href=str(request.url_for(f"{self.name}:{LIST_PRODUCTS}")),
242+
href=self.url_for(request, f"{self.name}:{LIST_PRODUCTS}"),
240243
rel="self",
241244
type=TYPE_JSON,
242245
),
@@ -339,33 +342,32 @@ def add_product(self, product: Product, *args: Any, **kwargs: Any) -> None:
339342
self.product_routers[product.id] = product_router
340343
self.product_ids = [*self.product_routers.keys()]
341344

342-
def generate_order_href(self, request: Request, order_id: str) -> URL:
343-
return request.url_for(f"{self.name}:{GET_ORDER}", order_id=order_id)
345+
def generate_order_href(self, request: Request, order_id: str) -> str:
346+
return self.url_for(request, f"{self.name}:{GET_ORDER}", order_id=order_id)
344347

345-
def generate_order_statuses_href(self, request: Request, order_id: str) -> URL:
346-
return request.url_for(f"{self.name}:{LIST_ORDER_STATUSES}", order_id=order_id)
348+
def generate_order_statuses_href(self, request: Request, order_id: str) -> str:
349+
return self.url_for(request, f"{self.name}:{LIST_ORDER_STATUSES}", order_id=order_id)
347350

348351
def order_links(self, order: Order[OrderStatus], request: Request) -> list[Link]:
349352
return [
350353
Link(
351-
href=str(self.generate_order_href(request, order.id)),
354+
href=self.generate_order_href(request, order.id),
352355
rel="self",
353356
type=TYPE_GEOJSON,
354357
),
355358
Link(
356-
href=str(self.generate_order_statuses_href(request, order.id)),
359+
href=self.generate_order_statuses_href(request, order.id),
357360
rel="monitor",
358361
type=TYPE_JSON,
359362
),
360363
]
361364

362365
def order_statuses_link(self, request: Request, order_id: str) -> Link:
363366
return Link(
364-
href=str(
365-
request.url_for(
366-
f"{self.name}:{LIST_ORDER_STATUSES}",
367-
order_id=order_id,
368-
)
367+
href=self.url_for(
368+
request,
369+
f"{self.name}:{LIST_ORDER_STATUSES}",
370+
order_id=order_id,
369371
),
370372
rel="self",
371373
type=TYPE_JSON,
@@ -453,8 +455,9 @@ async def get_opportunity_search_record_statuses(
453455
case _:
454456
raise AssertionError("Expected code to be unreachable")
455457

456-
def generate_opportunity_search_record_href(self, request: Request, search_record_id: str) -> URL:
457-
return request.url_for(
458+
def generate_opportunity_search_record_href(self, request: Request, search_record_id: str) -> str:
459+
return self.url_for(
460+
request,
458461
f"{self.name}:{GET_OPPORTUNITY_SEARCH_RECORD}",
459462
search_record_id=search_record_id,
460463
)
@@ -463,7 +466,7 @@ def opportunity_search_record_self_link(
463466
self, opportunity_search_record: OpportunitySearchRecord, request: Request
464467
) -> Link:
465468
return Link(
466-
href=str(self.generate_opportunity_search_record_href(request, opportunity_search_record.id)),
469+
href=self.generate_opportunity_search_record_href(request, opportunity_search_record.id),
467470
rel="self",
468471
type=TYPE_JSON,
469472
)

stapi-pydantic/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- ProductRouter and RootRouter now have a method `url_for` that makes the link generation code slightly cleaner and
14+
allows for overridding in child classes, to support proxy rewrite of the links.
15+
1116
## [0.0.4] - 2025-07-17
1217

1318
### Added

0 commit comments

Comments
 (0)