Source code for next.partial.headers

"""Partial-request headers, intent parsing, and Vary stamping."""

import enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast

from django.utils.cache import patch_vary_headers


if TYPE_CHECKING:
    from django.http import HttpRequest, HttpResponseBase


REQUEST_FLAG = "X-Next-Request"
ZONE = "X-Next-Zone"
VALIDATE = "X-Next-Validate"
MERGE = "X-Next-Merge"
VERSION = "X-Next-Version"
REQUEST_ID = "X-Next-Request-Id"
ORIGIN = "X-Next-Origin"

RESPONSE_VERSION = "X-Next-Version"
RESPONSE_FORM = "X-Next-Form"
RESPONSE_ACTION = "X-Next-Action"

CONTENT_TYPE = "application/vnd.next.patches+json"
ACCEPT = "application/vnd.next.patches+json, text/html;q=0.9"

# VALIDATE, ORIGIN, and REQUEST_ID never change the cached body, so not in Vary.
VARY_HEADERS: tuple[str, ...] = (REQUEST_FLAG, ZONE, MERGE, VERSION)

_INTENT_ATTR = "_next_partial_intent"
_UNSET = object()


[docs] class MergeMode(enum.StrEnum): """Merge intent of a paginating partial request.""" APPEND = "append" PREPEND = "prepend"
[docs] @dataclass(frozen=True, slots=True) class PartialIntent: """Parsed partial-request headers naming what the client asks for. The fields mirror the request-header table of the wire protocol. A request without the `X-Next-Request` switch is not partial and every derived field stays empty. Names are server-registry indices, never selectors or swap strategies. """ partial: bool = False zones: tuple[str, ...] = () validate_fields: tuple[str, ...] = () merge: "MergeMode | None" = None version: str | None = None request_id: str | None = None origin: str | None = None
def _split_names(raw: str | None) -> tuple[str, ...]: """Split a comma-separated header value into trimmed non-empty names.""" if not raw: return () return tuple(name.strip() for name in raw.split(",") if name.strip()) def _parse_merge(raw: str | None) -> "MergeMode | None": """Return the merge mode named by the header value, if recognised.""" if not raw: return None try: return MergeMode(raw.strip()) except ValueError: return None def _value(request: "HttpRequest", name: str) -> str | None: """Return a single request header value, or None when absent.""" return request.headers.get(name) def _parse_intent(request: "HttpRequest") -> PartialIntent: """Parse the partial-request headers of a request into an intent.""" if _value(request, REQUEST_FLAG) != "1": return PartialIntent() return PartialIntent( partial=True, zones=_split_names(_value(request, ZONE)), validate_fields=_split_names(_value(request, VALIDATE)), merge=_parse_merge(_value(request, MERGE)), version=_value(request, VERSION), request_id=_value(request, REQUEST_ID), origin=_value(request, ORIGIN), )
[docs] def partial_intent(request: "HttpRequest") -> PartialIntent: """Return the partial intent of the request, memoised on the request.""" cached = getattr(request, _INTENT_ATTR, _UNSET) if cached is not _UNSET: return cast("PartialIntent", cached) intent = _parse_intent(request) setattr(request, _INTENT_ATTR, intent) return intent
[docs] def is_partial_request(request: "HttpRequest") -> bool: """Return True when the request asks for a partial response.""" return partial_intent(request).partial
def set_partial_vary(response: "HttpResponseBase") -> None: """Add the partial Vary headers so shared caches stay unpoisoned.""" patch_vary_headers(response, VARY_HEADERS) __all__ = [ "ACCEPT", "CONTENT_TYPE", "VARY_HEADERS", "MergeMode", "PartialIntent", "is_partial_request", "partial_intent", "set_partial_vary", ]