def test_case_insensitive_enum_choices(self): from enum import Enum class TestEnum(Enum): # pylint: disable=too-few-public-methods opt1 = "ALL_CAPS" opt2 = "camelCase" opt3 = "snake_case" def test_handler(): pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('opt', '--opt', required=True, **enum_choice_list(TestEnum)) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --opt alL_cAps'.split()) self.assertEqual(args.opt, 'ALL_CAPS') args = parser.parse_args('test command --opt CAMELCASE'.split()) self.assertEqual(args.opt, 'camelCase') args = parser.parse_args('test command --opt sNake_CASE'.split()) self.assertEqual(args.opt, 'snake_case')
def test_register_simple_commands(self): def test_handler1(): pass def test_handler2(): pass command = CliCommand('command the-name', test_handler1) command2 = CliCommand('sub-command the-second-name', test_handler2) cmd_table = { 'command the-name': command, 'sub-command the-second-name': command2 } parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('command the-name'.split()) self.assertIs(args.func, test_handler1) args = parser.parse_args('sub-command the-second-name'.split()) self.assertIs(args.func, test_handler2) AzCliCommandParser.error = VerifyError(self, ) parser.parse_args('sub-command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_register_simple_commands(self): def test_handler1(): pass def test_handler2(): pass cli = TestCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'command the-name', test_handler1) command2 = AzCliCommand(cli.loader, 'sub-command the-second-name', test_handler2) cmd_table = {'command the-name': command, 'sub-command the-second-name': command2} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('command the-name'.split()) self.assertIs(args.func, command) args = parser.parse_args('sub-command the-second-name'.split()) self.assertIs(args.func, command2) AzCliCommandParser.error = VerifyError(self,) parser.parse_args('sub-command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_register_simple_commands(self): def test_handler1(): pass def test_handler2(): pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'command the-name', test_handler1) command2 = AzCliCommand(cli.loader, 'sub-command the-second-name', test_handler2) cmd_table = { 'command the-name': command, 'sub-command the-second-name': command2 } cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('command the-name'.split()) self.assertIs(args.func, command) args = parser.parse_args('sub-command the-second-name'.split()) self.assertIs(args.func, command2) AzCliCommandParser.error = VerifyError(self, ) parser.parse_args('sub-command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_case_insensitive_enum_choices(self): from enum import Enum class TestEnum(Enum): # pylint: disable=too-few-public-methods opt1 = "ALL_CAPS" opt2 = "camelCase" opt3 = "snake_case" def test_handler(): pass command = CliCommand('test command', test_handler) command.add_argument('opt', '--opt', required=True, **enum_choice_list(TestEnum)) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --opt alL_cAps'.split()) self.assertEqual(args.opt, 'ALL_CAPS') args = parser.parse_args('test command --opt CAMELCASE'.split()) self.assertEqual(args.opt, 'camelCase') args = parser.parse_args('test command --opt sNake_CASE'.split()) self.assertEqual(args.opt, 'snake_case')
def test_nargs_parameter(self): def test_handler(): pass command = CliCommand('test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, test_handler) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_required_parameter(self): def test_handler(args): # pylint: disable=unused-argument pass command = CliCommand('test command', test_handler) command.add_argument('req', '--req', required=True) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep'.split()) self.assertIs(args.func, test_handler) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_required_parameter(self): def test_handler(args): # pylint: disable=unused-argument pass command = CliCommand('test command', test_handler) command.add_argument('req', '--req', required=True) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep'.split()) self.assertIs(args.func, test_handler) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_nargs_parameter(self): def test_handler(): pass command = CliCommand('test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_nargs_parameter(self): def test_handler(): pass cli = TestCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} parser = AzCliCommandParser(cli) parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_register_simple_commands(self): def test_handler1(): pass def test_handler2(): pass command = CliCommand('command the-name', test_handler1) command2 = CliCommand('sub-command the-second-name', test_handler2) cmd_table = {'command the-name': command, 'sub-command the-second-name': command2} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('command the-name'.split()) self.assertIs(args.func, command) args = parser.parse_args('sub-command the-second-name'.split()) self.assertIs(args.func, command2) AzCliCommandParser.error = VerifyError(self,) parser.parse_args('sub-command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_required_parameter(self): def test_handler(args): # pylint: disable=unused-argument pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --req yep'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_required_parameter(self): def test_handler(args): # pylint: disable=unused-argument pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --req yep'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_nargs_parameter(self): def test_handler(): pass cli = TestCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_nargs_parameter(self): def test_handler(): pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, command) with mock.patch('azure.cli.core.parser.AzCliCommandParser.error', new=VerifyError(self)): parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
class Application(object): TRANSFORM_RESULT = 'Application.TransformResults' FILTER_RESULT = 'Application.FilterResults' GLOBAL_PARSER_CREATED = 'GlobalParser.Created' COMMAND_PARSER_LOADED = 'CommandParser.Loaded' COMMAND_PARSER_PARSED = 'CommandParser.Parsed' COMMAND_TABLE_LOADED = 'CommandTable.Loaded' def __init__(self, config=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': { 'x-ms-client-request-id': str(uuid.uuid1()) }, 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.initialize(config or Configuration([])) def initialize(self, configuration): self.configuration = configuration def execute(self, unexpanded_argv): argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table() self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if len(argv) == 0: az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) log_telemetry('welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) log_telemetry(expanded_arg.command, log_type='pageview', output_type=self.configuration.output_format, parameters=[p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer= command_table[args.command].table_transformer, is_query_active=self.session['query_active']) def raise_event(self, name, **kwargs): '''Raise the event `name`. ''' logger.info("Application event '%s' with event data %s", name, kwargs) for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list func(**kwargs) def register(self, name, handler): '''Register a callable that will be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].append(handler) logger.info("Registered application event handler '%s' at %s", name, handler) def remove(self, name, handler): '''Remove a callable that is registered to be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].remove(handler) logger.info("Removed application event handler '%s' at %s", name, handler) @staticmethod def _register_builtin_arguments(**kwargs): global_group = kwargs['global_group'] global_group.add_argument('--subscription', dest='_subscription_id', help=argparse.SUPPRESS) global_group.add_argument('--output', '-o', dest='_output_format', choices=['json', 'tsv', 'list', 'table', 'jsonc'], default=az_config.get('core', 'output', fallback='json'), help='Output format', type=str.lower) # The arguments for verbosity don't get parsed by argparse but we add it here for help. global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true', help='Increase logging verbosity. Use --debug for full debug logs.') #pylint: disable=line-too-long global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true', help='Increase logging verbosity to show all debug logs.') @staticmethod def _expand_file_prefixed_files(argv): return list( [Application._load_file(arg[1:]) if arg.startswith('@') else arg for arg in argv] ) @staticmethod def _load_file(path): try: if path == '-': content = sys.stdin.read() else: with open(path, 'r') as input_file: content = input_file.read() return content[0:-1] if content[-1] == '\n' else content except: raise CLIError('Failed to open file {}'.format(path)) def _handle_builtin_arguments(self, **kwargs): args = kwargs['args'] self.configuration.output_format = args._output_format #pylint: disable=protected-access del args._output_format
class Application(object): TRANSFORM_RESULT = 'Application.TransformResults' FILTER_RESULT = 'Application.FilterResults' GLOBAL_PARSER_CREATED = 'GlobalParser.Created' COMMAND_PARSER_LOADED = 'CommandParser.Loaded' COMMAND_PARSER_PARSING = 'CommandParser.Parsing' COMMAND_PARSER_PARSED = 'CommandParser.Parsed' COMMAND_TABLE_LOADED = 'CommandTable.Loaded' COMMAND_TABLE_PARAMS_LOADED = 'CommandTableParams.Loaded' def __init__(self, configuration=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': { 'x-ms-client-request-id': str(uuid.uuid1()) }, 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.configuration = configuration self.progress_controller = progress.ProgressHook() def get_progress_controller(self, det=False): self.progress_controller.init_progress(progress.get_progress_view(det)) return self.progress_controller def initialize(self, configuration): self.configuration = configuration def execute(self, unexpanded_argv): # pylint: disable=too-many-statements argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table(argv) self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if not argv: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) # TODO: Question, is this needed? telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' # Rudimentary parsing to get the command nouns = [] for i, current in enumerate(argv): try: if current[0] == '-': break except IndexError: pass argv[i] = current.lower() nouns.append(argv[i]) command = ' '.join(nouns) if argv[-1] in ('--help', '-h') or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session['completer_active']: enable_autocomplete(self.parser) self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) telemetry.set_command_details(expanded_arg.command, self.configuration.output_format, [p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session['query_active']) def raise_event(self, name, **kwargs): '''Raise the event `name`. ''' data = truncate_text(str(kwargs), width=500) logger.debug("Application event '%s' with event data %s", name, data) for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list func(**kwargs) def register(self, name, handler): '''Register a callable that will be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].append(handler) logger.debug("Registered application event handler '%s' at %s", name, handler) def remove(self, name, handler): '''Remove a callable that is registered to be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].remove(handler) logger.debug("Removed application event handler '%s' at %s", name, handler) @staticmethod def _register_builtin_arguments(**kwargs): global_group = kwargs['global_group'] global_group.add_argument('--output', '-o', dest='_output_format', choices=['json', 'tsv', 'table', 'jsonc','pandas'], default=az_config.get('core', 'output', fallback='json'), help='Output format', type=str.lower) # The arguments for verbosity don't get parsed by argparse but we add it here for help. global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true', help='Increase logging verbosity. Use --debug for full debug logs.') # pylint: disable=line-too-long global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true', help='Increase logging verbosity to show all debug logs.') @staticmethod def _maybe_load_file(arg): ix = arg.find('@') if ix == -1: # no @ found return arg poss_file = arg[ix + 1:] if not poss_file: # if nothing after @ then it can't be a file return arg elif ix == 0: return Application._load_file(poss_file) # if @ not at the start it can't be a file return arg @staticmethod def _expand_file_prefix(arg): arg_split = arg.split('=', 1) try: return '='.join([arg_split[0], Application._maybe_load_file(arg_split[1])]) except IndexError: return Application._maybe_load_file(arg_split[0]) @staticmethod def _expand_file_prefixed_files(argv): return list([Application._expand_file_prefix(arg) for arg in argv]) @staticmethod def _load_file(path): if path == '-': content = sys.stdin.read() else: content = read_file_content(os.path.expanduser(path), allow_binary=True) return content[0:-1] if content and content[-1] == '\n' else content def _handle_builtin_arguments(self, **kwargs): args = kwargs['args'] self.configuration.output_format = args._output_format # pylint: disable=protected-access del args._output_format
class Application(object): TRANSFORM_RESULT = 'Application.TransformResults' FILTER_RESULT = 'Application.FilterResults' GLOBAL_PARSER_CREATED = 'GlobalParser.Created' COMMAND_PARSER_LOADED = 'CommandParser.Loaded' COMMAND_PARSER_PARSING = 'CommandParser.Parsing' COMMAND_PARSER_PARSED = 'CommandParser.Parsed' COMMAND_TABLE_LOADED = 'CommandTable.Loaded' COMMAND_TABLE_PARAMS_LOADED = 'CommandTableParams.Loaded' def __init__(self, configuration=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': {}, # the x-ms-client-request-id is generated before a command is to execute 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False, 'az_interactive_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.configuration = configuration self.progress_controller = progress.ProgressHook() def get_progress_controller(self, det=False): self.progress_controller.init_progress(progress.get_progress_view(det)) return self.progress_controller def initialize(self, configuration): self.configuration = configuration def execute(self, unexpanded_argv): # pylint: disable=too-many-statements self.refresh_request_id() argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table(argv) self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if not argv: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) # TODO: Question, is this needed? telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' # Rudimentary parsing to get the command nouns = [] for i, current in enumerate(argv): try: if current[0] == '-': break except IndexError: pass argv[i] = current.lower() nouns.append(argv[i]) command = ' '.join(nouns) if argv[-1] in ('--help', '-h') or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session['completer_active']: enable_autocomplete(self.parser) self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) telemetry.set_command_details(expanded_arg.command, self.configuration.output_format, [p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session['query_active']) def raise_event(self, name, **kwargs): '''Raise the event `name`. ''' data = truncate_text(str(kwargs), width=500) logger.debug("Application event '%s' with event data %s", name, data) for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list func(**kwargs) def register(self, name, handler): '''Register a callable that will be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].append(handler) logger.debug("Registered application event handler '%s' at %s", name, handler) def remove(self, name, handler): '''Remove a callable that is registered to be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].remove(handler) logger.debug("Removed application event handler '%s' at %s", name, handler) def refresh_request_id(self): """Assign a new randome GUID as x-ms-client-request-id The method must be invoked before each command execution in order to ensure unique client side request ID is generated. """ self.session['headers']['x-ms-client-request-id'] = str(uuid.uuid1()) @staticmethod def _register_builtin_arguments(**kwargs): global_group = kwargs['global_group'] global_group.add_argument('--output', '-o', dest='_output_format', choices=['json', 'tsv', 'table', 'jsonc'], default=az_config.get('core', 'output', fallback='json'), help='Output format', type=str.lower) # The arguments for verbosity don't get parsed by argparse but we add it here for help. global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true', help='Increase logging verbosity. Use --debug for full debug logs.') global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true', help='Increase logging verbosity to show all debug logs.') @staticmethod def _maybe_load_file(arg): ix = arg.find('@') if ix == -1: # no @ found return arg poss_file = arg[ix + 1:] if not poss_file: # if nothing after @ then it can't be a file return arg elif ix == 0: return Application._load_file(poss_file) # if @ not at the start it can't be a file return arg @staticmethod def _expand_file_prefix(arg): arg_split = arg.split('=', 1) try: return '='.join([arg_split[0], Application._maybe_load_file(arg_split[1])]) except IndexError: return Application._maybe_load_file(arg_split[0]) @staticmethod def _expand_file_prefixed_files(argv): return list([Application._expand_file_prefix(arg) for arg in argv]) @staticmethod def _load_file(path): if path == '-': content = sys.stdin.read() else: content = read_file_content(os.path.expanduser(path), allow_binary=True) return content[0:-1] if content and content[-1] == '\n' else content def _handle_builtin_arguments(self, **kwargs): args = kwargs['args'] self.configuration.output_format = args._output_format # pylint: disable=protected-access del args._output_format
class Application(object): TRANSFORM_RESULT = "Application.TransformResults" FILTER_RESULT = "Application.FilterResults" GLOBAL_PARSER_CREATED = "GlobalParser.Created" COMMAND_PARSER_LOADED = "CommandParser.Loaded" COMMAND_PARSER_PARSED = "CommandParser.Parsed" COMMAND_TABLE_LOADED = "CommandTable.Loaded" COMMAND_TABLE_PARAMS_LOADED = "CommandTableParams.Loaded" def __init__(self, config=None): self._event_handlers = defaultdict(lambda: []) self.session = { "headers": {"x-ms-client-request-id": str(uuid.uuid1())}, "command": "unknown", "completer_active": ARGCOMPLETE_ENV_NAME in os.environ, "query_active": False, } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog="az", add_help=False) global_group = self.global_parser.add_argument_group("global", "Global Arguments") self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog="az", parents=[self.global_parser]) self.initialize(config or Configuration([])) def initialize(self, configuration): self.configuration = configuration def execute(self, unexpanded_argv): argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table() self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if len(argv) == 0: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) log_telemetry("welcome") return None if argv[0].lower() == "help": argv[0] = "--help" # Rudimentary parsing to get the command nouns = [] for noun in argv: if noun[0] == "-": break nouns.append(noun) command = " ".join(nouns) if argv[-1] in ("--help", "-h") or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session["completer_active"]: enable_autocomplete(self.parser) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session["command"] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, "_parser", self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith("_")]) params.pop("subcommand", None) params.pop("func", None) params.pop("command", None) log_telemetry( expanded_arg.command, log_type="pageview", output_type=self.configuration.output_format, parameters=[p for p in unexpanded_argv if p.startswith("-")], ) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {"result": results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem( event_data["result"], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session["query_active"], ) def raise_event(self, name, **kwargs): """Raise the event `name`. """ logger.info("Application event '%s' with event data %s", name, kwargs) for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list func(**kwargs) def register(self, name, handler): """Register a callable that will be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. """ self._event_handlers[name].append(handler) logger.info("Registered application event handler '%s' at %s", name, handler) def remove(self, name, handler): """Remove a callable that is registered to be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. """ self._event_handlers[name].remove(handler) logger.info("Removed application event handler '%s' at %s", name, handler) @staticmethod def _register_builtin_arguments(**kwargs): global_group = kwargs["global_group"] global_group.add_argument( "--output", "-o", dest="_output_format", choices=["json", "tsv", "list", "table", "jsonc"], default=az_config.get("core", "output", fallback="json"), help="Output format", type=str.lower, ) # The arguments for verbosity don't get parsed by argparse but we add it here for help. global_group.add_argument( "--verbose", dest="_log_verbosity_verbose", action="store_true", help="Increase logging verbosity. Use --debug for full debug logs.", ) # pylint: disable=line-too-long global_group.add_argument( "--debug", dest="_log_verbosity_debug", action="store_true", help="Increase logging verbosity to show all debug logs.", ) @staticmethod def _expand_file_prefixed_files(argv): return list([Application._load_file(arg[1:]) if arg.startswith("@") else arg for arg in argv]) @staticmethod def _load_file(path): try: if path == "-": content = sys.stdin.read() else: with open(path, "r") as input_file: content = input_file.read() return content[0:-1] if content[-1] == "\n" else content except: raise CLIError("Failed to open file {}".format(path)) def _handle_builtin_arguments(self, **kwargs): args = kwargs["args"] self.configuration.output_format = args._output_format # pylint: disable=protected-access del args._output_format