refactor(plex): the browse, detail, people and collection pages read through queries and write through the service

The HTTP files browse, detail, people, collections and _shared do no database work of their own
now. New plex/queries.py (the rows a page shows, a user's watch state through userscope),
plex/service.py (the info pages' enrichment, a collection's editable flag, the row kept after a
failed follow-up read, the person bio's background fetch; none commits), plex/library.py (the
unified browse feed, its search and facets) and plex/collections.py (a collection's source,
editability, card, certificate and member order). Every watch-state join in the moved code names
its owner through owned_by.

Pins 354 -> 178 in 4 files; basedpyright baseline 132 -> 118.
This commit is contained in:
2026-10-19 22:11:20 +02:00
parent 533571f572
commit 3b0e365e26
19 changed files with 1513 additions and 1124 deletions
+10 -10
View File
@@ -191,7 +191,7 @@ def test_person_detail_schedules_enrich_off_request_path(db, monkeypatch, overri
db.commit()
scheduled: list[int] = []
monkeypatch.setattr(people, "_enrich_person_bg", lambda pid: scheduled.append(pid))
monkeypatch.setattr(people.service, "enrich_person_bg", lambda pid: scheduled.append(pid))
monkeypatch.setattr(people.sysconfig, "get_str", lambda d, k: "key")
people._enrich_inflight.clear() # ensure the per-person cooldown guard doesn't suppress this open
@@ -204,7 +204,7 @@ def test_person_detail_schedules_enrich_off_request_path(db, monkeypatch, overri
def test_enrich_person_bg_closes_session_and_isolates_errors(monkeypatch):
# The background-task body must open a fresh session, fetch the person, and ALWAYS close it — rolling
# back (never propagating) if enrich_person raises. Pure-unit via a fake session (no DB needed).
from app.routes.plex import people
from app.plex import service
calls = {"rollback": 0, "close": 0, "enriched": 0}
@@ -222,13 +222,13 @@ def test_enrich_person_bg_closes_session_and_isolates_errors(monkeypatch):
calls["close"] += 1
# Success path: person found → enrich called, session closed, no rollback.
monkeypatch.setattr(people, "SessionLocal", lambda: _FakeSession(object()))
monkeypatch.setattr(service, "SessionLocal", lambda: _FakeSession(object()))
monkeypatch.setattr(
people.plex_tmdb,
service.plex_tmdb,
"enrich_person",
lambda db, p: calls.__setitem__("enriched", calls["enriched"] + 1),
)
people._enrich_person_bg(5)
service.enrich_person_bg(5)
assert calls == {"rollback": 0, "close": 1, "enriched": 1}
# Error path: enrich raises → rollback + close, no exception escapes.
@@ -237,14 +237,14 @@ def test_enrich_person_bg_closes_session_and_isolates_errors(monkeypatch):
def _boom(db, p):
raise RuntimeError("enrich blew up")
monkeypatch.setattr(people.plex_tmdb, "enrich_person", _boom)
people._enrich_person_bg(5) # must not raise
monkeypatch.setattr(service.plex_tmdb, "enrich_person", _boom)
service.enrich_person_bg(5) # must not raise
assert calls["rollback"] == 1 and calls["close"] == 1
# Missing person (deleted id): None-guard skips enrich, session still closed.
calls.update({"rollback": 0, "close": 0, "enriched": 0})
monkeypatch.setattr(people, "SessionLocal", lambda: _FakeSession(None))
people._enrich_person_bg(9)
monkeypatch.setattr(service, "SessionLocal", lambda: _FakeSession(None))
service.enrich_person_bg(9)
assert calls == {"rollback": 0, "close": 1, "enriched": 0}
@@ -281,7 +281,7 @@ def test_person_detail_no_enrich_when_already_enriched(db, monkeypatch, override
db.commit()
scheduled: list[int] = []
monkeypatch.setattr(people, "_enrich_person_bg", lambda pid: scheduled.append(pid))
monkeypatch.setattr(people.service, "enrich_person_bg", lambda pid: scheduled.append(pid))
resp = _get(db, user, monkeypatch, override_dep, "/api/plex/person/Done P")
assert resp.status_code == 200, resp.text