This is an automated email from the ASF dual-hosted git repository.

juergbi pushed a commit to branch jbilleter/click
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit f48ecb43901191d230bc7738356b7514a4c71688
Author: Jürg Billeter <[email protected]>
AuthorDate: Fri Sep 19 13:07:32 2025 +0200

    _frontend: Fix shell completion with Click >= 8.2
---
 src/buildstream/_frontend/cli.py      | 18 ++++++++++++++----
 src/buildstream/_frontend/complete.py | 16 ++++++++++++----
 2 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/src/buildstream/_frontend/cli.py b/src/buildstream/_frontend/cli.py
index 9d7619bae..4f916dfbc 100644
--- a/src/buildstream/_frontend/cli.py
+++ b/src/buildstream/_frontend/cli.py
@@ -15,6 +15,7 @@ import os
 import re
 import sys
 from functools import partial
+from typing import TYPE_CHECKING
 
 import shutil
 import click
@@ -25,6 +26,15 @@ from ..types import _CacheBuildTrees, _SchedulerErrorAction, 
_PipelineSelection,
 from .._remotespec import RemoteSpec, RemoteSpecPurpose
 from ..utils import UtilError
 
+if TYPE_CHECKING or click.Command.__bases__ == (object,):
+    # Click >= 8.2
+    ClickCommandBaseClass = click.Command
+    ClickGroupBaseClass = click.Group
+else:
+    # Click < 8.2
+    ClickCommandBaseClass = click.BaseCommand
+    ClickGroupBaseClass = click.MultiCommand
+
 
 ##################################################################
 #              Helper classes and methods for Click              #
@@ -105,7 +115,7 @@ def search_command(args, *, context=None):
 # Completion for completing command names as help arguments
 def complete_commands(cmd, args, incomplete):
     command_ctx = search_command(args[1:])
-    if command_ctx and command_ctx.command and isinstance(command_ctx.command, 
click.MultiCommand):
+    if command_ctx and command_ctx.command and isinstance(command_ctx.command, 
ClickGroupBaseClass):
         return [
             subcommand + " "
             for subcommand in command_ctx.command.list_commands(command_ctx)
@@ -272,10 +282,10 @@ def override_main(self, args=None, prog_name=None, 
complete_var=None, standalone
     original_main(self, args=args, prog_name=prog_name, complete_var=None, 
standalone_mode=standalone_mode, **extra)
 
 
-original_main = click.BaseCommand.main
+original_main = ClickCommandBaseClass.main
 # Disable type checking since mypy doesn't support assigning to a method.
 # See https://github.com/python/mypy/issues/2427.
-click.BaseCommand.main = override_main  # type: ignore
+ClickCommandBaseClass.main = override_main  # type: ignore
 
 
 ##################################################################
@@ -392,7 +402,7 @@ def help_command(ctx, command):
     click.echo(command_ctx.command.get_help(command_ctx), err=True)
 
     # Hint about available sub commands
-    if isinstance(command_ctx.command, click.MultiCommand):
+    if isinstance(command_ctx.command, ClickGroupBaseClass):
         detail = " "
         if command:
             detail = " {} ".format(" ".join(command))
diff --git a/src/buildstream/_frontend/complete.py 
b/src/buildstream/_frontend/complete.py
index 6fef9d2c7..787db6d30 100644
--- a/src/buildstream/_frontend/complete.py
+++ b/src/buildstream/_frontend/complete.py
@@ -32,11 +32,19 @@
 import collections.abc
 import copy
 import os
+from typing import TYPE_CHECKING
 
 import click
-from click.core import MultiCommand, Option, Argument
+from click.core import Option, Argument
 from click.parser import split_arg_string
 
+if TYPE_CHECKING or click.Command.__bases__ == (object,):
+    # Click >= 8.2
+    ClickGroupBaseClass = click.Group
+else:
+    # Click < 8.2
+    ClickGroupBaseClass = click.MultiCommand
+
 WORDBREAK = "="
 
 COMPLETION_SCRIPT = """
@@ -176,7 +184,7 @@ def resolve_ctx(cli, prog_name, args):
     ctx = cli.make_context(prog_name, args, resilient_parsing=True)
     args_remaining = ctx.protected_args + ctx.args
     while ctx is not None and args_remaining:
-        if isinstance(ctx.command, MultiCommand):
+        if isinstance(ctx.command, ClickGroupBaseClass):
             cmd = ctx.command.get_command(ctx, args_remaining[0])
             if cmd is None:
                 return None
@@ -310,7 +318,7 @@ def get_choices(cli, prog_name, args, incomplete, override):
                 found_param = True
                 break
 
-    if not found_param and isinstance(ctx.command, MultiCommand):
+    if not found_param and isinstance(ctx.command, ClickGroupBaseClass):
         # completion for any subcommands
         choices.extend(
             [cmd + " " for cmd in ctx.command.list_commands(ctx) if not 
ctx.command.get_command(ctx, cmd).hidden]
@@ -319,7 +327,7 @@ def get_choices(cli, prog_name, args, incomplete, override):
     if (
         not start_of_option(incomplete)
         and ctx.parent is not None
-        and isinstance(ctx.parent.command, MultiCommand)
+        and isinstance(ctx.parent.command, ClickGroupBaseClass)
         and ctx.parent.command.chain
     ):
         # completion for chained commands

Reply via email to