Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
strategy:
matrix:
python-version:
- "3.11"
- "3.12"
- "3.13"
steps:
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.12
3.11
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "pystapi"
version = "0.0.0" # This package should never be released, only the workspace members should be
description = "Monorepo for Satellite Tasking API (STAPI) Specification Python packages"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.11"
dependencies = [
"pystapi-client",
"pystapi-validator",
Expand Down
2 changes: 1 addition & 1 deletion pystapi-client/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [
maintainers = [{ name = "Pete Gadomski", email = "[email protected]" }]
keywords = ["stapi"]
license = { text = "MIT" }
requires-python = ">=3.10"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.28.1",
"stapi-pydantic",
Expand Down
2 changes: 1 addition & 1 deletion pystapi-validator/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = [
]
license = "MIT"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.11"
dependencies = [
"schemathesis>=3.37.0",
"pytest>=8.3.3",
Expand Down
2 changes: 1 addition & 1 deletion stapi-fastapi/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ authors = [
readme = "README.md"
license = "MIT"

requires-python = ">=3.12"
requires-python = ">=3.11"

dependencies = [
"httpx>=0.27.0",
Expand Down
7 changes: 5 additions & 2 deletions stapi-fastapi/src/stapi_fastapi/routers/product_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
OrderStatus,
Prefer,
)
from stapi_pydantic import (
Product as ProductPydantic,
)

from stapi_fastapi.constants import TYPE_JSON
from stapi_fastapi.exceptions import ConstraintsException, NotFoundException
Expand All @@ -52,7 +55,7 @@ def get_prefer(prefer: str | None = Header(None)) -> str | None:
if prefer is None:
return None

if prefer not in Prefer:
if prefer not in Prefer._value2member_map_:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid Prefer header value: {prefer}",
Expand Down Expand Up @@ -168,7 +171,7 @@ async def _create_order(
tags=["Products"],
)

def get_product(self, request: Request) -> Product:
def get_product(self, request: Request) -> ProductPydantic:
links = [
Link(
href=str(
Expand Down
8 changes: 4 additions & 4 deletions stapi-fastapi/tests/backends.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime, timezone
from datetime import UTC, datetime
from uuid import uuid4

from fastapi import Request
Expand Down Expand Up @@ -81,15 +81,15 @@ async def mock_create_order(product_router: ProductRouter, payload: OrderPayload
"""
try:
status = OrderStatus(
timestamp=datetime.now(timezone.utc),
timestamp=datetime.now(UTC),
status_code=OrderStatusCode.received,
)
order = Order(
id=str(uuid4()),
geometry=payload.geometry,
properties=OrderProperties(
product_id=product_router.product.id,
created=datetime.now(timezone.utc),
created=datetime.now(UTC),
status=status,
search_parameters=OrderSearchParameters(
geometry=payload.geometry,
Expand Down Expand Up @@ -140,7 +140,7 @@ async def mock_search_opportunities_async(
) -> ResultE[OpportunitySearchRecord]:
try:
received_status = OpportunitySearchStatus(
timestamp=datetime.now(timezone.utc),
timestamp=datetime.now(UTC),
status_code=OpportunitySearchStatusCode.received,
)
search_record = OpportunitySearchRecord(
Expand Down
8 changes: 4 additions & 4 deletions stapi-fastapi/tests/shared.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections import defaultdict
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from typing import Any, Literal, Self
from datetime import UTC, datetime, timedelta
from typing import Any, Literal, Self, TypeAlias
from urllib.parse import parse_qs, urlparse
from uuid import uuid4

Expand Down Expand Up @@ -32,7 +32,7 @@
mock_search_opportunities_async,
)

type link_dict = dict[str, Any]
link_dict: TypeAlias = dict[str, Any]


def find_link(links: list[link_dict], rel: str) -> link_dict | None:
Expand Down Expand Up @@ -203,7 +203,7 @@ class MyOrderParameters(OrderParameters):


def create_mock_opportunity() -> Opportunity:
now = datetime.now(timezone.utc) # Use timezone-aware datetime
now = datetime.now(UTC) # Use timezone-aware datetime
start = now
end = start + timedelta(days=5)

Expand Down
4 changes: 2 additions & 2 deletions stapi-fastapi/tests/test_opportunity_async.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections.abc import Callable
from datetime import UTC, datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from typing import Any
from uuid import uuid4

Expand Down Expand Up @@ -217,7 +217,7 @@ def test_async_opportunity_search_to_completion(
)
)
search_record.status = OpportunitySearchStatus(
timestamp=datetime.now(timezone.utc),
timestamp=datetime.now(UTC),
status_code=OpportunitySearchStatusCode.completed,
)

Expand Down
8 changes: 4 additions & 4 deletions stapi-fastapi/tests/test_order.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import UTC, datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta

import pytest
from fastapi import status
Expand Down Expand Up @@ -174,17 +174,17 @@ def order_statuses() -> dict[str, list[OrderStatus]]:
statuses = {
"test_order_id": [
OrderStatus(
timestamp=datetime(2025, 1, 14, 2, 21, 48, 466726, tzinfo=timezone.utc),
timestamp=datetime(2025, 1, 14, 2, 21, 48, 466726, tzinfo=UTC),
status_code=OrderStatusCode.received,
links=[],
),
OrderStatus(
timestamp=datetime(2025, 1, 15, 5, 20, 48, 466726, tzinfo=timezone.utc),
timestamp=datetime(2025, 1, 15, 5, 20, 48, 466726, tzinfo=UTC),
status_code=OrderStatusCode.accepted,
links=[],
),
OrderStatus(
timestamp=datetime(2025, 1, 16, 10, 15, 32, 466726, tzinfo=timezone.utc),
timestamp=datetime(2025, 1, 16, 10, 15, 32, 466726, tzinfo=UTC),
status_code=OrderStatusCode.completed,
links=[],
),
Expand Down
1 change: 1 addition & 0 deletions stapi-pydantic/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Added

- python `3.11` support ([#73](https://github.com/stapi-spec/pystapi/pull/73))
- `stapi_type` and `stapi_version` ([#54](https://github.com/stapi-spec/pystapi/pull/54))

## [0.0.2] - 2025-04-02
Expand Down
2 changes: 1 addition & 1 deletion stapi-pydantic/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ authors = [
{ name = "Phil Varner", email = "[email protected]" },
{ name = "Pete Gadomski", email = "[email protected]" },
]
requires-python = ">=3.12"
requires-python = ">=3.11"
dependencies = ["cql2>=0.3.6", "geojson-pydantic>=1.2.0"]

[project.scripts]
Expand Down
2 changes: 1 addition & 1 deletion stapi-pydantic/src/stapi_pydantic/datetime_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def serialize(
return f"{value[0].isoformat()}/{value[1].isoformat()}"


type DatetimeInterval = Annotated[
DatetimeInterval = Annotated[
tuple[AwareDatetime, AwareDatetime],
BeforeValidator(validate_before),
AfterValidator(validate_after),
Expand Down
4 changes: 2 additions & 2 deletions stapi-pydantic/src/stapi_pydantic/filter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Annotated, Any
from typing import Annotated, Any, TypeAlias

from cql2 import Expr
from pydantic import BeforeValidator
Expand All @@ -11,7 +11,7 @@ def validate(v: dict[str, Any]) -> dict[str, Any]:
return v


type CQL2Filter = Annotated[
CQL2Filter: TypeAlias = Annotated[
dict,
BeforeValidator(validate),
]
2 changes: 1 addition & 1 deletion stapi-pydantic/src/stapi_pydantic/json_schema_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def serialize(v: type[BaseModel]) -> dict[str, Any]:
return v.model_json_schema()


type JsonSchemaModel = Annotated[
JsonSchemaModel = Annotated[
type[BaseModel],
PlainValidator(validate),
PlainSerializer(serialize),
Expand Down
2 changes: 1 addition & 1 deletion stapi-pydantic/src/stapi_pydantic/opportunity.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class OpportunityProperties(BaseModel):
class OpportunityPayload(BaseModel):
datetime: DatetimeInterval
geometry: Geometry
filter: CQL2Filter | None = None
filter: CQL2Filter | None = None # type: ignore [type-arg]

next: str | None = None
limit: int = 10
Expand Down
15 changes: 9 additions & 6 deletions stapi-pydantic/src/stapi_pydantic/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ class OrderStatus(BaseModel):
model_config = ConfigDict(extra="allow")


class OrderStatuses[T: OrderStatus](BaseModel):
T = TypeVar("T", bound=OrderStatus)


class OrderStatuses(BaseModel, Generic[T]):
statuses: list[T]
links: list[Link] = Field(default_factory=list)

Expand All @@ -64,10 +67,10 @@ class OrderSearchParameters(BaseModel):
datetime: DatetimeInterval
geometry: Geometry
# TODO: validate the CQL2 filter?
filter: CQL2Filter | None = None
filter: CQL2Filter | None = None # type: ignore [type-arg]


class OrderProperties[T: OrderStatus](BaseModel):
class OrderProperties(BaseModel, Generic[T]):
product_id: str
created: AwareDatetime
status: T
Expand All @@ -80,7 +83,7 @@ class OrderProperties[T: OrderStatus](BaseModel):


# derived from geojson_pydantic.Feature
class Order[T: OrderStatus](_GeoJsonBase):
class Order(_GeoJsonBase, Generic[T]):
# We need to enforce that orders have an id defined, as that is required to
# retrieve them via the API
id: StrictStr
Expand All @@ -105,7 +108,7 @@ def set_geometry(cls, geometry: Any) -> Any:


# derived from geojson_pydantic.FeatureCollection
class OrderCollection[T: OrderStatus](_GeoJsonBase):
class OrderCollection(_GeoJsonBase, Generic[T]):
type: Literal["FeatureCollection"] = "FeatureCollection"
features: list[Order[T]]
links: list[Link] = Field(default_factory=list)
Expand All @@ -127,7 +130,7 @@ class OrderPayload(BaseModel, Generic[ORP]):
datetime: DatetimeInterval
geometry: Geometry
# TODO: validate the CQL2 filter?
filter: CQL2Filter | None = None
filter: CQL2Filter | None = None # type: ignore [type-arg]

order_parameters: ORP

Expand Down
4 changes: 2 additions & 2 deletions stapi-pydantic/src/stapi_pydantic/product.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from enum import StrEnum
from typing import Any, Literal, Self
from typing import Any, Literal, Self, TypeAlias

from pydantic import AnyHttpUrl, BaseModel, Field

from .constants import STAPI_VERSION
from .shared import Link

type Constraints = BaseModel
Constraints: TypeAlias = BaseModel


class ProviderRole(StrEnum):
Expand Down
Loading