Flask with External APIs
January 31, 2020
Communicating with APIs
There are two ways that we communicate with APIs:
- Client-side Requests We use AJAX to make calls to APIs from our frontend.
- Server-side Requests We make calls to APIs from our backend.
Client-side Requests (AJAX)
Say we have a client: a frontend that the user interacts with directly to accomplish some end.
This client communicates with our server, which in turn communicates with our database. There is clear line from frontend to backend, with data flowing in one or the other direction.
Within the same client, we can also have other threads of communication that reach outward to servers other than our own.
Say, for example, our application is a currency converter. Because we want the latest conversion rates for our users, we use an external API (dedicated to conversion rates) that gives us back those rates when we request them.
Even though our frontend connects to our server, which connects to our database, our frontend also communicates with another server: the currency converter API.
Why Client-side?
Why might we choose to send a call to the API directly from our frontend, rather than our backend?
We don't have to involve our server at all, thus meaning less data being passed from place to place.
If we made our currency converter API call from within our server, we'd first have to call our server, which would then call the API.
The API would return that information to our server, and then we'd have to return it to our frontend.
Whew! Double the steps than if we just call it directly from the client.
Server-side Requests
Alternatively, we can have all API calls come from within our server.
Our application is still the same, but no matter what the user does, those requests go to our server first.
From our server, we take those requests and either handle them ourselves or make additional calls to external APIs, receiving back the data and passing it back to the frontend.
Why Server-side?
Remember the Same Origin Policy, which can prevent us from calling APIs from within the browser?
Placing our APIs requests on the backend allows us to get around this. Since we are not making a call from the client, the Same Origin Policy is no longer a problem (it cares about browser requests).
Communicating with external APIs on the server also makes it easier for us to store and process data.
Say our application used the data from another API to create the user experience.
Lastly, many APIs require credentials to use. Most often, this is either done to prevent misuse with rate limiting (limiting the number of times a service can be called within a certain time period) or for billing purposes (every time the service is used, you're billed for it).
We never store credentials on the frontend, so having that information concealed on our server allows us to keep it safe.
API Calls from the Terminal
Let's interact with an API outside of the context of an application to see what type of response to expect.
From the terminal, we run:
$ curl -i
'https://itunes.apple.com/search?term=billy+bragg&limit=3'
{
"resultCount":5,
"results": [
{"wrapperType":"track", "kind":"song", "artistId":"163251",
...
Though we're used to using libraries like axios
to do this, this is also an API call (just made via the Terminal). We're reaching out to the iTunes API, requesting specific information (which can be seen in our query string) and receiving a response in the form of JSON.
API Calls from Python
Back in our Python application, we want to do the exact same thing as we just did via curl
. In this case, we're going to pull in the standard library for making HTTP requests in Python, aptly named requests
.
To install requests
, we do exactly as we've done for each of our other utilities:
(venv) $ pip3 install requests
GET Requests
To retrieve data from an API, we use the requests.get()
method.
The .get()
method takes in two arguments: a url
(the API endpoint) and params
(the specifics of what we'd like to retrieve):
import requests
resp = requests.get(
"https://itunes.apple.com/search",
params={"term": "billy bragg", "limit": 3}
)
POST Requests
To send information to an API, we use the requests.post()
method.
Though there are many optional arugments, for our purposes, the .post()
method will take in two arguments: a url
(the API endpoint) and data
(the information we'd like to post to the server).
GET + POST Responses
Regardless of whether we are making a GET or POST request, we'll be returned a Response
instance.
Each Response
instance will give us access to the following information:
- .text Text of response
- .status_code Numeric status code (200, 404, etc)
- .json() Converts the JSON response text to Python dictionary
API Keys/Secrets
As mentioned above, many APIs will require you to pass along credentials when you make requests to them. Only if your credentials are valid will you be able to receieve the data you're requesting.
These keys and secrets are analogous to a username and password: a way to identify unique users and associated them with individual requests.
What Purpose do API Keys Serve?
There are several reasons an API may require credentials:
- Confidential Data. The API may provide access to confidential data or sensitive methods. For example, only you should be able to send tweets from your Twitter account!
- Pay to Play. The API costs money to use. Because of this, they need to know who to charge and assigned credentials makes that possible.
- Rate Limiting. The creators of the API want to limit abuse or overuse. Google Maps is free, but they want to keep people from abusing it!
Getting API Keys
In most cases, to get an API key, we request it via the API's website.
This varies from API to API, so make sure to read the docs!
Using API Keys
Once we've been granted an API key, we can start to use it when communicating with the service.
Although we must pass our key along with the other information in our request, the format is dependent on the individual API we're interacting with.
It might expect the key as a URL parameter:
requests.get("http://some-api.com/search",
params={"key": "dhf489tuhdfhdskfsdfsd34tg",
"isbn": "4675436632"})
Or it might need complex encoding.
Read the API docs!
Protecting Keys
API keys, just like credentials like our username and password, are not meant to be public information.
You don't want someone taking action on your behalf or accessing private information!
Say we have an application that looks like the following:
from flask import Flask
API_SECRET_KEY = "jdfghfkgdg9345dkjfgdfg"
app = Flask(__name__)
...
There is our API_SECRET_KEY
, directly in our code, for anyone to see (and use!). And we certainly do not want that, especially if we're paying for the service every time we make a request to it.
How can we safely store and use our API keys?
Using .gitignore
What we'll do is create a dedicated file for our credentials that we'll have access to, but that won't be committed and shared with the world.
In a new file named secrets.py
:
API_SECRET_KEY = "jdfghfkgdg9345dkjfgdfg"
We'll add the filename to a file named .gitignore
. The paths that are included within this file will be ignored by Git, and therefore never find their way to anywhere except our local machine:
In our .gitignore
file:
secrets.py
Finally, within our application, we'll import the ignored file. This will allow us to use our API keys without having to share them with the world by committing them to our repository!
from flask import Flask
from secrets import API_SECRET_KEY
app = Flask(__name__)
...
secrets.py
that held the production environment API keys.
Using External APIs in Flask
Let's tie it all together with two final examples of Flask routes that communicate with an external API.
When a user visits our route for /book-info
, we reach out to an external API to retrieve specifics about the book they're interested in:
@app.route("/book-info")
def show_book_info():
"""Return page about book."""
isbn = request.args["isbn"]
resp = requests.get("http://some-book-api.com/search",
params={"isbn": isbn, "key": API_SECRET_KEY})
book_data = resp.json()
# using the APIs JSON data, render full HTML page
return render_template("book_info.html", book=book_data)
Once we retrieve that information, we feed it into our view template and serve it up to the user.
Alternatively, it may be the case that we don't want to return a view to the user, but rather also provide them with only data in the form of JSON.
In this case, when the user makes a call to our endpoint, we make the same request to the external API, but instead of returning a template view, we return JSON:
@app.route("/book-data")
def show_book_info():
"""Return info about book."""
isbn = request.args["isbn"]
resp = requests.get("http://some-book-api.com/search",
params={"isbn": isbn, "key": API_SECRET_KEY})
book_data = resp.json()
# using the APIs JSON data, return that to browser
return jsonify(book_data)