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
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi/api/stac_fastapi/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class StacApi:
),
takes_self=True,
),
converter=update_openapi,
converter=update_openapi, # type: ignore
Copy link
Member Author

@vincentsarago vincentsarago May 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some reason attr + Mypy doesn't like this (if we ever move to attrs.field it works fine) but I didn't want to mix the usage of attr.ib and attrs.field in the project

)
router: APIRouter = attr.ib(default=attr.Factory(APIRouter))
search_get_request_model: Type[BaseSearchGetRequest] = attr.ib(
Expand Down
5 changes: 3 additions & 2 deletions stac_fastapi/api/stac_fastapi/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")

Expand Down Expand Up @@ -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)


Expand Down
Empty file.
32 changes: 14 additions & 18 deletions stac_fastapi/api/stac_fastapi/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import attr

from stac_fastapi.types import stac
from stac_fastapi.types.stac import ItemCollection

from .request import BaseCollectionSearchPostRequest

Expand All @@ -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`.
Expand All @@ -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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
)

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Empty file.
12 changes: 6 additions & 6 deletions stac_fastapi/types/stac_fastapi/types/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Empty file.