Skip to content

perf: import command modules lazily - #374

Draft
cloudsmith-iduffy wants to merge 3 commits into
perf/defer-mcp-importfrom
perf/lazy-command-registration
Draft

perf: import command modules lazily#374
cloudsmith-iduffy wants to merge 3 commits into
perf/defer-mcp-importfrom
perf/lazy-command-registration

Conversation

@cloudsmith-iduffy

@cloudsmith-iduffy cloudsmith-iduffy commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

Import command modules lazily. A static registry (cli/commands/registry.py) maps each top-level command name and alias to its module, and AliasGroup imports the module on first use. An invocation now imports only the module of the invoked command.

Before this change, cli/commands/__init__.py imported all 30 command modules on every startup so that they could self-register — the credential helpers paid for push, upstream, mcp and every API wrapper on each docker pull.

Results

Measured on macOS (M-series), Python 3.14.7, PYTHONDONTWRITEBYTECODE=1 (this repo's .envrc default). Baseline column = #373 merged.

Command #373 This PR Delta
cloudsmith --version 0.38 s 0.28 s -26%
credential-helper docker get 0.47 s 0.36 s -23%
Total import time (-X importtime) 309 ms 153 ms -50% of CLI imports

Cumulative vs master (2.72 s / 2.94 s): -90% / -88%.

Methodology (how to reproduce)

  1. Attribute the remaining time after perf: defer the mcp import to the mcp commands #373:

    python -X importtime -m cloudsmith_cli --version 2> importtime.txt
    sort -t'|' -k2 -n importtime.txt | tail -40

    cloudsmith_cli.cli.commands cost 232 ms: ~80 ms was the command modules themselves (upstream 21 ms, push 15 ms, mcp 16 ms, credential_helper 16 ms, …) and their core/api/* imports, on top of the shared decorators chain (151 ms).

  2. Design constraint. Aliases and names must resolve before any module import (e.g. cloudsmith ls, prefix match cloudsmith whoam, DYM suggestions, --help listing). The registry therefore duplicates the name/alias declarations statically. cli/tests/test_lazy_commands.py imports every command module with pkgutil.walk_packages and fails when the registry drifts from what the modules declare.

  3. Frozen binary. PyInstaller only bundles statically imported modules, so the lazy registry would silently drop every command module from the release binaries. The spec now uses collect_submodules("cloudsmith_cli"). Verified locally with a CI-equivalent build (uv sync --locked --no-dev --no-editable --group binary --extra all, then pyinstaller cloudsmith.spec):

    SELFTEST: OK (129 modules)
    push --help / ls --help OK
    credential-helper docker get: 0.35 s warm
    
  4. Behaviour checks. Full suite (800 passed, 40 skipped). Manually verified: --help lists all commands with name|alias suffixes and short help, alias resolution (ls), prefix match (whoam), DYM suggestions on typo, credential helper output.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • Other (please describe)

Additional Notes

  • cloudsmith --help still imports every command module (click fetches each command's short help), so --help is as slow as before — acceptable, it is not a hot path.
  • Adding/renaming/aliasing a top-level command now requires a registry entry

Flame graphs

Probe: cloudsmith --version. Icicle charts from python -X importtime: parents above children, width = cumulative import time. Totals include the interpreter's own site imports and vary a few ms between runs.

Before:

before

After:

after

cloudsmith-iduffy and others added 3 commits August 22, 2026 00:40
Register top-level commands through a static registry. AliasGroup
imports a command module on first use, so an invocation imports only
the module of the invoked command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
collect_submodules(cloudsmith_cli) bundles the modules that the lazy
registry imports at run time. Exclude the test packages and conftest,
which the sweep would otherwise pull in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cloudsmith-iduffy
cloudsmith-iduffy force-pushed the perf/lazy-command-registration branch from 10cb6bd to 3bb922e Compare August 21, 2026 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant