Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat subclass router issue 73 #80

Merged
merged 30 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
494f952
initial implementation of router composition for products
austin-sidekick Oct 9, 2024
c0d7a23
make OpportunityProperties and Opportunity models generics
austin-sidekick Oct 9, 2024
3139ec4
overhaul the main router as APIRouter, product router not a factory, …
austin-sidekick Oct 9, 2024
2d2ce71
Merge branch 'main' into feat-subclass-router-issue-73
austin-sidekick Oct 9, 2024
1ac2cc8
resolve merge conflicts
austin-sidekick Oct 9, 2024
73b9c03
sort out the conftest a bit more
austin-sidekick Oct 9, 2024
6fa822b
the /products/{product_id}/opportunities endpoint is accessible
austin-sidekick Oct 9, 2024
1c4126f
Merge commit '3d8e618cc1419d42805048e0dd69a6efa19d1a2b' into mp/feat-…
parksjr Oct 23, 2024
571f86a
adds product router and backend
parksjr Oct 23, 2024
91f8b65
wip: splits backend and router for root and product.
parksjr Oct 30, 2024
1adf323
Merge branch 'mp/feat-subclass-router-issue-73' into feat-subclass-ro…
parksjr Oct 30, 2024
db815d1
adds test for get_constraints
parksjr Oct 31, 2024
f79a589
remove unused modules
jkeifer Oct 31, 2024
6e0285d
various cleanup and refinements
jkeifer Oct 31, 2024
c9e3aa3
no longer a problem with the root router / route
jkeifer Nov 1, 2024
6a2f4d7
rename tests/backend{,s}.py
jkeifer Nov 1, 2024
fba52ed
get tests passing
jkeifer Nov 1, 2024
1bde9f5
more cleanup
jkeifer Nov 1, 2024
375c51c
fixes StapiExceptions
parksjr Nov 1, 2024
07b2597
removes dupe HTTPException in models
parksjr Nov 1, 2024
9355fdf
passes product router instead of product to backend
parksjr Nov 1, 2024
f965f99
Adds OrderCollection type and returns for get_orders
parksjr Nov 1, 2024
18c7be5
type the opporunities to the product constraints model
jkeifer Nov 1, 2024
19fdf34
upgrade fastapi
jkeifer Nov 1, 2024
2939a3e
fix GeoJSONResponse model bug per openapi docs
jkeifer Nov 1, 2024
6919ab5
minimal test server to allow accessing openapi docs
jkeifer Nov 1, 2024
671d66a
remove nulls from serialized links
jkeifer Nov 1, 2024
f5f3dd2
tests should fail not warn on spec incompatibility
jkeifer Nov 1, 2024
5948e22
add nocover to protocol implementations
jkeifer Nov 1, 2024
ba130c4
standardize links on models
jkeifer Nov 1, 2024
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: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

215 changes: 0 additions & 215 deletions src/stapi_fastapi/api.py

This file was deleted.

1 change: 0 additions & 1 deletion src/stapi_fastapi/backend.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from typing import Protocol

from fastapi import Request

from stapi_fastapi.models.opportunity import Opportunity, OpportunityRequest
Expand Down
4 changes: 4 additions & 0 deletions src/stapi_fastapi/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from typing import Any, Mapping
from fastapi import HTTPException

class StapiException(HTTPException):
jkeifer marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, status_code: int, detail: str) -> None:
super().__init__(status_code, detail)

class ConstraintsException(Exception):
detail: Mapping[str, Any] | None
Expand Down
138 changes: 138 additions & 0 deletions src/stapi_fastapi/main_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from typing import Self, Optional
from fastapi import APIRouter, HTTPException, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse

from stapi_fastapi.backend import StapiBackend
from stapi_fastapi.constants import TYPE_GEOJSON, TYPE_JSON
from stapi_fastapi.exceptions import StapiException, ConstraintsException, NotFoundException
from stapi_fastapi.models.opportunity import (
OpportunityCollection,
OpportunityRequest,
)
from stapi_fastapi.models.order import Order
from stapi_fastapi.models.product import Product, ProductsCollection
from stapi_fastapi.models.root import RootResponse
from stapi_fastapi.models.shared import HTTPException as HTTPExceptionModel
from stapi_fastapi.models.shared import Link
from stapi_fastapi.products_router import ProductRouter

"""
/products/{component router} # router for each product added to main router
/orders # list all orders
"""
class MainRouter(APIRouter):

def __init__(
self: Self,
backend: StapiBackend,
name: str = "main",
openapi_endpoint_name: str = "openapi",
docs_endpoint_name: str = "swagger_ui_html",
*args,
**kwargs,
):
super().__init__(*args, **kwargs)
self.backend = backend
self.name = name
self.openapi_endpoint_name = openapi_endpoint_name
self.docs_endpoint_name = docs_endpoint_name

self.product_routers: dict[str, ProductRouter] = {}

self.add_api_route(
"/",
self.root,
methods=["GET"],
name=f"{self.name}:root",
tags=["Root"],
)

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

self.add_api_route(
"/orders/{order_id}",
self.get_order,
methods=["GET"],
name=f"{self.name}:get-order",
tags=["Orders"],
)

def root(self, request: Request) -> RootResponse:
return RootResponse(
links=[
Link(
href=str(request.url_for(f"{self.name}:root")),
rel="self",
type=TYPE_JSON,
),
Link(
href=str(request.url_for(f"{self.name}:list-products")),
rel="products",
type=TYPE_JSON,
),
Link(
href=str(request.url_for(self.openapi_endpoint_name)),
rel="service-description",
type=TYPE_JSON,
),
Link(
href=str(request.url_for(self.docs_endpoint_name)),
rel="service-docs",
type="text/html",
),
]
)

def products(self, request: Request) -> ProductsCollection:
products = self.backend.products(request)
for product in products:
product.links.append(
Link(
href=str(
request.url_for(
f"{self.name}:get-product", product_id=product.id
)
),
rel="self",
type=TYPE_JSON,
)
)
return ProductsCollection(
products=products,
links=[
Link(
href=str(request.url_for(f"{self.name}:list-products")),
rel="self",
type=TYPE_JSON,
)
],
)

async def get_order(self, order_id: str, request: Request) -> Order:
"""
Get details for order with `order_id`.
"""
try:
order = await self.backend.get_order(order_id, request)
except NotFoundException as exc:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="not found") from exc

order.links.append(Link(href=str(request.url), rel="self", type=TYPE_GEOJSON))

return JSONResponse(
jsonable_encoder(order, exclude_unset=True),
status.HTTP_200_OK,
media_type=TYPE_GEOJSON,
)

def add_product_router(self, product_router: ProductRouter):
# Give the include a prefix from the product router
self.include_router(product_router, prefix=product_router.product.id)
self.product_routers[product_router.product.id] = product_router
Loading
Loading