Skip to content

Commit bd4083f

Browse files
committed
update from main
1 parent ea8ef04 commit bd4083f

File tree

28 files changed

+176
-119
lines changed

28 files changed

+176
-119
lines changed

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,13 @@ repos:
55
- id: ruff
66
args: [--fix, --exit-non-zero-on-fix]
77
- id: ruff-format
8+
9+
- repo: https://github.com/pre-commit/mirrors-mypy
10+
rev: v1.15.0
11+
hooks:
12+
- id: mypy
13+
language_version: python
14+
exclude: tests/.*
15+
additional_dependencies:
16+
- types-attrs
17+
- pydantic

CHANGES.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,29 @@
22

33
## [Unreleased]
44

5+
### Changed
6+
57
* Add Item and Collection `PATCH` endpoints with support for [RFC 6902](https://tools.ietf.org/html/rfc6902) and [RFC 7396](https://tools.ietf.org/html/rfc7386)
8+
- remove support of `cql-json` in Filter extension ([#840](https://github.com/stac-utils/stac-fastapi/pull/840))
9+
10+
### Fixed
11+
12+
- add `py.typed` to package distributions ([#842](https://github.com/stac-utils/stac-fastapi/pull/842))
13+
- update/fix type informations ([#842](https://github.com/stac-utils/stac-fastapi/pull/842))
14+
- pin `stac_pydantic` to `>=3.3.0` for the correct import path of `stac_pydantic.shared.SearchDatetime` ([#844](https://github.com/stac-utils/stac-fastapi/pull/844))
15+
16+
## [5.2.1] - 2025-04-18
17+
18+
### Fixed
19+
20+
- avoid future deprecation for pydantic.Field and use `json_schema_extra` instead of `openapi_examples`
21+
- use `orjson` based JSONResponse when available
22+
- changed from `AssertionError` to `HTTPException` for **bbox** parsing exceptions
23+
- update `$schema` in Filter's extension client responses to match OGC Feature specification
24+
25+
### Added
26+
27+
- add response model for `/_mgmt/health` endpoint
628

729
## [5.2.0] - 2025-04-18
830

@@ -153,7 +175,7 @@
153175

154176
## [3.0.0] - 2024-07-29
155177

156-
Full changelog: https://stac-utils.github.io/stac-fastapi/migrations/v3.0.0/#changelog
178+
Full changelog: https://stac-utils.github.io/stac-fastapi/migrations/v3.0.0/#changelog
157179
**Changes since 3.0.0b3:**
158180

159181
### Changed
@@ -616,7 +638,8 @@ Full changelog: https://stac-utils.github.io/stac-fastapi/migrations/v3.0.0/#cha
616638

617639
* First PyPi release!
618640

619-
[Unreleased]: <https://github.com/stac-utils/stac-fastapi/compare/5.2.0..main>
641+
[Unreleased]: <https://github.com/stac-utils/stac-fastapi/compare/5.2.1..main>
642+
[5.2.1]: <https://github.com/stac-utils/stac-fastapi/compare/5.2.0..5.2.1>
620643
[5.2.0]: <https://github.com/stac-utils/stac-fastapi/compare/5.1.1..5.2.0>
621644
[5.1.1]: <https://github.com/stac-utils/stac-fastapi/compare/5.1.0..5.1.1>
622645
[5.1.0]: <https://github.com/stac-utils/stac-fastapi/compare/5.0.3..5.1.0>

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5.2.0
1+
5.2.1

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ section-order = ["future", "standard-library", "third-party", "first-party", "lo
2323
[tool.ruff.format]
2424
quote-style = "double"
2525

26+
[tool.mypy]
27+
ignore_missing_imports = true
28+
namespace_packages = true
29+
explicit_package_bases = true
30+
exclude = ["tests", ".venv"]
31+
2632
[tool.bumpversion]
27-
current_version = "5.2.0"
33+
current_version = "5.2.1"
2834
parse = """(?x)
2935
(?P<major>\\d+)\\.
3036
(?P<minor>\\d+)\\.

stac_fastapi/api/stac_fastapi/api/app.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from stac_pydantic.shared import MimeTypes
1313
from stac_pydantic.version import STAC_VERSION
1414
from starlette.middleware import Middleware
15-
from starlette.responses import JSONResponse, Response
15+
from starlette.responses import Response
1616

1717
from stac_fastapi.api.errors import DEFAULT_STATUS_CODES, add_exception_handlers
1818
from stac_fastapi.api.middleware import CORSMiddleware, ProxyHeaderMiddleware
@@ -21,8 +21,10 @@
2121
CollectionUri,
2222
EmptyRequest,
2323
GeoJSONResponse,
24+
HealthCheck,
2425
ItemCollectionUri,
2526
ItemUri,
27+
JSONResponse,
2628
)
2729
from stac_fastapi.api.openapi import update_openapi
2830
from stac_fastapi.api.routes import (
@@ -108,7 +110,7 @@ class StacApi:
108110
),
109111
takes_self=True,
110112
),
111-
converter=update_openapi,
113+
converter=update_openapi, # type: ignore
112114
)
113115
router: APIRouter = attr.ib(default=attr.Factory(APIRouter))
114116
search_get_request_model: Type[BaseSearchGetRequest] = attr.ib(
@@ -396,12 +398,15 @@ async def ping():
396398
mgmt_router.add_api_route(
397399
name="Health",
398400
path="/_mgmt/health",
399-
response_model=Dict,
401+
response_model=(
402+
HealthCheck if self.settings.enable_response_models else None
403+
),
400404
responses={
401405
200: {
402406
"content": {
403407
MimeTypes.json.value: {},
404408
},
409+
"model": HealthCheck,
405410
},
406411
},
407412
response_class=self.response_class,

stac_fastapi/api/stac_fastapi/api/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from fastapi.exceptions import RequestValidationError, ResponseValidationError
99
from starlette import status
1010
from starlette.requests import Request
11-
from starlette.responses import JSONResponse
1211

12+
from stac_fastapi.api.models import JSONResponse
1313
from stac_fastapi.types.errors import (
1414
ConflictError,
1515
DatabaseError,

stac_fastapi/api/stac_fastapi/api/models.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Api request/response models."""
22

3-
from typing import List, Optional, Type, Union
3+
from typing import List, Literal, Optional, Type, Union
44

55
import attr
66
from fastapi import Path, Query
@@ -56,7 +56,8 @@ def create_request_model(
5656
for model in models:
5757
for k, field_info in model.model_fields.items():
5858
fields[k] = (field_info.annotation, field_info)
59-
return create_model(model_name, **fields, __base__=base_model)
59+
60+
return create_model(model_name, **fields, __base__=base_model) # type: ignore
6061

6162
raise TypeError("Mixed Request Model types. Check extension request types.")
6263

@@ -121,7 +122,7 @@ class ItemCollectionUri(APIRequest, DatetimeMixin):
121122
description="Limits the number of results that are included in each page of the response (capped to 10_000)." # noqa: E501
122123
),
123124
] = attr.ib(default=10)
124-
bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter)
125+
bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter) # type: ignore
125126
datetime: DateTimeQueryType = attr.ib(default=None, validator=_validate_datetime)
126127

127128

@@ -135,3 +136,9 @@ class JSONSchemaResponse(JSONResponse):
135136
"""JSON with custom, vendor content-type."""
136137

137138
media_type = "application/schema+json"
139+
140+
141+
class HealthCheck(BaseModel, extra="allow"):
142+
"""health check response model."""
143+
144+
status: Literal["UP", "DOWN"]

stac_fastapi/api/stac_fastapi/api/openapi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from fastapi import FastAPI
44
from starlette.requests import Request
5-
from starlette.responses import JSONResponse, Response
5+
from starlette.responses import Response
66
from starlette.routing import Route, request_response
77

88

@@ -22,7 +22,7 @@ def update_openapi(app: FastAPI) -> FastAPI:
2222
# Create a patched endpoint function that modifies the content type of the response
2323
async def patched_openapi_endpoint(req: Request) -> Response:
2424
# Get the response from the old endpoint function
25-
response: JSONResponse = await old_endpoint(req)
25+
response = await old_endpoint(req)
2626
# Update the content type header in place
2727
response.headers["content-type"] = "application/vnd.oai.openapi+json;version=3.0"
2828
# Return the updated response

stac_fastapi/api/stac_fastapi/api/py.typed

Whitespace-only changes.

stac_fastapi/api/stac_fastapi/api/routes.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import copy
44
import functools
55
import inspect
6-
from typing import Any, Callable, Dict, List, Optional, Type, TypedDict, Union
6+
from typing import Any, Awaitable, Callable, Dict, List, Optional, Type, TypedDict, Union
77

88
from fastapi import Depends, FastAPI, params
99
from fastapi.datastructures import DefaultPlaceholder
@@ -39,7 +39,7 @@ async def run(*args, **kwargs):
3939
def create_async_endpoint(
4040
func: Callable,
4141
request_model: Union[Type[APIRequest], Type[BaseModel], Dict],
42-
):
42+
) -> Callable[[Any, Any], Awaitable[Any]]:
4343
"""Wrap a function in a coroutine which may be used to create a FastAPI endpoint.
4444
4545
Synchronous functions are executed asynchronously using a background thread.
@@ -48,32 +48,28 @@ def create_async_endpoint(
4848
if not inspect.iscoroutinefunction(func):
4949
func = sync_to_async(func)
5050

51-
if issubclass(request_model, APIRequest):
51+
_endpoint: Callable[[Any, Any], Awaitable[Any]]
5252

53-
async def _endpoint(
54-
request: Request,
55-
request_data: request_model = Depends(), # type:ignore
56-
):
53+
if isinstance(request_model, dict):
54+
55+
async def _endpoint(request: Request, request_data: Dict[str, Any]):
56+
"""Endpoint."""
57+
return _wrap_response(await func(request_data, request=request))
58+
59+
elif issubclass(request_model, APIRequest):
60+
61+
async def _endpoint(request: Request, request_data=Depends(request_model)):
5762
"""Endpoint."""
5863
return _wrap_response(await func(request=request, **request_data.kwargs()))
5964

6065
elif issubclass(request_model, BaseModel):
6166

62-
async def _endpoint(
63-
request: Request,
64-
request_data: request_model, # type:ignore
65-
):
67+
async def _endpoint(request: Request, request_data: request_model): # type: ignore
6668
"""Endpoint."""
6769
return _wrap_response(await func(request_data, request=request))
6870

6971
else:
70-
71-
async def _endpoint(
72-
request: Request,
73-
request_data: Dict[str, Any], # type:ignore
74-
):
75-
"""Endpoint."""
76-
return _wrap_response(await func(request_data, request=request))
72+
raise ValueError(f"Unsupported type for request model {type(request_model)}")
7773

7874
return _endpoint
7975

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Library version."""
22

3-
__version__ = "5.2.0"
3+
__version__ = "5.2.1"

stac_fastapi/api/tests/test_models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ def test_create_get_request_model():
4343
model = request_model(bbox="0,0,0,1,1,1")
4444
assert model.bbox == (0.0, 0.0, 0.0, 1.0, 1.0, 1.0)
4545

46-
with pytest.raises(AssertionError):
46+
with pytest.raises(HTTPException):
47+
request_model(bbox="a,b")
48+
49+
with pytest.raises(HTTPException):
4750
request_model(bbox="0,0,0,1,1")
4851

4952
model = request_model(

stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import attr
66

7-
from stac_fastapi.types import stac
7+
from stac_fastapi.types.stac import ItemCollection
88

99
from .request import BaseCollectionSearchPostRequest
1010

@@ -18,7 +18,7 @@ async def post_all_collections(
1818
self,
1919
search_request: BaseCollectionSearchPostRequest,
2020
**kwargs,
21-
) -> stac.ItemCollection:
21+
) -> ItemCollection:
2222
"""Get all available collections.
2323
2424
Called with `POST /collections`.
@@ -37,7 +37,7 @@ class BaseCollectionSearchClient(abc.ABC):
3737
@abc.abstractmethod
3838
def post_all_collections(
3939
self, search_request: BaseCollectionSearchPostRequest, **kwargs
40-
) -> stac.ItemCollection:
40+
) -> ItemCollection:
4141
"""Get all available collections.
4242
4343
Called with `POST /collections`.

stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/collection_search.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ class CollectionSearchExtension(ApiExtension):
4747
the extension
4848
"""
4949

50-
GET: BaseCollectionSearchGetRequest = attr.ib(default=BaseCollectionSearchGetRequest)
51-
POST = None
50+
GET: BaseCollectionSearchGetRequest = attr.ib(default=BaseCollectionSearchGetRequest) # type: ignore
51+
POST = attr.ib(init=False)
5252

5353
conformance_classes: List[str] = attr.ib(
5454
default=[
55-
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
56-
CollectionSearchConformanceClasses.BASIS,
55+
CollectionSearchConformanceClasses.COLLECTIONSEARCH.value,
56+
CollectionSearchConformanceClasses.BASIS.value,
5757
]
5858
)
5959
schema_href: Optional[str] = attr.ib(default=None)
@@ -73,13 +73,14 @@ def register(self, app: FastAPI) -> None:
7373
def from_extensions(
7474
cls,
7575
extensions: List[ApiExtension],
76+
*,
7677
schema_href: Optional[str] = None,
7778
) -> "CollectionSearchExtension":
7879
"""Create CollectionSearchExtension object from extensions."""
7980

8081
conformance_classes = [
81-
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
82-
CollectionSearchConformanceClasses.BASIS,
82+
CollectionSearchConformanceClasses.COLLECTIONSEARCH.value,
83+
CollectionSearchConformanceClasses.BASIS.value,
8384
]
8485
for ext in extensions:
8586
conformance_classes.extend(ext.conformance_classes)
@@ -119,15 +120,15 @@ class CollectionSearchPostExtension(CollectionSearchExtension):
119120
settings: ApiSettings = attr.ib()
120121
conformance_classes: List[str] = attr.ib(
121122
default=[
122-
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
123-
CollectionSearchConformanceClasses.BASIS,
123+
CollectionSearchConformanceClasses.COLLECTIONSEARCH.value,
124+
CollectionSearchConformanceClasses.BASIS.value,
124125
]
125126
)
126127
schema_href: Optional[str] = attr.ib(default=None)
127128
router: APIRouter = attr.ib(factory=APIRouter)
128129

129-
GET: BaseCollectionSearchGetRequest = attr.ib(default=BaseCollectionSearchGetRequest)
130-
POST: BaseCollectionSearchPostRequest = attr.ib(
130+
GET: BaseCollectionSearchGetRequest = attr.ib(default=BaseCollectionSearchGetRequest) # type: ignore
131+
POST: BaseCollectionSearchPostRequest = attr.ib( # type: ignore
131132
default=BaseCollectionSearchPostRequest
132133
)
133134

@@ -163,19 +164,19 @@ def register(self, app: FastAPI) -> None:
163164
app.include_router(self.router)
164165

165166
@classmethod
166-
def from_extensions(
167+
def from_extensions( # type: ignore
167168
cls,
168169
extensions: List[ApiExtension],
169170
*,
171+
schema_href: Optional[str] = None,
170172
client: Union[AsyncBaseCollectionSearchClient, BaseCollectionSearchClient],
171173
settings: ApiSettings,
172-
schema_href: Optional[str] = None,
173174
router: Optional[APIRouter] = None,
174175
) -> "CollectionSearchPostExtension":
175176
"""Create CollectionSearchPostExtension object from extensions."""
176177
conformance_classes = [
177-
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
178-
CollectionSearchConformanceClasses.BASIS,
178+
CollectionSearchConformanceClasses.COLLECTIONSEARCH.value,
179+
CollectionSearchConformanceClasses.BASIS.value,
179180
]
180181
for ext in extensions:
181182
conformance_classes.extend(ext.conformance_classes)

0 commit comments

Comments
 (0)