From 3171ce3acd0f7ae72c5829ce7b4dc62567b68f69 Mon Sep 17 00:00:00 2001 From: geekiot Date: Fri, 24 Oct 2025 16:19:18 +0500 Subject: [PATCH] Add [router registry]: add import modules method for router registry --- src/bot/handlers/utils/registry/__init__.py | 122 ++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/bot/handlers/utils/registry/__init__.py b/src/bot/handlers/utils/registry/__init__.py index 43c52a8..c66c19e 100644 --- a/src/bot/handlers/utils/registry/__init__.py +++ b/src/bot/handlers/utils/registry/__init__.py @@ -1,6 +1,8 @@ __all__ = ["RouterRegistry"] +import importlib +from pathlib import Path from typing import Any, Callable, Union from aiogram import Bot, F, Router @@ -9,6 +11,7 @@ from aiogram.types.bot_command import BotCommand from aiogram.types.bot_command_scope_all_private_chats import ( BotCommandScopeAllPrivateChats, ) +from loguru import logger as loguru_logger from bot.handlers.utils.filters import ChatTypeFilter from bot.handlers.utils.types import ( @@ -142,3 +145,122 @@ class RouterRegistry: ], scope=BotCommandScopeAllPrivateChats(), ) + + @staticmethod + def _get_modules_names( + modules_folder: Path, + package: str, + ) -> list[str]: + """ + Getting absolute Python modules names from the modules_folder. + + Args: + modules_folder (Path): + The folder containing the modules. + A relative path from the file that + calls this method should be used. + package (str): + The existing variable __package__ + from the file that calls this method. + + Warnings: + The attempt to import __init__.py + indicates something isn't right. + + Returns: + list[str]: Absolute module names for import. + """ + modules_names: list[str] = list() + + for file_path in modules_folder.rglob("*.py"): + if file_path.name == "__init__.py": + loguru_logger.warning( + "An attempt to load a path using RouterRegistry " + + "containing __init__.py " + + "indicates something isn't right. Skipping. " + + "modules_folder path: {} | ".format( + modules_folder.absolute().as_posix() + ) + + "package: {}".format(package) + ) + continue + + module_name = ( + package + + "." + + ( + file_path.with_suffix("") + .relative_to(modules_folder.parent) + .as_posix() + .replace("/", ".") + ) + ) + + modules_names.append(module_name) + + return modules_names + + def import_modules( + self, + modules_folder: Path, + package: str | None, + ) -> None: + """ + Importing modules from the folder with Python modules. + It is implied that the modules contain configurations + for RouterRegistry. + + For example, there may be register decorators contained there. + + Example: + ``` + registry: RouterRegistry = RouterRegistry() + + registry_dir = Path(__file__).parent + + registry.import_modules( + registry_dir / "commands", + __package__, + ) + + registry.import_modules( + registry_dir / "callbacks", + __package__, + ) + + dispatcher.include_router(registry.router) + ``` + + Args: + modules_folder (Path): + The folder containing the modules. + A relative path from the file that + calls this method should be used. + package (str | None): + The existing variable __package__ + from the file that calls this method. + + Raises: + ImportError: The package variable cannot be None. + """ + if package is None: + loguru_logger.error( + "It is required that the package be a Python path " + + "to the module from which the folder of modules " + + "needs to be loaded." + ) + raise ImportError("The package variable cannot be None.") + + registry_modules: list[str] = self._get_modules_names( + modules_folder=modules_folder, + package=package, + ) + + loguru_logger.debug( + "Trying to load these modules: " + str(registry_modules) + ) + + for module_name in registry_modules: + importlib.import_module(module_name) + + loguru_logger.debug("Modules loaded successfully.")