Skip to content

engine.router_builder — Parametric router assembly

build_main_router

build_main_router(installed_features, module_prefix='', handler_module='handlers', extra_routers=None, router_name='main_router')

Assembles the main application router.

Creates a Router(name=router_name), includes routers from all features and additional routers (e.g., common_fsm_router).

Parameters:

Name Type Description Default
installed_features list[str]

List of paths to features.

required
module_prefix str

Module prefix (see collect_feature_routers).

''
handler_module str

Name of the module with the router (default is "handlers").

'handlers'
extra_routers list[Router] | None

Additional routers included after features (e.g., [common_fsm_router]).

None
router_name str

Name of the main router.

'main_router'

Returns:

Type Description
Router

The assembled Router.

Example
from codex_bot.engine.router_builder import build_main_router
from codex_bot.fsm import common_fsm_router

main_router = build_main_router(
    installed_features=settings.INSTALLED_FEATURES,
    module_prefix="myproject",
    extra_routers=[common_fsm_router],
)
dp.include_router(main_router)
Source code in src/codex_bot/engine/router_builder/router_builder.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def build_main_router(
    installed_features: list[str],
    module_prefix: str = "",
    handler_module: str = "handlers",
    extra_routers: list[Router] | None = None,
    router_name: str = "main_router",
) -> Router:
    """Assembles the main application router.

    Creates a ``Router(name=router_name)``, includes routers from all features
    and additional routers (e.g., ``common_fsm_router``).

    Args:
        installed_features: List of paths to features.
        module_prefix: Module prefix (see ``collect_feature_routers``).
        handler_module: Name of the module with the router (default is ``"handlers"``).
        extra_routers: Additional routers included after features
            (e.g., ``[common_fsm_router]``).
        router_name: Name of the main router.

    Returns:
        The assembled ``Router``.

    Example:
        ```python
        from codex_bot.engine.router_builder import build_main_router
        from codex_bot.fsm import common_fsm_router

        main_router = build_main_router(
            installed_features=settings.INSTALLED_FEATURES,
            module_prefix="myproject",
            extra_routers=[common_fsm_router],
        )
        dp.include_router(main_router)
        ```
    """
    main_router = Router(name=router_name)
    feature_routers = collect_feature_routers(
        installed_features=installed_features,
        module_prefix=module_prefix,
        handler_module=handler_module,
    )

    all_routers: list[Router] = feature_routers + (extra_routers or [])
    if all_routers:
        main_router.include_routers(*all_routers)

    log.info(f"RouterBuilder | UI features loaded={len(feature_routers)} extra={len(extra_routers or [])}")
    return main_router

collect_feature_routers

collect_feature_routers(installed_features, module_prefix='', handler_module='handlers')

Collects Aiogram routers from feature modules.

For each feature in installed_features, it attempts to import {module_prefix}.{feature_path}.{handler_module} and extract the router attribute from it.

Fail Fast: ImportError (no handlers file) — silent skip. Any other error (syntax, runtime) — exception propagates up, the bot does not start.

Parameters:

Name Type Description Default
installed_features list[str]

List of paths to features (e.g., ["features.telegram.booking", "features.telegram.profile"]).

required
module_prefix str

Module prefix (e.g., "myproject""myproject.features.telegram.booking.handlers"). Empty string — feature_path is used directly.

''
handler_module str

Name of the module with the router (default is "handlers").

'handlers'

Returns:

Type Description
list[Router]

List of found Router objects.

Raises:

Type Description
Exception

Any module loading error except ImportError (no file).

Example
routers = collect_feature_routers(
    installed_features=["features.telegram.booking", "features.telegram.profile"],
    module_prefix="myproject",
)
Source code in src/codex_bot/engine/router_builder/router_builder.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def collect_feature_routers(
    installed_features: list[str],
    module_prefix: str = "",
    handler_module: str = "handlers",
) -> list[Router]:
    """Collects Aiogram routers from feature modules.

    For each feature in ``installed_features``, it attempts to import
    ``{module_prefix}.{feature_path}.{handler_module}``
    and extract the ``router`` attribute from it.

    **Fail Fast:** ``ImportError`` (no handlers file) — silent skip.
    Any other error (syntax, runtime) — exception propagates up, the bot does not start.

    Args:
        installed_features: List of paths to features
            (e.g., ``["features.telegram.booking", "features.telegram.profile"]``).
        module_prefix: Module prefix
            (e.g., ``"myproject"`` → ``"myproject.features.telegram.booking.handlers"``).
            Empty string — ``feature_path`` is used directly.
        handler_module: Name of the module with the router (default is ``"handlers"``).

    Returns:
        List of found ``Router`` objects.

    Raises:
        Exception: Any module loading error except ``ImportError`` (no file).

    Example:
        ```python
        routers = collect_feature_routers(
            installed_features=["features.telegram.booking", "features.telegram.profile"],
            module_prefix="myproject",
        )
        ```
    """
    routers: list[Router] = []

    for feature_path in installed_features:
        module_path = (
            f"{module_prefix}.{feature_path}.{handler_module}" if module_prefix else f"{feature_path}.{handler_module}"
        )

        try:
            module = importlib.import_module(module_path)
            feature_router = getattr(module, "router", None)
            if feature_router and isinstance(feature_router, Router):
                routers.append(feature_router)
                log.info(f"RouterBuilder | feature='{feature_path}' status=loaded")
            else:
                log.debug(f"RouterBuilder | feature='{feature_path}' status=no_router_attr")
        except ImportError as e:
            if getattr(e, "name", None) == module_path:
                # No handlers.py file — feature without UI, this is normal
                log.debug(f"RouterBuilder | feature='{feature_path}' status=no_handlers_file")
            else:
                # handlers.py exists, but there's a broken import inside — fail
                log.critical(f"RouterBuilder | Broken import inside '{feature_path}': {e}")
                raise
        # SyntaxError, NameError, etc. — not caught, the application fails immediately

    return routers