Unique IDs that automatically expire — like UUID but with a TTL
Project description
tempid
Unique IDs that automatically expire, store encrypted payloads, and strictly limit usages — built for Enterprise Python.
tempid gives you Stripe-like, highly secure temporary tokens (TEMP-V2.XXXX...) without the boilerplate.
tempid operates as a full Hybrid Token Engine. It supports embedded JSON payloads, Strict Use-Count Limits (e.g. "burn after reading"), and high-concurrency Async/Sync database backends.
⚡ Features at a Glance
- Stateless by Default: Tokens hold their own expiration time. No database required for basic time-based expiry.
- Encrypted Payloads: Embed JSON data directly inside the token (up to 512 bytes). Fully encrypted, users cannot read or tamper with it.
- Strict Use Limits: Set a
max_useslimit on tokens (e.g., a one-time-use OTP). - Enterprise DB Backends: Built-in connection pooling for Redis, PostgreSQL, MySQL, SQLite, and MongoDB.
- Hybrid Async Engine: First-class
async/awaitsupport for FastAPI, Sanic, and Starlette via dedicatedAsyncBackends. - Zero Dependencies (Core): The core engine uses only standard Python libraries.
📦 Installation
pip install tempid
If you plan to use database backends for strict usage limits (max_uses), install the appropriate driver:
pip install tempid[redis] # For RedisBackend
pip install tempid[mysql] # For MySQLBackend
pip install tempid[postgres] # For PostgreSQLBackend
pip install tempid[mongo] # For MongoBackend
pip install tempid[async-postgres] # For AsyncPostgreSQLBackend
pip install tempid[async-mysql] # For AsyncMySQLBackend
pip install tempid[async-mongo] # For AsyncMongoBackend
pip install tempid[all] # Install all drivers
🚀 Quick Start (Stateless Mode)
By default, tempid operates completely offline without needing a database. Expiration is cryptographically signed into the token itself.
from tempid import TempID
# 1. Create a token that expires in 15 minutes
tid = TempID.new("15m")
print(tid.value) # TEMP-V2.AIAGU-PSOHJ...
# 2. Check time remaining
print(tid.remaining()) # "14m 59s"
# 3. Verify securely (e.g., when a user submits it)
# verify() returns the TempID object if valid, or None if expired/tampered
verified = TempID.verify(tid.value)
if verified:
print("Token is valid!")
else:
print("Token is invalid or expired.")
🧳 Encrypted Payloads
You can embed JSON-serializable dictionaries directly into the token. The data is heavily compressed (zlib) and fully encrypted (AES-like CTR mode). Users cannot read or tamper with it.
# Create a token with a payload
tid = TempID.new("2h", payload={"user_id": 42, "role": "admin"})
# Later, verify and extract the data
verified_token = TempID.verify(tid.value)
if verified_token:
print(verified_token.payload["user_id"]) # 42
print(verified_token.payload["role"]) # "admin"
Note: Maximum payload size after compression is 512 bytes. If exceeded, TempIDPayloadTooLargeError is raised.
🛡️ Strict Use Limits (max_uses)
Need a token that can only be used exactly 3 times, or a password reset link that burns after 1 use? You can set max_uses.
To use this feature, you must configure a database backend at application startup so tempid can track the usage atomically across your servers.
1. Configure a Backend (Startup)
from tempid import configure
from tempid.backends import RedisBackend
# Run this ONCE when your app starts
configure(store=RedisBackend("redis://localhost:6379/0"))
2. Generate a Limited Token
# Expires in 1 hour, OR after 1 successful use
tid = TempID.new("1h", max_uses=1)
3. Consume the Token
# check_uses=True checks the database limit WITHOUT consuming a use
verified = TempID.verify(token_str, check_uses=True)
if verified:
# use() attempts to consume 1 use atomically in the database
if verified.use():
print("Success! Action performed.")
else:
print("Token limit reached (Already used!).")
4. Check Remaining Uses
info = verified.uses_info()
print(f"Used: {info['used']} / Total: {info['total']}")
⚡ Async Support (FastAPI / Starlette)
tempid is fully async-native. If you are building high-concurrency apps, use the Async backends and methods to prevent blocking your event loop.
import asyncio
from tempid import TempID, configure
from tempid.async_backends import AsyncPostgreSQLBackend
# 1. Configure the Async Backend
configure(store=AsyncPostgreSQLBackend("postgresql://root:pass@localhost/mydb"))
async def api_endpoint(token_string: str):
# 2. Verify (Checks DB asynchronously without blocking)
tid = await TempID.verify_async(token_string, check_uses=True)
if not tid:
return {"error": "Invalid or exhausted token"}
# 3. Consume a use
success = await tid.use_async()
if success:
return {"data": tid.payload}
else:
return {"error": "Token already used!"}
# Check info
info = await tid.uses_info_async()
print(info)
🏭 Supported Backends Reference
All backends guarantee perfect atomicity (no race conditions or double-spending) even under extreme loads.
Synchronous Backends (tempid.backends)
MemoryBackend(): Stores uses in a thread-safe dict. (Development only).SQLiteBackend(db_path): Uses WAL mode and locks. Great for single-server production.RedisBackend(uri): Uses atomic Lua scripts. Ideal for distributed caching.MongoBackend(uri, db): Usesfind_one_and_updateon unique indexes.MySQLBackend(host, port, user, password, db): Connection pooled, usesSELECT FOR UPDATE.PostgreSQLBackend(dsn): Connection pooled, usesON CONFLICT DO UPDATE.
Asynchronous Backends (tempid.async_backends)
AsyncMemoryBackend(): Thread-safe, asyncio-safe. (Development only).AsyncSQLiteBackend(db_path): Usesaiosqlite.AsyncRedisBackend(uri): Usesredis.asynciowith Lua scripts.AsyncMongoBackend(uri, db): Usesmotor.AsyncMySQLBackend(host, port, user, password, db): Usesaiomysqlpools.AsyncPostgreSQLBackend(dsn): Usesasyncpgpools (highest performance).
🔐 Security & Secrets
The Secret Key (Required)
tempid uses a 96-bit HMAC-SHA256 signature to prevent tampering and encryption. In production, you must set an environment variable to share the secret across your instances.
# Set this in your OS, Docker, or .env
export TEMPID_SECRET="your-super-secret-32-byte-key-here"
To generate a highly secure secret, run:
python -c "import secrets; print(secrets.token_hex(32))"
Encryption Engine
Payloads and timestamps are encrypted using a custom XOR-CTR stream cipher combined with HMAC-SHA256, ensuring no parts of the internal data can be deciphered without the TEMPID_SECRET.
📖 Complete API Reference
TempID Core Class
TempID.new(expires_in: str, payload: dict = None, max_uses: int = 0) -> TempID
Creates a new token.
expires_in: Duration string (e.g."30s","15m","2h","7d").payload: Optional dict. Must be JSON serializable. Max 512 bytes.max_uses: Strict use limit.0means unlimited.
TempID.from_string(value: str) -> TempID
Parses a token string but does not verify uses limit or signature. Used internally or for manual exception handling.
TempID.verify(value: str, check_uses: bool = False) -> TempID | None
The primary, safe way to verify a token. Returns the TempID object if valid, unexpired, and untampered. If check_uses=True, it verifies the backend limit without consuming a use. Returns None on any failure.
await TempID.verify_async(value: str, check_uses: bool = False) -> TempID | None
The async counterpart for verify().
tid.use() -> bool
Attempts to consume 1 use in the database. Returns True if successful, False if the limit is reached or the token is expired.
await tid.use_async() -> bool
The async counterpart for use().
tid.uses_info() -> dict
Returns {"total": max_uses, "used": count, "left": remaining}.
await tid.uses_info_async() -> dict
The async counterpart for uses_info().
tid.valid() -> bool
Returns True if the token's timestamp has not expired. (Does NOT check database limits).
tid.expired() -> bool
Opposite of valid().
tid.remaining() -> str
Returns a human-readable string of time left (e.g., "1h 4m 3s", "expired").
tid.on_expire(callback: Callable) -> TempID
Registers a function to be called exactly once when the token is first determined to be expired by valid().
⚠️ Exceptions Reference
Located in tempid.exceptions.
| Exception | Reason Thrown |
|---|---|
TempIDFormatError |
The token string is malformed or invalid base32. |
TempIDTamperedError |
The HMAC signature does not match (someone tried to forge or alter it). |
TempIDExpiredError |
The token's timestamp has passed. |
TempIDPayloadTooLargeError |
Passed payload exceeds 512 compressed bytes. |
TempIDRevokedError |
(Reserved for future manual revocation feature). |
Example of manual handling:
from tempid import TempID
from tempid.exceptions import TempIDFormatError, TempIDTamperedError
try:
tid = TempID.from_string(user_input)
if tid.valid():
print(tid.payload)
except TempIDTamperedError:
print("Someone tried to forge this token!")
except TempIDFormatError:
print("Malformed token.")
📝 Real World Examples
Example 1: Password Reset (Stateless + Payload)
# User clicks "Forgot Password"
tid = TempID.new("15m", payload={"email": user.email})
send_email(user.email, f"https://myapp.com/reset?t={tid.value}")
# When user clicks the link
verified = TempID.verify(request.args["t"])
if verified:
reset_password(verified.payload["email"], new_password)
Example 2: One-Time-Password (OTP / Burn-After-Reading)
configure(store=RedisBackend("redis://localhost"))
# Create 1-time use OTP
otp = TempID.new("5m", max_uses=1)
send_sms(user.phone, otp.value)
# Verify
verified = TempID.verify(user_input, check_uses=True)
if verified and verified.use():
login_user()
else:
print("Invalid or already used OTP!")
🤝 Backward Compatibility
tempid is fully backward compatible with legacy v1 tokens (XXXX-XXXX-XXXX). Passing a v1 token to TempID.from_string() will parse it seamlessly.
📄 License
MIT © 2026 Rahul Vachhani
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 tempid-2.0.0.tar.gz.
File metadata
- Download URL: tempid-2.0.0.tar.gz
- Upload date:
- Size: 30.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0bd81398d57f0dd6f54b3f9ee19a52e5835f0c53ebadef8f2be9f84c7a915b1
|
|
| MD5 |
27913d69db7a6dd0eeba334756de6cb5
|
|
| BLAKE2b-256 |
5b24f60f4844f22caeecb4c6658b7c54a11502fd33f37d896f21be079dabccb1
|
File details
Details for the file tempid-2.0.0-py3-none-any.whl.
File metadata
- Download URL: tempid-2.0.0-py3-none-any.whl
- Upload date:
- Size: 22.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9294458a12de8df7f7711d0fde1c02e94a4e730a2ad49ea3b8f40f2acd6e8ef5
|
|
| MD5 |
6768b5f94740f334b6ab501f15b230ce
|
|
| BLAKE2b-256 |
d12f0decb741af3cbfcf36f1b222b0ca9edf451497a32e91c84d05b7bdbbd8f3
|