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

Commit 0046fae

Browse files
committed
sketch of constraints/opportunity properties split
1 parent 77a6aa6 commit 0046fae

File tree

9 files changed

+65
-37
lines changed

9 files changed

+65
-37
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ command `pytest`.
3636

3737
This project cannot be run on its own because it does not have any backend
3838
implementations. However, a minimal test implementation is provided in
39-
[`./bin/server.py`](./bin/server.py). It can be run with `uvicorn` as a way to
40-
interact with the API and to view the OpenAPI documentation. Run it like so
41-
from the project root:
39+
[`./tests/application.py`](./tests/application.py). It can be run with
40+
`uvicorn` as a way to interact with the API and to view the OpenAPI
41+
documentation. Run it like so from the project root:
4242

4343
```commandline
44-
uvicorn server:app --app-dir ./bin --reload
44+
uvicorn application:app --app-dir ./tests --reload
4545
```
4646

4747
With the `uvicorn` defaults the app should be accessible at

src/stapi_fastapi/models/opportunity.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# Copied and modified from https://github.com/stac-utils/stac-pydantic/blob/main/stac_pydantic/item.py#L11
1313
class OpportunityProperties(BaseModel):
1414
datetime: DatetimeInterval
15+
product_id: str
1516
model_config = ConfigDict(extra="allow")
1617

1718

src/stapi_fastapi/models/product.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
from stapi_fastapi.backends.product_backend import ProductBackend
1414

1515

16+
type Constraints = BaseModel
17+
18+
1619
class ProviderRole(str, Enum):
1720
licensor = "licensor"
1821
producer = "producer"
@@ -28,7 +31,7 @@ class Provider(BaseModel):
2831

2932
# redefining init is a hack to get str type to validate for `url`,
3033
# as str is ultimately coerced into an AnyHttpUrl automatically anyway
31-
def __init__(self, url: AnyHttpUrl | str, **kwargs):
34+
def __init__(self, url: AnyHttpUrl | str, **kwargs) -> None:
3235
super().__init__(url=url, **kwargs)
3336

3437

@@ -44,31 +47,38 @@ class Product(BaseModel):
4447
links: list[Link] = Field(default_factory=list)
4548

4649
# we don't want to include these in the model fields
47-
_constraints: type[OpportunityProperties]
50+
_constraints: type[Constraints]
51+
_opportunity_properties: type[OpportunityProperties]
4852
_order_parameters: type[OrderParameters]
4953
_backend: ProductBackend
5054

5155
def __init__(
5256
self,
5357
*args,
5458
backend: ProductBackend,
55-
constraints: type[OpportunityProperties],
59+
constraints: type[Constraints],
60+
opportunity_properties: type[OpportunityProperties],
5661
order_parameters: type[OrderParameters],
5762
**kwargs,
5863
) -> None:
5964
super().__init__(*args, **kwargs)
6065
self._backend = backend
6166
self._constraints = constraints
67+
self._opportunity_properties = opportunity_properties
6268
self._order_parameters = order_parameters
6369

6470
@property
6571
def backend(self: Self) -> ProductBackend:
6672
return self._backend
6773

6874
@property
69-
def constraints(self: Self) -> type[OpportunityProperties]:
75+
def constraints(self: Self) -> type[Constraints]:
7076
return self._constraints
7177

78+
@property
79+
def opportunity_properties(self: Self) -> type[OpportunityProperties]:
80+
return self._opportunity_properties
81+
7282
@property
7383
def order_parameters(self: Self) -> type[OrderParameters]:
7484
return self._order_parameters

src/stapi_fastapi/routers/product_router.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ def __init__(
5151
methods=["POST"],
5252
response_class=GeoJSONResponse,
5353
# unknown why mypy can't see the constraints property on Product, ignoring
54-
response_model=OpportunityCollection[Geometry, self.product.constraints], # type: ignore
54+
response_model=OpportunityCollection[
55+
Geometry,
56+
self.product.opportunity_properties, # type: ignore
57+
],
5558
summary="Search Opportunities for the product",
5659
tags=["Products"],
5760
)

bin/server.py renamed to tests/application.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from datetime import datetime, timezone
2+
from typing import Literal, Self
23
from uuid import uuid4
34

45
from fastapi import FastAPI, Request
6+
from pydantic import BaseModel, Field, model_validator
57
from returns.maybe import Maybe
68
from returns.result import Failure, Result, Success
79

@@ -110,10 +112,27 @@ async def create_order(
110112
return Failure(e)
111113

112114

113-
class MyOpportunityProperties(OpportunityProperties):
115+
class MyProductConstraints(BaseModel):
114116
off_nadir: int
115117

116118

119+
class OffNadirRange(BaseModel):
120+
minimum: int = Field(ge=0, le=45)
121+
maximum: int = Field(ge=0, le=45)
122+
123+
@model_validator(mode="after")
124+
def validate_range(self) -> Self:
125+
if self.minimum > self.maximum:
126+
raise ValueError("range minimum cannot be greater than maximum")
127+
return self
128+
129+
130+
class MyOpportunityProperties(OpportunityProperties):
131+
off_nadir: OffNadirRange
132+
vehicle_id: list[Literal[1, 2, 5, 7, 8]]
133+
platform: Literal["platform_id"]
134+
135+
117136
class MyOrderParameters(OrderParameters):
118137
s3_path: str | None = None
119138

@@ -137,7 +156,8 @@ class MyOrderParameters(OrderParameters):
137156
keywords=["test", "satellite"],
138157
providers=[provider],
139158
links=[],
140-
constraints=MyOpportunityProperties,
159+
constraints=MyProductConstraints,
160+
opportunity_properties=MyOpportunityProperties,
141161
order_parameters=MyOrderParameters,
142162
backend=product_backend,
143163
)

tests/conftest.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,21 @@
1414
Opportunity,
1515
)
1616
from stapi_fastapi.models.product import (
17-
OrderParameters,
1817
Product,
1918
Provider,
2019
ProviderRole,
2120
)
2221
from stapi_fastapi.routers.root_router import RootRouter
2322

24-
from .backends import MockOrderDB, MockProductBackend, MockRootBackend
25-
from .shared import SpotlightOpportunityProperties, SpotlightOrderParameters, find_link
26-
27-
28-
class TestSpotlightOrderParameters(OrderParameters):
29-
s3_path: str | None = None
23+
from .application import (
24+
MockOrderDB,
25+
MockProductBackend,
26+
MockRootBackend,
27+
MyOpportunityProperties,
28+
MyOrderParameters,
29+
MyProductConstraints,
30+
)
31+
from .shared import find_link
3032

3133

3234
@pytest.fixture(scope="session")
@@ -62,8 +64,9 @@ def mock_product_test_spotlight(
6264
keywords=["test", "satellite"],
6365
providers=[mock_provider],
6466
links=[],
65-
constraints=SpotlightOpportunityProperties,
66-
order_parameters=SpotlightOrderParameters,
67+
constraints=MyProductConstraints,
68+
opportunity_properties=MyOpportunityProperties,
69+
order_parameters=MyOrderParameters,
6770
backend=product_backend,
6871
)
6972

@@ -140,10 +143,13 @@ def mock_test_spotlight_opportunities() -> list[Opportunity]:
140143
type="Point",
141144
coordinates=Position2D(longitude=0.0, latitude=0.0),
142145
),
143-
properties=SpotlightOpportunityProperties(
146+
properties=MyOpportunityProperties(
144147
product_id="xyz123",
145148
datetime=(start, end),
146-
off_nadir=20,
149+
off_nadir={"minimum": 20, "maximum": 22},
150+
vehicle_id=[1],
151+
platform="platform_id",
152+
other_thing="abcd1234",
147153
),
148154
),
149155
]

tests/order_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99

1010
from stapi_fastapi.models.order import OrderRequest
1111

12+
from .application import MyOrderParameters
1213
from .backends import MockProductBackend
13-
from .shared import SpotlightOrderParameters, find_link
14+
from .shared import find_link
1415

1516
NOW = datetime.now(UTC)
1617
START = NOW
@@ -29,7 +30,7 @@ def create_order_allowed_payloads() -> list[OrderRequest]:
2930
datetime.fromisoformat("2024-11-15T18:55:33Z"),
3031
),
3132
filter=None,
32-
order_parameters=SpotlightOrderParameters(s3_path="s3://my-bucket"),
33+
order_parameters=MyOrderParameters(s3_path="s3://my-bucket"),
3334
),
3435
]
3536

tests/product_test.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ def test_product_constraints_response(
4848

4949
json_schema = res.json()
5050
assert "properties" in json_schema
51-
assert "datetime" in json_schema["properties"]
5251
assert "off_nadir" in json_schema["properties"]
5352

5453

tests/shared.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
from typing import Any
22

3-
from stapi_fastapi.models.opportunity import OpportunityProperties
4-
from stapi_fastapi.models.order import OrderParameters
5-
6-
7-
class SpotlightOpportunityProperties(OpportunityProperties):
8-
off_nadir: int
9-
10-
11-
class SpotlightOrderParameters(OrderParameters):
12-
s3_path: str | None = None
13-
14-
153
type link_dict = dict[str, Any]
164

175

0 commit comments

Comments
 (0)