Skip to content

Commit 1d49423

Browse files
add health endpoint (#825)
* add health endpoint * fix type
1 parent a402be8 commit 1d49423

File tree

3 files changed

+92
-5
lines changed

3 files changed

+92
-5
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Added
1010

1111
- add `enable_direct_response` settings to by-pass Pydantic validation and FastAPI serialization for responses
12+
- add `/_mgmt/health` endpoint (`readiness`) and `health_check: Callable[[], [Dict]` optional attribute in `StacApi` class
1213

1314
## [5.1.1] - 2025-03-17
1415

stac_fastapi/api/stac_fastapi/api/app.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Fastapi app creation."""
22

33

4-
from typing import Dict, List, Optional, Tuple, Type, Union
4+
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, Type, Union
55

66
import attr
77
from brotli_asgi import BrotliMiddleware
@@ -67,6 +67,10 @@ class StacApi:
6767
specified routes. This is useful
6868
for applying custom auth requirements to routes defined elsewhere in
6969
the application.
70+
health_check:
71+
A Callable which return application's `health` information.
72+
Defaults to `def health: return {"status": "UP"}`
73+
7074
"""
7175

7276
settings: ApiSettings = attr.ib()
@@ -128,6 +132,9 @@ class StacApi:
128132
)
129133
)
130134
route_dependencies: List[Tuple[List[Scope], List[Depends]]] = attr.ib(default=[])
135+
health_check: Union[Callable[[], Dict], Callable[[], Awaitable[Dict]]] = attr.ib(
136+
default=lambda: {"status": "UP"}
137+
)
131138

132139
def get_extension(self, extension: Type[ApiExtension]) -> Optional[ApiExtension]:
133140
"""Get an extension.
@@ -363,14 +370,44 @@ def register_core(self) -> None:
363370

364371
def add_health_check(self) -> None:
365372
"""Add a health check."""
366-
mgmt_router = APIRouter(prefix=self.app.state.router_prefix)
367373

368-
@mgmt_router.get("/_mgmt/ping")
369374
async def ping():
370-
"""Liveliness/readiness probe."""
375+
"""Liveliness probe."""
371376
return {"message": "PONG"}
372377

373-
self.app.include_router(mgmt_router, tags=["Liveliness/Readiness"])
378+
self.app.router.add_api_route(
379+
name="Ping",
380+
path="/_mgmt/ping",
381+
response_model=Dict,
382+
responses={
383+
200: {
384+
"content": {
385+
MimeTypes.json.value: {},
386+
},
387+
},
388+
},
389+
response_class=self.response_class,
390+
methods=["GET"],
391+
endpoint=ping,
392+
tags=["Liveliness/Readiness"],
393+
)
394+
395+
self.app.router.add_api_route(
396+
name="Health",
397+
path="/_mgmt/health",
398+
response_model=Dict,
399+
responses={
400+
200: {
401+
"content": {
402+
MimeTypes.json.value: {},
403+
},
404+
},
405+
},
406+
response_class=self.response_class,
407+
methods=["GET"],
408+
endpoint=self.health_check,
409+
tags=["Liveliness/Readiness"],
410+
)
374411

375412
def add_route_dependencies(
376413
self, scopes: List[Scope], dependencies: List[Depends]

stac_fastapi/api/tests/test_app.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from fastapi.testclient import TestClient
77
from pydantic import ValidationError
88
from stac_pydantic import api
9+
from starlette.requests import Request
910
from typing_extensions import Annotated
1011

1112
from stac_fastapi.api import app
@@ -529,3 +530,51 @@ def item_collection(
529530
assert post_search.json() == "2020-01-01T00:00:00.00001Z"
530531
assert post_search_zero.status_code == 200, post_search_zero.text
531532
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+
}

0 commit comments

Comments
 (0)