Skip to content

Commit d97be24

Browse files
author
Phil Varner
committed
refactor url_for
1 parent 6962c1c commit d97be24

File tree

5 files changed

+43
-34
lines changed

5 files changed

+43
-34
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,20 @@ repos:
77
language: system
88
types: [python]
99
pass_filenames: false
10-
verbose: true
1110

1211
- id: ruff-format
1312
name: Format with ruff
1413
entry: uv run ruff format
1514
language: system
1615
types: [python]
1716
pass_filenames: false
18-
verbose: true
1917

2018
- id: mypy
2119
name: Check typing with mypy
2220
entry: uv run mypy
2321
language: system
2422
types: [python]
2523
pass_filenames: false
26-
verbose: true
2724

2825
- id: pymarkdown
2926
name: Markdownlint
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import Any
2+
3+
from fastapi import (
4+
APIRouter,
5+
Request,
6+
)
7+
from starlette.datastructures import URL
8+
9+
10+
class StapiFastapiBaseRouter(APIRouter):
11+
@staticmethod
12+
def url_for(request: Request, name: str, /, **path_params: Any) -> URL:
13+
return request.url_for(name, **path_params)

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from typing import TYPE_CHECKING, Any
66

77
from fastapi import (
8-
APIRouter,
98
Depends,
109
Header,
1110
HTTPException,
@@ -38,6 +37,7 @@
3837
from stapi_fastapi.errors import NotFoundError, QueryablesError
3938
from stapi_fastapi.models.product import Product
4039
from stapi_fastapi.responses import GeoJSONResponse
40+
from stapi_fastapi.routers.base import StapiFastapiBaseRouter
4141
from stapi_fastapi.routers.route_names import (
4242
CONFORMANCE,
4343
CREATE_ORDER,
@@ -84,7 +84,7 @@ def build_conformances(product: Product, root_router: RootRouter) -> list[str]:
8484
return list(conformances)
8585

8686

87-
class ProductRouter(APIRouter):
87+
class ProductRouter(StapiFastapiBaseRouter):
8888
# FIXME ruff is complaining that the init is too complex
8989
def __init__( # noqa
9090
self,
@@ -199,10 +199,6 @@ 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-
206202
def get_product(self, request: Request) -> ProductPydantic:
207203
links = [
208204
Link(
@@ -411,7 +407,7 @@ def pagination_link(self, request: Request, opp_req: OpportunityPayload, paginat
411407
body = opp_req.body()
412408
body["next"] = pagination_token
413409
return Link(
414-
href=str(request.url),
410+
href=request.url,
415411
rel="next",
416412
type=TYPE_JSON,
417413
method="POST",

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

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import traceback
33
from typing import Any
44

5-
from fastapi import APIRouter, HTTPException, Request, status
5+
from fastapi import HTTPException, Request, status
66
from returns.maybe import Maybe, Some
77
from returns.result import Failure, Success
88
from stapi_pydantic import (
@@ -18,6 +18,7 @@
1818
ProductsCollection,
1919
RootResponse,
2020
)
21+
from starlette.datastructures import URL
2122

2223
from stapi_fastapi.backends.root_backend import (
2324
GetOpportunitySearchRecord,
@@ -32,6 +33,7 @@
3233
from stapi_fastapi.errors import NotFoundError
3334
from stapi_fastapi.models.product import Product
3435
from stapi_fastapi.responses import GeoJSONResponse
36+
from stapi_fastapi.routers.base import StapiFastapiBaseRouter
3537
from stapi_fastapi.routers.product_router import ProductRouter
3638
from stapi_fastapi.routers.route_names import (
3739
CONFORMANCE,
@@ -48,7 +50,7 @@
4850
logger = logging.getLogger(__name__)
4951

5052

51-
class RootRouter(APIRouter):
53+
class RootRouter(StapiFastapiBaseRouter):
5254
def __init__(
5355
self,
5456
get_orders: GetOrders,
@@ -170,10 +172,6 @@ def __init__(
170172

171173
self.conformances = list(_conformances)
172174

173-
@staticmethod
174-
def url_for(request: Request, name: str, /, **path_params: Any) -> str:
175-
return str(request.url_for(name, **path_params))
176-
177175
def get_root(self, request: Request) -> RootResponse:
178176
links = [
179177
Link(
@@ -245,7 +243,7 @@ def get_products(self, request: Request, next: str | None = None, limit: int = 1
245243
),
246244
]
247245
if end > 0 and end < len(self.product_ids):
248-
links.append(self.pagination_link(request, self.product_ids[end], limit))
246+
links.append(self.pagination_link(request, f"{self.name}:{LIST_PRODUCTS}", self.product_ids[end], limit))
249247
return ProductsCollection(
250248
products=[self.product_routers[product_id].get_product(request) for product_id in ids],
251249
links=links,
@@ -261,8 +259,8 @@ async def get_orders( # noqa: C901
261259
for order in orders:
262260
order.links.extend(self.order_links(order, request))
263261
match maybe_pagination_token:
264-
case Some(x):
265-
links.append(self.pagination_link(request, x, limit))
262+
case Some(next_):
263+
links.append(self.pagination_link(request, f"{self.name}:{LIST_ORDERS}", next_, limit))
266264
case Maybe.empty:
267265
pass
268266
match maybe_orders_count:
@@ -325,8 +323,12 @@ async def get_order_statuses(
325323
case Success(Some((statuses, maybe_pagination_token))):
326324
links.append(self.order_statuses_link(request, order_id))
327325
match maybe_pagination_token:
328-
case Some(x):
329-
links.append(self.pagination_link(request, x, limit))
326+
case Some(next_):
327+
links.append(
328+
self.pagination_link(
329+
request, f"{self.name}:{LIST_ORDER_STATUSES}", next_, limit, order_id=order_id
330+
)
331+
)
330332
case Maybe.empty:
331333
pass
332334
case Success(Maybe.empty):
@@ -353,10 +355,10 @@ def add_product(self, product: Product, *args: Any, **kwargs: Any) -> None:
353355
self.product_routers[product.id] = product_router
354356
self.product_ids = [*self.product_routers.keys()]
355357

356-
def generate_order_href(self, request: Request, order_id: str) -> str:
358+
def generate_order_href(self, request: Request, order_id: str) -> URL:
357359
return self.url_for(request, f"{self.name}:{GET_ORDER}", order_id=order_id)
358360

359-
def generate_order_statuses_href(self, request: Request, order_id: str) -> str:
361+
def generate_order_statuses_href(self, request: Request, order_id: str) -> URL:
360362
return self.url_for(request, f"{self.name}:{LIST_ORDER_STATUSES}", order_id=order_id)
361363

362364
def order_links(self, order: Order[OrderStatus], request: Request) -> list[Link]:
@@ -384,13 +386,9 @@ def order_statuses_link(self, request: Request, order_id: str) -> Link:
384386
type=TYPE_JSON,
385387
)
386388

387-
def pagination_link(self, request: Request, pagination_token: str, limit: int) -> Link:
388-
href = str(request.url.include_query_params(next=pagination_token, limit=limit)).replace(
389-
str(request.url_for(f"{self.name}:{ROOT}")), self.url_for(request, f"{self.name}:{ROOT}"), 1
390-
)
391-
389+
def pagination_link(self, request: Request, name: str, pagination_token: str, limit: int, **kwargs: Any) -> Link:
392390
return Link(
393-
href=href,
391+
href=self.url_for(request, name, **kwargs).include_query_params(next=pagination_token, limit=limit),
394392
rel="next",
395393
type=TYPE_JSON,
396394
)
@@ -404,8 +402,12 @@ async def get_opportunity_search_records(
404402
for record in records:
405403
record.links.append(self.opportunity_search_record_self_link(record, request))
406404
match maybe_pagination_token:
407-
case Some(x):
408-
links.append(self.pagination_link(request, x, limit))
405+
case Some(next_):
406+
links.append(
407+
self.pagination_link(
408+
request, f"{self.name}:{LIST_OPPORTUNITY_SEARCH_RECORDS}", next_, limit
409+
)
410+
)
409411
case Maybe.empty:
410412
pass
411413
case Failure(ValueError()):
@@ -470,7 +472,7 @@ async def get_opportunity_search_record_statuses(
470472
case _:
471473
raise AssertionError("Expected code to be unreachable")
472474

473-
def generate_opportunity_search_record_href(self, request: Request, search_record_id: str) -> str:
475+
def generate_opportunity_search_record_href(self, request: Request, search_record_id: str) -> URL:
474476
return self.url_for(
475477
request,
476478
f"{self.name}:{GET_OPPORTUNITY_SEARCH_RECORD}",

stapi-pydantic/src/stapi_pydantic/shared.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
SerializerFunctionWrapHandler,
88
model_serializer,
99
)
10+
from starlette.datastructures import URL
1011

1112

1213
class Link(BaseModel):
@@ -22,8 +23,8 @@ class Link(BaseModel):
2223

2324
# redefining init is a hack to get str type to validate for `href`,
2425
# as str is ultimately coerced into an AnyUrl automatically anyway
25-
def __init__(self, href: AnyUrl | str, **kwargs: Any) -> None:
26-
super().__init__(href=href, **kwargs)
26+
def __init__(self, href: AnyUrl | URL | str, **kwargs: Any) -> None:
27+
super().__init__(href=str(href), **kwargs)
2728

2829
# overriding the default serialization to filter None field values from
2930
# dumped json

0 commit comments

Comments
 (0)