Stop Writing the Same Data-Fetching Code Over and Over in Python
One library handles caching, retries, and deduplication — so you can stop reinventing the wheel every project.
One install. Zero dependencies. Works with any async Python app.
If you’ve ever built something in Python that talks to an API — weather data, user profiles, stock prices, anything — you’ve written the same boring code a dozen times: fetch data, store it somewhere, check if it’s still fresh, fetch again if it’s not. PyStackQuery does all of that automatically, so you write one line instead of twenty.
🧠 What Even Is This? — The 30-Second Explanation
Think of it like a smart middleman between your Python app and any data source (API, database, whatever).
Without it, every time your app needs data, it goes straight to the source — even if it already grabbed the exact same data 2 seconds ago. That’s slow, wasteful, and annoying to code around.
With PyStackQuery, the flow works like this:
- First request → fetches from the real source, stores a copy
- Second request (same data) → instantly returns the stored copy. Zero wait.
- Background → quietly checks if the stored copy is outdated, refreshes it behind the scenes
That’s called Stale-While-Revalidate (SWR) — show what you have immediately, update silently in the background. The user never waits. The data stays fresh. Everybody wins.
The “why should I care” version: Your app feels faster because it shows cached data instantly, then updates without the user noticing. No loading spinners. No repeated API calls. No wasted bandwidth.
🔥 Why This Exists — The Problem It Solves
Every Python dev who works with APIs ends up writing something like this:
cache = {}
pending = {}
lock = asyncio.Lock()
async def get_user(user_id):
key = f"user_{user_id}"
async with lock:
if key in cache:
return cache[key]
if key in pending:
return await pending[key]
task = asyncio.create_task(fetch_user(user_id))
pending[key] = task
try:
result = await task
cache[key] = result
return result
finally:
del pending[key]
That’s 15 lines of boilerplate — manual cache dictionary, manual lock, manual deduplication, manual cleanup. And you rewrite some version of this for every project.
With PyStackQuery, the same thing becomes:
user = await client.fetch_query(
QueryOptions(("user", user_id), lambda: fetch_user(user_id))
)
Two lines. Caching, deduplication, retries, background refresh — all handled automatically.
That’s the entire pitch. Less code, fewer bugs, same result.
⚙️ What It Actually Does — Feature Breakdown
| Feature | What It Means (Plain English) |
|---|---|
| SWR Caching | Shows stored data instantly, refreshes in the background. Your app feels fast even when the network is slow |
| Request Deduplication | If 10 parts of your app ask for the same data at the same time, only ONE actual request goes out. The rest share the result |
| Automatic Retries | If a request fails (network blip, server error), it retries with increasing wait times instead of just crashing |
| Dual-Tier Cache (L1 + L2) | L1 = fast memory cache (lives in RAM, dies when app restarts). L2 = plug in Redis or SQLite so your cache survives restarts and works across multiple app instances |
| Sync-Safe Observer API | The subscription/notification system works synchronously — safe for desktop apps (Tkinter, etc.) without blocking the UI loop |
| Query Keys | Every piece of data gets a unique label (like ("user", "123")). The library uses these labels to organize, cache, and deduplicate everything |
| Cache Invalidation | Tell the library “this data is outdated, go get a fresh copy” — and it handles the rest |
| Zero Dependencies | The core library needs nothing else installed. No dependency chain, no version conflicts, no bloat |
🛠️ How to Get Started — 3 Minutes, No Experience Needed
Step 1 — Install it:
pip install pystackquery
Requires Python 3.11+ (uses modern Python generics for clean type hints).
Step 2 — Basic usage:
import asyncio
from pystackquery import QueryClient, QueryOptions
client = QueryClient()
async def fetch_user(user_id: int) -> dict:
# Your fetch logic — could be any API call
async with aiohttp.ClientSession() as session:
async with session.get(f"https://api.example.com/users/{user_id}") as resp:
return await resp.json()
async def main():
# First call → hits the API, caches result
user = await client.fetch_query(
QueryOptions(
query_key=("user", "123"),
query_fn=lambda: fetch_user(123)
)
)
# Second call → returns instantly from cache
user_again = await client.fetch_query(
QueryOptions(
query_key=("user", "123"),
query_fn=lambda: fetch_user(123)
)
)
asyncio.run(main())
That’s it. First call fetches from the API. Second call returns from cache — no network request, no delay.
Step 3 — Explore further:
The GitHub repo includes a docs/ folder with deeper documentation, an examples/ folder with ready-to-run code, and a full test suite.
🤔 Who Is This For? — Use Cases
| If You’re Building… | How This Helps |
|---|---|
| FastAPI backend | Cache expensive database queries or third-party API calls. Deduplication prevents redundant requests under load |
| CLI tools that talk to APIs | Stop hitting rate limits by caching responses. Retries handle flaky connections automatically |
| Desktop apps (Tkinter, PyQt) | Sync-safe observer API means your UI doesn’t freeze while data loads in the background |
| Data pipelines | L2 cache (Redis/SQLite) means processed data survives restarts. No re-fetching everything from scratch |
| Any async Python project | If you’re tired of writing if key in cache logic and asyncio.Lock() boilerplate — this replaces all of it |
📊 Quick Context — Where This Fits
For anyone coming from the JavaScript/React world: this is essentially TanStack Query (React Query) or SWR by Vercel — but for Python.
| Concept | JS Equivalent | PyStackQuery |
|---|---|---|
| Smart data fetching + caching | TanStack Query / SWR | |
| Stale-While-Revalidate | Built into SWR | |
| Request deduplication | Both libraries do this | |
| Background refresh | Both libraries do this | |
| L2 persistent cache | Custom adapters needed |
For non-JS devs: Think of it as a caching library that’s smart enough to know when data is stale and refreshes it without you telling it to. That’s the whole concept.
Heads up: This is a newer project — clean codebase, active development, but still early. If you’re using it for something production-critical, test thoroughly.
Quick Hits
| Want | Do |
|---|---|
pip install pystackquery |
|
| GitHub — PyStackQuery | |
| 3.11+ required | |
| Plug in Redis or SQLite as L2 | |
| ~3 minutes from install to working cache |
Less boilerplate. Smarter caching. Your async Python apps just got a serious upgrade.
!