The person page now leads with a TMDB bio, lazily fetched the first time it's
opened (off the S2-linked `tmdb_person_id`) and then served from the row:
- Migration 0072: ADD-only nullable person columns on plex_people — biography,
birthday, deathday, place_of_birth, known_for_department, also_known_as,
known_for ([{tmdb_id, media_type, title}]), tmdb_enriched_at.
- tmdb.py: TmdbClient.fetch_person (/person/{id}+combined_credits), parse_person
(bio + a popularity-ranked, deduped known_for), apply_person, enrich_person
(lazy one-shot, key+id guarded, commits, 404 stamps the watermark).
- people.py person_detail: loads the full person row, lazily enriches (best-effort
— never fails the page), serves the bio fields + resolves known_for to library
rows by guid (in-library only, popularity order).
- FE PlexPersonView: bio/birth-facts/aka hero + a "Known for" Carousel rail above
the existing Movies/Shows filmography; PlexPersonDetail type + i18n (en+hu).
Tests: test_plex_person_bio.py (6: parse / lazy-enrich guards / serve) + extended
PlexPersonView.dom.test.tsx + the unknown-name contract in test_plex_person.py.
Browser-E2E'd on Jim Carrey (live TMDB enrich → bio 1937 chars, born 1962-01-17
Newmarket, Known-for resolved). Gate: vitest 337, backend lane 402.
41 lines
1.6 KiB
Python
41 lines
1.6 KiB
Python
"""plex person TMDB bio columns (E5 S6)
|
|
|
|
Additive TMDB-sourced person fields on plex_people — biography, birth/death dates, place of birth,
|
|
known-for department, also-known-as, and a `known_for` credit pool ([{tmdb_id, media_type, title}]
|
|
resolved to library rows by guid at serve time) — plus a `tmdb_enriched_at` watermark. Filled the
|
|
first time a person page is opened (from /person/{tmdb_person_id}), then SERVED FROM THE ROW. The
|
|
`tmdb_person_id` link itself already exists (0071). ADD-only, all nullable → no table rewrite, no
|
|
ACCESS EXCLUSIVE lock. `tmdb_enriched_at` starts NULL, so every person is enriched once on first view.
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
revision: str = "0072_plex_person_bio"
|
|
down_revision: Union[str, None] = "0071_plex_tmdb_fields"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
_COLS: tuple[tuple[str, sa.types.TypeEngine], ...] = (
|
|
("biography", sa.Text()),
|
|
("birthday", sa.String(length=10)),
|
|
("deathday", sa.String(length=10)),
|
|
("place_of_birth", sa.Text()),
|
|
("known_for_department", sa.String(length=64)),
|
|
("also_known_as", postgresql.JSONB()),
|
|
("known_for", postgresql.JSONB()),
|
|
("tmdb_enriched_at", sa.DateTime(timezone=True)),
|
|
)
|
|
|
|
|
|
def upgrade() -> None:
|
|
for name, type_ in _COLS:
|
|
op.add_column("plex_people", sa.Column(name, type_, nullable=True))
|
|
|
|
|
|
def downgrade() -> None:
|
|
for name, _ in reversed(_COLS):
|
|
op.drop_column("plex_people", name)
|