Skip to main content

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

  1. Start with examples/hello_echo_bot.py for the smallest runnable bot.
  2. Read docs/PYTHON_DRIVER.md for the architecture and runtime model.
  3. Browse examples/README.md for feature-focused examples.
  4. Use docs/OAUTH2.md and docs/APPLICATION_API.md when you need auth or application resources.

Mental model

  • Bot is a facade/orchestrator that wires runtime + REST client + routers.
  • GatewayRuntime owns websocket lifecycle (connect/identify/heartbeat/dispatch loop).
  • APIClient owns Discord REST calls and delegates HTTP details to HTTPClient.
  • Dispatcher is the root router and runtime coordinator.
  • Router groups features into reusable modules.
  • Middleware wraps event handling.
  • Filters decide whether a handler runs and can inject data.
  • FSM stores 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 F for expressive filters like F.message.content.startswith("/start").
  • Use @router.on_message_state(...) when a flow depends on FSM state.
  • Use MockBot or MockDiscordServer for deterministic tests.

Sending messages

Use message-bound helpers inside message handlers:

@router.on_message()
async def echo(message: Message) -> None:
    await message.answer("pong")
    await message.reply("reply pong", mention_author=False)

Use Bot.send_message(channel_id, content, **kwargs) when you only have a channel id or when sending from service code outside a message handler.

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 bool or dict.
  • dict means: filter passed and payload is injected into event.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(message: Message, 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 Dispatcher with 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

Typing guide

Router handlers and middleware are typed via vaidcord.typing protocols:

  • EventHandler: async def handler(event: Event, **kwargs: Any) -> object | None
  • Middleware: async def middleware(event: Event, next_handler: NextHandler) -> object | None
  • FilterDataMap: 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 Message

router = Router()

@router.on_message()
async def echo(message: Message) -> None:
    await message.answer("pong")
from vaidcord.filters import F
from vaidcord.router import Router
from vaidcord.types import Message

router = Router()

@router.on_message(F.message.content.startswith("/set "))
async def set_value(message: Message, matched_text: str) -> None:
    # `matched_text` comes from filter-return payload
    await 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 Message

class PingHandler(AbstractEventHandler[Message]):
    async def __call__(self, message: Message, **kwargs: object) -> None:
        await message.answer("pong")

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vaidcord-0.1.0b2.tar.gz (127.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vaidcord-0.1.0b2-py3-none-any.whl (101.4 kB view details)

Uploaded Python 3

File details

Details for the file vaidcord-0.1.0b2.tar.gz.

File metadata

  • Download URL: vaidcord-0.1.0b2.tar.gz
  • Upload date:
  • Size: 127.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for vaidcord-0.1.0b2.tar.gz
Algorithm Hash digest
SHA256 c7d7b87b7d38fb70cad84a0fac228764cea1c03faf9481cf3aa3bc98a3e717a9
MD5 9f55fc39da1d3317371c4b1271ddc05d
BLAKE2b-256 fdbac04c11f4090acc89edb609dbdb013c72c5aadd7177f7afb5f9feffa254ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for vaidcord-0.1.0b2.tar.gz:

Publisher: publish-pypi.yml on Vadim-Khristenko/vaidcord

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vaidcord-0.1.0b2-py3-none-any.whl.

File metadata

  • Download URL: vaidcord-0.1.0b2-py3-none-any.whl
  • Upload date:
  • Size: 101.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for vaidcord-0.1.0b2-py3-none-any.whl
Algorithm Hash digest
SHA256 17635916ba13accbb8517409157f9671f3633743c32d45a2d01575c00d96e488
MD5 1841f77fbad5867b7bd68b3506b19541
BLAKE2b-256 d3502a7e3494a77a3af0a79718be8c09fb6510ce4a8a8e3319ca15ec755637da

See more details on using hashes here.

Provenance

The following attestation bundles were made for vaidcord-0.1.0b2-py3-none-any.whl:

Publisher: publish-pypi.yml on Vadim-Khristenko/vaidcord

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page