stac-fastapi.api 6.3.0__tar.gz → 6.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/PKG-INFO +1 -1
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/app.py +17 -17
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/errors.py +3 -2
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/middleware.py +6 -7
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/models.py +18 -19
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/routes.py +8 -7
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/version.py +1 -1
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/.gitignore +0 -0
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/README.md +0 -0
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/pyproject.toml +0 -0
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/__init__.py +0 -0
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/config.py +0 -0
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/openapi.py +0 -0
- {stac_fastapi_api-6.3.0 → stac_fastapi_api-6.3.1}/stac_fastapi/api/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stac-fastapi-api
|
|
3
|
-
Version: 6.3.
|
|
3
|
+
Version: 6.3.1
|
|
4
4
|
Summary: An implementation of STAC API based on the FastAPI framework.
|
|
5
5
|
Project-URL: Homepage, https://github.com/stac-utils/stac-fastapi
|
|
6
6
|
Author-email: ArturoAI <engineering@arturo.ai>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Fastapi app creation."""
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
5
|
|
|
6
6
|
import attr
|
|
7
7
|
from brotli_asgi import BrotliMiddleware
|
|
@@ -78,9 +78,9 @@ class StacApi:
|
|
|
78
78
|
"""
|
|
79
79
|
|
|
80
80
|
settings: ApiSettings = attr.ib()
|
|
81
|
-
client:
|
|
82
|
-
extensions:
|
|
83
|
-
exceptions:
|
|
81
|
+
client: AsyncBaseCoreClient | BaseCoreClient = attr.ib()
|
|
82
|
+
extensions: list[ApiExtension] = attr.ib(default=attr.Factory(list))
|
|
83
|
+
exceptions: dict[type[Exception], int] = attr.ib(
|
|
84
84
|
default=attr.Factory(lambda: DEFAULT_STATUS_CODES)
|
|
85
85
|
)
|
|
86
86
|
title: str = attr.ib(
|
|
@@ -115,14 +115,14 @@ class StacApi:
|
|
|
115
115
|
converter=update_openapi, # type: ignore
|
|
116
116
|
)
|
|
117
117
|
router: APIRouter = attr.ib(default=attr.Factory(APIRouter))
|
|
118
|
-
search_get_request_model:
|
|
119
|
-
search_post_request_model:
|
|
120
|
-
collections_get_request_model:
|
|
121
|
-
collection_get_request_model:
|
|
122
|
-
items_get_request_model:
|
|
123
|
-
item_get_request_model:
|
|
124
|
-
response_class:
|
|
125
|
-
middlewares:
|
|
118
|
+
search_get_request_model: type[APIRequest] = attr.ib(default=BaseSearchGetRequest)
|
|
119
|
+
search_post_request_model: type[BaseModel] = attr.ib(default=BaseSearchPostRequest)
|
|
120
|
+
collections_get_request_model: type[APIRequest] = attr.ib(default=EmptyRequest)
|
|
121
|
+
collection_get_request_model: type[APIRequest] = attr.ib(default=CollectionUri)
|
|
122
|
+
items_get_request_model: type[APIRequest] = attr.ib(default=ItemCollectionUri)
|
|
123
|
+
item_get_request_model: type[APIRequest] = attr.ib(default=ItemUri)
|
|
124
|
+
response_class: type[Response] = attr.ib(default=JSONResponse)
|
|
125
|
+
middlewares: list[Middleware] = attr.ib(
|
|
126
126
|
default=attr.Factory(
|
|
127
127
|
lambda: [
|
|
128
128
|
Middleware(BrotliMiddleware),
|
|
@@ -138,12 +138,12 @@ class StacApi:
|
|
|
138
138
|
]
|
|
139
139
|
)
|
|
140
140
|
)
|
|
141
|
-
route_dependencies:
|
|
142
|
-
health_check:
|
|
141
|
+
route_dependencies: list[tuple[list[Scope], list[Depends]]] = attr.ib(default=[])
|
|
142
|
+
health_check: Callable[[], dict] | Callable[[], Awaitable[dict]] = attr.ib(
|
|
143
143
|
default=lambda: {"status": "UP"}
|
|
144
144
|
)
|
|
145
145
|
|
|
146
|
-
def get_extension(self, extension:
|
|
146
|
+
def get_extension(self, extension: type[ApiExtension]) -> ApiExtension | None:
|
|
147
147
|
"""Get an extension.
|
|
148
148
|
|
|
149
149
|
Args:
|
|
@@ -387,7 +387,7 @@ class StacApi:
|
|
|
387
387
|
mgmt_router.add_api_route(
|
|
388
388
|
name="Ping",
|
|
389
389
|
path="/_mgmt/ping",
|
|
390
|
-
response_model=
|
|
390
|
+
response_model=dict,
|
|
391
391
|
responses={
|
|
392
392
|
200: {
|
|
393
393
|
"content": {
|
|
@@ -423,7 +423,7 @@ class StacApi:
|
|
|
423
423
|
self.app.include_router(mgmt_router, tags=["Liveliness/Readiness"])
|
|
424
424
|
|
|
425
425
|
def add_route_dependencies(
|
|
426
|
-
self, scopes:
|
|
426
|
+
self, scopes: list[Scope], dependencies: list[Depends]
|
|
427
427
|
) -> None:
|
|
428
428
|
"""Add custom dependencies to routes.
|
|
429
429
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Error handling."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import TypedDict
|
|
5
6
|
|
|
6
7
|
from fastapi import FastAPI
|
|
7
8
|
from fastapi.encoders import jsonable_encoder
|
|
@@ -69,7 +70,7 @@ def exception_handler_factory(status_code: int) -> Callable:
|
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
def add_exception_handlers(
|
|
72
|
-
app: FastAPI, status_codes:
|
|
73
|
+
app: FastAPI, status_codes: dict[type[Exception], int]
|
|
73
74
|
) -> None:
|
|
74
75
|
"""Add exception handlers to the FastAPI application.
|
|
75
76
|
|
|
@@ -5,7 +5,6 @@ import re
|
|
|
5
5
|
import typing
|
|
6
6
|
import warnings
|
|
7
7
|
from http.client import HTTP_PORT, HTTPS_PORT
|
|
8
|
-
from typing import List, Optional, Tuple
|
|
9
8
|
|
|
10
9
|
from starlette.middleware.cors import CORSMiddleware as _CORSMiddleware
|
|
11
10
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
@@ -29,7 +28,7 @@ class CORSMiddleware(_CORSMiddleware):
|
|
|
29
28
|
),
|
|
30
29
|
allow_headers: typing.Sequence[str] = ("Content-Type",),
|
|
31
30
|
allow_credentials: bool = False,
|
|
32
|
-
allow_origin_regex:
|
|
31
|
+
allow_origin_regex: str | None = None,
|
|
33
32
|
expose_headers: typing.Sequence[str] = (),
|
|
34
33
|
max_age: int = 600,
|
|
35
34
|
**kwargs: typing.Any,
|
|
@@ -91,7 +90,7 @@ class ProxyHeaderMiddleware:
|
|
|
91
90
|
|
|
92
91
|
await self.app(scope, receive, send)
|
|
93
92
|
|
|
94
|
-
def _get_forwarded_url_parts(self, scope: Scope) ->
|
|
93
|
+
def _get_forwarded_url_parts(self, scope: Scope) -> tuple[str, str, int]:
|
|
95
94
|
proto = scope.get("scheme", "http")
|
|
96
95
|
# Assume default port based on protocol, can be overridden later
|
|
97
96
|
port = 443 if proto == "https" else 80
|
|
@@ -107,7 +106,7 @@ class ProxyHeaderMiddleware:
|
|
|
107
106
|
domain, port = scope["server"]
|
|
108
107
|
port = int(port)
|
|
109
108
|
|
|
110
|
-
forwarded_port:
|
|
109
|
+
forwarded_port: str | None = None
|
|
111
110
|
forwarding_occurred = any(
|
|
112
111
|
key
|
|
113
112
|
in [
|
|
@@ -144,8 +143,8 @@ class ProxyHeaderMiddleware:
|
|
|
144
143
|
return (proto, domain, port)
|
|
145
144
|
|
|
146
145
|
def _get_header_value_by_name(
|
|
147
|
-
self, scope: Scope, header_name: str, default_value:
|
|
148
|
-
) ->
|
|
146
|
+
self, scope: Scope, header_name: str, default_value: str | None = None
|
|
147
|
+
) -> str | None:
|
|
149
148
|
headers = scope["headers"]
|
|
150
149
|
candidates = [
|
|
151
150
|
value.decode() for key, value in headers if key.decode() == header_name
|
|
@@ -155,7 +154,7 @@ class ProxyHeaderMiddleware:
|
|
|
155
154
|
@staticmethod
|
|
156
155
|
def _replace_header_value_by_name(
|
|
157
156
|
scope: Scope, header_name: str, new_value: str
|
|
158
|
-
) ->
|
|
157
|
+
) -> list[tuple[str, str]]:
|
|
159
158
|
return [
|
|
160
159
|
(name, value)
|
|
161
160
|
for name, value in scope["headers"]
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Api request/response models."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Annotated, Literal, cast
|
|
4
4
|
|
|
5
5
|
import attr
|
|
6
6
|
from fastapi import Path, Query
|
|
7
7
|
from pydantic import BaseModel, create_model
|
|
8
8
|
from stac_pydantic.shared import BBox
|
|
9
|
-
from typing_extensions import Annotated
|
|
10
9
|
|
|
11
10
|
from stac_fastapi.types.extension import ApiExtension
|
|
12
11
|
from stac_fastapi.types.search import (
|
|
@@ -29,14 +28,14 @@ except ImportError: # pragma: nocover
|
|
|
29
28
|
|
|
30
29
|
def create_request_model(
|
|
31
30
|
model_name="SearchGetRequest",
|
|
32
|
-
base_model:
|
|
33
|
-
extensions:
|
|
34
|
-
mixins:
|
|
31
|
+
base_model: type[BaseModel] | type[APIRequest] = BaseSearchGetRequest,
|
|
32
|
+
extensions: list[ApiExtension] | None = None,
|
|
33
|
+
mixins: list[type[BaseModel]] | list[type[APIRequest]] | None = None,
|
|
35
34
|
request_type: str = "GET",
|
|
36
|
-
) ->
|
|
35
|
+
) -> type[BaseModel] | type[APIRequest]:
|
|
37
36
|
"""Create a pydantic model for validating request bodies."""
|
|
38
37
|
fields = {}
|
|
39
|
-
extension_models:
|
|
38
|
+
extension_models: list[type[BaseModel] | type[APIRequest]] = []
|
|
40
39
|
|
|
41
40
|
# Check extensions for additional parameters to search
|
|
42
41
|
for extension in extensions or []:
|
|
@@ -50,7 +49,7 @@ def create_request_model(
|
|
|
50
49
|
# Handle GET requests
|
|
51
50
|
if all([issubclass(m, APIRequest) for m in models]):
|
|
52
51
|
get_model = attr.make_class(model_name, attrs={}, bases=tuple(models))
|
|
53
|
-
return cast(
|
|
52
|
+
return cast(type[APIRequest], get_model)
|
|
54
53
|
|
|
55
54
|
# Handle POST requests
|
|
56
55
|
elif all([issubclass(m, BaseModel) for m in models]):
|
|
@@ -59,15 +58,15 @@ def create_request_model(
|
|
|
59
58
|
fields[k] = (field_info.annotation, field_info)
|
|
60
59
|
|
|
61
60
|
post_model = create_model(model_name, **fields, __base__=base_model) # type: ignore
|
|
62
|
-
return cast(
|
|
61
|
+
return cast(type[BaseModel], post_model)
|
|
63
62
|
|
|
64
63
|
raise TypeError("Mixed Request Model types. Check extension request types.")
|
|
65
64
|
|
|
66
65
|
|
|
67
66
|
def create_get_request_model(
|
|
68
|
-
extensions:
|
|
69
|
-
base_model:
|
|
70
|
-
) ->
|
|
67
|
+
extensions: list[ApiExtension] | None,
|
|
68
|
+
base_model: type[APIRequest] = BaseSearchGetRequest,
|
|
69
|
+
) -> type[APIRequest]:
|
|
71
70
|
"""Wrap create_request_model to create the GET request model."""
|
|
72
71
|
model = create_request_model(
|
|
73
72
|
"SearchGetRequest",
|
|
@@ -75,13 +74,13 @@ def create_get_request_model(
|
|
|
75
74
|
extensions=extensions,
|
|
76
75
|
request_type="GET",
|
|
77
76
|
)
|
|
78
|
-
return cast(
|
|
77
|
+
return cast(type[APIRequest], model)
|
|
79
78
|
|
|
80
79
|
|
|
81
80
|
def create_post_request_model(
|
|
82
|
-
extensions:
|
|
83
|
-
base_model:
|
|
84
|
-
) ->
|
|
81
|
+
extensions: list[ApiExtension] | None,
|
|
82
|
+
base_model: type[BaseModel] = BaseSearchPostRequest,
|
|
83
|
+
) -> type[BaseModel]:
|
|
85
84
|
"""Wrap create_request_model to create the POST request model."""
|
|
86
85
|
model = create_request_model(
|
|
87
86
|
"SearchPostRequest",
|
|
@@ -89,7 +88,7 @@ def create_post_request_model(
|
|
|
89
88
|
extensions=extensions,
|
|
90
89
|
request_type="POST",
|
|
91
90
|
)
|
|
92
|
-
return cast(
|
|
91
|
+
return cast(type[BaseModel], model)
|
|
93
92
|
|
|
94
93
|
|
|
95
94
|
@attr.s
|
|
@@ -120,12 +119,12 @@ class ItemCollectionUri(APIRequest, DatetimeMixin):
|
|
|
120
119
|
|
|
121
120
|
collection_id: Annotated[str, Path(description="Collection ID")] = attr.ib()
|
|
122
121
|
limit: Annotated[
|
|
123
|
-
|
|
122
|
+
Limit | None,
|
|
124
123
|
Query(
|
|
125
124
|
description="Limits the number of results that are included in each page of the response (capped to 10_000)." # noqa: E501
|
|
126
125
|
),
|
|
127
126
|
] = attr.ib(default=10)
|
|
128
|
-
bbox:
|
|
127
|
+
bbox: BBox | None = attr.ib(default=None, converter=_bbox_converter) # type: ignore [misc]
|
|
129
128
|
datetime: DateTimeQueryType = attr.ib(default=None, validator=_validate_datetime)
|
|
130
129
|
|
|
131
130
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import copy
|
|
4
4
|
import functools
|
|
5
5
|
import inspect
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Awaitable, Callable
|
|
7
|
+
from typing import Any, TypedDict
|
|
7
8
|
|
|
8
9
|
from fastapi import Depends, FastAPI, params
|
|
9
10
|
from fastapi.datastructures import DefaultPlaceholder
|
|
@@ -38,7 +39,7 @@ def sync_to_async(func):
|
|
|
38
39
|
|
|
39
40
|
def create_async_endpoint(
|
|
40
41
|
func: Callable,
|
|
41
|
-
request_model:
|
|
42
|
+
request_model: type[APIRequest] | type[BaseModel] | dict,
|
|
42
43
|
) -> Callable[[Any, Any], Awaitable[Any]]:
|
|
43
44
|
"""Wrap a function in a coroutine which may be used to create a FastAPI endpoint.
|
|
44
45
|
|
|
@@ -52,7 +53,7 @@ def create_async_endpoint(
|
|
|
52
53
|
|
|
53
54
|
if isinstance(request_model, dict):
|
|
54
55
|
|
|
55
|
-
async def _endpoint(request: Request, request_data:
|
|
56
|
+
async def _endpoint(request: Request, request_data: dict[str, Any]):
|
|
56
57
|
"""Endpoint."""
|
|
57
58
|
return _wrap_response(await func(request_data, request=request))
|
|
58
59
|
|
|
@@ -80,7 +81,7 @@ class Scope(TypedDict, total=False):
|
|
|
80
81
|
# https://github.com/encode/starlette/blob/6af5c515e0a896cbf3f86ee043b88f6c24200bcf/starlette/types.py#L3
|
|
81
82
|
path: str
|
|
82
83
|
method: str
|
|
83
|
-
type:
|
|
84
|
+
type: str | None
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
def _is_endpoint_route(route: BaseRoute) -> bool:
|
|
@@ -91,7 +92,7 @@ def _is_endpoint_route(route: BaseRoute) -> bool:
|
|
|
91
92
|
|
|
92
93
|
|
|
93
94
|
def _apply_dependencies_to_route(
|
|
94
|
-
route: BaseRoute, scopes:
|
|
95
|
+
route: BaseRoute, scopes: list[Scope], dependencies: list[params.Depends]
|
|
95
96
|
) -> None:
|
|
96
97
|
"""Apply dependencies to a single route matching the given scopes."""
|
|
97
98
|
for scope in scopes:
|
|
@@ -123,7 +124,7 @@ def _apply_dependencies_to_route(
|
|
|
123
124
|
|
|
124
125
|
|
|
125
126
|
def add_route_dependencies(
|
|
126
|
-
routes:
|
|
127
|
+
routes: list[BaseRoute], scopes: list[Scope], dependencies: list[params.Depends]
|
|
127
128
|
) -> None:
|
|
128
129
|
"""Add dependencies to routes.
|
|
129
130
|
|
|
@@ -158,7 +159,7 @@ def add_direct_response(app: FastAPI) -> None:
|
|
|
158
159
|
ref: https://gist.github.com/Zaczero/00f3a2679ebc0a25eb938ed82bc63553
|
|
159
160
|
"""
|
|
160
161
|
|
|
161
|
-
def wrap_endpoint(endpoint: Callable, cls:
|
|
162
|
+
def wrap_endpoint(endpoint: Callable, cls: type[Response]):
|
|
162
163
|
@functools.wraps(endpoint)
|
|
163
164
|
async def wrapper(*args, **kwargs):
|
|
164
165
|
content = await endpoint(*args, **kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|