Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 55 additions & 11 deletions docs/reference/api.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,73 @@
# API Reference

## Checks
This is the auto-generated reference for django-watchman's Python API.
For a higher-level overview see the [Getting Started](../getting-started.md)
guide and the [Configuration](../configuration.md) page.

::: watchman.checks
---

## Constants
## Views

::: watchman.constants
The Django views that power watchman's HTTP endpoints.

## Decorators
::: watchman.views
options:
show_source: false
members:
- status
- bare_status
- ping
- dashboard
- run_checks

::: watchman.decorators
## Checks

Built-in health-check functions for Django backing services. Each function
can be referenced by its dotted path in
[`WATCHMAN_CHECKS`][watchman.settings.WATCHMAN_CHECKS].

::: watchman.checks
options:
members:
- caches
- databases
- email
- storage

## Settings

All settings are read from your Django `settings` module with sensible
defaults. See the [Configuration](../configuration.md) guide for usage
details.

::: watchman.settings

## URLs

::: watchman.urls
Include these in your root URL configuration with
`url(r'^watchman/', include('watchman.urls'))`.

## Utils
| URL pattern | View | Name |
|--------------|-----------------------------------------------|-------------|
| `/` | [`status`][watchman.views.status] | `status` |
| `/dashboard/`| [`dashboard`][watchman.views.dashboard] | `dashboard` |
| `/ping/` | [`ping`][watchman.views.ping] | `ping` |

::: watchman.utils
## Constants

## Views
::: watchman.constants

::: watchman.views
## Decorators

::: watchman.decorators
options:
show_source: false
members:
- check
- parse_auth_header
- token_required
- auth

## Utils

::: watchman.utils
4 changes: 3 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ plugins:
options:
show_source: true
show_root_heading: true
heading_level: 2
heading_level: 3
members_order: source
filters:
- "!^_"

markdown_extensions:
- admonition
Expand Down
52 changes: 52 additions & 0 deletions watchman/checks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
"""Built-in health check functions for Django backing services.

Each public function (e.g. `caches`, `databases`, `email`, `storage`) returns
a dictionary suitable for inclusion in the watchman JSON response. They can be
referenced by their dotted Python path in the
[`WATCHMAN_CHECKS`][watchman.settings.WATCHMAN_CHECKS] setting.
"""

import uuid
from pathlib import PurePath
from typing import Any
Expand Down Expand Up @@ -77,16 +85,60 @@ def _check_storage() -> CheckStatus:


def caches() -> dict[str, list[CheckResult]]:
"""Check all configured caches by writing, reading, and deleting a key.

Iterates over every cache defined in
[`WATCHMAN_CACHES`][watchman.settings.WATCHMAN_CACHES] (defaults to
Django's `CACHES` setting).

Returns:
A dictionary of the form:

{"caches": [{"default": {"ok": True}}, ...]}
"""
return {"caches": _check_caches(watchman_settings.WATCHMAN_CACHES)}


def databases() -> dict[str, list[CheckResult]]:
"""Check all configured databases by executing a ``SELECT 1`` query.

Iterates over every database defined in
[`WATCHMAN_DATABASES`][watchman.settings.WATCHMAN_DATABASES] (defaults to
Django's `DATABASES` setting).

Returns:
A dictionary of the form:

{"databases": [{"default": {"ok": True}}, ...]}
"""
return {"databases": _check_databases(watchman_settings.WATCHMAN_DATABASES)}


def email() -> dict[str, CheckResult]:
"""Check the email backend by sending a test message.

Only included when
[`WATCHMAN_ENABLE_PAID_CHECKS`][watchman.settings.WATCHMAN_ENABLE_PAID_CHECKS]
is ``True`` or when explicitly listed in
[`WATCHMAN_CHECKS`][watchman.settings.WATCHMAN_CHECKS].

Returns:
A dictionary of the form:

{"email": {"ok": True}}
"""
return {"email": _check_email()}


def storage() -> dict[str, CheckResult]:
"""Check the default file storage backend.

Creates a temporary file, reads it back, and deletes it using Django's
default storage.

Returns:
A dictionary of the form:

{"storage": {"ok": True}}
"""
return {"storage": _check_storage()}
6 changes: 6 additions & 0 deletions watchman/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"""Default check tuples used to populate [`WATCHMAN_CHECKS`][watchman.settings.WATCHMAN_CHECKS]."""

DEFAULT_CHECKS: tuple[str, ...] = (
"watchman.checks.caches",
"watchman.checks.databases",
"watchman.checks.storage",
)
"""Checks included by default: caches, databases, and storage."""

PAID_CHECKS: tuple[str, ...] = ("watchman.checks.email",)
"""Checks that may incur cost (e.g. sending an email). Only included when
[`WATCHMAN_ENABLE_PAID_CHECKS`][watchman.settings.WATCHMAN_ENABLE_PAID_CHECKS]
is ``True``."""
18 changes: 12 additions & 6 deletions watchman/decorators.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Decorators used to protect and wrap watchman views and checks."""

import logging
import traceback
from collections.abc import Callable
Expand All @@ -14,8 +16,10 @@


def check(func: Callable[..., CheckResult]) -> Callable[..., CheckResult]:
"""
Decorator which wraps checks and returns an error response on failure.
"""Decorator that wraps a check function and converts exceptions into error results.

If the wrapped function raises, the exception is caught and returned as a
`CheckStatus` with ``ok=False``, the error message, and a stacktrace.
"""

def wrapped(*args: Any, **kwargs: Any) -> CheckResult:
Expand Down Expand Up @@ -71,12 +75,14 @@ def parse_auth_header(auth_header: str) -> str:
def token_required(
view_func: Callable[..., HttpResponse],
) -> Callable[..., HttpResponse]:
"""
Decorator which ensures that one of the WATCHMAN_TOKENS is provided if set.
"""Decorator that enforces token-based authentication on a view.

WATCHMAN_TOKEN_NAME can also be set if the token GET parameter must be
customized.
When [`WATCHMAN_TOKENS`][watchman.settings.WATCHMAN_TOKENS] (or the
deprecated `WATCHMAN_TOKEN`) is set, the request must supply a matching
token via the ``Authorization`` header or a query parameter named by
[`WATCHMAN_TOKEN_NAME`][watchman.settings.WATCHMAN_TOKEN_NAME].

Returns an ``HTTP 403`` response when the token is missing or invalid.
"""

def _get_passed_token(request: HttpRequest) -> str | None:
Expand Down
42 changes: 40 additions & 2 deletions watchman/settings.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,83 @@
"""Resolved watchman settings with their defaults.

All settings are read from your Django ``settings`` module via ``getattr``
with sensible defaults. See the [Configuration](../configuration.md) guide for
full usage details.
"""

from typing import Any

from django.conf import settings

from watchman.constants import DEFAULT_CHECKS, PAID_CHECKS

# TODO: these should not be module level (https://github.com/mwarkentin/django-watchman/issues/13)
WATCHMAN_ENABLE_PAID_CHECKS: bool = getattr(
settings, "WATCHMAN_ENABLE_PAID_CHECKS", False
)
"""Include paid checks (e.g. email) in the default check list. Default: ``False``."""

WATCHMAN_AUTH_DECORATOR: str | None = getattr(
settings, "WATCHMAN_AUTH_DECORATOR", "watchman.decorators.token_required"
)
# TODO: Remove for django-watchman 1.0
"""Dotted path to a decorator applied to protected views. Set to ``None`` to
disable authentication. Default: ``"watchman.decorators.token_required"``."""

WATCHMAN_TOKEN: str | None = getattr(settings, "WATCHMAN_TOKEN", None)
"""*Deprecated* -- use [`WATCHMAN_TOKENS`][watchman.settings.WATCHMAN_TOKENS]
instead. Will be removed in django-watchman 1.0."""

WATCHMAN_TOKENS: str | None = getattr(settings, "WATCHMAN_TOKENS", None)
"""Comma-separated list of accepted authentication tokens. Default: ``None``
(no token required)."""

WATCHMAN_TOKEN_NAME: str = getattr(settings, "WATCHMAN_TOKEN_NAME", "watchman-token")
"""Name of the query-string parameter used to pass the token.
Default: ``"watchman-token"``."""

WATCHMAN_ERROR_CODE: int = getattr(settings, "WATCHMAN_ERROR_CODE", 500)
"""HTTP status code returned when a check fails. Default: ``500``."""

WATCHMAN_EMAIL_SENDER: str = getattr(
settings, "WATCHMAN_EMAIL_SENDER", "[email protected]"
)
"""``From`` address for the email check. Default: ``"[email protected]"``."""

WATCHMAN_EMAIL_RECIPIENTS: list[str] = getattr(
settings, "WATCHMAN_EMAIL_RECIPIENTS", ["[email protected]"]
)
"""List of ``To`` addresses for the email check. Default: ``["[email protected]"]``."""

WATCHMAN_EMAIL_HEADERS: dict[str, str] = getattr(settings, "WATCHMAN_EMAIL_HEADERS", {})
"""Extra headers added to the test email. Default: ``{}``."""

WATCHMAN_CACHES: dict[str, Any] = getattr(settings, "WATCHMAN_CACHES", settings.CACHES)
"""Cache aliases to check. Defaults to Django's ``CACHES`` setting."""

WATCHMAN_DATABASES: dict[str, Any] = getattr(
settings, "WATCHMAN_DATABASES", settings.DATABASES
)
"""Database aliases to check. Defaults to Django's ``DATABASES`` setting."""

WATCHMAN_DISABLE_APM: bool = getattr(settings, "WATCHMAN_DISABLE_APM", False)
"""Suppress APM tracing (New Relic, Datadog) for watchman views. Default: ``False``."""

WATCHMAN_STORAGE_PATH: str = getattr(
settings, "WATCHMAN_STORAGE_PATH", settings.MEDIA_ROOT
)
"""Subdirectory within the default storage backend used for the storage check.
Defaults to ``MEDIA_ROOT``."""

_checks: tuple[str, ...] = DEFAULT_CHECKS

if WATCHMAN_ENABLE_PAID_CHECKS:
_checks = DEFAULT_CHECKS + PAID_CHECKS

WATCHMAN_CHECKS: tuple[str, ...] = getattr(settings, "WATCHMAN_CHECKS", _checks)
"""Tuple of dotted paths to check functions that watchman will execute.
Defaults to [`DEFAULT_CHECKS`][watchman.constants.DEFAULT_CHECKS] (plus
[`PAID_CHECKS`][watchman.constants.PAID_CHECKS] when
[`WATCHMAN_ENABLE_PAID_CHECKS`][watchman.settings.WATCHMAN_ENABLE_PAID_CHECKS]
is ``True``)."""

EXPOSE_WATCHMAN_VERSION: bool = getattr(settings, "EXPOSE_WATCHMAN_VERSION", False)
"""Add an ``X-Watchman-Version`` header to responses. Default: ``False``."""
14 changes: 14 additions & 0 deletions watchman/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Utility helpers used internally by watchman checks and views."""

from collections.abc import Generator
from typing import Any

Expand All @@ -9,13 +11,25 @@


def get_cache(cache_name: str) -> BaseCache:
"""Return the Django cache backend for *cache_name*."""
return django_cache.caches[cache_name]


def get_checks(
check_list: list[str] | None = None,
skip_list: list[str] | None = None,
) -> Generator[Any]:
"""Yield callable check functions from [`WATCHMAN_CHECKS`][watchman.settings.WATCHMAN_CHECKS].

Args:
check_list: If provided, only checks whose dotted path is in this list
are yielded.
skip_list: If provided, checks whose dotted path is in this list are
excluded.

Yields:
Callable check functions resolved via `import_string`.
"""
checks_to_run = frozenset(WATCHMAN_CHECKS)

if check_list is not None:
Expand Down
Loading