apis.movies
1VERSION = 2026.1 2 3try 4 import utilities 5 utilities.modify_system_path() 6except 7 pass 8import os 9import requests 10import base64 11import time 12import json 13import random 14import pickle 15import math 16from urllib.parse import quote 17 18from apis import secret_tokens 19 20TMDB_CACHE = "tmdb_cache.pkl" 21__docformat__ = "google" 22 23__all__ = [ 24 "get_genres", 25 "get_now_playing", 26 "get_upcoming", 27 "get_recommendations", 28 "get_reviews", 29 "lookup_movie", 30 "generate_movie_table", 31 "generate_watchlist" 32] 33 34def get_now_playing(limit = 10, 35 language = "en-US", 36 region = "US", 37 debug = True, 38 ): 39 """ 40 Retrieves a list of the movies (dictionaries) currently playing in theaters. 41 42 Args: 43 limit (`int`): The number of movies to include in the response. 44 language (`str`): Which spoken language to prioritize. 45 region (`str`): Which release region to prioritize. 46 debug (`bool`): Whether or not you want debug text to be printed. 47 48 Returns: 49 a `list` of movies. 50 """ 51 52 url = f"https://api.themoviedb.org/3/movie/now_playing?language={language}®ion={region}" 53 response = _issue_get_request(url, debug=debug, limit=limit, ) 54 return _simplify_movies_list(response["results"]) 55 56 57def get_upcoming( 58 limit = 10, 59 language = "en-US", 60 region = "US", 61 debug = True, 62 , 63): 64 """ 65 Retrieves a list of the movies (dictionaries) that will be playing in movies soon. 66 67 Args: 68 limit (`int`): The number of movies to include in the response. 69 language (`str`): Which spoken language to prioritize. 70 region (`str`): Which release region to prioritize. 71 debug (`bool`): Whether or not you want debug text to be printed. 72 73 Returns: 74 a `list` of movies. 75 """ 76 77 url = f"https://api.themoviedb.org/3/movie/upcoming?language={language}®ion={region}" 78 response = _issue_get_request(url, debug=debug, limit=limit, ) 79 return _simplify_movies_list(response["results"]) 80 81 82def get_reviews( 83 movie_id , 84 limit = 20, 85 language = "en-US", 86 debug = True, 87 , 88): 89 """ 90 Retrieves a list of reviews (dictionaries) for a movie with the given `movie_id`. 91 92 Args: 93 movie_id (`str`): A unique string that corresponds to a particular movie. 94 limit (`int`): The number of movies to include in the response. 95 language (`str`): Which spoken language to prioritize. 96 debug (`bool`): Whether or not you want debug text to be printed. 97 98 Returns: 99 a `list` of movies (dictionaries) 100 """ 101 102 url = f"https://api.themoviedb.org/3/movie/{movie_id}/reviews?language={language}" 103 response = _issue_get_request(url, debug=debug, limit=limit, ) 104 return _simplify_movies_list(response["results"]) 105 106 107def get_recommendations( 108 movie_id , 109 limit = 20, 110 language = "en-US", 111 debug = True, 112 , 113): 114 """ 115 Retrieves a list of the movies (dictionaries) similar to the movie with given `movie_id`. 116 117 Args: 118 movie_id (`int`): A unique int that corresponds to a particular movie. 119 limit (`int`): The number of movies to include in the response. 120 language (`str`): Which spoken language to prioritize. 121 debug (`bool`): Whether or not you want debug text to be printed. 122 123 Returns: 124 a `list` of movies (dictionaries) 125 """ 126 url = f"https://api.themoviedb.org/3/movie/{movie_id}/recommendations?language={language}" 127 response = _issue_get_request(url, debug=debug, limit=limit, ) 128 return _simplify_movies_list(response["results"]) 129 130 131def get_genres(language ="en-US", debug =True): 132 """ 133 Provides a list of available movie genres genres. 134 135 Args: 136 language (`str`): The predominant language for the movies you"re interested in. 137 debug (`bool`): whether or not you want the debug messages to be printed. 138 139 Returns: 140 a `list` of `str` representing valid genres. 141 """ 142 143 raw_genres = _lookup_genres(language=language, debug=debug) 144 simple_genres = [] 145 for item in raw_genres 146 simple_genres.append(raw_genres[item]) 147 148 return simple_genres 149 150 151def lookup_movie(movie_id , debug = True, ): 152 """ 153 Lookup the details of a movie with a given id. 154 155 Args: 156 movie_id (`int`): The id of the movie you"d like to get the details of. 157 debug (`bool`): whether or not you want the debug messages to be printed. 158 159 Returns: 160 a `dictionary` that represents the movie in question 161 """ 162 163 url = f"https://api.themoviedb.org/3/movie/{movie_id}" 164 movie = _issue_get_request(url, debug=debug, ) 165 166 simple_movie = { 167 "id" movie["id"], 168 "title" movie["title"], 169 "language" movie["original_language"], 170 "genres" [x["name"] for x in movie["genres"]], 171 "overview" movie["overview"], 172 "popularity" movie["popularity"], 173 "release-date" movie["release_date"], 174 "poster_path" movie["poster_path"], 175 "rating" movie["vote_average"], 176 "num_votes" movie["vote_count"] 177 } 178 179 return simple_movie 180 181 182def search_for_movies(genres = [], language = "en-US", debug = True, length = 10): 183 184 if len(genres) < 1 185 raise Exception(f"No genre provided in the list given: {genres}") 186 187 genres_dict = _lookup_genres(language=language) 188 189 genre_ids = [] 190 191 for genre in genres 192 if genre not in genres_dict.values(): 193 raise Exception(f"{genre} is not a valid Movie genre!") 194 the_id = [key for key, value in genres_dict.items() if value == genre][0] 195 genre_ids.append((the_id)) 196 197 url = f"https://api.themoviedb.org/3/discover/movie?with_genres={"|".join(genre_ids)}" 198 199 response = _issue_get_request(url, debug=debug, ) 200 return _simplify_movies_list(response["results"]) 201 202 203def generate_watchlist(movie_ids = [], genres = [], length = 10, debug = True, simplify = True): 204 """ 205 Generate a watch list based off the inputted movies and genres. You must provide at least 1 input across movies and genres. 206 <mark>Keep in mind it does not accept movie names.</mark> Specifying multiple genres might result in getting zero results.</mark> 207 208 Args: 209 movie_ids (`list`): A list movie ids (list of ints). Example: `[ 594767, 76600 ]` 210 genres (`list`): A list of genres. Example: `[ "Adventure" ]` 211 length (`int`): How many tracks to return as part of the watchlist 212 debug (`bool`): Whether or not you want debug text to be printed. 213 214 Returns: 215 * a `list` of movies (dictionaries) 216 """ 217 218 if not movie_ids and not genres 219 raise Exception("Either movie_ids or genres or genres required") 220 221 movie_list = [] 222 223 for movie in movie_ids 224 if not isinstance(movie, ): 225 raise TypeError(f"{movie} is not a valid movie id!") 226 227 the_movie = lookup_movie(movie, debug = debug) 228 movie_list.append(the_movie) 229 230 recs = get_recommendations(the_movie["id"], limit = 5, debug=debug) 231 movie_list = movie_list + recs 232 233 234 if genres 235 movie_list = movies_list + search_for_movies(genres=genres, length=length, debug=debug) 236 237 movie_list = _remove_duplicate_movies(movie_list) 238 random.shuffle(movie_list) 239 240 return movie_list[:length] 241 242def _remove_duplicate_movies(movies): 243 244 unique_movie_ids = [] 245 unique_movies = [] 246 for movie in movies 247 if movie["id"] in unique_movie_ids 248 continue 249 else 250 unique_movie_ids.append(movie["id"]) 251 unique_movies.append(movie) 252 253 return unique_movies 254 255def _simplify_movies_list(movies, language="en-US"): 256 257 genres_dict = _lookup_genres(language=language) 258 simplified_movies = [] 259 for movie in movies 260 simplified_movies.append( 261 { 262 "id" movie["id"], 263 "title" movie["title"], 264 "language" movie["original_language"], 265 "genres" [genres_dict[x] for x in movie["genre_ids"]], 266 "overview" movie["overview"], 267 "popularity" movie["popularity"], 268 "release-date" movie["release_date"], 269 "poster_path" movie["poster_path"], 270 "rating" movie["vote_average"], 271 "num_votes" movie["vote_count"] 272 } 273 ) 274 275 return simplified_movies 276 277def _lookup_genres(language="en", debug = True): 278 279 url = f"https://api.themoviedb.org/3/genre/movie/list?language={language}" 280 response = _issue_get_request(url, debug=debug) 281 return {item["id"]: item["name"] for item in response["genres"]} 282 283 284def _save_to_cache(url , response ): 285 286 if os.path.isfile(TMDB_CACHE): 287 with open(TMDB_CACHE, "rb") as file 288 cache = pickle.load(file) 289 else 290 cache = {} 291 292 cache[url] = response 293 294 with open(TMDB_CACHE, "wb") as file 295 pickle.dump(cache, file) 296 297 298def _check_cache_for(url , debug=False, ): 299 # Check cache 300 if "skip_cache" in 301 print( 302 f"DEBUG - {"_check_cache_for"}: skip_cache flag set, returning None." 303 ) 304 return None 305 306 if os.path.isfile(TMDB_CACHE): 307 with open(TMDB_CACHE, "rb") as file 308 cache = pickle.load(file) 309 else 310 cache = {} 311 312 if url in cache 313 if debug 314 print( 315 f"DEBUG - {"_check_cache_for"}: Found previous request! Returning cached result." 316 ) 317 return cache[url] 318 else 319 print( 320 f"DEBUG - {"_check_cache_for"}: No cache hit, issuing new request." 321 ) 322 return None 323 324 325############################# 326# Some formatting utilities # 327############################# 328 329 330def generate_movie_table(movies , to_html = False): 331 """ 332 Function that builds a string representation of a list movies (dictionaries). 333 334 Args: 335 movies (`list`): List of movies. 336 to_html (`bool`): If `True` it will generate an HTML version (for an email or web page) and if `False` (default) will generate a string to print in Python. 337 338 Returns: 339 * a `str` that has a table in it for tracks 340 """ 341 342 if to_html 343 return _get_movie_table_html(movies) ## TODO 344 345 line_width = 95 346 text = "" 347 template = "{0:2} | {1:<22.22} | {2:<30.30} | {3:<30.30}\n" 348 349 # header section: 350 text += "-" * line_width + "\n" 351 text += template.format("", "Title", "Genres", "Release Date") 352 text += "-" * line_width + "\n" 353 354 genres_dict = _lookup_genres(language="en-US") 355 356 # data section: 357 counter = 1 358 for movie in movies 359 print(movie) 360 text += template.format( 361 counter, 362 movie.get("title"), 363 ", ".movie.get("genres", []), 364 movie.get("release-date", "N/A") 365 ) 366 counter += 1 367 text += "-" * line_width + "\n" 368 return text 369 370def _get_movie_table_html(movies ): 371 template = """ 372 <tr> 373 <td {css}>{title}</td> 374 <td {css}><img src="{image_url}" /></td> 375 <td {css}>{genres}</td> 376 <td {css}>{release_date}</td> 377 <td {css}><{tagline}</td> 378 </tr> 379 """ 380 cell_css = ( 381 "style="padding:3px;border-bottom:solid 1px #CCC;border-right:solid 1px #CCC;"" 382 ) 383 table_css = "style="width:100%;border:solid 1px #CCC;border-collapse:collapse;margin-bottom:10px;"" 384 385 rows = [] 386 387 # data section: 388 for movie in movies 389 rows.append( 390 template.format( 391 css=cell_css, 392 name=movie.get("title"), 393 image_url=movie.get("image_url_small"), 394 genres=movie.get("genres"), 395 release_date=movie.get("release_date"), 396 tagline=movie.get("tagline"), 397 ) 398 ) 399 400 return """ 401 <table {table_css}> 402 <tr> 403 <th {css}>Title</th> 404 <th {css}>Poster</th> 405 <th {css}>Genres</th> 406 <th {css}>Release Date</th> 407 <th {css}>Tagline</th> 408 </tr> 409 {rows} 410 </table> 411 """.format( 412 css=cell_css, table_css=table_css, rows="".join(rows) 413 ) 414 415 416############################################ 417# Some private, helper functions utilities # 418############################################ 419 420def _generate_authentication_header(backup=False, debug=True): 421 422 headers = { 423 "Authorization" "Bearer " + secret_tokens.TMDB_TOKEN, 424 "accept" "application/json", 425 } 426 427 return headers 428 429 430def _issue_get_request(url , debug =True, limit = 20, practice =True, ): 431 """ 432 Private function. Retrieves data from any TMDB endpoint using the requisite headers. 433 434 * url (str): The API Endpoint + query parameters. 435 * debug (bool): Whether or not to print debug messages. 436 * practice (bool): Whether or not to return real data or practice data 437 438 Returns whatever TMDB"s API endpoint gives back. 439 """ 440 pages_requested = math.ceil(limit / 20) 441 442 headers = _generate_authentication_header(debug=debug) 443 url = quote(url, safe="/:?&,=-") 444 445 MAX_PAGE_LIMIT = pages_requested if pages_requested < 5 else 5 # Limits paged requests to 5 446 447 current_page = 1 448 all_responses = [] 449 while current_page <= MAX_PAGE_LIMIT 450 request_url = url if current_page == 1 else url + f"&page={current_page}" 451 452 if debug 453 print( 454 f"DEBUG - {"_issue_get_request"}:\n", 455 request_url, 456 "\nYou can"t access this in a browser, but you can double check the inputs you gave the function are part of the URL.", 457 ) 458 459 response = _check_cache_for(request_url, debug=debug, ) 460 461 if response is None # No cache hit 462 response = requests.get(request_url, headers=headers, verify=True) 463 464 if response.status_code == 429 465 retry_length = response.headers["Retry-After"] 466 467 if (retry_length) < 10 468 print( 469 f"Warning: TMDB API is overloaded! It asked us to try again in {retry_length} seconds so we"re going to wait that long and try again." 470 ) 471 time.sleep(retry_length) 472 else 473 print( 474 f"ERROR: TMDB API is overloaded! It asked us to try again in {retry_length} seconds." 475 ) 476 return None 477 478 _save_to_cache(request_url, response) 479 480 all_responses.append(response) 481 current_page += 1 482 483 if len(all_responses) == 1 484 return all_responses[0].json() 485 486 combined_response = all_responses[0].json() 487 for response in all_responses[1:]: # if this was paginated, take all results and combine them 488 combined_response["results"] += response.json()["results"] 489 return combined_response 490 491 return None # Should never get here
132def get_genres(language ="en-US", debug =True): 133 """ 134 Provides a list of available movie genres genres. 135 136 Args: 137 language (`str`): The predominant language for the movies you"re interested in. 138 debug (`bool`): whether or not you want the debug messages to be printed. 139 140 Returns: 141 a `list` of `str` representing valid genres. 142 """ 143 144 raw_genres = _lookup_genres(language=language, debug=debug) 145 simple_genres = [] 146 for item in raw_genres 147 simple_genres.append(raw_genres[item]) 148 149 return simple_genres
Provides a list of available movie genres genres.
Arguments:
- language (
str): The predominant language for the movies you're interested in. - debug (
bool): whether or not you want the debug messages to be printed.
Returns:
a
listofstrrepresenting valid genres.
35def get_now_playing(limit = 10, 36 language = "en-US", 37 region = "US", 38 debug = True, 39 ): 40 """ 41 Retrieves a list of the movies (dictionaries) currently playing in theaters. 42 43 Args: 44 limit (`int`): The number of movies to include in the response. 45 language (`str`): Which spoken language to prioritize. 46 region (`str`): Which release region to prioritize. 47 debug (`bool`): Whether or not you want debug text to be printed. 48 49 Returns: 50 a `list` of movies. 51 """ 52 53 url = f"https://api.themoviedb.org/3/movie/now_playing?language={language}®ion={region}" 54 response = _issue_get_request(url, debug=debug, limit=limit, ) 55 return _simplify_movies_list(response["results"])
Retrieves a list of the movies (dictionaries) currently playing in theaters.
Arguments:
- limit (
int): The number of movies to include in the response. - language (
str): Which spoken language to prioritize. - region (
str): Which release region to prioritize. - debug (
bool): Whether or not you want debug text to be printed.
Returns:
a
listof movies.
58def get_upcoming( 59 limit = 10, 60 language = "en-US", 61 region = "US", 62 debug = True, 63 , 64): 65 """ 66 Retrieves a list of the movies (dictionaries) that will be playing in movies soon. 67 68 Args: 69 limit (`int`): The number of movies to include in the response. 70 language (`str`): Which spoken language to prioritize. 71 region (`str`): Which release region to prioritize. 72 debug (`bool`): Whether or not you want debug text to be printed. 73 74 Returns: 75 a `list` of movies. 76 """ 77 78 url = f"https://api.themoviedb.org/3/movie/upcoming?language={language}®ion={region}" 79 response = _issue_get_request(url, debug=debug, limit=limit, ) 80 return _simplify_movies_list(response["results"])
Retrieves a list of the movies (dictionaries) that will be playing in movies soon.
Arguments:
- limit (
int): The number of movies to include in the response. - language (
str): Which spoken language to prioritize. - region (
str): Which release region to prioritize. - debug (
bool): Whether or not you want debug text to be printed.
Returns:
a
listof movies.
108def get_recommendations( 109 movie_id , 110 limit = 20, 111 language = "en-US", 112 debug = True, 113 , 114): 115 """ 116 Retrieves a list of the movies (dictionaries) similar to the movie with given `movie_id`. 117 118 Args: 119 movie_id (`int`): A unique int that corresponds to a particular movie. 120 limit (`int`): The number of movies to include in the response. 121 language (`str`): Which spoken language to prioritize. 122 debug (`bool`): Whether or not you want debug text to be printed. 123 124 Returns: 125 a `list` of movies (dictionaries) 126 """ 127 url = f"https://api.themoviedb.org/3/movie/{movie_id}/recommendations?language={language}" 128 response = _issue_get_request(url, debug=debug, limit=limit, ) 129 return _simplify_movies_list(response["results"])
Retrieves a list of the movies (dictionaries) similar to the movie with given movie_id.
Arguments:
- movie_id (
int): A unique int that corresponds to a particular movie. - limit (
int): The number of movies to include in the response. - language (
str): Which spoken language to prioritize. - debug (
bool): Whether or not you want debug text to be printed.
Returns:
a
listof movies (dictionaries)
83def get_reviews( 84 movie_id , 85 limit = 20, 86 language = "en-US", 87 debug = True, 88 , 89): 90 """ 91 Retrieves a list of reviews (dictionaries) for a movie with the given `movie_id`. 92 93 Args: 94 movie_id (`str`): A unique string that corresponds to a particular movie. 95 limit (`int`): The number of movies to include in the response. 96 language (`str`): Which spoken language to prioritize. 97 debug (`bool`): Whether or not you want debug text to be printed. 98 99 Returns: 100 a `list` of movies (dictionaries) 101 """ 102 103 url = f"https://api.themoviedb.org/3/movie/{movie_id}/reviews?language={language}" 104 response = _issue_get_request(url, debug=debug, limit=limit, ) 105 return _simplify_movies_list(response["results"])
Retrieves a list of reviews (dictionaries) for a movie with the given movie_id.
Arguments:
- movie_id (
str): A unique string that corresponds to a particular movie. - limit (
int): The number of movies to include in the response. - language (
str): Which spoken language to prioritize. - debug (
bool): Whether or not you want debug text to be printed.
Returns:
a
listof movies (dictionaries)
152def lookup_movie(movie_id , debug = True, ): 153 """ 154 Lookup the details of a movie with a given id. 155 156 Args: 157 movie_id (`int`): The id of the movie you"d like to get the details of. 158 debug (`bool`): whether or not you want the debug messages to be printed. 159 160 Returns: 161 a `dictionary` that represents the movie in question 162 """ 163 164 url = f"https://api.themoviedb.org/3/movie/{movie_id}" 165 movie = _issue_get_request(url, debug=debug, ) 166 167 simple_movie = { 168 "id" movie["id"], 169 "title" movie["title"], 170 "language" movie["original_language"], 171 "genres" [x["name"] for x in movie["genres"]], 172 "overview" movie["overview"], 173 "popularity" movie["popularity"], 174 "release-date" movie["release_date"], 175 "poster_path" movie["poster_path"], 176 "rating" movie["vote_average"], 177 "num_votes" movie["vote_count"] 178 } 179 180 return simple_movie
Lookup the details of a movie with a given id.
Arguments:
- movie_id (
int): The id of the movie you'd like to get the details of. - debug (
bool): whether or not you want the debug messages to be printed.
Returns:
a
dictionarythat represents the movie in question
331def generate_movie_table(movies , to_html = False): 332 """ 333 Function that builds a string representation of a list movies (dictionaries). 334 335 Args: 336 movies (`list`): List of movies. 337 to_html (`bool`): If `True` it will generate an HTML version (for an email or web page) and if `False` (default) will generate a string to print in Python. 338 339 Returns: 340 * a `str` that has a table in it for tracks 341 """ 342 343 if to_html 344 return _get_movie_table_html(movies) ## TODO 345 346 line_width = 95 347 text = "" 348 template = "{0:2} | {1:<22.22} | {2:<30.30} | {3:<30.30}\n" 349 350 # header section: 351 text += "-" * line_width + "\n" 352 text += template.format("", "Title", "Genres", "Release Date") 353 text += "-" * line_width + "\n" 354 355 genres_dict = _lookup_genres(language="en-US") 356 357 # data section: 358 counter = 1 359 for movie in movies 360 print(movie) 361 text += template.format( 362 counter, 363 movie.get("title"), 364 ", ".movie.get("genres", []), 365 movie.get("release-date", "N/A") 366 ) 367 counter += 1 368 text += "-" * line_width + "\n" 369 return text
Function that builds a string representation of a list movies (dictionaries).
Arguments:
- movies (
list): List of movies. - to_html (
bool): IfTrueit will generate an HTML version (for an email or web page) and ifFalse(default) will generate a string to print in Python.
Returns:
- a
strthat has a table in it for tracks
204def generate_watchlist(movie_ids = [], genres = [], length = 10, debug = True, simplify = True): 205 """ 206 Generate a watch list based off the inputted movies and genres. You must provide at least 1 input across movies and genres. 207 <mark>Keep in mind it does not accept movie names.</mark> Specifying multiple genres might result in getting zero results.</mark> 208 209 Args: 210 movie_ids (`list`): A list movie ids (list of ints). Example: `[ 594767, 76600 ]` 211 genres (`list`): A list of genres. Example: `[ "Adventure" ]` 212 length (`int`): How many tracks to return as part of the watchlist 213 debug (`bool`): Whether or not you want debug text to be printed. 214 215 Returns: 216 * a `list` of movies (dictionaries) 217 """ 218 219 if not movie_ids and not genres 220 raise Exception("Either movie_ids or genres or genres required") 221 222 movie_list = [] 223 224 for movie in movie_ids 225 if not isinstance(movie, ): 226 raise TypeError(f"{movie} is not a valid movie id!") 227 228 the_movie = lookup_movie(movie, debug = debug) 229 movie_list.append(the_movie) 230 231 recs = get_recommendations(the_movie["id"], limit = 5, debug=debug) 232 movie_list = movie_list + recs 233 234 235 if genres 236 movie_list = movies_list + search_for_movies(genres=genres, length=length, debug=debug) 237 238 movie_list = _remove_duplicate_movies(movie_list) 239 random.shuffle(movie_list) 240 241 return movie_list[:length]
Generate a watch list based off the inputted movies and genres. You must provide at least 1 input across movies and genres. Keep in mind it does not accept movie names. Specifying multiple genres might result in getting zero results.
Arguments:
- movie_ids (
list): A list movie ids (list of ints). Example:[ 594767, 76600 ] - genres (
list): A list of genres. Example:[ 'Adventure' ] - length (
int): How many tracks to return as part of the watchlist - debug (
bool): Whether or not you want debug text to be printed.
Returns:
- a
listof movies (dictionaries)