Skip to main content

Jetstream Lakehouse

jetstream-lakehouse is a Python 3.10+ destination-neutral client for Jetstream v2 archive snapshots and its canonical live WebSocket. It contains no sink, checkpoint store, Spark, Databricks, Kafka, or Snowflake code. Downstream consumers own delivery and durable cursor storage.

The archive decoder is adapted from the Apache-2.0 licensed Databricks Community Connector Bluesky source. See NOTICE for attribution. It verifies archive generations, exact byte ranges, xxh3 metadata checksums, Zstandard checksums and sizes, CBOR structure, and sequence ordering. Exact collection/DID/kind matching happens after decoding.

Install

After the first PyPI release, install with:

pip install jetstream-lakehouse

For development from a checkout, use uv for the environment, lock, and build:

uv sync --locked
uv build

See RELEASING.md for the TestPyPI and PyPI release process.

This package is a library and does not load configuration from environment variables or provide a command-line entry point. Pass settings to Config directly. Keep credentials in your application's secret store; api_key is optional and applies only to Jetstream archive requests.

Public API

from jetstream_lakehouse import Client, Config
from contextlib import closing

config = Config(
    endpoint="https://jetstream.us-east.bsky.network",
    api_key="",  # optionally pass an archive API key from your application's secret store
    collections=("app.bsky.feed.post",),
    kinds=("commit", "identity", "account", "sync"),
    max_sequence_span=10_000,
)
client = Client(config)

# One bounded sealed-archive range: events in (after_seq, through_seq].
batch = client.snapshot(after_seq=123456)
for event in batch.events:
    process(event)
print(batch.through_seq)  # candidate cursor, including valid zero-event windows

# Explicit cursor resumes history; omit cursor only to follow from the current live tip.
with closing(client.live(cursor=123456)) as stream:
    for event in stream:
        process(event)

# Backfill bounded archive batches, then continue as one-event live batches.
with closing(client.replay(after_seq=123456)) as stream:
    for batch in stream:
        write_to_sink(batch.events)
        await_durability()
        save_cursor(batch.through_seq)

Event is immutable and uses the same fields for archive and live input: seq, event_time, witnessed_at, did, kind, operation, collection, rkey, cid, rev, is_resync, record, event_payload, and optional raw_payload_base64. JSON values are compact JSON strings. Missing values are None. Raw archive CBOR is base64 text and is not available on live events. Create, update, delete, identity, account, sync, and resync markers remain individual events; this client does not fold them into current-record state.

event_payload contains the record for creates/updates, the original upstream marker object for identity/account/sync, and None for deletes. It has the same shape in both modes. JSON keys are sorted and timestamps are normalized to UTC. A sync's rev is extracted from its marker object.

Archive ranges are exclusive/inclusive: (after_seq, through_seq]. A returned through_seq means the complete range was validated, including when filters matched no events. It is only a candidate cursor; the caller persists it after its own destination confirms durability. If a write fails, resume from the caller's last saved cursor. This provides at-least-once delivery to the caller, not exactly-once persistence in an external system.

snapshot requires an explicit after_seq and reads at most max_sequence_span. A window can start and end inside a physical segment. replay pins one sealed tip for the whole backfill, then connects live at that boundary. Segments sealed during backfill arrive through the server's cold replay. If live lookback expires, replay discovers a new archive boundary from its last yielded cursor; if the archive cannot bridge the gap yet, it raises CursorTooOld without skipping ahead.

live(cursor=None) starts at the current live tip. Numeric cursors are sequences; live values at or above 10**15 are rejected because the server would interpret them as timestamps. Transient connection errors, timeouts, HTTP 429/5xx, and ConsumerTooSlow reconnect from the last yielded sequence, suppressing repeats. max_retries bounds consecutive failures and resets after forward event progress. Restart after a destination failure with the application's last saved cursor.

Live uses one buffered WebSocket frame and a max_batch_bytes message limit. Calls yield one event at a time to apply backpressure. Use closing(...) when a loop may exit early. Pass a threading.Event as stop to interrupt idle reads and retry waits; replay checks it between archive batches too. An active connection attempt is bounded by request_timeout_seconds. client.live(timeout_seconds=20) imposes a total deadline across connections, idle reads, and backoff, raising StreamTimeout when it expires; socket shutdown can take up to one additional second.

Filters and limits

Set collections, dids, and kinds on Config. Kinds default to all supported kinds. Collection filters constrain commits; DID-level marker events are retained unless kinds or DID filters exclude them. Providing collections while excluding commit is rejected, as required by the upstream API. Limits are 4 kind entries, 100 collection patterns, and 10,000 DIDs. Archive planning may over-select blocks, so filters are applied exactly after decoding. Live uses the canonical v2 repeated filter parameters.

Config also exposes max_sequence_span, max_batch_bytes, request_timeout_seconds, refresh_timeout_seconds, max_retries, and include_raw_payload. Invalid options and non-HTTPS origins are rejected before network calls. HTTP requests disable ambient netrc credentials, refuse redirects, cap response sizes, and pin selected bytes to ETags. Archive corruption, changed generations, malformed plans, expired cursors, and exhausted deadlines raise typed exceptions from jetstream_lakehouse.errors; no successful candidate cursor is returned for an incomplete archive window. The live stream uses /xrpc/network.bsky.jetstream.subscribeEvents with the xrpc.v1.json subprotocol, not the legacy endpoint.

Catch ProtocolError for invalid data or size limits, ArchiveChanged for a changed segment generation, and CursorTooOld for unavailable replay history. RefreshTimeout covers archive work; StreamTimeout covers an optional live deadline. LiveStreamError.code identifies terminal v2 errors without exposing server messages. These all inherit from JetstreamError, which also covers denied access and exhausted retries. Live connections disable automatic environment proxy discovery.

Tests and downstream use

uv run ruff check ., uv run pytest, and uv build run without service credentials (installing/building dependencies may require PyPI access). The ordinary test suite performs no external network requests. See tests/FIXTURES.md for the independent Go-writer fixture's provenance. Synthetic events cover all supported kinds, including deletes and resyncs; malformed archives and HTTP ranges exercise checksum and generation validation.

The public live contract test reads three events with a 20-second deadline and requires no key or cursor:

JETSTREAM_INTEGRATION_LIVE=1 uv run pytest tests/integration -k live

The archive contract test requires a cursor inside sealed history and a key where the endpoint requires authentication. Put the raw token in a local file with mode 0600, for example .secrets/jetstream_api_key; .secrets/ is ignored by Git. Pass only the path on the command line:

JETSTREAM_API_KEY_FILE="$PWD/.secrets/jetstream_api_key" \
JETSTREAM_INTEGRATION_CURSOR=123456 \
uv run pytest tests/integration -k archive

The archive test caps the sequence span at 1,000 by default and the read deadline at 30 seconds. It requires actual events, so an empty or future range cannot pass as a verified archive read. JETSTREAM_ENDPOINT overrides the test endpoint; JETSTREAM_INTEGRATION_LIVE_CURSOR optionally tests explicit live resume. These variables belong only to the test harness; the library continues to require explicit Config objects. Both network tests are skipped in ordinary CI.

For further content diversity, public post datasets such as two-million-bluesky-posts can supply record JSON. They don't contain Jetstream's archive framing, generation checks, full marker stream, or cursor semantics, so use the native fixture and protocol tests for those guarantees. The public Jetstream service provides real v2 frames for the live contract check.

A Lakeflow adapter or a Zerobus producer can consume the public models and own its own delivery contract and checkpoint. Those integrations belong in their downstream repositories/packages; this library does not import or depend on either.

Protocol reference: Jetstream v2 documentation.

Metadata

Release files for jetstream-lakehouse 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jetstream-lakehouse 0.1.0
File Size Uploaded
jetstream_lakehouse-0.1.0.tar.gz 101.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jetstream-lakehouse 0.1.0
File Interpreter ABI Platform
jetstream_lakehouse-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 137.7 kB

Release files / jetstream_lakehouse-0.1.0.tar.gz

Download URL jetstream_lakehouse-0.1.0.tar.gz
Size 101.4 kB
Tags Source
SHA-256 checksum
How to use checksums
6076ad7b063ce5838493ea721a2e4759286f4e2611c10716feb0d52b3be32743
BLAKE2b-256 checksum
How to use checksums
4dbf66c5153c4fb22420aae089bb31a4473ea60eba88bae7161444dec37fcc09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 3, 2026.

Transparency log

Release files / jetstream_lakehouse-0.1.0-py3-none-any.whl

Download URL jetstream_lakehouse-0.1.0-py3-none-any.whl
Size 36.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8f2b43956baf657f4234955bc547f8ef1cdaa45bc1f43020c742ec62b8d9d413
BLAKE2b-256 checksum
How to use checksums
4417bddae87e50f12f6c315ce6054fde6877fd6527cf16b09db025b038691d86
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release 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