Stop Overpaying for Slow API Calls! Cache Your Results, Save Time, and Cut Costs

API requests can be time-consuming and costly. Especially when developing or testing, making repeated requests to an API increases waiting time and can also use up unnecessary API calls. This is where requests_cachecomes in handy — caching the responses, makes your work faster and reduces the number of API hits.

What is requests_cache?

  • requests_cache is a Python library that extends the requests library, allowing you to cache responses locally.
  • Caching helps you avoid repeated network calls when the same request is made multiple times.

Why Use requests_cache?

  1. Speed: By storing the API responses locally, it bypasses the need to send requests over the network, thus reducing response time.
  2. Cost Efficiency: Many APIs have rate limits or charge for usage. Caching responses reduces the number of calls you make, saving money and avoiding rate limit issues.
  3. Testing and Development: When developing code that involves APIs, avoiding repeated requests to the same endpoint accelerates your debugging cycle.

What you should be careful about?

Apparently as with every convenience in life has it drawbacks, same applies here. Note below what you should ALWAYS have in mind when working with this module:

  • The most important — do not use it if you retrieve live data that change all the time, such as currency prices.
  • Define your expiration time wisely. Define your expiration time wisely. Set an expiration time that makes sense for when you need to retrieve live data from the API rather than relying on cached responses.
  • Always, understand your flow of the code, and when it makes sense to get live data or cache.

How does it work

First, you should install it along with the usual module of requests

pip install requests requests-cache

Let’s take a simple example and check the basic functionality of the module. We will call the free Coindesk API for the current price 100 times using the live API, do the same using the cached results and compare.

import time
import requests

session = requests.Session()
start_time = time.time()
loops_elapsed_time = []
loop = 100
for i in range(loop):
    start_time_loop = time.time()
    session.get('https://api.coindesk.com/v1/bpi/currentprice.json')
    end_time_loop = time.time()
    elapsed_loop = round((end_time_loop - start_time_loop) * 1000,0)
    loops_elapsed_time.append(elapsed_loop)
end_time = time.time()

elapsed_time = end_time - start_time
print(f"Called {loop} times the api in {elapsed_time:.2f} seconds with average time of {elapsed_time/loop:.2f} seconds")
print(f'The maximum time was {max(loops_elapsed_time)} milliseconds and minimum time was {min(loops_elapsed_time)} milliseconds')

OUTPUT
Called 100 times the api in 3.65 seconds with average time of 0.0365 seconds
The maximum time was 168.0 milliseconds and minimum time was 31.0 milliseconds




Now let’s try the same using cache and explain the code:

import time
import requests_cache

session = requests_cache.CachedSession('my_cache', backend='sqlite', expire_after=300)
start_time = time.time()
loops_elapsed_time = []
loop = 100
for i in range(loop):
    start_time_loop = time.time()
    session.get('https://api.coindesk.com/v1/bpi/currentprice.json')
    end_time_loop = time.time()
    elapsed_loop = round((end_time_loop - start_time_loop) * 1000,0)
    loops_elapsed_time.append(elapsed_loop)
end_time = time.time()

elapsed_time = end_time - start_time
print(f"Called {loop} times the api in {elapsed_time:.2f} seconds with average time of {elapsed_time/loop:.4f} seconds")
print(f'The maximum time was {max(loops_elapsed_time)} milliseconds and minimum time was {min(loops_elapsed_time)} milliseconds')

OUTPUT
Called 100 times the api in 0.15 seconds with average time of 0.0015 seconds
The maximum time was 3.0 milliseconds and minimum time was 1.0 milliseconds




The only change we had to make in the code was initiating the session from the requests_cache library instead of the requests library.

session = requests_cache.CachedSession('my_cache', backend='sqlite', expire_after=300)




  • my_cacheis the name of the SQLite database where the results will be cached
  • backendis which type of database you will store the cache. SQLite is the default option (so this is practically an optional parameter). The module also supports Redis, MongoDB, and more.
  • expire_afteris for how many seconds the specific call will return the cached results, after which it will call again the live API

The results are incredible! (Well… expected if you have the slightest experience with Python and API calls, but still cool to see in numbers!) While calling the API 100 times took 3.65 seconds, using the cache it took only 0.15 seconds, an approximate 95% decrease in time, while the API was called only one time instead of 100.

A few fun tricks!

Patching. You can keep your existing code using requests, but you can install requests_cache “on top” of it. Let’s see how:

import time
import requests
import requests_cache

# requests_cache.install_cache('my_cache', expire_after=300)
start_time = time.time()
loops_elapsed_time = []
loop = 100
for i in range(loop):
    start_time_loop = time.time()
    requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')
    end_time_loop = time.time()
    elapsed_loop = round((end_time_loop - start_time_loop) * 1000,0)
    loops_elapsed_time.append(elapsed_loop)
end_time = time.time()

elapsed_time = end_time - start_time
print(f"Called {loop} times the api in {elapsed_time:.2f} seconds with average time of {elapsed_time/loop:.4f} seconds")
print(f'The maximum time was {max(loops_elapsed_time)} milliseconds and minimum time was {min(loops_elapsed_time)} milliseconds')




As you see above instead of a session we have separate request.get()calls. The above code took 13 seconds to run. If you uncomment the requests_cache.install_cacheline, the time will be dropped to less than a second. One line of code, and it is 95% more efficient.

Sometimes we want to check whether the result came from the cache or if it was a live API call. This can be done with the following example:

import requests
import requests_cache
import time

requests_cache.install_cache('my_cache', backend='sqlite', expire_after=2)
requests_cache.clear()

for i in range(10):
    response = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')
    time.sleep(1)
    if response.from_cache:
        print("Got the result from cache")
    else:
        print("Got the results from live API Call")

OUTPUT
Got the results from live API Call
Got the result from cache
Got the results from live API Call
Got the result from cache
Got the results from live API Call
Got the result from cache
Got the results from live API Call
Got the result from cache
Got the results from live API Call
Got the result from cache




The response object has the property from_cache.If it’s true, the result was returned from the cache; if it’s false, a live API call was made. In the above code, we set the expiration to 2 seconds but we call the API using the sleep function every second. As expected every 2 calls, one result is from the API and one from the cache.

If you want to search easily through the cached results, you can store also your cached results in a meaningful way, instead of a database. Run this code:

import time
import requests_cache

session = requests_cache.CachedSession('http_cache', backend='filesystem')
session.get('https://api.coindesk.com/v1/bpi/currentprice.json')




By selecting the backend as a filesystem, you will see that a folder will be created http_cache and there will be various JSON files with the responses of your API calls, as well as information about expiration time, time elapsed, URL called, etc.

Another interesting configuration is if you feel it’s risky to keep the cache on your filesystem and prefer to store it temporarily and retrieve fresh results every time you run your script, you should use the use_temp parameter. Run this:

import time
import requests_cache

session = requests_cache.CachedSession('my_cache', expire_after=300, use_temp = True)
start_time = time.time()
loop = 100
for i in range(loop):
    session.get('https://api.coindesk.com/v1/bpi/currentprice.json')
end_time = time.time()

elapsed_time = end_time - start_time
print(f"Called {loop} times the api in {elapsed_time:.2f} seconds with average time of {elapsed_time/loop:.4f} seconds")




You simply need to add use_temp = True, and the results will not be stored. However, you will see from the time needed, that the time was super fast, and cached result was used

The requests_cache does not stop there. It has much more functionality and you can explore it at pypi.org and their documentation page. Honest KUDOS to the team creating and maintaining it!