stac-fastapi.api 5.1.1__tar.gz → 5.2.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-5.1.1 → stac_fastapi_api-5.2.1}/PKG-INFO +9 -6
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/setup.py +9 -2
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/app.py +80 -67
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/errors.py +1 -1
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/middleware.py +2 -2
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/models.py +7 -1
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/openapi.py +2 -2
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/routes.py +36 -4
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/version.py +1 -1
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/PKG-INFO +9 -6
- stac_fastapi_api-5.2.1/stac_fastapi.api.egg-info/requires.txt +21 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_app.py +74 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_app_prefix.py +3 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_models.py +4 -1
- stac_fastapi_api-5.1.1/stac_fastapi.api.egg-info/requires.txt +0 -18
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/README.md +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/setup.cfg +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/__init__.py +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/config.py +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/SOURCES.txt +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/dependency_links.txt +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/not-zip-safe +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/top_level.txt +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_api.py +0 -0
- {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_middleware.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: stac-fastapi.api
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2.1
|
|
4
4
|
Summary: An implementation of STAC API based on the FastAPI framework.
|
|
5
5
|
Home-page: https://github.com/stac-utils/stac-fastapi
|
|
6
6
|
Author: Arturo Engineering
|
|
@@ -19,7 +19,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
19
19
|
Requires-Python: >=3.9
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
Requires-Dist: brotli_asgi
|
|
22
|
-
Requires-Dist: stac-fastapi.types~=5.
|
|
22
|
+
Requires-Dist: stac-fastapi.types~=5.2
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: httpx; extra == "dev"
|
|
25
25
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -30,9 +30,12 @@ Requires-Dist: requests; extra == "dev"
|
|
|
30
30
|
Provides-Extra: benchmark
|
|
31
31
|
Requires-Dist: pytest-benchmark; extra == "benchmark"
|
|
32
32
|
Provides-Extra: docs
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist: mkdocs
|
|
35
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: black>=23.10.1; extra == "docs"
|
|
34
|
+
Requires-Dist: mkdocs>=1.4.3; extra == "docs"
|
|
35
|
+
Requires-Dist: mkdocs-jupyter>=0.24.5; extra == "docs"
|
|
36
|
+
Requires-Dist: mkdocs-material[imaging]>=9.5; extra == "docs"
|
|
37
|
+
Requires-Dist: griffe-inherited-docstrings>=1.0.0; extra == "docs"
|
|
38
|
+
Requires-Dist: mkdocstrings[python]>=0.25.1; extra == "docs"
|
|
36
39
|
Dynamic: author
|
|
37
40
|
Dynamic: author-email
|
|
38
41
|
Dynamic: classifier
|
|
@@ -7,7 +7,7 @@ with open("README.md") as f:
|
|
|
7
7
|
|
|
8
8
|
install_requires = [
|
|
9
9
|
"brotli_asgi",
|
|
10
|
-
"stac-fastapi.types~=5.
|
|
10
|
+
"stac-fastapi.types~=5.2",
|
|
11
11
|
]
|
|
12
12
|
|
|
13
13
|
extra_reqs = {
|
|
@@ -22,7 +22,14 @@ extra_reqs = {
|
|
|
22
22
|
"benchmark": [
|
|
23
23
|
"pytest-benchmark",
|
|
24
24
|
],
|
|
25
|
-
"docs": [
|
|
25
|
+
"docs": [
|
|
26
|
+
"black>=23.10.1",
|
|
27
|
+
"mkdocs>=1.4.3",
|
|
28
|
+
"mkdocs-jupyter>=0.24.5",
|
|
29
|
+
"mkdocs-material[imaging]>=9.5",
|
|
30
|
+
"griffe-inherited-docstrings>=1.0.0",
|
|
31
|
+
"mkdocstrings[python]>=0.25.1",
|
|
32
|
+
],
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Fastapi app creation."""
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
from typing import Dict, List, Optional, Tuple, Type, Union
|
|
4
|
+
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, Type, Union
|
|
5
5
|
|
|
6
6
|
import attr
|
|
7
7
|
from brotli_asgi import BrotliMiddleware
|
|
@@ -12,7 +12,7 @@ from stac_pydantic.api.collections import Collections
|
|
|
12
12
|
from stac_pydantic.shared import MimeTypes
|
|
13
13
|
from stac_pydantic.version import STAC_VERSION
|
|
14
14
|
from starlette.middleware import Middleware
|
|
15
|
-
from starlette.responses import
|
|
15
|
+
from starlette.responses import Response
|
|
16
16
|
|
|
17
17
|
from stac_fastapi.api.errors import DEFAULT_STATUS_CODES, add_exception_handlers
|
|
18
18
|
from stac_fastapi.api.middleware import CORSMiddleware, ProxyHeaderMiddleware
|
|
@@ -21,11 +21,18 @@ from stac_fastapi.api.models import (
|
|
|
21
21
|
CollectionUri,
|
|
22
22
|
EmptyRequest,
|
|
23
23
|
GeoJSONResponse,
|
|
24
|
+
HealthCheck,
|
|
24
25
|
ItemCollectionUri,
|
|
25
26
|
ItemUri,
|
|
27
|
+
JSONResponse,
|
|
26
28
|
)
|
|
27
29
|
from stac_fastapi.api.openapi import update_openapi
|
|
28
|
-
from stac_fastapi.api.routes import
|
|
30
|
+
from stac_fastapi.api.routes import (
|
|
31
|
+
Scope,
|
|
32
|
+
add_direct_response,
|
|
33
|
+
add_route_dependencies,
|
|
34
|
+
create_async_endpoint,
|
|
35
|
+
)
|
|
29
36
|
from stac_fastapi.types.config import ApiSettings, Settings
|
|
30
37
|
from stac_fastapi.types.core import AsyncBaseCoreClient, BaseCoreClient
|
|
31
38
|
from stac_fastapi.types.extension import ApiExtension
|
|
@@ -62,6 +69,10 @@ class StacApi:
|
|
|
62
69
|
specified routes. This is useful
|
|
63
70
|
for applying custom auth requirements to routes defined elsewhere in
|
|
64
71
|
the application.
|
|
72
|
+
health_check:
|
|
73
|
+
A Callable which return application's `health` information.
|
|
74
|
+
Defaults to `def health: return {"status": "UP"}`
|
|
75
|
+
|
|
65
76
|
"""
|
|
66
77
|
|
|
67
78
|
settings: ApiSettings = attr.ib()
|
|
@@ -123,6 +134,9 @@ class StacApi:
|
|
|
123
134
|
)
|
|
124
135
|
)
|
|
125
136
|
route_dependencies: List[Tuple[List[Scope], List[Depends]]] = attr.ib(default=[])
|
|
137
|
+
health_check: Union[Callable[[], Dict], Callable[[], Awaitable[Dict]]] = attr.ib(
|
|
138
|
+
default=lambda: {"status": "UP"}
|
|
139
|
+
)
|
|
126
140
|
|
|
127
141
|
def get_extension(self, extension: Type[ApiExtension]) -> Optional[ApiExtension]:
|
|
128
142
|
"""Get an extension.
|
|
@@ -138,12 +152,8 @@ class StacApi:
|
|
|
138
152
|
return ext
|
|
139
153
|
return None
|
|
140
154
|
|
|
141
|
-
def register_landing_page(self):
|
|
142
|
-
"""Register landing page (GET /).
|
|
143
|
-
|
|
144
|
-
Returns:
|
|
145
|
-
None
|
|
146
|
-
"""
|
|
155
|
+
def register_landing_page(self) -> None:
|
|
156
|
+
"""Register landing page (GET /)."""
|
|
147
157
|
self.router.add_api_route(
|
|
148
158
|
name="Landing Page",
|
|
149
159
|
path="/",
|
|
@@ -165,12 +175,8 @@ class StacApi:
|
|
|
165
175
|
endpoint=create_async_endpoint(self.client.landing_page, EmptyRequest),
|
|
166
176
|
)
|
|
167
177
|
|
|
168
|
-
def register_conformance_classes(self):
|
|
169
|
-
"""Register conformance classes (GET /conformance).
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
None
|
|
173
|
-
"""
|
|
178
|
+
def register_conformance_classes(self) -> None:
|
|
179
|
+
"""Register conformance classes (GET /conformance)."""
|
|
174
180
|
self.router.add_api_route(
|
|
175
181
|
name="Conformance Classes",
|
|
176
182
|
path="/conformance",
|
|
@@ -192,12 +198,8 @@ class StacApi:
|
|
|
192
198
|
endpoint=create_async_endpoint(self.client.conformance, EmptyRequest),
|
|
193
199
|
)
|
|
194
200
|
|
|
195
|
-
def register_get_item(self):
|
|
196
|
-
"""Register get item endpoint (GET /collections/{collection_id}/items/{item_id}).
|
|
197
|
-
|
|
198
|
-
Returns:
|
|
199
|
-
None
|
|
200
|
-
"""
|
|
201
|
+
def register_get_item(self) -> None:
|
|
202
|
+
"""Register get item endpoint (GET /collections/{collection_id}/items/{item_id}).""" # noqa: E501
|
|
201
203
|
self.router.add_api_route(
|
|
202
204
|
name="Get Item",
|
|
203
205
|
path="/collections/{collection_id}/items/{item_id}",
|
|
@@ -219,12 +221,8 @@ class StacApi:
|
|
|
219
221
|
),
|
|
220
222
|
)
|
|
221
223
|
|
|
222
|
-
def register_post_search(self):
|
|
223
|
-
"""Register search endpoint (POST /search).
|
|
224
|
-
|
|
225
|
-
Returns:
|
|
226
|
-
None
|
|
227
|
-
"""
|
|
224
|
+
def register_post_search(self) -> None:
|
|
225
|
+
"""Register search endpoint (POST /search)."""
|
|
228
226
|
self.router.add_api_route(
|
|
229
227
|
name="Search",
|
|
230
228
|
path="/search",
|
|
@@ -248,12 +246,8 @@ class StacApi:
|
|
|
248
246
|
),
|
|
249
247
|
)
|
|
250
248
|
|
|
251
|
-
def register_get_search(self):
|
|
252
|
-
"""Register search endpoint (GET /search).
|
|
253
|
-
|
|
254
|
-
Returns:
|
|
255
|
-
None
|
|
256
|
-
"""
|
|
249
|
+
def register_get_search(self) -> None:
|
|
250
|
+
"""Register search endpoint (GET /search)."""
|
|
257
251
|
self.router.add_api_route(
|
|
258
252
|
name="Search",
|
|
259
253
|
path="/search",
|
|
@@ -277,12 +271,8 @@ class StacApi:
|
|
|
277
271
|
),
|
|
278
272
|
)
|
|
279
273
|
|
|
280
|
-
def register_get_collections(self):
|
|
281
|
-
"""Register get collections endpoint (GET /collections).
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
None
|
|
285
|
-
"""
|
|
274
|
+
def register_get_collections(self) -> None:
|
|
275
|
+
"""Register get collections endpoint (GET /collections)."""
|
|
286
276
|
self.router.add_api_route(
|
|
287
277
|
name="Get Collections",
|
|
288
278
|
path="/collections",
|
|
@@ -306,12 +296,8 @@ class StacApi:
|
|
|
306
296
|
),
|
|
307
297
|
)
|
|
308
298
|
|
|
309
|
-
def register_get_collection(self):
|
|
310
|
-
"""Register get collection endpoint (GET /collection/{collection_id}).
|
|
311
|
-
|
|
312
|
-
Returns:
|
|
313
|
-
None
|
|
314
|
-
"""
|
|
299
|
+
def register_get_collection(self) -> None:
|
|
300
|
+
"""Register get collection endpoint (GET /collection/{collection_id})."""
|
|
315
301
|
self.router.add_api_route(
|
|
316
302
|
name="Get Collection",
|
|
317
303
|
path="/collections/{collection_id}",
|
|
@@ -335,12 +321,8 @@ class StacApi:
|
|
|
335
321
|
),
|
|
336
322
|
)
|
|
337
323
|
|
|
338
|
-
def register_get_item_collection(self):
|
|
339
|
-
"""Register get item collection endpoint (GET /collection/{collection_id}/items).
|
|
340
|
-
|
|
341
|
-
Returns:
|
|
342
|
-
None
|
|
343
|
-
"""
|
|
324
|
+
def register_get_item_collection(self) -> None:
|
|
325
|
+
"""Register get item collection endpoint (GET /collection/{collection_id}/items).""" # noqa: E501
|
|
344
326
|
self.router.add_api_route(
|
|
345
327
|
name="Get ItemCollection",
|
|
346
328
|
path="/collections/{collection_id}/items",
|
|
@@ -364,7 +346,7 @@ class StacApi:
|
|
|
364
346
|
),
|
|
365
347
|
)
|
|
366
348
|
|
|
367
|
-
def register_core(self):
|
|
349
|
+
def register_core(self) -> None:
|
|
368
350
|
"""Register core STAC endpoints.
|
|
369
351
|
|
|
370
352
|
GET /
|
|
@@ -378,8 +360,6 @@ class StacApi:
|
|
|
378
360
|
|
|
379
361
|
Injects application logic (StacApi.client) into the API layer.
|
|
380
362
|
|
|
381
|
-
Returns:
|
|
382
|
-
None
|
|
383
363
|
"""
|
|
384
364
|
self.register_landing_page()
|
|
385
365
|
self.register_conformance_classes()
|
|
@@ -390,41 +370,71 @@ class StacApi:
|
|
|
390
370
|
self.register_get_collection()
|
|
391
371
|
self.register_get_item_collection()
|
|
392
372
|
|
|
393
|
-
def add_health_check(self):
|
|
373
|
+
def add_health_check(self) -> None:
|
|
394
374
|
"""Add a health check."""
|
|
375
|
+
|
|
395
376
|
mgmt_router = APIRouter(prefix=self.app.state.router_prefix)
|
|
396
377
|
|
|
397
|
-
@mgmt_router.get("/_mgmt/ping")
|
|
398
378
|
async def ping():
|
|
399
|
-
"""Liveliness
|
|
379
|
+
"""Liveliness probe."""
|
|
400
380
|
return {"message": "PONG"}
|
|
401
381
|
|
|
382
|
+
mgmt_router.add_api_route(
|
|
383
|
+
name="Ping",
|
|
384
|
+
path="/_mgmt/ping",
|
|
385
|
+
response_model=Dict,
|
|
386
|
+
responses={
|
|
387
|
+
200: {
|
|
388
|
+
"content": {
|
|
389
|
+
MimeTypes.json.value: {},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
response_class=self.response_class,
|
|
394
|
+
methods=["GET"],
|
|
395
|
+
endpoint=ping,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
mgmt_router.add_api_route(
|
|
399
|
+
name="Health",
|
|
400
|
+
path="/_mgmt/health",
|
|
401
|
+
response_model=(
|
|
402
|
+
HealthCheck if self.settings.enable_response_models else None
|
|
403
|
+
),
|
|
404
|
+
responses={
|
|
405
|
+
200: {
|
|
406
|
+
"content": {
|
|
407
|
+
MimeTypes.json.value: {},
|
|
408
|
+
},
|
|
409
|
+
"model": HealthCheck,
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
response_class=self.response_class,
|
|
413
|
+
methods=["GET"],
|
|
414
|
+
endpoint=self.health_check,
|
|
415
|
+
)
|
|
402
416
|
self.app.include_router(mgmt_router, tags=["Liveliness/Readiness"])
|
|
403
417
|
|
|
404
418
|
def add_route_dependencies(
|
|
405
|
-
self, scopes: List[Scope], dependencies
|
|
419
|
+
self, scopes: List[Scope], dependencies: List[Depends]
|
|
406
420
|
) -> None:
|
|
407
421
|
"""Add custom dependencies to routes.
|
|
408
422
|
|
|
409
423
|
Args:
|
|
410
|
-
scopes: list of
|
|
411
|
-
and `method` property.
|
|
412
|
-
dependencies: list of
|
|
413
|
-
dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/)
|
|
424
|
+
scopes: list of Scope.
|
|
425
|
+
Each scope should be a dict with a `path` and `method` property.
|
|
426
|
+
dependencies: list of fastapi.Depends
|
|
427
|
+
[FastAPI dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/)
|
|
414
428
|
to apply to each scope.
|
|
415
429
|
|
|
416
|
-
Returns:
|
|
417
|
-
None
|
|
418
430
|
"""
|
|
419
431
|
return add_route_dependencies(self.app.router.routes, scopes, dependencies)
|
|
420
432
|
|
|
421
|
-
def __attrs_post_init__(self):
|
|
433
|
+
def __attrs_post_init__(self) -> None:
|
|
422
434
|
"""Post-init hook.
|
|
423
435
|
|
|
424
436
|
Responsible for setting up the application upon instantiation of the class.
|
|
425
437
|
|
|
426
|
-
Returns:
|
|
427
|
-
None
|
|
428
438
|
"""
|
|
429
439
|
# inject settings
|
|
430
440
|
self.client.extensions = self.extensions
|
|
@@ -463,3 +473,6 @@ class StacApi:
|
|
|
463
473
|
# customize route dependencies
|
|
464
474
|
for scopes, dependencies in self.route_dependencies:
|
|
465
475
|
self.add_route_dependencies(scopes=scopes, dependencies=dependencies)
|
|
476
|
+
|
|
477
|
+
if self.app.state.settings.enable_direct_response:
|
|
478
|
+
add_direct_response(self.app)
|
|
@@ -8,8 +8,8 @@ from fastapi.encoders import jsonable_encoder
|
|
|
8
8
|
from fastapi.exceptions import RequestValidationError, ResponseValidationError
|
|
9
9
|
from starlette import status
|
|
10
10
|
from starlette.requests import Request
|
|
11
|
-
from starlette.responses import JSONResponse
|
|
12
11
|
|
|
12
|
+
from stac_fastapi.api.models import JSONResponse
|
|
13
13
|
from stac_fastapi.types.errors import (
|
|
14
14
|
ConflictError,
|
|
15
15
|
DatabaseError,
|
|
@@ -82,7 +82,7 @@ class ProxyHeaderMiddleware:
|
|
|
82
82
|
|
|
83
83
|
await self.app(scope, receive, send)
|
|
84
84
|
|
|
85
|
-
def _get_forwarded_url_parts(self, scope: Scope) -> Tuple[str]:
|
|
85
|
+
def _get_forwarded_url_parts(self, scope: Scope) -> Tuple[str, str, str]:
|
|
86
86
|
proto = scope.get("scheme", "http")
|
|
87
87
|
header_host = self._get_header_value_by_name(scope, "host")
|
|
88
88
|
if header_host is None:
|
|
@@ -127,7 +127,7 @@ class ProxyHeaderMiddleware:
|
|
|
127
127
|
@staticmethod
|
|
128
128
|
def _replace_header_value_by_name(
|
|
129
129
|
scope: Scope, header_name: str, new_value: str
|
|
130
|
-
) -> List[Tuple[str]]:
|
|
130
|
+
) -> List[Tuple[str, str]]:
|
|
131
131
|
return [
|
|
132
132
|
(name, value)
|
|
133
133
|
for name, value in scope["headers"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Api request/response models."""
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional, Type, Union
|
|
3
|
+
from typing import List, Literal, Optional, Type, Union
|
|
4
4
|
|
|
5
5
|
import attr
|
|
6
6
|
from fastapi import Path, Query
|
|
@@ -135,3 +135,9 @@ class JSONSchemaResponse(JSONResponse):
|
|
|
135
135
|
"""JSON with custom, vendor content-type."""
|
|
136
136
|
|
|
137
137
|
media_type = "application/schema+json"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class HealthCheck(BaseModel, extra="allow"):
|
|
141
|
+
"""health check response model."""
|
|
142
|
+
|
|
143
|
+
status: Literal["UP", "DOWN"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from fastapi import FastAPI
|
|
4
4
|
from starlette.requests import Request
|
|
5
|
-
from starlette.responses import
|
|
5
|
+
from starlette.responses import Response
|
|
6
6
|
from starlette.routing import Route, request_response
|
|
7
7
|
|
|
8
8
|
|
|
@@ -22,7 +22,7 @@ def update_openapi(app: FastAPI) -> FastAPI:
|
|
|
22
22
|
# Create a patched endpoint function that modifies the content type of the response
|
|
23
23
|
async def patched_openapi_endpoint(req: Request) -> Response:
|
|
24
24
|
# Get the response from the old endpoint function
|
|
25
|
-
response
|
|
25
|
+
response = await old_endpoint(req)
|
|
26
26
|
# Update the content type header in place
|
|
27
27
|
response.headers["content-type"] = "application/vnd.oai.openapi+json;version=3.0"
|
|
28
28
|
# Return the updated response
|
|
@@ -5,13 +5,15 @@ import functools
|
|
|
5
5
|
import inspect
|
|
6
6
|
from typing import Any, Callable, Dict, List, Optional, Type, TypedDict, Union
|
|
7
7
|
|
|
8
|
-
from fastapi import Depends, params
|
|
9
|
-
from fastapi.
|
|
8
|
+
from fastapi import Depends, FastAPI, params
|
|
9
|
+
from fastapi.datastructures import DefaultPlaceholder
|
|
10
|
+
from fastapi.dependencies.utils import get_dependant, get_parameterless_sub_dependant
|
|
11
|
+
from fastapi.routing import APIRoute
|
|
10
12
|
from pydantic import BaseModel
|
|
11
13
|
from starlette.concurrency import run_in_threadpool
|
|
12
14
|
from starlette.requests import Request
|
|
13
15
|
from starlette.responses import Response
|
|
14
|
-
from starlette.routing import BaseRoute, Match
|
|
16
|
+
from starlette.routing import BaseRoute, Match, request_response
|
|
15
17
|
from starlette.status import HTTP_204_NO_CONTENT
|
|
16
18
|
|
|
17
19
|
from stac_fastapi.api.models import APIRequest
|
|
@@ -86,7 +88,7 @@ class Scope(TypedDict, total=False):
|
|
|
86
88
|
|
|
87
89
|
|
|
88
90
|
def add_route_dependencies(
|
|
89
|
-
routes: List[BaseRoute], scopes: List[Scope], dependencies
|
|
91
|
+
routes: List[BaseRoute], scopes: List[Scope], dependencies: List[params.Depends]
|
|
90
92
|
) -> None:
|
|
91
93
|
"""Add dependencies to routes.
|
|
92
94
|
|
|
@@ -131,3 +133,33 @@ def add_route_dependencies(
|
|
|
131
133
|
# https://github.com/tiangolo/fastapi/blob/58ab733f19846b4875c5b79bfb1f4d1cb7f4823f/fastapi/applications.py#L337-L360
|
|
132
134
|
# https://github.com/tiangolo/fastapi/blob/58ab733f19846b4875c5b79bfb1f4d1cb7f4823f/fastapi/routing.py#L677-L678
|
|
133
135
|
route.dependencies.extend(dependencies)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def add_direct_response(app: FastAPI) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Setup FastAPI application's endpoints to return Response Object directly, avoiding
|
|
141
|
+
Pydantic validation and FastAPI (slow) serialization.
|
|
142
|
+
|
|
143
|
+
ref: https://gist.github.com/Zaczero/00f3a2679ebc0a25eb938ed82bc63553
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def wrap_endpoint(endpoint: Callable, cls: Type[Response]):
|
|
147
|
+
@functools.wraps(endpoint)
|
|
148
|
+
async def wrapper(*args, **kwargs):
|
|
149
|
+
content = await endpoint(*args, **kwargs)
|
|
150
|
+
return content if isinstance(content, Response) else cls(content)
|
|
151
|
+
|
|
152
|
+
return wrapper
|
|
153
|
+
|
|
154
|
+
for route in app.routes:
|
|
155
|
+
if not isinstance(route, APIRoute):
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
response_class = route.response_class
|
|
159
|
+
if isinstance(response_class, DefaultPlaceholder):
|
|
160
|
+
response_class = response_class.value
|
|
161
|
+
|
|
162
|
+
if issubclass(response_class, Response):
|
|
163
|
+
route.endpoint = wrap_endpoint(route.endpoint, response_class)
|
|
164
|
+
route.dependant = get_dependant(path=route.path_format, call=route.endpoint)
|
|
165
|
+
route.app = request_response(route.get_route_handler())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: stac-fastapi.api
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2.1
|
|
4
4
|
Summary: An implementation of STAC API based on the FastAPI framework.
|
|
5
5
|
Home-page: https://github.com/stac-utils/stac-fastapi
|
|
6
6
|
Author: Arturo Engineering
|
|
@@ -19,7 +19,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
19
19
|
Requires-Python: >=3.9
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
Requires-Dist: brotli_asgi
|
|
22
|
-
Requires-Dist: stac-fastapi.types~=5.
|
|
22
|
+
Requires-Dist: stac-fastapi.types~=5.2
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: httpx; extra == "dev"
|
|
25
25
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -30,9 +30,12 @@ Requires-Dist: requests; extra == "dev"
|
|
|
30
30
|
Provides-Extra: benchmark
|
|
31
31
|
Requires-Dist: pytest-benchmark; extra == "benchmark"
|
|
32
32
|
Provides-Extra: docs
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist: mkdocs
|
|
35
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: black>=23.10.1; extra == "docs"
|
|
34
|
+
Requires-Dist: mkdocs>=1.4.3; extra == "docs"
|
|
35
|
+
Requires-Dist: mkdocs-jupyter>=0.24.5; extra == "docs"
|
|
36
|
+
Requires-Dist: mkdocs-material[imaging]>=9.5; extra == "docs"
|
|
37
|
+
Requires-Dist: griffe-inherited-docstrings>=1.0.0; extra == "docs"
|
|
38
|
+
Requires-Dist: mkdocstrings[python]>=0.25.1; extra == "docs"
|
|
36
39
|
Dynamic: author
|
|
37
40
|
Dynamic: author-email
|
|
38
41
|
Dynamic: classifier
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
brotli_asgi
|
|
2
|
+
stac-fastapi.types~=5.2
|
|
3
|
+
|
|
4
|
+
[benchmark]
|
|
5
|
+
pytest-benchmark
|
|
6
|
+
|
|
7
|
+
[dev]
|
|
8
|
+
httpx
|
|
9
|
+
pytest
|
|
10
|
+
pytest-cov
|
|
11
|
+
pytest-asyncio
|
|
12
|
+
pre-commit
|
|
13
|
+
requests
|
|
14
|
+
|
|
15
|
+
[docs]
|
|
16
|
+
black>=23.10.1
|
|
17
|
+
mkdocs>=1.4.3
|
|
18
|
+
mkdocs-jupyter>=0.24.5
|
|
19
|
+
mkdocs-material[imaging]>=9.5
|
|
20
|
+
griffe-inherited-docstrings>=1.0.0
|
|
21
|
+
mkdocstrings[python]>=0.25.1
|
|
@@ -6,6 +6,7 @@ from fastapi import Path, Query
|
|
|
6
6
|
from fastapi.testclient import TestClient
|
|
7
7
|
from pydantic import ValidationError
|
|
8
8
|
from stac_pydantic import api
|
|
9
|
+
from starlette.requests import Request
|
|
9
10
|
from typing_extensions import Annotated
|
|
10
11
|
|
|
11
12
|
from stac_fastapi.api import app
|
|
@@ -108,6 +109,31 @@ def test_client_invalid_response_type(validate, TestCoreClient, item_dict):
|
|
|
108
109
|
assert item.status_code == 200, item.text
|
|
109
110
|
|
|
110
111
|
|
|
112
|
+
def test_client_response_by_pass(TestCoreClient, item_dict):
|
|
113
|
+
"""Check with `enable_direct_response` option."""
|
|
114
|
+
|
|
115
|
+
class InValidResponseClient(TestCoreClient):
|
|
116
|
+
def get_item(self, item_id: str, collection_id: str, **kwargs) -> stac.Item:
|
|
117
|
+
item_dict.pop("bbox", None)
|
|
118
|
+
item_dict.pop("geometry", None)
|
|
119
|
+
return item_dict
|
|
120
|
+
|
|
121
|
+
test_app = app.StacApi(
|
|
122
|
+
settings=ApiSettings(
|
|
123
|
+
enable_response_models=False,
|
|
124
|
+
enable_direct_response=True,
|
|
125
|
+
),
|
|
126
|
+
client=InValidResponseClient(),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
with TestClient(test_app.app) as client:
|
|
130
|
+
item = client.get("/collections/test/items/test")
|
|
131
|
+
|
|
132
|
+
assert item.json()
|
|
133
|
+
assert item.status_code == 200
|
|
134
|
+
assert item.headers["content-type"] == "application/geo+json"
|
|
135
|
+
|
|
136
|
+
|
|
111
137
|
def test_client_openapi(TestCoreClient):
|
|
112
138
|
"""Test if response models are all documented with OpenAPI."""
|
|
113
139
|
|
|
@@ -504,3 +530,51 @@ def test_client_datetime_input_params():
|
|
|
504
530
|
assert post_search.json() == "2020-01-01T00:00:00.00001Z"
|
|
505
531
|
assert post_search_zero.status_code == 200, post_search_zero.text
|
|
506
532
|
assert post_search_zero.json() == "2020-01-01T00:00:00.0000Z"
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def test_mgmt_endpoints(AsyncTestCoreClient):
|
|
536
|
+
"""Test ping/health endpoints."""
|
|
537
|
+
|
|
538
|
+
test_app = app.StacApi(
|
|
539
|
+
settings=ApiSettings(),
|
|
540
|
+
client=AsyncTestCoreClient(),
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
with TestClient(test_app.app) as client:
|
|
544
|
+
resp = client.get("/_mgmt/ping")
|
|
545
|
+
assert resp.status_code == 200
|
|
546
|
+
assert resp.json() == {"message": "PONG"}
|
|
547
|
+
|
|
548
|
+
resp = client.get("/_mgmt/health")
|
|
549
|
+
assert resp.status_code == 200
|
|
550
|
+
assert resp.json() == {"status": "UP"}
|
|
551
|
+
|
|
552
|
+
def health_check(request: Request):
|
|
553
|
+
return {
|
|
554
|
+
"status": "UP",
|
|
555
|
+
"database": {
|
|
556
|
+
"status": "UP",
|
|
557
|
+
"version": "0.1.0",
|
|
558
|
+
},
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
test_app = app.StacApi(
|
|
562
|
+
settings=ApiSettings(),
|
|
563
|
+
client=AsyncTestCoreClient(),
|
|
564
|
+
health_check=health_check,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
with TestClient(test_app.app) as client:
|
|
568
|
+
resp = client.get("/_mgmt/ping")
|
|
569
|
+
assert resp.status_code == 200
|
|
570
|
+
assert resp.json() == {"message": "PONG"}
|
|
571
|
+
|
|
572
|
+
resp = client.get("/_mgmt/health")
|
|
573
|
+
assert resp.status_code == 200
|
|
574
|
+
assert resp.json() == {
|
|
575
|
+
"status": "UP",
|
|
576
|
+
"database": {
|
|
577
|
+
"status": "UP",
|
|
578
|
+
"version": "0.1.0",
|
|
579
|
+
},
|
|
580
|
+
}
|
|
@@ -34,6 +34,9 @@ def test_api_prefix(TestCoreClient, prefix):
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
with TestClient(api.app, base_url="http://stac.io") as client:
|
|
37
|
+
ping = client.get(f"{prefix}/_mgmt/ping")
|
|
38
|
+
assert ping.status_code == 200, ping.json() == {"message": "PONG"}
|
|
39
|
+
|
|
37
40
|
landing = client.get(f"{prefix}/")
|
|
38
41
|
assert landing.status_code == 200, landing.json()
|
|
39
42
|
|
|
@@ -43,7 +43,10 @@ def test_create_get_request_model():
|
|
|
43
43
|
model = request_model(bbox="0,0,0,1,1,1")
|
|
44
44
|
assert model.bbox == (0.0, 0.0, 0.0, 1.0, 1.0, 1.0)
|
|
45
45
|
|
|
46
|
-
with pytest.raises(
|
|
46
|
+
with pytest.raises(HTTPException):
|
|
47
|
+
request_model(bbox="a,b")
|
|
48
|
+
|
|
49
|
+
with pytest.raises(HTTPException):
|
|
47
50
|
request_model(bbox="0,0,0,1,1")
|
|
48
51
|
|
|
49
52
|
model = request_model(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|