diff --git a/azure-cli.pyproj b/azure-cli.pyproj index f39c9761fea..f34f17e456e 100644 --- a/azure-cli.pyproj +++ b/azure-cli.pyproj @@ -15,8 +15,7 @@ {1dd9c42b-5980-42ce-a2c3-46d3bf0eede4} 3.5 False - - + vm list-all --query "[].name" @@ -36,7 +35,6 @@ - Code @@ -48,15 +46,11 @@ - - Code - - diff --git a/pylintrc b/pylintrc index f304190876a..e964c356744 100644 --- a/pylintrc +++ b/pylintrc @@ -6,6 +6,8 @@ # W0511 fixme disable=C0111,C0103,I0011,W0511 [DESIGN] +# Minimum lines number of a similarity. +min-similarity-lines=6 # Maximum number of locals for function / method body max-locals=25 # Maximum number of branch for function / method body diff --git a/requirements.txt b/requirements.txt index 45159a2c1ac..28463f097cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ applicationinsights==0.10.0 +argcomplete==1.1.0 azure==2.0.0rc1 jmespath mock==1.3.0 diff --git a/src/azure/cli/_event_dispatcher.py b/src/azure/cli/_event_dispatcher.py deleted file mode 100644 index 869ea9f0f7a..00000000000 --- a/src/azure/cli/_event_dispatcher.py +++ /dev/null @@ -1,46 +0,0 @@ -from collections import defaultdict - -class EventDispatcher(object): - """Register for and raise events. - - During the execution of a command, a set of events are raised - that allow extensions to change the flow of actions. - - Clients can register handlers by calling the `EventDispatcher.register` - method passing in the event handler function. - """ - - REGISTER_GLOBAL_PARAMETERS = 'RegisterGlobalParameters' - PARSING_PARAMETERS = 'ParsingParameters' - VALIDATING_PARAMETERS = 'ValidatingParameters' - EXECUTING_COMMAND = 'ExecutingCommand' - TRANSFORM_RESULT = 'TransformResult' - FILTER_RESULT = 'FilterResult' - - def __init__(self): - self._handlers = defaultdict(lambda: []) - - def raise_event(self, name, event_data): - for func in self._handlers[name]: - func(name, event_data) - - 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._handlers[name].append(handler) - - def event_handler(self, name): - '''Any function decorated by @event_handler will - be registered as a handler for the given event name - ''' - def wrapper(func): - self.register(name, func) - return func - return wrapper - diff --git a/src/azure/cli/_output.py b/src/azure/cli/_output.py index c74943191bf..2c19ca09eeb 100644 --- a/src/azure/cli/_output.py +++ b/src/azure/cli/_output.py @@ -3,8 +3,6 @@ import sys import json import re -from datetime import datetime -from enum import Enum from six import StringIO class OutputFormatException(Exception): @@ -51,40 +49,13 @@ class OutputProducer(object): #pylint: disable=too-few-public-methods 'list': format_list } - KEYS_CAMELCASE_PATTERN = re.compile('(?!^)_([a-zA-Z])') - def __init__(self, formatter=format_list, file=sys.stdout): #pylint: disable=redefined-builtin self.formatter = formatter self.file = file - def out(self, session, obj): - obj = OutputProducer.todict(obj) - event_data = {'result': obj} - session.raise_event(session.TRANSFORM_RESULT, event_data) - session.raise_event(session.FILTER_RESULT, event_data) - print(self.formatter(event_data['result']), file=self.file) + def out(self, obj): + print(self.formatter(obj), file=self.file) - @staticmethod - def todict(obj): #pylint: disable=too-many-return-statements - def to_camelcase(s): - return re.sub(OutputProducer.KEYS_CAMELCASE_PATTERN, lambda x: x.group(1).upper(), s) - - if isinstance(obj, dict): - return {k: OutputProducer.todict(v) for (k, v) in obj.items()} - elif isinstance(obj, list): - return [OutputProducer.todict(a) for a in obj] - elif isinstance(obj, Enum): - return obj.value - elif isinstance(obj, datetime): - return obj.isoformat() - elif hasattr(obj, '_asdict'): - return OutputProducer.todict(obj._asdict()) - elif hasattr(obj, '__dict__'): - return dict([(to_camelcase(k), OutputProducer.todict(v)) - for k, v in obj.__dict__.items() - if not callable(v) and not k.startswith('_')]) - else: - return obj @staticmethod def get_formatter(format_type): diff --git a/src/azure/cli/application.py b/src/azure/cli/application.py index d96c0da560d..411781d393e 100644 --- a/src/azure/cli/application.py +++ b/src/azure/cli/application.py @@ -1,75 +1,133 @@ +from collections import defaultdict +from datetime import datetime +import sys +import re import argparse import logging +from enum import Enum from .parser import AzCliCommandParser -import argcomplete -import logging -from ._event_dispatcher import EventDispatcher import azure.cli.extensions -class Session(EventDispatcher): - """The session object tracks session specific data such - as output formats, log settings as well as providing an - event dispatch mechanism that allows us to make modifications - during the execution of a command +class Configuration(object): # pylint: disable=too-few-public-methods + """The configuration object tracks session specific data such + as output formats, available commands etc. """ - def __init__(self): - super(Session, self).__init__() + def __init__(self, argv=None): + self.argv = argv or sys.argv self.log = logging.getLogger('az') self.output_format = 'list' + def get_command_table(self): + import azure.cli.commands as commands + + # Find the first noun on the command line and only load commands from that + # module to improve startup time. + for a in self.argv: + if not a.startswith('-'): + return commands.get_command_table(a) + + # No noun found, so load all commands. + return commands.get_command_table() + class Application(object): - def __init__(self, session=None): - self.session = session or Session() + TRANSFORM_RESULT = 'Application.TransformResults' + FILTER_RESULT = 'Application.FilterResults' + GLOBAL_PARSER_CREATED = 'GlobalParser.Created' + COMMAND_PARSER_CREATED = 'CommandParser.Created' + COMMAND_PARSER_LOADED = 'CommandParser.Loaded' + COMMAND_PARSER_PARSED = 'CommandParser.Parsed' + + def __init__(self, configuration=None): + self._event_handlers = defaultdict(lambda: []) + self.configuration = configuration or Configuration() # Register presence of and handlers for global parameters - self.session.register('GlobalParser.Created', self._register_builtin_arguments) - self.session.register('CommandParser.Parsed', self._handle_builtin_arguments) + self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) + self.register(self.COMMAND_PARSER_LOADED, Application._enable_autocomplete) + self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) - self.session.raise_event('GlobalParser.Created', self.global_parser) + self.raise_event(self.GLOBAL_PARSER_CREATED, self.global_parser) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) - self.session.raise_event('CommandParser.Created', self.parser) - - def load_commands(self, argv): - import azure.cli.commands as commands - - # Find the first noun on the command line and only load commands from that - # module to improve startup time. - for a in argv: - if not a.startswith('-'): - commands.add_to_parser(self.parser, self.session, a) - break - else: - # No noun found, so load all commands. - commands.add_to_parser(self.parser, self.session) + self.raise_event(self.COMMAND_PARSER_CREATED, self.parser) - self.session.raise_event('CommandParser.Loaded', self.parser) - argcomplete.autocomplete(self.parser) + def load_commands(self): + self.parser.load_command_table(self.configuration.get_command_table()) + self.raise_event(self.COMMAND_PARSER_LOADED, self.parser) def execute(self, argv): args = self.parser.parse_args(argv) - self.session.raise_event('CommandParser.Parsed', args) + self.raise_event(self.COMMAND_PARSER_PARSED, args) # 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 args.__dict__.items() if not key.startswith('_')]) - result = args.func(params, {}) # TODO: Unexpected parameters passed in? - return result + result = args.func(params) + + result = self.todict(result) + event_data = {'result': result} + self.raise_event(self.TRANSFORM_RESULT, event_data) + self.raise_event(self.FILTER_RESULT, event_data) + return event_data['result'] + + def raise_event(self, name, event_data): + '''Raise the event `name`. + ''' + for func in self._event_handlers[name]: + func(event_data) + + 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) - def _register_builtin_arguments(self, name, parser): + KEYS_CAMELCASE_PATTERN = re.compile('(?!^)_([a-zA-Z])') + def todict(self, obj): #pylint: disable=too-many-return-statements + + def to_camelcase(s): + return re.sub(Application.KEYS_CAMELCASE_PATTERN, lambda x: x.group(1).upper(), s) + + if isinstance(obj, dict): + return {k: self.todict(v) for (k, v) in obj.items()} + elif isinstance(obj, list): + return [self.todict(a) for a in obj] + elif isinstance(obj, Enum): + return obj.value + elif isinstance(obj, datetime): + return obj.isoformat() + elif hasattr(obj, '_asdict'): + return self.todict(obj._asdict()) + elif hasattr(obj, '__dict__'): + return dict([(to_camelcase(k), self.todict(v)) + for k, v in obj.__dict__.items() + if not callable(v) and not k.startswith('_')]) + else: + return obj + + @staticmethod + def _enable_autocomplete(parser): + import argcomplete + argcomplete.autocomplete(parser) + + @staticmethod + def _register_builtin_arguments(parser): parser.add_argument('--subscription', dest='_subscription_id', help=argparse.SUPPRESS) - parser.add_argument('--output', '-o', dest='_output_format', choices=['list', 'json']) - - def _handle_builtin_arguments(self, name, args): - try: - self.session.output_format = args._output_format #pylint: disable=protected-access - del args._output_format - except Exception: - pass + parser.add_argument('--output', '-o', dest='_output_format', choices=['list', 'json'], + help=argparse.SUPPRESS) + + def _handle_builtin_arguments(self, args): + self.configuration.output_format = args._output_format #pylint: disable=protected-access + del args._output_format diff --git a/src/azure/cli/commands/__init__.py b/src/azure/cli/commands/__init__.py index e018fdf870f..40dec44f2fd 100644 --- a/src/azure/cli/commands/__init__.py +++ b/src/azure/cli/commands/__init__.py @@ -13,7 +13,7 @@ COMMON_PARAMETERS = { 'resource_group_name': { - 'name': '--resourcegroup --rg', + 'name': '--resourcegroup -g', 'metavar': 'RESOURCE GROUP', 'help': 'Name of resource group', 'required': True @@ -39,7 +39,7 @@ class CommandTable(defaultdict): ArgumentParser.add_parser. """ def __init__(self): - super(CommandTable, self).__init__(lambda: {'options': []}) + super(CommandTable, self).__init__(lambda: {'arguments': []}) def command(self, name, **kwargs): def wrapper(func): @@ -58,19 +58,19 @@ def option(self, name, **kwargs): def wrapper(func): opt = dict(kwargs) opt['name'] = name - self[func]['options'].append(opt) + self[func]['arguments'].append(opt) return func return wrapper -def get_command_table(command_name): +def _get_command_table(command_name): module = __import__('azure.cli.commands.' + command_name) for part in ('cli.commands.' + command_name).split('.'): module = getattr(module, part) return module.get_command_table() -def add_to_parser(parser, session, module_name=None): - '''Loads commands into the parser +def get_command_table(module_name=None): + '''Loads command table(s) When `module_name` is specified, only commands from that module will be loaded. If the module is not found, all commands are loaded. @@ -78,13 +78,15 @@ def add_to_parser(parser, session, module_name=None): loaded = False if module_name: try: - parser.load_command_table(session, get_command_table(module_name)) + command_table = _get_command_table(module_name) loaded = True except ImportError: # Unknown command - we'll load all below pass if not loaded: + command_table = {} for mod in COMMAND_MODULES: - parser.load_command_table(session, get_command_table(mod)) + command_table.update(_get_command_table(mod)) loaded = True + return command_table diff --git a/src/azure/cli/commands/_auto_command.py b/src/azure/cli/commands/_auto_command.py index ba0c2f0bfc0..cefeabc9987 100644 --- a/src/azure/cli/commands/_auto_command.py +++ b/src/azure/cli/commands/_auto_command.py @@ -5,9 +5,7 @@ from msrest.paging import Paged from msrest.exceptions import ClientException from azure.cli.parser import IncorrectUsageError -from ..commands import CommandTable, COMMON_PARAMETERS - -from argcomplete import warn +from ..commands import COMMON_PARAMETERS EXCLUDED_PARAMS = frozenset(['self', 'raw', 'custom_headers', 'operation_config']) @@ -51,7 +49,7 @@ def _get_member(obj, path): return obj def _make_func(client_factory, member_path, return_type_or_func, unbound_func): - def call_client(args, unexpected): #pylint: disable=unused-argument + def call_client(args): client = client_factory() ops_instance = _get_member(client, member_path) try: @@ -110,6 +108,6 @@ def build_operation(command_table, command_name, member_path, client_type, opera command_table[func] = { 'name': ' '.join([command_name, opname]), 'handler': func, - 'options': options + 'arguments': options } diff --git a/src/azure/cli/commands/account.py b/src/azure/cli/commands/account.py index f1c36f1486d..ab283db0b27 100644 --- a/src/azure/cli/commands/account.py +++ b/src/azure/cli/commands/account.py @@ -4,19 +4,20 @@ command_table = CommandTable() -def get_command_table(): +def get_command_table(): # pylint:disable=duplicate-code return command_table @command_table.command('account list', description=L('List the imported subscriptions.')) -def list_subscriptions(args, unexpected): #pylint: disable=unused-argument +def list_subscriptions(_): profile = Profile() subscriptions = profile.load_subscriptions() return subscriptions -@command_table.option('--subscription-id -n', metavar='SUBSCRIPTION_ID', dest='subscription_id', help=L('Subscription Id, unique name also works.')) +@command_table.option('--subscription-id -n', metavar='SUBSCRIPTION_ID', dest='subscription_id', + help=L('Subscription Id, unique name also works.')) @command_table.command('account set', description=L('Set the current subscription')) -def set_active_subscription(args, unexpected): #pylint: disable=unused-argument +def set_active_subscription(args): subscription_id = args.get('subscription-id') if not id: raise ValueError(L('Please provide subscription id or unique name.')) diff --git a/src/azure/cli/commands/login.py b/src/azure/cli/commands/login.py index e7cb086aca5..0b136e1ddd9 100644 --- a/src/azure/cli/commands/login.py +++ b/src/azure/cli/commands/login.py @@ -15,17 +15,20 @@ command_table = CommandTable() -def get_command_table(): +def get_command_table(): # pylint:disable=duplicate-code return command_table @command_table.option('--username -u', - help=L('organization Id or service principal. Microsoft Account is not yet supported.')) -@command_table.option('--password -p', help=L('user password or client secret, will prompt if not given.')) -@command_table.option('--service-principal', help=L('the credential represents a service principal.')) + help=L('organization Id or service principal. Microsoft Account is not yet supported.')) # pylint: disable=line-too-long +@command_table.option('--password -p', + help=L('user password or client secret, will prompt if not given.')) +@command_table.option('--service-principal', + help=L('the credential represents a service principal.')) @command_table.option('--tenant -t', help=L('the tenant associated with the service principal.')) -@command_table.command('login', description=L('log in to an Azure subscription using Active Directory Organization Id')) -def login(args, unexpected): #pylint: disable=unused-argument +@command_table.command('login', + description=L('log in to an Azure subscription using Active Directory Organization Id')) # pylint: disable=line-too-long +def login(args): interactive = False username = args.get('username') diff --git a/src/azure/cli/commands/logout.py b/src/azure/cli/commands/logout.py index 3242ec4d704..847e3f3af82 100644 --- a/src/azure/cli/commands/logout.py +++ b/src/azure/cli/commands/logout.py @@ -4,12 +4,14 @@ command_table = CommandTable() -def get_command_table(): +def get_command_table(): # pylint:disable=duplicate-code return command_table -@command_table.option('--username -u', help=L('User name used to log out from Azure Active Directory.'), +@command_table.option('--username -u', + help=L('User name used to log out from Azure Active Directory.'), required=True) -@command_table.command('logout', description=L('Log out from Azure subscription using Active Directory.')) -def logout(args, unexpected): #pylint: disable=unused-argument +@command_table.command('logout', + description=L('Log out from Azure subscription using Active Directory.')) +def logout(args): profile = Profile() profile.logout(args['username']) diff --git a/src/azure/cli/commands/network.py b/src/azure/cli/commands/network.py index 0a509bf8705..9124616f26b 100644 --- a/src/azure/cli/commands/network.py +++ b/src/azure/cli/commands/network.py @@ -255,7 +255,7 @@ def _network_client_factory(): @command_table.option('--address-space -a', metavar='ADDRESS SPACE', help=L('the VNet address-space in CIDR notation or multiple address-spaces, quoted and space-separated'), required=True) @command_table.option('--dns-servers -d', metavar='DNS SERVERS', help=L('the VNet DNS servers, quoted and space-separated')) @command_table.command('network vnet create') -def create_update_vnet(args, unexpected): #pylint: disable=unused-argument +def create_update_vnet(args): from azure.mgmt.network.models import AddressSpace, DhcpOptions, VirtualNetwork resource_group = args.get('resource-group') @@ -279,7 +279,7 @@ def create_update_vnet(args, unexpected): #pylint: disable=unused-argument @command_table.option('--vnet -v', help=L('the name of the subnet vnet'), required=True) @command_table.option('--address-prefix -a', help=L('the the address prefix in CIDR format'), required=True) @command_table.command('network subnet create') -def create_update_subnet(args, unexpected): #pylint: disable=unused-argument +def create_update_subnet(args): from azure.mgmt.network.models import Subnet resource_group = args.get('resource-group') diff --git a/src/azure/cli/commands/resource.py b/src/azure/cli/commands/resource.py index 2b651b631e9..055817934ed 100644 --- a/src/azure/cli/commands/resource.py +++ b/src/azure/cli/commands/resource.py @@ -14,7 +14,7 @@ def get_command_table(): @command_table.option('--tag-name -tn', help=L("the resource group's tag name")) @command_table.option('--tag-value -tv', help=L("the resource group's tag value")) @command_table.command('resource group list', description=L('List resource groups')) -def list_groups(args, unexpected): #pylint: disable=unused-argument +def list_groups(args): from azure.mgmt.resource.resources.models import ResourceGroup, ResourceGroupFilter rmc = get_mgmt_service_client(ResourceManagementClient, ResourceManagementClientConfiguration) @@ -40,8 +40,8 @@ def list_groups(args, unexpected): #pylint: disable=unused-argument required=True) @command_table.option('--api-version -o', help=L('the API version of the resource provider')) @command_table.option('--parent', - help=L('the name of the parent resource (if needed), in / format')) -def show_resource(args, unexpected): #pylint: disable=unused-argument + help=L('the name of the parent resource (if needed), in / format')) # pylint: disable=line-too-long +def show_resource(args): rmc = get_mgmt_service_client(ResourceManagementClient, ResourceManagementClientConfiguration) full_type = args.get('resource-type').split('/') diff --git a/src/azure/cli/commands/storage.py b/src/azure/cli/commands/storage.py index 49b5d3acd71..ab1a5e2b5b0 100644 --- a/src/azure/cli/commands/storage.py +++ b/src/azure/cli/commands/storage.py @@ -7,7 +7,6 @@ from azure.mgmt.storage import StorageManagementClient, StorageManagementClientConfiguration from azure.mgmt.storage.operations import StorageAccountsOperations -from ..parser import IncorrectUsageError from ..commands import CommandTable, COMMON_PARAMETERS as GLOBAL_COMMON_PARAMETERS from ._command_creation import get_mgmt_service_client, get_data_service_client from ..commands._auto_command import build_operation @@ -23,31 +22,39 @@ def extend_parameter(parameter_metadata, **kwargs): COMMON_PARAMETERS = GLOBAL_COMMON_PARAMETERS.copy() COMMON_PARAMETERS.update({ 'account-name': { - 'name': '--account-name -n', - 'help': L('the storage account name'), - 'required': not environ.get('AZURE_STORAGE_ACCOUNT'), - 'default': environ.get('AZURE_STORAGE_ACCOUNT') + 'name': '--account-name -n', + 'help': L('the storage account name'), + # While account name *may* actually be required if the environment variable hasn't been + # specified, it is only required unless the connection string has been specified + 'required': False, + 'default': environ.get('AZURE_STORAGE_ACCOUNT') }, - 'optional_resource_group_name': extend_parameter(GLOBAL_COMMON_PARAMETERS['resource_group_name'], required=False), + 'optional_resource_group_name': + extend_parameter(GLOBAL_COMMON_PARAMETERS['resource_group_name'], required=False), 'account_key': { - 'name': '--account-key -k', - 'help': L('the storage account key'), - 'required': not environ.get('AZURE_STORAGE_ACCESS_KEY'), - 'default': environ.get('AZURE_STORAGE_ACCESS_KEY') + 'name': '--account-key -k', + 'help': L('the storage account key'), + # While account key *may* actually be required if the environment variable hasn't been + # specified, it is only required unless the connection string has been specified + 'required': False, + 'default': environ.get('AZURE_STORAGE_ACCESS_KEY') }, 'blob-name': { 'name': '--blob-name -bn', - 'help': L('the name of the blob'), + 'help': L('the name of the blob'), 'required': True }, 'container-name': { 'name': '--container-name -c', 'required': True - }, + }, 'connection-string': { 'name': '--connection-string -t', 'help': L('the storage connection string'), - 'required': not environ.get('AZURE_STORAGE_CONNECTION_STRING'), + # You can either specify connection string or name/key. There is no convenient way + # to express this kind of relationship in argparse... + # TODO: Move to exclusive group + 'required': False, 'default': environ.get('AZURE_STORAGE_CONNECTION_STRING') } }) @@ -73,7 +80,7 @@ def _storage_client_factory(): @command_table.command('storage account list', description=L('List storage accounts.')) @command_table.option(**COMMON_PARAMETERS['optional_resource_group_name']) -def list_accounts(args, unexpected): #pylint: disable=unused-argument +def list_accounts(args): from azure.mgmt.storage.models import StorageAccount from msrestazure.azure_active_directory import UserPassCredentials smc = _storage_client_factory() @@ -88,9 +95,9 @@ def list_accounts(args, unexpected): #pylint: disable=unused-argument @command_table.description(L('Regenerate one or both keys for a storage account.')) @command_table.option(**COMMON_PARAMETERS['resource_group_name']) @command_table.option(**COMMON_PARAMETERS['account-name']) -@command_table.option('--key -y', default=['key1', 'key2'], +@command_table.option('--key -y', default=['key1', 'key2'], choices=['key1', 'key2'], help=L('Key to renew')) -def renew_account_keys(args, unexpected): #pylint: disable=unused-argument +def renew_account_keys(args): smc = _storage_client_factory() for key in args.get('key'): result = smc.storage_accounts.regenerate_key( @@ -103,7 +110,7 @@ def renew_account_keys(args, unexpected): #pylint: disable=unused-argument @command_table.command('storage account usage') @command_table.description( L('Show the current count and limit of the storage accounts under the subscription.')) -def show_account_usage(args, unexpected): #pylint: disable=unused-argument +def show_account_usage(_): smc = _storage_client_factory() return next((x for x in smc.usage.list() if x.name.value == 'StorageAccounts'), None) @@ -113,10 +120,10 @@ def show_account_usage(args, unexpected): #pylint: disable=unused-argument @command_table.option(**COMMON_PARAMETERS['account-name']) @command_table.option('--use-http', action='store_const', const='http', default='https', help=L('use http as the default endpoint protocol')) -def show_storage_connection_string(args, unexpected): #pylint: disable=unused-argument +def show_storage_connection_string(args): smc = _storage_client_factory() - endpoint_protocol = args.get('use-http') + endpoint_protocol = args.get('use-http') storage_account = args.get('account-name') keys = smc.storage_accounts.list_keys(args.get('resource_group_name'), storage_account) @@ -138,9 +145,9 @@ def show_storage_connection_string(args, unexpected): #pylint: disable=unused-ar @command_table.option(**COMMON_PARAMETERS['account-name']) @command_table.option(**COMMON_PARAMETERS['account_key']) @command_table.option(**COMMON_PARAMETERS['connection-string']) -@command_table.option('--public-access -p', default=None, choices=public_access_types.keys(), +@command_table.option('--public-access -p', default=None, choices=public_access_types.keys(), type=lambda x: public_access_types[x]) -def create_container(args, unexpected): #pylint: disable=unused-argument +def create_container(args): bbs = _get_blob_service_client(args) public_access = args.get('public-access') @@ -154,7 +161,7 @@ def create_container(args, unexpected): #pylint: disable=unused-argument @command_table.option(**COMMON_PARAMETERS['account_key']) @command_table.option(**COMMON_PARAMETERS['connection-string']) @command_table.option('--force -f', help=L('supress delete confirmation prompt')) -def delete_container(args, unexpected): #pylint: disable=unused-argument +def delete_container(args): bbs = _get_blob_service_client(args) container_name = args.get('container-name') prompt_for_delete = args.get('force') is None @@ -173,7 +180,7 @@ def delete_container(args, unexpected): #pylint: disable=unused-argument @command_table.option(**COMMON_PARAMETERS['account_key']) @command_table.option(**COMMON_PARAMETERS['connection-string']) @command_table.option('--prefix -p', help=L('container name prefix to filter by')) -def list_containers(args, unexpected): #pylint: disable=unused-argument +def list_containers(args): bbs = _get_blob_service_client(args) results = bbs.list_containers(args.get('prefix')) return results @@ -184,7 +191,7 @@ def list_containers(args, unexpected): #pylint: disable=unused-argument @command_table.option(**COMMON_PARAMETERS['account-name']) @command_table.option(**COMMON_PARAMETERS['account_key']) @command_table.option(**COMMON_PARAMETERS['connection-string']) -def show_container(args, unexpected): #pylint: disable=unused-argument +def show_container(args): bbs = _get_blob_service_client(args) result = bbs.get_container_properties(args.get('container-name')) return result @@ -200,7 +207,8 @@ def show_container(args, unexpected): #pylint: disable=unused-argument @command_table.option(**COMMON_PARAMETERS['account-name']) @command_table.option(**COMMON_PARAMETERS['account_key']) @command_table.option(**COMMON_PARAMETERS['connection-string']) -@command_table.option('--container.public-access -cpa', default=None, choices=public_access_types.keys(), +@command_table.option('--container.public-access -cpa', default=None, + choices=public_access_types.keys(), type=lambda x: public_access_types[x]) @command_table.option('--content.type -cst') @command_table.option('--content.disposition -csd') @@ -208,7 +216,7 @@ def show_container(args, unexpected): #pylint: disable=unused-argument @command_table.option('--content.language -csl') @command_table.option('--content.md5 -csm') @command_table.option('--content.cache-control -cscc') -def create_block_blob(args, unexpected): #pylint: disable=unused-argument +def create_block_blob(args): from azure.storage.blob import ContentSettings bbs = _get_blob_service_client(args) public_access = args.get('container.public-access') @@ -234,7 +242,7 @@ def create_block_blob(args, unexpected): #pylint: disable=unused-argument @command_table.option(**COMMON_PARAMETERS['account_key']) @command_table.option(**COMMON_PARAMETERS['connection-string']) @command_table.option('--prefix -p', help=L('blob name prefix to filter by')) -def list_blobs(args, unexpected): #pylint: disable=unused-argument +def list_blobs(args): bbs = _get_blob_service_client(args) blobs = bbs.list_blobs(args.get('container-name'), prefix=args.get('prefix')) @@ -244,7 +252,7 @@ def list_blobs(args, unexpected): #pylint: disable=unused-argument @command_table.description(L('Delete a blob from a container.')) @command_table.option(**COMMON_PARAMETERS['container-name']) @command_table.option(**COMMON_PARAMETERS['blob-name']) -def delete_blob(args, unexpected): #pylint: disable=unused-argument +def delete_blob(args): bbs = _get_blob_service_client(args) return bbs.delete_blob(args.get('container-name'), args.get('blob-name')) @@ -252,7 +260,7 @@ def delete_blob(args, unexpected): #pylint: disable=unused-argument @command_table.description(L('Show properties of the specified blob.')) @command_table.option(**COMMON_PARAMETERS['container-name']) @command_table.option(**COMMON_PARAMETERS['blob-name']) -def show_blob(args, unexpected): #pylint: disable=unused-argument +def show_blob(args): bbs = _get_blob_service_client(args) return bbs.get_blob_properties(args.get('container-name'), args.get('blob-name')) @@ -261,7 +269,7 @@ def show_blob(args, unexpected): #pylint: disable=unused-argument @command_table.option(**COMMON_PARAMETERS['container-name']) @command_table.option(**COMMON_PARAMETERS['blob-name']) @command_table.option('--download-to -dt', help=L('the file path to download to'), required=True) -def download_blob(args, unexpected): #pylint: disable=unused-argument +def download_blob(args): bbs = _get_blob_service_client(args) # show dot indicator of download progress (one for every 10%) @@ -280,7 +288,7 @@ def download_blob(args, unexpected): #pylint: disable=unused-argument @command_table.option(**COMMON_PARAMETERS['account-name']) @command_table.option(**COMMON_PARAMETERS['account_key']) @command_table.option(**COMMON_PARAMETERS['container-name']) -def storage_file_create(args, unexpected): #pylint: disable=unused-argument +def storage_file_create(args): fsc = _get_file_service_client(args) fsc.create_file_from_path(args.get('share-name'), args.get('directory-name'), @@ -292,16 +300,16 @@ def storage_file_create(args, unexpected): #pylint: disable=unused-argument def _get_blob_service_client(args): from azure.storage.blob import BlockBlobService return get_data_service_client(BlockBlobService, - args['storage-account'], - args['storage-account-key'], - args['connection-string']) + args.get('storage-account', None), + args.get('storage-account-key', None), + args.get('connection-string', None)) def _get_file_service_client(args): from azure.storage.file import FileService return get_data_service_client(FileService, - args['storage-account'], - args['storage-account-key'], - args['connection-string']) + args.get('storage-account', None), + args.get('storage-account-key', None), + args.get('connection-string', None)) def _update_progress(current, total): if total: diff --git a/src/azure/cli/extensions/__init__.py b/src/azure/cli/extensions/__init__.py index 094ccbf7fd9..84dffe9399f 100644 --- a/src/azure/cli/extensions/__init__.py +++ b/src/azure/cli/extensions/__init__.py @@ -1,8 +1,6 @@ from .query import register as register_query from .transform import register as register_transform -from .experimental import register as register_experimental def register_extensions(application): - register_query(application.session) - register_transform(application.session) - register_experimental(application.session) + register_query(application) + register_transform(application) diff --git a/src/azure/cli/extensions/query.py b/src/azure/cli/extensions/query.py index 960caa174d7..bbcd29e5c07 100644 --- a/src/azure/cli/extensions/query.py +++ b/src/azure/cli/extensions/query.py @@ -1,19 +1,24 @@ -def register(event_dispatcher): - def register_global_parameter(self, parser): +def register(application): + def register_global_parameter(parser): # Let the program know that we are adding a parameter --query - parser.add_argument('--query', dest='_jmespath_query', metavar='JMESPATH', help='JMESPath query string. See http://jmespath.org/ for more information and examples.') + parser.add_argument('--query', dest='_jmespath_query', metavar='JMESPATH', + help='JMESPath query string. See http://jmespath.org/ for more information and examples.') # pylint: disable=line-too-long - def handle_query_parameter(_, args): + def handle_query_parameter(args): # Of there is a query specified on the command line, we'll take care of that! - query_value = args._jmespath_query - del(args._jmespath_query) + try: + query_value = args._jmespath_query # pylint: disable=protected-access + del args._jmespath_query - if query_value: - def filter_output(_, event_data): - import jmespath - event_data['result'] = jmespath.search(query_value, event_data['result']) + if query_value: + def filter_output(event_data): + import jmespath + event_data['result'] = jmespath.search(query_value, event_data['result']) - event_dispatcher.register(event_dispatcher.FILTER_RESULT, filter_output) + application.register(application.FILTER_RESULT, filter_output) - event_dispatcher.register('GlobalParser.Created', register_global_parameter) - event_dispatcher.register('CommandParser.Parsed', handle_query_parameter) + except AttributeError: + pass + + application.register(application.GLOBAL_PARSER_CREATED, register_global_parameter) + application.register(application.COMMAND_PARSER_PARSED, handle_query_parameter) diff --git a/src/azure/cli/extensions/transform.py b/src/azure/cli/extensions/transform.py index f9152e1f816..5804d22b607 100644 --- a/src/azure/cli/extensions/transform.py +++ b/src/azure/cli/extensions/transform.py @@ -1,8 +1,7 @@ import re def register(event_dispatcher): - @event_dispatcher.event_handler(event_dispatcher.TRANSFORM_RESULT) - def resource_group_transform(_, event_data): # pylint: disable=unused-variable + def resource_group_transform(event_data): def parse_id(strid): parsed = {} parts = re.split('/', strid) @@ -25,3 +24,4 @@ def add_resource_group(obj): add_resource_group(obj[item_key]) add_resource_group(event_data['result']) + event_dispatcher.register('TransformResult', resource_group_transform) diff --git a/src/azure/cli/main.py b/src/azure/cli/main.py index ba3c4536424..063a71ca2ea 100644 --- a/src/azure/cli/main.py +++ b/src/azure/cli/main.py @@ -1,7 +1,6 @@ import os import sys -from .parser import AzCliCommandParser from .application import Application from ._logging import configure_logging, logger @@ -26,14 +25,14 @@ def main(args, file=sys.stdout): #pylint: disable=redefined-builtin CONFIG.get('locale', 'en-US'))) app = Application() - app.load_commands(args) + app.load_commands() try: cmd_result = app.execute(args) # Commands can return a dictionary/list of results # If they do, we print the results. if cmd_result: - formatter = OutputProducer.get_formatter(app.session.output_format) - OutputProducer(formatter=formatter, file=file).out(app.session, cmd_result) + formatter = OutputProducer.get_formatter(app.configuration.output_format) + OutputProducer(formatter=formatter, file=file).out(cmd_result) except RuntimeError as ex: logger.error(ex.args[0]) return ex.args[1] if len(ex.args) >= 2 else -1 diff --git a/src/azure/cli/parser.py b/src/azure/cli/parser.py index ee19c016bf9..6718dcbd265 100644 --- a/src/azure/cli/parser.py +++ b/src/azure/cli/parser.py @@ -15,7 +15,7 @@ def __init__(self, **kwargs): self.subparsers = {} self.parents = kwargs.get('parents', None) - def load_command_table(self, session, command_table): + def load_command_table(self, command_table): """Load a command table into our parser. """ # If we haven't already added a subparser, we @@ -31,11 +31,10 @@ def load_command_table(self, session, command_table): # To work around http://bugs.python.org/issue9253, we artificially add any new # parsers we add to the "choices" section of the subparser. subparser.choices[command_name] = command_name - command_parser = subparser.add_parser(command_name, description=metadata.get('description'), + command_parser = subparser.add_parser(command_name, + description=metadata.get('description'), parents=self.parents, conflict_handler='resolve') - session.raise_event('AzCliCommandParser.SubparserCreated', - {'parser': command_parser, 'metadata': metadata}) - for arg in metadata['options']: + for arg in metadata['arguments']: names = arg.pop('name').split() command_parser.add_argument(*names, **arg) diff --git a/src/azure/cli/tests/test_argparse.py b/src/azure/cli/tests/test_argparse.py deleted file mode 100644 index ba5265b75e1..00000000000 --- a/src/azure/cli/tests/test_argparse.py +++ /dev/null @@ -1,207 +0,0 @@ -import unittest -from six import StringIO - -from azure.cli._argparse import ArgumentParser, IncorrectUsageError -from azure.cli._logging import logger -import logging -import azure.cli._util as util - -class Test_argparse(unittest.TestCase): - @classmethod - def setUpClass(cls): - # Ensure initialization has occurred correctly - import azure.cli.main - logging.basicConfig(level=logging.DEBUG) - - @classmethod - def tearDownClass(cls): - logging.shutdown() - - def test_nouns(self): - p = ArgumentParser('test') - res = [False, False, False] - def set_n1(a, b): res[0] = True - def set_n2(a, b): res[1] = True - def set_n3(a, b): res[2] = True - p.add_command(set_n1, 'n1') - p.add_command(set_n2, 'n1 n2') - p.add_command(set_n3, 'n1 n2 n3') - - p.execute('n1 n2 n3'.split()) - self.assertSequenceEqual(res, (False, False, True)) - p.execute('n1'.split()) - self.assertSequenceEqual(res, (True, False, True)) - res[0] = False - p.execute('n1 n2'.split()) - self.assertSequenceEqual(res, (False, True, True)) - - def test_args(self): - p = ArgumentParser('test') - p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', False, None), ('-b ', '', False, None)]) - - cmd_result = p.execute('n1 -a x'.split()) - res, other = cmd_result.result - self.assertTrue(res.arg) - self.assertSequenceEqual(res.positional, ['x']) - - # Should recognize args with alternate prefix - cmd_result = p.execute('n1 /a'.split()) - res, other = cmd_result.result - self.assertTrue(res.arg) - cmd_result = p.execute('n1 /arg'.split()) - res, other = cmd_result.result - self.assertTrue(res.arg) - - # Should not recognize "------a" - cmd_result = p.execute('n1 ------a'.split()) - res, other = cmd_result.result - self.assertNotIn('arg', res) - # First two '--' match, so '----a' is added to dict - self.assertIn('----a', other) - - cmd_result = p.execute('n1 -a:x'.split()) - res = cmd_result.result - self.assertIsNone(res) - - cmd_result = p.execute('n1 -b -a x'.split()) - res, other = cmd_result.result - self.assertEqual(res.b, '-a') - self.assertSequenceEqual(res.positional, ['x']) - self.assertRaises(IncorrectUsageError, lambda: res.arg) - - cmd_result = p.execute('n1 -b:-a x'.split()) - res, other = cmd_result.result - self.assertEqual(res.b, '-a') - self.assertSequenceEqual(res.positional, ['x']) - self.assertRaises(IncorrectUsageError, lambda: res.arg) - - def test_unexpected_args(self): - p = ArgumentParser('test') - p.add_command(lambda a, b: (a, b), 'n1', args=[('-a', '', False, None)]) - - cmd_result = p.execute('n1 -b=2'.split()) - res, other = cmd_result.result - self.assertFalse(res) - self.assertEqual('2', other.b) - - cmd_result = p.execute('n1 -b.c.d=2'.split()) - res, other = cmd_result.result - self.assertFalse(res) - self.assertEqual('2', other.b.c.d) - - cmd_result = p.execute('n1 -b.c.d 2 -b.c.e:3'.split()) - res, other = cmd_result.result - self.assertFalse(res) - self.assertEqual('2', other.b.c.d) - self.assertEqual('3', other.b.c.e) - - def test_required_args(self): - p = ArgumentParser('test') - p.add_command(lambda a, b: (a, b), - 'n1', args=[('--arg -a', '', True, None), - ('-b ', '', False, None)]) - - cmd_result = p.execute('n1 -a x'.split()) - res, other = cmd_result.result - self.assertTrue(res.arg) - self.assertSequenceEqual(res.positional, ['x']) - - io = StringIO() - cmd_result = p.execute('n1 -b x'.split(), out=io) - self.assertIsNone(cmd_result.result) - self.assertTrue(io.getvalue().startswith("Missing required argument 'arg'")) - io.close() - - def test_specify_output_format(self): - p = ArgumentParser('test') - p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', True, None), ('-b ', '', False, None)]) - - cmd_result = p.execute('n1 -a x'.split()) - self.assertEqual(cmd_result.output_format, None) - - cmd_result = p.execute('n1 -a x --output json'.split()) - self.assertEqual(cmd_result.output_format, 'json') - - cmd_result = p.execute('n1 -a x --output table'.split()) - self.assertEqual(cmd_result.output_format, 'table') - - cmd_result = p.execute('n1 -a x --output text'.split()) - self.assertEqual(cmd_result.output_format, 'text') - - # Invalid format - io = StringIO() - cmd_res = p.execute('n1 -a x --output unknown'.split(), out=io) - self.assertIsNone(cmd_res.output_format) - self.assertTrue(io.getvalue().startswith("Invalid output format 'unknown'")) - io.close() - - # Invalid format - cmd_result = p.execute('n1 -a x --output'.split()) - self.assertEqual(cmd_result.output_format, None) - - def test_args_completion(self): - p = ArgumentParser('test') - p.add_command(lambda a, b: (a, b), 'n1', args=[('--arg -a', '', True, None), ('-b ', '', False, None)]) - - # Can't use "with StringIO() as ...", as Python2/StringIO doesn't have __exit__. - io = StringIO() - p.execute('n1 - --complete'.split(), - show_usage=False, - show_completions=True, - out=io) - candidates = util.normalize_newlines(io.getvalue()) - io.close() - self.assertEqual(candidates, '--arg\n-a\n-b\n') - - #matching '--arg for '--a' - io=StringIO() - p.execute('n1 --a --complete'.split(), - show_usage=False, - out=io) - candidates = util.normalize_newlines(io.getvalue()) - io.close() - self.assertEqual(candidates, '--arg\n') - - #matching 'n1' for 'n' - io = StringIO() - p.execute('n --complete'.split(), - show_usage=False, - show_completions=True, - out=io) - candidates = util.normalize_newlines(io.getvalue()) - io.close() - self.assertEqual(candidates, 'n1\n') - - #if --arg is used, then both '-a' and "--arg" should not be in the - #candidate list - io = StringIO() - p.execute('n1 --arg hello - --complete'.split(), - show_usage=False, - show_completions=True, - out=io) - candidates = util.normalize_newlines(io.getvalue()) - io.close() - self.assertEqual(candidates, '-b\n') - - #if all argument are used, candidate list is empty - io = StringIO() - p.execute('n1 -a -b --complete'.split(), - show_usage=False, - show_completions=True, - out=io) - candidates = util.normalize_newlines(io.getvalue()) - io.close() - self.assertEqual(candidates, '\n') - - #if at parameter value level, get nothing for N.Y.I. - io = StringIO() - p.execute('n1 -a --complete'.split(), - show_usage=False, - show_completions=True, - out=io) - candidates = util.normalize_newlines(io.getvalue()) - io.close() - self.assertEqual(candidates, '\n') - -if __name__ == '__main__': - unittest.main() diff --git a/src/azure/cli/tests/test_connection_verify.py b/src/azure/cli/tests/test_connection_verify.py index 7a6b1c6613b..ca6444f7ddf 100644 --- a/src/azure/cli/tests/test_connection_verify.py +++ b/src/azure/cli/tests/test_connection_verify.py @@ -5,7 +5,7 @@ except ImportError: from mock import MagicMock -from azure.cli._argparse import ArgumentParser, IncorrectUsageError +from azure.cli.parser import IncorrectUsageError from azure.cli._logging import logger import logging import azure.cli._debug as _debug