Enhanced thread-safe, time-versioned key-value store with snapshots, queries, async, and more.
Project description
ChronoMap
ChronoMap is a thread-safe, time-versioned key-value store for Python that maintains complete history of all changes. Perfect for configuration management, audit trails, time-series data, session stores, and any application requiring historical data tracking with concurrency, async, and analytics.
โจ Features
- โฑ๏ธ Time-Versioned Storage - Every value change is timestamped and preserved
- ๐ Thread-Safe - All operations are protected with locks for concurrent access
- ๐ Async Support โ Full AsyncChronoMap for asyncio applications
- ๐ธ Snapshots & Rollback - Create snapshots and rollback to any previous state
- โฐ TTL Support - Automatic key expiration with time-to-live
- ๐ Query & Analytics โ Filter, aggregate, count, and analyze your data
- ๐งน History Pruning โ Garbage-collect old versions to manage memory
- ๐ Statistics Tracking โ Monitor reads, writes, deletes, and snapshots
- ๐ Event Hooks โ Register callbacks on every change (on_change)
- ๐ Batch Operations - Efficient
put_many()anddelete_many()operations - ๐ Advanced Queries - Range queries, find latest keys, search by value
- ๐ค Merge & Diff - Merge multiple maps and track differences
- ๐ Context Manager โ Automatic rollback on exception with snapshot_context()
- ๐พ Persistence - Save/load from JSON or Pickle
- ๐ผ Pandas Export โ Export full history to DataFrame for analysis
- ๐ Pythonic API - Dictionary-like interface with magic methods
- ๐ Comprehensive Testing - 102 tests with >95% coverage
๐ฆ Installation
pip install chronomap
๐ก Optional: pip install pandas for to_dataframe() support
๐ Quick Start
from chronomap import ChronoMap
cm = ChronoMap()
cm['user'] = 'alice'
cm['status'] = 'active'
print(cm['user'])
# Query active users
active = cm.query(lambda k, v: v == 'active')
# Track changes
def log_change(key, old, new, ts):
print(f"{key}: {old} โ {new}")
cm.on_change(log_change)
# Prune old history
cm.prune_all_history(keep_last=100)
๐ Documentation
Basic Operations
cm.put('key', 'value')
value = cm.get('key')
cm.delete('key')
cm['key'] = 'value'
value = cm['key']
del cm['key']
if 'key' in cm:
print("Key exists")
value = cm.get('nonexistent', default='default_value')
try:
value = cm.get('nonexistent', strict=True)
except ChronoMapKeyError:
print("Key not found")
Time Travel with Datetime
# Use datetime strings or objects
cm.put('event', 'login', timestamp="2025-10-21T12:00:00")
cm.put('event', 'logout', timestamp=datetime(2025, 10, 21, 13, 0, 0))
# Query at specific time
value = cm.get('event', timestamp="2025-10-21T12:30:00")
TTL and Auto-Expiry
cm.put('session_token', 'abc123', ttl=3600)
print(cm.get('session_token'))
removed_count = cm.clean_expired_keys()
print(f"Removed {removed_count} expired keys")
Snapshots and Context Manager
cm['counter'] = 10
with cm.snapshot_context():
cm['counter'] = 100
cm['temp'] = 'data'
raise Exception("Rollback!") # Auto-rollback on exception
print(cm['counter']) # 10
print(cm.get('temp')) # None
Event Hooks
def audit_log(key, old, new, ts):
print(f"[AUDIT] {key}: {old} โ {new} at {ts}")
cm.on_change(audit_log)
cm['config'] = 'new_value'
Query & Aggregation
cm.put_many({'score1': 85, 'score2': 92, 'score3': 78})
# Filter
high_scores = cm.query(lambda k, v: isinstance(v, int) and v > 80)
# Aggregate
avg = cm.aggregate(lambda vals: sum(vals) / len(vals))
total = cm.aggregate(sum, keys=['score1', 'score2'])
# Count
active_count = cm.count(lambda k, v: v == 'active')
History Pruning
# Keep only last 10 versions
cm.prune_history('sensor', keep_last=10)
# Remove versions older than date
cm.prune_history('log', older_than="2025-01-01")
# Prune all keys
cm.prune_all_history(keep_last=100)
AsyncChronoMap
import asyncio
from chronomap import AsyncChronoMap
async def main():
cm = AsyncChronoMap()
await cm.put('key', 'value')
value = await cm.get('key')
snap = await cm.snapshot()
keys = await cm.keys()
latest = await cm.latest()
asyncio.run(main())
Pandas Export
# Requires: pip install pandas
df = cm.to_dataframe()
print(df.head())
# key value timestamp datetime version
# 0 temp 20 100.0 1970-01-01 00:01:40 0
Compression and Persistence
# Save with compression
cm.save_pickle('state.pkl', compress=True)
# Load automatically detects compression
cm2 = ChronoMap.load_pickle('state.pkl')
Statistics
stats = cm.get_stats()
print(f"Writes: {stats['writes']}, Reads: {stats['reads']}")
cm.reset_stats()
Batch Operations
users = {
'user:1': {'name': 'Alice', 'role': 'admin'},
'user:2': {'name': 'Bob', 'role': 'user'},
'user:3': {'name': 'Charlie', 'role': 'user'}
}
cm.put_many(users)
cm.put_many(users, ttl=3600)
cm.put_many(users, timestamp=1609459200)
deleted_count = cm.delete_many(['user:2', 'user:3'])
print(f"Deleted {deleted_count} keys")
Advanced Queries
cm.put('sensor', 10, timestamp=100)
cm.put('sensor', 15, timestamp=200)
cm.put('sensor', 20, timestamp=300)
readings = cm.get_range('sensor', start_ts=150, end_ts=250)
latest = cm.get_latest_keys(2)
for key, timestamp, value in latest:
print(f"{key}: {value} (updated at {timestamp})")
cm.put_many({'user1': 'active', 'user2': 'active', 'user3': 'inactive'})
active_users = cm.get_keys_by_value('active')
Merge and Diff
cm1 = ChronoMap()
cm2 = ChronoMap()
cm1.put('shared', 'v1', timestamp=100)
cm2.put('shared', 'v2', timestamp=200)
cm2.put('unique', 'data')
cm1.merge(cm2, strategy='timestamp')
changed_keys = cm1.diff(cm2)
Iteratons and Utilities
for key in cm:
print(key)
latest = cm.latest()
history = cm.history('key')
print(len(cm))
cm.clear()
๐งช Testing
# Run all tests
pytest tests/test_chronomap.py -v
# With coverage
pytest tests/test_chronomap.py --cov=chronomap --cov-report=html
# Run specific test class
pytest tests/test_chronomap.py::TestAsyncChronoMap -v
Test coverage includes:
- โ Basic operations (put, get, delete)
- โ Batch operations
- โ TTL and expiry
- โ Advanced queries
- โ Snapshots, context manager, rollback
- โ Merge and diff
- โ Magic methods & iteration
- โ Persistence (JSON, Pickle, compression)
- โ Thread safety & RWLock
- โ AsyncChronoMap
- โ Query, aggregation, pruning
- โ Event hooks & statistics
- โ Pandas export
- โ Edge cases & integration scenarios
Project Structure
chronomap/
โโโ chronomap/
โ โโโ __init__.py
โ โโโ chronomap.py # Core implementation
โ โโโ cli.py
โ โโโ __main__.py # CLI interface
โโโ tests/
โ โโโ test_chronomap.py # Test suite
โโโ examples/
โ โโโ config_manager.py
โ โโโ session_store.py
โ โโโ metrics_store.py
โโโ logo/
โ โโโ logo.png
โโโ README.md
โโโ setup.py
โโโ pyproject.toml
โโโ LICENSE
Contributing
- Fork the repository
- Write tests for your changes
- Ensure all tests pass (
pytest tests/ -v) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
๐ Requirements
- Python 3.8+
- No external dependencies for core functionality
- Optional: pandas for to_dataframe()
- Development dependencies: pytest, pytest-cov, pytest-asyncio
๐บ๏ธ Roadmap
- Async/await support โ (v2.1.0)
- Read-write locks โ (v2.1.0)
- Query & aggregation โ (v2.1.0)
- History pruning โ (v2.1.0)
- Event hooks โ (v2.1.0)
- Compression โ (v2.1.0)
- Pandas export โ (v2.1.0)
- SQLite backend for persistence
- Web UI for visualization
- Export to various formats (CSV, Parquet)
- Query language for complex searches
- Integration with popular frameworks (Django, Flask)
๐ Changelog
v2.1.0 (2025)
- Added AsyncChronoMap for asyncio support
- Implemented read-write locks for better concurrency
- Added query filters, aggregations, and counting
- Added event hooks (on_change callbacks)
- Added history pruning (prune_history, prune_all_history)
- Added snapshot context manager (snapshot_context)
- Added operation statistics tracking
- Added compression support (zlib + pickle)
- Added Pandas DataFrame export
- Enhanced datetime string support ("2025-10-21T12:00:00")
- Improved CLI demo and error handling
v2.0.0 (2025)
- Complete rewrite with enhanced features
- Added TTL/expiry support
- Added batch operations
- Added advanced queries (range, latest keys, search by value)
- Added merge and diff functionality
- Added comprehensive test suite (65 tests)
- Improved thread safety
- Enhanced documentation
v1.0.0
- Initial release
- Basic time-versioned storage
- Snapshot and rollback
- Persistence support
๐ก Tips and Best Practices
- Use TTL for temporary data - Session tokens, cache entries, temporary flags
- Use snapshot_context() - Risky operations โ Auto-rollback on failure
- Prune history regularly โ Prevent memory bloat with prune_all_history()
- Use async for I/O-bound apps โ Web servers, data pipelines
- Monitor Stats - Track usage patterns with get_stats()
- Enable compression โ For large persistent states
- Export to Pandas โ Analyze time-series data with familiar tools
- Take snapshots before risky operations - Database migrations, bulk updates
- Use batch operations - More efficient than individual operations
- Clean expired keys regularly - Call
clean_expired_keys()during maintenance - Leverage history for auditing - Track configuration changes, document versions
- Use descriptive key naming -
user:123:profileis better thanu123p - Persist regularly - Save state periodically using
save_json()orsave_pickle() - Monitor map size - Large histories may need archiving or cleanup
โ FAQ
Q: Is ChronoMap suitable for production use?
A: Yes! ChronoMap is thread-safe, well-tested, and used in production environments.
Q: How much memory does ChronoMap use?
A: Memory usage depends on the number of keys and history size. Each value change is stored, so keys with frequent updates will use more memory.
Q: Can I use ChronoMap as a database?
A: ChronoMap is an in-memory store. For persistence, use save_json() or save_pickle(). For large-scale data, consider a proper database.
Q: How do I limit history size?
A: Currently, you need to manually manage history. Consider periodically archiving old data or implementing custom cleanup logic.
Q: Is ChronoMap compatible with multiprocessing?
A: ChronoMap is thread-safe but not process-safe. For multiprocessing, use separate instances or implement inter-process communication.
Q: Can I use ChronoMap with Django/Flask?
A: Yes! ChronoMap works well as a cache layer or session store in web applications.
Q: How do I upgrade from v2.0.0?
A: Fully backward-compatible! Just pip install chronomap==2.1.0.
๐ License
This project is licensed under the MIT License
Made with ๐ by Devansh Singh
Project details
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 chronomap-2.1.0.tar.gz.
File metadata
- Download URL: chronomap-2.1.0.tar.gz
- Upload date:
- Size: 47.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
109d10543270bff403ea5e8746f571f27778b4fd9ada6ad32817d527572f4136
|
|
| MD5 |
db26dbee807c2e327d9b7708d9953645
|
|
| BLAKE2b-256 |
f670f5c9357c6b8e0dcadb194c22dafe341ea536b95d942bbc2ea510f0df1309
|
File details
Details for the file chronomap-2.1.0-py3-none-any.whl.
File metadata
- Download URL: chronomap-2.1.0-py3-none-any.whl
- Upload date:
- Size: 42.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9132d3b217ec65a1be39e09d1e07a19a1d5f510abd5cf6b0a0607cfcae0e19a6
|
|
| MD5 |
9c827fa9708faa49edc5edeed4c9e439
|
|
| BLAKE2b-256 |
b6c683c8076923079e4d93b5829eae10254c7a587bc01b7abe723c1e4983fdf5
|