Skip to content
This repository was archived by the owner on Apr 2, 2025. It is now read-only.

Commit 8f3a996

Browse files
author
Phil Varner
committed
refactor types to support correct typing of incoming OrderRequest
1 parent 436a6b5 commit 8f3a996

File tree

6 files changed

+94
-45
lines changed

6 files changed

+94
-45
lines changed

bin/server.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from fastapi import FastAPI, Request
44
from stapi_fastapi.backends.product_backend import ProductBackend
5-
from stapi_fastapi.exceptions import ConstraintsException, NotFoundException
5+
from stapi_fastapi.exceptions import NotFoundException
66
from stapi_fastapi.models.opportunity import (
77
Opportunity,
88
OpportunityProperties,
@@ -62,23 +62,25 @@ async def create_order(
6262
"""
6363
Create a new order.
6464
"""
65-
allowed: bool = any(allowed == payload for allowed in self._allowed_payloads)
66-
allowed = True
67-
if allowed:
68-
order = Order(
69-
id=str(uuid4()),
70-
geometry=payload.geometry,
71-
properties={
72-
"filter": payload.filter,
73-
"datetime": payload.datetime,
74-
"product_id": product_router.product.id,
75-
**dict(payload.order_parameters),
65+
order = Order(
66+
id=str(uuid4()),
67+
geometry=payload.geometry,
68+
properties={
69+
"product_id": product_router.product.id,
70+
"datetime": payload.datetime,
71+
"geometry": payload.geometry,
72+
"filter": payload.filter,
73+
"order_parameters": payload.order_parameters,
74+
"opportunity_properties": {
75+
"datetime": "2024-01-29T12:00:00Z/2024-01-30T12:00:00Z",
76+
"off_nadir": 10,
7677
},
77-
links=[],
78-
)
79-
self._orders[order.id] = order
80-
return order
81-
raise ConstraintsException("not allowed")
78+
},
79+
links=[],
80+
)
81+
82+
self._orders[order.id] = order
83+
return order
8284

8385

8486
class TestSpotlightProperties(OpportunityProperties):

src/stapi_fastapi/models/order.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
1-
from typing import Literal, TypeVar, Generic, Any
1+
from typing import Generic, Literal, TypeVar
22

33
from geojson_pydantic import Feature, FeatureCollection
44
from geojson_pydantic.geometries import Geometry
55
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
66

7-
from stapi_fastapi.models.opportunity import OpportunityRequest, OpportunityProperties
7+
from stapi_fastapi.models.opportunity import OpportunityProperties
88
from stapi_fastapi.models.shared import Link
9+
from stapi_fastapi.types.datetime_interval import DatetimeInterval
10+
from stapi_fastapi.types.filter import CQL2Filter
911

1012

1113
class OrderParameters(BaseModel):
1214
model_config = ConfigDict(extra="allow")
1315

14-
P = TypeVar("P", bound=OrderParameters)
15-
O = TypeVar("O", bound=OpportunityProperties)
1616

17-
class OrderRequest(OpportunityRequest, Generic[P]):
18-
order_parameters: P
17+
ORP = TypeVar("P", bound=OrderParameters)
18+
OPP = TypeVar("O", bound=OpportunityProperties)
1919

20-
class OrderProperties(BaseModel, Generic[O]):
21-
opportunity_properties: O
22-
order_parameters: dict[str, Any]
20+
21+
class OrderRequest(BaseModel, Generic[ORP]):
22+
datetime: DatetimeInterval
23+
geometry: Geometry
24+
# TODO: validate the CQL2 filter?
25+
filter: CQL2Filter | None = None
26+
27+
order_parameters: ORP
28+
29+
model_config = ConfigDict(strict=True)
30+
31+
32+
class OrderProperties(BaseModel, Generic[OPP, ORP]):
33+
product_id: str
34+
datetime: DatetimeInterval
35+
geometry: Geometry
36+
# TODO: validate the CQL2 filter?
37+
filter: CQL2Filter | None = None
38+
39+
opportunity_properties: OPP
40+
order_parameters: ORP
41+
42+
model_config = ConfigDict(extra="allow")
2343

2444

2545
class Order(Feature[Geometry, OrderProperties]):
@@ -29,6 +49,7 @@ class Order(Feature[Geometry, OrderProperties]):
2949
type: Literal["Feature"] = "Feature"
3050
links: list[Link] = Field(default_factory=list)
3151

52+
3253
class OrderCollection(FeatureCollection[Order]):
3354
type: Literal["FeatureCollection"] = "FeatureCollection"
3455
links: list[Link] = Field(default_factory=list)

src/stapi_fastapi/routers/product_router.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Self, Any
3+
from typing import TYPE_CHECKING, Self
44

55
from fastapi import APIRouter, HTTPException, Request, Response, status
66
from geojson_pydantic.geometries import Geometry
@@ -67,14 +67,27 @@ def __init__(
6767
summary="Get order parameters for the product",
6868
)
6969

70+
async def _create_order(
71+
payload: OrderRequest,
72+
request: Request,
73+
response: Response,
74+
) -> Order:
75+
return await self.create_order(payload, request, response)
76+
77+
_create_order.__annotations__["payload"] = OrderRequest[
78+
product.order_parameters
79+
]
7080

7181
self.add_api_route(
7282
path="/order",
73-
endpoint=self.create_order,
83+
endpoint=_create_order,
7484
name=f"{self.root_router.name}:{self.product.id}:create-order",
7585
methods=["POST"],
7686
response_class=GeoJSONResponse,
7787
status_code=status.HTTP_201_CREATED,
88+
response_model=Order[
89+
self.product.constraints, self.product.order_parameters
90+
],
7891
summary="Create an order for the product",
7992
)
8093

tests/backends.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,21 @@ async def create_order(
6060
id=str(uuid4()),
6161
geometry=payload.geometry,
6262
properties={
63-
"filter": payload.filter,
64-
"datetime": payload.datetime,
6563
"product_id": product_router.product.id,
66-
**dict(payload.order_parameters),
64+
"datetime": payload.datetime,
65+
"geometry": payload.geometry,
66+
"filter": payload.filter,
67+
"order_parameters": payload.order_parameters,
68+
"opportunity_properties": {
69+
"datetime": "2024-01-29T12:00:00Z/2024-01-30T12:00:00Z",
70+
"off_nadir": 10,
71+
},
6772
},
6873
links=[],
6974
)
7075
self._orders[order.id] = order
7176
return order
7277
else:
73-
raise ConstraintsException(f"not allowed: payload {payload.model_dump_json()} not in {[p.model_dump_json() for p in self._allowed_payloads]}")
78+
raise ConstraintsException(
79+
f"not allowed: payload {payload.model_dump_json()} not in {[p.model_dump_json() for p in self._allowed_payloads]}"
80+
)

tests/conftest.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections.abc import Iterator
2-
from datetime import UTC, datetime, timedelta, timezone
2+
from datetime import datetime, timedelta, timezone
33
from typing import Callable
44
from urllib.parse import urljoin
55
from uuid import uuid4
@@ -24,7 +24,7 @@ class TestSpotlightProperties(OpportunityProperties):
2424
off_nadir: int
2525

2626

27-
class TestSpotlightOrderProperties(OrderParameters):
27+
class TestSpotlightOrderParameters(OrderParameters):
2828
s3_path: str | None = None
2929

3030

@@ -62,7 +62,7 @@ def mock_product_test_spotlight(
6262
providers=[mock_provider],
6363
links=[],
6464
constraints=TestSpotlightProperties,
65-
order_parameters=TestSpotlightOrderProperties,
65+
order_parameters=TestSpotlightOrderParameters,
6666
backend=product_backend,
6767
)
6868

@@ -131,14 +131,17 @@ def mock_test_spotlight_opportunities() -> list[Opportunity]:
131131

132132

133133
@pytest.fixture
134-
def allowed_payloads() -> list[OrderRequest]:
134+
def create_order_allowed_payloads() -> list[OrderRequest]:
135135
return [
136136
OrderRequest(
137137
geometry=Point(
138138
type="Point", coordinates=Position2D(longitude=13.4, latitude=52.5)
139139
),
140-
datetime=(datetime.now(UTC), datetime.now(UTC)),
141-
filter={},
142-
order_parameters=TestSpotlightOrderProperties(s3_path="BUCKET"),
140+
datetime=(
141+
datetime.fromisoformat("2024-11-11T18:55:33Z"),
142+
datetime.fromisoformat("2024-11-15T18:55:33Z"),
143+
),
144+
filter=None,
145+
order_parameters=TestSpotlightOrderParameters(s3_path="BUCKET"),
143146
),
144147
]

tests/order_test.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ def new_order_response(
1919
product_id: str,
2020
product_backend: MockProductBackend,
2121
stapi_client: TestClient,
22-
allowed_payloads: list[OrderRequest],
22+
create_order_allowed_payloads: list[OrderRequest],
2323
) -> Response:
24-
product_backend._allowed_payloads = allowed_payloads
24+
product_backend._allowed_payloads = create_order_allowed_payloads
2525

2626
res = stapi_client.post(
2727
f"products/{product_id}/order",
28-
json=allowed_payloads[0].model_dump(),
28+
json=create_order_allowed_payloads[0].model_dump(),
2929
)
3030

3131
assert res.status_code == status.HTTP_201_CREATED, res.text
@@ -56,14 +56,17 @@ def get_order_response(
5656

5757

5858
@pytest.mark.parametrize("product_id", ["test-spotlight"])
59-
def test_get_order_properties(get_order_response: Response, allowed_payloads) -> None:
59+
def test_get_order_properties(
60+
get_order_response: Response, create_order_allowed_payloads
61+
) -> None:
6062
order = get_order_response.json()
6163

6264
assert order["geometry"] == {
6365
"type": "Point",
64-
"coordinates": list(allowed_payloads[0].geometry.coordinates),
66+
"coordinates": list(create_order_allowed_payloads[0].geometry.coordinates),
6567
}
6668

6769
assert (
68-
order["properties"]["datetime"] == allowed_payloads[0].model_dump()["datetime"]
70+
order["properties"]["datetime"]
71+
== create_order_allowed_payloads[0].model_dump()["datetime"]
6972
)

0 commit comments

Comments
 (0)