{"id":29504125,"url":"https://github.com/vvanglro/resumable-sse","last_synced_at":"2026-05-14T23:43:14.404Z","repository":{"id":304175201,"uuid":"1018046679","full_name":"vvanglro/resumable-sse","owner":"vvanglro","description":"Asynchronous recoverable SSE (Server-Sent Events) push toolkit, supporting Redis and in-memory backend.","archived":false,"fork":false,"pushed_at":"2025-07-22T08:55:30.000Z","size":26,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-22T10:42:45.900Z","etag":null,"topics":["fastapi","sse"],"latest_commit_sha":null,"homepage":"https://github.com/vvanglro/resumable-sse","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/vvanglro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-11T14:13:11.000Z","updated_at":"2025-07-11T14:36:50.000Z","dependencies_parsed_at":"2025-07-11T15:30:07.936Z","dependency_job_id":"1cec2bf7-d885-4363-b5f5-f7aa5335109d","html_url":"https://github.com/vvanglro/resumable-sse","commit_stats":null,"previous_names":["vvanglro/resumable-sse"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vvanglro/resumable-sse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvanglro%2Fresumable-sse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvanglro%2Fresumable-sse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvanglro%2Fresumable-sse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvanglro%2Fresumable-sse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vvanglro","download_url":"https://codeload.github.com/vvanglro/resumable-sse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvanglro%2Fresumable-sse/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267116181,"owners_count":24038623,"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","status":"online","status_checked_at":"2025-07-26T02:00:08.937Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["fastapi","sse"],"created_at":"2025-07-15T23:01:49.950Z","updated_at":"2026-05-14T23:43:09.359Z","avatar_url":"https://github.com/vvanglro.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# resumable-sse\n\n\u003e Asynchronous recoverable SSE (Server-Sent Events) push toolkit, supporting Redis and in-memory backend.\n\n## ✨ Features\n\n* ✅ Seamless integration with async generators (`async for`)\n* 🔁 Resumable message stream support with `Last-Event-ID`\n* 💾 Backend-agnostic: in-memory or Redis support\n* ⚙️ Clean abstraction, minimal API surface\n* 🧪 Easy to test, extendable for custom storage backends\n\n## 🤔 Why Use `resumable-sse`?\n\nIn AI chat or streaming applications, when a user refreshes the page or the network is interrupted, the ongoing Server-Sent Events (SSE) stream is lost. Without a recovery mechanism, the frontend has no way to continue receiving the previous stream, and the backend often has to **re-generate the entire response**, wasting time and compute.\n\n`resumable-sse` solves this by:\n\n* Caching every message chunk in a backend (in-memory or Redis)\n* Allowing clients to resume streaming from the last received event via `Last-Event-ID`\n* Decoupling message generation from delivery, so you don’t have to re-trigger expensive operations\n\n### 🧠 Use case example:\n\nYou’re building a web AI assistant. A user asks a long question. Halfway through the response, they accidentally refresh the browser. Instead of restarting the whole LLM generation, the backend resumes from where it left off and continues streaming seamlessly.\n\nThis avoids:\n\n* Unnecessary re-generation\n* Cost and latency from repeated model computation\n* Poor user experience\n\n## 📦 Installation\n\n```bash\npip install resumable-sse\n```\n\nOr from source:\n\n```bash\ngit clone https://github.com/vvanglro/resumable-sse.git\ncd resumable-sse\npip install -e .\n```\n\n## 🚀 Quick Start\n\nUsing with FastAPI:\n\n```python\nimport asyncio\nfrom fastapi import FastAPI, Request\nfrom sse_starlette.sse import EventSourceResponse\nfrom resumable_sse.factory import get_streamer\n\napp = FastAPI()\nstreamer = get_streamer(backend=\"memory\")\n\nasync def fake_generator():\n    for word in [\"Hello,\", \"I'm an AI.\", \"Nice to meet you.\"]:\n        yield word\n        await asyncio.sleep(1)\n\n@app.get(\"/stream\")\nasync def stream(request: Request, session_id: str):\n    return EventSourceResponse(\n        streamer.stream(session_id=session_id, generator=fake_generator())\n    )\n```\n\nSupports resume with:\n\n```http\nGET /stream?session_id=abc\n```\n\n## 🔌 Redis Backend Example\n\n```python\nimport redis.asyncio as redis\nfrom resumable_sse.factory import get_streamer\n\nredis_client = redis.Redis()\nstreamer = get_streamer(backend=\"redis\", redis_client=redis_client)\n```\n\n## 📘 API Reference\n\n```python\nasync def stream(\n    self,\n    session_id: str,\n    generator: ContentStream,\n    last_id: str = \"0\",\n) -\u003e AsyncGenerator[dict, None]:\n```\n\n* `session_id`: Unique ID per conversation or stream session\n* `last_id`: Resume position (used with `Last-Event-ID`)\n* `generator`: Async generator function that yields message chunks\n\n## 🧪 Run Tests\n\n```bash\npytest tests/\n```\n\n## 📁 Project Structure\n\n```\nresumable_sse/\n├── base.py           # Abstract base class\n├── memory.py         # In-memory implementation\n├── redis.py          # Redis implementation\n├── factory.py        # Factory for backend selection\n├── __init__.py\nREADME.md\npyproject.toml\n```\n\n## 📄 License\n\nMIT License\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvvanglro%2Fresumable-sse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvvanglro%2Fresumable-sse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvvanglro%2Fresumable-sse/lists"}