Skip to main content

django-hibira

django-hibira

ci

Live Django pages over Hibira, with no Django Channels, ASGI server or Redis to run.

Hibira is a hosted realtime service that speaks the Pusher Channels protocol. With this package your ordinary WSGI Django app renders HTML fragments as it already does and broadcasts them: every open page appends, replaces or removes them as they happen. It also signs your own private and presence channels, verifies webhooks and serves push notification tokens.

  • Model broadcasts: one decorator makes a list live on every page.
  • Live HTML: send any template fragment to any page, from views, Celery tasks, management commands or the shell.
  • Waits for the commit: nothing is sent for a rolled-back transaction.
  • Works with htmx (new fragments are processed) and Turbo 8 (refreshes morph).
  • No build step: one script tag, pusher-js included.

Supports Django 4.2, 5.1, 5.2, 6.0 and 6.1 on Python 3.10+.

Install

pip install django-hibira
# settings.py
INSTALLED_APPS = [
    # ...
    "django_hibira",
]

# urls.py
from django.urls import include, path

urlpatterns = [
    # ...
    path("hibira/", include("django_hibira.urls")),
]

Copy the app ID, key and secret from your app page on hibira.io/portal (the free plan needs no card) into the environment:

HIBIRA_APP_ID=your-app-id
HIBIRA_APP_KEY=your-app-key
HIBIRA_APP_SECRET=your-app-secret

The secret is shown only once, when you create the app; the app ID and key stay under Overview → Credentials. On the Free plan, allowed origins can't be *: add every origin your pages load from (http://localhost:8000 while developing, plus your production domain), or browsers are refused.

Then check them:

python manage.py hibira_check

Add the scripts to your base template's <head>:

{% load hibira %}
<head>
  ...
  {% hibira_scripts %}
</head>

Make a list live

# chat/models.py
from django.db import models
from django_hibira import broadcasts


@broadcasts(to="room", target="messages")
class Message(models.Model):
    room = models.ForeignKey(Room, on_delete=models.CASCADE, related_name="messages")
    body = models.TextField()
{# chat/_message.html: the root element carries the message's dom_id #}
{% load hibira %}
<li id="{% dom_id message %}">{{ message.body }}</li>
{# chat/room.html #}
{% load hibira %}
{% hibira_stream room %}
<ul id="messages">
  {% for message in room.messages.all %}{% include "chat/_message.html" %}{% endfor %}
</ul>

That's all. Creating a message renders chat/_message.html and appends it to #messages on every page showing that room; saving it again replaces #message_<pk>; deleting it removes the element. It works the same whether the row was saved by a view, the admin, a Celery task or manage.py shell.

broadcasts() options:

Option Default
to the model class The stream: an attribute path ("room", "room.workspace"), a callable taking the instance, or None
target plural model name (messages) Element ID new rows go into
insert "append" Or "prepend"
template <app>/_<model>.html The row partial
context_name model name (message) The instance's name in the partial
refresh False Send a page refresh instead of fragments
on create, update, delete Which changes to broadcast

@broadcasts(to="project", refresh=True) is the quickest way to make a page live: each change tells the open pages to fetch themselves again (several changes in a row become one fetch, the scroll position is kept, and a field someone is typing in is never thrown away).

Broadcast anything

import django_hibira

django_hibira.broadcast_append(room, target="messages",
                               template="chat/_message.html", context={"message": message})
django_hibira.broadcast_update(room, target="count", html=str(room.messages.count()))
django_hibira.broadcast_replace(order, target=order, template="orders/_status.html",
                                context={"order": order})
django_hibira.broadcast_remove(room, target="message_12")
django_hibira.broadcast_refresh(dashboard)

The actions are append, prepend, replace, update (inner HTML), before, after, remove and refresh. target is an element ID, or a model instance for its dom_id; targets=".unread-count" hits every element that matches a CSS selector. The fragment is html= or template= with context=.

A stream is anything pages can watch: a model instance (chat.room:5), a model class (chat.message), a string, or several together ({% hibira_stream room "typing" %}). Each stream maps to a private channel, and {% hibira_stream %} renders the stream name signed with your SECRET_KEY, so a browser can only watch streams your views rendered for it.

Broadcasts made inside a transaction are sent after it commits, and dropped if it rolls back (on_commit=False sends at once). Delivery errors are logged to the django_hibira logger and never break the request that saved the data.

Skip the sender

htmx requests carry the browser's socket ID. Pass it on, and the browser that made the change is left out (it usually swapped its own HTML already):

django_hibira.broadcast_append(room, target="messages", html=html,
                               socket_id=django_hibira.socket_id(request))

Appending or prepending an element whose ID is already in the list replaces it, so leaving socket_id out never shows a row twice.

In the browser

New fragments are processed by htmx when it is on the page, and Alpine.js picks them up on its own. hibira:before-swap (cancelable) and hibira:swapped fire on the target element, and hibira:refreshed on the document. <hibira-stream> gets a connected attribute while subscribed. Inline <script> tags in fragments don't run.

Your own channels and presence

window.Hibira.client() is the page's pusher-js client:

const channel = Hibira.client().subscribe("presence-room.1")
channel.bind("pusher:member_added", (member) => console.log(member.info.name))

Private and presence subscriptions are signed by POST /hibira/auth, which asks your function (CSRF protection applies, as on the rest of your site):

# settings.py
HIBIRA = {"AUTHORIZE_CHANNEL": "chat.realtime.authorize"}

# chat/realtime.py
def authorize(request, channel):
    if not request.user.is_authenticated:
        return False
    if channel.startswith("presence-room."):
        return {"user_id": request.user.pk, "user_info": {"name": request.user.get_full_name()}}
    return channel == f"private-user.{request.user.pk}"

Send plain events to them from Python:

django_hibira.trigger(f"private-user.{user.pk}", "invoice.paid", {"id": invoice.pk})

For anything else in the Python SDK (batch events, channel info, push, approvals, AI streaming), django_hibira.get_client() returns the configured hibira.Client.

Webhooks

Add https://your-site/hibira/webhooks on your app page → Webhooks. Each verified event runs your handlers (both the current and, during a secret rotation, the previous secret are accepted):

from django_hibira import on_webhook

@on_webhook("channel_vacated")
def room_emptied(event):
    Room.objects.filter(slug=event["channel"].removeprefix("presence-room.")).update(live=False)

The django_hibira.webhook_received signal carries every event too.

Push notifications

Set HIBIRA_PUSH_INSTANCE_ID and HIBIRA_PUSH_SECRET_KEY (app page → Push notifications), and GET /hibira/push/token?user_id=<pk> returns a user token for the signed-in user, for setUserId in the web, Android and iOS push SDKs. Send with django_hibira.get_client().push_to_users([...], {...}).

Settings

Everything goes in one optional HIBIRA dict. Connection settings fall back to environment variables.

Key Environment Default
APP_ID, KEY, SECRET HIBIRA_APP_ID, HIBIRA_APP_KEY, HIBIRA_APP_SECRET
HOST, PORT, USE_TLS HIBIRA_HOST, HIBIRA_PORT, HIBIRA_USE_TLS ws.hibira.io, 443, true
PUSH_INSTANCE_ID, PUSH_SECRET_KEY, PUSH_ENDPOINT HIBIRA_PUSH_*
AUTHORIZE_CHANNEL None (only streams can be subscribed to)
PUSH_USER_ID request.user.pk
ON_COMMIT True
OVERSIZE "refresh": a fragment over the plan's message size limit becomes a page refresh; or "log", "raise"
ENABLED on when the credentials are set; False turns sending off
CLIENT_OPTIONS {}, extra hibira.Client arguments such as timeout

Use the HOST shown on your app page if the app lives outside the default region.

Testing

from django_hibira.testing import capture_broadcasts

@pytest.mark.django_db(transaction=True)
def test_new_message_is_appended(room):
    with capture_broadcasts() as sent:
        Message.objects.create(room=room, body="hi")
    assert sent[0].action == "append"
    assert sent.for_stream(room)[0].html.count("hi") == 1

Inside capture_broadcasts() nothing is sent. Broadcasts wait for the commit, which Django's TestCase never does: use TransactionTestCase, self.captureOnCommitCallbacks(execute=True), or pytest-django's transaction=True / django_capture_on_commit_callbacks.

Example

example/ is a chat room with live messages and a "here now" presence list. Two browsers, light and dark:

Light browser Dark browser

cd example
pip install -e ..
export HIBIRA_APP_ID=... HIBIRA_APP_KEY=... HIBIRA_APP_SECRET=...
python manage.py migrate && python manage.py runserver

Development

pip install -e ".[test]"
pytest

中文摘要

django-hibira 讓一般的 Django(WSGI)專案不必架 Django Channels、ASGI 伺服器或 Redis,就能讓頁面即時更新。在 model 上加一行 @broadcasts(to="room", target="messages"),新增、修改、刪除資料時,所有開著該頁面的瀏覽器會自動插入、替換或移除對應的 HTML 片段;也可以用 broadcast_append 等函式從 view、Celery 任務或管理指令推送任何模板片段。廣播會等交易 commit 後才送出,並支援 htmx、Turbo 8、私有/presence 頻道授權、webhook 驗證與推播 token。到 hibira.io/portal 建立 app(免費方案不需信用卡),設定 HIBIRA_APP_ID、HIBIRA_APP_KEY、HIBIRA_APP_SECRET 即可開始。

License

MIT

Metadata

Release files for django-hibira 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 django-hibira 0.1.0
File Size Uploaded Provenance Actions
django_hibira-0.1.0.tar.gz 47.4 kB With provenance Details for django_hibira-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-hibira 0.1.0
File Interpreter ABI Platform Actions
django_hibira-0.1.0-py3-none-any.whl Python 3 none any Details for django_hibira-0.1.0-py3-none-any.whl

Total release size: 94.0 kB

Release files / django_hibira-0.1.0.tar.gz

Download URL django_hibira-0.1.0.tar.gz
Size 47.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5c5bea7a86b6b210d7c745769eebe7be12109b90eda9d8dcc931b5e0f81e77c3
BLAKE2b-256 checksum
How to use checksums
89dfe8836d205a0c0e79c9b7781a9694e86597e0ea7ead6cf301f695f41acf8a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

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

Download URL django_hibira-0.1.0-py3-none-any.whl
Size 46.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fd6e3051fb847b1d059af45c38ec95f343438d7f75a26fdd93f418932d0d6832
BLAKE2b-256 checksum
How to use checksums
85687c3f89f5bd483a6df27bfe7fff7d6bafee8eb8e6b09765501885906ab89a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 1, 2026.

Transparency log

Security for django-hibira 0.1.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_hibira-0.1.0.tar.gz

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 Nov 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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