django-health-check 3.23.2__py3-none-any.whl → 4.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. {django_health_check-3.23.2.dist-info → django_health_check-4.0rc1.dist-info}/METADATA +9 -5
  2. django_health_check-4.0rc1.dist-info/RECORD +20 -0
  3. health_check/__init__.py +5 -14
  4. health_check/_version.py +3 -3
  5. health_check/base.py +93 -0
  6. health_check/checks.py +329 -0
  7. health_check/contrib/celery.py +70 -0
  8. health_check/contrib/kafka.py +69 -0
  9. health_check/contrib/rabbitmq.py +43 -0
  10. health_check/contrib/redis.py +63 -0
  11. health_check/contrib/rss.py +113 -0
  12. health_check/exceptions.py +6 -9
  13. health_check/management/commands/health_check.py +20 -66
  14. health_check/templates/health_check/index.html +61 -43
  15. health_check/views.py +176 -75
  16. django_health_check-3.23.2.dist-info/RECORD +0 -63
  17. health_check/backends.py +0 -101
  18. health_check/cache/__init__.py +0 -0
  19. health_check/cache/apps.py +0 -14
  20. health_check/cache/backends.py +0 -50
  21. health_check/conf.py +0 -8
  22. health_check/contrib/celery/__init__.py +0 -3
  23. health_check/contrib/celery/apps.py +0 -31
  24. health_check/contrib/celery/backends.py +0 -46
  25. health_check/contrib/celery/tasks.py +0 -6
  26. health_check/contrib/celery_ping/__init__.py +0 -0
  27. health_check/contrib/celery_ping/apps.py +0 -19
  28. health_check/contrib/celery_ping/backends.py +0 -74
  29. health_check/contrib/db_heartbeat/__init__.py +0 -0
  30. health_check/contrib/db_heartbeat/apps.py +0 -19
  31. health_check/contrib/db_heartbeat/backends.py +0 -44
  32. health_check/contrib/mail/__init__.py +0 -0
  33. health_check/contrib/mail/apps.py +0 -19
  34. health_check/contrib/mail/backends.py +0 -61
  35. health_check/contrib/migrations/__init__.py +0 -0
  36. health_check/contrib/migrations/apps.py +0 -19
  37. health_check/contrib/migrations/backends.py +0 -31
  38. health_check/contrib/psutil/__init__.py +0 -0
  39. health_check/contrib/psutil/apps.py +0 -36
  40. health_check/contrib/psutil/backends.py +0 -63
  41. health_check/contrib/rabbitmq/__init__.py +0 -3
  42. health_check/contrib/rabbitmq/apps.py +0 -19
  43. health_check/contrib/rabbitmq/backends.py +0 -57
  44. health_check/contrib/redis/__init__.py +0 -3
  45. health_check/contrib/redis/apps.py +0 -19
  46. health_check/contrib/redis/backends.py +0 -75
  47. health_check/contrib/s3boto3_storage/__init__.py +0 -0
  48. health_check/contrib/s3boto3_storage/apps.py +0 -19
  49. health_check/contrib/s3boto3_storage/backends.py +0 -32
  50. health_check/contrib/s3boto_storage/__init__.py +0 -0
  51. health_check/contrib/s3boto_storage/apps.py +0 -20
  52. health_check/contrib/s3boto_storage/backends.py +0 -27
  53. health_check/db/__init__.py +0 -0
  54. health_check/db/apps.py +0 -20
  55. health_check/db/backends.py +0 -23
  56. health_check/db/migrations/0001_initial.py +0 -34
  57. health_check/db/migrations/0002_alter_testmodel_options.py +0 -32
  58. health_check/db/migrations/__init__.py +0 -0
  59. health_check/db/models.py +0 -9
  60. health_check/deprecation.py +0 -35
  61. health_check/mixins.py +0 -86
  62. health_check/plugins.py +0 -25
  63. health_check/storage/__init__.py +0 -0
  64. health_check/storage/apps.py +0 -12
  65. health_check/storage/backends.py +0 -73
  66. health_check/urls.py +0 -18
  67. {django_health_check-3.23.2.dist-info → django_health_check-4.0rc1.dist-info}/WHEEL +0 -0
  68. {django_health_check-3.23.2.dist-info → django_health_check-4.0rc1.dist-info}/licenses/LICENSE +0 -0
health_check/views.py CHANGED
@@ -1,23 +1,25 @@
1
+ import asyncio
1
2
  import re
2
- import warnings
3
- from functools import cached_property
3
+ import typing
4
4
 
5
5
  from django.db import transaction
6
6
  from django.http import HttpResponse, JsonResponse
7
+ from django.utils import timezone
8
+ from django.utils.cache import patch_vary_headers
7
9
  from django.utils.decorators import method_decorator
10
+ from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
8
11
  from django.utils.module_loading import import_string
9
12
  from django.views.decorators.cache import never_cache
10
13
  from django.views.generic import TemplateView
11
14
 
12
- from health_check.deprecation import deprecated
13
- from health_check.mixins import CheckMixin
15
+ from health_check.base import HealthCheck
14
16
 
15
17
 
16
18
  class MediaType:
17
19
  """
18
20
  Sortable object representing HTTP's accept header.
19
21
 
20
- .. seealso:: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
22
+ See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
21
23
  """
22
24
 
23
25
  pattern = re.compile(
@@ -54,20 +56,21 @@ class MediaType:
54
56
 
55
57
  @classmethod
56
58
  def from_string(cls, value):
57
- """Return single instance parsed from given accept header string."""
59
+ """Return single instance parsed from the given Accept-header string."""
58
60
  match = cls.pattern.search(value)
59
61
  if match is None:
60
62
  raise ValueError(f'"{value}" is not a valid media type')
61
- try:
62
- return cls(match.group("mime_type"), float(match.group("weight") or 1))
63
- except ValueError:
64
- return cls(value)
63
+ return cls(match.group("mime_type"), float(match.group("weight") or 1))
65
64
 
66
65
  @classmethod
67
66
  def parse_header(cls, value="*/*"):
68
67
  """Parse HTTP accept header and return instances sorted by weight."""
69
68
  yield from sorted(
70
- (cls.from_string(token.strip()) for token in value.split(",") if token.strip()),
69
+ (
70
+ cls.from_string(token.strip())
71
+ for token in value.split(",")
72
+ if token.strip()
73
+ ),
71
74
  reverse=True,
72
75
  )
73
76
 
@@ -84,103 +87,201 @@ class MediaType:
84
87
  return self.weight.__lt__(other.weight)
85
88
 
86
89
 
87
- @method_decorator(transaction.non_atomic_requests, name="dispatch")
88
- class _MainView(CheckMixin, TemplateView):
89
- """Deprecated: Use HealthCheckView instead."""
90
+ class HealthCheckView(TemplateView):
91
+ """Perform health checks and return results in various formats."""
90
92
 
91
93
  template_name = "health_check/index.html"
94
+ feed_author = "Django Health Check"
95
+
96
+ checks: typing.Iterable[
97
+ type[HealthCheck] | str | tuple[type[HealthCheck] | str, dict[str, typing.Any]]
98
+ ] = (
99
+ "health_check.checks.Cache",
100
+ "health_check.checks.Database",
101
+ "health_check.checks.Disk",
102
+ "health_check.checks.DNS",
103
+ "health_check.checks.Mail",
104
+ "health_check.checks.Memory",
105
+ "health_check.checks.Storage",
106
+ )
107
+
108
+ @method_decorator(transaction.non_atomic_requests)
109
+ async def dispatch(self, request, *args, **kwargs):
110
+ response = await super().dispatch(request, *args, **kwargs)
111
+ patch_vary_headers(response, ["Accept"])
112
+ return response
92
113
 
93
114
  @method_decorator(never_cache)
94
- def get(self, request, *args, **kwargs):
95
- subset = kwargs.get("subset")
96
- health_check_has_error = self.check(subset)
97
- status_code = 500 if health_check_has_error else 200
115
+ async def get(self, request, *args, **kwargs):
116
+ self.results = await asyncio.gather(
117
+ *(check.get_result() for check in self.get_checks())
118
+ )
119
+ has_errors = any(result.error for result in self.results)
120
+ status_code = 500 if has_errors else 200
98
121
  format_override = request.GET.get("format")
99
122
 
100
- if format_override == "json":
101
- return self.render_to_response_json(self.filter_plugins(subset=subset), status_code)
123
+ match format_override:
124
+ case "json":
125
+ return self.render_to_response_json(status_code)
126
+ case "text":
127
+ return self.render_to_response_text(status_code)
128
+ case "atom":
129
+ return self.render_to_response_atom()
130
+ case "rss":
131
+ return self.render_to_response_rss()
132
+ case "openmetrics":
133
+ return self.render_to_response_openmetrics()
102
134
 
103
135
  accept_header = request.headers.get("accept", "*/*")
104
136
  for media in MediaType.parse_header(accept_header):
105
- if media.mime_type in (
106
- "text/html",
107
- "application/xhtml+xml",
108
- "text/*",
109
- "*/*",
110
- ):
111
- context = self.get_context_data(**kwargs)
112
- return self.render_to_response(context, status=status_code)
113
- elif media.mime_type in ("application/json", "application/*"):
114
- return self.render_to_response_json(self.filter_plugins(subset=subset), status_code)
137
+ match media.mime_type:
138
+ case "text/plain":
139
+ return self.render_to_response_text(status_code)
140
+ case "text/html" | "application/xhtml+xml" | "text/*" | "*/*":
141
+ context = self.get_context_data(**kwargs)
142
+ return self.render_to_response(context, status=status_code)
143
+ case "application/json" | "application/*":
144
+ return self.render_to_response_json(status_code)
145
+ case "application/atom+xml":
146
+ return self.render_to_response_atom()
147
+ case "application/rss+xml":
148
+ return self.render_to_response_rss()
149
+ case "application/openmetrics-text":
150
+ return self.render_to_response_openmetrics()
115
151
  return HttpResponse(
116
- "Not Acceptable: Supported content types: text/html, application/json",
152
+ "Not Acceptable: Supported content types: text/plain, text/html, application/json, application/atom+xml, application/rss+xml, application/openmetrics-text",
117
153
  status=406,
118
154
  content_type="text/plain",
119
155
  )
120
156
 
121
157
  def get_context_data(self, **kwargs):
122
- subset = kwargs.get("subset")
123
158
  return {
124
159
  **super().get_context_data(**kwargs),
125
- "plugins": self.filter_plugins(subset=subset).values(),
126
- "errors": any(p.errors for p in self.filter_plugins(subset=subset).values()),
160
+ "results": self.results,
161
+ "errors": any(result.error for result in self.results),
127
162
  }
128
163
 
129
- def render_to_response_json(self, plugins, status):
164
+ def render_to_response_json(self, status):
165
+ """Return JSON response with health check results."""
130
166
  return JsonResponse(
131
- {label: str(p.pretty_status()) for label, p in plugins.items()},
167
+ {
168
+ repr(result.check): "OK" if not result.error else str(result.error)
169
+ for result in self.results
170
+ },
132
171
  status=status,
133
172
  )
134
173
 
174
+ def render_to_response_text(self, status):
175
+ """Return plain text response with health check results."""
176
+ lines = (
177
+ f"{repr(result.check)}: {'OK' if not result.error else str(result.error)}"
178
+ for result in self.results
179
+ )
180
+ return HttpResponse(
181
+ "\n".join(lines) + "\n",
182
+ content_type="text/plain; charset=utf-8",
183
+ status=status,
184
+ )
135
185
 
136
- @deprecated(
137
- "MainView is deprecated: use `HealthCheckView` instead (view-based API). Action: replace `MainView` usage with `HealthCheckView.as_view(checks=...)`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md)."
138
- )
139
- class MainView(_MainView):
140
- """Deprecated: Use HealthCheckView instead."""
186
+ def render_to_response_atom(self):
187
+ """Return Atom feed response with health check results."""
188
+ return self._render_feed(Atom1Feed)
141
189
 
142
- pass
190
+ def render_to_response_rss(self):
191
+ """Return RSS 2.0 feed response with health check results."""
192
+ return self._render_feed(Rss201rev2Feed)
143
193
 
194
+ def _escape_openmetrics_label_value(self, value):
195
+ r"""
196
+ Escape label value according to OpenMetrics specification.
144
197
 
145
- class HealthCheckView(_MainView):
146
- """Perform health checks and return results in various formats."""
198
+ Escapes backslashes, double quotes, and newlines as required by the spec:
199
+ - Backslash (\) -> \\
200
+ - Double quote (") -> \"
201
+ - Line feed (\n) -> \n
202
+ """
203
+ return value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
147
204
 
148
- checks: list[str | tuple[str, dict]] | None = None
205
+ def render_to_response_openmetrics(self):
206
+ """Return OpenMetrics response with health check results."""
207
+ lines = [
208
+ "# HELP django_health_check_status Health check status (1 = healthy, 0 = unhealthy)",
209
+ "# TYPE django_health_check_status gauge",
210
+ ]
211
+ has_errors: bool = False
149
212
 
150
- @classmethod
151
- def as_view(cls, **initkwargs):
152
- if "warnings_as_errors" in initkwargs:
153
- warnings.warn(
154
- "`warnings_as_errors` argument is deprecated and will be removed the next major version.",
155
- DeprecationWarning,
156
- stacklevel=2,
213
+ # Add status metrics for each check
214
+ for result in self.results:
215
+ safe_label = self._escape_openmetrics_label_value(repr(result.check))
216
+ has_errors |= bool(result.error)
217
+ lines.append(
218
+ f'django_health_check_status{{check="{safe_label}"}} {not result.error:d}'
219
+ )
220
+
221
+ # Add response time metrics
222
+ lines += [
223
+ "",
224
+ "# HELP django_health_check_response_time_seconds Health check response time in seconds",
225
+ "# TYPE django_health_check_response_time_seconds gauge",
226
+ ]
227
+
228
+ for result in self.results:
229
+ safe_label = self._escape_openmetrics_label_value(repr(result.check))
230
+ lines.append(
231
+ f'django_health_check_response_time_seconds{{check="{safe_label}"}} {result.time_taken:.6f}'
157
232
  )
158
- if "use_threading" in initkwargs:
159
- warnings.warn(
160
- "`use_threading` argument is deprecated and will be removed the next major version.",
161
- DeprecationWarning,
162
- stacklevel=2,
233
+
234
+ # Add overall health status
235
+ lines += [
236
+ "",
237
+ "# HELP django_health_check_overall_status Overall health check status (1 = all healthy, 0 = at least one unhealthy)",
238
+ "# TYPE django_health_check_overall_status gauge",
239
+ f"django_health_check_overall_status {not has_errors:d}",
240
+ "# EOF",
241
+ ]
242
+
243
+ return HttpResponse(
244
+ "\n".join(lines) + "\n",
245
+ content_type="application/openmetrics-text; version=1.0.0; charset=utf-8",
246
+ status=200, # Prometheus expects 200 even if checks fail
247
+ )
248
+
249
+ def _render_feed(self, feed_class):
250
+ """Generate RSS or Atom feed with health check results."""
251
+ feed = feed_class(
252
+ title="Health Check Status",
253
+ link=self.request.build_absolute_uri(),
254
+ description="Current status of system health checks",
255
+ feed_url=self.request.build_absolute_uri(),
256
+ )
257
+
258
+ for result in self.results:
259
+ feed.add_item(
260
+ title=repr(result.check),
261
+ link=self.request.build_absolute_uri(),
262
+ description=f"{result.check!r}\nResponse time: {result.time_taken:.3f}s",
263
+ pubdate=timezone.now(),
264
+ updateddate=timezone.now(),
265
+ author_name=self.feed_author,
266
+ categories=["error", "unhealthy"] if result.error else ["healthy"],
163
267
  )
164
- return super().as_view(**initkwargs)
165
-
166
- def get_plugins(self):
167
- for check in self.checks or [
168
- "health_check.Cache",
169
- "health_check.Database",
170
- "health_check.Disk",
171
- "health_check.Mail",
172
- "health_check.Memory",
173
- "health_check.Storage",
174
- ]:
268
+
269
+ response = HttpResponse(
270
+ feed.writeString("utf-8"),
271
+ content_type=feed.content_type,
272
+ status=200, # Feed readers expect 200 even if checks fail
273
+ )
274
+ return response
275
+
276
+ def get_checks(
277
+ self,
278
+ ) -> typing.Generator[HealthCheck, None, None]:
279
+ """Yield instantiated health check callables."""
280
+ for check in self.checks:
175
281
  try:
176
282
  check, options = check
177
- except ValueError:
283
+ except (ValueError, TypeError):
178
284
  options = {}
179
285
  if isinstance(check, str):
180
286
  check = import_string(check)
181
- plugin_instance = check(**options)
182
- yield repr(plugin_instance), plugin_instance
183
-
184
- @cached_property
185
- def plugins(self):
186
- return dict(self.get_plugins())
287
+ yield check(**options)
@@ -1,63 +0,0 @@
1
- health_check/__init__.py,sha256=vLH1k9cjhuLe_swu-qMxrz1_7fFJA179-UWNcsdA8QA,868
2
- health_check/_version.py,sha256=mxLf1sW9K0QQHtOQxElIqSODHiT_z1DtMeutHlOPwAs,714
3
- health_check/backends.py,sha256=Q4B8yRusBZz8mOz8jNZmqJWQ1Fu6YawYiLNFkw5sUmA,3414
4
- health_check/conf.py,sha256=l3Utl8zLLT42TKUnBFNLnD_i-wYNKmxPXVVyIucGUXc,319
5
- health_check/deprecation.py,sha256=uYGwPFW8lX1xQ33x0VRkoPFoyoLcrMt6DmpX3RHLxj4,1127
6
- health_check/exceptions.py,sha256=L9QiCWYpuz3H05an3wDhz_coKeulHvTYotIT4gjKMXA,743
7
- health_check/mixins.py,sha256=pm1JB1qwU5oBklpR8czPIdPru0JrqDDEnmsiYJGWtms,2924
8
- health_check/plugins.py,sha256=Y7TaRm5ILhvLUKmoF33KrIYz6ubA0Vd5Mjf7IOmvwO8,623
9
- health_check/urls.py,sha256=2AYTZzWO-BBILcsSBD2gTuRCstf9YTp0NZ3k8FfY1RM,627
10
- health_check/views.py,sha256=LJPanE4wZctDiwuqSn6zMTH_JsMqYPSVbLag1yBFbVQ,6543
11
- health_check/cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- health_check/cache/apps.py,sha256=aJKPag3O1oLTdRDlpxqI8l4vE_c-iA8AReROsnlckio,349
13
- health_check/cache/backends.py,sha256=a2nhNOE_wFmZNMBi1G_y8ByS_J1dm_R2X-8JdknB-kQ,1994
14
- health_check/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- health_check/contrib/celery/__init__.py,sha256=xdK73-N2QJ6N9iom2s4Zqhl7nc4_MQrhojjHdD7p7Y8,85
16
- health_check/contrib/celery/apps.py,sha256=0hV0CAUeZMnjHmu0QpmDhQnS48NI95iD1lssly4sAa0,1409
17
- health_check/contrib/celery/backends.py,sha256=iGtaNQq6ZDcXC6UUB8knvNaZS3IE5LkcwPJHnG7lfoc,2221
18
- health_check/contrib/celery/tasks.py,sha256=UWmgZa4oGsXuo_udRVRBoCAOVFibGHjqevw3s8rOnfQ,99
19
- health_check/contrib/celery_ping/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- health_check/contrib/celery_ping/apps.py,sha256=NOg01PnKYOCBSUCqSW62VbLwmeO99h7X_Mf8zjBmkbc,754
21
- health_check/contrib/celery_ping/backends.py,sha256=T8EC3EpfsuzT1HNvyk8ZFJAIMgvVlIwB7TGOsXvcCpQ,2522
22
- health_check/contrib/db_heartbeat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- health_check/contrib/db_heartbeat/apps.py,sha256=_zc7ZYis4U6JpObmQBoFrEZHgC1UFcvO415l0c0cUXw,752
24
- health_check/contrib/db_heartbeat/backends.py,sha256=vfdGzdtCW2wUWwcXS4VNFdm4LopLi4UpylMhUpApA0s,1336
25
- health_check/contrib/mail/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- health_check/contrib/mail/apps.py,sha256=UGwk9BM6oIVingQmr--Joe4ujNPtVsxe6nzQ-fDu9c0,683
27
- health_check/contrib/mail/backends.py,sha256=UdbRPyy-1mZSgSkjMopnvpMDZrJT1xybvSJI9Br3lS4,1940
28
- health_check/contrib/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- health_check/contrib/migrations/apps.py,sha256=oSvxHRPVU6JT48gdbASSI8G-zUUclAQZZVSdJKM_2oM,707
30
- health_check/contrib/migrations/backends.py,sha256=-qx9C4oxqv5ITAnoeQ-8bGxmxZYtbF7RE9y4IMoFIGM,1432
31
- health_check/contrib/psutil/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- health_check/contrib/psutil/apps.py,sha256=VjR4sw6kY4y1GmWohZSRxl8Em1JrdL3ApgPcZ8hGnE0,1350
33
- health_check/contrib/psutil/backends.py,sha256=-5yv3pM8UvrfQgr9PT4dyVmSRSg6j322-ZW-nm3FHho,2383
34
- health_check/contrib/rabbitmq/__init__.py,sha256=PlctAhXwp9DjxPo7DGpWvD1DBTfvWf807t-mhfMoNFU,78
35
- health_check/contrib/rabbitmq/apps.py,sha256=i-1Uo3W6Ofc0_StFABfrxysh_anWVP-mcNKUHqZ6_c8,721
36
- health_check/contrib/rabbitmq/backends.py,sha256=22zNDsX-Ie-a5Rwk4naIpGB3184SV2OaGTXzwBzWbU0,1836
37
- health_check/contrib/redis/__init__.py,sha256=TzSbuXNst7itI8mbrtsCNJXJP40ZpX-r2x6mxMNTgYE,69
38
- health_check/contrib/redis/apps.py,sha256=1bhVAy5iKZer8IwG4WXZYeYUSl7AMTtNPRRhR5ugrLs,700
39
- health_check/contrib/redis/backends.py,sha256=xQ73bnGlOGtvnaNiEOz4bhqxXsD-Qn2y6aVwoiQkWCg,2788
40
- health_check/contrib/s3boto3_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- health_check/contrib/s3boto3_storage/apps.py,sha256=1-7X_v7d6HeKD5MBxyDvrh5vdopsvmnJNjToiFjeDx4,732
42
- health_check/contrib/s3boto3_storage/backends.py,sha256=0WrNNNJiqDVn5_GHQZm6hlEBvSxhWTqWSWUtmVHkzzo,1331
43
- health_check/contrib/s3boto_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- health_check/contrib/s3boto_storage/apps.py,sha256=5pQWKqhBgb_IYpJEJ5pej8bYRcermiGecZUq2aZeRvk,569
45
- health_check/contrib/s3boto_storage/backends.py,sha256=jfvlK2GhJBGEtdqFzNnxsp9W11KPpoRlYzQVBweoncQ,1074
46
- health_check/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- health_check/db/apps.py,sha256=Xmh9FbjROw1qYQbMutb3o4c0XG64KHxHLpaWzq4W31s,708
48
- health_check/db/backends.py,sha256=c-rNcEoqq3X8r08sqJTHqdXZpxba-wloHAeKlI82Bxc,1028
49
- health_check/db/models.py,sha256=kMA0uoAQBwqHLpfVt4gMG8-h3BZIGcFRSAwD9oATBWU,204
50
- health_check/db/migrations/0001_initial.py,sha256=a5sN_dfDXju_Kr32uBqzPvd8B9Ysl1bV8WL5fQwsRZI,826
51
- health_check/db/migrations/0002_alter_testmodel_options.py,sha256=yLcp1aAdJ_2Q-F7MfCVVf2nTojIijnztdulOK1QmpUQ,984
52
- health_check/db/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
- health_check/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- health_check/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- health_check/management/commands/health_check.py,sha256=EWBAhMOq9clravsDwHqCiSt4JQJIRejcg5f2SvIsSeI,3864
56
- health_check/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
- health_check/storage/apps.py,sha256=f6958Deq62DXoe8myL57rnKSkKkPQf2FLM9iJkUdx-U,293
58
- health_check/storage/backends.py,sha256=pkj64HcVnvm9pvgWFhL81IDejTOH8-pLSFwJQ_90jbs,2636
59
- health_check/templates/health_check/index.html,sha256=pOC7GW_r0gDQ5Qz5aJacNFI22N52kbiOzh4aZb9_V-Q,4045
60
- django_health_check-3.23.2.dist-info/licenses/LICENSE,sha256=19Rs8FInCokFQuq03cab_KHwpeyF5pt-lTp7pfJX1iE,1101
61
- django_health_check-3.23.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
62
- django_health_check-3.23.2.dist-info/METADATA,sha256=BjOQs1iaWA_kROQ0sJeZuItjeBeWrRX3yX_xWaQn0lM,3554
63
- django_health_check-3.23.2.dist-info/RECORD,,
health_check/backends.py DELETED
@@ -1,101 +0,0 @@
1
- import dataclasses
2
- import logging
3
- import warnings
4
- from timeit import default_timer as timer
5
-
6
- from health_check.exceptions import HealthCheckException
7
-
8
- logger = logging.getLogger("health-check")
9
-
10
-
11
- @dataclasses.dataclass()
12
- class HealthCheck:
13
- """
14
- Base class for all health check backends.
15
-
16
- To create your own health check backend, subclass this class
17
- and implement the ``check_status`` method.
18
- """
19
-
20
- critical_service: bool = dataclasses.field(init=False, default=True, repr=False)
21
- errors: list[HealthCheckException] = dataclasses.field(init=False, default_factory=list, repr=False)
22
-
23
- def check_status(self):
24
- """
25
- Execute the health check logic.
26
-
27
- This method should be overridden by subclasses to implement
28
- specific health check logic. If the check fails, it should
29
- call `self.add_error` with an appropriate error message or
30
- raise a `HealthCheckException`.
31
-
32
- Raises:
33
- HealthCheckException: If the health check fails.
34
- ServiceWarning: If the health check encounters a warning condition.
35
-
36
- """
37
- raise NotImplementedError
38
-
39
- def run_check(self):
40
- start = timer()
41
- self.errors = []
42
- try:
43
- self.check_status()
44
- except HealthCheckException as e:
45
- self.add_error(e, e)
46
- except BaseException:
47
- logger.exception("Unexpected Error!")
48
- raise
49
- finally:
50
- self.time_taken = timer() - start
51
-
52
- def add_error(self, error, cause=None):
53
- if isinstance(error, HealthCheckException):
54
- pass
55
- elif isinstance(error, str):
56
- msg = error
57
- error = HealthCheckException(msg)
58
- else:
59
- msg = "unknown error"
60
- error = HealthCheckException(msg)
61
- if isinstance(cause, BaseException):
62
- logger.exception(str(error))
63
- else:
64
- logger.error(str(error))
65
- self.errors.append(error)
66
-
67
- def pretty_status(self):
68
- if self.errors:
69
- return "\n".join(str(e) for e in self.errors)
70
- return "OK"
71
-
72
- @property
73
- def status(self):
74
- return int(not self.errors)
75
-
76
- def __repr__(self):
77
- if hasattr(self, "identifier"):
78
- warnings.warn(
79
- "`identifier()` method is deprecated: implement `__repr__()` instead to return a stable identifier. Action: update your backend class to implement `__repr__` and remove `identifier()`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md).",
80
- DeprecationWarning,
81
- )
82
- return self.identifier()
83
-
84
- return self.__class__.__name__
85
-
86
-
87
- class BaseHealthCheck(HealthCheck):
88
- """
89
- Deprecated base class for health check backends.
90
-
91
- This class is maintained for backward compatibility.
92
- New health check backends should inherit from `HealthCheck` instead.
93
- """
94
-
95
- def __init_subclass__(cls, **kwargs):
96
- warnings.warn(
97
- f"{cls.__name__} inherits from deprecated `BaseHealthCheck`: inherit from `HealthCheck` instead. Action: update subclass to inherit from `health_check.backends.HealthCheck`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md).",
98
- DeprecationWarning,
99
- stacklevel=2,
100
- )
101
- super().__init_subclass__(**kwargs)
File without changes
@@ -1,14 +0,0 @@
1
- from django.apps import AppConfig
2
- from django.conf import settings
3
-
4
- from health_check.plugins import plugin_dir
5
-
6
-
7
- class HealthCheckConfig(AppConfig):
8
- name = "health_check.cache"
9
-
10
- def ready(self):
11
- from .backends import CacheBackend
12
-
13
- for backend in settings.CACHES:
14
- plugin_dir.register(CacheBackend, alias=backend)
@@ -1,50 +0,0 @@
1
- import dataclasses
2
- import datetime
3
-
4
- from django.conf import settings
5
- from django.core.cache import CacheKeyWarning, caches
6
-
7
- from health_check.backends import HealthCheck
8
- from health_check.exceptions import ServiceReturnedUnexpectedResult, ServiceUnavailable
9
-
10
- try:
11
- # Exceptions thrown by Redis do not subclass builtin exceptions like ConnectionError.
12
- # Additionally, not only connection errors (ConnectionError -> RedisError) can be raised,
13
- # but also errors for time-outs (TimeoutError -> RedisError)
14
- # and if the backend is read-only (ReadOnlyError -> ResponseError -> RedisError).
15
- # Since we know what we are trying to do here, we are not picky and catch the global exception RedisError.
16
- from redis.exceptions import RedisError
17
- except ModuleNotFoundError:
18
- # In case Redis is not installed and another cache backend is used.
19
- class RedisError(Exception):
20
- pass
21
-
22
-
23
- @dataclasses.dataclass
24
- class CacheBackend(HealthCheck):
25
- """
26
- Check that the cache backend is able to set and get a value.
27
-
28
- Args:
29
- alias: The cache alias to test against.
30
-
31
- """
32
-
33
- alias: str = dataclasses.field(default="default")
34
- cache_key: str = dataclasses.field(
35
- default=getattr(settings, "HEALTHCHECK_CACHE_KEY", "djangohealthcheck_test"), repr=False
36
- )
37
-
38
- def check_status(self):
39
- cache = caches[self.alias]
40
- ts = datetime.datetime.now().timestamp()
41
- try:
42
- cache.set(self.cache_key, f"itworks-{ts}")
43
- if not cache.get(self.cache_key) == f"itworks-{ts}":
44
- raise ServiceUnavailable(f"Cache key {self.cache_key} does not match")
45
- except CacheKeyWarning as e:
46
- self.add_error(ServiceReturnedUnexpectedResult("Cache key warning"), e)
47
- except ValueError as e:
48
- self.add_error(ServiceReturnedUnexpectedResult("ValueError"), e)
49
- except (ConnectionError, RedisError) as e:
50
- self.add_error(ServiceReturnedUnexpectedResult("Connection Error"), e)
health_check/conf.py DELETED
@@ -1,8 +0,0 @@
1
- from django.conf import settings
2
-
3
- HEALTH_CHECK = getattr(settings, "HEALTH_CHECK", {})
4
- HEALTH_CHECK.setdefault("DISK_USAGE_MAX", 90)
5
- HEALTH_CHECK.setdefault("MEMORY_MIN", 100)
6
- HEALTH_CHECK.setdefault("WARNINGS_AS_ERRORS", True)
7
- HEALTH_CHECK.setdefault("SUBSETS", {})
8
- HEALTH_CHECK.setdefault("DISABLE_THREADING", False)
@@ -1,3 +0,0 @@
1
- from ..celery_ping.backends import CeleryPingHealthCheck as Ping
2
-
3
- __all__ = ["Ping"]
@@ -1,31 +0,0 @@
1
- import warnings
2
-
3
- from celery import current_app
4
- from django.apps import AppConfig
5
- from django.conf import settings
6
-
7
- from health_check.plugins import plugin_dir
8
-
9
-
10
- class HealthCheckConfig(AppConfig):
11
- name = "health_check.contrib.celery"
12
-
13
- def ready(self):
14
- from .backends import CeleryHealthCheck
15
-
16
- warnings.warn(
17
- "The `health_check.contrib.celery` app is deprecated: checks are now configured via `HealthCheckView`. Action: remove this sub-app from `INSTALLED_APPS` and add the Celery check(s) to your `HealthCheckView.checks`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md).",
18
- DeprecationWarning,
19
- )
20
-
21
- if hasattr(settings, "HEALTHCHECK_CELERY_TIMEOUT"):
22
- warnings.warn(
23
- "`HEALTHCHECK_CELERY_TIMEOUT` setting is deprecated: it was split into separate timeouts. Action: replace it with `HEALTHCHECK_CELERY_RESULT_TIMEOUT` and `HEALTHCHECK_CELERY_QUEUE_TIMEOUT`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md).",
24
- DeprecationWarning,
25
- )
26
-
27
- for queue in current_app.amqp.queues:
28
- celery_class_name = "CeleryHealthCheck" + queue.title()
29
-
30
- celery_class = type(celery_class_name, (CeleryHealthCheck,), {"queue": queue})
31
- plugin_dir.register(celery_class)
@@ -1,46 +0,0 @@
1
- from celery.exceptions import TaskRevokedError, TimeoutError
2
- from django.conf import settings
3
-
4
- from health_check.backends import HealthCheck
5
- from health_check.deprecation import deprecated
6
- from health_check.exceptions import ServiceReturnedUnexpectedResult, ServiceUnavailable
7
-
8
- from .tasks import add
9
-
10
-
11
- @deprecated(
12
- "`CeleryHealthCheck` is deprecated: use `health_check.contrib.celery.Ping` (or the new view-based Celery ping check) instead. Action: remove legacy CeleryHealthCheck subclasses and configure `HealthCheckView` with the new Celery ping check in your `checks` list. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md)."
13
- )
14
- class CeleryHealthCheck(HealthCheck):
15
- def check_status(self):
16
- timeout = getattr(settings, "HEALTHCHECK_CELERY_TIMEOUT", 3)
17
- result_timeout = getattr(settings, "HEALTHCHECK_CELERY_RESULT_TIMEOUT", timeout)
18
- queue_timeout = getattr(settings, "HEALTHCHECK_CELERY_QUEUE_TIMEOUT", timeout)
19
- priority = getattr(settings, "HEALTHCHECK_CELERY_PRIORITY", None)
20
-
21
- try:
22
- result = add.apply_async(args=[4, 4], expires=queue_timeout, queue=self.queue, priority=priority)
23
- result.get(timeout=result_timeout)
24
- if result.result != 8:
25
- self.add_error(ServiceReturnedUnexpectedResult("Celery returned wrong result"))
26
- except OSError as e:
27
- self.add_error(ServiceUnavailable("IOError"), e)
28
- except NotImplementedError as e:
29
- self.add_error(
30
- ServiceUnavailable("NotImplementedError: Make sure CELERY_RESULT_BACKEND is set"),
31
- e,
32
- )
33
- except TaskRevokedError as e:
34
- self.add_error(
35
- ServiceUnavailable(
36
- "TaskRevokedError: The task was revoked, likely because it spent too long in the queue"
37
- ),
38
- e,
39
- )
40
- except TimeoutError as e:
41
- self.add_error(
42
- ServiceUnavailable("TimeoutError: The task took too long to return a result"),
43
- e,
44
- )
45
- except BaseException as e:
46
- self.add_error(ServiceUnavailable("Unknown error"), e)
@@ -1,6 +0,0 @@
1
- from celery import shared_task
2
-
3
-
4
- @shared_task(ignore_result=False)
5
- def add(x, y):
6
- return x + y