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

Commit 44a0ac9

Browse files
tylandersonPhil VarnerPhil Varner
authored
Add order parameters to order (#95)
* add order_parameters to product and order * update test implementation * WIP: order types * rename classes * refactor types to support correct typing of incoming OrderRequest * remove unnecessary response model * move around some code * remove unnecessary pre-commit statement * remove geometry from OrderProperties, as it is already provided by Feature * add typing to order api route * rename fields * change Order order_parameters to dict, OrderRequest to typed object * remove cql2 dependency * remove duplicate ruff dependency * remove unused Order typing --------- Co-authored-by: Phil Varner <[email protected]> Co-authored-by: Phil Varner <[email protected]>
1 parent c78f069 commit 44a0ac9

File tree

18 files changed

+455
-318
lines changed

18 files changed

+455
-318
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repos:
1010
- id: trailing-whitespace
1111
- id: no-commit-to-branch
1212
- repo: https://github.com/charliermarsh/ruff-pre-commit
13-
rev: v0.7.3
13+
rev: v0.7.4
1414
hooks:
1515
- id: ruff
1616
args: [--fix, --exit-non-zero-on-fix]

bin/server.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
1+
from datetime import datetime, timezone
12
from uuid import uuid4
23

34
from fastapi import FastAPI, Request
45

56
from stapi_fastapi.backends.product_backend import ProductBackend
67
from stapi_fastapi.backends.root_backend import RootBackend
7-
from stapi_fastapi.exceptions import ConstraintsException, NotFoundException
8+
from stapi_fastapi.exceptions import NotFoundException
89
from stapi_fastapi.models.conformance import CORE
910
from stapi_fastapi.models.opportunity import (
1011
Opportunity,
11-
OpportunityPropertiesBase,
12+
OpportunityProperties,
1213
OpportunityRequest,
1314
)
14-
from stapi_fastapi.models.order import Order, OrderCollection
15-
from stapi_fastapi.models.product import (
15+
from stapi_fastapi.models.order import (
16+
Order,
17+
OrderCollection,
1618
OrderParameters,
19+
OrderRequest,
20+
OrderStatus,
21+
OrderStatusCode,
22+
)
23+
from stapi_fastapi.models.product import (
1724
Product,
1825
Provider,
1926
ProviderRole,
2027
)
28+
from stapi_fastapi.routers.product_router import ProductRouter
2129
from stapi_fastapi.routers.root_router import RootRouter
2230

2331

@@ -48,43 +56,57 @@ async def get_order(self, order_id: str, request: Request) -> Order:
4856
class MockProductBackend(ProductBackend):
4957
def __init__(self, orders: MockOrderDB) -> None:
5058
self._opportunities: list[Opportunity] = []
51-
self._allowed_payloads: list[OpportunityRequest] = []
59+
self._allowed_payloads: list[OrderRequest] = []
5260
self._orders: MockOrderDB = orders
5361

5462
async def search_opportunities(
55-
self, product: Product, search: OpportunityRequest, request: Request
63+
self,
64+
product_router: ProductRouter,
65+
search: OpportunityRequest,
66+
request: Request,
5667
) -> list[Opportunity]:
5768
return [o.model_copy(update=search.model_dump()) for o in self._opportunities]
5869

5970
async def create_order(
60-
self, product: Product, payload: OpportunityRequest, request: Request
71+
self, product_router: ProductRouter, payload: OrderRequest, request: Request
6172
) -> Order:
6273
"""
6374
Create a new order.
6475
"""
65-
allowed: bool = any(allowed == payload for allowed in self._allowed_payloads)
66-
if allowed:
67-
order = Order(
68-
id=str(uuid4()),
69-
geometry=payload.geometry,
70-
properties={
71-
"filter": payload.filter,
76+
order = Order(
77+
id=str(uuid4()),
78+
geometry=payload.geometry,
79+
properties={
80+
"product_id": product_router.product.id,
81+
"created": datetime.now(timezone.utc),
82+
"status": OrderStatus(
83+
timestamp=datetime.now(timezone.utc),
84+
status_code=OrderStatusCode.accepted,
85+
),
86+
"search_parameters": {
87+
"geometry": payload.geometry,
7288
"datetime": payload.datetime,
73-
"product_id": product.id,
89+
"filter": payload.filter,
90+
},
91+
"order_parameters": payload.order_parameters.model_dump(),
92+
"opportunity_properties": {
93+
"datetime": "2024-01-29T12:00:00Z/2024-01-30T12:00:00Z",
94+
"off_nadir": 10,
7495
},
75-
links=[],
76-
)
77-
self._orders[order.id] = order
78-
return order
79-
raise ConstraintsException("not allowed")
96+
},
97+
links=[],
98+
)
99+
100+
self._orders[order.id] = order
101+
return order
80102

81103

82-
class TestSpotlightProperties(OpportunityPropertiesBase):
104+
class MyOpportunityProperties(OpportunityProperties):
83105
off_nadir: int
84106

85107

86-
class TestSpotlightOrderParameters(OrderParameters):
87-
delivery_mechanism: str | None = None
108+
class MyOrderParameters(OrderParameters):
109+
s3_path: str | None = None
88110

89111

90112
order_db = MockOrderDB()
@@ -106,8 +128,8 @@ class TestSpotlightOrderParameters(OrderParameters):
106128
keywords=["test", "satellite"],
107129
providers=[provider],
108130
links=[],
109-
constraints=TestSpotlightProperties,
110-
order_parameters=TestSpotlightOrderParameters,
131+
constraints=MyOpportunityProperties,
132+
order_parameters=MyOrderParameters,
111133
backend=product_backend,
112134
)
113135

poetry.lock

Lines changed: 214 additions & 213 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ optional = true
2020

2121
[tool.poetry.group.dev.dependencies]
2222
pytest = "^8.1.1"
23-
ruff = "^0.3.4"
23+
ruff = "^0.7.4"
2424
uvicorn = "^0.29.0"
2525
pydantic-settings = "^2.2.1"
2626
httpx = "^0.27.0"

src/stapi_fastapi/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .backends import ProductBackend, RootBackend
22
from .models import (
33
Link,
4-
OpportunityPropertiesBase,
4+
OpportunityProperties,
55
Product,
66
Provider,
77
ProviderRole,
@@ -10,7 +10,7 @@
1010

1111
__all__ = [
1212
"Link",
13-
"OpportunityPropertiesBase",
13+
"OpportunityProperties",
1414
"Product",
1515
"ProductBackend",
1616
"ProductRouter",

src/stapi_fastapi/backends/product_backend.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi import Request
66

77
from stapi_fastapi.models.opportunity import Opportunity, OpportunityRequest
8-
from stapi_fastapi.models.order import Order
8+
from stapi_fastapi.models.order import Order, OrderRequest
99
from stapi_fastapi.routers.product_router import ProductRouter
1010

1111

@@ -27,7 +27,7 @@ async def search_opportunities(
2727
async def create_order(
2828
self,
2929
product_router: ProductRouter,
30-
search: OpportunityRequest,
30+
search: OrderRequest,
3131
request: Request,
3232
) -> Order:
3333
"""

src/stapi_fastapi/models/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from .opportunity import OpportunityPropertiesBase
1+
from .opportunity import OpportunityProperties
22
from .product import Product, Provider, ProviderRole
33
from .shared import Link
44

55
__all__ = [
66
"Link",
7-
"OpportunityPropertiesBase",
7+
"OpportunityProperties",
88
"Product",
99
"Provider",
1010
"ProviderRole",

src/stapi_fastapi/models/opportunity.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010

1111

1212
# Copied and modified from https://github.com/stac-utils/stac-pydantic/blob/main/stac_pydantic/item.py#L11
13-
class OpportunityPropertiesBase(BaseModel):
14-
product_id: str
13+
class OpportunityProperties(BaseModel):
1514
datetime: DatetimeInterval
1615
model_config = ConfigDict(extra="allow")
1716

@@ -25,7 +24,7 @@ class OpportunityRequest(BaseModel):
2524

2625

2726
G = TypeVar("G", bound=Geometry)
28-
P = TypeVar("P", bound=OpportunityPropertiesBase)
27+
P = TypeVar("P", bound=OpportunityProperties)
2928

3029

3130
class Opportunity(Feature[G, P]):

src/stapi_fastapi/models/order.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,74 @@
1-
from typing import Literal
1+
from enum import Enum
2+
from typing import Any, Generic, Literal, Optional, TypeVar
23

34
from geojson_pydantic import Feature, FeatureCollection
45
from geojson_pydantic.geometries import Geometry
5-
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
6+
from pydantic import (
7+
AwareDatetime,
8+
BaseModel,
9+
ConfigDict,
10+
Field,
11+
StrictInt,
12+
StrictStr,
13+
)
614

15+
from stapi_fastapi.models.opportunity import OpportunityProperties
716
from stapi_fastapi.models.shared import Link
817
from stapi_fastapi.types.datetime_interval import DatetimeInterval
18+
from stapi_fastapi.types.filter import CQL2Filter
19+
20+
21+
class OrderParameters(BaseModel):
22+
model_config = ConfigDict(extra="forbid")
23+
24+
25+
OPP = TypeVar("OPP", bound=OpportunityProperties)
26+
ORP = TypeVar("ORP", bound=OrderParameters)
27+
28+
29+
class OrderRequest(BaseModel, Generic[ORP]):
30+
datetime: DatetimeInterval
31+
geometry: Geometry
32+
# TODO: validate the CQL2 filter?
33+
filter: CQL2Filter | None = None
34+
35+
order_parameters: ORP
36+
37+
model_config = ConfigDict(strict=True)
38+
39+
40+
class OrderStatusCode(str, Enum):
41+
received = "received"
42+
accepted = "accepted"
43+
rejected = "rejected"
44+
completed = "completed"
45+
canceled = "canceled"
46+
47+
48+
class OrderStatus(BaseModel):
49+
timestamp: AwareDatetime
50+
status_code: OrderStatusCode
51+
reason_code: Optional[str] = None
52+
reason_text: Optional[str] = None
53+
links: list[Link] = Field(default_factory=list)
54+
55+
56+
class OrderSearchParameters(BaseModel):
57+
datetime: DatetimeInterval
58+
geometry: Geometry
59+
# TODO: validate the CQL2 filter?
60+
filter: CQL2Filter | None = None
961

1062

1163
class OrderProperties(BaseModel):
1264
product_id: str
13-
datetime: DatetimeInterval
65+
created: AwareDatetime
66+
status: OrderStatus
67+
68+
search_parameters: OrderSearchParameters
69+
opportunity_properties: dict[str, Any]
70+
order_parameters: dict[str, Any]
71+
1472
model_config = ConfigDict(extra="allow")
1573

1674

src/stapi_fastapi/models/product.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
from enum import Enum
55
from typing import TYPE_CHECKING, Literal, Optional, Self
66

7-
from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field
7+
from pydantic import AnyHttpUrl, BaseModel, Field
88

9-
from stapi_fastapi.models.opportunity import OpportunityPropertiesBase
9+
from stapi_fastapi.models.opportunity import OpportunityProperties
10+
from stapi_fastapi.models.order import OrderParameters
1011
from stapi_fastapi.models.shared import Link
1112

1213
if TYPE_CHECKING:
@@ -32,10 +33,6 @@ def __init__(self, url: AnyHttpUrl | str, **kwargs):
3233
super().__init__(url=url, **kwargs)
3334

3435

35-
class OrderParameters(BaseModel):
36-
model_config = ConfigDict(extra="allow")
37-
38-
3936
class Product(BaseModel):
4037
type_: Literal["Product"] = Field(default="Product", alias="type")
4138
conformsTo: list[str] = Field(default_factory=list)
@@ -48,15 +45,15 @@ class Product(BaseModel):
4845
links: list[Link] = Field(default_factory=list)
4946

5047
# we don't want to include these in the model fields
51-
_constraints: type[OpportunityPropertiesBase]
48+
_constraints: type[OpportunityProperties]
5249
_order_parameters: type[OrderParameters]
5350
_backend: ProductBackend
5451

5552
def __init__(
5653
self,
5754
*args,
5855
backend: ProductBackend,
59-
constraints: type[OpportunityPropertiesBase],
56+
constraints: type[OpportunityProperties],
6057
order_parameters: type[OrderParameters],
6158
**kwargs,
6259
) -> None:
@@ -70,7 +67,7 @@ def backend(self: Self) -> ProductBackend:
7067
return self._backend
7168

7269
@property
73-
def constraints(self: Self) -> type[OpportunityPropertiesBase]:
70+
def constraints(self: Self) -> type[OpportunityProperties]:
7471
return self._constraints
7572

7673
@property

0 commit comments

Comments
 (0)