Coverage for src / puzzletree / cli / register.py: 84.85%
27 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-12 20:35 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-12 20:35 +0000
1"""Command registration module for dynamically discovering and registering CLI commands."""
3import importlib
4from pathlib import Path
6from typer import Typer
8from puzzletree.utils.logging import get_logger_console
11def _register_commands(app: Typer, path: Path | None = None) -> None:
12 """Dynamically discover and register CLI commands from submodules in the commands directory.
14 Scans all subdirectories in the commands directory and registers any that have an 'app'
15 attribute defined in their __init__.py file.
16 """
17 logger, _ = get_logger_console()
19 # Get the cli directory path
20 cli_dir = Path(__file__).parent / "commands" if path is None else path
22 # Find all subdirectories in the commands directory that have __init__.py
23 command_modules = [
24 d.name for d in cli_dir.iterdir() if d.is_dir() and (d / "__init__.py").exists() and d.name != "__pycache__"
25 ]
27 registered_commands = set()
29 # Import and register commands from each submodule
30 for module_name in command_modules:
31 logger.debug(f"Importing submodule: {module_name}")
33 try:
34 # Dynamically import the submodule (its __init__.py will be loaded)
35 module = importlib.import_module(f"puzzletree.cli.commands.{module_name}")
37 # Check if the module has an 'app' attribute
38 if not hasattr(module, "app"):
39 logger.debug(f"Submodule '{module_name}' does not have an 'app' attribute. Skipping.")
41 continue
43 # Get the app from the module
44 sub_command = module.app
46 if module_name in registered_commands: 46 ↛ 47line 46 didn't jump to line 47 because the condition on line 46 was never true
47 logger.warning(
48 f"Duplicate command name '{module_name}' found in submodule '{module_name}'. Skipping registration.",
49 )
50 continue
52 app.add_typer(sub_command, name=module_name)
53 registered_commands.add(module_name)
54 logger.debug(f"Registered command '{module_name}' from submodule '{module_name}'")
56 except ImportError as e:
57 logger.warning(f"Failed to import submodule '{module_name}': {e}. Skipping.")
58 except (AttributeError, TypeError, ValueError) as e:
59 logger.warning(f"Error processing submodule '{module_name}': {e}. Skipping.")