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.

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(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 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 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


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.0b1.tar.gz (126.5 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.0b1-py3-none-any.whl (100.9 kB view details)

Uploaded Python 3

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

Hashes for vaidcord-0.1.0b1.tar.gz
Algorithm Hash digest
SHA256 8db8666d82c8739b7a177fa3e1c42292630c11254cd436e7b5e680f3ccec295a
MD5 ba12ec7a9f64395af417a388638fe8f8
BLAKE2b-256 56c075b2121b7d2eb93ddcdbd3b7a1e11fd3d6d6fdfba451013404869c8900f3

See more details on using hashes here.

Provenance

The following attestation bundles were made for vaidcord-0.1.0b1.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.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

Hashes for vaidcord-0.1.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 205ea64bccc46ea1c800e56c83a86fdd9168087994a8610b2831c2a204177f40
MD5 6fb5e7a2f85fa3541651737e9b436d63
BLAKE2b-256 93a95ac2f29330b460a7c7ab7b03aba8831d7c68c32458f5248d49a037827716

See more details on using hashes here.

Provenance

The following attestation bundles were made for vaidcord-0.1.0b1-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