Skip to content

📁 Multifiling

Introduction

As your module grows, keeping all the code in a single file can become cumbersome. PBModular provides the ModuleExtension class (base.mod_ext.ModuleExtension) to help you organize your code into multiple files while maintaining access to the main module's context and seamlessly registering handlers.

INFO

This is distinct from BaseExtension (base.base_ext.BaseExtension), which affects all modules during loading. ModuleExtension is specific to the module defining it.

Step 1: Create an Extension File

Create a new Python file within your module's directory (a sub-directory like extensions/ is common practice but not mandatory). Define a class in this file that inherits from ModuleExtension.

Example (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 # Import necessary types
from pyrogram import Client, filters
from pyrogram.types import Message, InlineKeyboardButton
from pyrogram.handlers import MessageHandler # For custom handlers

# Get a logger specific to this extension file if desired
# Note: self.logger within methods will refer to the *main* module's logger
ext_logger = logging.getLogger(__name__)

class ExtraHandlersExtension(ModuleExtension):

    def on_init(self):
        """Custom initialization for this extension."""
        # This runs *after* the main module's stage2 and its on_init.
        # Access inherited attributes:
        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 (if available) are accessible.
        # self.logger points to the main module's logger.

    @command("extra")
    async def extra_cmd(self, _: Client, message: Message):
        """An extra command defined in the extension."""
        # Use translations from the main module
        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):
        """Handles photos in private chat."""
        if self.db: # Check if DB is available
            # Example DB interaction (requires db setup in main module)
            # async with self.db.session_maker() as session:
            #     # ... do something with the db ...
            #     pass
            await message.reply(self.S["photo_received_db_notice"])
        else:
            await message.reply(self.S["photo_received"])
        ext_logger.info(f"Received photo from {message.from_user.id}")

    # Example of adding a custom Pyrogram handler
    @property
    def custom_handlers(self) -> list[MessageHandler]:
        """Define handlers that don't fit standard decorators."""
        # return [
        #     MessageHandler(self.raw_update_handler, filters.chat(config.log_chat))
        # ]
        return [] # Return empty list if no custom handlers needed

    # async def raw_update_handler(self, client: Client, update):
    #     """Handles specific raw updates."""
    #     # Process the raw update
    #     ext_logger.debug(f"Raw update handler caught: {update}")
    #     pass

    # Example accessing main module state or methods if needed (use self.__base_mod)
    def get_main_module_version(self) -> str:
         return self.__base_mod.module_info.version

Step 2: Register the Extension in the Main Module Class

In your main module class (e.g., in main.py), override the module_extensions property to return a list containing your extension class(es).

python
# main.py (or wherever your main BaseModule class is)

from base.module import BaseModule # Other imports...
from typing import Type # Import Type for hinting

# Import your extension class
from .extensions.extra_handlers import ExtraHandlersExtension

class MyModule(BaseModule):
    # ... other properties and methods ...

    @property
    def module_extensions(self) -> list[Type[ModuleExtension]]:
        """Register module extensions."""
        return [
            ExtraHandlersExtension,
            # Add other extension classes here if you have more
        ]

    # ... rest of the module class ...

How it Works

  1. When the main module's stage2 method runs, it iterates through the classes listed in module_extensions.
  2. For each extension class, it creates an instance, passing the main module instance (self) to the extension's __init__.
  3. The ModuleExtension.__init__ automatically:
    • Inherits key attributes (bot, S, rawS, cur_lang, loader, logger, state_machine, get_sm) from the main module instance.
    • Stores a reference to the main module instance as self.__base_mod.
    • Crucially, calls self.__base_mod.register_all(ext=self), which scans the extension instance for methods decorated with @command, @callback_query, @message and registers them with Pyrogram, just like handlers in the main class.
    • Calls the extension's on_init method.
  4. Handlers defined in the extension now function as if they were part of the main module, with access to its shared resources (S, db, etc.).

This allows you to neatly separate concerns, like putting all administrative commands in one extension file, user commands in another, and background task logic elsewhere, all while sharing the core module's setup and state.