From ea42af46bd0a6f89545b496e9ddf73b1d832b57e Mon Sep 17 00:00:00 2001 From: David Lynch Date: Mon, 10 Aug 2026 11:21:49 +0100 Subject: [PATCH] chore(ENG-13226): migrating from black/pylint to ruff Summary: Updating CI to use ruff, and fixing lints from changes Ensuring binaries workflow runs after code validation --- .github/scripts/check_wheel.py | 3 +- .pre-commit-config.yaml | 37 +--- cloudsmith_cli/cli/command.py | 14 +- cloudsmith_cli/cli/commands/auth.py | 2 +- cloudsmith_cli/cli/commands/check.py | 25 +-- cloudsmith_cli/cli/commands/copy.py | 21 +- cloudsmith_cli/cli/commands/delete.py | 12 +- cloudsmith_cli/cli/commands/dependencies.py | 21 +- cloudsmith_cli/cli/commands/download.py | 62 +++--- cloudsmith_cli/cli/commands/entitlements.py | 180 +++++++++--------- cloudsmith_cli/cli/commands/list_.py | 54 +++--- cloudsmith_cli/cli/commands/login.py | 39 ++-- cloudsmith_cli/cli/commands/main.py | 21 +- cloudsmith_cli/cli/commands/mcp.py | 38 ++-- cloudsmith_cli/cli/commands/metadata.py | 136 ++++++------- .../cli/commands/metrics/entitlements.py | 26 +-- .../cli/commands/metrics/packages.py | 16 +- cloudsmith_cli/cli/commands/move.py | 18 +- cloudsmith_cli/cli/commands/policy/deny.py | 69 +++---- cloudsmith_cli/cli/commands/policy/license.py | 57 +++--- .../cli/commands/policy/vulnerability.py | 59 +++--- cloudsmith_cli/cli/commands/push.py | 143 +++++++------- cloudsmith_cli/cli/commands/quarantine.py | 28 ++- cloudsmith_cli/cli/commands/quota/history.py | 8 +- cloudsmith_cli/cli/commands/quota/quota.py | 8 +- cloudsmith_cli/cli/commands/repos.py | 65 ++++--- cloudsmith_cli/cli/commands/resync.py | 13 +- cloudsmith_cli/cli/commands/status.py | 20 +- cloudsmith_cli/cli/commands/tags.py | 119 ++++++------ cloudsmith_cli/cli/commands/tokens.py | 55 +++--- cloudsmith_cli/cli/commands/upstream.py | 87 ++++----- .../cli/commands/vulnerabilities.py | 24 +-- cloudsmith_cli/cli/commands/whoami.py | 8 +- cloudsmith_cli/cli/config.py | 27 +-- cloudsmith_cli/cli/decorators.py | 3 +- cloudsmith_cli/cli/exceptions.py | 36 ++-- cloudsmith_cli/cli/saml.py | 6 +- .../tests/commands/test_credential_helper.py | 10 +- .../test_credential_helper_install.py | 30 +-- .../cli/tests/commands/test_entitlements.py | 2 +- .../cli/tests/commands/test_login.py | 4 +- cloudsmith_cli/cli/tests/commands/test_mcp.py | 28 +-- .../tests/commands/test_package_commands.py | 4 +- .../cli/tests/commands/test_tokens.py | 1 - .../cli/tests/commands/test_upstream.py | 4 +- cloudsmith_cli/cli/tests/conftest.py | 2 +- cloudsmith_cli/cli/tests/test_push.py | 54 +++--- cloudsmith_cli/cli/tests/test_utils.py | 2 +- cloudsmith_cli/cli/tests/test_webserver.py | 172 +++++++++-------- cloudsmith_cli/cli/tests/utils.py | 4 +- cloudsmith_cli/cli/utils.py | 36 +--- cloudsmith_cli/cli/validators.py | 19 +- cloudsmith_cli/cli/webserver.py | 4 +- cloudsmith_cli/core/api/packages.py | 4 +- cloudsmith_cli/core/api/quota.py | 26 ++- cloudsmith_cli/core/api/upstreams.py | 8 +- cloudsmith_cli/core/api/vulnerabilities.py | 4 +- cloudsmith_cli/core/config.py | 21 +- .../core/credentials/oidc/exchange.py | 3 +- cloudsmith_cli/core/download.py | 15 +- cloudsmith_cli/core/keyring.py | 8 +- cloudsmith_cli/core/mcp/server.py | 27 +-- cloudsmith_cli/core/pagination.py | 4 +- cloudsmith_cli/core/ratelimits.py | 16 +- cloudsmith_cli/core/rest.py | 2 +- cloudsmith_cli/core/tests/test_cache_utils.py | 24 +-- .../tests/test_credential_chain_priority.py | 56 +++--- cloudsmith_cli/core/tests/test_keyring.py | 8 +- .../core/tests/test_keyring_provider.py | 76 ++++---- cloudsmith_cli/core/tests/test_metadata.py | 12 +- cloudsmith_cli/credential_helpers/common.py | 9 +- .../credential_helpers/docker/installer.py | 3 +- .../credential_helpers/docker/runtime.py | 6 +- pyproject.toml | 6 + 74 files changed, 1139 insertions(+), 1135 deletions(-) diff --git a/.github/scripts/check_wheel.py b/.github/scripts/check_wheel.py index df5def3f..0671d385 100644 --- a/.github/scripts/check_wheel.py +++ b/.github/scripts/check_wheel.py @@ -1,5 +1,6 @@ # Copyright 2026 Cloudsmith Ltd """Fail if the built wheel contains packaging or test files.""" + import glob import zipfile @@ -11,7 +12,7 @@ forbidden = [ name for name in names - if name.startswith("packaging/") or "/tests/" in name or name.startswith("tests/") + if name.startswith(("packaging/", "tests/")) or "/tests/" in name ] if forbidden: raise SystemExit(f"wheel contains non-runtime files: {forbidden}") diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 26d261af..22bcbcfa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,6 @@ # Pre-Commit hooks # See: https://pre-commit.com/hooks.html repos: - - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: @@ -42,22 +41,6 @@ repos: )$ - id: trailing-whitespace -- repo: https://github.com/psf/black - rev: 25.1.0 - hooks: - - id: black - -- repo: https://github.com/pycqa/flake8 - rev: '7.1.2' - hooks: - - id: flake8 - args: ['--config=.flake8'] - -- repo: https://github.com/pycqa/isort - rev: 6.0.1 - hooks: - - id: isort - - repo: https://github.com/asottile/pyupgrade rev: v3.21.2 hooks: @@ -70,19 +53,17 @@ repos: hooks: - id: zizmor -- repo: local +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.16.1 hooks: - - id: pylint - name: pylint - entry: pylint - language: system - types: [python] + - id: ruff-format + args: ['--check'] + - id: ruff-check + # excluding tests until all lints pass args: - [ - "-rn", # Only display messages - "-sn", # Don't display the score - "--rcfile=.pylintrc", # Link to your config file - ] + - '--output-format=github' + - '--exclude=cloudsmith_cli/cli/tests' + - '--exclude=cloudsmith_cli/core/tests' - repo: https://github.com/crate-ci/typos rev: v1.42.1 diff --git a/cloudsmith_cli/cli/command.py b/cloudsmith_cli/cli/command.py index 3c1132cb..7332e26c 100644 --- a/cloudsmith_cli/cli/command.py +++ b/cloudsmith_cli/cli/command.py @@ -24,9 +24,12 @@ def _is_json_output_requested(exception): return True for idx, arg in enumerate(argv): - if arg in ("-F", "--output-format") and idx + 1 < len(argv): - if argv[idx + 1] in ("json", "pretty_json"): - return True + if ( + arg in ("-F", "--output-format") + and idx + 1 < len(argv) + and argv[idx + 1] in ("json", "pretty_json") + ): + return True return False @@ -88,9 +91,8 @@ def list_commands(self, ctx): return commands def get_command(self, ctx, cmd_name): - if getattr(ctx, "showing_help", False): - if "|" in cmd_name: - cmd_name = cmd_name.split("|")[0] + if getattr(ctx, "showing_help", False) and "|" in cmd_name: + cmd_name = cmd_name.split("|")[0] try: cmd_name = self.inverse[cmd_name] diff --git a/cloudsmith_cli/cli/commands/auth.py b/cloudsmith_cli/cli/commands/auth.py index ebd04a95..6ef1df7a 100644 --- a/cloudsmith_cli/cli/commands/auth.py +++ b/cloudsmith_cli/cli/commands/auth.py @@ -133,7 +133,7 @@ def authenticate( err=True, ) - owner = owner[0].strip("'[]'") + owner = owner[0].strip("[]'") click.echo( f"Beginning authentication for the {click.style(owner, bold=True)} org ... ", diff --git a/cloudsmith_cli/cli/commands/check.py b/cloudsmith_cli/cli/commands/check.py index ea0772b5..6a3543d2 100644 --- a/cloudsmith_cli/cli/commands/check.py +++ b/cloudsmith_cli/cli/commands/check.py @@ -33,9 +33,11 @@ def rates(ctx, opts): click.echo("Retrieving rate limits ... ", nl=False, err=use_stderr) context_msg = "Failed to retrieve status!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - resources_limits = get_rate_limits() + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + resources_limits = get_rate_limits() click.secho("OK", fg="green", err=use_stderr) @@ -53,11 +55,10 @@ def rates(ctx, opts): "Yes" if limits.throttled else "No", fg="red" if limits.throttled else "green", ), - "%(remaining)s/%(limit)s" - % { - "remaining": click.style(str(limits.remaining), fg="yellow"), - "limit": click.style(str(limits.limit), fg="yellow"), - }, + "{remaining}/{limit}".format( + remaining=click.style(str(limits.remaining), fg="yellow"), + limit=click.style(str(limits.limit), fg="yellow"), + ), click.style(str(limits.interval), fg="blue"), click.style(str(limits.reset), fg="magenta"), ] @@ -85,9 +86,11 @@ def service(ctx, opts): click.echo("Retrieving service status ... ", nl=False, err=use_stderr) context_msg = "Failed to retrieve status!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - status, version = get_status(with_version=True) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + status, version = get_status(with_version=True) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/copy.py b/cloudsmith_cli/cli/commands/copy.py index 2f52d3c7..ac953970 100644 --- a/cloudsmith_cli/cli/commands/copy.py +++ b/cloudsmith_cli/cli/commands/copy.py @@ -59,24 +59,21 @@ def copy( use_stderr = utils.should_use_stderr(opts) click.echo( - "Copying %(slug)s package from %(source)s to %(dest)s ... " - % { - "slug": click.style(slug, bold=True), - "source": click.style(source, bold=True), - "dest": click.style(destination, bold=True), - }, + f"Copying {click.style(slug, bold=True)} package from {click.style(source, bold=True)} to {click.style(destination, bold=True)} ... ", nl=False, err=use_stderr, ) context_msg = "Failed to copy package!" - with handle_api_exceptions( - ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + with ( + handle_api_exceptions( + ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + ), + maybe_spinner(opts), ): - with maybe_spinner(opts): - _, new_slug = copy_package( - owner=owner, repo=source, identifier=slug, destination=destination - ) + _, new_slug = copy_package( + owner=owner, repo=source, identifier=slug, destination=destination + ) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/delete.py b/cloudsmith_cli/cli/commands/delete.py index af8b127c..c58db106 100644 --- a/cloudsmith_cli/cli/commands/delete.py +++ b/cloudsmith_cli/cli/commands/delete.py @@ -47,20 +47,22 @@ def delete(ctx, opts, owner_repo_package, yes): use_stderr = utils.should_use_stderr(opts) - prompt = "delete the %(package)s from %(owner)s/%(repo)s" % delete_args + prompt = "delete the {package} from {owner}/{repo}".format(**delete_args) if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr): return click.echo( - "Deleting %(package)s from %(owner)s/%(repo)s ... " % delete_args, + "Deleting {package} from {owner}/{repo} ... ".format(**delete_args), nl=False, err=use_stderr, ) context_msg = "Failed to delete the package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - delete_package(owner=owner, repo=repo, identifier=slug) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + delete_package(owner=owner, repo=repo, identifier=slug) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/dependencies.py b/cloudsmith_cli/cli/commands/dependencies.py index 98187250..b9be2131 100644 --- a/cloudsmith_cli/cli/commands/dependencies.py +++ b/cloudsmith_cli/cli/commands/dependencies.py @@ -53,23 +53,20 @@ def list_dependencies(ctx, opts, owner_repo_package): use_stderr = utils.should_use_stderr(opts) click.echo( - "Getting direct (non-transitive) dependencies of %(package)s in " - "%(owner)s/%(repo)s ... " - % { - "owner": click.style(owner, bold=True), - "repo": click.style(repo, bold=True), - "package": click.style(identifier, bold=True), - }, + f"Getting direct (non-transitive) dependencies of {click.style(identifier, bold=True)} in " + f"{click.style(owner, bold=True)}/{click.style(repo, bold=True)} ... ", nl=False, err=use_stderr, ) context_msg = "Failed to get dependencies of package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - deps, page_info = get_package_dependencies( - owner=owner, repo=repo, identifier=identifier - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + deps, page_info = get_package_dependencies( + owner=owner, repo=repo, identifier=identifier + ) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/download.py b/cloudsmith_cli/cli/commands/download.py index 54d0126a..1df1ee4a 100644 --- a/cloudsmith_cli/cli/commands/download.py +++ b/cloudsmith_cli/cli/commands/download.py @@ -172,17 +172,17 @@ def download( click.echo(f"Using authentication: {auth_source}", err=True) # Step 2: Find package(s) - filter_kwargs = dict( - owner=owner, - repo=repo, - name=name, - version=version, - format_filter=format_filter, - os_filter=os_filter, - arch_filter=arch_filter, - tag_filter=tag_filter, - filename_filter=filename_filter, - ) + filter_kwargs = { + "owner": owner, + "repo": repo, + "name": name, + "version": version, + "format_filter": format_filter, + "os_filter": os_filter, + "arch_filter": arch_filter, + "tag_filter": tag_filter, + "filename_filter": filename_filter, + } packages = _find_packages(ctx, opts, filter_kwargs, download_all, yes, use_stderr) # Step 3: Resolve download items (url + output path for each file) @@ -224,14 +224,18 @@ def _find_packages( """Find matching packages using the API.""" if download_all: context_msg = "Failed to find packages!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - packages = resolve_all_packages(**filter_kwargs) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + packages = resolve_all_packages(**filter_kwargs) else: context_msg = "Failed to find package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - packages = [resolve_package(**filter_kwargs, yes=yes)] + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + packages = [resolve_package(**filter_kwargs, yes=yes)] if not use_stderr: click.secho("OK", fg="green") @@ -304,9 +308,11 @@ def _resolve_all_files_items( click.echo("Getting package details ...", nl=False) context_msg = f"Failed to get details for {pkg_name}!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - detail = get_package_detail(owner=owner, repo=repo, identifier=pkg["slug"]) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + detail = get_package_detail(owner=owner, repo=repo, identifier=pkg["slug"]) if not use_stderr: click.secho("OK", fg="green") @@ -367,12 +373,12 @@ def _resolve_single_file_item( if not use_stderr: click.echo("Getting package details ...", nl=False) context_msg = "Failed to get package details!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - detail = get_package_detail( - owner=owner, repo=repo, identifier=pkg["slug"] - ) - download_url = get_download_url(detail or pkg) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + detail = get_package_detail(owner=owner, repo=repo, identifier=pkg["slug"]) + download_url = get_download_url(detail or pkg) if not use_stderr: click.secho("OK", fg="green") @@ -485,7 +491,7 @@ def _perform_downloads( ) _echo_status(use_stderr, " OK", fg="green") results.append({**item, "status": "OK"}) - except Exception as e: # pylint: disable=broad-except + except Exception as e: _echo_status(use_stderr, " FAILED", fg="red") results.append({**item, "status": "FAILED", "error": str(e)}) @@ -498,7 +504,7 @@ def _echo_progress(use_stderr: bool, message: str) -> None: click.echo(message, nl=False, err=use_stderr) -def _echo_status(use_stderr: bool, message: str, fg: str = None) -> None: +def _echo_status(use_stderr: bool, message: str, fg: str | None = None) -> None: """Print styled status message to stdout or stderr.""" if fg and not use_stderr: click.secho(message, fg=fg) diff --git a/cloudsmith_cli/cli/commands/entitlements.py b/cloudsmith_cli/cli/commands/entitlements.py index aa465f6f..d1c81c12 100644 --- a/cloudsmith_cli/cli/commands/entitlements.py +++ b/cloudsmith_cli/cli/commands/entitlements.py @@ -92,24 +92,26 @@ def list_entitlements(ctx, opts, owner_repo, page, page_size, show_tokens, page_ use_stderr = utils.should_use_stderr(opts) click.echo( - "Getting list of entitlements for the %(repository)s " - "repository ... " % {"repository": click.style(repo, bold=True)}, + f"Getting list of entitlements for the {click.style(repo, bold=True)} " + "repository ... ", nl=False, err=use_stderr, ) context_msg = "Failed to get list of entitlements!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - entitlements_, page_info = paginate_results( - api.list_entitlements, - page_all=page_all, - page=page, - page_size=page_size, - owner=owner, - repo=repo, - show_tokens=show_tokens, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + entitlements_, page_info = paginate_results( + api.list_entitlements, + page_all=page_all, + page=page, + page_size=page_size, + owner=owner, + repo=repo, + show_tokens=show_tokens, + ) click.secho("OK", fg="green", err=use_stderr) @@ -139,11 +141,10 @@ def print_entitlements(opts, data, page_info=None, show_list_info=True): rows.append( [ click.style( - "%(name)s (%(type)s)" - % { - "name": click.style(entitlement["name"], fg="cyan"), - "type": "user" if entitlement["user"] else "token", - } + "{name} ({type})".format( + name=click.style(entitlement["name"], fg="cyan"), + type="user" if entitlement["user"] else "token", + ) ), click.style(entitlement["token"], fg="yellow"), click.style(ent_updated_at or ent_created_at, fg="blue"), @@ -245,11 +246,10 @@ def print_entitlements_with_restrictions( [ click.style(entitlement["slug_perm"], fg="green"), click.style( - "%(name)s (%(type)s)" - % { - "name": click.style(name, fg="cyan"), - "type": "user" if user else "token", - } + "{name} ({type})".format( + name=click.style(name, fg="cyan"), + type="user" if user else "token", + ) ), click.style(updated_at or created_at, fg="white"), click.style("yes" if is_active else "no", fg="yellow"), @@ -326,22 +326,20 @@ def create(ctx, opts, owner_repo, show_tokens, name, token): use_stderr = utils.should_use_stderr(opts) click.secho( - "Creating %(name)s entitlement for the %(repository)s " - "repository ... " - % { - "name": click.style(name, bold=True), - "repository": click.style(repo, bold=True), - }, + f"Creating {click.style(name, bold=True)} entitlement for the {click.style(repo, bold=True)} " + "repository ... ", nl=False, err=use_stderr, ) context_msg = "Failed to create the entitlement!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - entitlement = api.create_entitlement( - owner=owner, repo=repo, name=name, token=token, show_tokens=show_tokens - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + entitlement = api.create_entitlement( + owner=owner, repo=repo, name=name, token=token, show_tokens=show_tokens + ) click.secho("OK", fg="green", err=use_stderr) @@ -388,8 +386,9 @@ def delete(ctx, opts, owner_repo_identifier, yes): } prompt = ( - "delete the %(identifier)s entitlement from the %(repository)s " - "repository" % delete_args + "delete the {identifier} entitlement from the {repository} repository".format( + **delete_args + ) ) use_stderr = utils.should_use_stderr(opts) @@ -398,16 +397,18 @@ def delete(ctx, opts, owner_repo_identifier, yes): return click.secho( - "Deleting %(identifier)s entitlement from the %(repository)s " - "repository ... " % delete_args, + "Deleting {identifier} entitlement from the {repository} " + "repository ... ".format(**delete_args), nl=False, err=use_stderr, ) context_msg = "Failed to delete the entitlement!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api.delete_entitlement(owner=owner, repo=repo, identifier=identifier) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api.delete_entitlement(owner=owner, repo=repo, identifier=identifier) click.secho("OK", fg="green") @@ -461,27 +462,25 @@ def update(ctx, opts, owner_repo_identifier, show_tokens, name, token): use_stderr = utils.should_use_stderr(opts) click.secho( - "Updating %(identifier)s entitlement for the %(repository)s " - "repository ... " - % { - "identifier": click.style(identifier, bold=True), - "repository": click.style(repo, bold=True), - }, + f"Updating {click.style(identifier, bold=True)} entitlement for the {click.style(repo, bold=True)} " + "repository ... ", nl=False, err=use_stderr, ) context_msg = "Failed to update the entitlement!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - entitlement = api.update_entitlement( - owner=owner, - repo=repo, - identifier=identifier, - name=name, - token=token, - show_tokens=show_tokens, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + entitlement = api.update_entitlement( + owner=owner, + repo=repo, + identifier=identifier, + name=name, + token=token, + show_tokens=show_tokens, + ) click.secho("OK", fg="green", err=use_stderr) @@ -534,25 +533,28 @@ def refresh(ctx, opts, owner_repo_identifier, show_tokens, yes): use_stderr = utils.should_use_stderr(opts) prompt = ( - "refresh the %(identifier)s entitlement for the %(repository)s " - "repository" % refresh_args + "refresh the {identifier} entitlement for the {repository} repository".format( + **refresh_args + ) ) if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr): return click.secho( - "Refreshing %(identifier)s entitlement for the %(repository)s " - "repository ... " % refresh_args, + "Refreshing {identifier} entitlement for the {repository} " + "repository ... ".format(**refresh_args), nl=False, err=use_stderr, ) context_msg = "Failed to refresh the entitlement!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - entitlement = api.refresh_entitlement( - owner=owner, repo=repo, identifier=identifier, show_tokens=show_tokens - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + entitlement = api.refresh_entitlement( + owner=owner, repo=repo, identifier=identifier, show_tokens=show_tokens + ) click.secho("OK", fg="green", err=use_stderr) @@ -611,34 +613,36 @@ def sync(ctx, opts, owner_repo, show_tokens, source, yes): if not yes: click.secho( - "%(warning)s This will DELETE ALL of the existing entitlements " - "in the %(dest)s repository and replace them with entitlements " - "from the %(source)s repository." % sync_args, + "{warning} This will DELETE ALL of the existing entitlements " + "in the {dest} repository and replace them with entitlements " + "from the {source} repository.".format(**sync_args), fg="yellow", err=use_stderr, ) click.echo() prompt = ( - "sync entitlements from the %(source)s repository to the " - "%(dest)s repository" % sync_args + "sync entitlements from the {source} repository to the " + "{dest} repository".format(**sync_args) ) if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr): return click.secho( - "Syncing entitlements from the %(source)s repository to the " - "%(dest)s repository" % sync_args, + "Syncing entitlements from the {source} repository to the " + "{dest} repository".format(**sync_args), nl=False, err=use_stderr, ) context_msg = "Failed to sync the entitlements!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - entitlements_, page_info = api.sync_entitlements( - owner=owner, repo=repo, source=source, show_tokens=show_tokens - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + entitlements_, page_info = api.sync_entitlements( + owner=owner, repo=repo, source=source, show_tokens=show_tokens + ) click.secho("OK", fg="green", err=use_stderr) @@ -775,12 +779,8 @@ def restrict( use_stderr = utils.should_use_stderr(opts) click.secho( - "Updating %(identifier)s entitlement for the %(repository)s " - "repository ... " - % { - "identifier": click.style(identifier, bold=True), - "repository": click.style(repo, bold=True), - }, + f"Updating {click.style(identifier, bold=True)} entitlement for the {click.style(repo, bold=True)} " + "repository ... ", nl=False, err=use_stderr, ) @@ -806,11 +806,13 @@ def restrict( data["limit_date_range_to"] = limit_date_range_to context_msg = "Failed to update the entitlement!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - entitlement = api.restrict_entitlement( - owner=owner, repo=repo, identifier=identifier, data=data - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + entitlement = api.restrict_entitlement( + owner=owner, repo=repo, identifier=identifier, data=data + ) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/list_.py b/cloudsmith_cli/cli/commands/list_.py index f958ac0e..692d4f76 100644 --- a/cloudsmith_cli/cli/commands/list_.py +++ b/cloudsmith_cli/cli/commands/list_.py @@ -59,9 +59,11 @@ def distros(ctx, opts, package_format): click.echo("Getting list of distributions ... ", nl=False, err=use_stderr) context_msg = "Failed to get list of distributions!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - distros_ = list_distros(package_format=package_format) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + distros_ = list_distros(package_format=package_format) if not use_stderr: click.secho("OK", fg="green", err=use_stderr) @@ -83,11 +85,10 @@ def distros(ctx, opts, package_format): click.style(distro["name"], fg="cyan"), click.style(release["name"], fg="yellow"), click.style(distro["format"], fg="blue"), - "%(distro)s/%(release)s" - % { - "distro": click.style(distro["slug"], fg="magenta"), - "release": click.style(release["slug"], fg="green"), - }, + "{distro}/{release}".format( + distro=click.style(distro["slug"], fg="magenta"), + release=click.style(release["slug"], fg="green"), + ), ] if package_format: @@ -216,18 +217,20 @@ def packages(ctx, opts, owner_repo, page, page_size, query, sort, page_all): click.echo("Getting list of packages ... ", nl=False, err=use_stderr) context_msg = "Failed to get list of packages!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - packages_, page_info = paginate_results( - list_packages, - page_all=page_all, - page=page, - page_size=page_size, - owner=owner, - repo=repo, - query=query, - sort=sort, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + packages_, page_info = paginate_results( + list_packages, + page_all=page_all, + page=page, + page_size=page_size, + owner=owner, + repo=repo, + query=query, + sort=sort, + ) if not use_stderr: click.secho("OK", fg="green", err=use_stderr) @@ -243,12 +246,11 @@ def packages(ctx, opts, owner_repo, page, page_size, query, sort, page_all): click.style(_get_package_name(package), fg="cyan"), click.style(_get_package_version(package), fg="yellow"), click.style(_get_package_status(package), fg="blue"), - "%(owner_slug)s/%(repo_slug)s/%(slug)s" - % { - "owner_slug": click.style(package["namespace"], fg="magenta"), - "repo_slug": click.style(package["repository"], fg="magenta"), - "slug": click.style(package["slug"], fg="green"), - }, + "{owner_slug}/{repo_slug}/{slug}".format( + owner_slug=click.style(package["namespace"], fg="magenta"), + repo_slug=click.style(package["repository"], fg="magenta"), + slug=click.style(package["slug"], fg="green"), + ), ] ) diff --git a/cloudsmith_cli/cli/commands/login.py b/cloudsmith_cli/cli/commands/login.py index 43840dc2..a2f73399 100644 --- a/cloudsmith_cli/cli/commands/login.py +++ b/cloudsmith_cli/cli/commands/login.py @@ -39,17 +39,18 @@ def login(ctx, opts, login, password): # pylint: disable=redefined-outer-name """Retrieve your API authentication token/key via login.""" use_stderr = utils.should_use_stderr(opts) click.echo( - "Retrieving API token for %(login)s ... " - % {"login": click.style(login, bold=True)}, + f"Retrieving API token for {click.style(login, bold=True)} ... ", nl=False, err=use_stderr, ) context_msg = "Failed to retrieve the API token!" try: - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api_key = get_user_token(login=login, password=password) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api_key = get_user_token(login=login, password=password) except TwoFactorRequiredException as e: click.echo("\r\033[K", nl=False, err=use_stderr) click.echo("Two-factor authentication is required.", err=use_stderr) @@ -58,21 +59,22 @@ def login(ctx, opts, login, password): # pylint: disable=redefined-outer-name "Enter your two-factor authentication code", type=str, err=use_stderr ) click.echo( - "Verifying two-factor code for %(login)s ... " - % {"login": click.style(login, bold=True)}, + f"Verifying two-factor code for {click.style(login, bold=True)} ... ", nl=False, err=use_stderr, ) try: - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api_key = get_user_token( - login=login, - password=password, - totp_token=totp_token, - two_factor_token=e.two_factor_token, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api_key = get_user_token( + login=login, + password=password, + totp_token=totp_token, + two_factor_token=e.two_factor_token, + ) except cloudsmith_api.rest.ApiException: click.echo("\r\033[K", nl=False, err=use_stderr) click.secho( @@ -84,15 +86,16 @@ def login(ctx, opts, login, password): # pylint: disable=redefined-outer-name except cloudsmith_api.rest.ApiException as e: click.echo("\r\033[K", nl=False, err=use_stderr) - click.secho(f"Authentication failed: {str(e)}", fg="red", err=use_stderr) + click.secho(f"Authentication failed: {e!s}", fg="red", err=use_stderr) ctx.exit(1) click.secho("OK", fg="green", err=use_stderr) if not utils.maybe_print_as_json(opts, {"token": api_key, "login": login}): click.echo( - "Your API key/token is: %(token)s" - % {"token": click.style(api_key, fg="magenta")} + "Your API key/token is: {token}".format( + token=click.style(api_key, fg="magenta") + ) ) create, has_errors = create_config_files(ctx, opts, api_key=api_key) diff --git a/cloudsmith_cli/cli/commands/main.py b/cloudsmith_cli/cli/commands/main.py index d1aa17b2..e476b8ca 100644 --- a/cloudsmith_cli/cli/commands/main.py +++ b/cloudsmith_cli/cli/commands/main.py @@ -7,7 +7,7 @@ from ...core.version import get_version as get_cli_version from .. import command, decorators, utils -CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) +CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]} def print_version(opts): @@ -22,14 +22,8 @@ def print_version(opts): if not utils.maybe_print_as_json(opts, data): click.echo("Versions:") - click.secho( - "CLI Package Version: %(version)s" - % {"version": click.style(cli_version, bold=True)} - ) - click.secho( - "API Package Version: %(version)s" - % {"version": click.style(api_version, bold=True)} - ) + click.secho(f"CLI Package Version: {click.style(cli_version, bold=True)}") + click.secho(f"API Package Version: {click.style(api_version, bold=True)}") @click.group( @@ -46,12 +40,11 @@ def print_version(opts): The Cloudsmith Command-Line Interface - Be Awesome. Automate Everything. """, - epilog=""" -For more help, see the docs: %(help_website)s + epilog=f""" +For more help, see the docs: {get_help_website()} -For issues/contributing: %(github_website)s - """ - % {"help_website": get_help_website(), "github_website": get_github_website()}, +For issues/contributing: {get_github_website()} + """, ) @click.option( "-V", diff --git a/cloudsmith_cli/cli/commands/mcp.py b/cloudsmith_cli/cli/commands/mcp.py index 20b2ee86..eb00e451 100644 --- a/cloudsmith_cli/cli/commands/mcp.py +++ b/cloudsmith_cli/cli/commands/mcp.py @@ -268,7 +268,7 @@ def configure(ctx, opts, client, is_global): # pylint: disable=unused-argument if not use_stderr: click.echo( click.style( - f"✗ Error configuring {client_name.title()}: {str(e)}", fg="red" + f"✗ Error configuring {client_name.title()}: {e!s}", fg="red" ) ) results.append({"client": client_name, "success": False, "error": str(e)}) @@ -459,30 +459,30 @@ def _atomic_write_json(path: Path, data) -> None: path.parent.mkdir(parents=True, exist_ok=True) existing_mode = path.stat().st_mode & 0o777 if path.exists() else None - tmp = tempfile.NamedTemporaryFile( + with tempfile.NamedTemporaryFile( mode="w", dir=path.parent, prefix=f".{path.name}.", suffix=".tmp", delete=False, - ) - tmp_path = Path(tmp.name) - try: - with tmp as f: - # json5 is used for reading; we write standard JSON, which drops - # any user comments (currently only relevant for VS Code's JSONC). - json.dump(data, f, indent=2) - f.flush() - os.fsync(f.fileno()) - if existing_mode is not None: - os.chmod(tmp_path, existing_mode) - os.replace(tmp_path, path) - except BaseException: + ) as tmp: + tmp_path = Path(tmp.name) try: - tmp_path.unlink() - except FileNotFoundError: - pass - raise + with tmp as f: + # json5 is used for reading; we write standard JSON, which drops + # any user comments (currently only relevant for VS Code's JSONC). + json.dump(data, f, indent=2) + f.flush() + os.fsync(f.fileno()) + if existing_mode is not None: + os.chmod(tmp_path, existing_mode) + os.replace(tmp_path, path) + except BaseException: + try: + tmp_path.unlink() + except FileNotFoundError: + pass + raise def _safe_update_json(path: Path, mutate, *, max_retries: int = 3) -> None: diff --git a/cloudsmith_cli/cli/commands/metadata.py b/cloudsmith_cli/cli/commands/metadata.py index 6fb80c07..7c37b84e 100644 --- a/cloudsmith_cli/cli/commands/metadata.py +++ b/cloudsmith_cli/cli/commands/metadata.py @@ -6,9 +6,17 @@ from ...core.api.metadata import ( create_metadata as api_create_metadata, +) +from ...core.api.metadata import ( delete_metadata as api_delete_metadata, +) +from ...core.api.metadata import ( get_metadata as api_get_metadata, +) +from ...core.api.metadata import ( list_metadata as api_list_metadata, +) +from ...core.api.metadata import ( update_metadata as api_update_metadata, ) from ...core.api.packages import get_package_slug_perm as api_get_package_slug_perm @@ -163,47 +171,46 @@ def list_metadata( if metadata_slug_perm: _echo_action( - "Fetching metadata %(metadata)s for %(package)s ... " - % { - "metadata": click.style(metadata_slug_perm, bold=True), - "package": click.style(package, bold=True), - }, + f"Fetching metadata {click.style(metadata_slug_perm, bold=True)} for {click.style(package, bold=True)} ... ", use_stderr, ) context_msg = "Could not fetch package metadata." - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - slug_perm = api_get_package_slug_perm( - owner=owner, repo=repo, identifier=package - ) - entry = api_get_metadata(slug_perm, metadata_slug_perm) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + slug_perm = api_get_package_slug_perm( + owner=owner, repo=repo, identifier=package + ) + entry = api_get_metadata(slug_perm, metadata_slug_perm) click.secho("OK", fg="green", err=use_stderr) _print_metadata_entry(opts, entry) return _echo_action( - "Listing metadata for %(package)s ... " - % {"package": click.style(package, bold=True)}, + f"Listing metadata for {click.style(package, bold=True)} ... ", use_stderr, ) context_msg = "Could not list package metadata." - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - slug_perm = api_get_package_slug_perm( - owner=owner, repo=repo, identifier=package - ) - entries, page_info = paginate_results( - api_list_metadata, - page_all=page_all, - page=page, - page_size=page_size, - package_slug_perm=slug_perm, - source_kind=source_kind, - classification=classification, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + slug_perm = api_get_package_slug_perm( + owner=owner, repo=repo, identifier=package + ) + entries, page_info = paginate_results( + api_list_metadata, + page_all=page_all, + page=page, + page_size=page_size, + package_slug_perm=slug_perm, + source_kind=source_kind, + classification=classification, + ) click.secho("OK", fg="green", err=use_stderr) _print_metadata_table(opts, entries, page_info=page_info, page_all=page_all) @@ -252,7 +259,7 @@ def list_metadata( "source_identity", default=None, help=( - "Identifier for the metadata source. " "Defaults to 'cloudsmith-cli@'." + "Identifier for the metadata source. Defaults to 'cloudsmith-cli@'." ), ) @click.pass_context @@ -307,23 +314,24 @@ def add_metadata( ) _echo_action( - "Attaching metadata to %(package)s ... " - % {"package": click.style(package, bold=True)}, + f"Attaching metadata to {click.style(package, bold=True)} ... ", use_stderr, ) context_msg = "Could not attach metadata." - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - slug_perm = api_get_package_slug_perm( - owner=owner, repo=repo, identifier=package - ) - entry = api_create_metadata( - slug_perm, - content=metadata.content, - content_type=metadata.content_type, - source_identity=metadata.source_identity, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + slug_perm = api_get_package_slug_perm( + owner=owner, repo=repo, identifier=package + ) + entry = api_create_metadata( + slug_perm, + content=metadata.content, + content_type=metadata.content_type, + source_identity=metadata.source_identity, + ) click.secho("OK", fg="green", err=use_stderr) _print_metadata_entry(opts, entry) @@ -358,8 +366,7 @@ def add_metadata( "inline_content", default=None, help=( - "Set replacement metadata content from inline JSON. Cannot be used with " - "--file." + "Set replacement metadata content from inline JSON. Cannot be used with --file." ), ) @click.option( @@ -415,21 +422,19 @@ def update_metadata( ) _echo_action( - "Updating metadata %(metadata)s for %(package)s ... " - % { - "metadata": click.style(metadata_slug_perm, bold=True), - "package": click.style(package, bold=True), - }, + f"Updating metadata {click.style(metadata_slug_perm, bold=True)} for {click.style(package, bold=True)} ... ", use_stderr, ) context_msg = "Could not update package metadata." - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - slug_perm = api_get_package_slug_perm( - owner=owner, repo=repo, identifier=package - ) - entry = api_update_metadata(slug_perm, metadata_slug_perm, **patch_kwargs) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + slug_perm = api_get_package_slug_perm( + owner=owner, repo=repo, identifier=package + ) + entry = api_update_metadata(slug_perm, metadata_slug_perm, **patch_kwargs) click.secho("OK", fg="green", err=use_stderr) _print_metadata_entry(opts, entry) @@ -473,22 +478,24 @@ def remove_metadata(ctx, opts, owner_repo_package, metadata_slug_perm, yes): "package": click.style(package, bold=True), } - prompt = "remove metadata %(metadata)s from package %(package)s" % remove_args + prompt = "remove metadata {metadata} from package {package}".format(**remove_args) if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr): return _echo_action( - "Removing metadata %(metadata)s from %(package)s ... " % remove_args, + "Removing metadata {metadata} from {package} ... ".format(**remove_args), use_stderr, ) context_msg = "Could not remove package metadata." - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - slug_perm = api_get_package_slug_perm( - owner=owner, repo=repo, identifier=package - ) - api_delete_metadata(slug_perm, metadata_slug_perm) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + slug_perm = api_get_package_slug_perm( + owner=owner, repo=repo, identifier=package + ) + api_delete_metadata(slug_perm, metadata_slug_perm) click.secho("OK", fg="green", err=use_stderr) @@ -497,7 +504,4 @@ def remove_metadata(ctx, opts, owner_repo_package, metadata_slug_perm, yes): return click.echo() - click.secho( - "Metadata removed: %(slug)s." - % {"slug": click.style(metadata_slug_perm, bold=True)} - ) + click.secho(f"Metadata removed: {click.style(metadata_slug_perm, bold=True)}.") diff --git a/cloudsmith_cli/cli/commands/metrics/entitlements.py b/cloudsmith_cli/cli/commands/metrics/entitlements.py index 3a1a8d4e..b3d00d27 100644 --- a/cloudsmith_cli/cli/commands/metrics/entitlements.py +++ b/cloudsmith_cli/cli/commands/metrics/entitlements.py @@ -46,9 +46,9 @@ def _print_metrics_table(opts, data): for metric_key in metrics_keys.values(): metric_data = getattr(category_data, metric_key, {}) if hasattr(metric_data, "display"): - value = getattr(metric_data, "display") + value = metric_data.display else: - value = getattr(metric_data, "value") + value = metric_data.value value = str(value or 0) cols.append(click.style(value, fg="green")) rows.append(cols) @@ -126,16 +126,18 @@ def usage(ctx, opts, owner_repo, tokens, start, finish): data = None context_msg = "Failed to get list of metrics!" data = {} - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - if owner and repo: - data = api.get_repository_entitlements_metrics( - owner=owner, repo=repo, tokens=tokens, start=start, finish=finish - ) - elif owner: - data = api.get_namespace_entitlements_metrics( - owner=owner, repo=repo, tokens=tokens, start=start, finish=finish - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + if owner and repo: + data = api.get_repository_entitlements_metrics( + owner=owner, repo=repo, tokens=tokens, start=start, finish=finish + ) + elif owner: + data = api.get_namespace_entitlements_metrics( + owner=owner, repo=repo, tokens=tokens, start=start, finish=finish + ) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/metrics/packages.py b/cloudsmith_cli/cli/commands/metrics/packages.py index bb7e7053..92b237ed 100644 --- a/cloudsmith_cli/cli/commands/metrics/packages.py +++ b/cloudsmith_cli/cli/commands/metrics/packages.py @@ -46,9 +46,9 @@ def _print_metrics_table(opts, data): for metric_key in metrics_keys.values(): metric_data = getattr(category_data, metric_key, {}) if hasattr(metric_data, "display"): - value = getattr(metric_data, "display") + value = metric_data.display else: - value = getattr(metric_data, "value") + value = metric_data.value value = str(value or 0) cols.append(click.style(value, fg="green")) rows.append(cols) @@ -117,11 +117,13 @@ def usage(ctx, opts, owner_repo, packages, start, finish): owner, repo = owner_repo context_msg = "Failed to get list of metrics!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - data = api.get_repository_packages_metrics( - owner=owner, repo=repo, packages=packages, start=start, finish=finish - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + data = api.get_repository_packages_metrics( + owner=owner, repo=repo, packages=packages, start=start, finish=finish + ) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/move.py b/cloudsmith_cli/cli/commands/move.py index 94822134..4ea41b9e 100644 --- a/cloudsmith_cli/cli/commands/move.py +++ b/cloudsmith_cli/cli/commands/move.py @@ -72,24 +72,26 @@ def move( use_stderr = utils.should_use_stderr(opts) - prompt = "move the %(slug)s from %(source)s to %(dest)s" % move_args + prompt = "move the {slug} from {source} to {dest}".format(**move_args) if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr): return click.echo( - "Moving %(slug)s package from %(source)s to %(dest)s ... " % move_args, + "Moving {slug} package from {source} to {dest} ... ".format(**move_args), nl=False, err=use_stderr, ) context_msg = "Failed to move package!" - with handle_api_exceptions( - ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + with ( + handle_api_exceptions( + ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + ), + maybe_spinner(opts), ): - with maybe_spinner(opts): - _, new_slug = move_package( - owner=owner, repo=source, identifier=slug, destination=destination - ) + _, new_slug = move_package( + owner=owner, repo=source, identifier=slug, destination=destination + ) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/policy/deny.py b/cloudsmith_cli/cli/commands/policy/deny.py index b555b83a..e60b2e2e 100644 --- a/cloudsmith_cli/cli/commands/policy/deny.py +++ b/cloudsmith_cli/cli/commands/policy/deny.py @@ -67,11 +67,13 @@ def list_deny_policies(ctx, opts, owner, page, page_size, page_all): click.echo("Getting deny policies ... ", nl=False, err=use_stderr) context_msg = "Failed to get deny policies!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - data, page_info = paginate_results( - orgs.list_deny_policies, page_all, page, page_size, owner=owner - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + data, page_info = paginate_results( + orgs.list_deny_policies, page_all, page, page_size, owner=owner + ) click.secho("OK", fg="green", err=use_stderr) @@ -114,19 +116,17 @@ def create_deny_policy(ctx, opts, owner, policy_config_file): ) click.secho( - "Creating %(name)s deny policy for the %(owner)s namespace ..." - % { - "name": click.style(policy_name, bold=True), - "owner": click.style(owner, bold=True), - }, + f"Creating {click.style(policy_name, bold=True)} deny policy for the {click.style(owner, bold=True)} namespace ...", nl=False, err=use_stderr, ) context_msg = "Failed to create the deny policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - data = orgs.create_deny_policy(owner=owner, policy_config=policy_config) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + data = orgs.create_deny_policy(owner=owner, policy_config=policy_config) click.secho("OK", fg="green", err=use_stderr) @@ -150,9 +150,11 @@ def get_deny_policy(ctx, opts, owner, identifier): click.echo("Getting deny policy ... ", nl=False, err=use_stderr) context_msg = "Failed to get deny policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - data = orgs.get_deny_policy(owner=owner, slug_perm=identifier) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + data = orgs.get_deny_policy(owner=owner, slug_perm=identifier) click.secho("OK", fg="green", err=use_stderr) @@ -179,21 +181,19 @@ def update_deny_policy(ctx, opts, owner, identifier, policy_config_file): policy_config = json.load(policy_config_file) click.secho( - "Updating %(identifier)s deny policy in the %(owner)s namespace ..." - % { - "identifier": click.style(identifier, bold=True), - "owner": click.style(owner, bold=True), - }, + f"Updating {click.style(identifier, bold=True)} deny policy in the {click.style(owner, bold=True)} namespace ...", nl=False, err=use_stderr, ) context_msg = "Failed to update the deny policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - data = orgs.update_deny_policy( - owner=owner, slug_perm=identifier, policy_config=policy_config - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + data = orgs.update_deny_policy( + owner=owner, slug_perm=identifier, policy_config=policy_config + ) click.secho("OK", fg="green", err=use_stderr) @@ -226,8 +226,9 @@ def delete_deny_policy(ctx, opts, owner, identifier, yes): } prompt = ( - "delete the %(identifier)s deny policy from the %(namespace)s namespace" - % delete_args + "delete the {identifier} deny policy from the {namespace} namespace".format( + **delete_args + ) ) use_stderr = utils.should_use_stderr(opts) @@ -235,14 +236,18 @@ def delete_deny_policy(ctx, opts, owner, identifier, yes): return click.secho( - "Deleting %(identifier)s from the %(namespace)s namespace ... " % delete_args, + "Deleting {identifier} from the {namespace} namespace ... ".format( + **delete_args + ), nl=False, err=use_stderr, ) context_msg = "Failed to delete the deny policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - orgs.delete_deny_policy(owner=owner, slug_perm=identifier) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + orgs.delete_deny_policy(owner=owner, slug_perm=identifier) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/policy/license.py b/cloudsmith_cli/cli/commands/policy/license.py index 3e1063eb..7fae19ab 100644 --- a/cloudsmith_cli/cli/commands/policy/license.py +++ b/cloudsmith_cli/cli/commands/policy/license.py @@ -105,11 +105,13 @@ def ls(ctx, opts, owner, page, page_size, page_all): click.echo("Getting license policies ... ", nl=False, err=use_stderr) context_msg = "Failed to get license policies!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - policies, page_info = paginate_results( - api.list_license_policies, page_all, page, page_size, owner=owner - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + policies, page_info = paginate_results( + api.list_license_policies, page_all, page, page_size, owner=owner + ) click.secho("OK", fg="green", err=use_stderr) @@ -183,19 +185,17 @@ def create(ctx, opts, owner, policy_config_file): ) click.secho( - "Creating %(name)s license policy for the %(owner)s namespace ..." - % { - "name": click.style(policy_name, bold=True), - "owner": click.style(owner, bold=True), - }, + f"Creating {click.style(policy_name, bold=True)} license policy for the {click.style(owner, bold=True)} namespace ...", nl=False, err=use_stderr, ) context_msg = "Failed to create the license policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - policies = [api.create_license_policy(owner, policy_config)] + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + policies = [api.create_license_policy(owner, policy_config)] click.secho("OK", fg="green", err=use_stderr) @@ -252,19 +252,17 @@ def update(ctx, opts, owner, identifier, policy_config_file): policy_config = json.load(policy_config_file) click.secho( - "Updating %(slug_perm)s license policy in the %(owner)s namespace ..." - % { - "slug_perm": click.style(identifier, bold=True), - "owner": click.style(owner, bold=True), - }, + f"Updating {click.style(identifier, bold=True)} license policy in the {click.style(owner, bold=True)} namespace ...", nl=False, err=use_stderr, ) context_msg = "Failed to update the license policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - policies = [api.update_license_policy(owner, identifier, policy_config)] + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + policies = [api.update_license_policy(owner, identifier, policy_config)] click.secho("OK", fg="green", err=use_stderr) @@ -313,8 +311,9 @@ def delete(ctx, opts, owner, identifier, yes): } prompt = ( - "delete the %(slug_perm)s license policy from the %(namespace)s namespace" - % delete_args + "delete the {slug_perm} license policy from the {namespace} namespace".format( + **delete_args + ) ) use_stderr = utils.should_use_stderr(opts) @@ -322,14 +321,18 @@ def delete(ctx, opts, owner, identifier, yes): return click.secho( - "Deleting %(slug_perm)s from the %(namespace)s namespace ... " % delete_args, + "Deleting {slug_perm} from the {namespace} namespace ... ".format( + **delete_args + ), nl=False, err=use_stderr, ) context_msg = "Failed to delete the license policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api.delete_license_policy(owner=owner, slug_perm=identifier) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api.delete_license_policy(owner=owner, slug_perm=identifier) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/policy/vulnerability.py b/cloudsmith_cli/cli/commands/policy/vulnerability.py index ff72b676..88e8b9c1 100644 --- a/cloudsmith_cli/cli/commands/policy/vulnerability.py +++ b/cloudsmith_cli/cli/commands/policy/vulnerability.py @@ -95,11 +95,13 @@ def ls(ctx, opts, owner, page, page_size, page_all): click.echo("Getting vulnerability policies ... ", nl=False, err=use_stderr) context_msg = "Failed to get package vulnerability policies!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - policies, page_info = paginate_results( - api.list_vulnerability_policies, page_all, page, page_size, owner=owner - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + policies, page_info = paginate_results( + api.list_vulnerability_policies, page_all, page, page_size, owner=owner + ) click.secho("OK", fg="green", err=use_stderr) @@ -167,19 +169,17 @@ def create(ctx, opts, owner, policy_config_file): ) click.secho( - "Creating %(name)s vulnerability policy for the %(owner)s namespace ..." - % { - "name": click.style(policy_name, bold=True), - "owner": click.style(owner, bold=True), - }, + f"Creating {click.style(policy_name, bold=True)} vulnerability policy for the {click.style(owner, bold=True)} namespace ...", nl=False, err=use_stderr, ) context_msg = "Failed to create the vulnerability policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - policies = [api.create_vulnerability_policy(owner, policy_config)] + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + policies = [api.create_vulnerability_policy(owner, policy_config)] click.secho("OK", fg="green", err=use_stderr) @@ -236,21 +236,17 @@ def update(ctx, opts, owner, identifier, policy_config_file): policy_config = json.load(policy_config_file) click.secho( - "Updating %(slug_perm)s vulnerability policy in the %(owner)s namespace ..." - % { - "slug_perm": click.style(identifier, bold=True), - "owner": click.style(owner, bold=True), - }, + f"Updating {click.style(identifier, bold=True)} vulnerability policy in the {click.style(owner, bold=True)} namespace ...", nl=False, err=use_stderr, ) context_msg = "Failed to update the vulnerability policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - policies = [ - api.update_vulnerability_policy(owner, identifier, policy_config) - ] + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + policies = [api.update_vulnerability_policy(owner, identifier, policy_config)] click.secho("OK", fg="green", err=use_stderr) @@ -298,9 +294,8 @@ def delete(ctx, opts, owner, identifier, yes): "slug_perm": click.style(identifier, bold=True), } - prompt = ( - "delete the %(slug_perm)s vulnerability policy from the %(namespace)s namespace" - % delete_args + prompt = "delete the {slug_perm} vulnerability policy from the {namespace} namespace".format( + **delete_args ) use_stderr = utils.should_use_stderr(opts) @@ -309,14 +304,18 @@ def delete(ctx, opts, owner, identifier, yes): return click.secho( - "Deleting %(slug_perm)s from the %(namespace)s namespace ... " % delete_args, + "Deleting {slug_perm} from the {namespace} namespace ... ".format( + **delete_args + ), nl=False, err=use_stderr, ) context_msg = "Failed to delete the vulnerability policy!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api.delete_vulnerability_policy(owner=owner, slug_perm=identifier) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api.delete_vulnerability_policy(owner=owner, slug_perm=identifier) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/push.py b/cloudsmith_cli/cli/commands/push.py index ab107211..d16fa3e2 100644 --- a/cloudsmith_cli/cli/commands/push.py +++ b/cloudsmith_cli/cli/commands/push.py @@ -6,7 +6,7 @@ import os import shlex import time -from datetime import datetime +from datetime import datetime, timezone import click @@ -16,17 +16,25 @@ CHUNK_SIZE, multi_part_upload_file, request_file_upload, - upload_file as api_upload_file, validate_request_file_upload, ) +from ...core.api.files import ( + upload_file as api_upload_file, +) from ...core.api.metadata import ( create_metadata as api_create_metadata, +) +from ...core.api.metadata import ( validate_metadata as api_validate_metadata, ) from ...core.api.packages import ( create_package as api_create_package, +) +from ...core.api.packages import ( get_package_formats, get_package_status, +) +from ...core.api.packages import ( validate_create_package as api_validate_create_package, ) from .. import command, decorators, utils, validators @@ -107,7 +115,7 @@ def _metadata_content_failure_info(exc): def _warn_metadata_failure(failure_info): click.secho( - "Metadata content is invalid: %(error)s" % failure_info, + "Metadata content is invalid: {error}".format(**failure_info), fg="yellow", err=True, ) @@ -272,8 +280,8 @@ def validate_metadata_payload( use_stderr = utils.should_use_stderr(opts) if source: - message = "Validating metadata content from {source} ... ".format( - source=click.style(source, bold=True), + message = ( + f"Validating metadata content from {click.style(source, bold=True)} ... " ) else: message = "Validating metadata content ... " @@ -362,8 +370,7 @@ def attach_metadata_to_package( use_stderr = utils.should_use_stderr(opts) click.echo( - "Attaching metadata to package %(slug)s ... " - % {"slug": click.style(slug_perm, bold=True)}, + f"Attaching metadata to package {click.style(slug_perm, bold=True)} ... ", nl=False, err=use_stderr, ) @@ -442,11 +449,7 @@ def attach_metadata_to_package( slug=click.style(slug, fg="green"), ) click.echo( - "Metadata attached: %(path)s/%(metadata)s" - % { - "path": package_path, - "metadata": click.style(metadata_slug_perm, bold=True), - }, + f"Metadata attached: {package_path}/{click.style(metadata_slug_perm, bold=True)}", err=use_stderr, ) @@ -465,20 +468,21 @@ def validate_upload_file(ctx, opts, owner, repo, filepath, skip_errors): use_stderr = utils.should_use_stderr(opts) click.echo( - "Checking %(filename)s file upload parameters ... " - % {"filename": click.style(basename, bold=True)}, + f"Checking {click.style(basename, bold=True)} file upload parameters ... ", nl=False, err=use_stderr, ) context_msg = "Failed to validate upload parameters!" - with handle_api_exceptions( - ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + with ( + handle_api_exceptions( + ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + ), + maybe_spinner(opts), ): - with maybe_spinner(opts): - md5_checksum = validate_request_file_upload( - owner=owner, repo=repo, filepath=filename - ) + md5_checksum = validate_request_file_upload( + owner=owner, repo=repo, filepath=filename + ) click.secho("OK", fg="green", err=use_stderr) @@ -497,24 +501,25 @@ def upload_file(ctx, opts, owner, repo, filepath, skip_errors, md5_checksum): use_stderr = utils.should_use_stderr(opts) click.echo( - "Requesting file upload for %(filename)s ... " - % {"filename": click.style(basename, bold=True)}, + f"Requesting file upload for {click.style(basename, bold=True)} ... ", nl=False, err=use_stderr, ) context_msg = "Failed to request file upload!" - with handle_api_exceptions( - ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + with ( + handle_api_exceptions( + ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + ), + maybe_spinner(opts), ): - with maybe_spinner(opts): - identifier, upload_url, upload_fields = request_file_upload( - owner=owner, - repo=repo, - filepath=filename, - md5_checksum=md5_checksum, - is_multi_part_upload=is_multi_part_upload, - ) + identifier, upload_url, upload_fields = request_file_upload( + owner=owner, + repo=repo, + filepath=filename, + md5_checksum=md5_checksum, + is_multi_part_upload=is_multi_part_upload, + ) click.secho("OK", fg="green", err=use_stderr) @@ -590,20 +595,21 @@ def validate_create_package( use_stderr = utils.should_use_stderr(opts) click.echo( - "Checking %(package_type)s package upload parameters ... " - % {"package_type": click.style(package_type, bold=True)}, + f"Checking {click.style(package_type, bold=True)} package upload parameters ... ", nl=False, err=use_stderr, ) context_msg = "Failed to validate upload parameters!" - with handle_api_exceptions( - ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + with ( + handle_api_exceptions( + ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + ), + maybe_spinner(opts), ): - with maybe_spinner(opts): - api_validate_create_package( - package_format=package_type, owner=owner, repo=repo, **kwargs - ) + api_validate_create_package( + package_format=package_type, owner=owner, repo=repo, **kwargs + ) click.secho("OK", fg="green", err=use_stderr) return True @@ -614,31 +620,31 @@ def create_package(ctx, opts, owner, repo, package_type, skip_errors, **kwargs): use_stderr = utils.should_use_stderr(opts) click.echo( - "Creating a new %(package_type)s package ... " - % {"package_type": click.style(package_type, bold=True)}, + f"Creating a new {click.style(package_type, bold=True)} package ... ", nl=False, err=use_stderr, ) context_msg = "Failed to create package!" - with handle_api_exceptions( - ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + with ( + handle_api_exceptions( + ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + ), + maybe_spinner(opts), ): - with maybe_spinner(opts): - slug_perm, slug = api_create_package( - package_format=package_type, owner=owner, repo=repo, **kwargs - ) + slug_perm, slug = api_create_package( + package_format=package_type, owner=owner, repo=repo, **kwargs + ) click.secho("OK", fg="green", err=use_stderr) click.echo( - "Created: %(owner)s/%(repo)s/%(slug)s (%(slug_perm)s)" - % { - "owner": click.style(owner, fg="magenta"), - "repo": click.style(repo, fg="magenta"), - "slug": click.style(slug, fg="green"), - "slug_perm": click.style(slug_perm, bold=True), - }, + "Created: {owner}/{repo}/{slug} ({slug_perm})".format( + owner=click.style(owner, fg="magenta"), + repo=click.style(repo, fg="magenta"), + slug=click.style(slug, fg="green"), + slug_perm=click.style(slug_perm, bold=True), + ), err=use_stderr, ) @@ -669,7 +675,7 @@ def display_status(current): fg="cyan", ) - start = datetime.now() + start = datetime.now(tz=timezone.utc) context_msg = "Failed to synchronise file!" with handle_api_exceptions( ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors @@ -726,26 +732,24 @@ def display_status(current): if left > 0: pb.update(left) - end = datetime.now() + end = datetime.now(tz=timezone.utc) seconds = (end - start).total_seconds() click.echo(err=use_stderr) if ok: click.secho( - "Package synchronised successfully in %(seconds)s second(s)!" - % {"seconds": click.style(str(seconds), bold=True)}, + f"Package synchronised successfully in {click.style(str(seconds), bold=True)} second(s)!", fg="green", err=use_stderr, ) return click.secho( - "Package failed to synchronise in %(seconds)s during stage: %(stage)s" - % { - "seconds": click.style(str(seconds), bold=True), - "stage": click.style(stage_str or "Unknown", fg="yellow"), - }, + "Package failed to synchronise in {seconds} during stage: {stage}".format( + seconds=click.style(str(seconds), bold=True), + stage=click.style(stage_str or "Unknown", fg="yellow"), + ), fg="red", err=use_stderr, ) @@ -771,11 +775,10 @@ def display_status(current): if attempts + 1 > 0: # Show attempts upto and including zero attempts left click.secho( - "Attempts left: %(left)s (%(action)s)" - % { - "left": click.style(str(attempts), bold=True), - "action": "trying again" if attempts > 0 else "giving up", - }, + "Attempts left: {left} ({action})".format( + left=click.style(str(attempts), bold=True), + action="trying again" if attempts > 0 else "giving up", + ), err=use_stderr, ) click.echo(err=use_stderr) @@ -1016,7 +1019,7 @@ def upload_files_and_create_package( return slug_perm, slug -def create_push_handlers(): # noqa: C901 +def create_push_handlers(): """Create a handler for upload per package format.""" # pylint: disable=fixme # HACK: hacky territory - Dynamically generate a handler for each of the diff --git a/cloudsmith_cli/cli/commands/quarantine.py b/cloudsmith_cli/cli/commands/quarantine.py index 07024f55..a4039cf6 100644 --- a/cloudsmith_cli/cli/commands/quarantine.py +++ b/cloudsmith_cli/cli/commands/quarantine.py @@ -72,19 +72,17 @@ def add_quarantine(ctx, opts, owner_repo_package, page, page_size, page_all): use_stderr = utils.should_use_stderr(opts) click.echo( - "Adding %(repository)s/%(package_slug)s to quarantine... " - % { - "repository": click.style(repo, bold=True), - "package_slug": click.style(slug, bold=True), - }, + f"Adding {click.style(repo, bold=True)}/{click.style(slug, bold=True)} to quarantine... ", nl=False, err=use_stderr, ) context_msg = "Failed quarantine!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api.quarantine_package(owner=owner, repo=repo, identifier=slug) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api.quarantine_package(owner=owner, repo=repo, identifier=slug) click.secho("OK", fg="green", err=use_stderr) @@ -121,19 +119,17 @@ def remove_quarantine(ctx, opts, owner_repo_package, page, page_size, page_all): use_stderr = utils.should_use_stderr(opts) click.echo( - "Removing %(repository)s/%(package_slug)s from quarantine... " - % { - "repository": click.style(repo, bold=True), - "package_slug": click.style(slug, bold=True), - }, + f"Removing {click.style(repo, bold=True)}/{click.style(slug, bold=True)} from quarantine... ", nl=False, err=use_stderr, ) context_msg = "Failed quarantine!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api.quarantine_restore_package(owner=owner, repo=repo, identifier=slug) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api.quarantine_restore_package(owner=owner, repo=repo, identifier=slug) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/quota/history.py b/cloudsmith_cli/cli/commands/quota/history.py index b465b539..f445f86e 100644 --- a/cloudsmith_cli/cli/commands/quota/history.py +++ b/cloudsmith_cli/cli/commands/quota/history.py @@ -110,9 +110,11 @@ def usage(ctx, opts, owner, oss): owner = owner[0] context_msg = "Failed to get quota!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - quota_ = api.quota_history(owner=owner, oss=oss) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + quota_ = api.quota_history(owner=owner, oss=oss) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/quota/quota.py b/cloudsmith_cli/cli/commands/quota/quota.py index 94ddb215..42b352b1 100644 --- a/cloudsmith_cli/cli/commands/quota/quota.py +++ b/cloudsmith_cli/cli/commands/quota/quota.py @@ -95,9 +95,11 @@ def usage(ctx, opts, owner, oss): owner = owner[0] context_msg = "Failed to get quota!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - quota_ = api.quota_limits(owner=owner, oss=oss) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + quota_ = api.quota_limits(owner=owner, oss=oss) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/repos.py b/cloudsmith_cli/cli/commands/repos.py index 428afdea..de3a3b89 100644 --- a/cloudsmith_cli/cli/commands/repos.py +++ b/cloudsmith_cli/cli/commands/repos.py @@ -35,11 +35,10 @@ def print_repositories(opts, data, page_info=None, show_list_info=True, page_all click.style(str(repo["package_group_count"]), fg="blue"), click.style(str(repo["num_downloads"]), fg="blue"), click.style(str(repo["size_str"]), fg="blue"), - "%(owner_slug)s/%(slug)s" - % { - "owner_slug": click.style(repo["namespace"], fg="magenta"), - "slug": click.style(repo["slug"], fg="green"), - }, + "{owner_slug}/{slug}".format( + owner_slug=click.style(repo["namespace"], fg="magenta"), + slug=click.style(repo["slug"], fg="green"), + ), ] ) @@ -127,11 +126,13 @@ def get(ctx, opts, owner_repo, page, page_size, page_all): click.echo("Getting list of repositories ... ", nl=False, err=use_stderr) context_msg = "Failed to get list of repositories!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - repos_, page_info = paginate_results( - api.list_repos, page_all, page, page_size, owner=owner, repo=repo - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + repos_, page_info = paginate_results( + api.list_repos, page_all, page, page_size, owner=owner, repo=repo + ) click.secho("OK", fg="green", err=use_stderr) @@ -191,19 +192,17 @@ def create(ctx, opts, owner, repo_config_file): ) click.secho( - "Creating %(name)s repository for the %(owner)s namespace ..." - % { - "name": click.style(repo_name, bold=True), - "owner": click.style(owner, bold=True), - }, + f"Creating {click.style(repo_name, bold=True)} repository for the {click.style(owner, bold=True)} namespace ...", nl=False, err=use_stderr, ) context_msg = "Failed to create the repository!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - repository = api.create_repo(owner, repo_config) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + repository = api.create_repo(owner, repo_config) click.secho("OK", fg="green", err=use_stderr) @@ -254,19 +253,17 @@ def update(ctx, opts, owner_repo, repo_config_file): repo_config = json.load(repo_config_file) click.secho( - "Updating %(name)s repository in the %(owner)s namespace ..." - % { - "name": click.style(repo, bold=True), - "owner": click.style(owner, bold=True), - }, + f"Updating {click.style(repo, bold=True)} repository in the {click.style(owner, bold=True)} namespace ...", nl=False, err=use_stderr, ) context_msg = "Failed to update the repository!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - repository = api.update_repo(owner, repo, repo_config) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + repository = api.update_repo(owner, repo, repo_config) click.secho("OK", fg="green", err=use_stderr) @@ -311,20 +308,26 @@ def delete(ctx, opts, owner_repo, yes): "repository": click.style(repo, bold=True), } - prompt = "delete the %(repository)s from the %(namespace)s namespace" % delete_args + prompt = "delete the {repository} from the {namespace} namespace".format( + **delete_args + ) # Use stderr for messages if the output is something else (e.g. JSON) use_stderr = utils.should_use_stderr(opts) if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr): return click.secho( - "Deleting %(repository)s from the %(namespace)s namespace ... " % delete_args, + "Deleting {repository} from the {namespace} namespace ... ".format( + **delete_args + ), nl=False, ) context_msg = "Failed to delete the repository!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api.delete_repo(owner=owner, repo=repo) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api.delete_repo(owner=owner, repo=repo) click.secho("OK", fg="green") diff --git a/cloudsmith_cli/cli/commands/resync.py b/cloudsmith_cli/cli/commands/resync.py index b60e7cef..7a0807fa 100644 --- a/cloudsmith_cli/cli/commands/resync.py +++ b/cloudsmith_cli/cli/commands/resync.py @@ -74,17 +74,18 @@ def resync_package(ctx, opts, owner, repo, slug, skip_errors): """Resynchronise a package.""" use_stderr = utils.should_use_stderr(opts) click.echo( - "Resynchonising the %(slug)s package ... " - % {"slug": click.style(slug, bold=True)}, + f"Resynchonising the {click.style(slug, bold=True)} package ... ", nl=False, err=use_stderr, ) context_msg = "Failed to resynchronise package!" - with handle_api_exceptions( - ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + with ( + handle_api_exceptions( + ctx, opts=opts, context_msg=context_msg, reraise_on_error=skip_errors + ), + maybe_spinner(opts), ): - with maybe_spinner(opts): - api_resync_package(owner=owner, repo=repo, identifier=slug) + api_resync_package(owner=owner, repo=repo, identifier=slug) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/status.py b/cloudsmith_cli/cli/commands/status.py index b86a9e20..7261eed8 100644 --- a/cloudsmith_cli/cli/commands/status.py +++ b/cloudsmith_cli/cli/commands/status.py @@ -35,21 +35,18 @@ def status(ctx, opts, owner_repo_package): use_stderr = utils.should_use_stderr(opts) click.echo( - "Getting status of %(package)s in %(owner)s/%(repo)s ... " - % { - "owner": click.style(owner, bold=True), - "repo": click.style(repo, bold=True), - "package": click.style(slug, bold=True), - }, + f"Getting status of {click.style(slug, bold=True)} in {click.style(owner, bold=True)}/{click.style(repo, bold=True)} ... ", nl=False, err=use_stderr, ) context_msg = "Failed to get status of package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - res = get_package_status(owner, repo, slug) - ok, failed, _, status_str, stage_str, reason = res + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + res = get_package_status(owner, repo, slug) + ok, failed, _, status_str, stage_str, reason = res click.secho("OK", fg="green", err=use_stderr) @@ -79,8 +76,7 @@ def status(ctx, opts, owner_repo_package): return click.secho( - "The package status is: %(status)s" - % {"status": click.style(package_status, fg=status_colour)}, + f"The package status is: {click.style(package_status, fg=status_colour)}", err=use_stderr, ) diff --git a/cloudsmith_cli/cli/commands/tags.py b/cloudsmith_cli/cli/commands/tags.py index 07275195..bb5f4e27 100644 --- a/cloudsmith_cli/cli/commands/tags.py +++ b/cloudsmith_cli/cli/commands/tags.py @@ -6,6 +6,8 @@ from ...core.api.packages import ( get_package_tags as api_get_package_tags, +) +from ...core.api.packages import ( tag_package as api_tag_package, ) from .. import command, decorators, utils, validators @@ -98,18 +100,19 @@ def list_tags(ctx, opts, owner_repo_package): use_stderr = utils.should_use_stderr(opts) click.echo( - "Listing tags for the '%(package)s' package ... " - % {"package": click.style(package, bold=True)}, + f"Listing tags for the '{click.style(package, bold=True)}' package ... ", nl=False, err=use_stderr, ) context_msg = "Failed to list tags for the package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - package_tags, package_tags_immutable = api_get_package_tags( - owner=owner, repo=repo, identifier=package - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + package_tags, package_tags_immutable = api_get_package_tags( + owner=owner, repo=repo, identifier=package + ) click.secho("OK", fg="green", err=use_stderr) @@ -164,25 +167,26 @@ def add_tags(ctx, opts, owner_repo_package, tags, immutable): use_stderr = utils.should_use_stderr(opts) click.echo( - "Adding '%(tags)s' tag%(s)s to the '%(package)s' package ... " - % { - "package": click.style(package, bold=True), - "tags": click.style(", ".join(tags or [])), - "s": "s" if len(tags) != 1 else "", - }, + "Adding '{tags}' tag{s} to the '{package}' package ... ".format( + package=click.style(package, bold=True), + tags=click.style(", ".join(tags or [])), + s="s" if len(tags) != 1 else "", + ), nl=False, err=use_stderr, ) context_msg = "Failed to add tags to package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - package_tags, package_tags_immutable = api_tag_package( - owner=owner, - repo=repo, - identifier=package, - data={"action": "add", "tags": tags, "is_immutable": immutable}, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + package_tags, package_tags_immutable = api_tag_package( + owner=owner, + repo=repo, + identifier=package, + data={"action": "add", "tags": tags, "is_immutable": immutable}, + ) click.secho("OK", fg="green", err=use_stderr) @@ -221,18 +225,19 @@ def clear_tags(ctx, opts, owner_repo_package): use_stderr = utils.should_use_stderr(opts) click.echo( - "Clearing tags on the '%(package)s' package ... " - % {"package": click.style(package, bold=True)}, + f"Clearing tags on the '{click.style(package, bold=True)}' package ... ", nl=False, err=use_stderr, ) context_msg = "Failed to clear tags on package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - package_tags, package_tags_immutable = api_tag_package( - owner=owner, repo=repo, identifier=package, data={"action": "clear"} - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + package_tags, package_tags_immutable = api_tag_package( + owner=owner, repo=repo, identifier=package, data={"action": "clear"} + ) click.secho("OK", fg="green", err=use_stderr) @@ -277,25 +282,26 @@ def remove_tags(ctx, opts, owner_repo_package, tags): use_stderr = utils.should_use_stderr(opts) click.echo( - "Removing '%(tags)s' tag%(s)s from the '%(package)s' package ... " - % { - "package": click.style(package, bold=True), - "tags": click.style(", ".join(tags or [])), - "s": "s" if len(tags) != 1 else "", - }, + "Removing '{tags}' tag{s} from the '{package}' package ... ".format( + package=click.style(package, bold=True), + tags=click.style(", ".join(tags or [])), + s="s" if len(tags) != 1 else "", + ), nl=False, err=use_stderr, ) context_msg = "Failed to remove tags from package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - package_tags, package_tags_immutable = api_tag_package( - owner=owner, - repo=repo, - identifier=package, - data={"action": "remove", "tags": tags}, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + package_tags, package_tags_immutable = api_tag_package( + owner=owner, + repo=repo, + identifier=package, + data={"action": "remove", "tags": tags}, + ) click.secho("OK", fg="green", err=use_stderr) @@ -350,25 +356,26 @@ def replace_tags(ctx, opts, owner_repo_package, tags, immutable): use_stderr = utils.should_use_stderr(opts) click.echo( - "Replacing existing with '%(tags)s' tag%(s)s on the '%(package)s' package ... " - % { - "package": click.style(package, bold=True), - "tags": click.style(", ".join(tags or [])), - "s": "s" if len(tags) != 1 else "", - }, + "Replacing existing with '{tags}' tag{s} on the '{package}' package ... ".format( + package=click.style(package, bold=True), + tags=click.style(", ".join(tags or [])), + s="s" if len(tags) != 1 else "", + ), nl=False, err=use_stderr, ) context_msg = "Failed to replace tags on package!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - package_tags, package_tags_immutable = api_tag_package( - owner=owner, - repo=repo, - identifier=package, - data={"action": "replace", "tags": tags, "is_immutable": immutable}, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + package_tags, package_tags_immutable = api_tag_package( + owner=owner, + repo=repo, + identifier=package, + data={"action": "replace", "tags": tags, "is_immutable": immutable}, + ) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/tokens.py b/cloudsmith_cli/cli/commands/tokens.py index d1aff28f..1ae56492 100644 --- a/cloudsmith_cli/cli/commands/tokens.py +++ b/cloudsmith_cli/cli/commands/tokens.py @@ -1,6 +1,7 @@ import click -from ...core.api import exceptions, user as api +from ...core.api import exceptions +from ...core.api import user as api from ...core.config import create_config_files, new_config_messaging from .. import command, decorators, utils from ..exceptions import handle_api_exceptions @@ -18,13 +19,12 @@ def handle_duplicate_token_error(exc, ctx, opts, save_config, force, json): and exc.detail and "User has already created an API key" in exc.detail ): - if not force: - if not click.confirm( - "User already has a token. Would you like to recreate it?", - abort=False, - err=json, - ): - return None + if not force and not click.confirm( + "User already has a token. Would you like to recreate it?", + abort=False, + err=json, + ): + return None return refresh_existing_token_interactive( ctx, opts, save_config=save_config, force=force, json=json ) @@ -75,9 +75,11 @@ def request_api_key(ctx, opts, save_config=False): ) # List tokens and select the first one - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with utils.maybe_spinner(opts): - api_tokens = api.list_user_tokens() + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + utils.maybe_spinner(opts), + ): + api_tokens = api.list_user_tokens() if not api_tokens: raise click.ClickException("No existing tokens found to rotate.") @@ -85,9 +87,11 @@ def request_api_key(ctx, opts, save_config=False): token_slug = api_tokens[0].slug_perm # Refresh the token - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with utils.maybe_spinner(opts): - new_token = api.refresh_user_token(token_slug) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + utils.maybe_spinner(opts), + ): + new_token = api.refresh_user_token(token_slug) if save_config: create, has_errors = create_config_files( @@ -99,7 +103,7 @@ def request_api_key(ctx, opts, save_config=False): # Other errors - use the handler with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - raise exc + raise @main.group(cls=command.AliasGroup, name="tokens") @@ -123,9 +127,11 @@ def list_tokens(ctx, opts): click.echo("Retrieving API tokens... ", nl=False, err=use_stderr) context_msg = "Failed to retrieve API tokens!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with utils.maybe_spinner(opts): - tokens = api.list_user_tokens() + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + utils.maybe_spinner(opts), + ): + tokens = api.list_user_tokens() click.secho("OK", fg="green", err=use_stderr) if utils.maybe_print_as_json(opts, tokens): @@ -193,9 +199,8 @@ def refresh(ctx, opts, token_slug, force, save_config): ctx, opts, token_slug, save_config, force ) - if new_token: - if utils.maybe_print_as_json(opts, new_token): - return new_token + if new_token and utils.maybe_print_as_json(opts, new_token): + return new_token def print_tokens(tokens): @@ -258,9 +263,11 @@ def refresh_existing_token_interactive( click.echo(f"Refreshing token {token_slug}... ", nl=False, err=json) try: - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with utils.maybe_spinner(opts): - new_token = api.refresh_user_token(token_slug) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + utils.maybe_spinner(opts), + ): + new_token = api.refresh_user_token(token_slug) if save_config: create, has_errors = create_config_files( diff --git a/cloudsmith_cli/cli/commands/upstream.py b/cloudsmith_cli/cli/commands/upstream.py index d6252613..df10effa 100644 --- a/cloudsmith_cli/cli/commands/upstream.py +++ b/cloudsmith_cli/cli/commands/upstream.py @@ -151,14 +151,11 @@ def build_upstream_group_func(upstream_fmt): def func(ctx, opts): pass - func.__doc__ = ( - """ - Manage %s upstreams for a repository. + func.__doc__ = f""" + Manage {upstream_fmt} upstreams for a repository. See the help for subcommands for more information on each. """ - % upstream_fmt - ) return func @@ -181,17 +178,19 @@ def func(ctx, opts, owner_repo, page, page_size, page_all): click.echo("Getting upstreams... ", nl=False, err=use_stderr) context_msg = "Failed to get upstreams!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - upstreams, page_info = paginate_results( - api.list_upstreams, - page_all, - page, - page_size, - owner=owner, - repo=repo, - upstream_format=upstream_fmt, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + upstreams, page_info = paginate_results( + api.list_upstreams, + page_all, + page, + page_size, + owner=owner, + repo=repo, + upstream_format=upstream_fmt, + ) if not use_stderr: click.secho("OK", fg="green", err=use_stderr) @@ -244,23 +243,20 @@ def func(ctx, opts, owner_repo, upstream_config_file): if not use_stderr: click.secho( - 'Creating "%(name)s" upstream for the %(owner)s/%(repo)s repository...' - % { - "name": click.style(upstream_name, bold=True), - "owner": click.style(owner, bold=True), - "repo": click.style(repo, bold=True), - }, + f'Creating "{click.style(upstream_name, bold=True)}" upstream for the {click.style(owner, bold=True)}/{click.style(repo, bold=True)} repository...', nl=False, err=use_stderr, ) context_msg = "Failed to create the upstream!" - with handle_api_exceptions(ctx, opts, context_msg=context_msg): - with maybe_spinner(opts): - upstream_resp_data = api.create_upstream( - owner, repo, upstream_fmt, upstream_config - ) + with ( + handle_api_exceptions(ctx, opts, context_msg=context_msg), + maybe_spinner(opts), + ): + upstream_resp_data = api.create_upstream( + owner, repo, upstream_fmt, upstream_config + ) if not use_stderr: click.secho("OK", fg="green", err=use_stderr) @@ -326,22 +322,19 @@ def func(ctx, opts, owner_repo_slug_perm, upstream_config_file): if not use_stderr: click.secho( - "Updating the %(slug_perm)s upstream from the %(owner)s/%(repo)s repository ... " - % { - "owner": click.style(owner, bold=True), - "repo": click.style(repo, bold=True), - "slug_perm": click.style(slug_perm, bold=True), - }, + f"Updating the {click.style(slug_perm, bold=True)} upstream from the {click.style(owner, bold=True)}/{click.style(repo, bold=True)} repository ... ", nl=False, err=use_stderr, ) context_msg = "Failed to update the upstream!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - upstream_resp_data = api.update_upstream( - owner, repo, slug_perm, upstream_fmt, upstream_config - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + upstream_resp_data = api.update_upstream( + owner, repo, slug_perm, upstream_fmt, upstream_config + ) if not use_stderr: click.secho("OK", fg="green", err=use_stderr) @@ -416,24 +409,28 @@ def func(ctx, opts, owner_repo_slug_perm, yes): } prompt = ( - "delete the %(slug_perm)s upstream from the %(owner)s/%(repo)s repository" - % delete_args + "delete the {slug_perm} upstream from the {owner}/{repo} repository".format( + **delete_args + ) ) if not utils.confirm_operation(prompt, assume_yes=yes, err=use_stderr): return if not use_stderr: click.secho( - "Deleting the %(slug_perm)s upstream from the %(owner)s/%(repo)s repository ... " - % delete_args, + "Deleting the {slug_perm} upstream from the {owner}/{repo} repository ... ".format( + **delete_args + ), nl=False, err=use_stderr, ) context_msg = "Failed to delete the upstream!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with maybe_spinner(opts): - api.delete_upstream(owner, repo, upstream_fmt, slug_perm) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + maybe_spinner(opts), + ): + api.delete_upstream(owner, repo, upstream_fmt, slug_perm) if not use_stderr: click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/vulnerabilities.py b/cloudsmith_cli/cli/commands/vulnerabilities.py index f140be71..6a434cf3 100644 --- a/cloudsmith_cli/cli/commands/vulnerabilities.py +++ b/cloudsmith_cli/cli/commands/vulnerabilities.py @@ -81,17 +81,19 @@ def vulnerabilities( total_filtered_vulns = 0 context_msg = "Failed to retrieve vulnerability report!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with utils.maybe_spinner(opts): - data = get_package_scan_result( - opts=opts, - owner=owner, - repo=repo, - package=slug, - show_assessment=show_assessment, - severity_filter=severity_filter, - fixable=fixable, - ) + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + utils.maybe_spinner(opts), + ): + data = get_package_scan_result( + opts=opts, + owner=owner, + repo=repo, + package=slug, + show_assessment=show_assessment, + severity_filter=severity_filter, + fixable=fixable, + ) click.secho("OK", fg="green", err=use_stderr) diff --git a/cloudsmith_cli/cli/commands/whoami.py b/cloudsmith_cli/cli/commands/whoami.py index 45d1f0fe..0b701095 100644 --- a/cloudsmith_cli/cli/commands/whoami.py +++ b/cloudsmith_cli/cli/commands/whoami.py @@ -155,9 +155,11 @@ def whoami(ctx, opts): ) context_msg = "Failed to retrieve your authentication status!" - with handle_api_exceptions(ctx, opts=opts, context_msg=context_msg): - with utils.maybe_spinner(opts): - is_auth, username, email, name = get_user_brief() + with ( + handle_api_exceptions(ctx, opts=opts, context_msg=context_msg), + utils.maybe_spinner(opts), + ): + is_auth, username, email, name = get_user_brief() click.secho("OK", fg="green", err=use_stderr) data = { diff --git a/cloudsmith_cli/cli/config.py b/cloudsmith_cli/cli/config.py index 1efd2b05..e623e421 100644 --- a/cloudsmith_cli/cli/config.py +++ b/cloudsmith_cli/cli/config.py @@ -3,6 +3,7 @@ import os import re import threading +from typing import ClassVar import click from click_configfile import ConfigFileReader, Param, SectionSchema, matches_section @@ -22,9 +23,8 @@ def __init__(self, *args, **kwargs): def parse(self, text): if text: text = text.strip() - if self.type.name == "boolean": - if not text: - return None + if self.type.name == "boolean" and not text: + return None return super().parse(text) def get_error_hint(self, ctx): @@ -83,10 +83,10 @@ class Profile(Default): class ConfigReader(ConfigFileReader): """Reader for standard configuration.""" - config_files = ["config.ini"] + config_files: ClassVar = ["config.ini"] config_name = "standard" - config_searchpath = list(_CFG_SEARCH_PATHS) - config_section_schemas = [ConfigSchema.Default, ConfigSchema.Profile] + config_searchpath: ClassVar = list(_CFG_SEARCH_PATHS) + config_section_schemas: ClassVar = [ConfigSchema.Default, ConfigSchema.Profile] @classmethod def select_config_schema_for(cls, section_name): @@ -195,7 +195,7 @@ def load_config(cls, opts, path=None, profile=None): cls._load_values_into_opts(opts, values) if profile and profile != "default": - values = config.get("profile:%s" % profile, {}) + values = config.get(f"profile:{profile}", {}) cls._load_values_into_opts(opts, values) return values @@ -206,9 +206,9 @@ def _load_values_into_opts(opts, values): if v is None: continue if isinstance(v, str): - if v.startswith('"') or v.startswith("'"): + if v.startswith(('"', "'")): v = v[1:] - if v.endswith('"') or v.endswith("'"): + if v.endswith(('"', "'")): v = v[:-1] if not v: continue @@ -235,10 +235,13 @@ class Profile(Default): class CredentialsReader(ConfigReader): """Reader for credentials configuration.""" - config_files = ["credentials.ini"] + config_files: ClassVar = ["credentials.ini"] config_name = "credentials" - config_searchpath = list(_CFG_SEARCH_PATHS) - config_section_schemas = [CredentialsSchema.Default, CredentialsSchema.Profile] + config_searchpath: ClassVar = list(_CFG_SEARCH_PATHS) + config_section_schemas: ClassVar = [ + CredentialsSchema.Default, + CredentialsSchema.Profile, + ] @classmethod def find_existing_files(cls): diff --git a/cloudsmith_cli/cli/decorators.py b/cloudsmith_cli/cli/decorators.py index 004f9f09..c6b30aba 100644 --- a/cloudsmith_cli/cli/decorators.py +++ b/cloudsmith_cli/cli/decorators.py @@ -24,8 +24,7 @@ def report_retry(seconds, context=None): if context == "retry-after": click.echo() click.echo( - "Request was throttled (429): Retrying after %(seconds)s second(s) ... " - % {"seconds": click.style(str(seconds), bold=True)} + f"Request was throttled (429): Retrying after {click.style(str(seconds), bold=True)} second(s) ... " ) diff --git a/cloudsmith_cli/cli/exceptions.py b/cloudsmith_cli/cli/exceptions.py index 0e0a116c..ae7a72c8 100644 --- a/cloudsmith_cli/cli/exceptions.py +++ b/cloudsmith_cli/cli/exceptions.py @@ -69,12 +69,7 @@ def handle_api_exceptions( click.secho("ERROR", fg="red", err=use_stderr) click.secho( - "%(context)s (status: %(code)s - %(code_text)s)" - % { - "context": context_msg, - "code": exc.status, - "code_text": exc.status_description, - }, + f"{context_msg} (status: {exc.status} - {exc.status_description})", fg="red", err=use_stderr, ) @@ -84,26 +79,26 @@ def handle_api_exceptions( if detail: click.secho( - "Detail: %(detail)s" - % {"detail": click.style(detail, fg="red", bold=False)}, + "Detail: {detail}".format( + detail=click.style(detail, fg="red", bold=False) + ), bold=True, err=use_stderr, ) if fields: for k, v in fields.items(): - field = "%s Field" % k.capitalize() + field = f"{k.capitalize()} Field" # Flatten list/tuple error messages for text output if isinstance(v, (list, tuple)): v = " ".join(v) click.secho( - "%(field)s: %(message)s" - % { - "field": click.style(field, bold=True), - "message": click.style(v, fg="red"), - }, + "{field}: {message}".format( + field=click.style(field, bold=True), + message=click.style(v, fg="red"), + ), err=use_stderr, ) @@ -113,12 +108,11 @@ def handle_api_exceptions( err=use_stderr, ) - if opts.verbose and not opts.debug: - if exc.headers: - click.echo(err=use_stderr) - click.echo("Headers in Reply:", err=use_stderr) - for k, v in exc.headers.items(): - click.echo(f"{k} = {v}", err=use_stderr) + if opts.verbose and not opts.debug and exc.headers: + click.echo(err=use_stderr) + click.echo("Headers in Reply:", err=use_stderr) + for k, v in exc.headers.items(): + click.echo(f"{k} = {v}", err=use_stderr) if reraise_on_error: raise @@ -161,7 +155,7 @@ def get_details(exc): def get_error_hint(ctx, opts, exc): """Get a hint to show to the user (if any).""" module = sys.modules[__name__] - get_specific_error_hint = getattr(module, "get_%s_error_hint" % exc.status, None) + get_specific_error_hint = getattr(module, f"get_{exc.status}_error_hint", None) if get_specific_error_hint: return get_specific_error_hint(ctx, opts, exc) return None diff --git a/cloudsmith_cli/cli/saml.py b/cloudsmith_cli/cli/saml.py index 087b0d7e..c3a20692 100644 --- a/cloudsmith_cli/cli/saml.py +++ b/cloudsmith_cli/cli/saml.py @@ -51,11 +51,7 @@ def exchange_2fa_token(api_host, two_factor_token, totp_token, session): exchange_data = {"two_factor_token": two_factor_token, "totp_token": totp_token} exchange_url = f"{api_host}/user/two-factor/" - headers = { - "Authorization": "Bearer {two_factor_token}".format( - two_factor_token=two_factor_token - ) - } + headers = {"Authorization": f"Bearer {two_factor_token}"} exchange_response = session.post( exchange_url, diff --git a/cloudsmith_cli/cli/tests/commands/test_credential_helper.py b/cloudsmith_cli/cli/tests/commands/test_credential_helper.py index 5936bea8..bd944649 100644 --- a/cloudsmith_cli/cli/tests/commands/test_credential_helper.py +++ b/cloudsmith_cli/cli/tests/commands/test_credential_helper.py @@ -29,6 +29,8 @@ from ....credential_helpers.docker.runtime import ( _REFUSAL_MESSAGE, execute, +) +from ....credential_helpers.docker.runtime import ( get_credentials as helper_get_credentials, ) @@ -70,13 +72,7 @@ def test_execute_protocol_matrix( """execute() returns the expected (code, stdout, stderr) for each operation.""" stdin = io.StringIO(stdin_text) - if operation == "get" and get_return is not None: - with patch( - "cloudsmith_cli.credential_helpers.docker.runtime.get_credentials", - return_value=get_return, - ): - code, stdout, stderr = execute(operation, stdin) - elif operation == "get": + if operation == "get" and get_return is not None or operation == "get": with patch( "cloudsmith_cli.credential_helpers.docker.runtime.get_credentials", return_value=get_return, diff --git a/cloudsmith_cli/cli/tests/commands/test_credential_helper_install.py b/cloudsmith_cli/cli/tests/commands/test_credential_helper_install.py index 205d554a..62ffc723 100644 --- a/cloudsmith_cli/cli/tests/commands/test_credential_helper_install.py +++ b/cloudsmith_cli/cli/tests/commands/test_credential_helper_install.py @@ -313,9 +313,9 @@ def test_docker_installer_status_type_contract(tmp_path, monkeypatch): launcher = result_after["launcher"] assert launcher is not None - assert isinstance( - launcher, str - ), f"status()['launcher'] must be str, got {type(launcher).__name__!r}" + assert isinstance(launcher, str), ( + f"status()['launcher'] must be str, got {type(launcher).__name__!r}" + ) assert launcher.endswith("docker-credential-cloudsmith") assert not isinstance(launcher, Path) @@ -406,9 +406,9 @@ def _should_not_be_called(*_a, **_kw): installer.install( bin_dir=str(bin_dir), discover=True, org=org, credential=creds[scenario] ) - assert ( - not called - ), "get_format_domains must not be called when org/credential absent" + assert not called, ( + "get_format_domains must not be called when org/credential absent" + ) cfg = json.loads((docker_dir / "config.json").read_text()) assert cfg["credHelpers"]["docker.cloudsmith.io"] == "cloudsmith" @@ -524,9 +524,9 @@ def _fake_list(*_a, **_kw): assert result == [fresh_domain] else: # API must NOT have been called - assert ( - not api_calls - ), "API must not be called when refresh=False with valid cache" + assert not api_calls, ( + "API must not be called when refresh=False with valid cache" + ) assert result == [cached_domain] @@ -636,9 +636,9 @@ def test_unwritable_bin_dir_gives_click_exception(runner, tmp_path, monkeypatch) ro_dir.chmod(0o700) assert result.exit_code != 0 - assert not isinstance( - result.exception, OSError - ), f"Raw OSError escaped: {result.exception}" + assert not isinstance(result.exception, OSError), ( + f"Raw OSError escaped: {result.exception}" + ) # --------------------------------------------------------------------------- @@ -717,9 +717,9 @@ def test_output_format_json( assert result.exit_code == 0, result.output # Pure JSON on stdout (no human text leaking before the JSON) - assert result.output.strip().startswith( - "{" - ), f"Output does not start with {{: {result.output[:100]!r}" + assert result.output.strip().startswith("{"), ( + f"Output does not start with {{: {result.output[:100]!r}" + ) parsed = json.loads(result.output) data = parsed["data"] diff --git a/cloudsmith_cli/cli/tests/commands/test_entitlements.py b/cloudsmith_cli/cli/tests/commands/test_entitlements.py index 187ce7f8..6b35610c 100644 --- a/cloudsmith_cli/cli/tests/commands/test_entitlements.py +++ b/cloudsmith_cli/cli/tests/commands/test_entitlements.py @@ -6,7 +6,7 @@ @pytest.mark.usefixtures("set_api_key_env_var", "set_api_host_env_var") def test_entitlements_list_with_show_all(runner, organization, tmp_repository): """Test listing entitlements with --show-all flag.""" - org_repo = f'{organization}/{tmp_repository["slug"]}' + org_repo = f"{organization}/{tmp_repository['slug']}" # Minimal show-all success (no pagination args besides flag) result = runner.invoke( diff --git a/cloudsmith_cli/cli/tests/commands/test_login.py b/cloudsmith_cli/cli/tests/commands/test_login.py index 43244802..b29be4c7 100644 --- a/cloudsmith_cli/cli/tests/commands/test_login.py +++ b/cloudsmith_cli/cli/tests/commands/test_login.py @@ -7,7 +7,7 @@ class TestLoginCommand: def test_login_via_prompt(self, runner, username, password, api_key): """Test that a user can `cloudsmith login` with interactive prompts.""" - expected = "Your API key/token is: %s" % api_key + expected = f"Your API key/token is: {api_key}" user_input = [ username, # Login: password, # Password: @@ -21,7 +21,7 @@ def test_login_via_prompt(self, runner, username, password, api_key): def test_login_via_args(self, runner, username, password, api_key): """Test that a user can `cloudsmith login -l -p `.""" - expected = "Your API key/token is: %s" % api_key + expected = f"Your API key/token is: {api_key}" # The "input" argument here answers the following prompt: # No default config file(s) found, do you want to create them? [y/N]: result = runner.invoke(login, ["-l", username, "-p", password], input="N\n") diff --git a/cloudsmith_cli/cli/tests/commands/test_mcp.py b/cloudsmith_cli/cli/tests/commands/test_mcp.py index 9507f8a7..53913469 100644 --- a/cloudsmith_cli/cli/tests/commands/test_mcp.py +++ b/cloudsmith_cli/cli/tests/commands/test_mcp.py @@ -407,9 +407,11 @@ def test_user_scope_merges_into_existing_claude_json(self, tmp_path): assert result["projects"] == existing["projects"] def test_user_scope_errors_when_claude_json_missing(self, tmp_path): - with patch("cloudsmith_cli.cli.commands.mcp.Path.home", return_value=tmp_path): - with pytest.raises(ValueError, match="Launch Claude Code at least once"): - _configure_claude_code("cloudsmith", SERVER_CONFIG, is_global=True) + with ( + patch("cloudsmith_cli.cli.commands.mcp.Path.home", return_value=tmp_path), + pytest.raises(ValueError, match="Launch Claude Code at least once"), + ): + _configure_claude_code("cloudsmith", SERVER_CONFIG, is_global=True) def test_project_scope_writes_local_mcp_json(self, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) @@ -485,12 +487,14 @@ def test_atomic_write_creates_parent_directory(self, tmp_path): def test_atomic_write_cleans_up_tempfile_on_failure(self, tmp_path): target = tmp_path / "config.json" - with patch( - "cloudsmith_cli.cli.commands.mcp.os.replace", - side_effect=OSError("boom"), + with ( + patch( + "cloudsmith_cli.cli.commands.mcp.os.replace", + side_effect=OSError("boom"), + ), + pytest.raises(OSError, match="boom"), ): - with pytest.raises(OSError, match="boom"): - _atomic_write_json(target, {"k": "v"}) + _atomic_write_json(target, {"k": "v"}) leftovers = [ p for p in tmp_path.iterdir() if p.name.startswith(".config.json.") @@ -554,9 +558,11 @@ def always_racing(self, *args, **kwargs): os.utime(target, ns=(result.st_atime_ns, result.st_mtime_ns + 1000)) return result - with patch.object(Path, "stat", always_racing): - with pytest.raises(ValueError, match="another process keeps modifying"): - _safe_update_json(target, lambda c: c, max_retries=2) + with ( + patch.object(Path, "stat", always_racing), + pytest.raises(ValueError, match="another process keeps modifying"), + ): + _safe_update_json(target, lambda c: c, max_retries=2) def test_safe_update_raises_on_malformed_json(self, tmp_path): target = tmp_path / "config.json" diff --git a/cloudsmith_cli/cli/tests/commands/test_package_commands.py b/cloudsmith_cli/cli/tests/commands/test_package_commands.py index 3a879269..79c65f01 100644 --- a/cloudsmith_cli/cli/tests/commands/test_package_commands.py +++ b/cloudsmith_cli/cli/tests/commands/test_package_commands.py @@ -23,7 +23,7 @@ def test_push_and_delete_raw_package( runner, organization, tmp_repository, tmp_path, filesize ): # List packages again - should be empty. - org_repo = f'{organization}/{tmp_repository["slug"]}' + org_repo = f"{organization}/{tmp_repository['slug']}" result = runner.invoke( list_, args=["pkgs", org_repo, "-F", "json"], catch_exceptions=False ) @@ -90,7 +90,7 @@ def test_push_and_delete_raw_package( @pytest.mark.usefixtures("set_api_key_env_var", "set_api_host_env_var") def test_list_packages_with_sort(runner, organization, tmp_repository, tmp_path): """Test listing packages with different sort options.""" - org_repo = f'{organization}/{tmp_repository["slug"]}' + org_repo = f"{organization}/{tmp_repository['slug']}" # Create and push two packages with different names for name in ["aaa", "zzz"]: diff --git a/cloudsmith_cli/cli/tests/commands/test_tokens.py b/cloudsmith_cli/cli/tests/commands/test_tokens.py index 349888d5..293274ca 100644 --- a/cloudsmith_cli/cli/tests/commands/test_tokens.py +++ b/cloudsmith_cli/cli/tests/commands/test_tokens.py @@ -11,7 +11,6 @@ @pytest.mark.usefixtures("set_api_host_env_var") class TestListTokensCommand: - def test_list_tokens_success(self, runner): """Test successful listing of tokens.""" mock_tokens = [ diff --git a/cloudsmith_cli/cli/tests/commands/test_upstream.py b/cloudsmith_cli/cli/tests/commands/test_upstream.py index 3069784f..381e5f05 100644 --- a/cloudsmith_cli/cli/tests/commands/test_upstream.py +++ b/cloudsmith_cli/cli/tests/commands/test_upstream.py @@ -13,7 +13,7 @@ def test_upstream_commands( ): upstream_config = { # "name" and "upstream_url" are the only required properties for most formats. - "name": "cli-test-upstream-%s" % upstream_format, + "name": f"cli-test-upstream-{upstream_format}", # This obviously isn't an upstream url and will not work on the server, # but we aren't testing the server. "upstream_url": "https://www.cloudsmith.io", @@ -23,7 +23,7 @@ def test_upstream_commands( "distro_versions": ["ubuntu/xenial"], } - upstream_config_file = tmp_path / ("cli-test-upstream-%s.json" % upstream_format) + upstream_config_file = tmp_path / (f"cli-test-upstream-{upstream_format}.json") upstream_config_file.write_text(str(json.dumps(upstream_config))) org_repo = f"{organization}/{tmp_repository['slug']}" diff --git a/cloudsmith_cli/cli/tests/conftest.py b/cloudsmith_cli/cli/tests/conftest.py index 653e574c..20254d20 100644 --- a/cloudsmith_cli/cli/tests/conftest.py +++ b/cloudsmith_cli/cli/tests/conftest.py @@ -13,7 +13,7 @@ def _get_env_var_or_skip(key): """Return the environment variable value if set, otherwise skip the test.""" value = os.environ.get(key) if not value: - pytest.skip("%s not provided" % key) + pytest.skip(f"{key} not provided") return value diff --git a/cloudsmith_cli/cli/tests/test_push.py b/cloudsmith_cli/cli/tests/test_push.py index ff01df0e..0c6670df 100644 --- a/cloudsmith_cli/cli/tests/test_push.py +++ b/cloudsmith_cli/cli/tests/test_push.py @@ -199,24 +199,24 @@ def test_upload_files_and_create_package_with_json_null_metadata(self): ) as mock_validate_create_package, patch("cloudsmith_cli.cli.commands.push.api_validate_metadata"), patch("cloudsmith_cli.cli.commands.push.api_create_metadata"), + pytest.raises(click.ClickException, match="JSON object"), ): - with pytest.raises(click.ClickException, match="JSON object"): - upload_files_and_create_package( - self.mock_ctx, - MagicMock(spec=[]), - self.package_type, - [self.owner, self.repo], - self.dry_run, - self.no_wait_for_sync, - self.wait_interval, - self.skip_errors, - self.sync_attempts, - package_file="path", - name="x", - version="1", - metadata_content="null", - metadata_content_type="application/json", - ) + upload_files_and_create_package( + self.mock_ctx, + MagicMock(spec=[]), + self.package_type, + [self.owner, self.repo], + self.dry_run, + self.no_wait_for_sync, + self.wait_interval, + self.skip_errors, + self.sync_attempts, + package_file="path", + name="x", + version="1", + metadata_content="null", + metadata_content_type="application/json", + ) mock_validate_create_package.assert_not_called() @@ -794,16 +794,18 @@ def test_resolve_push_metadata_options_warn_via_config_key(self): def test_resolve_push_metadata_options_flag_beats_env_error(self): """``--on-metadata-failure error`` overrides ``...=warn`` in the env.""" opts = SimpleNamespace(cli_metadata_failure_mode="error") - with patch.dict( - "cloudsmith_cli.cli.commands.push.os.environ", - {"CLOUDSMITH_METADATA_FAILURE_MODE": "warn"}, + with ( + patch.dict( + "cloudsmith_cli.cli.commands.push.os.environ", + {"CLOUDSMITH_METADATA_FAILURE_MODE": "warn"}, + ), + pytest.raises(click.ClickException, match="Invalid JSON"), ): - with pytest.raises(click.ClickException, match="Invalid JSON"): - resolve_push_metadata_options( - metadata_content="not-json", - metadata_content_type="application/json", - opts=opts, - ) + resolve_push_metadata_options( + metadata_content="not-json", + metadata_content_type="application/json", + opts=opts, + ) def test_resolve_push_metadata_options_env_beats_config_error(self): """``...=warn`` env var overrides ``metadata_failure_mode = error`` config.""" diff --git a/cloudsmith_cli/cli/tests/test_utils.py b/cloudsmith_cli/cli/tests/test_utils.py index 733be59a..04dd2fcc 100644 --- a/cloudsmith_cli/cli/tests/test_utils.py +++ b/cloudsmith_cli/cli/tests/test_utils.py @@ -5,7 +5,7 @@ @pytest.mark.parametrize( "data,max_length,expected_len", - [(range(0, 1), 5, 1), (range(0, 5), 5, 5), (list(), 5, 0), (None, 5, 0)], + [(range(1), 5, 1), (range(5), 5, 5), ([], 5, 0), (None, 5, 0)], ) def test_maybe_truncate_list(data, max_length, expected_len): truncated = maybe_truncate_list(data, max_length) diff --git a/cloudsmith_cli/cli/tests/test_webserver.py b/cloudsmith_cli/cli/tests/test_webserver.py index 12ecdf77..66fb660c 100644 --- a/cloudsmith_cli/cli/tests/test_webserver.py +++ b/cloudsmith_cli/cli/tests/test_webserver.py @@ -68,92 +68,98 @@ def mock_handler(self): def test_store_sso_tokens_called_when_keyring_enabled(self, mock_handler): """Verify store_sso_tokens is called and returns True when keyring is enabled.""" - with patch( - "cloudsmith_cli.cli.webserver.store_sso_tokens", return_value=True - ) as mock_store: - with patch.object(mock_handler, "_return_success_response"): - with patch.object( - AuthenticationWebRequestHandler, - "query_data", - new_callable=PropertyMock, - ) as mock_query: - with patch.object( - AuthenticationWebRequestHandler, - "api_host", - new_callable=PropertyMock, - ) as mock_host: - mock_query.return_value = { - "access_token": "test_access_token", - "refresh_token": "test_refresh_token", - } - mock_host.return_value = "https://api.cloudsmith.io" - - mock_handler.do_GET() - - mock_store.assert_called_once_with( - "https://api.cloudsmith.io", - "test_access_token", - "test_refresh_token", - ) + with ( + patch( + "cloudsmith_cli.cli.webserver.store_sso_tokens", return_value=True + ) as mock_store, + patch.object(mock_handler, "_return_success_response"), + patch.object( + AuthenticationWebRequestHandler, + "query_data", + new_callable=PropertyMock, + ) as mock_query, + patch.object( + AuthenticationWebRequestHandler, + "api_host", + new_callable=PropertyMock, + ) as mock_host, + ): + mock_query.return_value = { + "access_token": "test_access_token", + "refresh_token": "test_refresh_token", + } + mock_host.return_value = "https://api.cloudsmith.io" + + mock_handler.do_GET() + + mock_store.assert_called_once_with( + "https://api.cloudsmith.io", + "test_access_token", + "test_refresh_token", + ) def test_message_shown_when_keyring_disabled(self, mock_handler): """Verify message is shown when store_sso_tokens returns False.""" - with patch( - "cloudsmith_cli.cli.webserver.store_sso_tokens", return_value=False - ) as mock_store: - with patch("click.echo") as mock_echo: - with patch.object(mock_handler, "_return_success_response"): - with patch.object( - AuthenticationWebRequestHandler, - "query_data", - new_callable=PropertyMock, - ) as mock_query: - with patch.object( - AuthenticationWebRequestHandler, - "api_host", - new_callable=PropertyMock, - ) as mock_host: - mock_query.return_value = { - "access_token": "test_access_token", - "refresh_token": "test_refresh_token", - } - mock_host.return_value = "https://api.cloudsmith.io" - - mock_handler.do_GET() - - # store_sso_tokens should be called (returns False) - mock_store.assert_called_once() - - # Message should be displayed to stderr - mock_echo.assert_called_once_with( - "SSO tokens not stored (CLOUDSMITH_NO_KEYRING is set)", - err=True, - ) + with ( + patch( + "cloudsmith_cli.cli.webserver.store_sso_tokens", return_value=False + ) as mock_store, + patch("click.echo") as mock_echo, + patch.object(mock_handler, "_return_success_response"), + patch.object( + AuthenticationWebRequestHandler, + "query_data", + new_callable=PropertyMock, + ) as mock_query, + patch.object( + AuthenticationWebRequestHandler, + "api_host", + new_callable=PropertyMock, + ) as mock_host, + ): + mock_query.return_value = { + "access_token": "test_access_token", + "refresh_token": "test_refresh_token", + } + mock_host.return_value = "https://api.cloudsmith.io" + + mock_handler.do_GET() + + # store_sso_tokens should be called (returns False) + mock_store.assert_called_once() + + # Message should be displayed to stderr + mock_echo.assert_called_once_with( + "SSO tokens not stored (CLOUDSMITH_NO_KEYRING is set)", + err=True, + ) def test_access_token_stored_on_server_instance(self, mock_handler): """Verify the SSO access token is stored on the server instance for direct use.""" - with patch("cloudsmith_cli.cli.webserver.store_sso_tokens", return_value=True): - with patch.object(mock_handler, "_return_success_response"): - with patch.object( - AuthenticationWebRequestHandler, - "query_data", - new_callable=PropertyMock, - ) as mock_query: - with patch.object( - AuthenticationWebRequestHandler, - "api_host", - new_callable=PropertyMock, - ) as mock_host: - mock_query.return_value = { - "access_token": "sso_token_for_direct_use", - "refresh_token": "test_refresh_token", - } - mock_host.return_value = "https://api.cloudsmith.io" - - mock_handler.do_GET() - - # Verify do_GET() stored the access token on the server instance - assert ( - mock_handler.server_instance.sso_access_token - == "sso_token_for_direct_use" - ) + with ( + patch("cloudsmith_cli.cli.webserver.store_sso_tokens", return_value=True), + patch.object(mock_handler, "_return_success_response"), + patch.object( + AuthenticationWebRequestHandler, + "query_data", + new_callable=PropertyMock, + ) as mock_query, + patch.object( + AuthenticationWebRequestHandler, + "api_host", + new_callable=PropertyMock, + ) as mock_host, + ): + mock_query.return_value = { + "access_token": "sso_token_for_direct_use", + "refresh_token": "test_refresh_token", + } + mock_host.return_value = "https://api.cloudsmith.io" + + mock_handler.do_GET() + + # Verify do_GET() stored the access token on the server instance + assert ( + mock_handler.server_instance.sso_access_token + == "sso_token_for_direct_use" + ) diff --git a/cloudsmith_cli/cli/tests/utils.py b/cloudsmith_cli/cli/tests/utils.py index 2d44a208..1a4e1d76 100644 --- a/cloudsmith_cli/cli/tests/utils.py +++ b/cloudsmith_cli/cli/tests/utils.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from uuid import uuid4 @@ -9,4 +9,4 @@ def random_str(): def random_bool(): """Return a random bool.""" - return datetime.now().microsecond % 2 == 0 + return datetime.now(tz=timezone.utc).microsecond % 2 == 0 diff --git a/cloudsmith_cli/cli/utils.py b/cloudsmith_cli/cli/utils.py index 1db72b85..be17fef6 100644 --- a/cloudsmith_cli/cli/utils.py +++ b/cloudsmith_cli/cli/utils.py @@ -24,35 +24,16 @@ def make_user_agent(prefix=None): def pretty_print_list_info(num_results, page_info=None, suffix="", page_all=False): """Print information about list results.""" if page_all: - click.echo( - "Results: %(num_results)d %(suffix)s" - % { - "num_results": num_results, - "suffix": suffix, - } - ) + click.echo(f"Results: {num_results} {suffix}") elif page_info and page_info.page is not None and page_info.page_size is not None: start = (page_info.page - 1) * page_info.page_size + 1 end = min(start + num_results - 1, page_info.count or 0) click.echo( - "Results: %(start)d-%(end)d (%(count)d) of %(total)d %(suffix)s " - "(page: %(page)d/%(pages)d, page size: %(page_size)d)" - % { - "start": start, - "end": end, - "count": num_results, - "total": page_info.count or 0, - "suffix": suffix, - "page": page_info.page, - "pages": page_info.page_total or 1, - "page_size": page_info.page_size, - } + f"Results: {start}-{end} ({num_results}) of {page_info.count} {suffix} " + f"(page: {page_info.page}/{page_info.page_total}, page size: {page_info.page_size})" ) else: - click.echo( - "Results: %(num_results)d %(suffix)s" - % {"num_results": num_results, "suffix": suffix} - ) + click.echo(f"Results: {num_results} {suffix}") def fmt_datetime(value): @@ -127,8 +108,7 @@ def print_rate_limit_info(opts, rate_info): click.echo(err=True) click.secho( - "Throttling (rate limited) for: %(throttle)s seconds ... " - % {"throttle": click.style(str(rate_info.interval), reverse=True)}, + f"Throttling (rate limited) for: {click.style(str(rate_info.interval), reverse=True)} seconds ... ", err=True, reset=False, ) @@ -140,7 +120,7 @@ def json_serializer(obj): # convert date/datetime objects to strings if isinstance(obj, (datetime, date)): return fmt_datetime(obj) - raise TypeError("Type %s not serializable." % type(obj)) + raise TypeError(f"Type {type(obj)} not serializable.") def maybe_print_as_json(opts, data, page_info=None): @@ -173,7 +153,7 @@ def maybe_print_as_json(opts, data, page_info=None): else: dump = json.dumps(root, sort_keys=True, default=json_serializer) except (TypeError, ValueError) as e: - click.secho(f"Failed to convert to JSON: {str(e)}", fg="red", err=True) + click.secho(f"Failed to convert to JSON: {e!s}", fg="red", err=True) return True click.echo(dump) @@ -219,7 +199,7 @@ def confirm_operation(prompt, prefix=None, assume_yes=False, err=False): return True prefix = prefix or click.style( - "Are you %s certain you want to" % (click.style("absolutely", bold=True)) + "Are you {} certain you want to".format(click.style("absolutely", bold=True)) ) prompt = maybe_unstyle_prompt(f"{prefix} {prompt}?", err=err) diff --git a/cloudsmith_cli/cli/validators.py b/cloudsmith_cli/cli/validators.py index 6219ecc3..bb28fdf5 100644 --- a/cloudsmith_cli/cli/validators.py +++ b/cloudsmith_cli/cli/validators.py @@ -1,7 +1,7 @@ """CLI - Validators.""" import base64 -from datetime import datetime +from datetime import datetime, timezone from urllib.parse import urlsplit import click @@ -9,7 +9,7 @@ from .types import ExpandPath -CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) +CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]} BAD_API_HEADERS = ("user-agent", "host") API_HEADER_TRANSFORMS = {} PUBLIC_API_HOST_SUFFIXES = ("cloudsmith.io", "cloudsmith.com") @@ -48,7 +48,7 @@ def transform_api_header_authorization(param, value): value = f"{username.strip()}:{password}" value = base64.b64encode(bytes(value.encode())) - return "Basic %s" % value.decode("utf-8") + return "Basic {}".format(value.decode("utf-8")) API_HEADER_TRANSFORMS["Authorization"] = transform_api_header_authorization @@ -137,11 +137,8 @@ def validate_slashes( except ValueError: value = None - if value: - if len(value) < minimum: - value = None - elif maximum and len(value) > maximum: - value = None + if value and len(value) < minimum or maximum and len(value) > maximum: + value = None if not value: form = form or "/".join("VALUE" for _ in range(minimum)) @@ -277,8 +274,10 @@ def validate_optional_timestamp(ctx, param, value): if value: try: - return datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ").replace( - hour=0, minute=0, second=0 + return ( + datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ") + .replace(tzinfo=timezone.utc) + .replace(hour=0, minute=0, second=0) ) except ValueError: raise click.BadParameter( diff --git a/cloudsmith_cli/cli/webserver.py b/cloudsmith_cli/cli/webserver.py index 9a2e1aa0..3a2d2d3c 100644 --- a/cloudsmith_cli/cli/webserver.py +++ b/cloudsmith_cli/cli/webserver.py @@ -234,9 +234,9 @@ def do_GET(self): click.secho("\nAuthentication complete", fg="green", err=True) self._return_success_response() return - except Exception as exc: + except Exception: self._return_error_response() - raise exc + raise click.secho("\nNo valid authentication parameters received", fg="red", err=True) self._return_error_response() diff --git a/cloudsmith_cli/core/api/packages.py b/cloudsmith_cli/core/api/packages.py index 94879046..0b6209e6 100644 --- a/cloudsmith_cli/core/api/packages.py +++ b/cloudsmith_cli/core/api/packages.py @@ -32,7 +32,7 @@ def create_package(package_format, owner, repo, **kwargs): client = get_packages_api() with catch_raise_api_exception(): - upload = getattr(client, "packages_upload_%s_with_http_info" % package_format) + upload = getattr(client, f"packages_upload_{package_format}_with_http_info") data, _, headers = upload( owner=owner, repo=repo, data=make_create_payload(**kwargs) @@ -48,7 +48,7 @@ def validate_create_package(package_format, owner, repo, **kwargs): with catch_raise_api_exception(): check = getattr( - client, "packages_validate_upload_%s_with_http_info" % package_format + client, f"packages_validate_upload_{package_format}_with_http_info" ) _, _, headers = check( diff --git a/cloudsmith_cli/core/api/quota.py b/cloudsmith_cli/core/api/quota.py index 02c3c40e..f3ac3cd5 100644 --- a/cloudsmith_cli/core/api/quota.py +++ b/cloudsmith_cli/core/api/quota.py @@ -27,15 +27,14 @@ def quota_limits(owner=None, oss=False, **kwargs): res, _, headers = client.quota_oss_read_with_http_info( owner=owner, **api_kwargs ) - elif not oss: - if hasattr(client, "quota_read_with_http_info"): - with catch_raise_api_exception(): - res, _, headers = client.quota_read_with_http_info( - owner=owner, **api_kwargs - ) + elif not oss and hasattr(client, "quota_read_with_http_info"): + with catch_raise_api_exception(): + res, _, headers = client.quota_read_with_http_info( + owner=owner, **api_kwargs + ) ratelimits.maybe_rate_limit(client, headers) - return res if not res else res + return res def quota_history(owner=None, oss=False, **kwargs): @@ -53,12 +52,11 @@ def quota_history(owner=None, oss=False, **kwargs): res, _, headers = client.quota_oss_history_read_with_http_info( owner=owner, **api_kwargs ) - elif not oss: - if hasattr(client, "quota_history_read_with_http_info"): - with catch_raise_api_exception(): - res, _, headers = client.quota_history_read_with_http_info( - owner=owner, **api_kwargs - ) + elif not oss and hasattr(client, "quota_history_read_with_http_info"): + with catch_raise_api_exception(): + res, _, headers = client.quota_history_read_with_http_info( + owner=owner, **api_kwargs + ) ratelimits.maybe_rate_limit(client, headers) - return res if not res else res + return res diff --git a/cloudsmith_cli/core/api/upstreams.py b/cloudsmith_cli/core/api/upstreams.py index 8ef124e2..c367bbac 100644 --- a/cloudsmith_cli/core/api/upstreams.py +++ b/cloudsmith_cli/core/api/upstreams.py @@ -18,7 +18,7 @@ def list_upstreams(owner, repo, upstream_format, page, page_size): """List upstreams by format in a repo.""" client = get_upstreams_api() - func = getattr(client, "repos_upstream_%s_list_with_http_info" % upstream_format) + func = getattr(client, f"repos_upstream_{upstream_format}_list_with_http_info") with catch_raise_api_exception(): upstreams, _, headers = func( @@ -34,7 +34,7 @@ def create_upstream(owner, repo, upstream_format, upstream_config): """Create an upstream for a certain package format in a repo.""" client = get_upstreams_api() - func = getattr(client, "repos_upstream_%s_create_with_http_info" % upstream_format) + func = getattr(client, f"repos_upstream_{upstream_format}_create_with_http_info") with catch_raise_api_exception(): upstream, _, headers = func(owner=owner, identifier=repo, data=upstream_config) @@ -48,7 +48,7 @@ def update_upstream(owner, repo, slug_perm, upstream_format, upstream_config): client = get_upstreams_api() func = getattr( - client, "repos_upstream_%s_partial_update_with_http_info" % upstream_format + client, f"repos_upstream_{upstream_format}_partial_update_with_http_info" ) with catch_raise_api_exception(): @@ -64,7 +64,7 @@ def delete_upstream(owner, repo, upstream_format, slug_perm): """Delete an upstream from a repo.""" client = get_upstreams_api() - func = getattr(client, "repos_upstream_%s_delete_with_http_info" % upstream_format) + func = getattr(client, f"repos_upstream_{upstream_format}_delete_with_http_info") with catch_raise_api_exception(): _, _, headers = func(owner, repo, slug_perm) diff --git a/cloudsmith_cli/core/api/vulnerabilities.py b/cloudsmith_cli/core/api/vulnerabilities.py index 5f6927f4..c755b1f6 100644 --- a/cloudsmith_cli/core/api/vulnerabilities.py +++ b/cloudsmith_cli/core/api/vulnerabilities.py @@ -30,7 +30,7 @@ def _print_vulnerabilities_summary_table(data, severity_filter, total_filtered_v severity_keys = {k: v for k, v in severity_keys.items() if v in allowed} headers = [{"header": "Package", "justify": "left", "style": "cyan"}] - for key in severity_keys.keys(): + for key in severity_keys: headers.append({"header": key, "justify": "center", "style": "white"}) # Get package name and version for the target label @@ -55,7 +55,7 @@ def _print_vulnerabilities_summary_table(data, severity_filter, total_filtered_v # Create the single summary row row = [target_label] - for _header, key in severity_keys.items(): + for key in severity_keys.values(): row.append(str(counts[key])) rows = [row] diff --git a/cloudsmith_cli/core/config.py b/cloudsmith_cli/core/config.py index 9603009f..7c4b232a 100644 --- a/cloudsmith_cli/core/config.py +++ b/cloudsmith_cli/core/config.py @@ -52,13 +52,12 @@ def create_config_files(ctx, opts, api_key, force=False): has_errors = False for config in configs: click.echo( - "%(name)s config file: %(filepath)s ... " - % { - "name": click.style(config.reader.config_name.capitalize(), bold=True), - "filepath": click.style( + "{name} config file: {filepath} ... ".format( + name=click.style(config.reader.config_name.capitalize(), bold=True), + filepath=click.style( config.reader.get_default_filepath(), fg="magenta" ), - }, + ), nl=False, ) @@ -78,8 +77,9 @@ def create_config_files(ctx, opts, api_key, force=False): click.secho("ERROR", fg="red") click.secho( "The following error occurred while trying to " - "create the file: %(message)s" - % {"message": click.style(error_message, fg="red")} + "create the file: {message}".format( + message=click.style(error_message, fg="red") + ) ) continue @@ -100,8 +100,9 @@ def create_config_files(ctx, opts, api_key, force=False): click.secho("ERROR", fg="red") click.secho( "The following error occurred while trying to " - "update the file: %(message)s" - % {"message": click.style(exc.strerror, fg="red")} + "update the file: {message}".format( + message=click.style(exc.strerror, fg="red") + ) ) continue @@ -133,7 +134,7 @@ def new_config_messaging(has_errors, opts, create, api_key): ) click.secho( "If you need more help please see the documentation: " - "%(website)s" % {"website": click.style(get_help_website(), bold=True)} + f"{click.style(get_help_website(), bold=True)}" ) click.echo() diff --git a/cloudsmith_cli/core/credentials/oidc/exchange.py b/cloudsmith_cli/core/credentials/oidc/exchange.py index fdf04650..05d8c514 100644 --- a/cloudsmith_cli/core/credentials/oidc/exchange.py +++ b/cloudsmith_cli/core/credentials/oidc/exchange.py @@ -75,8 +75,7 @@ def exchange_oidc_token( error_detail = response.text[:200] raise OidcExchangeError( - f"OIDC token exchange failed with {response.status_code}: " - f"{error_detail}" + f"OIDC token exchange failed with {response.status_code}: {error_detail}" ) finally: if not context.session: diff --git a/cloudsmith_cli/core/download.py b/cloudsmith_cli/core/download.py index b9b28861..d561d438 100644 --- a/cloudsmith_cli/core/download.py +++ b/cloudsmith_cli/core/download.py @@ -150,9 +150,12 @@ def _search_packages( if tag_filter and not _matches_tag_filter(pkg, tag_filter): continue # Apply filename filter (glob patterns are client-side only) - if filename_filter and any(c in filename_filter for c in "*?["): - if not fnmatch.fnmatch(pkg.get("filename", ""), filename_filter): - continue + if ( + filename_filter + and any(c in filename_filter for c in "*?[") + and not fnmatch.fnmatch(pkg.get("filename", ""), filename_filter) + ): + continue filtered_packages.append(pkg) return filtered_packages @@ -403,7 +406,7 @@ def get_package_detail(owner: str, repo: str, identifier: str) -> dict: return data.to_dict() -def stream_download( # noqa: C901 +def stream_download( url: str, outfile: str, session: requests.Session, @@ -470,7 +473,7 @@ def stream_download( # noqa: C901 f"Failed to download package: HTTP {e.response.status_code}" ) except requests.exceptions.RequestException as e: - raise click.ClickException(f"Failed to download package: {str(e)}") + raise click.ClickException(f"Failed to download package: {e!s}") # Get content length for progress bar total_size = int(response.headers.get("content-length", 0)) @@ -537,7 +540,7 @@ def sort_key(pkg): return (tuple(version_parts), uploaded_at) - return sorted(packages, key=sort_key, reverse=True)[0] + return max(packages, key=sort_key) def _format_size(size_bytes: int) -> str: diff --git a/cloudsmith_cli/core/keyring.py b/cloudsmith_cli/core/keyring.py index 7aa12e98..e39fd98a 100644 --- a/cloudsmith_cli/core/keyring.py +++ b/cloudsmith_cli/core/keyring.py @@ -1,6 +1,6 @@ import getpass import os -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import keyring from keyring.errors import KeyringError @@ -51,7 +51,7 @@ def get_access_token(api_host): def update_refresh_attempted_at(api_host, refresh_time=None): if refresh_time is None: - refresh_time = datetime.utcnow() + refresh_time = datetime.now(tz=timezone.utc) refresh_attempted_at_value = refresh_time.isoformat() @@ -79,7 +79,9 @@ def should_refresh_access_token(api_host): token_refreshed_at = get_refresh_attempted_at(api_host) if token_refreshed_at: - return token_refreshed_at < (datetime.utcnow() - timedelta(minutes=30)) + return token_refreshed_at < ( + datetime.now(tz=timezone.utc) - timedelta(minutes=30) + ) return True diff --git a/cloudsmith_cli/core/mcp/server.py b/cloudsmith_cli/core/mcp/server.py index 1ff7ba6b..be64b10f 100644 --- a/cloudsmith_cli/core/mcp/server.py +++ b/cloudsmith_cli/core/mcp/server.py @@ -2,7 +2,7 @@ import copy import inspect import json -from typing import Any, Optional +from typing import Any from urllib import parse import cloudsmith_api @@ -237,11 +237,13 @@ def _get_tool_groups(self, tool_name: str) -> list[str]: action_parts_count = 0 for action_suffix in sorted_suffixes: action_suffix_parts = action_suffix.split("_") - if len(parts) >= len(action_suffix_parts): + if ( + len(parts) >= len(action_suffix_parts) # Check if the end of the tool name matches this action suffix - if parts[-len(action_suffix_parts) :] == action_suffix_parts: - action_parts_count = len(action_suffix_parts) - break + and parts[-len(action_suffix_parts) :] == action_suffix_parts + ): + action_parts_count = len(action_suffix_parts) + break # If no action suffix found, treat the last part as the action if action_parts_count == 0: @@ -363,7 +365,7 @@ async def dynamic_tool_func(**kwargs) -> str: # Create parameter with default value default = param_schema.get("default", None) annotation_type = ( - param_type if default is not None else Optional[param_type] + param_type if default is not None else param_type | None ) sig_params.append( @@ -433,12 +435,11 @@ def _get_request_params( continue # Validate enum values - if "enum" in param_schema: - if value not in param_schema["enum"]: - allowed_values = ", ".join(param_schema["enum"]) - raise ValueError( - f"Invalid value '{value}' for parameter '{key}'. Allowed values: {allowed_values}" - ) + if "enum" in param_schema and value not in param_schema["enum"]: + allowed_values = ", ".join(param_schema["enum"]) + raise ValueError( + f"Invalid value '{value}' for parameter '{key}'. Allowed values: {allowed_values}" + ) validated_arguments[key] = value else: @@ -519,7 +520,7 @@ async def _execute_api_call( except (json.JSONDecodeError, toon.ToonDecodeError): return response.text except httpx.HTTPError as e: - return f"HTTP error: {str(e)}" + return f"HTTP error: {e!s}" finally: await http_client.aclose() diff --git a/cloudsmith_cli/core/pagination.py b/cloudsmith_cli/core/pagination.py index 73131ec8..2a6f6bc3 100644 --- a/cloudsmith_cli/core/pagination.py +++ b/cloudsmith_cli/core/pagination.py @@ -19,8 +19,8 @@ def __str__(self): data = self.as_dict() data["valid"] = self.is_valid return ( - "Valid: %(valid)s, Count: %(count)s, Page: %(page)s, " - "Size: %(page_size)s, Total: %(results_total)s" % data + "Valid: {valid}, Count: {count}, Page: {page}, " + "Size: {page_size}, Total: {results_total}".format(**data) ) def calculate_range(self, num_results): diff --git a/cloudsmith_cli/core/ratelimits.py b/cloudsmith_cli/core/ratelimits.py index 77d836a1..70bf3d2f 100644 --- a/cloudsmith_cli/core/ratelimits.py +++ b/cloudsmith_cli/core/ratelimits.py @@ -15,16 +15,10 @@ class RateLimitsInfo: def __str__(self): """Get rate limit information as text.""" + throttled = "Yes" if self.throttled else "No" return ( - "Throttled: %(throttled)s, Remaining: %(remaining)d/%(limit)d, " - "Interval: %(interval)f, Reset: %(reset)s" - % { - "throttled": "Yes" if self.throttled else "No", - "remaining": self.remaining, - "limit": self.limit, - "interval": self.interval, - "reset": self.reset, - } + f"Throttled: {throttled}, Remaining: {self.remaining}/{self.limit}, " + f"Interval: {self.interval}, Reset: {self.reset}" ) @classmethod @@ -39,7 +33,9 @@ def from_dict(cls, data): if "remaining" in data: info.remaining = int(data["remaining"]) if "reset" in data: - info.reset = datetime.datetime.utcfromtimestamp(int(data["reset"])) + info.reset = datetime.datetime.fromtimestamp( + int(data["reset"]), tz=datetime.timezone.utc + ) if "throttled" in data: info.throttled = bool(data["throttled"]) else: diff --git a/cloudsmith_cli/core/rest.py b/cloudsmith_cli/core/rest.py index e9652931..fb1afe7a 100644 --- a/cloudsmith_cli/core/rest.py +++ b/cloudsmith_cli/core/rest.py @@ -240,7 +240,7 @@ def request( **request_kwargs, ) except requests.exceptions.RequestException as exc: - msg = f"{type(exc).__name__}\n{str(exc)}" + msg = f"{type(exc).__name__}\n{exc!s}" raise ApiException(status=0, reason=msg) resp.encoding = resp.apparent_encoding or "utf-8" diff --git a/cloudsmith_cli/core/tests/test_cache_utils.py b/cloudsmith_cli/core/tests/test_cache_utils.py index 15c32369..fcc7eed4 100644 --- a/cloudsmith_cli/core/tests/test_cache_utils.py +++ b/cloudsmith_cli/core/tests/test_cache_utils.py @@ -181,9 +181,9 @@ def test_backup_is_mode_0o600_regardless_of_source_perms(self, tmp_path): bak_path = path + ".bak" assert os.path.exists(bak_path), ".bak must be created" - assert ( - _perms(bak_path) == 0o600 - ), f".bak perms should be 0o600, got {oct(_perms(bak_path))}" + assert _perms(bak_path) == 0o600, ( + f".bak perms should be 0o600, got {oct(_perms(bak_path))}" + ) class TestMergeJsonFileIdempotent: @@ -214,9 +214,9 @@ def test_idempotent_no_overwrite_bak(self, tmp_path): merge_json_file(path, mutate) # second: no change bak_mtime_after_second = os.path.getmtime(bak_path) - assert ( - bak_mtime_after_first == bak_mtime_after_second - ), ".bak must not be refreshed" + assert bak_mtime_after_first == bak_mtime_after_second, ( + ".bak must not be refreshed" + ) class TestMergeJsonFileDryRun: @@ -337,9 +337,9 @@ def test_non_ascii_host_raw_utf8_not_escaped(self, tmp_path): with open(path, "rb") as fh: raw_bytes = fh.read() assert "café".encode() in raw_bytes, "expected raw UTF-8, not \\uXXXX" - assert ( - b"\\u" not in raw_bytes - ), "must not use JSON unicode escapes for non-ASCII" + assert b"\\u" not in raw_bytes, ( + "must not use JSON unicode escapes for non-ASCII" + ) # Second call: identical mutate → no change (idempotent) bak_path = path + ".bak" @@ -352,9 +352,9 @@ def test_non_ascii_host_raw_utf8_not_escaped(self, tmp_path): # .bak must not have been touched on the no-op call if bak_mtime_before is not None: - assert ( - os.path.getmtime(bak_path) == bak_mtime_before - ), ".bak must not refresh" + assert os.path.getmtime(bak_path) == bak_mtime_before, ( + ".bak must not refresh" + ) else: assert not os.path.exists(bak_path), ".bak must not be created on no-op" diff --git a/cloudsmith_cli/core/tests/test_credential_chain_priority.py b/cloudsmith_cli/core/tests/test_credential_chain_priority.py index a8b76128..55c82935 100644 --- a/cloudsmith_cli/core/tests/test_credential_chain_priority.py +++ b/cloudsmith_cli/core/tests/test_credential_chain_priority.py @@ -22,13 +22,13 @@ def test_cli_flag_beats_keyring(self): """Explicit --api-key must win over a keyring SSO token.""" context = self._context(api_key_from_flag="explicit-flag-key") - with patch.object(keyring, "should_use_keyring", return_value=True): - with patch.object(keyring, "get_access_token", return_value="sso-token"): - with patch.object( - keyring, "should_refresh_access_token", return_value=False - ): - chain = CredentialProviderChain() - result = chain.resolve(context) + with ( + patch.object(keyring, "should_use_keyring", return_value=True), + patch.object(keyring, "get_access_token", return_value="sso-token"), + patch.object(keyring, "should_refresh_access_token", return_value=False), + ): + chain = CredentialProviderChain() + result = chain.resolve(context) assert result is not None assert result.api_key == "explicit-flag-key" @@ -38,13 +38,13 @@ def test_env_var_beats_keyring(self): """CLOUDSMITH_API_KEY env var must win over keyring SSO.""" context = self._context(api_key_from_env="env-key") - with patch.object(keyring, "should_use_keyring", return_value=True): - with patch.object(keyring, "get_access_token", return_value="sso-token"): - with patch.object( - keyring, "should_refresh_access_token", return_value=False - ): - chain = CredentialProviderChain() - result = chain.resolve(context) + with ( + patch.object(keyring, "should_use_keyring", return_value=True), + patch.object(keyring, "get_access_token", return_value="sso-token"), + patch.object(keyring, "should_refresh_access_token", return_value=False), + ): + chain = CredentialProviderChain() + result = chain.resolve(context) assert result is not None assert result.api_key == "env-key" @@ -54,13 +54,13 @@ def test_credentials_file_beats_keyring(self): """credentials.ini must win over keyring SSO.""" context = self._context(api_key_from_file="file-key") - with patch.object(keyring, "should_use_keyring", return_value=True): - with patch.object(keyring, "get_access_token", return_value="sso-token"): - with patch.object( - keyring, "should_refresh_access_token", return_value=False - ): - chain = CredentialProviderChain() - result = chain.resolve(context) + with ( + patch.object(keyring, "should_use_keyring", return_value=True), + patch.object(keyring, "get_access_token", return_value="sso-token"), + patch.object(keyring, "should_refresh_access_token", return_value=False), + ): + chain = CredentialProviderChain() + result = chain.resolve(context) assert result is not None assert result.api_key == "file-key" @@ -112,13 +112,13 @@ def test_keyring_used_when_no_explicit_key(self): """Keyring SSO is the fallback when no explicit keys are set.""" context = self._context() - with patch.object(keyring, "should_use_keyring", return_value=True): - with patch.object(keyring, "get_access_token", return_value="sso-token"): - with patch.object( - keyring, "should_refresh_access_token", return_value=False - ): - chain = CredentialProviderChain() - result = chain.resolve(context) + with ( + patch.object(keyring, "should_use_keyring", return_value=True), + patch.object(keyring, "get_access_token", return_value="sso-token"), + patch.object(keyring, "should_refresh_access_token", return_value=False), + ): + chain = CredentialProviderChain() + result = chain.resolve(context) assert result is not None assert result.api_key == "sso-token" diff --git a/cloudsmith_cli/core/tests/test_keyring.py b/cloudsmith_cli/core/tests/test_keyring.py index 13e10938..863710b1 100644 --- a/cloudsmith_cli/core/tests/test_keyring.py +++ b/cloudsmith_cli/core/tests/test_keyring.py @@ -77,7 +77,7 @@ def test_get_access_token_when_error_raised(self, mock_get_user, mock_get_passwo @freeze_time("2024-06-01 10:00:00") def test_update_refresh_attempted_at(self, mock_get_user, mock_set_password): - attempted_at = datetime.utcnow().isoformat() + attempted_at = datetime.now(tz=timezone.utc).isoformat() update_refresh_attempted_at(self.api_host) @@ -126,7 +126,7 @@ def test_get_refresh_attempted_at_when_invalid_datetime_returned( def test_should_refresh_access_token_with_new_token( self, mock_get_user, mock_get_password ): - mock_get_password.return_value = datetime.utcnow().isoformat() + mock_get_password.return_value = datetime.now(tz=timezone.utc).isoformat() assert not should_refresh_access_token(self.api_host) mock_get_password.assert_called_once_with( @@ -139,7 +139,7 @@ def test_should_refresh_access_token_with_token_about_to_expire( self, mock_get_user, mock_get_password ): mock_get_password.return_value = ( - datetime.utcnow() - timedelta(minutes=30) + datetime.now(tz=timezone.utc) - timedelta(minutes=30) ).isoformat() assert not should_refresh_access_token(self.api_host) @@ -153,7 +153,7 @@ def test_should_refresh_access_token_with_expired_token( self, mock_get_user, mock_get_password ): mock_get_password.return_value = ( - datetime.utcnow() - timedelta(minutes=31) + datetime.now(tz=timezone.utc) - timedelta(minutes=31) ).isoformat() assert should_refresh_access_token(self.api_host) diff --git a/cloudsmith_cli/core/tests/test_keyring_provider.py b/cloudsmith_cli/core/tests/test_keyring_provider.py index dcd1ba52..334a389b 100644 --- a/cloudsmith_cli/core/tests/test_keyring_provider.py +++ b/cloudsmith_cli/core/tests/test_keyring_provider.py @@ -20,11 +20,13 @@ def test_returns_none_when_no_token(self): provider = KeyringProvider() env = os.environ.copy() env.pop("CLOUDSMITH_NO_KEYRING", None) - with patch.dict(os.environ, env, clear=True): - with patch.object(keyring, "should_use_keyring", return_value=True): - with patch.object(keyring, "get_access_token", return_value=None): - result = provider.resolve(CredentialContext()) - assert result is None + with ( + patch.dict(os.environ, env, clear=True), + patch.object(keyring, "should_use_keyring", return_value=True), + patch.object(keyring, "get_access_token", return_value=None), + ): + result = provider.resolve(CredentialContext()) + assert result is None def test_returns_bearer_token(self): from cloudsmith_cli.core import keyring @@ -32,19 +34,17 @@ def test_returns_bearer_token(self): provider = KeyringProvider() env = os.environ.copy() env.pop("CLOUDSMITH_NO_KEYRING", None) - with patch.dict(os.environ, env, clear=True): - with patch.object(keyring, "should_use_keyring", return_value=True): - with patch.object( - keyring, "get_access_token", return_value="sso_token" - ): - with patch.object( - keyring, "should_refresh_access_token", return_value=False - ): - result = provider.resolve(CredentialContext()) - assert result is not None - assert result.api_key == "sso_token" - assert result.auth_type == "bearer" - assert result.source_name == "keyring" + with ( + patch.dict(os.environ, env, clear=True), + patch.object(keyring, "should_use_keyring", return_value=True), + patch.object(keyring, "get_access_token", return_value="sso_token"), + patch.object(keyring, "should_refresh_access_token", return_value=False), + ): + result = provider.resolve(CredentialContext()) + assert result is not None + assert result.api_key == "sso_token" + assert result.auth_type == "bearer" + assert result.source_name == "keyring" def test_returns_none_on_refresh_failure(self): from cloudsmith_cli.cli import saml @@ -55,27 +55,19 @@ def test_returns_none_on_refresh_failure(self): context = CredentialContext(session=MagicMock()) env = os.environ.copy() env.pop("CLOUDSMITH_NO_KEYRING", None) - with patch.dict(os.environ, env, clear=True): - with patch.object(keyring, "should_use_keyring", return_value=True): - with patch.object( - keyring, "get_access_token", return_value="old_token" - ): - with patch.object( - keyring, "should_refresh_access_token", return_value=True - ): - with patch.object( - keyring, "get_refresh_token", return_value="refresh_tok" - ): - with patch.object( - saml, - "refresh_access_token", - side_effect=ApiException( - status=401, detail="Unauthorized" - ), - ): - with patch.object( - keyring, "update_refresh_attempted_at" - ): - result = provider.resolve(context) - assert result is None - assert context.keyring_refresh_failed is True + with ( + patch.dict(os.environ, env, clear=True), + patch.object(keyring, "should_use_keyring", return_value=True), + patch.object(keyring, "get_access_token", return_value="old_token"), + patch.object(keyring, "should_refresh_access_token", return_value=True), + patch.object(keyring, "get_refresh_token", return_value="refresh_tok"), + patch.object( + saml, + "refresh_access_token", + side_effect=ApiException(status=401, detail="Unauthorized"), + ), + patch.object(keyring, "update_refresh_attempted_at"), + ): + result = provider.resolve(context) + assert result is None + assert context.keyring_refresh_failed is True diff --git a/cloudsmith_cli/core/tests/test_metadata.py b/cloudsmith_cli/core/tests/test_metadata.py index 2c241857..b2f1e27d 100644 --- a/cloudsmith_cli/core/tests/test_metadata.py +++ b/cloudsmith_cli/core/tests/test_metadata.py @@ -481,8 +481,10 @@ def test_422_on_failing_schema(self): "detail": "Invalid input.", "fields": { "content": [ - "Content does not conform to the schema for content type" - " 'application/vnd.jfrog.buildinfo+json'." + ( + "Content does not conform to the schema for content type" + " 'application/vnd.jfrog.buildinfo+json'." + ) ] }, } @@ -511,8 +513,10 @@ def test_422_on_non_customer_writable_content_type(self): "detail": "Invalid input.", "fields": { "content_type": [ - "Content type 'application/vnd.cloudsmith.system+json'" - " is not customer-writable." + ( + "Content type 'application/vnd.cloudsmith.system+json'" + " is not customer-writable." + ) ] }, } diff --git a/cloudsmith_cli/credential_helpers/common.py b/cloudsmith_cli/credential_helpers/common.py index de0f9d26..17db3dca 100644 --- a/cloudsmith_cli/credential_helpers/common.py +++ b/cloudsmith_cli/credential_helpers/common.py @@ -30,8 +30,7 @@ def extract_hostname(url): normalized = url.lower().strip() # Remove sparse+ prefix (Cargo) - if normalized.startswith("sparse+"): - normalized = normalized[7:] + normalized = normalized.removeprefix("sparse+") # Remove protocol if "://" in normalized: @@ -74,10 +73,8 @@ def is_cloudsmith_domain( return False # Standard Cloudsmith domains — no auth needed, always match regardless of backend_kind - if ( - hostname in ("cloudsmith.io", "cloudsmith.com") - or hostname.endswith(".cloudsmith.io") - or hostname.endswith(".cloudsmith.com") + if hostname in ("cloudsmith.io", "cloudsmith.com") or hostname.endswith( + (".cloudsmith.io", ".cloudsmith.com") ): return True diff --git a/cloudsmith_cli/credential_helpers/docker/installer.py b/cloudsmith_cli/credential_helpers/docker/installer.py index 5ad9356e..818812d8 100644 --- a/cloudsmith_cli/credential_helpers/docker/installer.py +++ b/cloudsmith_cli/credential_helpers/docker/installer.py @@ -216,8 +216,7 @@ def mutate(config: dict) -> None: if changed: for host in hosts: actions.append( - f"set credHelpers[{host!r}]={self.HELPER_VALUE!r}" - f" in {config_path}" + f"set credHelpers[{host!r}]={self.HELPER_VALUE!r} in {config_path}" ) else: actions.append(f"config.json already up to date ({config_path})") diff --git a/cloudsmith_cli/credential_helpers/docker/runtime.py b/cloudsmith_cli/credential_helpers/docker/runtime.py index 2277bc25..38a2dc1c 100644 --- a/cloudsmith_cli/credential_helpers/docker/runtime.py +++ b/cloudsmith_cli/credential_helpers/docker/runtime.py @@ -118,6 +118,8 @@ def execute( return ( 1, None, - f"Error: Unknown operation '{operation}'. " - "Valid operations: get, store, erase, list", + ( + f"Error: Unknown operation '{operation}'. " + "Valid operations: get, store, erase, list" + ), ) diff --git a/pyproject.toml b/pyproject.toml index f780aece..e7286f6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,3 +136,9 @@ show_missing = true [tool.coverage.html] directory = "reports/coverage" + +[tool.ruff.lint] +ignore = ["TRY002", "BLE001"] + +[tool.ruff.lint.per-file-ignores] +'__init__.py' = ["F401"]