From 19a4d36a2d92648b04148dd1b3657b6522654f2d Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Mon, 19 May 2025 17:49:29 +0200 Subject: [PATCH] add type checking --- .pre-commit-config.yaml | 10 ++++++ pyproject.toml | 6 ++++ stac_fastapi/api/stac_fastapi/api/app.py | 2 +- stac_fastapi/api/stac_fastapi/api/models.py | 5 +-- stac_fastapi/api/stac_fastapi/api/py.typed | 0 stac_fastapi/api/stac_fastapi/api/routes.py | 32 ++++++++----------- .../core/collection_search/client.py | 6 ++-- .../collection_search/collection_search.py | 29 +++++++++-------- .../core/collection_search/request.py | 2 +- .../stac_fastapi/extensions/py.typed | 0 stac_fastapi/types/stac_fastapi/types/core.py | 12 +++---- .../types/stac_fastapi/types/py.typed | 0 12 files changed, 59 insertions(+), 45 deletions(-) create mode 100644 stac_fastapi/api/stac_fastapi/api/py.typed create mode 100644 stac_fastapi/extensions/stac_fastapi/extensions/py.typed create mode 100644 stac_fastapi/types/stac_fastapi/types/py.typed diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68c3b8567..ccefc364e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,3 +5,13 @@ repos: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.15.0 + hooks: + - id: mypy + language_version: python + exclude: tests/.* + additional_dependencies: + - types-attrs + - pydantic diff --git a/pyproject.toml b/pyproject.toml index 12b4afbd5..d6d50b810 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,12 @@ section-order = ["future", "standard-library", "third-party", "first-party", "lo [tool.ruff.format] quote-style = "double" +[tool.mypy] +ignore_missing_imports = true +namespace_packages = true +explicit_package_bases = true +exclude = ["tests", ".venv"] + [tool.bumpversion] current_version = "5.2.1" parse = """(?x) diff --git a/stac_fastapi/api/stac_fastapi/api/app.py b/stac_fastapi/api/stac_fastapi/api/app.py index ed39c31a7..a52343dd4 100644 --- a/stac_fastapi/api/stac_fastapi/api/app.py +++ b/stac_fastapi/api/stac_fastapi/api/app.py @@ -110,7 +110,7 @@ class StacApi: ), takes_self=True, ), - converter=update_openapi, + converter=update_openapi, # type: ignore ) router: APIRouter = attr.ib(default=attr.Factory(APIRouter)) search_get_request_model: Type[BaseSearchGetRequest] = attr.ib( diff --git a/stac_fastapi/api/stac_fastapi/api/models.py b/stac_fastapi/api/stac_fastapi/api/models.py index fc764efe1..79da7b162 100644 --- a/stac_fastapi/api/stac_fastapi/api/models.py +++ b/stac_fastapi/api/stac_fastapi/api/models.py @@ -56,7 +56,8 @@ def create_request_model( for model in models: for k, field_info in model.model_fields.items(): fields[k] = (field_info.annotation, field_info) - return create_model(model_name, **fields, __base__=base_model) + + return create_model(model_name, **fields, __base__=base_model) # type: ignore raise TypeError("Mixed Request Model types. Check extension request types.") @@ -121,7 +122,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) + bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter) # type: ignore datetime: DateTimeQueryType = attr.ib(default=None, validator=_validate_datetime) diff --git a/stac_fastapi/api/stac_fastapi/api/py.typed b/stac_fastapi/api/stac_fastapi/api/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/stac_fastapi/api/stac_fastapi/api/routes.py b/stac_fastapi/api/stac_fastapi/api/routes.py index 79150c473..c8fb27c47 100644 --- a/stac_fastapi/api/stac_fastapi/api/routes.py +++ b/stac_fastapi/api/stac_fastapi/api/routes.py @@ -3,7 +3,7 @@ import copy import functools import inspect -from typing import Any, Callable, Dict, List, Optional, Type, TypedDict, Union +from typing import Any, Awaitable, Callable, Dict, List, Optional, Type, TypedDict, Union from fastapi import Depends, FastAPI, params from fastapi.datastructures import DefaultPlaceholder @@ -39,7 +39,7 @@ async def run(*args, **kwargs): def create_async_endpoint( func: Callable, request_model: Union[Type[APIRequest], Type[BaseModel], Dict], -): +) -> Callable[[Any, Any], Awaitable[Any]]: """Wrap a function in a coroutine which may be used to create a FastAPI endpoint. Synchronous functions are executed asynchronously using a background thread. @@ -48,32 +48,28 @@ def create_async_endpoint( if not inspect.iscoroutinefunction(func): func = sync_to_async(func) - if issubclass(request_model, APIRequest): + _endpoint: Callable[[Any, Any], Awaitable[Any]] - async def _endpoint( - request: Request, - request_data: request_model = Depends(), # type:ignore - ): + if isinstance(request_model, dict): + + async def _endpoint(request: Request, request_data: Dict[str, Any]): + """Endpoint.""" + return _wrap_response(await func(request_data, request=request)) + + elif issubclass(request_model, APIRequest): + + async def _endpoint(request: Request, request_data=Depends(request_model)): """Endpoint.""" return _wrap_response(await func(request=request, **request_data.kwargs())) elif issubclass(request_model, BaseModel): - async def _endpoint( - request: Request, - request_data: request_model, # type:ignore - ): + async def _endpoint(request: Request, request_data: request_model): # type: ignore """Endpoint.""" return _wrap_response(await func(request_data, request=request)) else: - - async def _endpoint( - request: Request, - request_data: Dict[str, Any], # type:ignore - ): - """Endpoint.""" - return _wrap_response(await func(request_data, request=request)) + raise ValueError(f"Unsupported type for request model {type(request_model)}") return _endpoint diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/client.py b/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/client.py index ac148dfb4..d46895e28 100644 --- a/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/client.py +++ b/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/client.py @@ -4,7 +4,7 @@ import attr -from stac_fastapi.types import stac +from stac_fastapi.types.stac import ItemCollection from .request import BaseCollectionSearchPostRequest @@ -18,7 +18,7 @@ async def post_all_collections( self, search_request: BaseCollectionSearchPostRequest, **kwargs, - ) -> stac.ItemCollection: + ) -> ItemCollection: """Get all available collections. Called with `POST /collections`. @@ -37,7 +37,7 @@ class BaseCollectionSearchClient(abc.ABC): @abc.abstractmethod def post_all_collections( self, search_request: BaseCollectionSearchPostRequest, **kwargs - ) -> stac.ItemCollection: + ) -> ItemCollection: """Get all available collections. Called with `POST /collections`. 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 e5ab77b50..384911b06 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 @@ -47,13 +47,13 @@ class CollectionSearchExtension(ApiExtension): the extension """ - GET: BaseCollectionSearchGetRequest = attr.ib(default=BaseCollectionSearchGetRequest) - POST = None + GET: BaseCollectionSearchGetRequest = attr.ib(default=BaseCollectionSearchGetRequest) # type: ignore + POST = attr.ib(init=False) conformance_classes: List[str] = attr.ib( default=[ - CollectionSearchConformanceClasses.COLLECTIONSEARCH, - CollectionSearchConformanceClasses.BASIS, + CollectionSearchConformanceClasses.COLLECTIONSEARCH.value, + CollectionSearchConformanceClasses.BASIS.value, ] ) schema_href: Optional[str] = attr.ib(default=None) @@ -73,13 +73,14 @@ def register(self, app: FastAPI) -> None: def from_extensions( cls, extensions: List[ApiExtension], + *, schema_href: Optional[str] = None, ) -> "CollectionSearchExtension": """Create CollectionSearchExtension object from extensions.""" conformance_classes = [ - CollectionSearchConformanceClasses.COLLECTIONSEARCH, - CollectionSearchConformanceClasses.BASIS, + CollectionSearchConformanceClasses.COLLECTIONSEARCH.value, + CollectionSearchConformanceClasses.BASIS.value, ] for ext in extensions: conformance_classes.extend(ext.conformance_classes) @@ -119,15 +120,15 @@ class CollectionSearchPostExtension(CollectionSearchExtension): settings: ApiSettings = attr.ib() conformance_classes: List[str] = attr.ib( default=[ - CollectionSearchConformanceClasses.COLLECTIONSEARCH, - CollectionSearchConformanceClasses.BASIS, + CollectionSearchConformanceClasses.COLLECTIONSEARCH.value, + CollectionSearchConformanceClasses.BASIS.value, ] ) schema_href: Optional[str] = attr.ib(default=None) router: APIRouter = attr.ib(factory=APIRouter) - GET: BaseCollectionSearchGetRequest = attr.ib(default=BaseCollectionSearchGetRequest) - POST: BaseCollectionSearchPostRequest = attr.ib( + GET: BaseCollectionSearchGetRequest = attr.ib(default=BaseCollectionSearchGetRequest) # type: ignore + POST: BaseCollectionSearchPostRequest = attr.ib( # type: ignore default=BaseCollectionSearchPostRequest ) @@ -163,19 +164,19 @@ def register(self, app: FastAPI) -> None: app.include_router(self.router) @classmethod - def from_extensions( + def from_extensions( # type: ignore cls, extensions: List[ApiExtension], *, + schema_href: Optional[str] = None, client: Union[AsyncBaseCollectionSearchClient, BaseCollectionSearchClient], settings: ApiSettings, - schema_href: Optional[str] = None, router: Optional[APIRouter] = None, ) -> "CollectionSearchPostExtension": """Create CollectionSearchPostExtension object from extensions.""" conformance_classes = [ - CollectionSearchConformanceClasses.COLLECTIONSEARCH, - CollectionSearchConformanceClasses.BASIS, + CollectionSearchConformanceClasses.COLLECTIONSEARCH.value, + CollectionSearchConformanceClasses.BASIS.value, ] for ext in extensions: conformance_classes.extend(ext.conformance_classes) diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/request.py b/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/request.py index 2ac4b608d..4b9f7f91d 100644 --- a/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/request.py +++ b/stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/request.py @@ -24,7 +24,7 @@ class BaseCollectionSearchGetRequest(APIRequest, DatetimeMixin): """Basics additional Collection-Search parameters for the GET request.""" - bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter) + bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter) # type: ignore datetime: DateTimeQueryType = attr.ib(default=None, validator=_validate_datetime) limit: Annotated[ Optional[Limit], diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/py.typed b/stac_fastapi/extensions/stac_fastapi/extensions/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/stac_fastapi/types/stac_fastapi/types/core.py b/stac_fastapi/types/stac_fastapi/types/core.py index f90b72823..c3d84e950 100644 --- a/stac_fastapi/types/stac_fastapi/types/core.py +++ b/stac_fastapi/types/stac_fastapi/types/core.py @@ -13,12 +13,12 @@ from stac_pydantic.version import STAC_VERSION from starlette.responses import Response -from stac_fastapi.types import stac -from stac_fastapi.types.config import ApiSettings -from stac_fastapi.types.conformance import BASE_CONFORMANCE_CLASSES -from stac_fastapi.types.extension import ApiExtension -from stac_fastapi.types.requests import get_base_url -from stac_fastapi.types.search import BaseSearchPostRequest +from . import stac +from .config import ApiSettings +from .conformance import BASE_CONFORMANCE_CLASSES +from .extension import ApiExtension +from .requests import get_base_url +from .search import BaseSearchPostRequest __all__ = [ "NumType", diff --git a/stac_fastapi/types/stac_fastapi/types/py.typed b/stac_fastapi/types/stac_fastapi/types/py.typed new file mode 100644 index 000000000..e69de29bb