Skip to main content

Async VK bot framework with FastAPI-style decorators and aiogram-style FSM

Project description

Async VK bot framework with FastAPI-style decorators and aiogram-style FSM.

Package version Supported Python versions GitHub Stars License


Source Code: https://github.com/ndugram/fastvk


FastVK is a modern async VK bot framework for Python. It brings a decorator-based handler API — similar to FastAPI and aiogram, but for VK — with FSM, middleware, filters, and clean dependency injection out of the box.

Key features:

  • Familiar — if you know FastAPI or aiogram, you already know FastVK. Same patterns, same ergonomics.
  • Async — built on aiohttp with full async/await support from top to bottom.
  • FSM — built-in Finite State Machine with State, StatesGroup, and pluggable storage backends.
  • FiltersCommand, Text, StateFilter, FromUser, IsChat and custom filters via any callable.
  • Injection — handler parameters injected by name: message, state, api, update — no manual wiring.
  • Routers — split handlers across multiple Router instances, include them into the main bot.
  • Middleware — intercept every update before and after handlers with BaseMiddleware.
  • Typed — full type annotations throughout; works great with mypy and pyright.

Requirements

Python 3.10+

FastVK depends on:

  • aiohttp — async HTTP transport for Long Poll and VK API calls.
  • annotated-docDoc() annotations for rich parameter documentation.

Installation

$ pip install fastvk

---> 100%

Example

Create it

Create a file main.py:

from fastvk import FastVK
from fastvk.filters import Command
from fastvk.types import Message

bot = FastVK(token="vk1.a.YOUR_TOKEN", group_id=123456789)


@bot.message(Command("start"))
async def start(message: Message) -> None:
    await message.answer("Привет! Я FastVK бот 🤖")


if __name__ == "__main__":
    bot.run_polling()

Run it

$ python main.py

Check it

You will see output like:

INFO  fastvk  FastVK started (group_id=123456789)
INFO  fastvk  Polling started

Send /start to your bot — it replies instantly.

Upgrade the example

With FSM (multi-step forms)...

Use StatesGroup and State to collect data across multiple messages:

from fastvk import FastVK
from fastvk.filters import Command, StateFilter
from fastvk.fsm import FSMContext, State, StatesGroup
from fastvk.types import Message

bot = FastVK(token="vk1.a.YOUR_TOKEN", group_id=123456789)


class RegistrationForm(StatesGroup):
    waiting_name = State()
    waiting_age  = State()


@bot.message(Command("start"))
async def cmd_start(message: Message, state: FSMContext) -> None:
    await state.set_state(RegistrationForm.waiting_name)
    await message.answer("Как тебя зовут?")


@bot.message(StateFilter(RegistrationForm.waiting_name))
async def got_name(message: Message, state: FSMContext) -> None:
    await state.update_data(name=message.text)
    await state.set_state(RegistrationForm.waiting_age)
    await message.answer(f"Отлично, {message.text}! Сколько тебе лет?")


@bot.message(StateFilter(RegistrationForm.waiting_age))
async def got_age(message: Message, state: FSMContext) -> None:
    data = await state.update_data(age=message.text)
    await state.clear()
    await message.answer(
        f"Готово!\nИмя: {data['name']}\nВозраст: {data['age']}"
    )


if __name__ == "__main__":
    bot.run_polling()
With routers...

Split handlers into separate modules and include them into the bot:

from fastvk import FastVK, Router
from fastvk.filters import Command, Text
from fastvk.types import Message

shop_router = Router()


@shop_router.message(Command("catalog"))
async def catalog(message: Message) -> None:
    await message.answer("📦 Наш каталог: ...")


@shop_router.message(Text("цена", contains=True, ignore_case=True))
async def price_mention(message: Message) -> None:
    await message.answer("Цены начинаются от 99₽")


bot = FastVK(token="vk1.a.YOUR_TOKEN", group_id=123456789)
bot.include_router(shop_router)

if __name__ == "__main__":
    bot.run_polling()
With middleware...

Intercept every incoming update to add logging, rate limiting, or custom data:

from collections.abc import Awaitable, Callable
from typing import Any

from fastvk import FastVK
from fastvk.middleware import BaseMiddleware
from fastvk.filters import Command
from fastvk.types import Message


class LoggingMiddleware(BaseMiddleware):
    async def __call__(
        self,
        handler: Callable[[Any, dict], Awaitable[Any]],
        event: Any,
        data: dict,
    ) -> Any:
        print(f"→ incoming: {type(event).__name__}")
        result = await handler(event, data)
        print(f"← handled")
        return result


bot = FastVK(
    token="vk1.a.YOUR_TOKEN",
    group_id=123456789,
    middleware=[LoggingMiddleware()],
)


@bot.message(Command("ping"))
async def ping(message: Message) -> None:
    await message.answer("pong")


if __name__ == "__main__":
    bot.run_polling()
With filters...

Combine built-in and custom filters on any handler:

from fastvk import FastVK
from fastvk.filters import Command, FromUser, IsChat, Text
from fastvk.types import Message

ADMIN_ID = 123456789

bot = FastVK(token="vk1.a.YOUR_TOKEN", group_id=987654321)


@bot.message(Command("ban"), FromUser(ADMIN_ID))
async def admin_ban(message: Message) -> None:
    await message.answer("Пользователь заблокирован.")


@bot.message(IsChat("private"), Text("помощь", contains=True, ignore_case=True))
async def help_in_pm(message: Message) -> None:
    await message.answer("Список команд: /start, /help")


def is_long_message(message: Message, data: dict) -> bool:
    return len(message.text or "") > 200


@bot.message(is_long_message)
async def long_message(message: Message) -> None:
    await message.answer("Это очень длинное сообщение!")


if __name__ == "__main__":
    bot.run_polling()
With raw VK API calls...

Access the full VK API via the injected api parameter:

from fastvk import FastVK
from fastvk.api import APIClient
from fastvk.filters import Command
from fastvk.types import Message

bot = FastVK(token="vk1.a.YOUR_TOKEN", group_id=123456789)


@bot.message(Command("me"))
async def cmd_me(message: Message, api: APIClient) -> None:
    users = await api.users.get(user_ids=message.from_id, fields="photo_200")
    user = users[0]
    await message.answer(f"Ты: {user['first_name']} {user['last_name']}")


@bot.message(Command("members"))
async def cmd_members(message: Message, api: APIClient) -> None:
    data = await api.groups.getMembers(group_id=bot.group_id, count=1)
    await message.answer(f"Участников в группе: {data['count']}")


if __name__ == "__main__":
    bot.run_polling()
With event handlers...

Handle any VK event type — not just messages:

from fastvk import FastVK
from fastvk.api import APIClient
from fastvk.types import Update

bot = FastVK(token="vk1.a.YOUR_TOKEN", group_id=123456789)


@bot.group_join()
async def on_join(event: dict, api: APIClient) -> None:
    user_id = event.get("user_id")
    await api.messages.send(
        peer_id=user_id,
        message="Добро пожаловать в группу!",
        random_id=0,
    )


@bot.wall_post_new()
async def on_new_post(event: dict) -> None:
    print(f"Новый пост: {event.get('id')}")


@bot.on("photo_new")
async def on_photo(update: Update) -> None:
    print(f"Новое фото: {update.object}")


if __name__ == "__main__":
    bot.run_polling()

Dependency injection

Handler parameters are resolved automatically by name (with type-based fallback). No manual wiring needed:

Parameter Type Description
message Message Parsed message object (for message_new events)
state FSMContext FSM context for current user
api APIClient VK API client
event dict Raw event payload
update Update Full update object
@router.message()
async def handler(
    message: Message,
    state: FSMContext,
    api: APIClient,
) -> None:
    ...

Contributing

Contributions are welcome! Please open an issue before submitting a pull request.

Found a bug? Open an issue on GitHub.

License

This project is licensed under the terms of the MIT license.

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

fastvk-0.0.1.tar.gz (20.4 kB view details)

Uploaded Source

Built Distribution

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

fastvk-0.0.1-py3-none-any.whl (22.5 kB view details)

Uploaded Python 3

File details

Details for the file fastvk-0.0.1.tar.gz.

File metadata

  • Download URL: fastvk-0.0.1.tar.gz
  • Upload date:
  • Size: 20.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for fastvk-0.0.1.tar.gz
Algorithm Hash digest
SHA256 299fbc82b66befc50f626b84dd823c8c2619cb6f7d95c7dd63a99427c1c0f82d
MD5 60f48b80b9a0f7f4dca5e400ccd6bc4a
BLAKE2b-256 28c63f647dc0a0ca84610ab0056e7eec6408f76b211fce45da37128383192b1a

See more details on using hashes here.

File details

Details for the file fastvk-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: fastvk-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 22.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for fastvk-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c92188680f5354c2a6ffceac2f5850dad3651eeb95a9aa40e35b050ebf3c3bda
MD5 3802fdb03fb9c0fa75191450ddbdb3c8
BLAKE2b-256 9f86b582d14750e09c5b3639165d51cc08761bfeb5bb6dea63730e0e36b660e1

See more details on using hashes here.

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