17 KiB
Python Programming
Unit 5 Lab: Finalizing the Movie App
Overview
This is the lab we've been working toward. Here, you'll add an API call to actually get the Rotten Tomatoes rating, prompt a user for whether they want to search for a movie or view a rating, and read in an API key from a file.
We'll be using the OMDb API.
It's a long lab, but it's going to be worth it. Buckle up — here we go!
Deliverables
You're going to continue building this locally from the last lab. You'll write all of your code in the same movie_app.py file.
Run the file from the command line to check your work.
Additionally, you will have a file, omdb-api-key.txt, with your own OMDb API key.
Requirements
In addition to the previous print statements, your app prompts a user for a 1 or a 2, calls an API, and does one action accordingly:
-
Inputting
1when the program starts searches for a movie:> ./main.py Search (1) or Ratings (2)? 1 Enter a search term: back to the future Back to the Future Back to the Future Part II Back to the Future Part III Ivan Vasilievich: Back to the Future Back to the Future... The Ride Back to the Future Back to the Future: Making the Trilogy Back to the Future: The Game - Episode 1, It's About Time The Secrets of the Back to the Future Trilogy Back to the Future: The Game - Episode 5, Outatime -
Inputting
2when the program starts prints the Rotten Tomatoes rating:> ./main.py Search (1) or Ratings (2)? 2 Enter the movie title: Back to the Future The rating for "Back to the Future" is 96%.
Directions
Augment the code you wrote for the Unit 2 lab.
Part 1: User Input
Part 1a: Search or Rating Prompt
Let's start with the simplest part — having a user input 1 or 2.
-
Delete the
search_or_ratingsdeclaration at the top of your file — you won't need it. -
At the top of your
whileloop inmain, create thesearch_or_ratingsvariable. Set it equal to theinputof the prompt,"Would you like to search for a movie (1) or find the rating of a specific movie (2)?" -
Remember that
inputalways returns a string, so either typecast it tointor change yourifblock accordingly.
Part 1b: Movie Title Prompt
We want the user to search for a movie, not always print our hard-coded value.
-
In the
ifandelifblocks, prompt a user with"Enter the movie title: "and save the input into a new variable,movie_query.- Food for thought: Why do you think we put two separate
movie_queryprompts versus one above theifblock?
- Food for thought: Why do you think we put two separate
-
Let's now use this title. If the user inputs
2, the app callsprint_single_movie_rating("Moana"). Change this argument to be the user's input. Test it out! -
list_search_results()is eventually going to do just that — list the search results for the user's input! We don't have the search functionality yet, so right now let's just prep for it by passing in the user's input as well asdefault_movie_list.
Part 2: Prepping for the API
Part 2a: Adjusting Existing Code
- For this API to work, you'll need a few libraries. At the top of your code, add these lines:
import urllib
import json
import requests
-
Next, you'll need to sign up for an API key. It's free!
- Go to this URL.
- Follow the prompts and you'll get a key that looks something like this:
eraa00e882(that one's not real — don't use it!). Test out your API key by putting this URL in your browser:https://www.omdbapi.com/?apikey=[your key]&t=moana. - It returns quite a lot! But, pulling out a few key pieces: The very first thing is the
Title, which we'll need. Near the end, right after the poster URL, you can see theRatings— it's aSource : Valuedictionary, exactly like you already have.
-
Let's make the key available to your program. Save this key in a file called
omdb-api-key.txtin the same folder as yourmovie_app.py.- Now that it's in a file, let's create a function that reads it in. Create a function called
get_apikey(). - In it, open
omdb-api-key.txt,readthe contents, and save the result into a variable,key. - Then, use
.strip()to strip any whitespace characters andprintout the key. - Call
get_apikey()frommainso you can see it working (but once you've verified, delete the call).
- Now that it's in a file, let's create a function that reads it in. Create a function called
-
Let's prepare our
Movieclass for this. When we createMovieobjects, we'll be passing in this block of OMDb data asmovie_data. Let's make our variables a little more descriptive.- Where you have
self.movie_data = movie_data, change that toself.omdb_data = movie_data. Change the getters accordingly. - In
get_movie_rating(), change the default for thesourceparameter to"Rotten Tomatoes". - Remember that capitalization matters! We have a lowercase
titleandratingright now, but in the API the dictionary keys areTitleandRatings. Change yours in the two getter functions. - Your movie class is set! You can minimize it — we won't be changing it again in this lab.
- Where you have
Part 2b: Applying Programmatic Thinking
-
Before we add the API, let's apply some programmatic thinking. Where should we put the API call? What needs to call the API?
list_search_results()is going to need to call the API to find the search results. Follow along: Let's pseudocode out what we need to do and mark it withTODOso we don't forget.
# Because we're going to get the list by searching, this function should only take in the `movie_query`. def list_search_results(movie_query): """ Print list of movies. Later, print a list of title results from a movie search. """ # TODO: All below. # To call the API, we'll need the API key. # Call `get_apikey()` and save it as `apikey`. # Then, we'll need to make an API client to call the API. We'll also need to search for the actual movie. This might look something like: # omdb_results = OMDB_call(apikey, movie_query) # But, we aren't really sure until we actually call the API. # When we do make this, we should probably put it in a try/except block in case the API call fails. # Once we have our results, we can loop through them and print them. # We know from the example call on the website that each movie object is a dictionary with a "Title" key. We can use list comprehensions to make a list from this. If we save it in `movie_titles`, we don't need to change the `for` loop below, and we can delete the list parameter in the function declaration: # movie_titles = [each_movie["Title"] for each_movie in matching_movie_list] # Loop through list of titles and print them (indented with four spaces). for title in movie_titles: print(" " + title)-
In preparation for this, change the call to
list_search_results()to only have themovie_query. -
Also, change
get_apikey()toreturnthe key instead of printing it.get_apikey()is now done — you can minimize it!
-
Where else will we need to call the API?
return_single_movie_object()literally returns theMovieobject given a movie title, so probably there. Let's think about it:-
Once again, because we're getting the movie data from the API call, we only need the movie title passed in. Take off the
movie_ratingparameter; look through the rest of the code for any calls toreturn_single_movie_object()and make sure they're only passing in a title. -
Once you've done this, you can minimize the functions
print_all_ratings()andprint_single_movie_rating(). We're done with them! -
While we're at it, you can minimize
main. We're done with that, too! You're making great progress. -
Continuing, let's think about
return_single_movie_object(). How does it compare tolist_search_results? Let's pseudocode it out:
# Because we're looking up the movie, we don't need to pass in the rating. Let's change the other parameter from "movie_title" to "movie_query" so it's more clear that we haven't looked it up yet. def return_single_movie_object(movie_query): """ Take in the movie title and rating, and return the movie object. """ # TODO: All below. # To call the API, we'll need the API key. # Call `get_apikey()` and save it as `apikey`. # Then, we'll need to make an API client to call the API. We'll also need to search for the actual movie. This might look something like: # omdb_results = OMDB_call(apikey, movie_query) # But, we aren't really sure until we actually call the API. # When we do make this, we should probably put it in a try/except block in case the API call fails. # Once we have our movie from the API result, we can make a `Movie` object out of it and return that. Something like this, but we're not really sure yet: # return Movie({"title": omdb_result[Title], "rating": omdb_result[Ratings]}) -
We've done everything we can in preparation. We know where we need the API (return_single_movie_object() and list_search_results()), and we have a general idea in pseudocode of what we're going to do with the results, so let's actually call it.
Part 3: Calling the API
Part 3a: Creating the API Class
Before we do anything, put this code at the top of your file, right underneath the import calls:
class OMDBError(Exception):
"""
OMDBError represents an error returned by the OMDb API.
"""
pass
Now, for the API. Let's put the API calls in a class. This way, we can keep all the API-related methods in one place, and it's easy for any other function to call.
What methods might our class have?
- We'll certainly need an
__init__.- In it, we can set the API key as an instance variable.
- To call an API, we need the URL we're calling. We'll have a
build_url()method.- The parameters will be anything we want to put in the URL. We'll use
**kwargs, as we won't know what they are in advance.
- The parameters will be anything we want to put in the URL. We'll use
- Lastly, we'll want to actually call the API — we'll have a
call_api()method.- This will get called by outside functions and therefore will call
build_url(). We'll need to take in the**kwargsas a parameter here to be able to pass them on.
- This will get called by outside functions and therefore will call
Let's get to it!
-
Create a class,
OMDB(object). Put it near the top of your file, right under theMovieclass, so all of your methods are available in the rest of the file. -
Take in
apikeyand setself.apikey = apikey. (Do you remember where?) -
In the class, create a method,
build_url(), that takes in an arbitrary number of keyword arguments (**kwargs), builds the URL, and returns the URL to call the API.- Use the code below, but make sure you understand it:
def build_url(self, **kwargs): """ build_url returns a properly formatted URL to the OMDb API including the API key. """ # Add API key to dictionary of parameters to send to OMDb. kwargs["apikey"] = self.apikey # Start building URL. url = "http://www.omdbapi.com/?" # urlencode the API parameters dictionary and append it to the URL. url += urlencode(kwargs) # Return the complete URL. return url ``` -
Next, let's make a
call_api()method, which, as the name implies, actually calls the API. It takes in**kwargsas well — these will be the search parameters to send to the API, such as the name of the movie we're searching for.- The first thing it does it call
build_url(**kwargs)and saves that into aurlvariable. - Next, it should open the URL (using
urlopen(url)) and save that into aresponsevariable. - Then,
read()that response and save it intoresponse_data. Now we have some JSON with all of the movie information. - Let's use
json.loads()to decode theresponse_dataand save that into a variable calledresponse_data_decoded. - Before we continue, let's error-check! Add this code under your
response_data_decodeddeclaration:
```python # Check for an error and throw an exception if needed. if "Error" in response_data_decoded: raise OMDBError(response_data_decoded["Error"]) ```- Then, return
response_data_decoded, so that whatever called the API now has access to the movie information.
- The first thing it does it call
Part 3b: Using the API Class
At this point, what do you think we should do?
- We have a way to call the API, so we could go use this in
return_single_movie_object()orlist_search_results(). - However, what if, in the future, we want to get a list of search results or a single movie's information and not anything else that's in those functions?
- Let's make more class methods to handle this.
-
Let's create a method in our OMDb class that calls gets a single movie's info that
return_single_movie_object()can call.- Create a method called
get_movie()that takes in amovie_queryparameter — this is whatreturn_single_movie_object(), or any other function in the future, will pass to it. - In it, set a variable
movie_datato the return ofself.call_apiwith (t=movie_query) as an argument. The "t" here stands for "Title." - Take the
movie_datathat's returned and create aMovieobject out of it. Return thatMovieobject.
- Create a method called
-
Let's check out our
return_single_movie_object()and see if we can do something about this pseudocode. -
First, let's call the API key and save it in
apikey. That's straightforward — we had that already! -
Next, we have this pseudocode existing in our function:
# Then, we'll need to make an API client to call the API. We'll also need to search for the actual movie. This might look something like: # omdb_results = OMDB_call(apikey, movie_query) # But, we aren't really sure until we actually call the API. # When we do make this, we should probably put it in a try/except block in case the API call fails. -
However, most of this is done in our class. Let's make an OMDb object from the class, passing in our API key, and save it as
omdb. -
Next, let's get that
Movieobject. Callomdb.get_movie(movie_to_look_up)and save it asmy_movie_object. -
Return
my_movie_object. -
We're almost good to try it out! Let's add an error check in case it fails. Wrap your
omdbcall in atry/exceptblock, like this:
# Get `Movie` object. If OMDb error occurs, print the error message and exit.
try:
my_movie_object = omdb.get_movie(movie_to_look_up)
return my_movie_object
except OMDBError as err:
print("OMDB Error: {0}".format(err))
return
Break: Let's Test!
At this point, we can try it out!
-
We'll need to comment out any calls to
list_search_results(), because we haven't done anything in that function yet (the only one is inmain). -
Now, try running it! Before you even input anything, the
printstatements at the beginning should run:The movie Back to the Future has a rating of 96% The movie Blade has a rating of 54% The movie Spirited Away has a rating of 97%Then, inputting
2and entering "Moana" should return a ratings result of 97% (remember, capitals matter!).
If your program hangs (doesn't respond) for more than a minute, hit ctrl-c to stop it. If you get stuck, try troubleshooting it, then ask for help.
Moving on: Let's Add Search!
This is the last bit! Great job. How cool is your app so far?
- In the class, add a
search()method.- Like
get_movie(), take in amovie_query. - Call the API, but have the argument be
s=movie_query; the "s" here stands for "search." Save the result in a variable calledmovie_dictionaries. - Then, return the value of the
Searchkey of that dictionary.
- Like
That's it for the OMDb class! Let's do the final piece, which is list_search_results(). This function is going to look very similar to return_single_movie_object(). The few differences are:
- In
list_search_results(), instead of calling.get_movie, call.searchand save the result in a variable,matching_movie_list. - Below the API call and above the
forloop, uncomment the list comprehension from the pseudocode.
Test this out by uncommenting the call to list_search_results() in main, then searching for Blade.
If you get stuck, ask for help!
That's the end! Amazing job.