{"id":26892343,"url":"https://github.com/mopeyjellyfish/rhubarb","last_synced_at":"2025-05-12T14:11:53.296Z","repository":{"id":171649900,"uuid":"424765705","full_name":"mopeyjellyfish/rhubarb","owner":"mopeyjellyfish","description":"An Event Bus library that simplifies interacting with multiple queue backends into a single API","archived":false,"fork":false,"pushed_at":"2024-03-15T14:57:31.000Z","size":1279,"stargazers_count":6,"open_issues_count":54,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-12T14:11:51.500Z","etag":null,"topics":["asyncio","eventbus","kafka","message-queue","postgres","python","rabbitmq","redis","websocket","websockets"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mopeyjellyfish.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-11-04T23:10:04.000Z","updated_at":"2025-03-05T23:24:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"ed89062a-5c70-42ca-a6b1-c5e5010604bb","html_url":"https://github.com/mopeyjellyfish/rhubarb","commit_stats":null,"previous_names":["mopeyjellyfish/rhubarb"],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mopeyjellyfish%2Frhubarb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mopeyjellyfish%2Frhubarb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mopeyjellyfish%2Frhubarb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mopeyjellyfish%2Frhubarb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mopeyjellyfish","download_url":"https://codeload.github.com/mopeyjellyfish/rhubarb/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253754219,"owners_count":21958842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncio","eventbus","kafka","message-queue","postgres","python","rabbitmq","redis","websocket","websockets"],"created_at":"2025-03-31T22:50:03.854Z","updated_at":"2025-05-12T14:11:53.288Z","avatar_url":"https://github.com/mopeyjellyfish.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rhubarb\n\n\u003cdiv align=\"center\"\u003e\n\n[![Release](https://img.shields.io/github/v/release/mopeyjellyfish/rhubarb)](https://img.shields.io/github/v/release/mopeyjellyfish/rhubarb)\n[![Build](https://github.com/mopeyjellyfish/rhubarb/workflows/build/badge.svg?branch=main\u0026event=push)](https://github.com/mopeyjellyfish/rhubarb/actions?query=workflow%3Abuild)\n[![Python Version](https://img.shields.io/pypi/pyversions/rhubarb-py.svg)](https://pypi.org/project/rhubarb-py)\n[![Dependencies Status](https://img.shields.io/badge/dependencies-up%20to%20date-brightgreen.svg)](https://github.com/mopeyjellyfish/rhubarb/pulls?utf8=%E2%9C%93\u0026q=is%3Apr%20author%3Aapp%2Fdependabot)\n[![codecov](https://codecov.io/gh/mopeyjellyfish/rhubarb/branch/main/graph/badge.svg?token=E8F5LMKDBK)](https://codecov.io/gh/mopeyjellyfish/rhubarb)\n[![Documentation Status](https://readthedocs.org/projects/rhubarb-py/badge/?version=latest)](https://rhubarb-py.readthedocs.io/en/latest/?badge=latest)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Security: bandit](https://img.shields.io/badge/security-bandit-green.svg)](https://github.com/PyCQA/bandit)\n[![Pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit\u0026logoColor=white)](https://github.com/mopeyjellyfish/rhubarb/blob/master/.pre-commit-config.yaml)\n[![Semantic Versions](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--versions-e10079.svg)](https://github.com/mopeyjellyfish/rhubarb/releases)\n[![Commit activity](https://img.shields.io/github/commit-activity/m/mopeyjellyfish/rhubarb)](https://img.shields.io/github/commit-activity/m/mopeyjellyfish/rhubarb)\n[![License](https://img.shields.io/github/license/mopeyjellyfish/rhubarb)](https://github.com/mopeyjellyfish/rhubarb/blob/master/LICENSE)\n\nRhubarb is a library that simplifies realtime streaming of events for a number of backends in to a single API. Currently supports [`Postgres`](https://github.com/MagicStack/asyncpg), [`kafka`](https://github.com/aio-libs/aiokafka), [`RabbitMQ`](https://github.com/mosquito/aio-pika), [`redis`](https://github.com/aio-libs/aioredis-py) as well as an internal memory backend useful for testing.\n\n\u003c/div\u003e\n\n## Installation\n\nThere are a number of backends that can be used with Rhubarb:\n\n| Kafka | Postgres | Redis | RabbitMQ |\n| --------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |--------------------------------------------------------------------------------- |\n| \u003cp align=\"center\"\u003e\u003cimg src=\"./README_assets/kafka.png\" width=\"60\" height=\"100\"/\u003e\u003c/p\u003e    | \u003cp align=\"center\"\u003e\u003cimg src=\"./README_assets/postgres.png\" width=\"100\" height=\"100\" /\u003e\u003c/p\u003e | \u003cp align=\"center\"\u003e\u003cimg src=\"./README_assets/redis.png\" width=\"100\" height=\"80\"/\u003e\u003c/p\u003e | \u003cp align=\"center\"\u003e\u003cimg src=\"./README_assets/rabbitmq.jpg\" width=\"100\" height=\"100\" /\u003e\u003c/p\u003e |\n| `pip install rhubarb-py[kafka]` | `pip install rhubarb-py[postgres]` | `pip install rhubarb-py[redis]` | `pip install rhubarb-py[rabbitmq]` |\n\n## Backends\n\n- `Rhubarb(\"redis://localhost:6379/0\")`\n- `Rhubarb(\"kafka://localhost:9092\")`\n- `Rhubarb(\"postgres://postgres:postgres@localhost:5432/rhubarb\")`\n- `Rhubarb(\"amqp://guest:guest@localhost/\")`\n- `Rhubarb(\"memory://\")`\n\n## Quick start\n\n### Simple event consumer\n\n```python\nasync with Rhubarb(\"redis://localhost:6379/0\") as events:\n    async with events.subscribe(channel=\"CHATROOM\") as subscriber:\n        async for event in subscriber:\n            await websocket.send_text(event.message)\n```\n\n### Simple event producer\n\n```python\nasync with Rhubarb(\"redis://localhost:6379/0\") as events:\n    await events.publish(\"test message\")\n```\n\n### History retrieval\n\n```python\nasync with Rhubarb(\"redis://localhost:6379/0\") as events:\n    async with events.subscribe(channel=\"CHATROOM\", history=10) as subscriber: # read the last 10 events published to the channel\n        async for event in subscriber:\n            await websocket.send_text(event.message)\n```\n\n### Custom serializer \u0026 deserializer\n\n```python\nasync with Rhubarb(\"redis://localhost:6379/0\", serializer=json.dumps, deserializer=json.loads) as events:\n    async with events.subscribe(channel=\"CHATROOM\", history=10) as subscriber: # read the last 10 events published to the channel\n        async for event in subscriber:\n            await websocket.send_text(event.message)\n```\n\n### Group subscribing (at-most-once processing)\n\n```python\nasync with Rhubarb(\"redis://localhost:6379/0\", serializer=json.dumps, deserializer=json.loads) as events:\n    async with events.subscribe(\n        \"TEST-GROUP-CHANNEL\", group_name=\"TEST_GROUP\", consumer_name=\"sub_1\"\n    ) as subscriber_1:\n        async for event in subscriber:\n            await process_job(event)\n```\n\n## Example\n\nA minimal working example can be found in [example](https://github.com/mopeyjellyfish/rhubarb/blob/main/example/app.py) directory.\n\n```python\nimport os\n\nfrom starlette.applications import Starlette\nfrom starlette.concurrency import run_until_first_complete\nfrom starlette.responses import HTMLResponse\nfrom starlette.routing import Route, WebSocketRoute\n\nfrom rhubarb import Rhubarb\n\nURL = os.environ.get(\"URL\", \"redis://localhost:6379/0\")\n\nevents = Rhubarb(URL)\n\nhtml = \"\"\"\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle\u003eChat\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003ch1\u003eWebSocket Chat\u003c/h1\u003e\n        \u003cform action=\"\" onsubmit=\"sendMessage(event)\"\u003e\n            \u003cinput type=\"text\" id=\"messageText\" autocomplete=\"off\"/\u003e\n            \u003cbutton\u003eSend\u003c/button\u003e\n        \u003c/form\u003e\n        \u003cul id='messages'\u003e\n        \u003c/ul\u003e\n        \u003cscript\u003e\n            var ws = new WebSocket(\"ws://localhost:8000/ws\");\n            ws.onmessage = function(event) {\n                var messages = document.getElementById('messages')\n                var message = document.createElement('li')\n                var content = document.createTextNode(event.data)\n                message.appendChild(content)\n                messages.appendChild(message)\n            };\n            function sendMessage(event) {\n                var input = document.getElementById(\"messageText\")\n                ws.send(input.value)\n                input.value = ''\n                event.preventDefault()\n            }\n        \u003c/script\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n\"\"\"\n\n\nasync def homepage(_):\n    return HTMLResponse(html)\n\n\nasync def room_consumer(websocket):\n    async for message in websocket.iter_text():\n        await events.publish(channel=\"chatroom\", message=message)\n\n\nasync def room_producer(websocket):\n    async with events.subscribe(channel=\"chatroom\") as subscriber:\n        async for event in subscriber:\n            await websocket.send_text(event.message)\n\n\nasync def ws(websocket):\n    await websocket.accept()\n    await run_until_first_complete(\n        (room_consumer, {\"websocket\": websocket}),\n        (room_producer, {\"websocket\": websocket}),\n    )\n\n\nroutes = [\n    Route(\"/\", homepage),\n    WebSocketRoute(\"/ws\", ws, name=\"chatroom_ws\"),\n]\n\n\napp = Starlette(\n    routes=routes,\n    on_startup=[events.connect],\n    on_shutdown=[events.disconnect],\n)\n```\n\n## 🛡 License\n\n[![License](https://img.shields.io/github/license/mopeyjellyfish/rhubarb)](https://github.com/mopeyjellyfish/rhubarb/blob/master/LICENSE)\n\nThis project is licensed under the terms of the `MIT` license. See [LICENSE](https://github.com/mopeyjellyfish/rhubarb/blob/master/LICENSE) for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmopeyjellyfish%2Frhubarb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmopeyjellyfish%2Frhubarb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmopeyjellyfish%2Frhubarb/lists"}