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}&region={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}&region={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
def get_genres(language = "en-US", debug = True):
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 list of str representing valid genres.

def get_now_playing( limit = 10, language = "en-US", region = "US", debug = True,):
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}&region={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 list of movies.

def get_upcoming( limit = 10, language = "en-US", region = "US", debug = True,):
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}&region={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 list of movies.

def get_recommendations( movie_id , limit = 20, language = "en-US", debug = True,):
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 list of movies (dictionaries)

def get_reviews( movie_id , limit = 20, language = "en-US", debug = True,):
 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 list of movies (dictionaries)

def lookup_movie(movie_id, debug = True, ):
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 dictionary that represents the movie in question

def generate_movie_table(movies, to_html = False):
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): 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.
Returns:
  • a str that has a table in it for tracks
def generate_watchlist( movie_ids = [], genres = [], length = 10, debug = True, simplify = True):
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 list of movies (dictionaries)