From decf4638cfd8de4047f75122bbbbd35bab4b5bf2 Mon Sep 17 00:00:00 2001 From: Jiashuo Li Date: Fri, 20 Mar 2020 18:08:23 +0800 Subject: [PATCH] {Core} Knack adoption (#12604) * Allow disabling color (#12601) * Support --only-show-errors (#12544) * Add experimental tag (#12543) * Move yaml output to Knack (#12603) --- src/azure-cli-core/azure/cli/core/__init__.py | 18 ++-- src/azure-cli-core/azure/cli/core/_help.py | 19 +++-- src/azure-cli-core/azure/cli/core/_output.py | 28 ------- .../azure/cli/core/commands/__init__.py | 82 +++++++++++-------- .../azure/cli/core/commands/arm.py | 1 + .../azure/cli/core/commands/constants.py | 8 +- .../azure/cli/core/extension/operations.py | 7 +- src/azure-cli-core/azure/cli/core/parser.py | 1 + .../azure/cli/core/tests/test_output.py | 2 +- src/azure-cli-core/setup.py | 2 +- src/azure-cli/requirements.py3.Darwin.txt | 2 +- src/azure-cli/requirements.py3.Linux.txt | 2 +- src/azure-cli/requirements.py3.windows.txt | 2 +- src/azure-cli/setup.py | 2 +- 14 files changed, 86 insertions(+), 90 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/__init__.py b/src/azure-cli-core/azure/cli/core/__init__.py index e2b8e13d2c8..1810256d627 100644 --- a/src/azure-cli-core/azure/cli/core/__init__.py +++ b/src/azure-cli-core/azure/cli/core/__init__.py @@ -18,6 +18,7 @@ from knack.introspection import extract_args_from_signature, extract_full_summary_from_signature from knack.log import get_logger from knack.preview import PreviewItem +from knack.experimental import ExperimentalItem from knack.util import CLIError from knack.arguments import ArgumentsContext, CaseInsensitiveList # pylint: disable=unused-import @@ -85,8 +86,8 @@ def get_cli_version(self): def show_version(self): from azure.cli.core.util import get_az_version_string - from azure.cli.core.commands.constants import SURVEY_PROMPT - import colorama + from azure.cli.core.commands.constants import SURVEY_PROMPT, SURVEY_PROMPT_COLOR + ver_string, updates_available = get_az_version_string() print(ver_string) if updates_available == -1: @@ -98,9 +99,7 @@ def show_version(self): else: print('Your CLI is up-to-date.') - colorama.init() # This could be removed when knack fix is released - print('\n' + SURVEY_PROMPT) - colorama.deinit() # This could be removed when knack fix is released + print('\n' + (SURVEY_PROMPT_COLOR if self.enable_color else SURVEY_PROMPT)) def exception_handler(self, ex): # pylint: disable=no-self-use from azure.cli.core.util import handle_exception @@ -162,7 +161,7 @@ def _update_command_table_from_modules(args): # Changing this error message requires updating CI script that checks for failed # module loading. import azure.cli.core.telemetry as telemetry - logger.error("Error loading command module '%s'", mod) + logger.error("Error loading command module '%s': %s", mod, ex) telemetry.set_exception(exception=ex, fault_type='module-load-error-' + mod, summary='Error loading module: {}'.format(mod)) logger.debug(traceback.format_exc()) @@ -450,6 +449,13 @@ def command_group(self, group_name, command_type=None, **kwargs): kwargs['deprecate_info'].target = group_name if kwargs.get('is_preview', False): kwargs['preview_info'] = PreviewItem( + cli_ctx=self.cli_ctx, + target=group_name, + object_type='command group' + ) + if kwargs.get('is_experimental', False): + kwargs['experimental_info'] = ExperimentalItem( + cli_ctx=self.cli_ctx, target=group_name, object_type='command group' ) diff --git a/src/azure-cli-core/azure/cli/core/_help.py b/src/azure-cli-core/azure/cli/core/_help.py index 8c03b09b588..d35b65a0708 100644 --- a/src/azure-cli-core/azure/cli/core/_help.py +++ b/src/azure-cli-core/azure/cli/core/_help.py @@ -7,7 +7,7 @@ import argparse from azure.cli.core.commands import ExtensionCommandSource -from azure.cli.core.commands.constants import SURVEY_PROMPT +from azure.cli.core.commands.constants import SURVEY_PROMPT, SURVEY_PROMPT_COLOR from knack.help import (HelpFile as KnackHelpFile, CommandHelpFile as KnackCommandHelpFile, GroupHelpFile as KnackGroupHelpFile, ArgumentGroupRegistry as KnackArgumentGroupRegistry, @@ -64,6 +64,7 @@ def _print_header(self, cli_name, help_file): def _print_detailed_help(self, cli_name, help_file): CLIPrintMixin._print_extensions_msg(help_file) super(CLIPrintMixin, self)._print_detailed_help(cli_name, help_file) + self._print_az_find_message(help_file.command, self.cli_ctx.enable_color) @staticmethod def _get_choices_defaults_sources_str(p): @@ -75,7 +76,6 @@ def _get_choices_defaults_sources_str(p): @staticmethod def _print_examples(help_file): - from colorama import Style indent = 0 _print_indent('Examples', indent) for e in help_file.examples: @@ -86,9 +86,15 @@ def _print_examples(help_file): _print_indent(u'{0}'.format(e.long_summary), indent) _print_indent(u'{0}'.format(e.command), indent) print('') + + @staticmethod + def _print_az_find_message(command, enable_color): + from colorama import Style indent = 0 - message = 'For more specific examples, use: az find "az {}"'.format(help_file.command) - _print_indent(Style.BRIGHT + message + Style.RESET_ALL + '\n', indent) + message = 'For more specific examples, use: az find "az {}"'.format(command) + if enable_color: + message = Style.BRIGHT + message + Style.RESET_ALL + _print_indent(message + '\n', indent) @staticmethod def _process_value_sources(p): @@ -153,8 +159,6 @@ def new_normalize_text(s): def show_help(self, cli_name, nouns, parser, is_group): self.update_loaders_with_help_file_contents(nouns) - import colorama - colorama.init(autoreset=True) delimiters = ' '.join(nouns) help_file = self.command_help_cls(self, delimiters, parser) if not is_group \ else self.group_help_cls(self, delimiters, parser) @@ -165,7 +169,7 @@ def show_help(self, cli_name, nouns, parser, is_group): AzCliHelp.update_examples(help_file) self._print_detailed_help(cli_name, help_file) - print(SURVEY_PROMPT) + print(SURVEY_PROMPT_COLOR if self.cli_ctx.enable_color else SURVEY_PROMPT) def _register_help_loaders(self): import azure.cli.core._help_loaders as help_loaders @@ -288,6 +292,7 @@ def __init__(self, help_ctx, delimiters, parser): 'name_source': [action.metavar or action.dest], 'deprecate_info': getattr(action, 'deprecate_info', None), 'preview_info': getattr(action, 'preview_info', None), + 'experimental_info': getattr(action, 'experimental_info', None), 'description': action.help, 'choices': action.choices, 'required': False, diff --git a/src/azure-cli-core/azure/cli/core/_output.py b/src/azure-cli-core/azure/cli/core/_output.py index 3d7358d00d5..5cf98b38d8b 100644 --- a/src/azure-cli-core/azure/cli/core/_output.py +++ b/src/azure-cli-core/azure/cli/core/_output.py @@ -7,34 +7,6 @@ class AzOutputProducer(knack.output.OutputProducer): - def __init__(self, cli_ctx=None): - super(AzOutputProducer, self).__init__(cli_ctx) - additional_formats = { - 'yaml': self.format_yaml, - 'yamlc': self.format_yaml_color, - 'none': self.format_none - } - super(AzOutputProducer, self)._FORMAT_DICT.update(additional_formats) - - @staticmethod - def format_yaml(obj): - from yaml import (safe_dump, representer) - import json - - try: - return safe_dump(obj.result, default_flow_style=False) - except representer.RepresenterError: - # yaml.safe_dump fails when obj.result is an OrderedDict. knack's --query implementation converts the result to an OrderedDict. https://github.com/microsoft/knack/blob/af674bfea793ff42ae31a381a21478bae4b71d7f/knack/query.py#L46. # pylint: disable=line-too-long - return safe_dump(json.loads(json.dumps(obj.result)), default_flow_style=False) - - @staticmethod - def format_yaml_color(obj): - from pygments import highlight, lexers, formatters - return highlight(AzOutputProducer.format_yaml(obj), lexers.YamlLexer(), formatters.TerminalFormatter()) # pylint: disable=no-member - - @staticmethod - def format_none(_): - return "" def check_valid_format_type(self, format_type): return format_type in self._FORMAT_DICT diff --git a/src/azure-cli-core/azure/cli/core/commands/__init__.py b/src/azure-cli-core/azure/cli/core/commands/__init__.py index 8ad74d1c8b4..281da334f1f 100644 --- a/src/azure-cli-core/azure/cli/core/commands/__init__.py +++ b/src/azure-cli-core/azure/cli/core/commands/__init__.py @@ -30,10 +30,11 @@ import azure.cli.core.telemetry as telemetry from knack.arguments import CLICommandArgument -from knack.commands import CLICommand, CommandGroup +from knack.commands import CLICommand, CommandGroup, PREVIEW_EXPERIMENTAL_CONFLICT_ERROR from knack.deprecation import ImplicitDeprecated, resolve_deprecate_info from knack.invocation import CommandInvoker from knack.preview import ImplicitPreviewItem, PreviewItem, resolve_preview_info +from knack.experimental import ImplicitExperimentalItem, ExperimentalItem, resolve_experimental_info from knack.log import get_logger from knack.util import CLIError, CommandResultItem, todict from knack.events import EVENT_INVOKER_TRANSFORM_RESULT @@ -686,8 +687,6 @@ def resolve_warnings(self, cmd, parsed_args): self._resolve_extension_override_warning(cmd) def _resolve_preview_and_deprecation_warnings(self, cmd, parsed_args): - import colorama - deprecations = [] + getattr(parsed_args, '_argument_deprecations', []) if cmd.deprecate_info: deprecations.append(cmd.deprecate_info) @@ -724,12 +723,31 @@ def _resolve_preview_and_deprecation_warnings(self, cmd, parsed_args): del preview_kwargs['_get_message'] previews.append(ImplicitPreviewItem(**preview_kwargs)) - colorama.init() - for d in deprecations: - print(d.message, file=sys.stderr) - for p in previews: - print(p.message, file=sys.stderr) - colorama.deinit() + experimentals = [] + getattr(parsed_args, '_argument_experimentals', []) + if cmd.experimental_info: + experimentals.append(cmd.experimental_info) + else: + # search for implicit command experimental status + path_comps = cmd.name.split()[:-1] + implicit_experimental_info = None + while path_comps and not implicit_experimental_info: + implicit_experimental_info = resolve_experimental_info(self.cli_ctx, ' '.join(path_comps)) + del path_comps[-1] + + if implicit_experimental_info: + experimental_kwargs = implicit_experimental_info.__dict__.copy() + experimental_kwargs['object_type'] = 'command' + del experimental_kwargs['_get_tag'] + del experimental_kwargs['_get_message'] + experimentals.append(ImplicitExperimentalItem(**experimental_kwargs)) + + if not self.cli_ctx.only_show_errors: + for d in deprecations: + print(d.message, file=sys.stderr) + for p in previews: + print(p.message, file=sys.stderr) + for e in experimentals: + print(e.message, file=sys.stderr) def _resolve_extension_override_warning(self, cmd): # pylint: disable=no-self-use if isinstance(cmd.command_source, ExtensionCommandSource) and cmd.command_source.overrides_command: @@ -864,12 +882,8 @@ def _generate_template_progress(self, correlation_id): # pylint: disable=no-sel logger.info(result) def __call__(self, poller): - import colorama from msrest.exceptions import ClientException - # https://github.com/azure/azure-cli/issues/3555 - colorama.init() - correlation_message = '' self.cli_ctx.get_progress_controller().begin() correlation_id = None @@ -910,7 +924,6 @@ def __call__(self, poller): handle_long_running_operation_exception(client_exception) self.cli_ctx.get_progress_controller().end() - colorama.deinit() return result @@ -1162,11 +1175,8 @@ def custom_command(self, name, method_name=None, **kwargs): def _command(self, name, method_name, custom_command=False, **kwargs): self._check_stale() merged_kwargs = self._flatten_kwargs(kwargs, get_command_type_kwarg(custom_command)) - # don't inherit deprecation or preview info from command group - merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) - merged_kwargs['preview_info'] = None - if kwargs.get('is_preview', False): - merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + self._apply_tags(merged_kwargs, kwargs, name) + operations_tmpl = merged_kwargs['operations_tmpl'] command_name = '{} {}'.format(self.group_name, name) if self.group_name else name self.command_loader._cli_command(command_name, # pylint: disable=protected-access @@ -1206,11 +1216,7 @@ def generic_update_command(self, name, getter_name='get', getter_type=None, self._check_stale() merged_kwargs = self._flatten_kwargs(kwargs, get_command_type_kwarg()) merged_kwargs_custom = self._flatten_kwargs(kwargs, get_command_type_kwarg(custom_command=True)) - # don't inherit deprecation or preview info from command group - merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) - merged_kwargs['preview_info'] = None - if kwargs.get('is_preview', False): - merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + self._apply_tags(merged_kwargs, kwargs, name) getter_op = self._resolve_operation(merged_kwargs, getter_name, getter_type) setter_op = self._resolve_operation(merged_kwargs, setter_name, setter_type) @@ -1241,11 +1247,7 @@ def _wait_command(self, name, getter_name='get', getter_type=None, custom_comman from azure.cli.core.commands.arm import _cli_wait_command self._check_stale() merged_kwargs = self._flatten_kwargs(kwargs, get_command_type_kwarg(custom_command)) - # don't inherit deprecation or preview info from command group - merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) - merged_kwargs['preview_info'] = None - if kwargs.get('is_preview', False): - merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + self._apply_tags(merged_kwargs, kwargs, name) if getter_type: merged_kwargs = _merge_kwargs(getter_type.settings, merged_kwargs, CLI_COMMAND_KWARGS) @@ -1263,11 +1265,7 @@ def _show_command(self, name, getter_name='get', getter_type=None, custom_comman from azure.cli.core.commands.arm import _cli_show_command self._check_stale() merged_kwargs = self._flatten_kwargs(kwargs, get_command_type_kwarg(custom_command)) - # don't inherit deprecation or preview info from command group - merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) - merged_kwargs['preview_info'] = None - if kwargs.get('is_preview', False): - merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + self._apply_tags(merged_kwargs, kwargs, name) if getter_type: merged_kwargs = _merge_kwargs(getter_type.settings, merged_kwargs, CLI_COMMAND_KWARGS) @@ -1275,6 +1273,22 @@ def _show_command(self, name, getter_name='get', getter_type=None, custom_comman _cli_show_command(self.command_loader, '{} {}'.format(self.group_name, name), getter_op=getter_op, custom_command=custom_command, **merged_kwargs) + def _apply_tags(self, merged_kwargs, kwargs, command_name): + # don't inherit deprecation or preview info from command group + merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) + + # transform is_preview and is_experimental to StatusTags + merged_kwargs['preview_info'] = None + merged_kwargs['experimental_info'] = None + is_preview = kwargs.get('is_preview', False) + is_experimental = kwargs.get('is_experimental', False) + if is_preview and is_experimental: + raise CLIError(PREVIEW_EXPERIMENTAL_CONFLICT_ERROR.format("command", self.group_name + " " + command_name)) + if is_preview: + merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + if is_experimental: + merged_kwargs['experimental_info'] = ExperimentalItem(self.command_loader.cli_ctx, object_type='command') + def register_cache_arguments(cli_ctx): from knack import events diff --git a/src/azure-cli-core/azure/cli/core/commands/arm.py b/src/azure-cli-core/azure/cli/core/commands/arm.py index 59ffd823b48..336d7a9b304 100644 --- a/src/azure-cli-core/azure/cli/core/commands/arm.py +++ b/src/azure-cli-core/azure/cli/core/commands/arm.py @@ -224,6 +224,7 @@ def add_ids_arguments(_, **kwargs): # pylint: disable=unused-argument 'dest': 'ids' if id_arg else '_ids', 'deprecate_info': deprecate_info, 'is_preview': id_arg.settings.get('is_preview', None) if id_arg else None, + 'is_experimental': id_arg.settings.get('is_experimental', None) if id_arg else None, 'nargs': '+', 'arg_group': group_name } diff --git a/src/azure-cli-core/azure/cli/core/commands/constants.py b/src/azure-cli-core/azure/cli/core/commands/constants.py index ed9dfe710fb..5f0dab2c26e 100644 --- a/src/azure-cli-core/azure/cli/core/commands/constants.py +++ b/src/azure-cli-core/azure/cli/core/commands/constants.py @@ -8,7 +8,8 @@ CLI_COMMON_KWARGS = ['min_api', 'max_api', 'resource_type', 'operation_group', - 'custom_command_type', 'command_type', 'is_preview', 'preview_info'] + 'custom_command_type', 'command_type', 'is_preview', 'preview_info', + 'is_experimental', 'experimental_info'] CLI_COMMAND_KWARGS = ['transform', 'table_transformer', 'confirmation', 'exception_handler', 'client_factory', 'operations_tmpl', 'no_wait_param', 'supports_no_wait', 'validator', @@ -31,5 +32,6 @@ BLACKLISTED_MODS = ['context', 'shell', 'documentdb', 'component'] -SURVEY_PROMPT = Fore.YELLOW + Style.BRIGHT + 'Please let us know how we are doing: ' + Fore.BLUE \ - + 'https://aka.ms/clihats' + Style.RESET_ALL +SURVEY_PROMPT = 'Please let us know how we are doing: https://aka.ms/clihats' +SURVEY_PROMPT_COLOR = Fore.YELLOW + Style.BRIGHT + 'Please let us know how we are doing: ' + Fore.BLUE + \ + 'https://aka.ms/clihats' + Style.RESET_ALL diff --git a/src/azure-cli-core/azure/cli/core/extension/operations.py b/src/azure-cli-core/azure/cli/core/extension/operations.py index f826560a717..97a5aea0e02 100644 --- a/src/azure-cli-core/azure/cli/core/extension/operations.py +++ b/src/azure-cli-core/azure/cli/core/extension/operations.py @@ -80,8 +80,6 @@ def _validate_whl_extension(ext_file): def _add_whl_ext(cmd, source, ext_sha256=None, pip_extra_index_urls=None, pip_proxy=None): # pylint: disable=too-many-statements - import colorama - colorama.init() # Required for displaying the spinner correctly on windows issue #9140 cmd.cli_ctx.get_progress_controller().add(message='Analyzing') if not source.endswith('.whl'): raise ValueError('Unknown extension type. Only Python wheels are supported.') @@ -158,7 +156,7 @@ def _add_whl_ext(cmd, source, ext_sha256=None, pip_extra_index_urls=None, pip_pr dst = os.path.join(extension_path, whl_filename) shutil.copyfile(ext_file, dst) logger.debug('Saved the whl to %s', dst) - colorama.deinit() + return extension_name @@ -205,10 +203,7 @@ def add_extension(cmd, source=None, extension_name=None, index_url=None, yes=Non pip_extra_index_urls=None, pip_proxy=None): ext_sha256 = None if extension_name: - import colorama - colorama.init() # Required for displaying the spinner correctly on windows issue #9140 cmd.cli_ctx.get_progress_controller().add(message='Searching') - colorama.deinit() ext = None try: ext = get_extension(extension_name) diff --git a/src/azure-cli-core/azure/cli/core/parser.py b/src/azure-cli-core/azure/cli/core/parser.py index c93530d373b..83615ab86b5 100644 --- a/src/azure-cli-core/azure/cli/core/parser.py +++ b/src/azure-cli-core/azure/cli/core/parser.py @@ -119,6 +119,7 @@ def load_command_table(self, command_loader): param.completer = arg.completer param.deprecate_info = arg.deprecate_info param.preview_info = arg.preview_info + param.experimental_info = arg.experimental_info command_parser.set_defaults( func=metadata, command=command_name, diff --git a/src/azure-cli-core/azure/cli/core/tests/test_output.py b/src/azure-cli-core/azure/cli/core/tests/test_output.py index 26a89db4f44..b114e888ff2 100644 --- a/src/azure-cli-core/azure/cli/core/tests/test_output.py +++ b/src/azure-cli-core/azure/cli/core/tests/test_output.py @@ -38,7 +38,7 @@ def test_yaml_output_with_ordered_dict(self): } output_producer = AzOutputProducer(DummyCli()) - yaml_output = output_producer.format_yaml(CommandResultItem(result=OrderedDict(account_dict))) + yaml_output = output_producer.get_formatter('yaml')(CommandResultItem(result=OrderedDict(account_dict))) self.assertEqual(account_dict, yaml.safe_load(yaml_output)) diff --git a/src/azure-cli-core/setup.py b/src/azure-cli-core/setup.py index 66a971dcfcd..d2858a9627a 100644 --- a/src/azure-cli-core/setup.py +++ b/src/azure-cli-core/setup.py @@ -57,7 +57,7 @@ 'colorama>=0.3.9', 'humanfriendly~=4.7', 'jmespath', - 'knack~=0.6.2', + 'knack==0.7.0rc1', 'msrest>=0.4.4', 'msrestazure>=0.6.2', 'paramiko>=2.0.8,<3.0.0', diff --git a/src/azure-cli/requirements.py3.Darwin.txt b/src/azure-cli/requirements.py3.Darwin.txt index 55fffdfe4e9..0f83294861b 100644 --- a/src/azure-cli/requirements.py3.Darwin.txt +++ b/src/azure-cli/requirements.py3.Darwin.txt @@ -98,7 +98,7 @@ isodate==0.6.0 Jinja2==2.10.1 jmespath==0.9.4 jsmin==2.2.2 -knack==0.6.3 +knack==0.7.0rc1 MarkupSafe==1.1.1 mock==4.0.2 msrestazure==0.6.2 diff --git a/src/azure-cli/requirements.py3.Linux.txt b/src/azure-cli/requirements.py3.Linux.txt index bc354029442..0d36352c6ba 100644 --- a/src/azure-cli/requirements.py3.Linux.txt +++ b/src/azure-cli/requirements.py3.Linux.txt @@ -98,7 +98,7 @@ isodate==0.6.0 Jinja2==2.10.1 jmespath==0.9.4 jsmin==2.2.2 -knack==0.6.3 +knack==0.7.0rc1 MarkupSafe==1.1.1 mock==4.0.2 msrest==0.6.9 diff --git a/src/azure-cli/requirements.py3.windows.txt b/src/azure-cli/requirements.py3.windows.txt index c1813b9f00c..f2b846c48ad 100644 --- a/src/azure-cli/requirements.py3.windows.txt +++ b/src/azure-cli/requirements.py3.windows.txt @@ -96,7 +96,7 @@ isodate==0.6.0 Jinja2==2.10.1 jmespath==0.9.4 jsmin==2.2.2 -knack==0.6.3 +knack==0.7.0rc1 MarkupSafe==1.1.1 mock==4.0.2 msrest==0.6.9 diff --git a/src/azure-cli/setup.py b/src/azure-cli/setup.py index 4c7a44a38d3..83fb1cf68de 100644 --- a/src/azure-cli/setup.py +++ b/src/azure-cli/setup.py @@ -130,7 +130,7 @@ 'cryptography>=2.3.1,<3.0.0', 'fabric~=2.4', 'jsmin~=2.2.2', - 'knack~=0.6,>=0.6.3', + 'knack==0.7.0rc1', 'mock~=4.0', 'paramiko>=2.0.8,<3.0.0', 'pygments~=2.4',