Skip to content

Commit 1cefc94

Browse files
committed
fix: migrate custom_health_checks to django-health-check v4 API
Replace BaseHealthCheckBackend/plugin_dir/MainView with HealthCheck/ HealthCheckView. Switch check_status() to run() and register checks via HealthCheckView.checks instead of plugin_dir. Refs: NS-209
1 parent 290a3b5 commit 1cefc94

5 files changed

Lines changed: 35 additions & 46 deletions

File tree

custom_health_checks/apps.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
1-
import logging
2-
31
from django.apps import AppConfig
4-
from health_check.plugins import plugin_dir
5-
6-
logger = logging.getLogger(__name__)
72

83

94
class CustomHealthChecksAppConfig(AppConfig):
105
name = "custom_health_checks"
11-
12-
def ready(self):
13-
from .backends import DatabaseHealthCheck
14-
15-
plugin_dir.register(DatabaseHealthCheck)
16-
logger.info("Registered DatabaseHealthCheck to health_check plugins.")

custom_health_checks/backends.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1+
import dataclasses
2+
13
from django.db import connection
2-
from health_check.backends import BaseHealthCheckBackend
4+
from health_check.base import HealthCheck
35
from health_check.exceptions import ServiceUnavailable
46

57

6-
class DatabaseHealthCheck(BaseHealthCheckBackend):
8+
@dataclasses.dataclass
9+
class DatabaseHealthCheck(HealthCheck):
710
"""
811
Custom health check for the database connection.
912
"""
1013

11-
critical_service = True
12-
13-
def check_status(self):
14+
def run(self):
1415
"""
1516
Checks the database connection by executing a simple query.
1617
"""
@@ -19,6 +20,3 @@ def check_status(self):
1920
cursor.execute("SELECT 1 FROM users_user LIMIT 1")
2021
except Exception as e:
2122
raise ServiceUnavailable(f"Database connection failed: {e}")
22-
23-
def identifier(self):
24-
return self.__class__.__name__ # Display name on the endpoint.

custom_health_checks/tests/test_backends.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,34 @@
1010
@patch("django.db.connection.cursor")
1111
def test_database_check_status_success(mock_cursor):
1212
"""
13-
Test that check_status passes when the database connection is successful.
13+
Test that run passes when the database connection is successful.
1414
"""
1515
mock_cursor.return_value.__enter__.return_value.execute.return_value = True
1616
health_check = DatabaseHealthCheck()
17-
health_check.check_status() # Should not raise an exception
17+
health_check.run() # Should not raise an exception
1818

1919

2020
@patch("django.db.connection.cursor")
2121
def test_database_check_status_failure(mock_cursor):
2222
"""
23-
Test that check_status raises ServiceUnavailable when the database
24-
connection fails.
23+
Test that run raises ServiceUnavailable when the database connection fails.
2524
"""
2625
mock_cursor.return_value.__enter__.return_value.execute.side_effect = (
2726
OperationalError("Database error")
2827
)
2928
health_check = DatabaseHealthCheck()
3029
with pytest.raises(ServiceUnavailable) as exc_info:
31-
health_check.check_status()
30+
health_check.run()
3231
assert "Database connection failed:" in str(exc_info.value)
3332

3433

3534
def test_database_check_status_with_real_database(db): # Use the 'db' fixture
3635
"""
37-
Test check_status with the actual database connection.
36+
Test run with the actual database connection.
3837
This assumes your test database is set up correctly.
3938
"""
4039
try:
4140
health_check = DatabaseHealthCheck()
42-
health_check.check_status() # Should not raise an exception
41+
health_check.run() # Should not raise an exception
4342
except ServiceUnavailable as e:
4443
pytest.fail(f"Database health check failed: {e}")

custom_health_checks/tests/test_healthz.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@
55
from health_check.exceptions import ServiceUnavailable
66

77

8-
@patch("custom_health_checks.backends.DatabaseHealthCheck.check_status")
9-
def test_healthz_success(mock_check_status, client: Client): # Use the 'client' fixture
8+
@patch("custom_health_checks.backends.DatabaseHealthCheck.run")
9+
def test_healthz_success(mock_run, client: Client): # Use the 'client' fixture
1010
"""
1111
Test /healthz endpoint with successful health checks.
1212
"""
13-
mock_check_status.return_value = None # Simulate successful check
13+
mock_run.return_value = None # Simulate successful check
1414
url = reverse("healthz")
1515
response = client.get(url)
1616
assert response.status_code == 200
17-
assert response.content == b'{"DatabaseHealthCheck": "working"}'
17+
assert response.json() == {"DatabaseHealthCheck()": "OK"}
1818

1919

20-
@patch("custom_health_checks.backends.DatabaseHealthCheck.check_status")
21-
def test_healthz_database_error(mock_check_status, client: Client):
20+
@patch("custom_health_checks.backends.DatabaseHealthCheck.run")
21+
def test_healthz_database_error(mock_run, client: Client):
2222
"""
2323
Test /healthz endpoint with a database error.
2424
"""
25-
mock_check_status.side_effect = ServiceUnavailable("Database error")
25+
mock_run.side_effect = ServiceUnavailable("Database error")
2626
url = reverse("healthz")
2727
response = client.get(url)
2828
assert response.status_code == 500

custom_health_checks/views.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
import asyncio
2+
13
from django.utils.decorators import method_decorator
24
from django.views.decorators.cache import never_cache
3-
from health_check.views import MainView
5+
from health_check.views import HealthCheckView
46

57

6-
class HealthCheckJSONView(MainView):
8+
class HealthCheckJSONView(HealthCheckView):
79
"""
8-
The original `health_check` view uses format-attribute or headers to determine
9-
what response type is used. The HealthCheckJSONView can be used to ensure
10-
that a JSON Response is always used (for health check reporting).
10+
Always returns a JSON response, regardless of the Accept header.
1111
1212
To apply it, in project's `urls.py`, add the custom JSON view in use like this:
1313
>>> # doctest: +SKIP
@@ -17,17 +17,20 @@ class HealthCheckJSONView(MainView):
1717
... # ...
1818
... path(
1919
... r"healthz",
20-
... views.HealthCheckCustomView.as_view(),
20+
... views.HealthCheckJSONView.as_view(),
2121
... name="health_check_custom",
2222
... ),
2323
... ]
2424
"""
2525

26+
checks = ("custom_health_checks.backends.DatabaseHealthCheck",)
27+
2628
@method_decorator(never_cache)
27-
def get(self, request, *args, **kwargs):
28-
subset = kwargs.get("subset", None)
29-
health_check_has_error = self.check(subset)
30-
status_code = 500 if health_check_has_error else 200
31-
return self.render_to_response_json(
32-
self.filter_plugins(subset=subset), status_code
33-
)
29+
async def get(self, request, *args, **kwargs):
30+
with self.get_executor() as executor:
31+
self.results = await asyncio.gather(
32+
*(check.get_result(executor) for check in self.get_checks())
33+
)
34+
has_errors = any(result.error for result in self.results)
35+
status_code = 500 if has_errors else 200
36+
return self.render_to_response_json(status_code)

0 commit comments

Comments
 (0)