Metadata-Version: 2.4
Name: redispg
Version: 0.1.0
Summary: A narrow RESP2-compatible key/value server backed by PostgreSQL
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: psycopg[binary,pool]>=3.1
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: redis>=5; extra == "test"

# redispg

A narrow RESP2-compatible key/value server backed by PostgreSQL. See [spec.md](spec.md) for the full specification.

Ordinary Redis clients (`redis-py`, `redis-cli`) work for the supported subset. This is a **Redis-compatible PostgreSQL key/value service**, not a high-performance Redis replacement.

Only RESP2 is spoken. Clients that default to RESP3 must be pinned to protocol 2, e.g. `redis.Redis(..., protocol=2)` with redis-py 6+ or `redis-cli -2`.

## Supported commands

```text
PING [message]
GET key
SET key value [EX seconds] [NX]
DEL key [key ...]
EXISTS key [key ...]
EXPIRE key seconds
TTL key
MGET key [key ...]
MSET key value [key value ...]
INCR key
DECR key
INCRBY key increment
DECRBY key decrement
```

Everything else is rejected with an explicit error.

## Installation

```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt        # runtime only
# or, as a package with test dependencies:
pip install -e ".[test]"
```

## Publishing to PyPI

Publishing is triggered when a GitHub release is published. Configure the repository
secret `PYPI_API_TOKEN` with the PyPI API token (a placeholder value can be used
until the real token is available), then publish a release to build and upload the
package with Twine.

## Running

```bash
export REDISPG_DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
redispg                    # if installed with pip install -e .
# or without installing:
PYTHONPATH=src python -m redispg
```

The schema ([src/redispg/migrations/001_init.sql](src/redispg/migrations/001_init.sql)) is applied automatically at startup.

Try it:

```bash
redis-cli -2 -p 6399 SET foo bar EX 60
redis-cli -2 -p 6399 GET foo
redis-cli -2 -p 6399 TTL foo
```

### Docker

```bash
docker build -t redispg .
docker run -e REDISPG_DATABASE_URL=postgresql://user:pass@host:5432/dbname \
    -e REDISPG_HOST=0.0.0.0 -p 6399:6399 redispg
```

## Configuration (environment variables)

| Variable | Default | Description |
|---|---|---|
| `REDISPG_DATABASE_URL` | *(required)* | PostgreSQL DSN |
| `REDISPG_HOST` | `127.0.0.1` | Listen address |
| `REDISPG_PORT` | `6399` | Listen port |
| `REDISPG_POOL_MIN` / `REDISPG_POOL_MAX` | `2` / `10` | Connection pool bounds |
| `REDISPG_MAX_KEY_SIZE` | `1024` | Max key bytes |
| `REDISPG_MAX_VALUE_SIZE` | `16777216` | Max value bytes (16 MiB) |
| `REDISPG_MAX_REQUEST_SIZE` | `17825792` | Max request bytes (17 MiB) |
| `REDISPG_MAX_ARRAY_ITEMS` | `10000` | Max RESP array items |
| `REDISPG_MAX_CLIENTS` | `1000` | Max concurrent clients |
| `REDISPG_READ_TIMEOUT` | `300` | Idle client timeout (s) |
| `REDISPG_COMMAND_TIMEOUT` | `30` | Per-command timeout (s) |
| `REDISPG_STATEMENT_TIMEOUT_MS` | `15000` | PostgreSQL statement_timeout |
| `REDISPG_LOCK_TIMEOUT_MS` | `5000` | PostgreSQL lock_timeout |
| `REDISPG_CLEANUP_INTERVAL` | `30` | Expired-row cleanup period (s) |
| `REDISPG_CLEANUP_BATCH_SIZE` | `1000` | Cleanup delete batch size |

## Security

The service is unauthenticated. Bind it only to localhost or a trusted network. `AUTH` and TLS are out of scope for the MVP (see spec.md).

## Tests

```bash
pip install -e ".[test]"
python -m pytest                       # protocol unit tests always run
export REDISPG_DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
python -m pytest                       # + end-to-end tests against PostgreSQL
```

Integration tests are skipped when `REDISPG_DATABASE_URL` is unset or PostgreSQL is unreachable.

## Layout

```text
redispg/
├── README.md
├── spec.md
├── Dockerfile
├── requirements.txt
├── pyproject.toml
├── src/redispg/
│   ├── __main__.py     # entry point
│   ├── config.py       # env-based configuration
│   ├── protocol.py     # RESP2 parser and encoders
│   ├── commands.py     # command dispatch
│   ├── store.py        # PostgreSQL persistence
│   ├── server.py       # asyncio TCP server
│   ├── cleanup.py      # expired-row cleanup worker
│   ├── errors.py
│   └── migrations/001_init.sql
└── tests/
```
