| Index: tools/telemetry/third_party/gsutilz/third_party/apitools/run_pylint.py
|
| diff --git a/tools/telemetry/third_party/gsutilz/third_party/apitools/run_pylint.py b/tools/telemetry/third_party/gsutilz/third_party/apitools/run_pylint.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d6b89d259285abbd0cc78ed6422c0c5a6e8e903c
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/gsutilz/third_party/apitools/run_pylint.py
|
| @@ -0,0 +1,227 @@
|
| +"""Custom script to run PyLint on apitools codebase.
|
| +
|
| +"Inspired" by the similar script in gcloud-python.
|
| +
|
| +This runs pylint as a script via subprocess in two different
|
| +subprocesses. The first lints the production/library code
|
| +using the default rc file (PRODUCTION_RC). The second lints the
|
| +demo/test code using an rc file (TEST_RC) which allows more style
|
| +violations (hence it has a reduced number of style checks).
|
| +"""
|
| +
|
| +import ConfigParser
|
| +import copy
|
| +import os
|
| +import subprocess
|
| +import sys
|
| +
|
| +
|
| +IGNORED_DIRECTORIES = [
|
| + 'samples/storage_sample/storage',
|
| +]
|
| +IGNORED_FILES = [
|
| + 'ez_setup.py',
|
| + 'run_pylint.py',
|
| + 'setup.py',
|
| +]
|
| +PRODUCTION_RC = 'default.pylintrc'
|
| +TEST_RC = 'reduced.pylintrc'
|
| +TEST_DISABLED_MESSAGES = [
|
| + 'attribute-defined-outside-init',
|
| + 'exec-used',
|
| + 'import-error',
|
| + 'invalid-name',
|
| + 'missing-docstring',
|
| + 'no-init',
|
| + 'no-self-use',
|
| + 'protected-access',
|
| + 'superfluous-parens',
|
| + 'too-few-public-methods',
|
| + 'too-many-locals',
|
| + 'too-many-public-methods',
|
| + 'unbalanced-tuple-unpacking',
|
| +]
|
| +TEST_RC_ADDITIONS = {
|
| + 'MESSAGES CONTROL': {
|
| + 'disable': ', '.join(TEST_DISABLED_MESSAGES),
|
| + },
|
| +}
|
| +
|
| +
|
| +def read_config(filename):
|
| + """Reads pylintrc config onto native ConfigParser object."""
|
| + config = ConfigParser.ConfigParser()
|
| + with open(filename, 'r') as file_obj:
|
| + config.readfp(file_obj)
|
| + return config
|
| +
|
| +
|
| +def make_test_rc(base_rc_filename, additions_dict, target_filename):
|
| + """Combines a base rc and test additions into single file."""
|
| + main_cfg = read_config(base_rc_filename)
|
| +
|
| + # Create fresh config for test, which must extend production.
|
| + test_cfg = ConfigParser.ConfigParser()
|
| + test_cfg._sections = copy.deepcopy(main_cfg._sections)
|
| +
|
| + for section, opts in additions_dict.items():
|
| + curr_section = test_cfg._sections.setdefault(
|
| + section, test_cfg._dict())
|
| + for opt, opt_val in opts.items():
|
| + curr_val = curr_section.get(opt)
|
| + if curr_val is None:
|
| + raise KeyError('Expected to be adding to existing option.')
|
| + curr_section[opt] = '%s, %s' % (curr_val, opt_val)
|
| +
|
| + with open(target_filename, 'w') as file_obj:
|
| + test_cfg.write(file_obj)
|
| +
|
| +
|
| +def valid_filename(filename):
|
| + """Checks if a file is a Python file and is not ignored."""
|
| + for directory in IGNORED_DIRECTORIES:
|
| + if filename.startswith(directory):
|
| + return False
|
| + return (filename.endswith('.py') and
|
| + filename not in IGNORED_FILES)
|
| +
|
| +
|
| +def is_production_filename(filename):
|
| + """Checks if the file contains production code.
|
| +
|
| + :rtype: boolean
|
| + :returns: Boolean indicating production status.
|
| + """
|
| + return not ('demo' in filename or 'test' in filename or
|
| + filename.startswith('regression'))
|
| +
|
| +
|
| +def get_files_for_linting(allow_limited=True):
|
| + """Gets a list of files in the repository.
|
| +
|
| + By default, returns all files via ``git ls-files``. However, in some cases
|
| + uses a specific commit or branch (a so-called diff base) to compare
|
| + against for changed files. (This requires ``allow_limited=True``.)
|
| +
|
| + To speed up linting on Travis pull requests against master, we manually
|
| + set the diff base to origin/master. We don't do this on non-pull requests
|
| + since origin/master will be equivalent to the currently checked out code.
|
| + One could potentially use ${TRAVIS_COMMIT_RANGE} to find a diff base but
|
| + this value is not dependable.
|
| +
|
| + To allow faster local ``tox`` runs, the environment variables
|
| + ``GCLOUD_REMOTE_FOR_LINT`` and ``GCLOUD_BRANCH_FOR_LINT`` can be set to
|
| + specify a remote branch to diff against.
|
| +
|
| + :type allow_limited: boolean
|
| + :param allow_limited: Boolean indicating if a reduced set of files can
|
| + be used.
|
| +
|
| + :rtype: pair
|
| + :returns: Tuple of the diff base using the the list of filenames to be
|
| + linted.
|
| + """
|
| + diff_base = None
|
| + if (os.getenv('TRAVIS_BRANCH') == 'master' and
|
| + os.getenv('TRAVIS_PULL_REQUEST') != 'false'):
|
| + # In the case of a pull request into master, we want to
|
| + # diff against HEAD in master.
|
| + diff_base = 'origin/master'
|
| + elif os.getenv('TRAVIS') is None:
|
| + # Only allow specified remote and branch in local dev.
|
| + remote = os.getenv('GCLOUD_REMOTE_FOR_LINT')
|
| + branch = os.getenv('GCLOUD_BRANCH_FOR_LINT')
|
| + if remote is not None and branch is not None:
|
| + diff_base = '%s/%s' % (remote, branch)
|
| +
|
| + if diff_base is not None and allow_limited:
|
| + result = subprocess.check_output(['git', 'diff', '--name-only',
|
| + diff_base])
|
| + print 'Using files changed relative to %s:' % (diff_base,)
|
| + print '-' * 60
|
| + print result.rstrip('\n') # Don't print trailing newlines.
|
| + print '-' * 60
|
| + else:
|
| + print 'Diff base not specified, listing all files in repository.'
|
| + result = subprocess.check_output(['git', 'ls-files'])
|
| +
|
| + return result.rstrip('\n').split('\n'), diff_base
|
| +
|
| +
|
| +def get_python_files(all_files=None):
|
| + """Gets a list of all Python files in the repository that need linting.
|
| +
|
| + Relies on :func:`get_files_for_linting()` to determine which files should
|
| + be considered.
|
| +
|
| + NOTE: This requires ``git`` to be installed and requires that this
|
| + is run within the ``git`` repository.
|
| +
|
| + :type all_files: list or ``NoneType``
|
| + :param all_files: Optional list of files to be linted.
|
| +
|
| + :rtype: tuple
|
| + :returns: A tuple containing two lists and a boolean. The first list
|
| + contains all production files, the next all test/demo files and
|
| + the boolean indicates if a restricted fileset was used.
|
| + """
|
| + using_restricted = False
|
| + if all_files is None:
|
| + all_files, diff_base = get_files_for_linting()
|
| + using_restricted = diff_base is not None
|
| +
|
| + library_files = []
|
| + non_library_files = []
|
| + for filename in all_files:
|
| + if valid_filename(filename):
|
| + if is_production_filename(filename):
|
| + library_files.append(filename)
|
| + else:
|
| + non_library_files.append(filename)
|
| +
|
| + return library_files, non_library_files, using_restricted
|
| +
|
| +
|
| +def lint_fileset(filenames, rcfile, description):
|
| + """Lints a group of files using a given rcfile."""
|
| + # Only lint filenames that exist. For example, 'git diff --name-only'
|
| + # could spit out deleted / renamed files. Another alternative could
|
| + # be to use 'git diff --name-status' and filter out files with a
|
| + # status of 'D'.
|
| + filenames = [filename for filename in filenames
|
| + if os.path.exists(filename)]
|
| + if filenames:
|
| + rc_flag = '--rcfile=%s' % (rcfile,)
|
| + pylint_shell_command = ['pylint', rc_flag] + filenames
|
| + status_code = subprocess.call(pylint_shell_command)
|
| + if status_code != 0:
|
| + error_message = ('Pylint failed on %s with '
|
| + 'status %d.' % (description, status_code))
|
| + print >> sys.stderr, error_message
|
| + sys.exit(status_code)
|
| + else:
|
| + print 'Skipping %s, no files to lint.' % (description,)
|
| +
|
| +
|
| +def main():
|
| + """Script entry point. Lints both sets of files."""
|
| + make_test_rc(PRODUCTION_RC, TEST_RC_ADDITIONS, TEST_RC)
|
| + library_files, non_library_files, using_restricted = get_python_files()
|
| + try:
|
| + lint_fileset(library_files, PRODUCTION_RC, 'library code')
|
| + lint_fileset(non_library_files, TEST_RC, 'test and demo code')
|
| + except SystemExit:
|
| + if not using_restricted:
|
| + raise
|
| +
|
| + message = 'Restricted lint failed, expanding to full fileset.'
|
| + print >> sys.stderr, message
|
| + all_files, _ = get_files_for_linting(allow_limited=False)
|
| + library_files, non_library_files, _ = get_python_files(
|
| + all_files=all_files)
|
| + lint_fileset(library_files, PRODUCTION_RC, 'library code')
|
| + lint_fileset(non_library_files, TEST_RC, 'test and demo code')
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + main()
|
|
|