Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

durastream

Minimal durable streaming on local disk. Append-only, crash-safe, tailable streams you import into any Python app — no server, no dependencies (stdlib only).

A tiny reimplementation of the durable-stream idea behind PicoMQ, stripped to a single machine: one append-only log file per stream (fsync = durable) plus a SQLite index. Offset and metadata semantics mirror PicoMQ's Durable Streams (DS) HTTP protocol, so a DS-compatible HTTP layer can be bolted on later without touching the core.

Install

uv pip install -e .        # or: pip install -e .

Requires Python 3.12+. No runtime dependencies.

Quick start

from durastream import Store

store = Store("./data")  # creates ./data/{streams,meta.db}
s = store.create("orders", content_type="text/plain")  # idempotent, like DS PUT
# content_type is optional; defaults to "application/octet-stream"

s.append(b"order-1")  # -> 1  (new next_offset)
s.append(b"order-2")  # -> 2
s.append_many([b"o-3", b"o-4"])  # -> 4  batch: one fsync for the whole list

s.read(0)  # [b"order-1", b"order-2", b"o-3", b"o-4"]  all records from offset 0
s.read(1)  # [b"order-2", b"o-3", b"o-4"]              from offset 1 to tail
s.read(0, 1)  # [b"order-1"]                           half-open [start, end)

s.next_offset  # 4                                     == record count
s.content_type  # "text/plain"

Durability is per-flush: append() writes a length+CRC-framed record and fsyncs before returning; append_many() writes the whole list in one fsync (much faster for bulk ingest — same durability guarantee once it returns). After a crash, reopening the store rebuilds state by scanning the log — a torn or corrupt tail record is dropped, the intact prefix survives.

store2 = Store("./data")  # fresh process, same disk
s = store2.open("orders")
s.read(0)  # [b"order-1", b"order-2"]  — recovered from the log

Tailing (tail -f)

subscribe() yields existing records from an offset, then blocks and yields new ones as they're appended (same process):

import threading


def worker():
    for record in s.subscribe(0):  # replays history, then follows the tail
        print("got", record)


threading.Thread(target=worker, daemon=True).start()
s.append(b"live-1")  # worker prints it

The generator returns once the stream is closed and the consumer has caught up.

Demo

demos/bulk_stream.py bulk-streams 100k JSON readings through one stream while a second thread tails them live, then reopens the store from disk to prove the data survived a restart:

make demo        # or: python3 demos/bulk_stream.py
ingesting 100,000 readings in batches of 1,000 ...
  tailed  10,000     0.5 MB     409,514 rec/s
  ...
ingested 100,000 readings (5.1 MB) in 0.22s  ->  447,142 rec/s, 23 MB/s
reopened from disk: next_offset=100,000  (DS token 00000000000000100000)
resumed read at offset 50,000: [b'{"id": 50000, ...}', b'{"id": 50001, ...}']
durable OK — data survived the restart.

demos/append_vs_batch.py (make demo-bench) contrasts append() (one fsync per record) with append_many() (one fsync per batch) — same durability, ~27x faster here (more on platforms with a costlier fsync).

Closing & deleting

s.close()  # no more appends; reads still work
s.append(b"x")  # raises StreamClosed
s.closed  # True (persisted)

store.delete("orders")  # removes the log file + metadata row
store.list()  # ["other-stream", ...]

Offsets

Offset = logical record index (0-based). next_offset is the record count and the position the next append lands at. Helpers convert to/from the DS wire token format:

from durastream import to_token, from_token

to_token(1)  # "00000000000000000001"
from_token("-1", next_offset)  # 0            (start of stream)
from_token("now", next_offset)  # next_offset  (current tail)
from_token("00000000000000000003", next_offset)  # 3

On-disk layout

data/
  meta.db                 SQLite: name, content_type, closed, created_at
  streams/
    orders.log            append-only frames: [u32 len][u32 crc32][payload]...

CRC is zlib.crc32 (CRC-32/ISO-HDLC, the same algorithm PicoMQ uses). One writer per stream is serialized by an in-process lock; SQLite runs in WAL mode.

Mapping to the Durable Streams HTTP protocol

The library covers the DS semantics; wiring it behind HTTP is mechanical:

DS HTTP durastream
PUT /ds/{name} (Content-Type) store.create(name, content_type)
POST /ds/{name} body stream.append(body)Stream-Next-Offset: to_token(next_offset)
GET /ds/{name}?offset= stream.read(from_token(offset, next_offset))
GET ...&live=long-poll|sse stream.subscribe(...)
HEAD /ds/{name} content_type, next_offset, closed
POST ... Stream-Closed: true stream.close()
DELETE /ds/{name} store.delete(name)

Scope

Single-node, single-process durability. Deliberately not included (add if you need them): HTTP server, content-type shaping (text concat / JSON arrays), producer fencing, S3/object-store tier, cross-process tailing, TTL/ETag. These are the upgrade paths from PicoMQ's full design.

Develop

make test        # dep-free self-check (python3 tests/test_durastream.py)
make lint        # ruff format + ty typecheck

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

durastream-0.0.1a1.tar.gz (11.6 kB view details)

Uploaded Source

Built Distribution

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

durastream-0.0.1a1-py3-none-any.whl (8.8 kB view details)

Uploaded Python 3

File details

Details for the file durastream-0.0.1a1.tar.gz.

File metadata

  • Download URL: durastream-0.0.1a1.tar.gz
  • Upload date:
  • Size: 11.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for durastream-0.0.1a1.tar.gz
Algorithm Hash digest
SHA256 5ad67e83234ecd27d38a105774717491847f5e478118f53283a8b2e37232954f
MD5 6487d1263b32e1a36c2c2ee10930f0c6
BLAKE2b-256 9e74f2f37c9f1e8ab9e0c83d1f4207453a0c33175688e73484b4781bbf581292

See more details on using hashes here.

File details

Details for the file durastream-0.0.1a1-py3-none-any.whl.

File metadata

  • Download URL: durastream-0.0.1a1-py3-none-any.whl
  • Upload date:
  • Size: 8.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for durastream-0.0.1a1-py3-none-any.whl
Algorithm Hash digest
SHA256 ab80eea1fb6f75d36c6e7ad0bdada242d2cb8e66eb719551192f14c7ebf7fd84
MD5 5a7417f9d48d60c6605507d900b51929
BLAKE2b-256 e6e4b0d920a66e30a8e9a86ff270bedc1a2c83bca1691b09f67f0eb6d3d1f564

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.0.1a1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page