diff --git a/.travis.yml b/.travis.yml
index 2c5e2e0fee8..fc69fd75712 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,9 @@ python:
install:
- pip install azure==2.0.0a1
- pip install mock==1.3.0
+ - pip install pylint==1.5.4
script:
- export PYTHONPATH=$PATHONPATH:./src
- python -m azure.cli
- - python -m unittest discover -s src/azure/cli/tests
+ - pylint src/azure
+ - python -m unittest discover -s src/azure/cli/tests
\ No newline at end of file
diff --git a/azure-cli.pyproj b/azure-cli.pyproj
index 4b08683558d..10e839a95da 100644
--- a/azure-cli.pyproj
+++ b/azure-cli.pyproj
@@ -44,6 +44,7 @@
Code
+
@@ -52,6 +53,7 @@
Code
+
diff --git a/pylintrc b/pylintrc
new file mode 100644
index 00000000000..f9e4a383c98
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,13 @@
+[MESSAGES CONTROL]
+# For all codes, run 'pylint --list-msgs' or go to 'http://pylint-messages.wikidot.com/all-codes'
+# C0111 Missing docstring
+# C0103 Invalid %s name "%s"
+# C0303 Trailing whitespace
+# I0011 Warning locally suppressed using disable-msg
+# W0511 fixme
+disable=C0111,C0103,C0303,I0011,W0511
+[DESIGN]
+# Maximum number of locals for function / method body
+max-locals=25
+# Maximum number of branch for function / method body
+max-branches=20
\ No newline at end of file
diff --git a/src/azure/cli/_argparse.py b/src/azure/cli/_argparse.py
index 2e46c9186f4..623bfbadc8c 100644
--- a/src/azure/cli/_argparse.py
+++ b/src/azure/cli/_argparse.py
@@ -1,9 +1,7 @@
from __future__ import print_function
-import json
-import os
import sys
-from ._locale import get_file as locale_get_file
+from ._locale import L, get_file as locale_get_file
from ._logging import logger
# Named arguments are prefixed with one of these strings
@@ -20,7 +18,7 @@ class IncorrectUsageError(Exception):
pass
class Arguments(dict):
- def __init__(self, source=None):
+ def __init__(self, source=None): #pylint: disable=super-init-not-called
self.positional = []
if source:
self.update(source)
@@ -40,7 +38,7 @@ def __getattr__(self, key):
except LookupError:
pass
logger.debug('Argument %s is required', key)
- raise IncorrectUsageError(_("Argument {0} is required").format(key))
+ raise IncorrectUsageError(L('Argument {0} is required').format(key))
def _read_arg(string):
for prefix in ARG_PREFIXES:
@@ -66,11 +64,15 @@ def __init__(self, prog):
self.noun_map = {
'$doc': 'azure-cli.txt',
}
- self.help_args = { '--help', '-h' }
- self.complete_args = { '--complete' }
- self.global_args = { '--verbose', '--debug' }
-
- def add_command(self, handler, name=None, description=None, args=None):
+ self.help_args = {'--help', '-h'}
+ self.complete_args = {'--complete'}
+ self.global_args = {'--verbose', '--debug'}
+
+ def add_command(self,
+ handler,
+ name=None,
+ description=None,
+ args=None):
'''Registers a command that may be parsed by this parser.
`handler` is the function to call with two `Arguments` objects.
@@ -105,7 +107,7 @@ def add_command(self, handler, name=None, description=None, args=None):
m['$args'] = []
m['$kwargs'] = kw = {}
m['$argdoc'] = ad = []
- for spec, desc in (args or []):
+ for spec, desc in args or []:
if not any(spec.startswith(p) for p in ARG_PREFIXES):
m['$args'].append(spec.strip('<> '))
ad.append((spec, desc))
@@ -121,7 +123,11 @@ def add_command(self, handler, name=None, description=None, args=None):
ad.append(('/'.join(aliases), desc))
- def execute(self, args, show_usage=False, show_completions=False, out=sys.stdout):
+ def execute(self,
+ args,
+ show_usage=False,
+ show_completions=False,
+ out=sys.stdout):
'''Parses `args` and invokes the associated handler.
The handler is passed two `Arguments` objects with all arguments other
@@ -142,10 +148,11 @@ def execute(self, args, show_usage=False, show_completions=False, out=sys.stdout
if not show_completions:
show_completions = any(a in self.complete_args for a in args)
- all_global_args = set(a.lstrip('-/') for a in self.help_args | self.complete_args | self.global_args)
+ all_global_args = set(
+ a.lstrip('-/') for a in self.help_args | self.complete_args | self.global_args)
def not_global(a):
return a.lstrip('-/') not in all_global_args
- it = filter(not_global, args).__iter__()
+ it = filter(not_global, args).__iter__() #pylint: disable=bad-builtin
m = self.noun_map
nouns = []
@@ -161,7 +168,6 @@ def not_global(a):
n = next(it, '')
try:
- expected_args = m['$args']
expected_kwargs = m['$kwargs']
handler = m['$handler']
except LookupError:
@@ -169,9 +175,9 @@ def not_global(a):
show_usage = True
if show_completions:
- return self._display_completions(nouns, m, args, out)
+ return ArgumentParser._display_completions(m, out)
if show_usage:
- return self._display_usage(nouns, m, args, out)
+ return self._display_usage(nouns, m, out)
parsed = Arguments()
others = Arguments()
@@ -189,8 +195,9 @@ def not_global(a):
elif target_value[1] is True:
# Arg with no value
if value is not None:
- print(_("argument '{0}' does not take a value").format(key_n), file=out)
- return self._display_usage(nouns, m, args, out)
+ print(L("argument '{0}' does not take a value").format(key_n),
+ file=out)
+ return self._display_usage(nouns, m, out)
parsed.add_from_dotted(target_value[0], True)
else:
# Arg with a value
@@ -208,11 +215,11 @@ def not_global(a):
return handler(parsed, others)
except IncorrectUsageError as ex:
print(str(ex), file=out)
- return self._display_usage(nouns, m, args, out)
+ return self._display_usage(nouns, m, out)
finally:
sys.stdout = old_stdout
- def _display_usage(self, nouns, noun_map, arguments, out=sys.stdout):
+ def _display_usage(self, nouns, noun_map, out=sys.stdout):
spec = ' '.join(noun_map.get('$spec') or nouns)
print(' {} {}'.format(self.prog, spec), file=out)
print(file=out)
@@ -246,7 +253,8 @@ def _display_usage(self, nouns, noun_map, arguments, out=sys.stdout):
out.flush()
logger.debug('Expected documentation at %s', doc_file)
- def _display_completions(self, nouns, noun_map, arguments, out=sys.stdout):
+ @staticmethod
+ def _display_completions(noun_map, out=sys.stdout):
completions = [k for k in noun_map if not k.startswith('$')]
kwargs = noun_map.get('$kwargs')
diff --git a/src/azure/cli/_debug.py b/src/azure/cli/_debug.py
index 845ec253cd9..c5d8a1db767 100644
--- a/src/azure/cli/_debug.py
+++ b/src/azure/cli/_debug.py
@@ -5,7 +5,8 @@
def allow_debug_connection(client):
if should_disable_connection_verify():
- logger.warn("Connection verification disabled by environment variable %s", DISABLE_VERIFY_VARIABLE_NAME)
+ logger.warning("Connection verification disabled by environment variable %s",
+ DISABLE_VERIFY_VARIABLE_NAME)
client.config.connection.verify = False
def should_disable_connection_verify():
diff --git a/src/azure/cli/_locale.py b/src/azure/cli/_locale.py
index 97faae764d4..b56abe94a66 100644
--- a/src/azure/cli/_locale.py
+++ b/src/azure/cli/_locale.py
@@ -1,11 +1,16 @@
import os.path
+from codecs import open as codecs_open
-from codecs import open
+_translations = dict()
+_locale_dir = ''
+
+def L(key):
+ return _translations.get(key) or ''.format(key)
def install(locale_dir):
mapping = []
- with open(os.path.join(locale_dir, "messages.txt"), 'r', encoding='utf-8-sig') as f:
+ with codecs_open(os.path.join(locale_dir, "messages.txt"), 'r', encoding='utf-8-sig') as f:
for i in f:
if not i or i.startswith('#') or not i.strip():
continue
@@ -14,16 +19,12 @@ def install(locale_dir):
else:
mapping[-1] = (mapping[-1][0], i.strip())
- translations = dict(mapping)
- def _(key):
- return translations.get(key) or ''.format(key)
- _.locale_dir = locale_dir
-
- __builtins__['_'] = _
+ globals()['_translations'] = dict(mapping)
+ globals()['_locale_dir'] = locale_dir
def get_file(name):
try:
- src = _.locale_dir
+ src = _locale_dir
except (NameError, AttributeError):
raise RuntimeError("localizations not installed")
diff --git a/src/azure/cli/_logging.py b/src/azure/cli/_logging.py
index ada07b71ef4..0102e0aabe3 100644
--- a/src/azure/cli/_logging.py
+++ b/src/azure/cli/_logging.py
@@ -1,7 +1,7 @@
import logging as _logging
import sys
-__all__ = ['logging', 'configure_logging']
+__all__ = ['logger', 'configure_logging']
logger = _logging.Logger('az', _logging.WARNING)
diff --git a/src/azure/cli/_output.py b/src/azure/cli/_output.py
index 53d84bc87c6..529e25ea99f 100644
--- a/src/azure/cli/_output.py
+++ b/src/azure/cli/_output.py
@@ -8,7 +8,7 @@
from io import StringIO
except ImportError:
# Python 2
- from StringIO import StringIO
+ from StringIO import StringIO #pylint: disable=import-error
class OutputFormatException(Exception):
pass
@@ -40,9 +40,9 @@ def format_text(obj):
except TypeError:
return ''
-class OutputProducer(object):
+class OutputProducer(object): #pylint: disable=too-few-public-methods
- def __init__(self, formatter=format_json, file=sys.stdout):
+ def __init__(self, formatter=format_json, file=sys.stdout): #pylint: disable=redefined-builtin
self.formatter = formatter
self.file = file
@@ -100,14 +100,14 @@ def add(self, identifier, value):
def dump(self):
with StringIO() as io:
- for id in sorted(self.identifiers):
- io.write(id.upper())
+ for identifier in sorted(self.identifiers):
+ io.write(identifier.upper())
io.write('\t')
- for col in self.identifiers[id]:
+ for col in self.identifiers[identifier]:
if isinstance(col, str):
io.write(col)
else:
- # TODO: Handle complex objects
+ # TODO: Need to handle complex objects
io.write("null")
io.write('\t')
io.write('\n')
diff --git a/src/azure/cli/_profile.py b/src/azure/cli/_profile.py
index f72e70ecaa5..87834bbefb5 100644
--- a/src/azure/cli/_profile.py
+++ b/src/azure/cli/_profile.py
@@ -1,6 +1,6 @@
-from msrest.authentication import BasicTokenAuthentication
+import collections
+from msrest.authentication import BasicTokenAuthentication
from .main import CONFIG
-import collections
class Profile(object):
@@ -37,10 +37,9 @@ def set_subscriptions(self, new_subscriptions, access_token):
for s in subscriptions:
s['active'] = False
- if new_active_one:
- new_active_one['active'] = True
- else:
- new_subscriptions[0]['active'] = True
+ if not new_active_one:
+ new_active_one = new_subscriptions[0]
+ new_active_one['active'] = True
else:
new_subscriptions[0]['active'] = True
diff --git a/src/azure/cli/_session.py b/src/azure/cli/_session.py
index 45cdcab163d..39881574ef6 100644
--- a/src/azure/cli/_session.py
+++ b/src/azure/cli/_session.py
@@ -7,7 +7,7 @@
import collections
-from codecs import open
+from codecs import open as codecs_open
class Session(collections.MutableMapping):
'''A simple dict-like class that is backed by a JSON file.
@@ -28,14 +28,14 @@ def load(self, filename, max_age=0):
st = os.stat(self.filename)
if st.st_mtime + max_age < time.clock():
self.save()
- with open(self.filename, 'r', encoding='utf-8-sig') as f:
+ with codecs_open(self.filename, 'r', encoding='utf-8-sig') as f:
self.data = json.load(f)
except (OSError, IOError):
self.save()
def save(self):
if self.filename:
- with open(self.filename, 'w', encoding='utf-8-sig') as f:
+ with codecs_open(self.filename, 'w', encoding='utf-8-sig') as f:
json.dump(self.data, f)
def save_with_retry(self, retries=5):
diff --git a/src/azure/cli/_util.py b/src/azure/cli/_util.py
index 6449b631737..f667ddd59bb 100644
--- a/src/azure/cli/_util.py
+++ b/src/azure/cli/_util.py
@@ -1,3 +1,2 @@
-
def normalize_newlines(str_to_normalize):
return str_to_normalize.replace('\r\n', '\n')
diff --git a/src/azure/cli/commands/__init__.py b/src/azure/cli/commands/__init__.py
index f4172e18b04..b39c45a5492 100644
--- a/src/azure/cli/commands/__init__.py
+++ b/src/azure/cli/commands/__init__.py
@@ -21,21 +21,21 @@ def add_command(handler):
return handler
return add_command
-def description(description):
+def description(description_text):
def add_description(handler):
- _COMMANDS.setdefault(handler, {})['description'] = description
- logger.debug('Added description "%s" to %s', description, handler)
+ _COMMANDS.setdefault(handler, {})['description'] = description_text
+ logger.debug('Added description "%s" to %s', description_text, handler)
return handler
return add_description
-def option(spec, description=None):
+def option(spec, description_text=None):
def add_option(handler):
- _COMMANDS.setdefault(handler, {}).setdefault('args', []).append((spec, description))
+ _COMMANDS.setdefault(handler, {}).setdefault('args', []).append((spec, description_text))
logger.debug('Added option "%s" to %s', spec, handler)
return handler
return add_option
-def add_to_parser(parser, command=None):
+def add_to_parser(parser, command_name=None):
'''Loads commands into the parser
When `command` is specified, only commands from that module will be loaded.
@@ -45,9 +45,9 @@ def add_to_parser(parser, command=None):
# Importing the modules is sufficient to invoke the decorators. Then we can
# get all of the commands from the _COMMANDS variable.
loaded = False
- if command:
+ if command_name:
try:
- __import__('azure.cli.commands.' + command)
+ __import__('azure.cli.commands.' + command_name)
loaded = True
except ImportError:
# Unknown command - we'll load all below
diff --git a/src/azure/cli/commands/account.py b/src/azure/cli/commands/account.py
index 4df6168ecf4..c49a479b533 100644
--- a/src/azure/cli/commands/account.py
+++ b/src/azure/cli/commands/account.py
@@ -1,21 +1,22 @@
from .._profile import Profile
from ..commands import command, description, option
+from .._locale import L
@command('account list')
-@description(_('List the imported subscriptions.'))
-def list_subscriptions(args, unexpected):
+@description(L('List the imported subscriptions.'))
+def list_subscriptions(args, unexpected): #pylint: disable=unused-argument
profile = Profile()
subscriptions = profile.load_subscriptions()
return subscriptions
@command('account set')
-@description(_('Set the current subscription'))
-@option('--subscription-id -n ', _('Subscription Id, unique name also works.'))
-def set_active_subscription(args, unexpected):
- id = args.get('subscription-id')
+@description(L('Set the current subscription'))
+@option('--subscription-id -n ', L('Subscription Id, unique name also works.'))
+def set_active_subscription(args, unexpected): #pylint: disable=unused-argument
+ subscription_id = args.get('subscription-id')
if not id:
- raise ValueError(_('Please provide subscription id or unique name.'))
+ raise ValueError(L('Please provide subscription id or unique name.'))
profile = Profile()
- profile.set_active_subscription(id)
+ profile.set_active_subscription(subscription_id)
diff --git a/src/azure/cli/commands/login.py b/src/azure/cli/commands/login.py
index 09e30b57a51..31125f3492f 100644
--- a/src/azure/cli/commands/login.py
+++ b/src/azure/cli/commands/login.py
@@ -1,33 +1,36 @@
-from msrestazure.azure_active_directory import UserPassCredentials
+from msrest import Serializer
+from msrestazure.azure_active_directory import UserPassCredentials
from azure.mgmt.resource.subscriptions import SubscriptionClient, \
SubscriptionClientConfiguration
-from msrest import Serializer
-
from .._profile import Profile
from ..commands import command, description, option
from .._debug import should_disable_connection_verify
+from .._locale import L
CLIENT_ID = '04b07795-8ddb-461a-bbee-02f9e1bf7b46'
@command('login')
-@description(_('log in to an Azure subscription using Active Directory Organization Id'))
-@option('--username -u ', _('organization Id. Microsoft Account is not yet supported.'))
-@option('--password -p ', _('user password, will prompt if not given.'))
-def login(args, unexpected):
+@description(L('log in to an Azure subscription using Active Directory Organization Id'))
+@option('--username -u ', L('organization Id. Microsoft Account is not yet supported.'))
+@option('--password -p ', L('user password, will prompt if not given.'))
+def login(args, unexpected): #pylint: disable=unused-argument
username = args.get('username')
password = args.get('password')
if not password:
import getpass
- password = getpass.getpass(_('Password: '))
+ password = getpass.getpass(L('Password: '))
- credentials = UserPassCredentials(username, password, client_id=CLIENT_ID, verify=not should_disable_connection_verify())
+ credentials = UserPassCredentials(username,
+ password,
+ client_id=CLIENT_ID,
+ verify=not should_disable_connection_verify())
client = SubscriptionClient(SubscriptionClientConfiguration(credentials))
subscriptions = client.subscriptions.list()
if not subscriptions:
- raise RuntimeError(_('No subscriptions found for this account.'))
+ raise RuntimeError(L('No subscriptions found for this account.'))
serializable = Serializer().serialize_data(subscriptions, "[Subscription]")
diff --git a/src/azure/cli/commands/logout.py b/src/azure/cli/commands/logout.py
index 341904576e7..d4ce01a2024 100644
--- a/src/azure/cli/commands/logout.py
+++ b/src/azure/cli/commands/logout.py
@@ -1,13 +1,14 @@
from .._profile import Profile
from ..commands import command, description, option
+from .._locale import L
@command('logout')
-@description(_('Log out from Azure subscription using Active Directory.'))
-@option('--username -u ', _('User name used to log out from Azure Active Directory.'))
-def logout(args, unexpected):
+@description(L('Log out from Azure subscription using Active Directory.'))
+@option('--username -u ', L('User name used to log out from Azure Active Directory.'))
+def logout(args, unexpected): #pylint: disable=unused-argument
username = args.get('username')
if not username:
- raise ValueError(_('Please provide a valid username to logout.'))
+ raise ValueError(L('Please provide a valid username to logout.'))
profile = Profile()
profile.logout(username)
diff --git a/src/azure/cli/commands/resourcegroup.py b/src/azure/cli/commands/resourcegroup.py
index 6a7eec557b5..bea80ae2cc7 100644
--- a/src/azure/cli/commands/resourcegroup.py
+++ b/src/azure/cli/commands/resourcegroup.py
@@ -1,24 +1,24 @@
from msrest import Serializer
-
-from ..commands import command, description, option
+from ..commands import command, description
from .._profile import Profile
@command('resource group list')
@description('List resource groups')
# TODO: waiting on Python Azure SDK bug fixes
-#@option('--tag-name -g ', _("the resource group's tag name"))
-#@option('--tag-value -g ', _("the resource group's tag value"))
-#@option('--top -g ', _("Top N resource groups to retrieve"))
-def list_groups(args, unexpected):
- from azure.mgmt.resource.resources import ResourceManagementClient, ResourceManagementClientConfiguration
+# @option('--tag-name -g ', L('the resource group's tag name'))
+# @option('--tag-value -g ', L('the resource group's tag value'))
+# @option('--top -g ', L('Top N resource groups to retrieve'))
+def list_groups(args, unexpected): #pylint: disable=unused-argument
+ from azure.mgmt.resource.resources import ResourceManagementClient, \
+ ResourceManagementClientConfiguration
from azure.mgmt.resource.resources.models import ResourceGroup, ResourceGroupFilter
- rmc = ResourceManagementClient(ResourceManagementClientConfiguration(*Profile().get_login_credentials()))
+ rmc = ResourceManagementClient(
+ ResourceManagementClientConfiguration(*Profile().get_login_credentials()))
# TODO: waiting on Python Azure SDK bug fixes
#group_filter = ResourceGroupFilter(args.get('tag-name'), args.get('tag-value'))
#groups = rmc.resource_groups.list(filter=None, top=args.get('top'))
groups = rmc.resource_groups.list()
-
serializable = Serializer().serialize_data(groups, "[ResourceGroup]")
return serializable
diff --git a/src/azure/cli/commands/storage.py b/src/azure/cli/commands/storage.py
index 15fde8488db..b765431c9e4 100644
--- a/src/azure/cli/commands/storage.py
+++ b/src/azure/cli/commands/storage.py
@@ -1,14 +1,14 @@
from msrest import Serializer
-
-from ..main import SESSION
from ..commands import command, description, option
from ._command_creation import get_service_client
+from .._logging import logger
+from .._locale import L
@command('storage account list')
-@description(_('List storage accounts'))
-@option('--resource-group -g ', _('the resource group name'))
-@option('--subscription -s ', _('the subscription id'))
-def list_accounts(args, unexpected):
+@description(L('List storage accounts'))
+@option('--resource-group -g ', L('the resource group name'))
+@option('--subscription -s ', L('the subscription id'))
+def list_accounts(args, unexpected): #pylint: disable=unused-argument
from azure.mgmt.storage import StorageManagementClient, StorageManagementClientConfiguration
from azure.mgmt.storage.models import StorageAccount
from msrestazure.azure_active_directory import UserPassCredentials
@@ -26,10 +26,7 @@ def list_accounts(args, unexpected):
@command('storage account check')
@option('--account-name ')
-def checkname(args, unexpected):
+def checkname(args, unexpected): #pylint: disable=unused-argument
from azure.mgmt.storage import StorageManagementClient, StorageManagementClientConfiguration
-
smc = get_service_client(StorageManagementClient, StorageManagementClientConfiguration)
- logger.warn(smc.storage_accounts.check_name_availability(args.account_name))
-
-
+ logger.warning(smc.storage_accounts.check_name_availability(args.account_name))
diff --git a/src/azure/cli/main.py b/src/azure/cli/main.py
index 08aaeee6ea7..a685484ccfa 100644
--- a/src/azure/cli/main.py
+++ b/src/azure/cli/main.py
@@ -1,7 +1,6 @@
import os
from ._argparse import ArgumentParser
-from ._locale import install as locale_install
from ._logging import configure_logging, logger
from ._session import Session
from ._output import OutputProducer
@@ -12,17 +11,18 @@
# SESSION provides read-write session variables
SESSION = Session()
-# Load the user's preferred locale from their configuration
-LOCALE = CONFIG.get('locale', 'en-US')
-locale_install(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'locale', LOCALE))
-
-
def main(args):
CONFIG.load(os.path.expanduser('~/az.json'))
SESSION.load(os.path.expanduser('~/az.sess'), max_age=3600)
configure_logging(args, CONFIG)
+ from ._locale import install as locale_install
+ locale_install(os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ 'locale',
+ CONFIG.get('locale', 'en-US')))
+
+
parser = ArgumentParser("az")
import azure.cli.commands as commands