"""Plex's writes for the HTTP layer, one function per use case. None of them commits: the handler does, so a page that fails to save an enrichment still renders, and a Plex write and its local mirror land together. The rest of the mirror's writes stay with the sync (`plex.sync`): a collection's targeted re-sync after a write-back, its local cleanup after a delete. """ from datetime import datetime, timezone from sqlalchemy.orm import Session from app import logs from app.db import SessionLocal from app.plex import detail as plex_detail from app.plex import people as plex_people from app.plex import tmdb as plex_tmdb from app.plex.lookups import by_rating_key from app.plex.models import PlexCollection, PlexItem, PlexLibrary, PlexPerson, PlexShow log = logs.get_logger(__name__) def enrich_show(db: Session, sh: PlexShow, meta: dict) -> None: """Fill a not-yet-enriched show from its live metadata: the full cast (filterable), IMDb and tagline, stamped so later views serve from the row.""" plex_people.enrich_row_people(db, sh, plex_people.cast_from_meta(meta)) plex_detail.apply_rich_detail(sh, meta) sh.people_enriched_at = datetime.now(timezone.utc) def enrich_movie(db: Session, it: PlexItem, meta: dict) -> None: """The same for a movie. The listing sync stored only its top ~3 cast; the live metadata carries all of them.""" cast = plex_people.cast_from_meta(meta) if cast: plex_people.enrich_row_people(db, it, cast) plex_detail.apply_rich_detail(it, meta) it.people_enriched_at = datetime.now(timezone.utc) def set_collection_editable(col: PlexCollection, editable: bool) -> None: col.editable = editable def keep_created_collection( db: Session, lib: PlexLibrary, rating_key: str, title: str, seed: str ) -> None: """A collection Plex made, whose follow-up read failed: its row is kept, editable. The full sync preserves `editable` but creates a missing row read-only, so the collection would drop out of every "add to" list; and the duplicate-name check reads these rows, so a retry of the same name would make a second one on Plex. Plex made it with exactly the seed in it, so the seed's row says so too — otherwise the title showed unticked in the collection it was just added to, and the collection empty, until that sync.""" db.add( PlexCollection( rating_key=rating_key, library_id=lib.id, title=title, editable=True, child_count=1 ) ) Model = PlexItem if lib.kind == "movie" else PlexShow if row := by_rating_key(db, Model, seed): row.collection_keys = list(dict.fromkeys([*(row.collection_keys or []), rating_key])) db.flush() def enrich_person_bg(person_id: int) -> None: """Fetch + persist one person's TMDB bio OFF the request path — a FastAPI background task that runs AFTER the response is sent, so the outbound TMDB call never sits on the serve latency (the module's "no live TMDB call at serve time" invariant). The request's session is already closed by then, so open a fresh one. Best-effort: a TMDB failure just leaves `tmdb_enriched_at` NULL to retry on the next open, and the bio lands from the row by then.""" db = SessionLocal() try: person = db.get(PlexPerson, person_id) if person is not None: plex_tmdb.enrich_person(db, person) except Exception: # noqa: BLE001 — background best-effort; never surfaces to a user db.rollback() log.warning("background person bio enrich failed for id=%s", person_id, exc_info=True) finally: db.close()