from __future__ import annotations from datetime import datetime # Small, dependency-free value coercions shared across the blueprints. Kept in one # place so the "parse an ISO date" / "is this flag truthy" logic has a single # definition instead of a near-identical copy per module. def iso(dt: datetime | None) -> str | None: """A tz-aware datetime as an ISO-8601 string, or None. Collapses the `x.isoformat() if x else None` idiom repeated across every serializer.""" return dt.isoformat() if dt else None def parse_dt(raw: object) -> datetime | None: """Parse an ISO-8601 timestamp (accepting a trailing 'Z' for UTC). Returns None for anything that isn't a non-empty string or doesn't parse, so callers can treat "absent", "blank", and "malformed" uniformly (a route that wants a 400 on malformed input checks for None itself). """ if not isinstance(raw, str) or not raw: return None try: return datetime.fromisoformat(raw.replace("Z", "+00:00")) except ValueError: return None def coerce_bool(raw: object) -> bool: """Truthy for the common flag spellings ('true'/'1'/'yes'/'on', or a real bool). Used for query-string booleans (?has_reminder=true) and DB-backed bool settings, which arrive as strings. """ if isinstance(raw, bool): return raw if isinstance(raw, str): return raw.strip().lower() in ("true", "1", "yes", "on") return False