Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions cloudsmith_cli/core/keyring.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
import os
from datetime import datetime, timedelta, timezone

import keyring
from keyring.errors import KeyringError

ACCESS_TOKEN_KEY = "cloudsmith_cli-access_token-{api_host}"


Expand Down Expand Up @@ -47,12 +44,17 @@ def _prepare_keyring_backend():
them through the library's own documented mechanism. Apply them here
instead, once the backend has been resolved.
"""
import keyring

_sync_keyring_backend_env()
_sync_keyring_property_env()
keyring.get_keyring().set_properties_from_env()


def _get_value(key):
import keyring
from keyring.errors import KeyringError

_prepare_keyring_backend()
username = _get_username()
try:
Expand All @@ -62,6 +64,8 @@ def _get_value(key):


def _set_value(key, value):
import keyring

_prepare_keyring_backend()
username = _get_username()
keyring.set_password(key, username, value)
Expand Down Expand Up @@ -142,6 +146,9 @@ def store_sso_tokens(api_host, access_token, refresh_token):


def _delete_value(key):
import keyring
from keyring.errors import KeyringError

_prepare_keyring_backend()
username = _get_username()
try:
Expand Down Expand Up @@ -178,6 +185,8 @@ def delete_sso_tokens(api_host):

def store_oidc_token(api_host, org, service_slug, token_data):
"""Store OIDC token in keyring if enabled."""
from keyring.errors import KeyringError

if not should_use_keyring():
return False

Expand Down
4 changes: 2 additions & 2 deletions packaging/pyinstaller/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import pkgutil
import sys

import keyring.backend

import cloudsmith_cli
from cloudsmith_cli.cli.commands.main import main

Expand Down Expand Up @@ -39,6 +37,8 @@ def _check_extra_keyring_backends(failed: list) -> None:
absent from discovery rather than raising, so this checks the discovered
class list explicitly instead of relying on an import error.
"""
import keyring.backend

discovered = [type(b).__module__ for b in keyring.backend.get_all_keyring()]
for module_prefix in ("keyrings.cryptfile", "keyrings.alt"):
if not any(name.startswith(module_prefix) for name in discovered):
Expand Down
28 changes: 27 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,34 @@ show_missing = true
directory = "reports/coverage"

[tool.ruff.lint]
extend-select = ["FA", "TC"]
extend-select = ["FA", "TC", "TID253"]
ignore = ["TRY002", "BLE001"]

[tool.ruff.lint.flake8-tidy-imports]
# These modules dominate CLI startup time. Import them inside functions,
# or add the file to the allowlist below when a module is itself loaded
# lazily (see cli/commands/registry.py and cli/tests/test_startup_imports.py).
banned-module-level-imports = [
"cloudsmith_api",
"httpx",
"keyring",
"mcp",
"requests",
"rich",
"semver",
"urllib3",
]

[tool.ruff.lint.per-file-ignores]
'__init__.py' = ["F401"]
# Lazy leaves: modules that are only imported when their command or code
# path runs, so module-level imports of heavy modules are free at startup.
'cloudsmith_cli/cli/commands/*' = ["TID253"]
'cloudsmith_cli/cli/saml.py' = ["TID253"]
'cloudsmith_cli/conftest.py' = ["TID253"]
'cloudsmith_cli/core/api/*' = ["TID253"]
'cloudsmith_cli/core/credentials/oidc/exchange.py' = ["TID253"]
'cloudsmith_cli/core/download.py' = ["TID253"]
'cloudsmith_cli/core/mcp/*' = ["TID253"]
'cloudsmith_cli/core/rest.py' = ["TID253"]
'cloudsmith_cli/core/session.py' = ["TID253"]
Loading