refactor(plex): split browse.py god-module into per-concern routers (E3 S1)

routes/plex/browse.py was a 1693-line god-module (libraries + facets +
collections + playlists + show/item detail + people). Split it into focused
sibling routers under the same /api/plex prefix — pure code motion, no SQL /
route-path / response-shape changes:

  browse.py  1693 -> 469  (list_libraries, unified_library, facets)
  collections.py  (new 253)  browse + admin collection editor; owns _collection_source
  playlists.py    (new 405)  the user's orderable playlists
  detail.py       (new 438)  show/item detail + enrichment; _lib_key lives here
  people.py       (new 139)  cast/crew chips + person filmography

Cross-module helpers _norm_lang + _content_libs move to _shared.py (their one
home); detail imports _collection_source from collections (one-way, no cycle).
__init__.py includes the four new routers. Three DB-lane tests follow the moved
symbols (import path for _content_libs; monkeypatch targets for PlexClient /
_show_enrichment now on detail).

Behaviour-equivalent: ruff clean, 348 DB-lane pass (unchanged), parity net green.
This commit is contained in:
2026-08-18 21:06:04 +02:00
parent 9bf7da2cbe
commit 52b6535dc6
10 changed files with 1283 additions and 1194 deletions
+6 -6
View File
@@ -13,7 +13,7 @@ from types import SimpleNamespace
from fastapi.testclient import TestClient
import app.routes.plex.browse as browse
import app.routes.plex.detail as detail_routes
import app.state as state
from app.auth import current_user
from app.db import get_db
@@ -216,7 +216,7 @@ def test_item_detail_movie_serves_from_row_despite_live_outage(db, monkeypatch):
user = _user(db, "outage@example.com")
db.commit()
monkeypatch.setattr(browse, "PlexClient", lambda db: _StubClient(raise_=True))
monkeypatch.setattr(detail_routes, "PlexClient", lambda db: _StubClient(raise_=True))
resp = _get(db, user, monkeypatch, "/api/plex/item/m1")
assert resp.status_code == 200, resp.text
body = resp.json()
@@ -240,7 +240,7 @@ def test_item_detail_movie_lazy_fills_when_unenriched(db, monkeypatch):
user = _user(db, "lazy@example.com")
db.commit()
monkeypatch.setattr(browse, "PlexClient", lambda db: _StubClient(RICH_META))
monkeypatch.setattr(detail_routes, "PlexClient", lambda db: _StubClient(RICH_META))
resp = _get(db, user, monkeypatch, "/api/plex/item/m1")
assert resp.status_code == 200, resp.text
assert resp.json()["imdb_id"] == "tt0068646"
@@ -267,7 +267,7 @@ def test_item_detail_episode_serves_rich_from_live(db, monkeypatch):
user = _user(db, "ep@example.com")
db.commit()
monkeypatch.setattr(browse, "PlexClient", lambda db: _StubClient(RICH_META))
monkeypatch.setattr(detail_routes, "PlexClient", lambda db: _StubClient(RICH_META))
resp = _get(db, user, monkeypatch, "/api/plex/item/e1")
assert resp.status_code == 200, resp.text
body = resp.json()
@@ -298,7 +298,7 @@ def test_show_detail_serves_from_row(db, monkeypatch):
def _boom(db_, rk):
raise RuntimeError("plex unavailable")
monkeypatch.setattr(browse, "_show_enrichment", _boom)
monkeypatch.setattr(detail_routes, "_show_enrichment", _boom)
resp = _get(db, user, monkeypatch, "/api/plex/show/s1")
assert resp.status_code == 200, resp.text
body = resp.json()["show"]
@@ -322,7 +322,7 @@ def test_show_detail_lazy_fills_when_unenriched(db, monkeypatch):
db.commit()
# _show_enrichment returns (live meta, no related) → the fill branch runs.
monkeypatch.setattr(browse, "_show_enrichment", lambda db_, rk: (RICH_META, []))
monkeypatch.setattr(detail_routes, "_show_enrichment", lambda db_, rk: (RICH_META, []))
resp = _get(db, user, monkeypatch, "/api/plex/show/s1")
assert resp.status_code == 200, resp.text
assert resp.json()["show"]["imdb_id"] == "tt0068646"
+1 -1
View File
@@ -11,7 +11,7 @@ import app.plex.sync as syncmod
from app import sysconfig
from app.models import PlexItem, PlexLibrary
from app.plex.sync import _apply_item, _canonical_guid, _sync_overlay
from app.routes.plex.browse import _content_libs
from app.routes.plex._shared import _content_libs
class _StubPlex:
+1 -1
View File
@@ -19,7 +19,7 @@ from app.models import (
PlexState,
User,
)
from app.routes.plex.browse import _content_libs
from app.routes.plex._shared import _content_libs
NAME = "Robert De Niro"