2323import pandas as pd
2424import requests
2525
26+ try :
27+ from dotenv import load_dotenv
28+ except ImportError :
29+
30+ def load_dotenv (path : str = ".env" ) -> bool :
31+ """Minimal .env loader fallback for KEY=VALUE lines."""
32+ if not os .path .exists (path ):
33+ return False
34+ with open (path , encoding = "utf-8" ) as env_file :
35+ for raw_line in env_file :
36+ line = raw_line .strip ()
37+ if not line or line .startswith ("#" ) or "=" not in line :
38+ continue
39+ key , value = line .split ("=" , 1 )
40+ os .environ .setdefault (key .strip (), value .strip ().strip ('"' ).strip ("'" ))
41+ return True
42+
43+
2644DATA_DIR = os .path .join (os .path .dirname (__file__ ), "data" )
2745MOVIELENS_DIR = os .path .join (DATA_DIR , "ml-32m" )
2846
47+ load_dotenv ()
48+ load_dotenv (os .path .join (os .path .dirname (DATA_DIR ), ".env" ))
49+
2950# ---------------------------------------------------------------------------
3051# TMDB poster configuration
3152#
@@ -378,9 +399,11 @@ def _build_poster_cache(df: pd.DataFrame) -> pd.Series:
378399 @param df - The master DataFrame (must have title, year, content_type columns)
379400 @returns pd.Series of poster URLs indexed to match df
380401 """
381- # Load existing cache if present
402+ # Load existing cache if present. This should work even when TMDB_API_KEY is
403+ # not set, because production restarts should be able to use the cached URLs.
382404 if os .path .exists (POSTER_CACHE_PATH ):
383405 cache_df = pd .read_csv (POSTER_CACHE_PATH )
406+ cache_df ["poster_url" ] = cache_df ["poster_url" ].where (cache_df ["poster_url" ].notna (), None )
384407 cache = dict (zip (cache_df ["cache_key" ], cache_df ["poster_url" ]))
385408 print (f"Loaded { len (cache ):,} cached poster URLs from disk." )
386409 else :
@@ -389,12 +412,24 @@ def _build_poster_cache(df: pd.DataFrame) -> pd.Series:
389412 # Build a unique key per row: "title||year||content_type"
390413 def _cache_key (row ):
391414 title_key = str (row ["title" ]).lower ().strip ()
392- return f"{ title_key } ||{ row .get ('year' , '' )} ||{ row .get ('content_type' , '' )} "
415+ year = row .get ("year" , "" )
416+ if pd .notna (year ) and year != "" :
417+ year = str (int (float (year )))
418+ else :
419+ year = ""
420+ return f"{ title_key } ||{ year } ||{ row .get ('content_type' , '' )} "
393421
394422 keys = df .apply (_cache_key , axis = 1 )
395423 missing_mask = ~ keys .isin (cache )
396424 missing_count = missing_mask .sum ()
397425
426+ if missing_count > 0 and not TMDB_API_KEY :
427+ print (
428+ f"TMDB_API_KEY is not set; using { len (cache ):,} cached poster URLs "
429+ f"and leaving { missing_count :,} missing."
430+ )
431+ return keys .map (cache )
432+
398433 if missing_count > 0 :
399434 print (f"Fetching { missing_count :,} new poster URLs from TMDB (this may take a while)..." )
400435 for i , (idx , row ) in enumerate (df [missing_mask ].iterrows ()):
@@ -627,11 +662,8 @@ def _build_soup(row) -> str:
627662 "movielens_rating_count" ,
628663 "tfidf_soup" ,
629664 ]
630- # ----- Fetch TMDB poster URLs (cached after first run) -----
631- if TMDB_API_KEY :
632- df ["poster_url" ] = _build_poster_cache (df )
633- else :
634- df ["poster_url" ] = None
665+ # ----- Load/fetch TMDB poster URLs (cached after first run) -----
666+ df ["poster_url" ] = _build_poster_cache (df )
635667
636668 # Add poster_url to canonical column order
637669 ordered .append ("poster_url" )
0 commit comments