stac-fastapi v3.0 Migration Guide¶
This document aims to help you update your application from stac-fastapi 2.5 to 3.0.0.
CHANGELOG¶
Removed¶
- Removed the
Context
extension - Removed
stac_fastapi.api.openapi.config_openapi
method andstac_fastapi.api.openapi.VndOaiResponse
class - Removed
response_class
argument instac_fastapi.api.routes.create_async_endpoint
method - Removed
filter_fields
property instac_fastapi.extensions.core.fields.request.PostFieldsExtension
class - Removed
pagination_extension
attribute instac_fastapi.api.app.StacApi
- Removed
default_includes
fromstac_fastapi.types.config.ApiSettings
(#706) - Removed use of
pagination_extension
inregister_get_item_collection
function (User now need to construct the request model and pass it usingitems_get_request_model
attribute) - Removed use of
FieldsExtension
instac_fastapi.api.app.StacApi
. NOTE: If users useFieldsExtension
, they HAVE TO handle skipping the model validation step by returning aJSONResponse
from thepost_search
andget_search
client methods - Removed
add_middleware
method inStacApi
object and let starlette handle the middleware stack creation (#721) - Removed
pystac
dependecy, as it was just used for a datetime-to-string function (#690) - Removed internal Search and Operator Types in favor of stac_pydantic Types (#625)
Changed¶
- Update to pydantic 2.0 (#625)
- Update stac-pydantic requirement to
~3.1
(#697) - Switch from
fastapi
tofastapi-slim
to avoid installing unwanted dependencies (#687) - Update FastAPI requirement to
>=0.111.0
- Moved
AsyncBaseFiltersClient
andBaseFiltersClient
classes instac_fastapi.extensions.core.filter.client
submodule - Add more openapi metadata in input models (#734)
- Use same
Limit
(capped to10_000
) for/items
andGET - /search
input models (#738) - Moved
GETPagination
,POSTPagination
,GETTokenPagination
andPOSTTokenPagination
tostac_fastapi.extensions.core.pagination.request
submodule (#717) - Added option for default route dependencies
*
can be used forpath
ormethod
to match all allowed route. (#705) - Moved
AsyncBaseFiltersClient
andBaseFiltersClient
classes instac_fastapi.extensions.core.filter.client
submodule (#704) - Replace Enum with
Literal
forFilterLang
. (#686) - Fix response model validation (#625)
- Use status code 201 for Item/Collection creation (#625)
- Add
response_class
attribute inFilterExtension
class - Add version pinning (
~=3.0
) for stac-fastapi submodules
Fixed¶
- Updated default
filter
language in filter extension's POST search request model to match the extension's documentation (#711) - Fix missing default (
None
) for optionalquery
attribute inQueryExtensionPostRequest
model (#701) - Make
str_to_interval
not return a tuple for single-value input (fixingdatetime
argument as passed toget_search
). (#692)
Added¶
- Add enhanced middleware configuration to the StacApi class, enabling specific middleware options and dynamic addition post-application initialization. (#442)
- Add response pydantic models to OpenAPI, even if model validation is turned off (#625)
- Add attributes to
stac_fastapi.api.app.StacApi
to enable customization of request model for: /collections
: collections_get_request_model, default toEmptyRequest
/collections/{collection_id}
: collection_get_request_model, default toCollectionUri
/collections/{collection_id}/items
: items_get_request_model, default toItemCollectionUri
/collections/{collection_id}/items/{item_id}
: item_get_request_model, default toItemUri
- Add Aggregation extension (#684)
- Add Free-text extension (#655)
- Add Collection-Search extension (#736, #739)
Dependencies¶
- pydantic~=2.0
- fastapi>=0.111
- stac-pydantic~=3.1
Most of the stac-fastapi's dependencies have been upgraded. Moving from pydantic v1 to v2 is mostly the one update bringing most breaking changes (see docs.pydantic.dev/latest/migration/).
In addition to pydantic v2 update, stac-pydantic
has been updated to better match the STAC and STAC-API specifications (see github.com/stac-utils/stac-pydantic/blob/main/CHANGELOG.md#310-2024-05-21)
Deprecation¶
-
the
ContextExtension
have been removed (see stac-utils/stac-pydantic!138) and was replaced by optionalNumberMatched
andNumberReturned
attributes, defined by the OGC features specification. -
stac_fastapi.api.config_openapi
method was removed (see stac-utils/stac-fastapi!523) -
passing
response_class
instac_fastapi.api.routes.create_async_endpoint
is now deprecated. The response class now has to be set when registering the endpoint to the application (see stac-utils/stac-fastapi#461) -
PostFieldsExtension.filter_fields
property has been removed.
Middlewares configuration¶
The StacApi.middlewares
attribute has been updated to accept a list of starlette.middleware.Middleware
. This enables dynamic configuration of middlewares (see stac-utils/stac-fastapi!442).
# before
class myMiddleware(mainMiddleware):
option1 = option1
option2 = option2
stac = StacApi(
middlewares=[
myMiddleware,
]
)
# now
stac = StacApi(
middlewares=[
Middleware(myMiddleware, option1, option2),
]
)
Request Models¶
In stac-fastapi v2.0, users could already customize both GET/POST search request models. For v3.0, we've added more attributes to enable other endpoints customization:
collections_get_request_model
: GET request model for the/collections
endpoint (default toEmptyRequest
)collection_get_request_model
: GET request model for the/collections/{collection_id}
endpoint (default tostac_fastapi.api.models.CollectionUri
)items_get_request_model
: GET request model for the/collections/{collection_id}/items
endpoint (default tostac_fastapi.api.models.ItemCollectionUri
)item_get_request_model
: GET request model for the/collections/{collection_id}/items/{item_id}
endpoint (default tostac_fastapi.api.models.ItemUri
)
# before
getSearchModel = create_request_model(
model_name="SearchGetRequest",
base_model=BaseSearchGetRequest
extensions=[...],
request_type="GET"
)
stac = StacApi(
search_get_request_model=getSearchModel,
search_post_request_model=...,
)
# now
@attr.s
class CollectionsRequest(APIRequest):
user: Annotated[str, Query(...)] = attr.ib()
stac = StacApi(
search_get_request_model=getSearchModel,
search_post_request_model=postSearchModel,
collections_get_request_model=CollectionsRequest,
collection_get_request_model=...,
items_get_request_model=...,
item_get_request_model=...,
)
APIRequest - GET Request Model¶
Most of the GET endpoints are configured with stac_fastapi.types.search.APIRequest
base class.
e.g the BaseSearchGetRequest, default for the GET - /search
endpoint:
@attr.s
class BaseSearchGetRequest(APIRequest):
"""Base arguments for GET Request."""
collections: Optional[List[str]] = attr.ib(default=None, converter=_collection_converter)
ids: Optional[List[str]] = attr.ib(default=None, converter=_ids_converter)
bbox: Optional[BBox] = attr.ib(default=None, converter=_bbox_converter)
intersects: Annotated[Optional[str], Query()] = attr.ib(default=None)
datetime: Optional[DateTimeType] = attr.ib(
default=None, converter=_datetime_converter
)
limit: Annotated[Optional[int], Query()] = attr.ib(default=10)
We use python attrs to construct those classes. Type Hint for each attribute is important and should be defined using Annotated[{type}, fastapi.Query()]
form.
@attr.s
class SomeRequest(APIRequest):
user_number: Annotated[Optional[int], Query(alias="user-number")] = attr.ib(default=None)
Note: when an attribute has a converter
(e.g _ids_converter
), the Type Hint should be defined directly in the converter:
def _ids_converter(
val: Annotated[
Optional[str],
Query(
description="Array of Item ids to return.",
),
] = None,
) -> Optional[List[str]]:
return str2list(val)
@attr.s
class BaseSearchGetRequest(APIRequest):
"""Base arguments for GET Request."""
ids: Optional[List[str]] = attr.ib(default=None, converter=_ids_converter)
Filter extension¶
default_includes
attribute has been removed from the ApiSettings
object. If you need defaults
includes you can overwrite the FieldExtension
models (see stac-utils/stac-fastapi!706).
# before
stac = StacApi(
extensions=[
FieldsExtension()
]
)
# now
class PostFieldsExtension(requests.PostFieldsExtension):
include: Optional[Set[str]] = Field(
default_factory=lambda: {
"id",
"type",
"stac_version",
"geometry",
"bbox",
"links",
"assets",
"properties.datetime",
"collection",
}
)
exclude: Optional[Set[str]] = set()
class FieldsExtensionPostRequest(BaseModel):
"""Additional fields and schema for the POST request."""
fields: Optional[PostFieldsExtension] = Field(PostFieldsExtension())
class FieldsExtension(FieldsExtensionBase):
"""Override the POST model"""
POST = FieldsExtensionPostRequest
from stac_fastapi.api.app import StacApi
stac = StacApi(
extensions=[
FieldsExtension()
]
)
Pagination extension¶
In stac-fastapi v3.0, we removed the pagination_extension
attribute in stac_fastapi.api.app.StacApi
. This attribute was used within the register_get_item_collection
to update the request model for the /collections/{collection_id}/items
endpoint.
It's now up to the user to create the request model and use the items_get_request_model=
attribute in the StacApi object.
# before
stac=StacApi(
pagination_extension=TokenPaginationExtension,
extension=[TokenPaginationExtension]
)
# now
items_get_request_model = create_request_model(
"ItemCollectionURI",
base_model=ItemCollectionUri,
mixins=[TokenPaginationExtension().GET],
)
stac=StacApi(
extension=[TokenPaginationExtension],
items_get_request_model=items_get_request_model,
)
Fields extension and model validation¶
When using the Fields
extension, the /search
endpoint should be able to return `invalid STAC Items. This creates an issue when model validation is enabled at the application level.
Previously when adding the FieldsExtension
to the extensions list and if setting output model validation, we were turning off the validation for both GET/POST /search
endpoints. This was by-passing validation even when users were not using the fields
options in requests.
In stac-fastapi
v3.0, implementers will have to by-pass the validation step at Client
level by returning JSONResponse
from the post_search
and get_search
client methods.
# before
class BadCoreClient(BaseCoreClient):
def post_search(
self, search_request: BaseSearchPostRequest, **kwargs
) -> stac.ItemCollection:
return {"not": "a proper stac item"}
def get_search(
self,
collections: Optional[List[str]] = None,
ids: Optional[List[str]] = None,
bbox: Optional[List[NumType]] = None,
intersects: Optional[str] = None,
datetime: Optional[Union[str, datetime]] = None,
limit: Optional[int] = 10,
**kwargs,
) -> stac.ItemCollection:
return {"not": "a proper stac item"}
# now
class BadCoreClient(BaseCoreClient):
def post_search(
self, search_request: BaseSearchPostRequest, **kwargs
) -> stac.ItemCollection:
resp = {"not": "a proper stac item"}
# if `fields` extension is enabled, then we return a JSONResponse
# to avoid Item validation
if getattr(search_request, "fields", None):
return JSONResponse(content=resp)
return resp
def get_search(
self,
collections: Optional[List[str]] = None,
ids: Optional[List[str]] = None,
bbox: Optional[List[NumType]] = None,
intersects: Optional[str] = None,
datetime: Optional[Union[str, datetime]] = None,
limit: Optional[int] = 10,
**kwargs,
) -> stac.ItemCollection:
resp = {"not": "a proper stac item"}
# if `fields` extension is enabled, then we return a JSONResponse
# to avoid Item validation
if "fields" in kwargs:
return JSONResponse(content=resp)
return resp