High-performance Discord framework inspired by Aiogram 3.x architecture
Project description
VaidCord Python SDK (vaidcord)
VaidCord is a Python Discord SDK built around a small set of composable pieces: Bot, Dispatcher, Router, filters, middleware, and FSM.
Current package status: Beta (0.1.0b1).
Why it exists
- Predictable routing with feature-oriented routers.
- Hierarchical dependency injection.
- Filter-driven handlers that can also extract data.
- Middleware for cross-cutting behavior.
- Pluggable FSM storage for conversational workflows.
- Mock utilities for fast, deterministic tests.
Installation
uv add vaidcord
uv add "vaidcord[redis]"
uv add "vaidcord[mongo]"
uv add "vaidcord[postgres]"
Recommended entry points
- Start with examples/hello_echo_bot.py for the smallest runnable bot.
- Read docs/PYTHON_DRIVER.md for the architecture and runtime model.
- Browse examples/README.md for feature-focused examples.
- Use docs/OAUTH2.md and docs/APPLICATION_API.md when you need auth or application resources.
Mental model
Botis a facade/orchestrator that wires runtime + REST client + routers.GatewayRuntimeowns websocket lifecycle (connect/identify/heartbeat/dispatch loop).APIClientowns Discord REST calls and delegates HTTP details toHTTPClient.Dispatcheris the root router and runtime coordinator.Routergroups features into reusable modules.Middlewarewraps event handling.Filtersdecide whether a handler runs and can inject data.FSMstores scoped state for conversations and workflows.
Core patterns
- Use
dispatcher.provide("name", value)for global services. - Use
router.provide("name", value)for feature-local services. - Use
Ffor expressive filters likeF.message.content.startswith("/start"). - Use
@router.on_message_state(...)when a flow depends on FSM state. - Use
MockBotorMockDiscordServerfor deterministic tests.
Send DM to a user
Bot.send_dm(user_id, content, **kwargs) opens or reuses the DM channel via
POST /users/@me/channels, then sends the message with the same payload options
as send_message (embeds, components, etc.).
There is also an alias: send_message_to_user(...).
message = await bot.send_dm(
user_id=123456789012345678,
content="Hi from VaidCord!",
embeds=[{"title": "DM"}],
)
See runnable example: examples/send_dm_to_user.py.
Filter composition semantics
- Filter may return
boolordict. dictmeans: filter passed and payload is injected intoevent.context["filter_data"]and handler kwargs (by parameter name).A & B: both filters must pass; dict payloads from both sides are merged left-to-right.A | B: first passing filter wins; payload from that branch is used.
@router.on_message((F.message.content.startswith("!admin")) & my_role_filter)
async def admin_handler(event: Event, role: str | None = None):
...
Runtime modes
await dp.start_polling(bot)
await dp.start_websocket(bot)
await dp.start_webhook(bot, drop_pending_updates=True)
await dp.start_polling_many([bot_a, bot_b])
await dp.start_webhook_many([bot_a, bot_b], drop_pending_updates=True)
Dispatcher() auto-registers FSM middleware. If you do not pass storage, it uses in-memory storage by default.
Support status
| Area | Status | Notes |
|---|---|---|
| Gateway lifecycle | supported | Identify, heartbeat, reconnect-oriented runtime, typed common events. |
| Messages | supported | Send, reply, edit, delete, reactions, pins, polls, and typed message events. |
| Channels and threads | partial | Core channel, invite, permission overwrite, and thread helpers exist; keep checking Discord parity as new fields/routes land. |
| Guilds and users | partial | Common fetch/update/list helpers exist; advanced administration is still expanding. |
| Interactions | partial | Low-level interaction callback/follow-up helpers exist; high-level decorator command sync is planned. |
| OAuth2 | supported | URL, token, refresh, revoke, and userinfo helpers. |
| FSM | supported | Scoped storage with memory, SQLite, Redis, Mongo, and Postgres backends. |
| Mock server/testing | supported | Local mock server, mock bot, builders, and deterministic test helpers. |
| Multi-bot startup | experimental | start_polling_many and start_webhook_many are covered by regression tests, but large deployments should load-test their router/middleware mix. |
| Voice | planned | Voice event shortcuts exist; voice gateway, UDP, speaking state, and audio transport are not implemented yet. |
Performance and memory notes
- Prefer one
Dispatcherwith feature routers over duplicating large router trees per bot. - Use external FSM storage for long-lived or multi-process state; in-memory storage is fastest but process-local.
- Keep filters cheap on high-volume routes. Expensive I/O belongs in handlers or middleware after narrow filters pass.
- Avoid storing large Discord payloads in
event.context; pass IDs or compact service objects instead. - For benchmarks, measure event propagation with representative middleware/filter counts and HTTP concurrency with real route mixes.
- Contributors should avoid per-event introspection and avoid unnecessary object copies in parser, filter, and router hot paths.
Feature map
- docs/PYTHON_DRIVER.md - architecture, DI, filters, middleware, FSM, lifecycle
- docs/FILTERS.md - class filters, MagicFilter, MagicData, BotFilter, composition examples
- docs/MIDDLEWARE.md - outer/inner middleware model and FSM as system middleware
- docs/APPLICATION_API.md - Discord application resources and role connection metadata
- docs/OAUTH2.md - OAuth2 helpers and token workflows
- docs/PUBLISHING.md - beta versioning and PyPI trusted publishing workflow
- examples/README.md - quick index of runnable examples
Typing guide
Router handlers and middleware are typed via vaidcord.typing protocols:
EventHandler:async def handler(event: Event, **kwargs: Any) -> object | NoneMiddleware:async def middleware(event: Event, next_handler: NextHandler) -> object | NoneFilterDataMap: alias for filter payload (dict[str, Any]) injected into handler kwargs.AbstractEventHandler/AbstractMiddleware: ABC-based option for class-style architecture.DIEventCallable/DIWrapper: generic helpers (TypeVar,ParamSpec,Concatenate) for advanced wrappers.
from vaidcord.router import Router
from vaidcord.types import Event
router = Router()
@router.on_message()
async def echo(event: Event) -> None:
await event.message.answer("pong")
from vaidcord.filters import F
from vaidcord.router import Router
from vaidcord.types import Event
router = Router()
@router.on_message(F.message.content.startswith("/set "))
async def set_value(event: Event, matched_text: str) -> None:
# `matched_text` comes from filter-return payload
await event.message.answer(f"Got: {matched_text}")
from vaidcord.router import Router
from vaidcord.typing import NextHandler
from vaidcord.types import Event
router = Router()
@router.middleware()
async def trace(event: Event, next_handler: NextHandler):
print("before", event.type)
result = await next_handler(event)
print("after", event.type)
return result
from vaidcord.typing import AbstractEventHandler
from vaidcord.types import Event
class PingHandler(AbstractEventHandler[Event]):
async def __call__(self, event: Event, **kwargs: object) -> None:
await event.message.answer("pong")
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vaidcord-0.1.0b1.tar.gz.
File metadata
- Download URL: vaidcord-0.1.0b1.tar.gz
- Upload date:
- Size: 126.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8db8666d82c8739b7a177fa3e1c42292630c11254cd436e7b5e680f3ccec295a
|
|
| MD5 |
ba12ec7a9f64395af417a388638fe8f8
|
|
| BLAKE2b-256 |
56c075b2121b7d2eb93ddcdbd3b7a1e11fd3d6d6fdfba451013404869c8900f3
|
Provenance
The following attestation bundles were made for vaidcord-0.1.0b1.tar.gz:
Publisher:
publish-pypi.yml on Vadim-Khristenko/vaidcord
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vaidcord-0.1.0b1.tar.gz -
Subject digest:
8db8666d82c8739b7a177fa3e1c42292630c11254cd436e7b5e680f3ccec295a - Sigstore transparency entry: 1423463628
- Sigstore integration time:
-
Permalink:
Vadim-Khristenko/vaidcord@80e85f4310ba41c5ffcf198d9ea18baf3f638947 -
Branch / Tag:
refs/tags/v0.1.0b1 - Owner: https://github.com/Vadim-Khristenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@80e85f4310ba41c5ffcf198d9ea18baf3f638947 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vaidcord-0.1.0b1-py3-none-any.whl.
File metadata
- Download URL: vaidcord-0.1.0b1-py3-none-any.whl
- Upload date:
- Size: 100.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
205ea64bccc46ea1c800e56c83a86fdd9168087994a8610b2831c2a204177f40
|
|
| MD5 |
6fb5e7a2f85fa3541651737e9b436d63
|
|
| BLAKE2b-256 |
93a95ac2f29330b460a7c7ab7b03aba8831d7c68c32458f5248d49a037827716
|
Provenance
The following attestation bundles were made for vaidcord-0.1.0b1-py3-none-any.whl:
Publisher:
publish-pypi.yml on Vadim-Khristenko/vaidcord
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vaidcord-0.1.0b1-py3-none-any.whl -
Subject digest:
205ea64bccc46ea1c800e56c83a86fdd9168087994a8610b2831c2a204177f40 - Sigstore transparency entry: 1423463722
- Sigstore integration time:
-
Permalink:
Vadim-Khristenko/vaidcord@80e85f4310ba41c5ffcf198d9ea18baf3f638947 -
Branch / Tag:
refs/tags/v0.1.0b1 - Owner: https://github.com/Vadim-Khristenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@80e85f4310ba41c5ffcf198d9ea18baf3f638947 -
Trigger Event:
push
-
Statement type: