style: apply ruff format across backend + scripts (line-length 100)
Mechanical, whole-tree `ruff format` pass — no behaviour change. Recorded in .git-blame-ignore-revs (next commit) so git blame skips it.
This commit is contained in:
@@ -3,6 +3,7 @@ DB-lane batch/serve integration (enrich stamps the watermark + links people; ser
|
||||
recommendations to library rows; the whole thing no-ops without an API key).
|
||||
|
||||
The DB tests use the `db` fixture (real Postgres) — skipped when none is reachable."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import httpx
|
||||
@@ -21,11 +22,28 @@ MOVIE_PAYLOAD = {
|
||||
"keywords": {"keywords": [{"id": 1, "name": "mafia"}, {"id": 2, "name": "crime family"}]},
|
||||
"credits": {
|
||||
"cast": [
|
||||
{"id": 3084, "name": "Marlon Brando", "character": "Vito", "profile_path": "/a.jpg", "order": 0},
|
||||
{"id": 1158, "name": "Al Pacino", "character": "Michael", "profile_path": "/b.jpg", "order": 1},
|
||||
{
|
||||
"id": 3084,
|
||||
"name": "Marlon Brando",
|
||||
"character": "Vito",
|
||||
"profile_path": "/a.jpg",
|
||||
"order": 0,
|
||||
},
|
||||
{
|
||||
"id": 1158,
|
||||
"name": "Al Pacino",
|
||||
"character": "Michael",
|
||||
"profile_path": "/b.jpg",
|
||||
"order": 1,
|
||||
},
|
||||
],
|
||||
"crew": [
|
||||
{"id": 1776, "name": "Francis Ford Coppola", "job": "Director", "profile_path": "/c.jpg"},
|
||||
{
|
||||
"id": 1776,
|
||||
"name": "Francis Ford Coppola",
|
||||
"job": "Director",
|
||||
"profile_path": "/c.jpg",
|
||||
},
|
||||
{"id": 1776, "name": "Francis Ford Coppola", "job": "Writer", "profile_path": "/c.jpg"},
|
||||
{"id": 9, "name": "Some Gaffer", "job": "Gaffer"}, # not a key job → dropped
|
||||
],
|
||||
@@ -34,7 +52,12 @@ MOVIE_PAYLOAD = {
|
||||
"production_countries": [{"name": "United States of America"}],
|
||||
"recommendations": {
|
||||
"results": [
|
||||
{"id": 240, "title": "The Godfather Part II", "poster_path": "/p.jpg", "media_type": "movie"},
|
||||
{
|
||||
"id": 240,
|
||||
"title": "The Godfather Part II",
|
||||
"poster_path": "/p.jpg",
|
||||
"media_type": "movie",
|
||||
},
|
||||
{"id": 999, "title": "Not In Library", "poster_path": "/q.jpg", "media_type": "movie"},
|
||||
]
|
||||
},
|
||||
@@ -79,6 +102,7 @@ class FakeTmdb:
|
||||
|
||||
# ---- pure parse/persist --------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_parse_movie_pulls_all_fields():
|
||||
d = tmdb.parse_tmdb(MOVIE_PAYLOAD, "movie")
|
||||
assert d["tmdb_id"] == 238
|
||||
@@ -88,8 +112,18 @@ def test_parse_movie_pulls_all_fields():
|
||||
assert d["revenue"] == 245066411
|
||||
assert d["keywords"] == ["mafia", "crime family"]
|
||||
assert d["crew"] == [
|
||||
{"name": "Francis Ford Coppola", "job": "Director", "tmdb_id": 1776, "profile_path": "/c.jpg"},
|
||||
{"name": "Francis Ford Coppola", "job": "Writer", "tmdb_id": 1776, "profile_path": "/c.jpg"},
|
||||
{
|
||||
"name": "Francis Ford Coppola",
|
||||
"job": "Director",
|
||||
"tmdb_id": 1776,
|
||||
"profile_path": "/c.jpg",
|
||||
},
|
||||
{
|
||||
"name": "Francis Ford Coppola",
|
||||
"job": "Writer",
|
||||
"tmdb_id": 1776,
|
||||
"profile_path": "/c.jpg",
|
||||
},
|
||||
] # the Gaffer is filtered; the two Coppola jobs survive (dedup is by name+job)
|
||||
assert d["studios"] == ["Paramount", "Alfran"]
|
||||
assert d["production_countries"] == ["United States of America"]
|
||||
@@ -110,21 +144,30 @@ def test_parse_tv_uses_aggregate_credits_and_no_money():
|
||||
# filtered; TV jobs are read from jobs[0]. Crew is display-priority ordered (Creator before EP).
|
||||
assert d["crew"] == [
|
||||
{"name": "Vince Gilligan", "job": "Creator", "tmdb_id": 66633, "profile_path": "/vg.jpg"},
|
||||
{"name": "Vince Gilligan", "job": "Executive Producer", "tmdb_id": 66633, "profile_path": None},
|
||||
{
|
||||
"name": "Vince Gilligan",
|
||||
"job": "Executive Producer",
|
||||
"tmdb_id": 66633,
|
||||
"profile_path": None,
|
||||
},
|
||||
]
|
||||
assert d["tmdb_rating"] == 9.0
|
||||
assert ("Bryan Cranston", 17419) in {(p["name"], p["tmdb_id"]) for p in d["people"]}
|
||||
|
||||
|
||||
def test_parse_zero_votes_and_money_are_unknown():
|
||||
d = tmdb.parse_tmdb({"id": 1, "vote_average": 0.0, "vote_count": 0, "budget": 0, "revenue": 0}, "movie")
|
||||
d = tmdb.parse_tmdb(
|
||||
{"id": 1, "vote_average": 0.0, "vote_count": 0, "budget": 0, "revenue": 0}, "movie"
|
||||
)
|
||||
assert d["tmdb_rating"] is None # 0.0 with no votes is meaningless
|
||||
assert d["tmdb_vote_count"] is None
|
||||
assert d["budget"] is None and d["revenue"] is None
|
||||
|
||||
|
||||
def test_apply_writes_row_fields_without_touching_plex_owned():
|
||||
it = PlexItem(rating_key="m1", kind="movie", title="The Godfather", imdb_rating=8.7, full_cast=[{"x": 1}])
|
||||
it = PlexItem(
|
||||
rating_key="m1", kind="movie", title="The Godfather", imdb_rating=8.7, full_cast=[{"x": 1}]
|
||||
)
|
||||
tmdb.apply_tmdb_detail(it, tmdb.parse_tmdb(MOVIE_PAYLOAD, "movie"))
|
||||
assert it.tmdb_id == 238
|
||||
assert it.budget == 6000000
|
||||
@@ -157,6 +200,7 @@ def test_guid_to_tmdb_id_tvdb_source_and_tv_media_type():
|
||||
|
||||
# ---- DB-lane: batch enrich + serve --------------------------------------------------------------
|
||||
|
||||
|
||||
def _lib(db, key="1", kind="movie"):
|
||||
lib = PlexLibrary(plex_key=key, title="L", kind=kind, enabled=True)
|
||||
db.add(lib)
|
||||
@@ -166,12 +210,18 @@ def _lib(db, key="1", kind="movie"):
|
||||
|
||||
def test_enrich_batch_persists_stamps_and_links_people(db):
|
||||
lib = _lib(db)
|
||||
mv = PlexItem(rating_key="m1", library_id=lib.id, kind="movie", title="The Godfather", guid="tmdb:238")
|
||||
unmatched = PlexItem(rating_key="m2", library_id=lib.id, kind="movie", title="Nope", guid="tmdb:777")
|
||||
mv = PlexItem(
|
||||
rating_key="m1", library_id=lib.id, kind="movie", title="The Godfather", guid="tmdb:238"
|
||||
)
|
||||
unmatched = PlexItem(
|
||||
rating_key="m2", library_id=lib.id, kind="movie", title="Nope", guid="tmdb:777"
|
||||
)
|
||||
db.add_all([mv, unmatched])
|
||||
db.commit()
|
||||
|
||||
fake = FakeTmdb(payloads={("movie", 238): MOVIE_PAYLOAD}) # 777 has no payload → definitive no-match
|
||||
fake = FakeTmdb(
|
||||
payloads={("movie", 238): MOVIE_PAYLOAD}
|
||||
) # 777 has no payload → definitive no-match
|
||||
n = tmdb.enrich_batch(db, fake, 50)
|
||||
assert n == 2 # both processed (one matched, one definitively unmatched)
|
||||
|
||||
@@ -185,17 +235,23 @@ def test_enrich_batch_persists_stamps_and_links_people(db):
|
||||
assert unmatched.tmdb_enriched_at is not None # stamped so it isn't re-fetched every run
|
||||
assert unmatched.tmdb_id is None # but nothing stored
|
||||
|
||||
brando = db.execute(
|
||||
PlexPerson.__table__.select().where(PlexPerson.name == "Marlon Brando")
|
||||
).mappings().first()
|
||||
brando = (
|
||||
db.execute(PlexPerson.__table__.select().where(PlexPerson.name == "Marlon Brando"))
|
||||
.mappings()
|
||||
.first()
|
||||
)
|
||||
assert brando is not None and brando["tmdb_person_id"] == 3084
|
||||
|
||||
|
||||
def test_serve_block_none_until_enriched_and_resolves_recs(db):
|
||||
lib = _lib(db)
|
||||
# A recommendation target that IS in our library (guid tmdb:240) → deep-linkable.
|
||||
sequel = PlexItem(rating_key="m240", library_id=lib.id, kind="movie", title="Part II", guid="tmdb:240")
|
||||
fresh = PlexItem(rating_key="m1", library_id=lib.id, kind="movie", title="The Godfather", guid="tmdb:238")
|
||||
sequel = PlexItem(
|
||||
rating_key="m240", library_id=lib.id, kind="movie", title="Part II", guid="tmdb:240"
|
||||
)
|
||||
fresh = PlexItem(
|
||||
rating_key="m1", library_id=lib.id, kind="movie", title="The Godfather", guid="tmdb:238"
|
||||
)
|
||||
db.add_all([sequel, fresh])
|
||||
db.commit()
|
||||
|
||||
@@ -272,7 +328,9 @@ def test_enrich_batch_transient_error_leaves_watermark_null(db):
|
||||
# A transient TmdbError mid-batch must leave the row's tmdb_enriched_at NULL so the next run retries
|
||||
# it (unlike a definitive no-match, which stamps the watermark). The row is NOT counted as processed.
|
||||
lib = _lib(db)
|
||||
mv = PlexItem(rating_key="m1", library_id=lib.id, kind="movie", title="Godfather", guid="tmdb:238")
|
||||
mv = PlexItem(
|
||||
rating_key="m1", library_id=lib.id, kind="movie", title="Godfather", guid="tmdb:238"
|
||||
)
|
||||
db.add(mv)
|
||||
db.commit()
|
||||
|
||||
@@ -285,7 +343,9 @@ def test_enrich_batch_transient_error_leaves_watermark_null(db):
|
||||
|
||||
def test_enrich_batch_skips_shows_without_guid(db):
|
||||
lib = _lib(db, key="2", kind="show")
|
||||
sh = PlexShow(rating_key="s1", library_id=lib.id, title="No Guid Show") # guid None → not selected
|
||||
sh = PlexShow(
|
||||
rating_key="s1", library_id=lib.id, title="No Guid Show"
|
||||
) # guid None → not selected
|
||||
db.add(sh)
|
||||
db.commit()
|
||||
assert tmdb.enrich_batch(db, FakeTmdb(), 50) == 0
|
||||
@@ -299,8 +359,12 @@ def test_resolve_library_guids_scopes_to_given_libraries(db):
|
||||
# and an explicit empty list skips that media side entirely (distinct from None = match all).
|
||||
lib_on = _lib(db, key="on", kind="movie")
|
||||
lib_off = _lib(db, key="off", kind="movie")
|
||||
m_on = PlexItem(rating_key="on1", library_id=lib_on.id, kind="movie", title="In Scope", guid="tmdb:100")
|
||||
m_off = PlexItem(rating_key="off1", library_id=lib_off.id, kind="movie", title="Out", guid="tmdb:200")
|
||||
m_on = PlexItem(
|
||||
rating_key="on1", library_id=lib_on.id, kind="movie", title="In Scope", guid="tmdb:100"
|
||||
)
|
||||
m_off = PlexItem(
|
||||
rating_key="off1", library_id=lib_off.id, kind="movie", title="Out", guid="tmdb:200"
|
||||
)
|
||||
db.add_all([m_on, m_off])
|
||||
db.commit()
|
||||
entries = [{"tmdb_id": 100, "media_type": "movie"}, {"tmdb_id": 200, "media_type": "movie"}]
|
||||
|
||||
Reference in New Issue
Block a user