Atomic per-string counter with sliding TTL in Redis (async).
Project description
hashcounter
hashcounter is a tiny async Redis helper: per-string counter with a sliding TTL.
Input string → SHA-256 → key "{prefix}:{hash}" → atomic INCR + EXPIRE in one transaction.
Installation
pip install hashcounter
Requires redis>=5.0.0 and a running Redis server.
Quick start
import asyncio
from hashcounter import bump_string_counter
async def main() -> None:
count = await bump_string_counter(
"world_debug_test_v234",
redis_url="redis://localhost:6379/0",
key_prefix="llm7:seen",
ttl_seconds=600, # 10 minutes
)
print(count) # 1 on first call, then 2, 3, ...
if __name__ == "__main__":
asyncio.run(main())
FastAPI (shared Redis client)
from __future__ import annotations
import redis.asyncio as redis
from fastapi import FastAPI
from hashcounter import bump_string_counter
app = FastAPI()
r = redis.from_url("redis://localhost:6379/0", decode_responses=False)
@app.on_event("shutdown")
async def _shutdown() -> None:
await r.aclose()
@app.post("/hit")
async def hit(s: str) -> dict[str, int]:
count = await bump_string_counter(
s,
redis_url="redis://localhost:6379/0",
key_prefix="llm7:seen",
ttl_seconds=600,
client=r, # reuse connection pool
)
return {"count": count}
Behaviour
- Key:
f"{key_prefix}:{sha256(value)}". - Atomicity:
INCRandEXPIRErun in a singleMULTI/EXECtransaction. - Sliding TTL: TTL resets to
ttl_secondson every call. - First call: creates the key with value
1. - Return: current counter value (
int). - Validation: raises
ValueErrorifttl_seconds <= 0.
API
async def bump_string_counter(
value: str,
*,
redis_url: str,
key_prefix: str,
ttl_seconds: int = 600,
client: Optional[redis.Redis] = None,
) -> int:
"""Increment the counter and refresh TTL. Return the current value."""
Parameters:
value: arbitrary string to count.redis_url: Redis connection URL (ignored ifclientis provided).key_prefix: namespace prefix for keys.ttl_seconds: key time-to-live (seconds), refreshed on each call.client: existingredis.asyncio.Redisinstance (recommended in services).
Use cases
- Prompt/request deduplication and anti-spam windows.
- Per-key rate limiting by arbitrary features (prompt hash, user token, IP).
- Telemetry: “seen count in the last N minutes”.
Contributing
Issues and PRs are welcome: https://github.com/chigwell/hashcounter/issues.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hashcounter-2025.8.241445.tar.gz.
File metadata
- Download URL: hashcounter-2025.8.241445.tar.gz
- Upload date:
- Size: 4.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de6ffed20bfcf706b04830500ed3c39e47b5b82579dc4ad674ce84f4def18c38
|
|
| MD5 |
f75b21c057b9821372d488f60c4dbedf
|
|
| BLAKE2b-256 |
f0e7f89bf27dc8e8366038abd6def2ddad15487d89e1776d139f5dd68c8f65f1
|
File details
Details for the file hashcounter-2025.8.241445-py3-none-any.whl.
File metadata
- Download URL: hashcounter-2025.8.241445-py3-none-any.whl
- Upload date:
- Size: 4.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d54136dabd124eaab7eb15b22241fcbf0aa77169a62781cf9bde3db8cbdd654b
|
|
| MD5 |
83b9f8fda88c1e59149760b4c0899e07
|
|
| BLAKE2b-256 |
415ce03c23aa119d9f8a9b5246993d1932fbb4605bd3bdda7eb9025346cfbec3
|