Skip to main content

Redis-like commands backed by SQLite. No server to run.

Project description

babyredis

🧱 Redis-like commands, SQLite underneath, no server to run.

[!NOTE] In progress. Open source contributions welcome.

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 dependenciessqlite3 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 (set with ex/px/nx/xx/keepttl/get, ttl returning -2/-1, bytes responses with a decode_responses flag), 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/

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

babyredis-0.5.0.tar.gz (17.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

babyredis-0.5.0-py3-none-any.whl (19.3 kB view details)

Uploaded Python 3

File details

Details for the file babyredis-0.5.0.tar.gz.

File metadata

  • Download URL: babyredis-0.5.0.tar.gz
  • Upload date:
  • Size: 17.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for babyredis-0.5.0.tar.gz
Algorithm Hash digest
SHA256 2007899a162335f38a75739dd0ccfe63f379a039751564f9a2108cc1ff80edb6
MD5 216ef46243f0eb360c4a6bc62184722b
BLAKE2b-256 99ce8a5d89b472fd75285f256094412bbf788307c9bbe90eed964725e16f10b0

See more details on using hashes here.

Provenance

The following attestation bundles were made for babyredis-0.5.0.tar.gz:

Publisher: release.yml on ciaracade/babyredis

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file babyredis-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: babyredis-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 19.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for babyredis-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 92ba607c2b38ddbb02b0f2a1e27349482c73ebb21aeda2f3f0bcc598c950cc3f
MD5 ccbfdcd74f805af1c2995264a05085cc
BLAKE2b-256 499c4c2ea2b53532fa6d3e63120a12a8f0283d9519d44934054eee47100bb23c

See more details on using hashes here.

Provenance

The following attestation bundles were made for babyredis-0.5.0-py3-none-any.whl:

Publisher: release.yml on ciaracade/babyredis

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page