A Redis-based hierarchical entity model for application data management
Project description
hotcore
A Redis-backed entity model for managing hierarchical structured data with relationship support and attribute-based search capabilities.
Overview
Hotcore provides a clean, Redis-backed data model for applications that need hierarchical data storage with powerful querying capabilities. It organizes entities in parent-child relationships and indexes all attributes automatically for efficient searching.
Key Features
- Hierarchical Structure: Organize entities in a tree-like parent-child structure
- Attribute-Based Search: Find entities by exact values or patterns (wildcards)
- Geospatial Search: Automatic geospatial indexing and bounding box search for entities with coordinates
- Optimistic Locking: Safely modify entities in concurrent environments
- Automatic Indexing: All entity attributes are automatically indexed for fast retrieval
- Type Hinting: Comprehensive type annotations for improved IDE support
- Error Handling: Robust error handling with informative messages
- TLS-Ready Connections: Flexible SSL/TLS configuration with custom contexts and connection parameters
Installation
Prerequisites
- Python 3.10+
- redis-py 5.0.4 or newer
- Redis server (local or remote)
Setup
- Install the package:
# From PyPI (minimal installation)
pip install hotcore
# For development with all tools
pip install -r requirements-dev.txt
# From source (minimal installation)
git clone https://github.com/your-org/hotcore.git
cd hotcore
pip install -e .
# Development installation with all tools
pip install -e ".[dev]"
# Enable geospatial extras (includes Uber's h3 library)
pip install -e ".[h3]"
- Make sure Redis is running:
# Local Redis server (default)
redis-server
# Or connect to a remote Redis instance in your code
Quick Start
from hotcore import Model
# Initialize the model (connects to Redis)
model = Model(host='localhost') # Default port is 6379
# Create a root entity
root = model.init({}) # Generates a UUID automatically
root['name'] = 'Root Entity'
root['type'] = 'container'
model.create('root', root) # 'root' is a special parent ID for root entities
# Create a child entity
child = model.init({})
child['name'] = 'Child Entity'
child['type'] = 'item'
child['status'] = 'active'
model.create(root['uuid'], child) # Root UUID as parent
# Retrieve an entity
entity = model.get(child['uuid'])
print(entity) # {'uuid': '...', 'name': 'Child Entity', 'type': 'item', 'status': 'active'}
# Update an entity
changes = {'uuid': child['uuid'], 'status': 'inactive', 'priority': 'high'}
model.apply(changes)
# Delete an entity
model.delete(child)
# Find entities by attribute values
for entity in model.find(type='item', status='active'):
print(entity)
# Find entities with pattern matching (wildcards)
for entity in model.find(name='Child*'):
print(entity)
# Geospatial search for entities within a bounding box
# (entities with 'lat' and 'long' attributes are automatically indexed)
entities_in_nyc = model.search_bounding_box(40.0, 41.0, -74.0, -73.0)
for entity in entities_in_nyc:
print(f"{entity['name']} at ({entity['lat']}, {entity['long']})")
# Get all children of a parent
for child in model.get_children(root['uuid']):
print(child)
# Get parent of an entity
parent = model.get_parent(child['uuid'])
print(parent)
TLS / SSL configuration
RedisConnectionManager and the Model facade now accept custom TLS settings. Supply an ssl.SSLContext or explicit connection_kwargs to match your Redis deployment:
import ssl
from hotcore import Model
tls_context = ssl.create_default_context()
tls_context.load_verify_locations("/etc/ssl/certs/redis-ca.pem")
secure_model = Model(
host="redis.example.com",
port=6380,
ssl_context=tls_context,
connection_kwargs={"ssl_check_hostname": True},
)
You can also provide write_connection_kwargs (and write_ssl_context) if your write path needs a different configuration than reads.
Project Structure
hotcore/model.py– Facade that wires storage, relationships, search, geospatial, and optional H3 indexing.hotcore/connection.py– Redis connection pooling and key helpers.hotcore/storage.py– CRUD operations with attribute indexing.hotcore/relationships.py– Parent/child traversal utilities.hotcore/search.py– Attribute-based and wildcard search helpers.hotcore/geospatial.py– Redis GEO index management.hotcore/h3_index.py– Optional Uber H3 integration (requireshotcore[h3]).hotcore/hotcore.py– Compatibility exports for the legacy import surface.
Unit and integration tests live under tests/; see tests/README.md for an overview.
Data Model Concepts
Entities
An entity is a dictionary-like object with attributes:
- Must have a
uuidattribute (automatically generated bymodel.init()) - Can contain any number of key-value pairs (attributes)
- Values should be strings for optimal indexing and searching
- Geospatial Support: Entities with
latandlongattributes are automatically added to a Redis geospatial index
Relationships
- Each entity can have one parent (except root entities)
- Each entity can have multiple children
- The parent-child relationship forms a tree structure
Indexing
- All entity attributes are automatically indexed
- Indexes enable fast lookup by attribute value
- Pattern-based searches are supported (using wildcards)
Advanced Usage
Optimistic Locking
The model uses optimistic locking to handle concurrent modifications:
# If two processes try to modify the same entity simultaneously,
# one will succeed and the other will automatically retry (up to 3 times by default)
Pattern Matching
You can use Redis pattern matching in searches:
# Find entities with names starting with "A"
for entity in model.find(name='A*'):
print(entity)
# Find entities with specific pattern in type
for entity in model.find(type='user_?????'):
print(entity)
# Find entities with names containing specific characters
for entity in model.find(name='*[Aa]dmin*'):
print(entity)
Combining Search Criteria
When multiple criteria are provided, all must match (logical AND):
# Find active users with admin role
for entity in model.find(type='user', status='active', role='admin'):
print(entity)
Geospatial Search
Hotcore automatically manages geospatial indexing for entities with lat and long attributes:
# Create entities with coordinates (automatically indexed)
user1 = model.init({
'name': 'Alice Johnson',
'type': 'user',
'lat': 40.7128, # NYC latitude
'long': -74.0060 # NYC longitude
})
model.create('root', user1)
user2 = model.init({
'name': 'Bob Smith',
'type': 'user',
'lat': 34.0522, # LA latitude
'long': -118.2437 # LA longitude
})
model.create('root', user2)
# Search for entities within a bounding box by type
nyc_users = model.search_bounding_box(40.0, 41.0, -74.0, -73.0, "user")
for user in nyc_users:
print(f"{user['name']} in NYC area")
# Update coordinates (automatically updates geospatial index)
changes = {
'uuid': user1['uuid'],
'lat': 42.3601, # Boston latitude
'long': -71.0589 # Boston longitude
}
model.apply(changes)
# Search again to see updated locations
boston_users = model.search_bounding_box(42.0, 43.0, -72.0, -71.0)
Key Features:
- Automatic Indexing: Entities with
latandlongare automatically added to the geospatial index - Transparent Updates: Coordinate changes automatically update the geospatial index
- Bounding Box Search: Find entities within rectangular geographic areas
- Performance: O(log(N)) for adding/updating, O(N+log(M)) for searching
- Validation: Coordinates are validated to ensure they're within valid ranges (-90 to 90° lat, -180 to 180° lon)
API Reference
Model Class
Initialization
Model(host='localhost', port=6379, db=0): Initialize the model with Redis connection parameters
Entity Management
init(entity: dict) -> dict: Add a UUID to an entity dictionarycreate(parent_uuid: str, entity: dict) -> dict: Create and save a new entityget(entity_uuid: str) -> dict: Retrieve an entity by its UUIDapply(change: dict) -> None: Apply changes to an existing entitydelete(entity: dict) -> None: Delete an entity
Relationships
get_children(parent_uuid: str) -> Generator[dict]: Get all children of a parent entityget_parent(child_uuid: str) -> dict: Get the parent of an entity
Search
find(**kwargs) -> Generator[dict]: Find entities matching attribute criteriaget_entity_from_index(index_hit: str) -> Generator[dict]: Get entities from a specific indexsearch_bounding_box(min_lat, max_lat, min_lon, max_lon, entity_type="default") -> List[dict]: Search for entities of a specific type within a geographic bounding box
Best Practices
- Use UUIDs for entity identification: Generate UUIDs using
model.init({}) - Keep attributes as strings: For optimal indexing and searching
- Manage entity lifecycle: Create, update, and delete entities using the model API
- Use pattern matching judiciously: Wildcard searches can be powerful but resource-intensive
- Handle errors: Implement proper error handling for potential Redis errors
- Geospatial coordinates: Use
latandlongattributes for automatic geospatial indexing and search
Performance Considerations
- Connection Pooling: The model uses Redis connection pooling for optimal performance
- Pipelines: Operations use Redis pipelines to minimize network roundtrips
- Optimistic Locking: Helps maintain data consistency with minimal performance impact
- Temporary Sets: Complex searches using wildcards create temporary sets that expire after 60 seconds
Testing
Hotcore's test suite is designed to work without requiring a Redis server for most tests. We use fakeredis to simulate Redis functionality for unit and integration tests.
Running Tests
# Run tests using the provided script
./run_tests.sh
# Run only unit tests
./run_tests.sh --unit
# Run only integration tests
./run_tests.sh --integration
# Run tests with coverage report
./run_tests.sh --cov
# Run with a real Redis server
./run_tests.sh --real-redis
With Virtual Environment (venv)
# Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate
# Install development dependencies
pip install -r requirements-dev.txt
# Run tests that don't require Redis
python -m pytest tests/unit/ tests/integration/
# Run all tests including those requiring Redis
USE_REAL_REDIS=true python -m pytest
With System Python
# Install development dependencies
pip install -r requirements-dev.txt
# Run tests that don't require Redis
python -m pytest tests/unit/ tests/integration/
# Run all tests including those requiring Redis
USE_REAL_REDIS=true python -m pytest
Test Organization
The tests are organized to minimize Redis server dependencies:
tests/unit/- Unit tests (no Redis server needed, uses fakeredis)tests/integration/- Integration tests (no Redis server needed, uses fakeredis)tests/real_redis/- Tests that require a real Redis server (skipped by default)
For more details, see the tests README.
Development
- Read the contribution guide for environment setup, coding standards, and release process.
- All contributions are covered by our Code of Conduct.
- Security issues should be reported privately (see SECURITY.md).
Testing Strategy
- Default test runs use
fakeredisand cover both unit and integration suites (pytestor./run_tests.sh). - Tests that require a real Redis instance live in
tests/real_redis/and are marked with@pytest.mark.redis_required. - Optional geospatial features can be exercised by installing
hotcore[h3]; related tests skip automatically when H3 is unavailable. - CI (planned) will enforce formatting (
black,isort), linting (flake8), type checks (mypy), and the full pytest suite across supported Python versions.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Optional Dependencies
hotcore[h3]: Enables H3-based geospatial indexing via Uber'sh3library (Apache License 2.0).
Third-Party Notices
This project distributes under the MIT License. Optional geospatial functionality depends on Uber's h3 library, which is available under the Apache License 2.0.
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 hotcore-1.2.1.tar.gz.
File metadata
- Download URL: hotcore-1.2.1.tar.gz
- Upload date:
- Size: 48.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff5c9a22b8663a9c22728858bc704a0ef44bd952ac0eb9e37035729a50b26d81
|
|
| MD5 |
04b60866ee6cf23cf58362178f56d3ac
|
|
| BLAKE2b-256 |
202466cfb3a733d66882bf63e4f083176ccc3089a364252a4c2c52743271c25a
|
File details
Details for the file hotcore-1.2.1-py3-none-any.whl.
File metadata
- Download URL: hotcore-1.2.1-py3-none-any.whl
- Upload date:
- Size: 25.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e10ca3054da0fcc9ec382dc422ea4f7a28fe5569d55fb0e24363a5f2c18cc8b
|
|
| MD5 |
d6262e9a78df1ad5662fbac01ee08227
|
|
| BLAKE2b-256 |
e7ae4fd559e4095d21ff9d4509ae734f959c2a7cae039453be4e2bcb70a9fa9c
|