APICache
Data researchers and engineers often work with APIs that are metered, restricted, or prone to rate limits. These constraints can slow down development, especially in the early exploration and prototyping phase - when developers want to experiment, iterate, and understand how the API behaves.
Before building stable pipelines, teams need to:
But that’s hard when the API keeps saying:
“429 Too Many Requests”
or worse
“402 Payment Required”
At Bay Information Systems, we built a solution to remove this bottleneck: a lightweight, transparent client-side memory proxy.
APICache
?APICache
is a tiny proxy layer that sits between your code and any structured API.
It’s ideal for:
from cache import APICache
import requests
import os
API_KEY = os.environ["COMPANIES_HOUSE_API_KEY"]
BASE_URL = "https://api.company-information.service.gov.uk"
def fetch_from_ch_api(url, params):
full_url = url.format(**{**params, "BASE_URL": BASE_URL})
response = requests.get(full_url, auth=(API_KEY, ""))
if response.status_code != 200:
raise ValueError(f"Failed [{response.status_code}]")
return response.json()
cache = APICache(request_fn=fetch_from_ch_api, ttl=3600)
hit, data = cache.request("{BASE_URL}/company/{company_number}", {"company_number": "12345678"})
print("Cache hit?", hit)
print("Company:", data["company_name"])
import feedparser
from cache import APICache
def fetch_google_news(url, params):
query = params["q"]
full_url = f"https://news.google.com/rss/search?q={query}"
return feedparser.parse(full_url)
cache = APICache(request_fn=fetch_google_news, ttl=86400)
hit, parsed = cache.request("https://news.google.com/rss/search?q={q}", {"q": "next high tide"})
for entry in parsed["entries"]:
print(entry["title"])
import requests
from cache import APICache
def fetch_github(url, params):
full_url = url.format(**params)
resp = requests.get(full_url, headers={"Accept": "application/vnd.github+json"})
return resp.json()
cache = APICache(request_fn=fetch_github)
hit, user = cache.request("https://api.github.com/users/{username}", {"username": "torvalds"})
print("Name:", user["name"])
You can cache expensive calls to ChatGPT or DALL-E:
import openai
from openai.util import convert_to_openai_object
from cache import APICache
class OpenAICache:
def __init__(self):
self.cache = APICache(request_fn=self._fetch_from_openai)
def _fetch_from_openai(self, key: str, params: dict):
if key == "openai:chat":
response = openai.ChatCompletion.create(**params)
elif key == "openai:image":
response = openai.Image.create(**params)
else:
raise ValueError("Unsupported endpoint")
return response.to_dict()
def chat(self, **params):
hit, raw = self.cache.request("openai:chat", params)
return convert_to_openai_object(raw), hit
Even mathematical functions can be “proxied” for demo purposes:
def slow_fibonacci(_, params):
n = params["n"]
if n < 2:
return n
return slow_fibonacci(_, {"n": n - 1}) + slow_fibonacci(_, {"n": n - 2})
cache = APICache(request_fn=slow_fibonacci)
for i in range(10):
hit, result = cache.request("fib", {"n": i})
print(f"fib({i}) = {result} (from cache: {hit})")
In truth, you will always be better off using functools.lru_cache
for this situation, but sometimes it is nice to capture intermediate results in dynamic programming.
APICache
is simple by design - built to support exploration, prototyping, and fast feedback loops.
If your team hits rate-limit errors, re-runs expensive API calls, or spendds time on fragile mocks, this lightweight layer may be exactly what you need.
Give it a try: github.com/your-org/apicache