Merge pull request 'feature/handlers - add base utils for handlers' (#4) from feature/handlers into develop

Reviewed-on: #4
This commit is contained in:
Kirill Samoylenkov 2025-10-21 00:06:30 +05:00
commit c9501b3a87
11 changed files with 191 additions and 4 deletions

View file

@ -1,12 +1,27 @@
__all__ = ["start_bot"]
from aiogram import Bot, Dispatcher
from redis.asyncio.client import Redis
from sqlalchemy.ext.asyncio import AsyncSession
from .handlers import connect_handlers, registry
from .middlewares import connect_middlewares
async def start_bot(
bot_token: str,
redis_client: Redis,
database_session: AsyncSession,
) -> None: ...
) -> None:
bot = Bot(bot_token)
dispatcher = Dispatcher()
connect_handlers(dispatcher)
connect_middlewares(dispatcher)
async def on_startup(bot: Bot):
await bot.delete_webhook(drop_pending_updates=True)
await registry.set_menu(bot)
await dispatcher.start_polling(bot, on_startup=on_startup)

View file

@ -1 +1,17 @@
__all__ = []
__all__ = ["connect_handlers", "registry"]
import importlib
from aiogram import Router
from .utils.registry import RouterRegistry
registry: RouterRegistry = RouterRegistry()
def connect_handlers(dispatcher: Router) -> None:
importlib.import_module("bot.handlers.callbacks")
importlib.import_module("bot.handlers.commands")
dispatcher.include_router(registry.router)

View file

@ -0,0 +1,36 @@
__all__ = ["ChatTypeFilter"]
from typing import Union
from aiogram.filters import BaseFilter
from aiogram.types import CallbackQuery, Message
from bot.handlers.utils.types import ChatType
class ChatTypeFilter(BaseFilter):
def __init__(
self,
chat_types: Union[ChatType, list[ChatType]],
) -> None:
if isinstance(chat_types, ChatType):
self.chat_type = [chat_types.value]
else:
self.chat_type = [chat.value for chat in chat_types]
async def __call__(
self,
event: Union[Message, CallbackQuery],
) -> bool:
current_chat_type: str
if isinstance(event, Message):
current_chat_type = event.chat.type
else:
if event.message is None:
return False
current_chat_type = event.message.chat.type
return current_chat_type in self.chat_type

View file

@ -0,0 +1,82 @@
__all__ = ["RouterRegistry"]
from typing import Any, Callable, Optional, Union
from aiogram import Bot, Router
from aiogram.filters import BaseFilter, Command
from aiogram.types.bot_command import BotCommand
from aiogram.types.bot_command_scope_all_private_chats import (
BotCommandScopeAllPrivateChats,
)
from bot.handlers.utils.filters import ChatTypeFilter
from bot.handlers.utils.types import (
ChatType,
HandlerMeta,
HandlerType,
)
class RouterRegistry:
def __init__(self) -> None:
self._router = Router()
self._handlers: list[HandlerMeta] = list()
@property
def router(self) -> Router:
return self._router
def register(
self,
*,
description: str,
chat_types: Union[ChatType, list[ChatType]],
filters: Union[BaseFilter, list[BaseFilter]] = [],
command: Optional[str] = None,
) -> Callable[[Any], HandlerType]:
if isinstance(chat_types, ChatType):
chat_types = [chat_types]
if isinstance(filters, BaseFilter):
filters = [filters]
def decorator(func: HandlerType) -> HandlerType:
meta = HandlerMeta(
func=func,
description=description,
chat_types=chat_types,
command=command,
)
self._handlers.append(meta)
if command is None:
self._router.callback_query.register(
func,
*filters,
ChatTypeFilter(chat_types=chat_types),
)
else:
self._router.message.register(
func,
*filters,
ChatTypeFilter(chat_types=chat_types),
Command(commands=[command]),
)
return func
return decorator
async def set_menu(self, bot: Bot) -> None:
await bot.set_my_commands(
[
BotCommand(
command=meta.command,
description=meta.description,
)
for meta in self._handlers
if meta.command is not None
and ChatType.PRIVATE in meta.chat_types
],
scope=BotCommandScopeAllPrivateChats(),
)

View file

@ -0,0 +1,5 @@
__all__ = ["ChatType", "HandlerType", "HandlerMeta"]
from .chat import ChatType
from .handler import HandlerMeta, HandlerType

View file

@ -0,0 +1,11 @@
from enum import Enum
class ChatType(Enum):
PRIVATE = "private"
GROUP = "group"
SUPERGROUP = "supergroup"
CHANNEL = "channel"
def __str__(self) -> str:
return self.value

View file

@ -0,0 +1,16 @@
from dataclasses import dataclass
from typing import Any, Awaitable, Callable, Optional, TypeVar
from bot.handlers.utils.types import ChatType
HandlerType = TypeVar(
"HandlerType", bound=Callable[[Any], Awaitable[Any]]
)
@dataclass
class HandlerMeta:
func: Callable[[Any], Awaitable[Any]]
description: str
chat_types: list[ChatType]
command: Optional[str] = None

View file

@ -1 +1,7 @@
__all__ = []
__all__ = ["connect_middlewares"]
from aiogram import Router
def connect_middlewares(dispatcher: Router): ...