Coverage for src/lumen/api/_helpers.py: 86%
35 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-08-31 18:46 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-08-31 18:46 +0000
1"""
2This file contains shared helper utilities for HTTP requests and responses.
3It provides functions for parsing headers, handling locations, and structuring outcomes.
4"""
6from __future__ import annotations
8from datetime import UTC, datetime
9from email.utils import format_datetime
10from typing import Any
12import uuid_utils
14from ..core.exceptions import OperationOutcomeError
17def etag(version_id: int) -> str:
18 return f'W/"{version_id}"'
21def last_modified(dt: datetime) -> str:
22 """HTTP-date for the ``Last-Modified`` response header (RFC 7231)."""
23 if dt.tzinfo is None:
24 dt = dt.replace(tzinfo=UTC)
25 return format_datetime(dt, usegmt=True)
28def last_modified_instant(dt: datetime) -> str:
29 """FHIR ``instant`` (ISO 8601) for ``Bundle.entry.response.lastModified``.
31 This is a FHIR data field, not an HTTP header — it must be ISO 8601, not
32 the RFC 7231 HTTP-date produced by :func:`last_modified`.
33 """
34 if dt.tzinfo is None:
35 dt = dt.replace(tzinfo=UTC)
36 return dt.isoformat()
39def inject_meta(
40 resource: dict[str, Any], version_id: int, last_updated: datetime
41) -> dict[str, Any]:
42 """Return resource with meta.versionId and meta.lastUpdated set from DB values."""
43 meta: dict[str, Any] = dict(resource.get("meta") or {})
44 meta["versionId"] = str(version_id)
45 if last_updated.tzinfo is None:
46 last_updated = last_updated.replace(tzinfo=UTC)
47 meta["lastUpdated"] = last_updated.isoformat()
48 return {**resource, "meta": meta}
51def new_id() -> str:
52 return str(uuid_utils.uuid7())
55def parse_if_match(header: str | None) -> int | None:
56 """Parse If-Match: W/"<version_id>" → int, or None if absent."""
57 if header is None:
58 return None
59 stripped = header.strip()
60 if stripped.startswith('W/"') and stripped.endswith('"'):
61 try:
62 return int(stripped[3:-1])
63 except ValueError:
64 pass
65 raise OperationOutcomeError(400, "invalid", f"Malformed If-Match header: {header}")