Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-django-health-check for
openSUSE:Factory checked in at 2026-04-16 17:25:53
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-health-check (Old)
and /work/SRC/openSUSE:Factory/.python-django-health-check.new.11940 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-health-check"
Thu Apr 16 17:25:53 2026 rev:15 rq:1347168 version:4.3.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-django-health-check/python-django-health-check.changes
2026-04-11 22:32:36.395684835 +0200
+++
/work/SRC/openSUSE:Factory/.python-django-health-check.new.11940/python-django-health-check.changes
2026-04-16 17:26:18.269788546 +0200
@@ -1,0 +2,7 @@
+Wed Apr 15 20:22:23 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 4.3.0:
+ * Ref #701 -- Add support for a custom executor for synchronous
+ checks
+
+-------------------------------------------------------------------
Old:
----
django-health-check-4.2.2.tar.gz
New:
----
django-health-check-4.3.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-health-check.spec ++++++
--- /var/tmp/diff_new_pack.DqSd3K/_old 2026-04-16 17:26:18.949816530 +0200
+++ /var/tmp/diff_new_pack.DqSd3K/_new 2026-04-16 17:26:18.949816530 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-django-health-check
-Version: 4.2.2
+Version: 4.3.0
Release: 0
Summary: Run checks on Django and is dependent services
License: MIT
++++++ django-health-check-4.2.2.tar.gz -> django-health-check-4.3.0.tar.gz
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-health-check-4.2.2/.github/workflows/release.yml
new/django-health-check-4.3.0/.github/workflows/release.yml
--- old/django-health-check-4.2.2/.github/workflows/release.yml 2026-04-09
18:03:15.000000000 +0200
+++ new/django-health-check-4.3.0/.github/workflows/release.yml 2026-04-15
19:08:10.000000000 +0200
@@ -11,7 +11,7 @@
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
- run: uv run mkdocs build
- - uses: actions/upload-pages-artifact@v4
+ - uses: actions/upload-pages-artifact@v5
with:
path: site
gh-page-deploy:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-health-check-4.2.2/.pre-commit-config.yaml
new/django-health-check-4.3.0/.pre-commit-config.yaml
--- old/django-health-check-4.2.2/.pre-commit-config.yaml 2026-04-09
18:03:15.000000000 +0200
+++ new/django-health-check-4.3.0/.pre-commit-config.yaml 2026-04-15
19:08:10.000000000 +0200
@@ -36,7 +36,7 @@
- mdformat-gfm-alerts
exclude: '.github/agents/'
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.15.9
+ rev: v0.15.10
hooks:
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-health-check-4.2.2/docs/usage.md
new/django-health-check-4.3.0/docs/usage.md
--- old/django-health-check-4.2.2/docs/usage.md 2026-04-09 18:03:15.000000000
+0200
+++ new/django-health-check-4.3.0/docs/usage.md 2026-04-15 19:08:10.000000000
+0200
@@ -141,3 +141,37 @@
Similar to the http version, a critical error will cause the command to
quit with the exit code `1`.
+
+## Performance tweaks
+
+All checks are executed asynchronously, either via `asyncio` or via a thread
pool,
+depending on the implementation of the individual checks.
+This allows for concurrent execution of the IO-bound checks,
+which reduces the response time.
+
+The event loop's default executor is used to run synchronous checks
+(e.g. [Database][health_check.checks.Database],
[Mail][health_check.checks.Mail],
+or [Storage][health_check.checks.Storage]) in a thread pool.
+This pool is usually persisted across requests. This may lead to high
performance while
+permanently allocating more memory. This may be undesirable for some
applications,
+especially with `S3Storage`, which uses thread-local connections.
+
+This can be mitigated by using a custom executor that creates a new
+thread pool for each request, which is then cleaned up after the checks
+are completed. This can be achieved by subclassing `HealthCheckView`
+and overriding the `get_executor` method to return a context manager
+providing a new `ThreadPoolExecutor` instance for each request.
+
+```python
+from concurrent.futures import ThreadPoolExecutor
+from health_check.views import HealthCheckView
+
+
+class CustomHealthCheckView(HealthCheckView):
+ def get_executor(self):
+ return ThreadPoolExecutor(max_workers=len(self.checks))
+```
+
+This approach ensures that each request gets a fresh thread pool,
+which can help manage memory usage more effectively
+while still providing the benefits of concurrent execution for synchronous
checks.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-health-check-4.2.2/health_check/base.py
new/django-health-check-4.3.0/health_check/base.py
--- old/django-health-check-4.2.2/health_check/base.py 2026-04-09
18:03:15.000000000 +0200
+++ new/django-health-check-4.3.0/health_check/base.py 2026-04-15
19:08:10.000000000 +0200
@@ -6,6 +6,7 @@
import inspect
import logging
import timeit
+from concurrent.futures import Executor
from health_check.exceptions import HealthCheckException
@@ -70,16 +71,16 @@
...
def pretty_status(self) -> str:
- """Return human-readable status string, always 'OK' for the check
itself."""
+ """Return a human-readable status string, always 'OK' for the check
itself."""
return "OK"
- async def get_result(self: HealthCheck) -> HealthCheckResult:
+ async def get_result(self, executor: Executor | None = None) ->
HealthCheckResult:
loop = asyncio.get_running_loop()
start = timeit.default_timer()
try:
await self.run() if inspect.iscoroutinefunction(
self.run
- ) else await loop.run_in_executor(None, self.run)
+ ) else await loop.run_in_executor(executor, self.run)
except HealthCheckException as e:
error = e
except BaseException:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-health-check-4.2.2/health_check/checks.py
new/django-health-check-4.3.0/health_check/checks.py
--- old/django-health-check-4.2.2/health_check/checks.py 2026-04-09
18:03:15.000000000 +0200
+++ new/django-health-check-4.3.0/health_check/checks.py 2026-04-15
19:08:10.000000000 +0200
@@ -194,11 +194,11 @@
@dataclasses.dataclass
class Mail(HealthCheck):
"""
- Check that mail backend is able to open and close connection.
+ Check that an email backend is able to open and close the connection.
Args:
backend: The email backend to test against.
- timeout: Timeout for connection to mail server in seconds.
+ timeout: Timeout for connection to an email server in seconds.
"""
@@ -231,7 +231,7 @@
"""
Check file storage backends by saving, reading, and deleting a test file.
- It can be setup multiple times for different storage backends if needed.
+ It can be set up multiple times for different storage backends if needed.
Args:
alias: The alias of the storage backend to check.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-health-check-4.2.2/health_check/views.py
new/django-health-check-4.3.0/health_check/views.py
--- old/django-health-check-4.2.2/health_check/views.py 2026-04-09
18:03:15.000000000 +0200
+++ new/django-health-check-4.3.0/health_check/views.py 2026-04-15
19:08:10.000000000 +0200
@@ -1,7 +1,9 @@
import asyncio
+import contextlib
import datetime
import re
import typing
+from concurrent.futures import Executor
from django.db import transaction
from django.http import HttpResponse, JsonResponse
@@ -111,9 +113,10 @@
@method_decorator(never_cache)
async def get(self, request, *args, **kwargs):
- self.results = await asyncio.gather(
- *(check.get_result() for check in self.get_checks())
- )
+ with self.get_executor() as executor:
+ self.results = await asyncio.gather(
+ *(check.get_result(executor) for check in self.get_checks())
+ )
has_errors = any(result.error for result in self.results)
status_code = 500 if has_errors else 200
format_override = request.GET.get("format")
@@ -159,6 +162,20 @@
"errors": any(result.error for result in self.results),
}
+ def get_executor(self) -> contextlib.AbstractContextManager[Executor |
None]:
+ """
+ Return a context manager providing an executor for synchronous checks.
+
+ Return a context manager that yields ``None`` to use the event loop's
+ default executor.
+
+ Example:
+ def get_executor(self):
+ return ThreadPoolExecutor(max_workers=5)
+
+ """
+ return contextlib.nullcontext(None)
+
def render_to_response_json(self, status):
"""Return JSON response with health check results."""
return JsonResponse(
@@ -170,7 +187,7 @@
)
def render_to_response_text(self, status):
- """Return plain text response with health check results."""
+ """Return a plain text response with health check results."""
lines = (
f"{repr(result.check)}: {'OK' if not result.error else
str(result.error)}"
for result in self.results
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-health-check-4.2.2/mkdocs.yml
new/django-health-check-4.3.0/mkdocs.yml
--- old/django-health-check-4.2.2/mkdocs.yml 2026-04-09 18:03:15.000000000
+0200
+++ new/django-health-check-4.3.0/mkdocs.yml 2026-04-15 19:08:10.000000000
+0200
@@ -30,8 +30,8 @@
- https://www.psycopg.org/psycopg3/docs/objects.inv
- https://docs.celeryq.dev/en/stable/objects.inv
- https://psutil.readthedocs.io/en/stable/objects.inv
- - https://django-storages.readthedocs.io/en/latest/objects.inv
- https://redis.readthedocs.io/en/stable/objects.inv
+ - https://django-storages.readthedocs.io/en/stable/objects.inv
theme:
name: material
logo: images/icon.svg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-health-check-4.2.2/tests/test_base.py
new/django-health-check-4.3.0/tests/test_base.py
--- old/django-health-check-4.2.2/tests/test_base.py 2026-04-09
18:03:15.000000000 +0200
+++ new/django-health-check-4.3.0/tests/test_base.py 2026-04-15
19:08:10.000000000 +0200
@@ -1,3 +1,6 @@
+import asyncio
+from unittest.mock import MagicMock, patch
+
import pytest
from health_check.base import HealthCheck, HealthCheckResult
@@ -47,6 +50,36 @@
assert isinstance(result, HealthCheckResult)
@pytest.mark.asyncio
+ async def test_run__sync_check_uses_custom_executor(self):
+ """Pass custom executor to run_in_executor for synchronous checks."""
+
+ class SyncCheck(HealthCheck):
+ def run(self):
+ pass
+
+ check = SyncCheck()
+ custom_executor = MagicMock()
+ loop = asyncio.get_running_loop()
+ with patch.object(loop, "run_in_executor", wraps=loop.run_in_executor)
as mock:
+ await check.get_result(executor=custom_executor)
+ mock.assert_called_once_with(custom_executor, check.run)
+
+ @pytest.mark.asyncio
+ async def test_run__sync_check_default_executor(self):
+ """Use default executor (None) for synchronous checks when none is
supplied."""
+
+ class SyncCheck(HealthCheck):
+ def run(self):
+ pass
+
+ check = SyncCheck()
+ loop = asyncio.get_running_loop()
+ with patch.object(loop, "run_in_executor", wraps=loop.run_in_executor)
as mock:
+ result = await check.get_result()
+ mock.assert_called_once_with(None, check.run)
+ assert result.error is None
+
+ @pytest.mark.asyncio
async def test_result__timing(self):
"""Result includes execution time."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-health-check-4.2.2/tests/test_views.py
new/django-health-check-4.3.0/tests/test_views.py
--- old/django-health-check-4.2.2/tests/test_views.py 2026-04-09
18:03:15.000000000 +0200
+++ new/django-health-check-4.3.0/tests/test_views.py 2026-04-15
19:08:10.000000000 +0200
@@ -1038,3 +1038,36 @@
assert ": OK" in content
assert "FailingBackend" in content
assert "Failed" in content
+
+ def test_get_executor__returns_null_context(self):
+ """Default get_executor returns a nullcontext yielding None."""
+ import contextlib
+
+ view = HealthCheckView()
+ ctx = view.get_executor()
+ assert isinstance(ctx, contextlib.AbstractContextManager)
+ with ctx as executor:
+ assert executor is None
+
+ @pytest.mark.asyncio
+ async def test_get_executor__custom_thread_pool(self):
+ """Custom get_executor returning a ThreadPoolExecutor is used for sync
checks."""
+ from concurrent.futures import ThreadPoolExecutor
+
+ from django.test import AsyncRequestFactory
+
+ class CustomHealthCheckView(HealthCheckView):
+ def get_executor(self):
+ return ThreadPoolExecutor(max_workers=1)
+
+ class SyncCheck(HealthCheck):
+ def run(self):
+ pass
+
+ factory = AsyncRequestFactory()
+ request = factory.get("/")
+ view = CustomHealthCheckView.as_view(checks=[SyncCheck])
+ response = await view(request)
+ if hasattr(response, "render"):
+ response.render()
+ assert response.status_code == 200