django-hibira
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:
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.