Skip to main content

stapel-chat

CI coverage pypi downloads python license llms.txt

Conversations and messaging over WebSocket: direct (1:1, idempotent by participant pair), group and support threads on one model; realtime send/edit/delete with a monotonic per-conversation seq for order and a separate revision sequence for resume, so an edit or a deletion made while a client was offline reaches it on reconnect; deletion is a tombstone the id keeps arriving under, so client caches and offline databases learn what to purge; attachments carry the render metadata a bubble needs on first paint (aspect, byte size, a 16px base64 thumbnail, voice duration and waveform, document mime and extension) behind an OPEN type registry; typing and activity states, read and delivery receipts as ephemeral signals; a live inbox stream so the conversation list does not poll either; anchor-paginated history and lists; leaving a thread, which hides it for the person who left and takes nothing away from anybody else (their messages, the other party's copy and their own history by id all stay), un-hides on the next message written there, and is undoable (the left threads are listed by ?left=true and put back by POST /conversations/{id}/rejoin, which clears the departure and nothing else); and a support layer (queue, first-come assignment, open/pending/resolved with reopen).

Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.

Install

pip install stapel-chat

At a glance

Fact Value
Version 0.9.0
Python >=3.11 (3.11, 3.12, 3.13, 3.14)
HTTP operations 16
Config axes 15
Usage surface 38
Extension points 9
Error codes 65
Fleet dependencies stapel-auth (optional) · stapel-cdn (optional) · stapel-core · stapel-realtime

Documentation

OpenAPI · capabilities.json · llms.txt (for agents)

One model backs three kinds of thread: direct (1:1, idempotent by participant pair), group, and support (a customer↔operator thread with a queue and assignment lifecycle).

Realtime is the module, not a mode of it. Messages are sent and received over a WebSocket; REST serves history, hydration and the support lifecycle. A deployment that cannot serve the socket fails manage.py check rather than degrading into a product that refreshes on a timer — because a polling fallback a product can end up in silently is exactly how "websockets are done" became a false claim once already.

Quick start

INSTALLED_APPS = [
    # ...
    "stapel_core.django.apps.CommonDjangoConfig",
    "stapel_core.django.users",
    "rest_framework",
    "stapel_realtime",
    "stapel_chat",
]

CHANNEL_LAYERS = {"default": {
    "BACKEND": "channels_redis.core.RedisChannelLayer",
    "CONFIG": {"hosts": [REDIS_URL]},
}}
STAPEL_COMM = {"SIGNAL_TRANSPORT": "channels"}
STAPEL_REALTIME = {"ALLOWED_ORIGINS": ["https://app.example.com"]}  # with the port

# urls.py
urlpatterns = [path("chat/", include("stapel_chat.urls"))]
# asgi.py — the whole file
from django.core.asgi import get_asgi_application
from stapel_realtime.asgi import build_websocket_application

application = build_websocket_application(http_application=get_asgi_application())
pip install 'stapel-chat[realtime]'

What you get

  • Two sockets. ws/chat/<conversation_id> is the resumable journal — hello{last_seq} → replay → live, with send / edit / delete / read / delivered / activity frames going through the same service layer the REST views call. ws/chat/inbox keeps the conversation list live, because a list with no socket refreshes on a timer forever however live the open thread is.
  • Two sequences. seq is a message's immutable place in the thread — the sort key and the history anchor. rev_seq is its place in the revision journal, re-allocated on every edit and delete, and it is what realtime replay is anchored on: an edit made while a client was offline arrives in the catch-up. A client upserts by id, sorts by seq, and remembers rev_seq as its cursor.
  • Edit and delete. An edit sets edited / edited_at. A delete leaves a tombstone: the id keeps being delivered with body: "", attachments: [] and deleted: true, so a client cache learns which id to purge. An id that stops arriving is an id nobody can purge. Retention is permanent.
  • Attachments that render on first paint — aspect, byte size and a ~16px base64 thumbnail for images and GIFs; duration and a waveform image for audio; mime and extension for documents; poster and duration for video. The type set is an open registry — stickers are a settings line — and the metadata comes from stapel-cdn by comm, once, at send time.
  • Receipts and activity. Separate delivery and read markers, both durable and both fanned out live; typing / recording_audio / sending_video / uploading_file as ephemeral signals with a TTL, from another open registry.
  • ConversationsPOST /chat/api/v1/conversations (direct / group / support); direct is get-or-create by participant pair. GET lists yours (anchor-paginated) with unread_count, and every row carries its own stream_key and socket_path. ?search= finds a thread by the three things its row draws — the counterpart's display name, the subject card's title and the last line — and ?unread=true narrows to the rows with a badge; both filter before the page is taken, so the anchor keeps its meaning.
  • LeavingDELETE /chat/api/v1/conversations/{id} is the caller leaving, not a delete: the thread drops off their list, counts and search, their live subscription is revoked and a chat.participant.left system line records it — while every message, every other participant and their own history by id stay exactly as they were. A new message from the other side brings the thread back. Staff erasure is not on this surface: user data has one deletion path, user.deleted.
  • MessagesGET/POST /chat/api/v1/conversations/{id}/messages, PATCH/DELETE .../messages/{message_id}. History is anchored on seq, newest-first, both directions.
  • SupportGET /chat/api/v1/support/queue, POST .../support/conversations/{id}/{assign,resolve,reopen}.

Configuration (STAPEL_CHAT)

Key Default Meaning
CHAT_KINDS ["direct","group","support"] Enabled thread kinds
ATTACHMENTS True Allow attachments on messages
MAX_BODY_LENGTH 4000 Hard cap on a text body
ATTACHMENT_TYPES {} Open registry, merged over image/gif/video/audio/file — the same names stapel-cdn uses
ACTIVITY_STATES {} Open registry, merged over typing/recording_audio/…
ATTACHMENT_METADATA "cdn" Ask cdn.describe, or trust the client
MAX_ATTACHMENTS 10 Attachments per message
MAX_PREVIEW_B64_BYTES 8192 Ceiling on an inline data: preview
EDIT_WINDOW_S 0 Seconds a message stays editable (0 = forever)
SCOPE_PROVIDER stapel_chat.scope.DefaultScopeProvider Resolve/enforce the opaque scope_key

There is no key that turns realtime off. See MODULE.md for the full wire contract, the extension seams and the anti-patterns.

License

MIT — see LICENSE.


This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.

Download files

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

Source Distribution

stapel_chat-0.9.0.tar.gz (221.3 kB view details)

Uploaded Source

Built Distribution

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

stapel_chat-0.9.0-py3-none-any.whl (176.1 kB view details)

Uploaded Python 3

File details

Details for the file stapel_chat-0.9.0.tar.gz.

File metadata

  • Download URL: stapel_chat-0.9.0.tar.gz
  • Upload date:
  • Size: 221.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stapel_chat-0.9.0.tar.gz
Algorithm Hash digest
SHA256 6c7b5707422c4776d984061080a9263370fb0ada2d88d11d73cf6e36628fe70c
MD5 e99e51c7306dec7d8ef95bf4dd25e749
BLAKE2b-256 d0b7edb0873fbeefd5ad46bbb76edce8225dd51941de77c634e41cabf2c47a0d

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_chat-0.9.0.tar.gz:

Publisher: publish.yml on usestapel/stapel-chat

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

File details

Details for the file stapel_chat-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: stapel_chat-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 176.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stapel_chat-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a349110e330cfbf99b9458f66979c36b7e0b5c1cdb3879392c7105ff3a819fdb
MD5 8b02c723fba246c6262ac954e681d8e6
BLAKE2b-256 d751f11448c97da603319a690ad1154231438f67b7c344719330855e068c8a05

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_chat-0.9.0-py3-none-any.whl:

Publisher: publish.yml on usestapel/stapel-chat

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

Release history Release notifications | RSS feed

0.10.0

2 files

0.9.3

2 files

0.9.2

2 files

0.9.1

2 files

This release

0.9.0 This release

2 files

0.8.6

2 files

0.8.5

2 files

0.8.4

2 files

0.8.3

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.4

2 files

0.7.3

2 files

0.7.2

2 files

0.7.0

2 files

0.6.3

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.1

2 files

0.4.0

2 files

0.3.1

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

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