Skip to content

perf: keep the SDK and requests out of startup - #375

Draft
cloudsmith-iduffy wants to merge 2 commits into
perf/lazy-command-registrationfrom
perf/defer-cloudsmith-api-sdk
Draft

perf: keep the SDK and requests out of startup#375
cloudsmith-iduffy wants to merge 2 commits into
perf/lazy-command-registrationfrom
perf/defer-cloudsmith-api-sdk

Conversation

@cloudsmith-iduffy

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

Copy link
Copy Markdown
Contributor

Description

Keep the cloudsmith_api SDK and requests out of CLI startup.

After #374, the shared cli/decorators.py chain was the last eager cost (145 ms of the remaining 153 ms). It pulled in the full SDK (cloudsmith_api/__init__ imports every API class and ~200 model modules, ~70 ms) plus requests (~30 ms) on every invocation — including --version and credential-helper calls that never touch either.

Changes:

  • core/session.py (new): create_requests_session moved out of core/rest.py. core/rest.py must import the SDK at module level (RestClient subclasses the generated RESTClientObject), so every importer of create_requests_session paid for the SDK. The session module reads the SDK Configuration defaults only when cloudsmith_api is already in sys.modulesinitialise_api() is the only writer of those defaults (via set_default()), and it cannot have run if the SDK is not imported, so the literal fallbacks are exact.
  • cli/decorators.py: import core.api.init inside the initialise_api wrapper and core.session inside the initialise_session wrapper.
  • keyring_provider: import cli.saml (→ requests + SDK exceptions) only on the token-refresh path.
  • OIDC detectors (github_actions, azure_devops): import the session module only on a detector match, per the existing lazy-optional-import convention in the detectors.
  • core/credentials/models.py: requests is annotation-only → TYPE_CHECKING.

Results

Measured on macOS (M-series), Python 3.14.7, PYTHONDONTWRITEBYTECODE=1 (repo .envrc default). Baseline column = #374.

Command #374 This PR Delta
cloudsmith --version 0.28 s 0.18 s -36%
credential-helper docker get 0.36 s 0.37 s ±0 (see note)
Total import time (-X importtime) 153 ms 60 ms -61%

Note: the credential helper builds a requests session and reads the keyring at run time, so its requests import cost moves rather than disappears. It no longer loads the SDK at all (~70 ms saved is offset by run-time requests/keyring imports that were previously counted at startup). Its remaining wall time is dominated by the macOS keyring backend roundtrip (~0.19 s), a separate follow-up.

Cumulative vs master: --version 2.72 s → 0.18 s (-93%), docker get 2.94 s → 0.37 s (-87%).

Methodology (how to reproduce)

  1. Attribute the remaining import time after perf: import command modules lazily #374:

    python -X importtime -m cloudsmith_cli --version 2> importtime.txt
    sort -t'|' -k2 -n importtime.txt | tail -40
  2. Walk the parent chain of each heavy module in the importtime tree (indentation = depth, children print before parents). The chains this PR breaks:

    cloudsmith_api <- core.api.init <- cli.decorators
    requests <- core.credentials.models <- core.credentials.chain <- cli.decorators
    requests <- cli.saml <- keyring_provider <- credentials chain
    requests <- core.session <- oidc.detectors.{github_actions,azure_devops}
    

    Note from cloudsmith_api.rest import ApiException runs the whole cloudsmith_api/__init__ (parent packages import first) — importing "just the submodule" does not avoid the cost.

  3. Guard against regression: HEAVY_PREFIXES in cli/tests/test_startup_imports.py now includes cloudsmith_api and requests (written first; failed with the full SDK loaded).

  4. Verify behaviour: full suite (800 passed, 40 skipped) plus a live cloudsmith whoami (exercises initialise_apiRestClient → session defaults with the SDK loaded) and a live credential-helper docker get.

Type of Change

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

Additional Notes

  • create_requests_session keeps identical semantics when the SDK is loaded; when it is not, the fallback values it already contained apply.

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 2 commits August 22, 2026 00:40
Move create_requests_session to core/session.py, which reads the SDK
Configuration defaults only when the SDK is already imported. Defer the
SDK, session and saml imports in the decorators, the keyring provider
and the OIDC detectors to the code paths that use them.

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/defer-cloudsmith-api-sdk branch from f81dca7 to 8799332 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