SwiftBot
SwiftBot is a fast, async-first Telegram bot framework built for simplicity and correctness. One decorator registers a handler, a rich Context object does the talking, and the framework quietly handles everything else — HTTP/2 connection pooling, Retry-After compliance, persistent state storage, and a fully typed error hierarchy. Everything is typed, everything is optional, and you never need an external database to get started.
Telegram Bot API compatibility: SwiftBot is compatible with Telegram Bot API 10.2.
pip install swiftbot
The built-in webhook server is optional. Polling does not require aiohttp; install the webhook extra only when you need webhook mode:
pip install "swiftbot[webhook]"
Why SwiftBot
SwiftBot is a strong fit for asynchronous, route-heavy bots where local dispatch speed, predictable resource use, typed APIs, and operational controls matter. The checked-in benchmark snapshot measured SwiftBot at 19,803 updates per second and 50.5 microseconds per update through its public raw-update test path for a controlled ten-route offline workload on Python 3.12.3. Every framework routed all expected handler calls correctly; these measurements exclude Telegram network latency, databases, business logic, and application-specific middleware.
| Advantage | What it means in practice | Evidence in this repository |
|---|---|---|
| Fast local dispatch | More handler work can be processed per process before application logic or network I/O becomes the bottleneck. | In the corrected public raw-update comparison, SwiftBot measured 1.91× pyTelegramBotAPI, 2.10× python-telegram-bot, and 16.24× aiogram for this workload. These are versioned public-path observations, not universal production claims. |
| Low measured framework footprint | Smaller framework distributions and lower measured peak RSS can help constrained deployments, while dependency footprints still need to be evaluated for the full application. | The corrected public-path snapshot measured 33.2 MiB peak RSS for SwiftBot; package and dependency footprints should be evaluated separately. |
| Useful async concurrency | I/O-like handlers can scale with bounded worker concurrency rather than relying on unbounded task creation. | The worker-pool benchmark reached 2,804 completed updates per second at eight workers versus 438 at one worker, with all 400 updates completed in every repeat. |
| Operational safeguards | HTTP/2 pooling, retry-after handling, bounded backpressure, dead-letter handling, and typed Telegram errors reduce the amount of reliability plumbing bot authors must build themselves. | These are implemented in the connection pool, worker pool, exception hierarchy, and webhook server. |
| Network-free testing | Handlers can be exercised without Telegram credentials or network calls, with outgoing API requests captured for assertions. | The built-in TestClient harness and the repository’s 212 passing tests provide the local testing path; 10 live-environment tests remain skipped without credentials. |
The benchmark results are controlled local measurements, not a universal claim that SwiftBot is faster for every production bot. Framework choice should still account for ecosystem maturity, compatibility requirements, workload shape, network latency, and a project-specific load test. See the complete benchmark report and reproduction guide for methods, charts, raw results, and caveats.
Quick Start
import asyncio
from swiftbot import SwiftBot, Filters as F
from swiftbot.types import Message
bot = SwiftBot(token="YOUR_BOT_TOKEN")
@bot.on(Message(text="hello"))
async def hello(ctx):
await ctx.reply("Hi there!")
@bot.on(Message(filters=F.command("start")))
async def start(ctx):
await ctx.reply("Welcome! Send me hello.")
asyncio.run(bot.run())
That is the whole bot. No router setup, no dispatch table, no middleware boilerplate.
Core Concepts
Handlers and Filters
Handlers are registered with @bot.on(Message(...)) and the first matching handler wins. Filters are composable — combine commands, regex patterns, and predicates:
@bot.on(Message(filters=F.command("ban") & F.chat(-100123456789)))
async def ban(ctx):
...
@bot.on(Message(pattern=r"^/weather (\w+)"))
async def weather(ctx):
city = ctx.match.group(1)
await ctx.reply(f"Weather in {city}: sunny")
Context
Every handler receives a Context with typed access to the update, plus shortcuts that cover most use cases: ctx.reply(), ctx.edit(), ctx.delete(), ctx.answer_callback(), and ctx.forward_to().
Buttons and Callbacks
from swiftbot import InlineKeyboard, Button, CallbackData
# Declare the payload shape once
page = CallbackData("page", int)
# Attach packed payloads to buttons
kb = InlineKeyboard([]).add_row(
Button.inline("1", page.pack(1)),
Button.inline("2", page.pack(2)),
)
await ctx.reply("Pick a page:", reply_markup=kb.to_dict())
# Filter on valid payloads and decode the typed data
@bot.on(CallbackQuery(pattern=rf"^{page.prefix}:[^:]+:[^:]+$"))
async def on_page(ctx):
page_num = page.unpack(ctx.callback_query.data)[0]
await ctx.answer_callback(f"Page {page_num}")
State and Dialogues
State survives restarts with pluggable storage backends — in-memory, JSON file, or Redis — and the dialogue system models multi-step conversations as declared transition graphs with per-state timeouts:
survey = bot.dialogue("survey")
@survey.state("ask_name", next=["ask_age"])
async def ask_name(ctx, prev=None):
await ctx.reply("What's your name?")
return Dialogue.next("ask_age", carry=ctx.text)
@survey.state("ask_age", timeout=120.0)
async def ask_age(ctx, prev=None):
await ctx.reply(f"Hi {prev}, how old are you?")
return Dialogue.end
Middleware, Pipelines and Scopes
Middleware runs around every handler, and the Composer bundles middleware chains with error boundaries. Pipeline adds dependency injection so handlers stay pure and testable, while scope() attaches middleware to only the updates matching a predicate.
from swiftbot.middleware import Logger, RateLimiter
from swiftbot.pipeline import Pipeline
bot.use(Logger())
bot.use(RateLimiter(rate=20, per=60))
pipe = Pipeline().deps(db=my_db)
async def stats(ctx, db):
await ctx.reply(f"{await db.count()} users")
pipe.handle(F.command("stats"), stats)
bot.pipeline(pipe)
bot.scope(F.private).use(RateLimiter(rate=1, per=1.0))
Safety Nets
Fallback handlers catch anything that no other handler matched, and outbound throttling keeps your bot under Telegram's limits automatically:
@bot.fallback
async def catch_all(ctx):
await ctx.reply("Try /help.")
bot.api.config.use(throttle(max_per_second=20.0, per_chat=4.0))
Testing Without Telegram
The built-in harness runs your real handlers with zero network calls — every outgoing API request is captured and every response is scriptable:
from swiftbot.testing import TestClient
async with TestClient(bot) as client:
await client.send_update({
"update_id": 1,
"message": {
"message_id": 1, "date": 1,
"chat": {"id": 1, "type": "private"},
"from": {"id": 2, "is_bot": False, "first_name": "A"},
"text": "hello",
},
})
assert client.outgoing[0]["method"] == "sendMessage"
More
| Topic | What you get |
|---|---|
| Commands | Declarative command specs with auto-generated /help |
| Transformers | Outbound API layer with auto-typing and recording |
| Deep Linking | Typed ?start= payloads for referrals and auth |
| Webhooks | aiohttp server with secret-token verification and metrics |
| Storage | In-memory, JSON-file, and Redis FSM backends |
| Exceptions | Typed ChatNotFound, TooManyRequests, Forbidden, ... |
| Benchmarks | Reproducible dispatch, memory, worker-pool, backpressure, and read-only Telegram smoke tests with reports and graphs |
The complete documentation lives in the docs/ folder. The complete benchmark report and reproduction guide are in tests/benchmark/README.md.
Contributing
Contributions are welcome — open an issue or submit a pull request.
License
MIT — Copyright (c) 2026 Arjun-M/SwiftBot
Metadata
Release files for swiftbot 1.6.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| swiftbot-1.6.4.tar.gz | 101.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| swiftbot-1.6.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 219.1 kB
Release files / swiftbot-1.6.4.tar.gz
| Download URL | swiftbot-1.6.4.tar.gz |
|---|---|
| Size | 101.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
418ff1be637d6a9330209b344cf4e2e2c9f374583d0f5ec71f9c810c024834c9
|
|
BLAKE2b-256 checksum How to use checksums |
b1f3d722ec1f804d0b4fa92ffcfe8fd924af55ac2ea51c83a7b9e1ac99fcb854
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / swiftbot-1.6.4-py3-none-any.whl
| Download URL | swiftbot-1.6.4-py3-none-any.whl |
|---|---|
| Size | 117.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
93589c3e77817b84a8f839a897d03fcf35d135a989d46feb655642061cc106b6
|
|
BLAKE2b-256 checksum How to use checksums |
d512f0da9a07670f164e13247b3b4a3d0cdecf95351c31ed3630a9c3832abb66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|