From 3fc2791046479a49001c8edf68798c1159a80726 Mon Sep 17 00:00:00 2001 From: Ian Duffy Date: Fri, 21 Aug 2026 23:22:32 +0100 Subject: [PATCH 1/3] perf(no-ticket): defer the mcp import to the mcp commands The mcp dependency costs ~1s to import. Import it in the initialise_mcp decorator so only the mcp commands pay that cost. Co-Authored-By: Claude Fable 5 --- cloudsmith_cli/cli/commands/mcp.py | 11 +++++- cloudsmith_cli/cli/decorators.py | 5 ++- .../cli/tests/test_startup_imports.py | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 cloudsmith_cli/cli/tests/test_startup_imports.py diff --git a/cloudsmith_cli/cli/commands/mcp.py b/cloudsmith_cli/cli/commands/mcp.py index eb00e451..ca37b817 100644 --- a/cloudsmith_cli/cli/commands/mcp.py +++ b/cloudsmith_cli/cli/commands/mcp.py @@ -1,17 +1,24 @@ """Main command/entrypoint.""" +from __future__ import annotations + import json import os import shutil import sys import tempfile from pathlib import Path +from typing import TYPE_CHECKING import click import json5 -from ...core.mcp import server -from ...core.mcp.data import OpenAPITool +# The mcp dependency costs ~1s to import. The runtime import happens in the +# initialise_mcp decorator; these names are only used in annotations. +if TYPE_CHECKING: + from ...core.mcp import server + from ...core.mcp.data import OpenAPITool + from .. import command, decorators, utils from .main import main diff --git a/cloudsmith_cli/cli/decorators.py b/cloudsmith_cli/cli/decorators.py index d4d9e60e..4ffe6fb0 100644 --- a/cloudsmith_cli/cli/decorators.py +++ b/cloudsmith_cli/cli/decorators.py @@ -16,7 +16,6 @@ disabled_detectors_from_env, registered_detectors, ) -from ..core.mcp import server from ..core.rest import create_requests_session as _create_session from . import config, utils @@ -627,6 +626,10 @@ def initialise_mcp(f): @click.pass_context @functools.wraps(f) def wrapper(ctx, *args, **kwargs): + # The mcp dependency costs ~1s to import. Import it here so that + # only the mcp commands pay that cost. + from ..core.mcp import server + opts = kwargs.get("opts") all_tools = kwargs.pop("all_tools") diff --git a/cloudsmith_cli/cli/tests/test_startup_imports.py b/cloudsmith_cli/cli/tests/test_startup_imports.py new file mode 100644 index 00000000..e61b69ad --- /dev/null +++ b/cloudsmith_cli/cli/tests/test_startup_imports.py @@ -0,0 +1,37 @@ +"""Tests that a CLI import does not load heavy dependencies. + +The credential helpers run the CLI on every package-manager request, so +module-level imports are the dominant startup cost. These tests import the +CLI in a subprocess and inspect ``sys.modules``. +""" + +import json +import subprocess +import sys + +HEAVY_PREFIXES = ("mcp", "httpx") + + +def modules_loaded_by_cli_import(): + code = ( + "import json, sys\n" + "import cloudsmith_cli.cli.commands\n" + "print(json.dumps(sorted(sys.modules)))\n" + ) + result = subprocess.run( + [sys.executable, "-c", code], + check=True, + capture_output=True, + text=True, + ) + return json.loads(result.stdout) + + +def test_cli_import_does_not_load_heavy_modules(): + modules = modules_loaded_by_cli_import() + heavy = [ + name + for name in modules + if any(name == p or name.startswith(p + ".") for p in HEAVY_PREFIXES) + ] + assert heavy == [] From a6bf483991f7a3043b0baa8efb8a1f0993553406 Mon Sep 17 00:00:00 2001 From: Ian Duffy Date: Fri, 21 Aug 2026 23:24:38 +0100 Subject: [PATCH 2/3] test(no-ticket): show the child stderr when the import probe fails Co-Authored-By: Claude Fable 5 --- cloudsmith_cli/cli/tests/test_startup_imports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudsmith_cli/cli/tests/test_startup_imports.py b/cloudsmith_cli/cli/tests/test_startup_imports.py index e61b69ad..ea0553a1 100644 --- a/cloudsmith_cli/cli/tests/test_startup_imports.py +++ b/cloudsmith_cli/cli/tests/test_startup_imports.py @@ -20,10 +20,10 @@ def modules_loaded_by_cli_import(): ) result = subprocess.run( [sys.executable, "-c", code], - check=True, capture_output=True, text=True, ) + assert result.returncode == 0, result.stderr return json.loads(result.stdout) From 44aa4074751a7b74b6aa10e3c9fd8b128dd9c7b9 Mon Sep 17 00:00:00 2001 From: Ian Duffy Date: Sat, 22 Aug 2026 00:02:40 +0100 Subject: [PATCH 3/3] chore(no-ticket): drop the import-cost comments Co-Authored-By: Claude Fable 5 --- cloudsmith_cli/cli/commands/mcp.py | 2 -- cloudsmith_cli/cli/decorators.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/cloudsmith_cli/cli/commands/mcp.py b/cloudsmith_cli/cli/commands/mcp.py index ca37b817..7e2d0f04 100644 --- a/cloudsmith_cli/cli/commands/mcp.py +++ b/cloudsmith_cli/cli/commands/mcp.py @@ -13,8 +13,6 @@ import click import json5 -# The mcp dependency costs ~1s to import. The runtime import happens in the -# initialise_mcp decorator; these names are only used in annotations. if TYPE_CHECKING: from ...core.mcp import server from ...core.mcp.data import OpenAPITool diff --git a/cloudsmith_cli/cli/decorators.py b/cloudsmith_cli/cli/decorators.py index 4ffe6fb0..3eb1fe94 100644 --- a/cloudsmith_cli/cli/decorators.py +++ b/cloudsmith_cli/cli/decorators.py @@ -626,8 +626,6 @@ def initialise_mcp(f): @click.pass_context @functools.wraps(f) def wrapper(ctx, *args, **kwargs): - # The mcp dependency costs ~1s to import. Import it here so that - # only the mcp commands pay that cost. from ..core.mcp import server opts = kwargs.get("opts")