Metadata-Version: 2.4
Name: grass-redis
Version: 0.2.0
Summary: Redis Sentinel client with mandatory mutual TLS for high-availability Redis clusters
Author: Alex
License-Expression: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: redis>=5.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# grass-redis

Redis Sentinel client with optional mutual TLS for high-availability Redis clusters.

## Features

- **Automatic failover** via Sentinel
- **Connection pooling** and caching
- **Read replicas** for load distribution
- **TLS security** with optional client certificates
- **Service discovery** - automatically find primary and replicas
- **Multiple databases** - manage multiple Redis instances from one client

## Installation

```bash
pip install grass-redis
```

## Quick Start

### Using Factory Method (Recommended)

Load configuration from `/etc/redis/credentials.env`:

```python
from grass_redis import create_client_from_credentials

# One-line client creation
with create_client_from_credentials() as client:
    # Connect to database
    db = client.get_primary("redis-instance-01")
    db.set("key", "value")
    
    # Read from replica for load distribution
    replica = client.get_replica("redis-instance-01")
    value = replica.get("key")
```

The factory method automatically:
- Loads Sentinel endpoints from `/etc/redis/credentials.env`
- Loads all database passwords
- Configures TLS with `/etc/redis/ca.crt`
- Returns a ready-to-use client

### Manual Configuration

```python
from pathlib import Path
from grass_redis import RedisSentinelClient, SentinelConfig

config = SentinelConfig(
    sentinel_hosts=[
        ("sentinel1.example.com", 26379),
        ("sentinel2.example.com", 26379),
        ("sentinel3.example.com", 26379),
    ],
    databases={"redis-instance-01": "password"},
    certs_dir=Path("/etc/redis"),
)

with RedisSentinelClient(config) as client:
    db = client.get_primary("redis-instance-01")
    db.set("key", "value")
```

## Configuration

### Credentials File Format

Deploy to `/etc/redis/credentials.env`:

```bash
# Sentinel endpoints
REDIS_SENTINEL_HOSTS=sentinel1.example.com:26379,sentinel2.example.com:26379

# Database credentials
REDIS_INSTANCE_01_MASTER=redis-instance-01
REDIS_INSTANCE_01_PORT=6379
REDIS_INSTANCE_01_PASSWORD=your-password
```

### Required Files

- `/etc/redis/ca.crt` - CA certificate (required, 644)
- `/etc/redis/credentials.env` - Configuration and passwords (600)

## Common Operations

### Basic Operations with TTL

```python
db = client.get_primary("redis-instance-01")
db.set("key", "value", ex=60)  # Expires in 60 seconds
value = db.get("key")
```

### Hash Operations

```python
db.hset("user:123", mapping={
    "name": "John",
    "email": "john@example.com",
    "status": "active"
})
data = db.hgetall("user:123")
name = db.hget("user:123", "name")
```

### List Operations

```python
# Push items to list
db.lpush("queue:tasks", "task1", "task2")

# Pop from list
task = db.rpop("queue:tasks")

# Get list length
length = db.llen("queue:tasks")
```

### Set Operations

```python
# Add items to set
db.sadd("processed:urls", "url1", "url2", "url3")

# Check membership
exists = db.sismember("processed:urls", "url1")

# Get all members
urls = db.smembers("processed:urls")
```

## Using Read Replicas

Distribute read load across replicas:

```python
with create_client_from_credentials() as client:
    # Write to primary
    primary = client.get_primary("redis-instance-01")
    primary.set("data", "value")
    
    # Read from replica (automatic load balancing)
    replica = client.get_replica("redis-instance-01")
    value = replica.get("data")
```

## Service Discovery

Find where databases are currently running:

```python
with create_client_from_credentials() as client:
    # Find current primary
    host, port = client.discover_primary("redis-instance-01")
    print(f"Primary: {host}:{port}")
    
    # Find all replicas
    replicas = client.discover_replicas("redis-instance-01")
    for host, port in replicas:
        print(f"Replica: {host}:{port}")
```

## Error Handling

```python
from redis.exceptions import ConnectionError, TimeoutError

try:
    with create_client_from_credentials() as client:
        db = client.get_primary("redis-instance-01")
        db.set("key", "value")
except FileNotFoundError as e:
    print(f"Missing credentials file: {e}")
except ConnectionError as e:
    print(f"Cannot connect to Redis: {e}")
except TimeoutError as e:
    print(f"Operation timed out: {e}")
except KeyError as e:
    print(f"Database not configured: {e}")
```

## API

### Factory Method
- `create_client_from_credentials()` - Load from `/etc/redis/credentials.env`

### SentinelConfig
- `sentinel_hosts` - List[(host, port)]
- `databases` - Dict[db_name, password]
- `certs_dir` - Path (default: `/etc/redis`)

### RedisSentinelClient
- `get_primary(db)` - Connection for writes
- `get_replica(db)` - Connection for reads (load balanced)
- `discover_primary(db)` - Get current primary address
- `discover_replicas(db)` - Get all replica addresses
- `test_connection(db)` - PING test

## Key Features

- **No Hardcoded Endpoints** - Everything loaded from configuration files
- **Sentinel Discovery** - Sentinels automatically find current primary location
- **TLS Required** - All connections use TLS with CA certificate
- **Connection Pooling** - Automatically managed, connections reused efficiently
- **Automatic Failover** - Sentinel promotes replicas if primary fails

## Example Scripts

Included example scripts:
- `example_production_usage.py` - Comprehensive production examples
- `example_new_setup.py` - Simple manual configuration example

## Publishing to PyPI

See `PUBLISHING.md` for complete instructions on publishing this package to PyPI.

## License

MIT License - see LICENSE file for details.
