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.
Files changed (25) hide show
  1. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/PKG-INFO +9 -6
  2. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/setup.py +9 -2
  3. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/app.py +80 -67
  4. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/errors.py +1 -1
  5. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/middleware.py +2 -2
  6. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/models.py +7 -1
  7. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/openapi.py +2 -2
  8. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/routes.py +36 -4
  9. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/version.py +1 -1
  10. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/PKG-INFO +9 -6
  11. stac_fastapi_api-5.2.1/stac_fastapi.api.egg-info/requires.txt +21 -0
  12. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_app.py +74 -0
  13. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_app_prefix.py +3 -0
  14. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_models.py +4 -1
  15. stac_fastapi_api-5.1.1/stac_fastapi.api.egg-info/requires.txt +0 -18
  16. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/README.md +0 -0
  17. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/setup.cfg +0 -0
  18. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/__init__.py +0 -0
  19. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi/api/config.py +0 -0
  20. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/SOURCES.txt +0 -0
  21. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/dependency_links.txt +0 -0
  22. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/not-zip-safe +0 -0
  23. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/stac_fastapi.api.egg-info/top_level.txt +0 -0
  24. {stac_fastapi_api-5.1.1 → stac_fastapi_api-5.2.1}/tests/test_api.py +0 -0
  25. {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.2
1
+ Metadata-Version: 2.4
2
2
  Name: stac-fastapi.api
3
- Version: 5.1.1
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.0
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: mkdocs; extra == "docs"
34
- Requires-Dist: mkdocs-material; extra == "docs"
35
- Requires-Dist: pdocs; extra == "docs"
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.0",
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": ["mkdocs", "mkdocs-material", "pdocs"],
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 JSONResponse, Response
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 Scope, add_route_dependencies, create_async_endpoint
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/readiness probe."""
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=List[Depends]
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 scopes. Each scope should be a dict with a `path`
411
- and `method` property.
412
- dependencies: list of [FastAPI
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 JSONResponse, Response
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: JSONResponse = await old_endpoint(req)
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.dependencies.utils import get_parameterless_sub_dependant
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=List[params.Depends]
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,3 +1,3 @@
1
1
  """Library version."""
2
2
 
3
- __version__ = "5.1.1"
3
+ __version__ = "5.2.1"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: stac-fastapi.api
3
- Version: 5.1.1
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.0
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: mkdocs; extra == "docs"
34
- Requires-Dist: mkdocs-material; extra == "docs"
35
- Requires-Dist: pdocs; extra == "docs"
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(AssertionError):
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(
@@ -1,18 +0,0 @@
1
- brotli_asgi
2
- stac-fastapi.types~=5.0
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
- mkdocs
17
- mkdocs-material
18
- pdocs