refactor(api): shared serializer + ownership + write-scope helpers (R6 S1a)
Establish the R6 API-contract infra before the S1b payload:dict migration: - iso(dt) in utils.py replaces the ~16 hand-rolled `x.isoformat() if x else None` serializer idioms across the routes. - get_or_404 / owned_or_404 in routes/_common.py — one 404 + existence-leak policy; plex's _own_playlist_or_404 and saved_views' _own_view now delegate to it. - require_write_scope dependency in auth.py collapses the inline has_write_scope→403 checks (channels subscribe/unsubscribe, playlists push/push-plan) that had drifted into three different messages into one; the conditional delete_playlist guard stays inline (its message was already canonical). No response-shape change (iso == isoformat for a present value). Gate: ruff clean, pytest 163 green (+ test_utils for iso).
This commit is contained in:
@@ -2,6 +2,13 @@
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
def iso(dt: datetime | None) -> str | None:
|
||||
"""Serialize a datetime for a JSON response: ISO-8601, or None when the column is NULL.
|
||||
Single source of truth for the `x.isoformat() if x else None` idiom the routes had
|
||||
hand-rolled ~17 times — so a future tz/format tweak lands in one place."""
|
||||
return dt.isoformat() if dt is not None else None
|
||||
|
||||
# Single source of truth for "looks like an email" — used by every endpoint that accepts an
|
||||
# address (auth register/reset/demo, admin invites + demo whitelist, the setup wizard). Keep
|
||||
# the strictness in one place so routes don't drift between this and a looser "@"-in check.
|
||||
|
||||
Reference in New Issue
Block a user