diff --git a/CHANGES.md b/CHANGES.md index e33d70ac0..2574d863b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,14 @@ ## [Unreleased] +## [6.1.4] - 2025-12-12 + +### Fixed + +- improve type hints + - `api.app.StacAPI.search_get_request_model` now defined as `Type[APIRequest]` + - `api.app.StacAPI.search_post_request_model` now defined as `Type[BaseModel]` + ## [6.1.3] - 2025-12-09 ### Fixed diff --git a/stac_fastapi/api/stac_fastapi/api/app.py b/stac_fastapi/api/stac_fastapi/api/app.py index 69a705a91..059f2c536 100644 --- a/stac_fastapi/api/stac_fastapi/api/app.py +++ b/stac_fastapi/api/stac_fastapi/api/app.py @@ -8,6 +8,7 @@ from brotli_asgi import BrotliMiddleware from fastapi import APIRouter, FastAPI from fastapi.params import Depends +from pydantic import BaseModel from stac_pydantic import api from stac_pydantic.shared import MimeTypes from stac_pydantic.version import STAC_VERSION @@ -114,12 +115,8 @@ class StacApi: converter=update_openapi, # type: ignore ) router: APIRouter = attr.ib(default=attr.Factory(APIRouter)) - search_get_request_model: Type[BaseSearchGetRequest] = attr.ib( - default=BaseSearchGetRequest - ) - search_post_request_model: Type[BaseSearchPostRequest] = attr.ib( - default=BaseSearchPostRequest - ) + search_get_request_model: Type[APIRequest] = attr.ib(default=BaseSearchGetRequest) + search_post_request_model: Type[BaseModel] = attr.ib(default=BaseSearchPostRequest) collections_get_request_model: Type[APIRequest] = attr.ib(default=EmptyRequest) collection_get_request_model: Type[APIRequest] = attr.ib(default=CollectionUri) items_get_request_model: Type[APIRequest] = attr.ib(default=ItemCollectionUri) diff --git a/stac_fastapi/api/stac_fastapi/api/errors.py b/stac_fastapi/api/stac_fastapi/api/errors.py index 8aa52ec74..5e9e72a1e 100644 --- a/stac_fastapi/api/stac_fastapi/api/errors.py +++ b/stac_fastapi/api/stac_fastapi/api/errors.py @@ -96,5 +96,5 @@ def request_validation_exception_handler( # TODO: Argument 2 to "add_exception_handler" of "Starlette" has incompatible type app.add_exception_handler( RequestValidationError, - request_validation_exception_handler, # type: ignore + request_validation_exception_handler, # type: ignore [arg-type] ) diff --git a/stac_fastapi/api/stac_fastapi/api/models.py b/stac_fastapi/api/stac_fastapi/api/models.py index 445bf21a9..7447212c7 100644 --- a/stac_fastapi/api/stac_fastapi/api/models.py +++ b/stac_fastapi/api/stac_fastapi/api/models.py @@ -1,6 +1,6 @@ """Api request/response models.""" -from typing import List, Literal, Optional, Type, Union +from typing import List, Literal, Optional, Type, Union, cast import attr from fastapi import Path, Query @@ -24,7 +24,7 @@ import orjson # noqa from fastapi.responses import ORJSONResponse as JSONResponse except ImportError: # pragma: nocover - from starlette.responses import JSONResponse # type: ignore + from starlette.responses import JSONResponse # type: ignore [assignment] def create_request_model( @@ -49,7 +49,8 @@ def create_request_model( # Handle GET requests if all([issubclass(m, APIRequest) for m in models]): - return attr.make_class(model_name, attrs={}, bases=tuple(models)) + get_model = attr.make_class(model_name, attrs={}, bases=tuple(models)) + return cast(Type[APIRequest], get_model) # Handle POST requests elif all([issubclass(m, BaseModel) for m in models]): @@ -57,35 +58,38 @@ def create_request_model( for k, field_info in model.model_fields.items(): # type: ignore fields[k] = (field_info.annotation, field_info) - return create_model(model_name, **fields, __base__=base_model) # type: ignore + post_model = create_model(model_name, **fields, __base__=base_model) # type: ignore + return cast(Type[BaseModel], post_model) raise TypeError("Mixed Request Model types. Check extension request types.") def create_get_request_model( extensions: Optional[List[ApiExtension]], - base_model: Type[BaseSearchGetRequest] = BaseSearchGetRequest, + base_model: Type[APIRequest] = BaseSearchGetRequest, ) -> Type[APIRequest]: """Wrap create_request_model to create the GET request model.""" - return create_request_model( # type: ignore + model = create_request_model( "SearchGetRequest", base_model=base_model, extensions=extensions, request_type="GET", ) + return cast(Type[APIRequest], model) def create_post_request_model( extensions: Optional[List[ApiExtension]], - base_model: Type[BaseSearchPostRequest] = BaseSearchPostRequest, + base_model: Type[BaseModel] = BaseSearchPostRequest, ) -> Type[BaseModel]: """Wrap create_request_model to create the POST request model.""" - return create_request_model( # type: ignore + model = create_request_model( "SearchPostRequest", base_model=base_model, extensions=extensions, request_type="POST", ) + return cast(Type[BaseModel], model) @attr.s @@ -121,7 +125,7 @@ class ItemCollectionUri(APIRequest, DatetimeMixin): description="Limits the number of results that are included in each page of the response (capped to 10_000)." # noqa: E501 ), ] = attr.ib(default=10) - bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter) # type: ignore + bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter) # type: ignore [misc] datetime: DateTimeQueryType = attr.ib(default=None, validator=_validate_datetime) diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/collection_search.py b/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/collection_search.py index adb45e6af..ff9173b29 100644 --- a/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/collection_search.py +++ b/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/collection_search.py @@ -1,7 +1,7 @@ """Collection-Search extension.""" from enum import Enum -from typing import List, Optional, Type, Union +from typing import List, Optional, Type, Union, cast import attr from fastapi import APIRouter, FastAPI @@ -87,15 +87,18 @@ def from_extensions( for ext in extensions: conformance_classes.extend(ext.conformance_classes) - get_request_model = create_request_model( - model_name="CollectionsGetRequest", - base_model=BaseCollectionSearchGetRequest, - extensions=extensions, - request_type="GET", + get_request_model = cast( + Type[APIRequest], + create_request_model( + model_name="CollectionsGetRequest", + base_model=BaseCollectionSearchGetRequest, + extensions=extensions, + request_type="GET", + ), ) return cls( - GET=get_request_model, # type: ignore + GET=get_request_model, conformance_classes=conformance_classes, schema_href=schema_href, ) @@ -164,7 +167,7 @@ def register(self, app: FastAPI) -> None: app.include_router(self.router) @classmethod - def from_extensions( # type: ignore + def from_extensions( # type: ignore [override] cls, extensions: List[ApiExtension], *, @@ -181,25 +184,31 @@ def from_extensions( # type: ignore for ext in extensions: conformance_classes.extend(ext.conformance_classes) - get_request_model = create_request_model( - model_name="CollectionsGetRequest", - base_model=BaseCollectionSearchGetRequest, - extensions=extensions, - request_type="GET", + get_request_model = cast( + Type[APIRequest], + create_request_model( + model_name="CollectionsGetRequest", + base_model=BaseCollectionSearchGetRequest, + extensions=extensions, + request_type="GET", + ), ) - post_request_model = create_request_model( - model_name="CollectionsPostRequest", - base_model=BaseCollectionSearchPostRequest, - extensions=extensions, - request_type="POST", + post_request_model = cast( + Type[BaseModel], + create_request_model( + model_name="CollectionsPostRequest", + base_model=BaseCollectionSearchPostRequest, + extensions=extensions, + request_type="POST", + ), ) return cls( client=client, settings=settings, - GET=get_request_model, # type: ignore - POST=post_request_model, # type: ignore + GET=get_request_model, + POST=post_request_model, conformance_classes=conformance_classes, router=router or APIRouter(), schema_href=schema_href, diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/third_party/bulk_transactions.py b/stac_fastapi/extensions/stac_fastapi/extensions/third_party/bulk_transactions.py index d1faa5c0f..aec905dff 100644 --- a/stac_fastapi/extensions/stac_fastapi/extensions/third_party/bulk_transactions.py +++ b/stac_fastapi/extensions/stac_fastapi/extensions/third_party/bulk_transactions.py @@ -31,7 +31,7 @@ def __iter__(self): return iter(self.items.values()) -@attr.s # type: ignore +@attr.s class BaseBulkTransactionsClient(abc.ABC): """BulkTransactionsClient.""" @@ -63,7 +63,7 @@ def bulk_item_insert( raise NotImplementedError -@attr.s # type: ignore +@attr.s class AsyncBaseBulkTransactionsClient(abc.ABC): """BulkTransactionsClient.""" diff --git a/stac_fastapi/types/stac_fastapi/types/rfc3339.py b/stac_fastapi/types/stac_fastapi/types/rfc3339.py index 681a491aa..d8e28fc57 100644 --- a/stac_fastapi/types/stac_fastapi/types/rfc3339.py +++ b/stac_fastapi/types/stac_fastapi/types/rfc3339.py @@ -140,6 +140,7 @@ def str_to_interval(interval: Optional[str]) -> Optional[DateTimeType]: raise HTTPException( status_code=400, detail="Double open-ended intervals are not allowed." ) + if start is not None and end is not None and start > end: raise HTTPException( status_code=400, detail="Start datetime cannot be before end datetime." diff --git a/stac_fastapi/types/stac_fastapi/types/search.py b/stac_fastapi/types/stac_fastapi/types/search.py index c30060746..83c4fd34a 100644 --- a/stac_fastapi/types/stac_fastapi/types/search.py +++ b/stac_fastapi/types/stac_fastapi/types/search.py @@ -2,7 +2,7 @@ """ from datetime import datetime as dt -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Union, cast import attr from fastapi import HTTPException, Query @@ -44,7 +44,7 @@ def str2bbox(x: str) -> Optional[BBox]: status_code=400, detail=f"BBox '{x}' must have 4 or 6 values." ) - return t # type: ignore + return cast(BBox, t) return None diff --git a/uv.lock b/uv.lock index 27b5754d8..5ca676494 100644 --- a/uv.lock +++ b/uv.lock @@ -3242,7 +3242,7 @@ wheels = [ [[package]] name = "stac-fastapi" -version = "6.1.2" +version = "6.1.3" source = { virtual = "." } dependencies = [ { name = "stac-fastapi-api" },