Skip to main content

Minimal NATS JetStream client for internal services

Project description

NATBus

• Auth: library uses explicit user=/password= with a host:port server so credentials are always sent.

• Streams: set stream_create=True to auto-create one stream (optional).

• Core mode: pass NatsCoreConfig to use plain NATS pub/sub (ack/nak/term are no-ops in core mode).

• Payloads: BusMessage.from_json sets content-type=application/json; binary pass-through uses from_bytes; protobuf via from_proto/publish_proto tags content-type=application/x-protobuf with the message name.

• Stream limits: configure stream_max_age_s (message TTL, seconds) and stream_max_bytes (total size cap) on NatsJetStreamConfig; they are applied when creating/updating the stream.

• Handlers: receive ReceivedMessage with ack()/nak()/term() for JetStream flow control.

• Durable consumers: pass durable="name"; bind=True lets pods restart without “already bound” errors.

Add to Project

Add to the service’s requirements.txt

pip install --find-links=/mnt/nas_share/python_package_repository/natbus natbus==<version>

Usage

import asyncio
from natbus import NatsJetStreamConfig, NatsBus, BusMessage, ReceivedMessage

CFG = NatsJetStreamConfig(
    server="nats-nats-jetstream:4222",
    username="nats-user",
    password="changeme",
    name="orders-svc",
    stream_create=True,
    stream_name="TEST_STREAM",
    stream_subjects=("test.stream",),

    # Optional defaults for PUSH; can be overridden per call
    queue_group=None,   # e.g. "orders-workers" to load-balance PUSH subscribers
    manual_ack=True,
)

# ---------- handlers ----------
async def handle_push(msg: ReceivedMessage):
    print("PUSH RX:", msg.subject, {
        "trace_id": msg.trace_id,
        "correlation_id": msg.correlation_id,
        "sender": msg.sender,
        "content_type": msg.content_type,
    })
    try:
        print("PUSH as_text:", msg.as_text())
    except Exception:
        pass
    await msg.ack()

async def handle_pull(msg: ReceivedMessage):
    print("PULL RX:", msg.subject, {
        "trace_id": msg.trace_id,
        "correlation_id": msg.correlation_id,
        "sender": msg.sender,
        "content_type": msg.content_type,
    })
    try:
        print("PULL as_text:", msg.as_text())
    except Exception:
        pass
    await msg.ack()

# ---------- app ----------
async def main():
    bus = NatsBus(CFG)
    await bus.connect()

    # --- PUSH consumer (server pushes to our callback) ---
    # Use a queue group to load-balance across many pods:
    # queue="orders-workers"  # uncomment to enable worker-pool behavior
    await bus.push_subscribe(
        subject="test.stream",
        handler=handle_push,
        durable="orders_push",   # retains cursor/acks
        # queue="orders-workers",  # optional: load-balanced PUSH
        manual_ack=True,
    )
    print("PUSH consumer ready (durable=orders_push)")

    # --- PULL consumer (we fetch batches, good for explicit backpressure) ---
    # Creates/ensures a pull-based durable (no queue group concept for pull)
    await bus.pull_subscribe(
        stream="TEST_STREAM",
        subject="test.stream",
        durable="orders_pull",
        handler=handle_pull,
        batch=10,        # fetch up to 10 msgs per request
        expires=1.5,     # server waits up to 1.5s for batch to fill
        manual_ack=True,
    )
    print("PULL consumer ready (durable=orders_pull)")

    # --- publish some messages (both consumers will see them, since durables differ) ---
    msg_json = BusMessage.from_json(
        "test.stream",
        {"hello": "world"},
        sender="orders-svc",
    )
    await bus.publish(msg_json)

    msg_bin = BusMessage.from_bytes(
        "test.stream",
        b"\xff\xd8\xff...binary...",
        sender="orders-svc",
        headers={"content-type": "image/jpeg"},
    )
    await bus.publish(msg_bin)

    # Keep the service alive
    while True:
        await asyncio.sleep(60)

if __name__ == "__main__":
    asyncio.run(main())

Core-only usage

import asyncio
from natbus import NatsCoreConfig, NatsBus, ReceivedMessage

CFG = NatsCoreConfig(server="nats://127.0.0.1:4222", queue_group="workers")


async def main():
    bus = NatsBus(CFG)
    await bus.connect()

    async def handler(msg: ReceivedMessage):
        print("CORE RX:", msg.as_json())
        await msg.ack()  # no-op in core mode

    await bus.subscribe("logs.info", handler)  # uses queue_group default
    await bus.publish_json("logs.info", {"hello": "world"}, message_type="log")


if __name__ == "__main__":
    asyncio.run(main())

Build

Version is controlled in pyproject.toml (project.version). Bump it before each release.

python3.11 -m venv .venv && . .venv/bin/activate
python -m pip install --upgrade pip build
python -m build
# artifacts: dist/natsbus-0.1.0.tar.gz and dist/natsbus-0.1.0-py3-none-any.whl

Note: releases 1.x.x are breaking changes from 0.x.x: NatsConfig was renamed to NatsJetStreamConfig and core-only usage now uses NatsCoreConfig.

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

natbus-1.0.0.tar.gz (15.8 kB view details)

Uploaded Source

Built Distribution

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

natbus-1.0.0-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

Details for the file natbus-1.0.0.tar.gz.

File metadata

  • Download URL: natbus-1.0.0.tar.gz
  • Upload date:
  • Size: 15.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.13

File hashes

Hashes for natbus-1.0.0.tar.gz
Algorithm Hash digest
SHA256 3947ef58f2fce7dfdbf76cb1f21ecfc42667b135f42acb7a123469d3e31bf28f
MD5 3fb745d364e2c60d01b6734d3816af09
BLAKE2b-256 9eb4b831739c7d6e8c2dfe9d0aa2e000d8d4eb5c2eddeb3645254f374daa4b8e

See more details on using hashes here.

File details

Details for the file natbus-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: natbus-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 14.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.13

File hashes

Hashes for natbus-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6953f9c64a753c9bbecc3e6494c8b68c29ade2b78b1d4bde6c5e00f85941cf70
MD5 88d3e5354e149c3b4cc1df0d695006fd
BLAKE2b-256 29ba4f6f9ef20841c7646c3ebe1b5be7ee14ca539f3b899f083dfe85d4f19071

See more details on using hashes here.

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