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 re import sys from functools import partial +from typing import TYPE_CHECKING import shutil import click @@ -25,6 +26,15 @@ 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 diff --git a/src/buildstream/types.py b/src/buildstream/types.py index efb338515..d4d5e3cdb 100644 --- a/src/buildstream/types.py +++ b/src/buildstream/types.py @@ -67,14 +67,14 @@ def __new__(cls, value): def __eq__(self, other): if self.__class__ is not other.__class__: - raise ValueError("Unexpected comparison between {} and {}".format(self, repr(other))) + return NotImplemented # Enums instances are unique, so creating an instance with the same value as another will just # send back the other one, hence we can use an identity comparison, which is much faster than '==' return self is other def __ne__(self, other): if self.__class__ is not other.__class__: - raise ValueError("Unexpected comparison between {} and {}".format(self, repr(other))) + return NotImplemented return self is not other def __hash__(self):