simple_keystore
A Python library for encrypted key storage in SQLite with optional Redis-based rate limiting. Designed for development and on-prem use. For production, consider secrets managers or other built-in secure provider methods.
Features
- Encrypted storage -- Keys are encrypted with Fernet symmetric encryption and stored in SQLite with metadata (name, source, login, batch, expiration dates).
- Rate limiting -- Optional sliding-window rate limiting per key using Redis and atomic Lua scripts.
- Key orchestration -- High-level API that combines storage and throttling with exponential backoff to find and claim an available key.
- CLI tool -- Interactive menu-driven interface for managing keys.
Installation
pip install simple_keystore
Redis is required only if you use the rate limiting features (SKSRateThrottler, get_available_key_for_use).
Configuration
Set the encryption key via environment variable (preferred) or .netrc:
export SIMPLE_KEYSTORE_KEY="your-fernet-key"
Or add an entry to ~/.netrc:
machine SIMPLE_KEYSTORE_KEY
login unused
password your-fernet-key
Generate a valid Fernet key with:
from cryptography.fernet import Fernet
print(Fernet.generate_key().decode())
Usage
Key Storage
from simple_keystore import SimpleKeyStore
ks = SimpleKeyStore("my_keys.db")
# Add keys
ks.add_key("openai", "sk-abc123", active=True)
ks.add_key("openai", "sk-def456", active=True, source="vendor-a", batch="2024-q1")
# Retrieve a key by name (raises if more than one match)
key = ks.get_key_by_name("openai")
# Query matching records
records = ks.get_matching_key_records(name="openai", active=True)
for r in records:
print(r["id"], r["key"], r["usable"])
# Deactivate / reactivate
ks.mark_key_inactive("sk-abc123")
ks.mark_key_active("sk-abc123")
# Smart key selection (soonest-expiring, smallest set)
next_key = ks.get_next_usable_key(name="openai")
Rate Limiting
from datetime import timedelta
from simple_keystore import SKSRateThrottler
# Allow 10 uses per 60-second window
throttler = SKSRateThrottler(
api_key_id=1,
number_of_uses_allowed=10,
amount_of_time=timedelta(seconds=60),
redis_host="localhost",
redis_port=6379,
)
# Check remaining uses
remaining, claimed = throttler.remaining_uses(claim_slot=False)
# Claim a slot
remaining, claimed = throttler.remaining_uses(claim_slot=True)
# Target a different key without mutating instance state
remaining, claimed = throttler.remaining_uses(claim_slot=True, api_key_id=42)
# Block until a slot opens (with timeout)
remaining = throttler.wait_until_available(timeout=300, verbose=True)
You can also inject a redis_client directly (e.g., for testing with fakeredis):
throttler = SKSRateThrottler(
api_key_id=1,
number_of_uses_allowed=10,
amount_of_time=timedelta(seconds=60),
redis_client=my_redis_instance,
)
When an external redis_client is provided, the throttler will not close it on destruction.
Combined: Get an Available Key
from simple_keystore import SimpleKeyStore, get_available_key_for_use
ks = SimpleKeyStore("my_keys.db")
key_record, remaining = get_available_key_for_use(
key_name="openai",
keystore=ks,
key_number_of_uses_allowed=10,
key_use_window_in_seconds=60,
redis_host="localhost",
redis_port=6379,
how_long_to_try_in_seconds=3600, # retry for up to 1 hour
max_wait_cap_in_seconds=180.0, # cap backoff at 3 minutes
)
print(key_record["key"], remaining)
This iterates through matching keys, attempts to claim a rate-limit slot for each, and retries with exponential backoff (1.5x multiplier) until one is available or the timeout is reached.
CLI
manage_simple_keys my_keys.db
Provides an interactive menu for adding, removing, activating/deactivating, and listing keys.
API Reference
SimpleKeyStore(name="simple_keystore.db")
| Method | Description |
|---|---|
add_key(name, unencrypted_key, ...) |
Add a key with optional metadata (active, expiration_in_sse, batch, source, login). Returns the new record id. |
get_key_by_name(name) |
Get the decrypted key value by name. Raises if not exactly one match. |
get_key_record(unencrypted_key) |
Get the full record dict for a key value. |
get_key_record_by_id(id) |
Get the full record dict by id. |
get_matching_key_records(...) |
Query records by name, active, batch, source, login, with optional sort_order. |
get_next_usable_key(...) |
Smart selection: soonest-expiring within 12h, then smallest set for load balancing. |
update_key(id_to_update, ...) |
Update metadata fields on a key record. |
mark_key_active(unencrypted_key) |
Mark a key as active. |
mark_key_inactive(unencrypted_key) |
Mark a key as inactive. |
delete_key_record(unencrypted_key) |
Delete a key record by value. |
delete_matching_key_records(...) |
Delete records matching the given filters. |
records_for_usability_report(...) |
Get sorted records with optional printed table. |
usability_counts_report(...) |
Get per-set counts (total, active, expired, usable). |
SKSRateThrottler(api_key_id, number_of_uses_allowed, amount_of_time, ...)
| Method | Description |
|---|---|
remaining_uses(claim_slot=False, api_key_id=None) |
Check remaining uses and optionally claim a slot. Returns (remaining, slot_claimed). Pass api_key_id to target a different rate bucket. |
wait_until_available(timeout=7200, verbose=False, api_key_id=None, sleep_func=None, clock_func=None) |
Block until a slot is claimed. Raises TimeoutError on timeout. Accepts injectable sleep_func/clock_func for testing. |
get_available_key_for_use(key_name, keystore, ...)
Finds and claims an available key matching key_name with rate limiting. Returns (key_record, remaining_uses). Retries with exponential backoff.
get_key_with_most_uses_remaining(key_name, keystore, throttler=None, verbose=False)
Returns the id of the key with the most remaining rate-limit uses, or None. When throttler is provided, it is reused with per-call api_key_id; when None, a new throttler is created per key with hardcoded defaults.
License
MIT
Metadata
Release files for simple_keystore 0.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| simple_keystore-0.11.0.tar.gz | 27.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| simple_keystore-0.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.5 kB
Release files / simple_keystore-0.11.0.tar.gz
| Download URL | simple_keystore-0.11.0.tar.gz |
|---|---|
| Size | 27.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a8fb24f62e6463058f0c36b58819b7a7172166d94f23621140ac2763a6addf59
|
|
BLAKE2b-256 checksum How to use checksums |
86b278f43b67608edb3b2bed011e2e23472c3abe7b36264f5a534d38792a42c2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
pdm/2.26.6 CPython/3.10.19 Linux/5.15.167.4-microsoft-standard-WSL2
|
Release files / simple_keystore-0.11.0-py3-none-any.whl
| Download URL | simple_keystore-0.11.0-py3-none-any.whl |
|---|---|
| Size | 19.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
869d65ee6ca1914effa0ea8f15410bb4a96feaff0cce12f445c797c9417e81b7
|
|
BLAKE2b-256 checksum How to use checksums |
53b21dda8f8acc0e9ad7cea2d6cee7349889eca116987544d7f3838abdb71232
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
pdm/2.26.6 CPython/3.10.19 Linux/5.15.167.4-microsoft-standard-WSL2
|
Security for simple_keystore 0.11.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.