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.
!