Skip to content

Instantly share code, notes, and snippets.

@danielomiya
Last active September 4, 2025 14:02
Show Gist options
  • Select an option

  • Save danielomiya/6a9c098c7094eefd1651eef2d967d3af to your computer and use it in GitHub Desktop.

Select an option

Save danielomiya/6a9c098c7094eefd1651eef2d967d3af to your computer and use it in GitHub Desktop.
Typer CLI app with lazy loading of subcommands
from __future__ import annotations
import importlib
from typing import TYPE_CHECKING
import typer
from typer.core import TyperGroup
if TYPE_CHECKING:
from click import Context
from click.core import Command
LAZY_SUBCOMMANDS = {
"db": "my_package.cli.commands.db_command",
"www": "my_package.cli.commands.www_command",
}
class LazyGroup(TyperGroup):
"""A TyperGroup that allows for lazy loading of subcommands."""
def __init__(
self,
*args,
lazy_subcommands: dict[str, str] | None = None,
**kwargs,
) -> None:
"""Initialize the LazyGroup."""
super().__init__(*args, **kwargs)
self.lazy_subcommands = lazy_subcommands or LAZY_SUBCOMMANDS
def list_commands(self, ctx: Context) -> list[str]:
base = super().list_commands(ctx)
lazy = sorted(self.lazy_subcommands.keys())
return base + lazy
def get_command(self, ctx: Context, cmd_name: str) -> Command | None:
if cmd_name in self.lazy_subcommands:
cmd = self._lazy_load(cmd_name)
cmd.info.name = cmd_name
return typer.main.get_command(cmd)
return super().get_command(ctx, cmd_name)
def _lazy_load(self, cmd_name: str) -> typer.Typer:
module_name = self.lazy_subcommands[cmd_name]
module = importlib.import_module(module_name)
app_object = getattr(module, "app", None) # looks for a Typer instance named 'app'
if not app_object:
raise ValueError(
f"Lazy loading of commands from module {module_name} failed."
)
return app_object
app = typer.Typer(no_args_is_help=True)
@app.callback(cls=LazyGroup)
def main() -> None:
"""My Package CLI."""
from __future__ import annotations
import typer
app = typer.Typer()
@app.command()
def upgrade() -> None:
"""Upgrade database schema."""
@app.callback()
def main() -> None:
"""Utilities to manage database."""
from __future__ import annotations
import typer
app = typer.Typer()
@app.command()
def start() -> None:
"""Start web server."""
@app.callback()
def main() -> None:
"""Utilities to manage web server."""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment