Skip to content

📁 Мультифайловость

Введение

По мере роста вашего модуля хранение всего кода в одном файле может стать обременительным. PBModular предоставляет класс ModuleExtension (base.mod_ext.ModuleExtension), чтобы помочь вам организовать ваш код в нескольких файлах, сохраняя при этом доступ к контексту основного модуля и беспрепятственно регистрируя обработчики.

INFO

Это отличается от BaseExtension (base.base_ext.BaseExtension), которое влияет на все модули при загрузке. ModuleExtension специфично для модуля, определяющего его.

Шаг 1: Создайте файл расширения

Создайте новый Python-файл в каталоге вашего модуля (подкаталог extensions/ является обычной практикой, но не обязателен). Определите в этом файле класс, который наследуется от ModuleExtension.

Пример (extensions/extra_handlers.py):

python
# extensions/extra_handlers.py
import logging
from base.mod_ext import ModuleExtension
from base.module import command, message, HelpPage, ModuleInfo # Импортируйте необходимые типы
from pyrogram import Client, filters
from pyrogram.types import Message, InlineKeyboardButton
from pyrogram.handlers import MessageHandler # Для пользовательских обработчиков

# При желании получите логгер, специфичный для этого файла расширения.
# Примечание: self.logger в методах будет ссылаться на логгер *главного* модуля
ext_logger = logging.getLogger(__name__)

class ExtraHandlersExtension(ModuleExtension):

    def on_init(self):
        """Пользовательская инициализация для этого расширения."""
        # Это выполняется *после* stage2 основного модуля и его on_init.
        # Доступ к унаследованным атрибутам:
        ext_logger.info(f"ExtraHandlersExtension initialized for module: {self.__base_mod.module_info.name}")
        ext_logger.info(f"Current language in main module: {self.cur_lang}")
        # self.bot, self.S, self.rawS, self.loader, self.db (если есть) доступны.
        # self.logger указывает на логгер главного модуля.

    @command("extra")
    async def extra_cmd(self, _: Client, message: Message):
        """Дополнительная команда, определенная в расширении."""
        # Используйте переводы из основного модуля
        reply_text = self.S["extra_command_reply"]
        await message.reply(reply_text.format(user=message.from_user.first_name))

    @message(filters.photo & filters.private)
    async def photo_handler(self, client: Client, message: Message):
        """Работа с фотографиями в приватном чате."""
        if self.db: # Проверьте, доступна ли БД
            # Пример взаимодействия с БД (требует настройки БД в главном модуле)
            # async with self.db.session_maker() as session:
            #     # ... сделать что-нибудь с базой данных ...
            #     pass
            await message.reply(self.S["photo_received_db_notice"])
        else:
            await message.reply(self.S["photo_received"])
        ext_logger.info(f"Получил фотографию от {message.from_user.id}")

    # Пример добавления пользовательского обработчика Pyrogram
    @property
    def custom_handlers(self) -> list[MessageHandler]:
        """Определите обработчики, которые не вписываются в стандартные декораторы."""
        # return [
        #     MessageHandler(self.raw_update_handler, filters.chat(config.log_chat))
        # ]
        return [] # Возвращаем пустой список, если пользовательские обработчики не нужны

    # async def raw_update_handler(self, client: Client, update):
    #     """Обработка специфических необработанных обновлений."""
    #     # Обработка необработанных обновлений
    #     ext_logger.debug(f"Raw update handler caught: {update}")
    #     pass

    # Пример доступа к состоянию основного модуля или методам, если это необходимо (используйте self.__base_mod)
    def get_main_module_version(self) -> str:
         return self.__base_mod.module_info.version

Шаг 2: Зарегистрируйте расширение в классе Главного Модуля

В классе вашего главного модуля (например, в main.py) переопределите свойство module_extensions, чтобы оно возвращало список, содержащий класс(ы) расширения.

python
# main.py (или там, где находится ваш основной класс BaseModule)

from base.module import BaseModule # Другие импорты...
from typing import Type # Тип импорта для подсказки

# Импортируйте класс расширения
from .extensions.extra_handlers import ExtraHandlersExtension

class MyModule(BaseModule):
    # ... другие свойства и методы ...

    @property
    def module_extensions(self) -> list[Type[ModuleExtension]]:
        """Register module extensions."""
        return [
            ExtraHandlersExtension,
            # Добавьте сюда другие классы расширения, если у вас их больше
        ]

    # ... остальная часть класса модуля ...

Как это работает

  1. Когда выполняется метод stage2 главного модуля, он перебирает классы, перечисленные в module_extensions.
  2. Для каждого класса расширения создается экземпляр, передавая экземпляр главного модуля (self) в __init__ расширения.
  3. Модуль расширения ModuleExtension.__init__ автоматически:
    • Наследует ключевые атрибуты (bot, S, rawS, cur_lang, loader, logger, state_machine, get_sm) от основного экземпляра модуля.
    • Сохраняет ссылку на экземпляр основного модуля как self.__base_mod.
    • Вызывает self.__base_mod.register_all(ext=self), который сканирует экземпляр расширения на наличие методов @command, @callback_query, @message и регистрирует их в Pyrogram, как и обработчики в основном классе.
    • Вызывает метод расширения on_init.
  4. Обработчики, определенные в расширении, теперь функционируют так, как если бы они были частью основного модуля, с доступом к его общим ресурсам (S, db и т.д.).

Это позволяет аккуратно разделить задачи, например, поместить все административные команды в один файл расширения, пользовательские команды - в другой, а логику фоновых задач - в третий, при этом разделяя настройку и состояние основного модуля.