From 26f105fe886956ebf747c346d4f3b95972e47b9d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 9 Jan 2026 04:06:51 +0100 Subject: [PATCH] feat(backend): Add dedicated health endpoint (#11104) * feat(backend): Add dedicated health endpoint * bump api version * add test for new endpoint * fix check --- .../InvenTree/InvenTree/api_version.py | 7 ++- src/backend/InvenTree/common/api.py | 49 +++++++++++++++++++ src/backend/InvenTree/common/tests.py | 29 +++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 4b8064a625..4b7efd3e8b 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,16 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 437 +INVENTREE_API_VERSION = 438 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v438 -> 2026-01-09 : https://github.com/inventree/InvenTree/pull/11104 + - Adds a simpler / faster health check endpoint at /api/system/health/ + v437 -> 2026-01-07 : https://github.com/inventree/InvenTree/pull/11084 - - Add generic parameter support for the StockLocation model + - Adds generic parameter support for the StockLocation model v436 -> 2026-01-06 : https://github.com/inventree/InvenTree/pull/11035 - Removes model-specific metadata endpoints and replaces them with redirects diff --git a/src/backend/InvenTree/common/api.py b/src/backend/InvenTree/common/api.py index a1fb8e2923..694d28940b 100644 --- a/src/backend/InvenTree/common/api.py +++ b/src/backend/InvenTree/common/api.py @@ -7,6 +7,7 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db.models import Q +from django.http import JsonResponse from django.http.response import HttpResponse from django.urls import include, path, re_path from django.utils.decorators import method_decorator @@ -33,6 +34,7 @@ import common.filters import common.models import common.serializers import InvenTree.conversion +import InvenTree.ready from common.icons import get_icon_packs from common.settings import get_global_setting from data_exporter.mixins import DataExportViewMixin @@ -1071,6 +1073,48 @@ class TestEmail(CreateAPI): ) # pragma: no cover +class HealthCheckStatusSerializer(serializers.Serializer): + """Status of the overall system health.""" + + status = serializers.ChoiceField( + help_text='Health status of the InvenTree server', + choices=['ok', 'loading'], + read_only=True, + default='ok', + ) + + +class HealthCheckView(APIView): + """Simple JSON endpoint for InvenTree health check. + + Intended to be used by external services to confirm that the InvenTree server is running. + """ + + permission_classes = [AllowAnyOrReadScope] + + @extend_schema( + responses={ + 200: OpenApiResponse( + response=HealthCheckStatusSerializer, + description='InvenTree server health status', + ) + } + ) + def get(self, request, *args, **kwargs): + """Simple health check endpoint for monitoring purposes. + + Use the root API endpoint for more detailed information (using an authenticated request). + """ + status = ( + InvenTree.ready.isPluginRegistryLoaded() + if settings.PLUGINS_ENABLED + else True + ) + return JsonResponse( + {'status': 'ok' if status else 'loading'}, status=200 if status else 503 + ) + + selection_urls = [ path( '/', @@ -1346,6 +1390,11 @@ common_api_urls = [ path('', DataOutputList.as_view(), name='api-data-output-list'), ]), ), + # System APIs (related to basic system functions) + path( + 'system/', + include([path('health/', HealthCheckView.as_view(), name='api-system-health')]), + ), ] admin_api_urls = [ diff --git a/src/backend/InvenTree/common/tests.py b/src/backend/InvenTree/common/tests.py index 4cc16136bd..5c03ad6db4 100644 --- a/src/backend/InvenTree/common/tests.py +++ b/src/backend/InvenTree/common/tests.py @@ -1471,6 +1471,35 @@ class CommonTest(InvenTreeAPITestCase): self.user.is_superuser = False self.user.save() + def test_health_api(self): + """Test health check URL.""" + from plugin import registry + + # Fully started system - ok + response_data = self.get(reverse('api-system-health'), expected_code=200).json() + self.assertIn('status', response_data) + self.assertEqual(response_data['status'], 'ok') + + # Simulate plugin reloading - Not ready + try: + registry.plugins_loaded = False + response_data = self.get( + reverse('api-system-health'), expected_code=503 + ).json() + self.assertIn('status', response_data) + self.assertEqual(response_data['status'], 'loading') + finally: + registry.plugins_loaded = True + + # No plugins enabled - still ok + with self.settings(PLUGINS_ENABLED=False): + self.assertEqual( + self.get(reverse('api-system-health'), expected_code=200).json()[ + 'status' + ], + 'ok', + ) + class CurrencyAPITests(InvenTreeAPITestCase): """Unit tests for the currency exchange API endpoints."""