Skip to content

Commit 8bb02b2

Browse files
committed
feat: add health check endpoints and database health check functionality
1 parent 9241e05 commit 8bb02b2

3 files changed

Lines changed: 91 additions & 3 deletions

File tree

src/api/endpoints/health.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from database import check_health_safe, engine
12
from fastapi import APIRouter
23
from starlette import status
34

@@ -14,4 +15,50 @@ def health():
1415
"""
1516
Performs a simple health check.
1617
"""
17-
return {"status": "OK"}
18+
return health_check(include_dependencies=False)
19+
20+
21+
@router.get(
22+
"/health/readiness",
23+
operation_id="isReady",
24+
summary="Check if the service and dependencies are healthy; i.e. capable of processing requests.",
25+
status_code=status.HTTP_200_OK,
26+
response_description="Return HTTP 200",
27+
)
28+
async def readiness_check():
29+
return health_check(include_dependencies=True)
30+
31+
32+
@router.get(
33+
"/health/liveness", # Explicit requirement
34+
operation_id="isAlive",
35+
summary="Check if the service is healthy; i.e. capable of responding to requests.",
36+
status_code=status.HTTP_200_OK,
37+
response_description="Return HTTP 200",
38+
)
39+
async def liveness_check():
40+
return health_check(include_dependencies=False)
41+
42+
43+
def health_check(include_dependencies: bool = False):
44+
"""
45+
Health check endpoint.
46+
47+
Args:
48+
include_dependencies (bool): Whether to include dependency checks.
49+
50+
Returns:
51+
dict: Health status.
52+
"""
53+
# Here you would typically check the health of your dependencies
54+
# e.g., database connection, external services, etc.
55+
# For simplicity, we return a static response.
56+
57+
status_response = {"status": "OK"}
58+
59+
if include_dependencies:
60+
# Perform checks for dependencies here
61+
db_status = check_health_safe(engine)
62+
status_response["database"] = "OK" if db_status else "FAIL"
63+
64+
return status_response

src/database.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from config import settings
2-
from sqlalchemy import Engine
2+
from sqlalchemy import Engine, text
33
from sqlmodel import SQLModel, create_engine
4+
from utils.log import get_logger
5+
6+
logger = get_logger()
47

58

69
def create_db_engine(verbose: bool = False, **kwargs):
@@ -30,6 +33,39 @@ def create_db_engine(verbose: bool = False, **kwargs):
3033
return engine
3134

3235

36+
def check_health(engine: Engine) -> None:
37+
"""
38+
Perform a health check on the database by executing a simple query.
39+
40+
Args:
41+
engine (Engine): The SQLAlchemy engine instance used to connect to the database.
42+
43+
Raises:
44+
Exception: If the database health check fails, the exception is logged and re-raised.
45+
"""
46+
try:
47+
with engine.connect() as conn:
48+
conn.execute(text("SELECT 1"))
49+
50+
except Exception as e:
51+
logger.error(f"Database health check failed: {e}")
52+
raise
53+
54+
55+
def check_health_safe(engine: Engine) -> bool:
56+
"""
57+
Safe version of check_health that returns a bool instead of raising for convenience.
58+
59+
Returns:
60+
True if the database is healthy and permissions are valid, False otherwise.
61+
"""
62+
try:
63+
check_health(engine)
64+
return True
65+
except Exception:
66+
return False
67+
68+
3369
def create_db_and_tables(engine: Engine):
3470
"""
3571
Create database tables based on the defined SQLModel models.

tests/conftest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ def data_folder_path():
1717

1818
@pytest.fixture(name="session")
1919
def session_fixture() -> Session:
20-
"""Create an engine with all migrations applied."""
20+
"""Create a new database session for a test."""
21+
# TODO: Use an in-memory SQLite database for faster tests if possible.
22+
# https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/#memory-database
23+
2124
# Create a temporary database file
2225
fd, path = tempfile.mkstemp(suffix=".db")
2326
os.close(fd)
@@ -45,6 +48,8 @@ def session_fixture() -> Session:
4548

4649
@pytest.fixture(name="client_with_db")
4750
def client_fixture(session: Session):
51+
"""Create a TestClient that uses the test database session."""
52+
4853
def get_session_override():
4954
return session
5055

0 commit comments

Comments
 (0)