babyredis
🧱 Redis-like commands, SQLite underneath, no server to run.
babyredis is a redis-py-shaped client
backed by a single SQLite file (WAL mode). Think
redka, but pure Python, in-process, and
pip-installable with zero dependencies — sqlite3 ships with Python.
from babyredis import Redis
r = Redis("cache.db") # or Redis(":memory:") for ephemeral
r.set("session:42", "ciara", ex=3600)
r.get("session:42") # b'ciara'
r.incr("page:views") # 1
r.ttl("session:42") # 3600
Why?
- No server. No daemon to install, configure, monitor, or pay for. Your cache is a file next to your app — perfect for single-server deployments, CLIs, scripts, and side projects.
- Survives restarts. Unlike a dict (or fakeredis), data persists and can be shared across processes on the same machine.
- Familiar API. Method signatures mirror redis-py (
setwithex/px/nx/xx/keepttl/get,ttlreturning -2/-1, bytes responses with adecode_responsesflag), so swapping a small app off Redis — or onto it later — is mostly an import change.
Honest trade-offs: local SQLite reads are fast (no TCP round-trip), but Redis will beat this on write throughput and tail latency, and there's no cross-machine networking. If you need pub/sub, clustering, or six-figure ops/sec, run real Redis. This is for everyone who doesn't.
Supported commands (v0.3)
Strings: set, get, getset, getdel, setex, psetex, setnx,
append, strlen, mset, mget, incr/incrby, decr/decrby,
incrbyfloat.
Hashes: hset (with mapping=), hget, hgetall, hdel, hexists,
hkeys, hvals, hlen, hmget, hsetnx, hincrby, hincrbyfloat,
hstrlen, hscan/hscan_iter.
Sets: sadd, srem, smembers, sismember, smismember, scard,
spop, srandmember, smove, sinter, sunion, sdiff,
sinterstore/sunionstore/sdiffstore, sscan/sscan_iter.
Sorted sets: zadd (nx/xx/gt/lt/ch/incr), zincrby,
zscore, zmscore, zrem, zcard, zcount, zrange/zrevrange,
zrangebyscore/zrevrangebyscore (with (-exclusive and ±inf bounds),
zrank/zrevrank, zpopmin/zpopmax,
zremrangebyrank/zremrangebyscore,
zunionstore/zinterstore (with weights and SUM/MIN/MAX aggregation;
plain sets participate at score 1.0), zscan/zscan_iter.
Lists: lpush, rpush, lpop/rpop (with count), llen,
lrange, lindex, lset, lrem, ltrim, linsert, lmove,
rpoplpush.
Keys & server: delete, exists, expire, pexpire, expireat,
persist, ttl, pttl, keys (Redis glob patterns),
scan/scan_iter, rename, renamenx, randomkey, type, dbsize,
flushdb, ping.
Pipelines: pipeline() queues commands and runs them in a single
SQLite transaction on execute() — unlike real Redis MULTI/EXEC, the
batch is fully ACID: an error rolls back the whole batch.
with r.pipeline() as pipe:
pipe.set("a", 1).incr("hits").rpush("log", "x")
results = pipe.execute() # [True, 1, 1]
Semantics follow Redis: operations against a key of the wrong type raise
ResponseError (WRONGTYPE), removing a collection's last element removes
the key, and TTLs apply per key across all types. Expired keys are
invisible immediately and physically purged lazily, with a periodic sweep
on writes.
Async
babyredis.aio mirrors redis.asyncio: same constructor, every command
awaitable, *_iter helpers are async generators. Commands run in a
worker thread so the event loop never blocks on SQLite I/O.
from babyredis.aio import Redis
async with Redis("cache.db") as r:
await r.set("k", "v", ex=60)
await r.get("k")
async for key in r.scan_iter(match="user:*"):
...
Performance
Single-operation latency beats Redis-over-loopback ~6-10x (no TCP round
trip) and fakeredis ~5x; a plain dict beats everything by ~40x; Redis
wins back parity with pipelining and wins outright on concurrent write
throughput. Full table, methodology, and honest caveats in
docs/performance.md. Reproduce with
python benchmarks/bench.py.
Concurrency
File-backed databases use one SQLite connection per thread: reads take no
Python lock (WAL allows concurrent readers) and writes serialize through
SQLite's own locking. Cross-process access to the same file is safe —
writes are atomic via immediate transactions. :memory: databases use a
single lock-guarded connection (SQLite memory databases can't be shared
across connections).
Compatibility validation
tests/test_redka_compat.py ports the edge-case suite from
redka's Go tests (score tie-breaking,
list range/trim boundary permutations, store-variant overwrite semantics,
atomic mset rollback, and more). Where redka deviates from Redis —
silent wrong-type reads, keeping emptied keys, refusing cross-type
rename, no negative zrange indices — babyredis follows Redis, and
each divergence is noted in the test.
On top of that, tests/test_oracle.py runs property-based tests with
fakeredis as the oracle: a
Hypothesis state machine fires random command sequences at both clients
and asserts identical results (or identical failures) after every step.
Testing
babyredis doubles as a Redis stand-in for tests. Opt into the bundled fixtures:
# conftest.py
pytest_plugins = ["babyredis.testing"]
def test_something(babyredis_client):
babyredis_client.set("k", "v")
Install
pip install babyredis
Roadmap
- Type hints +
py.typed
Development
pip install -e .
pip install pytest hypothesis fakeredis
pytest tests/
Release files for babyredis 0.5.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 | |
|---|---|---|---|
| babyredis-0.5.0.tar.gz | 17.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| babyredis-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.7 kB
Release files / babyredis-0.5.0.tar.gz
| Download URL | babyredis-0.5.0.tar.gz |
|---|---|
| Size | 17.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2007899a162335f38a75739dd0ccfe63f379a039751564f9a2108cc1ff80edb6
|
|
BLAKE2b-256 checksum How to use checksums |
99ce8a5d89b472fd75285f256094412bbf788307c9bbe90eed964725e16f10b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 24, 2026.
Transparency logRelease files / babyredis-0.5.0-py3-none-any.whl
| Download URL | babyredis-0.5.0-py3-none-any.whl |
|---|---|
| Size | 19.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
92ba607c2b38ddbb02b0f2a1e27349482c73ebb21aeda2f3f0bcc598c950cc3f
|
|
BLAKE2b-256 checksum How to use checksums |
499c4c2ea2b53532fa6d3e63120a12a8f0283d9519d44934054eee47100bb23c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 24, 2026.
Transparency log