Volans has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/361040 )

Change subject: Fix Pylint and other tools reported errors
......................................................................

Fix Pylint and other tools reported errors

- In preparation for the introduction of additional testing tools in tox
  such as Pylint

Bug: T154588
Change-Id: Icf2965ec31beb9325c07df8aa3e2d1d71265bc2c
---
M cumin/backends/__init__.py
M cumin/backends/direct.py
M cumin/backends/puppetdb.py
M cumin/cli.py
M cumin/grammar.py
M cumin/tests/__init__.py
M cumin/tests/integration/test_cli.py
M cumin/tests/unit/backends/test_puppetdb.py
M cumin/tests/unit/test_backends.py
M cumin/tests/unit/test_cli.py
M cumin/tests/unit/test_grammar.py
M cumin/tests/unit/test_query.py
M cumin/tests/unit/transports/test_clustershell.py
M cumin/tests/unit/transports/test_init.py
A cumin/tests/vulture_whitelist.py
M cumin/transports/__init__.py
M cumin/transports/clustershell.py
17 files changed, 119 insertions(+), 80 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/software/cumin 
refs/changes/40/361040/1

diff --git a/cumin/backends/__init__.py b/cumin/backends/__init__.py
index 937f752..120afb9 100644
--- a/cumin/backends/__init__.py
+++ b/cumin/backends/__init__.py
@@ -31,10 +31,10 @@
         """Add a category token to the query 'F:key = value'.
 
         Arguments:
-        category -- the category of the token, one of cumin.grammar.categories
+        category -- the category of the token, one of cumin.grammar.CATEGORIES
         key      -- the key for this category
         value    -- the value to match, if not specified the key itself will 
be matched [optional, default: None]
-        operator -- the comparison operator to use, one of 
cumin.grammar.operators [optional: default: =]
+        operator -- the comparison operator to use, one of 
cumin.grammar.OPERATORS [optional: default: =]
         neg      -- whether the token must be negated [optional, default: 
False]
         """
 
diff --git a/cumin/backends/direct.py b/cumin/backends/direct.py
index c315d24..23456c7 100644
--- a/cumin/backends/direct.py
+++ b/cumin/backends/direct.py
@@ -60,4 +60,5 @@
         return list(self.hosts)
 
 
-query_class = DirectQuery  # Required by the auto-loader in the 
cumin.query.Query factory
+# Required by the auto-loader in the cumin.query.Query factory
+query_class = DirectQuery  # pylint: disable=invalid-name
diff --git a/cumin/backends/puppetdb.py b/cumin/backends/puppetdb.py
index b9774c5..b49e424 100644
--- a/cumin/backends/puppetdb.py
+++ b/cumin/backends/puppetdb.py
@@ -1,3 +1,5 @@
+# pylint: skip-file
+# See https://github.com/PyCQA/astroid/issues/437
 """PuppetDB backend."""
 from string import capwords
 
@@ -75,7 +77,7 @@
 
     def add_hosts(self, hosts, neg=False):
         """Required by BaseQuery."""
-        if len(hosts) == 0:
+        if not hosts:
             return
 
         hosts_tokens = []
@@ -122,13 +124,13 @@
 
         return unique_hosts
 
-    def _get_resource_query(self, key, value=None, operator='='):
+    def _get_resource_query(self, key, value=None, operator='='):  # pylint: 
disable=no-self-use
         """Build a resource query based on the parameters, resolving the 
special cases for %params and @field.
 
         Arguments:
         key      -- the key of the resource
         value    -- the value to match, if not specified the key itself will 
be matched [optional, default: None]
-        operator -- the comparison operator to use, one of 
cumin.grammar.operators [optional: default: =]
+        operator -- the comparison operator to use, one of 
cumin.grammar.OPERATORS [optional: default: =]
         """
         if all(char in key for char in ('%', '@')):
             raise InvalidQueryError(("Resource key cannot contain both '%' 
(query a resource's parameter) and '@' "
@@ -210,4 +212,5 @@
         return resources.json()
 
 
-query_class = PuppetDBQuery  # Required by the auto-loader in the 
cumin.query.Query factory
+# Required by the auto-loader in the cumin.query.Query factory
+query_class = PuppetDBQuery  # pylint: disable=invalid-name
diff --git a/cumin/cli.py b/cumin/cli.py
index 187c364..8b27bf3 100644
--- a/cumin/cli.py
+++ b/cumin/cli.py
@@ -9,7 +9,7 @@
 import signal
 import sys
 
-from logging.handlers import RotatingFileHandler
+from logging.handlers import RotatingFileHandler  # pylint: 
disable=ungrouped-imports
 
 import colorama
 import yaml
@@ -22,7 +22,7 @@
 from cumin.transport import Transport
 from cumin.transports import Command
 
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)  # pylint: disable=invalid-name
 OUTPUT_FORMATS = ('txt', 'json')
 INTERACTIVE_BANNER = """===== Cumin Interactive REPL =====
 # Press Ctrl+d or type exit() to exit the program.
@@ -192,14 +192,14 @@
     return config
 
 
-def sigint_handler(*args):
+def sigint_handler(*args):  # pylint: disable=unused-argument
     """Signal handler for Ctrl+c / SIGINT, raises KeyboardInterruptError.
 
     Arguments (as defined in https://docs.python.org/2/library/signal.html):
     signum -- the signal number
     frame  -- the current stack frame
     """
-    if not sys.stdout.isatty():
+    if not sys.stdout.isatty():  # pylint: disable=no-member
         logger.warning('Execution interrupted by Ctrl+c/SIGINT')
         raise KeyboardInterruptError
 
@@ -254,7 +254,7 @@
     query = QueryBuilder(args.hosts, config, logger).build()
     hosts = query.execute()
 
-    if len(hosts) == 0:
+    if not hosts:
         stderr('No hosts found that matches the query')
         return hosts
 
@@ -267,7 +267,7 @@
     elif args.force:
         stderr('FORCE mode enabled, continuing without confirmation')
         return hosts
-    elif not sys.stdout.isatty():
+    elif not sys.stdout.isatty():  # pylint: disable=no-member
         message = 'Not in a TTY but neither DRY-RUN nor FORCE mode were 
specified.'
         stderr(message)
         raise CuminError(message)
@@ -324,7 +324,7 @@
     config -- a dictionary with the parsed configuration file
     """
     hosts = get_hosts(args, config)
-    if len(hosts) == 0:
+    if not hosts:
         return 0
 
     worker = Transport.new(config, logger)
@@ -345,7 +345,9 @@
     if args.interactive:
         # Define a help function h() that will be available in the interactive 
shell to print the help message.
         # The name is to not shadow the Python built-in help() that might be 
usefult too to inspect objects.
-        def h(): tqdm.write(INTERACTIVE_BANNER)
+        def h():  # pylint: disable=unused-variable,invalid-name
+            """Helper function for the interactive shell."""
+            tqdm.write(INTERACTIVE_BANNER)
         code.interact(banner=INTERACTIVE_BANNER, local=locals())
     elif args.output is not None:
         tqdm.write('_____FORMATTED_OUTPUT_____')
@@ -372,7 +374,7 @@
     except CuminError as e:
         stderr(e)
         return 2
-    except Exception as e:
+    except Exception as e:  # pylint: disable=broad-except
         stderr('Caught {name} exception: 
{msg}'.format(name=e.__class__.__name__, msg=e))
         return 3
 
@@ -390,7 +392,7 @@
     except KeyboardInterruptError:
         stderr('Execution interrupted by Ctrl+c/SIGINT/Aborted')
         exit_code = 98
-    except Exception as e:
+    except Exception as e:  # pylint: disable=broad-except
         stderr('Caught {name} exception: 
{msg}'.format(name=e.__class__.__name__, msg=e))
         logger.exception('Failed to execute')
         exit_code = 99
diff --git a/cumin/grammar.py b/cumin/grammar.py
index c5dbb0c..95de3ef 100644
--- a/cumin/grammar.py
+++ b/cumin/grammar.py
@@ -3,12 +3,12 @@
 import pyparsing as pp
 
 # Available categories
-categories = (
+CATEGORIES = (
     'F',  # Fact
     'R',  # Resource
 )
 # Available operators
-operators = ('=', '!=', '>=', '<=', '<', '>', '~')
+OPERATORS = ('=', '!=', '>=', '<=', '<', '>', '~')
 
 
 def _grammar():
@@ -35,7 +35,7 @@
     and_or = (pp.Keyword('and', caseless=True) | pp.Keyword('or', 
caseless=True))('bool')
     neg = pp.Keyword('not', caseless=True)('neg')  # 'neg' is used to allow 
the use of dot notation, 'not' is reserved
 
-    operator = pp.oneOf(operators, caseless=True)('operator')  # Comparison 
operators
+    operator = pp.oneOf(OPERATORS, caseless=True)('operator')  # Comparison 
operators
     quoted_string = pp.quotedString.addParseAction(pp.removeQuotes)  # Both 
single and double quotes are allowed
 
     # Parentheses
@@ -48,7 +48,7 @@
 
     # Key-value token for allowed categories using the available comparison 
operators
     # i.e. F:key = value
-    category = pp.oneOf(categories, caseless=True)('category')
+    category = pp.oneOf(CATEGORIES, caseless=True)('category')
     key = pp.Word(pp.alphanums + '-_.%@:')('key')
     selector = pp.Combine(category + ':' + key)  # i.e. F:key
     # All printables characters except the parentheses that are part of the 
grammar
@@ -58,12 +58,12 @@
 
     # Final grammar, see the docstring for it's BNF based on the tokens 
defined above
     # Groups are used to split the parsed results for an easy access
-    grammar = pp.Forward()
+    full_grammar = pp.Forward()
     item = pp.Group(pp.Optional(neg) + (token | hosts('hosts'))) | pp.Group(
-        pp.Optional(neg) + lpar + grammar + rpar)
-    grammar << item + pp.ZeroOrMore(pp.Group(and_or) + grammar)
+        pp.Optional(neg) + lpar + full_grammar + rpar)
+    full_grammar << item + pp.ZeroOrMore(pp.Group(and_or) + full_grammar)  # 
pylint: disable=expression-not-assigned
 
-    return grammar
+    return full_grammar
 
 
-grammar = _grammar()
+grammar = _grammar()  # pylint: disable=invalid-name
diff --git a/cumin/tests/__init__.py b/cumin/tests/__init__.py
index e3d937b..742e027 100644
--- a/cumin/tests/__init__.py
+++ b/cumin/tests/__init__.py
@@ -4,7 +4,7 @@
 import os
 
 logging.basicConfig(level=logging.DEBUG, name=__name__)
-_tests_base_path = os.path.realpath(os.path.dirname(__file__))
+_TESTS_BASE_PATH = os.path.realpath(os.path.dirname(__file__))
 
 
 def get_fixture(filename, as_string=False):
@@ -14,7 +14,7 @@
     filename  -- the file to be opened in the test's fixture directory
     as_string -- return the content as a multiline string instead of a list of 
lines [optional, default: False]
     """
-    with open(os.path.join(_tests_base_path, 'fixtures', filename)) as f:
+    with open(os.path.join(_TESTS_BASE_PATH, 'fixtures', filename)) as f:
         if as_string:
             content = f.read()
         else:
diff --git a/cumin/tests/integration/test_cli.py 
b/cumin/tests/integration/test_cli.py
index 931e1a2..23b6e96 100644
--- a/cumin/tests/integration/test_cli.py
+++ b/cumin/tests/integration/test_cli.py
@@ -206,6 +206,8 @@
             for j, commands_set in enumerate(_VARIANTS_COMMANDS):
                 commands_set['params'] = _VARIANTS_PARAMETERS[i] + 
commands_set.get('additional_params', [])
                 test_input = 
make_method('params{i:02d}_commands{j:02d}'.format(i=i, j=j), commands_set)
+                if test_input.__doc__ is None:
+                    raise AssertionError("Missing __doc__ for test 
{name}".format(name=test_input.__name__))
                 setattr(cls, test_input.__name__, test_input)
         return cls
 
@@ -220,8 +222,9 @@
 
     def setUp(self):
         """Set default properties."""
-        self.identifier = os.environ.get('CUMIN_IDENTIFIER')
-        self.config = os.path.join(os.environ.get('CUMIN_TMPDIR'), 
'config.yaml')
+        self.identifier = os.getenv('CUMIN_IDENTIFIER')
+        self.assertIsNotNone(self.identifier, msg='Unable to find 
CUMIN_IDENTIFIER environmental variable')
+        self.config = os.path.join(os.getenv('CUMIN_TMPDIR', ''), 
'config.yaml')
         self.default_params = ['--force', '-d', '-c', self.config]
         self.nodes_prefix = '{identifier}-'.format(identifier=self.identifier)
         self.all_nodes = '{prefix}[1-5]'.format(prefix=self.nodes_prefix)
diff --git a/cumin/tests/unit/backends/test_puppetdb.py 
b/cumin/tests/unit/backends/test_puppetdb.py
index 39bab82..b3ee47b 100644
--- a/cumin/tests/unit/backends/test_puppetdb.py
+++ b/cumin/tests/unit/backends/test_puppetdb.py
@@ -1,5 +1,5 @@
 """PuppetDB backend tests."""
-
+# pylint: disable=invalid-name
 import unittest
 
 import requests_mock
@@ -217,7 +217,7 @@
     def _register_uris(self, requests):
         """Setup the requests library mock for each test."""
         # Register a requests valid response for each endpoint
-        for category in self.query.endpoints.keys():
+        for category in self.query.endpoints:
             endpoint = self.query.endpoints[category]
             key = self.query.hosts_keys[category]
             requests.register_uri('GET', self.query.url + endpoint + 
'?query=', status_code=200, json=[
diff --git a/cumin/tests/unit/test_backends.py 
b/cumin/tests/unit/test_backends.py
index ea6b444..ad7988d 100644
--- a/cumin/tests/unit/test_backends.py
+++ b/cumin/tests/unit/test_backends.py
@@ -11,4 +11,4 @@
     def test_instantiation(self):
         """Class BaseQuery is not instantiable being an abstract class."""
         with self.assertRaises(TypeError):
-            BaseQuery({})
+            BaseQuery({})  # pylint: disable=abstract-class-instantiated
diff --git a/cumin/tests/unit/test_cli.py b/cumin/tests/unit/test_cli.py
index 43bc213..29e8eec 100644
--- a/cumin/tests/unit/test_cli.py
+++ b/cumin/tests/unit/test_cli.py
@@ -105,6 +105,7 @@
         mocked_os.path.exists.return_value = True
         cli.setup_logging('filename', debug=True)
         logging.setLevel.assert_called_with(DEBUG)
+        self.assertTrue(file_handler.called)
 
     def test_parse_config_ok(self):
         """The configuration file is properly parsed and accessible."""
@@ -133,7 +134,7 @@
     @mock.patch('cumin.cli.raw_input')
     @mock.patch('cumin.cli.sys.stdout.isatty')
     @mock.patch('cumin.cli.logger')
-    def test_sigint_handler(self, logging, isatty, mocked_raw_input, stderr):
+    def test_sigint_handler(self, logging, isatty, mocked_raw_input, stderr):  
# pylint: disable=unused-argument
         """Calling the SIGINT handler should raise KeyboardInterrupt or not 
based on tty and answer."""
         # Signal handler called without a tty
         isatty.return_value = False
@@ -198,6 +199,8 @@
         with self.assertRaises(cli.KeyboardInterruptError):
             cli.get_hosts(args, config)
 
+        self.assertTrue(stderr.called)
+
     @mock.patch('cumin.cli.stderr')
     @mock.patch('cumin.cli.sys.stdout.isatty')
     def test_get_hosts_no_tty_ko(self, isatty, stderr):
@@ -239,3 +242,4 @@
         config = {'backend': 'direct', 'transport': 'clustershell'}
         cli.run(args, config)
         transport.new.assert_called_once_with(config, cli.logger)
+        self.assertTrue(stderr.called)
diff --git a/cumin/tests/unit/test_grammar.py b/cumin/tests/unit/test_grammar.py
index 8d615f4..dfd77e9 100644
--- a/cumin/tests/unit/test_grammar.py
+++ b/cumin/tests/unit/test_grammar.py
@@ -6,14 +6,15 @@
 from cumin.tests import get_fixture
 
 
+def _get_category_key_token(category='F', key='key1', operator='=', 
value='value1'):
+    """Generate and return a category token string and it's expected 
dictionary of tokens when parsed."""
+    expected = {'category': category, 'key': key, 'operator': operator, 
'value': value}
+    token = '{category}:{key} {operator} {value}'.format(**expected)
+    return token, expected
+
+
 class TestGrammar(unittest.TestCase):
     """Grammar class tests."""
-
-    def _get_category_key_token(self, category='F', key='key1', operator='=', 
value='value1'):
-        """Generate and return a category token string and it's expected 
dictionary of tokens when parsed."""
-        expected = {'category': category, 'key': key, 'operator': operator, 
'value': value}
-        token = '{category}:{key} {operator} {value}'.format(**expected)
-        return token, expected
 
     def test_valid_strings(self):
         """Run quick pyparsing test over valid grammar strings."""
@@ -27,7 +28,7 @@
 
     def test_single_category_key_token(self):
         """A valid single token with a category that has key is properly 
parsed and interpreted."""
-        token, expected = self._get_category_key_token()
+        token, expected = _get_category_key_token()
         parsed = grammar.parseString(token, parseAll=True)
         self.assertDictEqual(parsed[0].asDict(), expected)
 
diff --git a/cumin/tests/unit/test_query.py b/cumin/tests/unit/test_query.py
index cd583b7..7c2b88d 100644
--- a/cumin/tests/unit/test_query.py
+++ b/cumin/tests/unit/test_query.py
@@ -1,5 +1,6 @@
 """Query handling tests."""
 
+import logging
 import os
 import pkgutil
 import unittest
@@ -19,6 +20,9 @@
     @staticmethod
     def new(config, logger=None):
         """Return an instance of the mocked query class."""
+        if logger is not None and not isinstance(logger, logging.Logger):
+            raise AssertionError('Expected logger parameter to be None or 
logging instance, got: {logger}'.format(
+                logger=logger))
         if not isinstance(config, dict):
             raise AssertionError("Expected instance of dict, got type '{type}' 
for config.".format(type=type(config)))
         return mock.MagicMock(spec_set=BaseQuery)
@@ -98,4 +102,4 @@
         """QueryBuilder._parse_token() should raise RuntimeError for an 
invalid token."""
         query_builder = QueryBuilder(self.invalid_query_string, self.config)
         with self.assertRaisesRegexp(RuntimeError, r"Invalid query string 
syntax"):
-            query_builder._parse_token('invalid_token')
+            query_builder._parse_token('invalid_token')  # pylint: 
disable=protected-access
diff --git a/cumin/tests/unit/transports/test_clustershell.py 
b/cumin/tests/unit/transports/test_clustershell.py
index 187adc7..180e8ac 100644
--- a/cumin/tests/unit/transports/test_clustershell.py
+++ b/cumin/tests/unit/transports/test_clustershell.py
@@ -1,5 +1,5 @@
 """ClusterShell transport tests."""
-
+# pylint: disable=invalid-name,no-member,protected-access
 import unittest
 
 import mock
@@ -32,7 +32,7 @@
     """ClusterShell backend worker test class."""
 
     @mock.patch('cumin.transports.clustershell.Task.task_self')
-    def setUp(self, task_self):
+    def setUp(self, task_self):  # pylint: disable=arguments-differ
         """Initialize default properties and instances"""
         self.config = {
             'clustershell': {
@@ -126,6 +126,8 @@
         self.worker.task.iter_buffers = TestClusterShellWorker.iter_buffers
         self.worker.handler = 'async'
         self.worker.execute()
+        nodes = None
+        output = None
         for nodes, output in self.worker.get_results():
             pass
         self.assertEqual(str(nodes), 'node[90-92]')
@@ -140,6 +142,8 @@
     def test_handler_setter_invalid(self):
         """Raise WorkerError if trying to set it to an invalid class or 
value"""
         class InvalidClass(object):
+            """Invalid class."""
+
             pass
 
         with self.assertRaisesRegexp(WorkerError, r'handler must be one of'):
@@ -173,7 +177,7 @@
 class TestBaseEventHandler(unittest.TestCase):
     """BaseEventHandler test class."""
 
-    def setUp(self, *args):
+    def setUp(self, *args):  # pylint: disable=arguments-differ
         """Initialize default properties and instances."""
         self.nodes = ['node1', 'node2']
         self.commands = [Command('command1', ok_codes=[0, 100]), 
Command('command2', timeout=5)]
@@ -182,6 +186,7 @@
         self.worker.command = 'command1'
         self.worker.nodes = clustershell.NodeSet.NodeSet.fromlist(self.nodes)
         self.handler = None
+        self.args = args
 
     @mock.patch('cumin.transports.clustershell.colorama')
     def test_close(self, colorama):
@@ -201,7 +206,7 @@
         self.pbar_ok = mock.Mock()
         self.pbar_ko = mock.Mock()
 
-    def close(self, worker):
+    def close(self, task):
         """Required by the BaseEventHandler class."""
 
 
@@ -210,7 +215,7 @@
 
     @mock.patch('cumin.transports.clustershell.colorama')
     @mock.patch('cumin.transports.clustershell.tqdm')
-    def setUp(self, tqdm, colorama):
+    def setUp(self, tqdm, colorama):  # pylint: disable=arguments-differ
         """Initialize default properties and instances."""
         super(TestConcreteBaseEventHandler, self).setUp()
         self.handler = ConcreteBaseEventHandler(
@@ -237,6 +242,7 @@
         self.handler.on_timeout(self.worker.task)
         self.assertTrue(self.handler.pbar_ko.update.called)
         self.assertTrue(self.handler.global_timedout)
+        self.assertTrue(tqdm.write.called)
 
     def test_ev_pickup(self):
         """Calling ev_pickup() should set the state of the current node to 
running."""
@@ -288,7 +294,7 @@
     @mock.patch('cumin.transports.clustershell.logging')
     @mock.patch('cumin.transports.clustershell.colorama')
     @mock.patch('cumin.transports.clustershell.tqdm')
-    def setUp(self, tqdm, colorama, logger):
+    def setUp(self, tqdm, colorama, logger):  # pylint: 
disable=arguments-differ
         """Initialize default properties and instances."""
         super(TestSyncEventHandler, self).setUp()
         self.handler = clustershell.SyncEventHandler(
@@ -297,6 +303,7 @@
         self.worker.eh = self.handler
         self.tqdm = tqdm
         self.colorama = colorama
+        self.logger = logger
 
     def test_instantiation(self):
         """An instance of SyncEventHandler should be an instance of 
BaseEventHandler."""
@@ -373,7 +380,7 @@
         
self.assertTrue(self.handler.nodes[self.worker.current_node].state.is_failed)
 
     @mock.patch('cumin.transports.clustershell.tqdm')
-    def test_close(self, tqdm):
+    def test_close(self, tqdm):  # pylint: disable=arguments-differ
         """Calling close should print the report when needed."""
         self.handler.current_command_index = 2
         self.handler.close(self.worker)
@@ -386,11 +393,14 @@
     @mock.patch('cumin.transports.clustershell.logging')
     @mock.patch('cumin.transports.clustershell.colorama')
     @mock.patch('cumin.transports.clustershell.tqdm')
-    def setUp(self, tqdm, colorama, logger):
+    def setUp(self, tqdm, colorama, logger):  # pylint: 
disable=arguments-differ
         """Initialize default properties and instances."""
         super(TestAsyncEventHandler, self).setUp()
         self.handler = clustershell.AsyncEventHandler(self.nodes, 
self.commands)
         self.worker.eh = self.handler
+        self.tqdm = tqdm
+        self.colorama = colorama
+        self.logger = logger
 
     def test_instantiation(self):
         """An instance of AsyncEventHandler should be an instance of 
BaseEventHandler and initialize progress bars."""
@@ -430,7 +440,7 @@
         self.handler.ev_timer(mock.Mock())
 
     @mock.patch('cumin.transports.clustershell.tqdm')
-    def test_close(self, tqdm):
+    def test_close(self, tqdm):  # pylint: disable=arguments-differ
         """Calling close with a worker should close progress bars."""
         self.worker.task.iter_buffers = TestClusterShellWorker.iter_buffers
         self.worker.num_timeout.return_value = 0
diff --git a/cumin/tests/unit/transports/test_init.py 
b/cumin/tests/unit/transports/test_init.py
index 315416e..5911c2e 100644
--- a/cumin/tests/unit/transports/test_init.py
+++ b/cumin/tests/unit/transports/test_init.py
@@ -1,4 +1,5 @@
 """Transport tests."""
+# pylint: disable=protected-access
 import unittest
 
 import mock
@@ -19,16 +20,6 @@
         yield "node", "output"
 
     @property
-    def mode(self):
-        """Required by BaseWorker"""
-        return self._mode
-
-    @mode.setter
-    def mode(self, value):
-        """Required by BaseWorker"""
-        self._mode = value
-
-    @property
     def handler(self):
         """Required by BaseWorker"""
         return self._handler
@@ -43,10 +34,11 @@
     """Command class tests."""
 
     def setUp(self):
-        command_with_options = 'command --with "options" -a -n -d params with\ 
spaces'
+        """Initialize test commands."""
+        command_with_options = r'command --with "options" -a -n -d params 
with\ spaces'
         self.same_command_a = transports.Command(command_with_options)
-        self.same_command_b = "command  --with  'options'  -a  -n  -d  params  
with\ spaces"
-        command_with_nested_quotes = 'command --with \'nested "quotes"\' -a -n 
-d params with\ spaces'
+        self.same_command_b = r"command  --with  'options'  -a  -n  -d  params 
 with\ spaces"
+        command_with_nested_quotes = 'command --with \'nested "quotes"\' -a -n 
-d params with\\ spaces'
 
         self.commands = [
             {'command': 'command1'},
@@ -62,7 +54,7 @@
             {'command': command_with_nested_quotes, 'ok_codes': [0, 255]},
             {'command': command_with_nested_quotes, 'timeout': 5, 'ok_codes': 
[0, 255]},
         ]
-        self.different_command = "command  --with  'other options'  -a  -n  -d 
 other_params  with\ spaces"
+        self.different_command = r"command  --with  'other options'  -a  -n  
-d  other_params  with\ spaces"
         for command in self.commands:
             command['obj'] = transports.Command(
                 command['command'], timeout=command.get('timeout', None), 
ok_codes=command.get('ok_codes', None))
@@ -78,7 +70,8 @@
     def test_repr(self):
         """A repr of a Command should allow to instantiate an instance with 
the same properties."""
         for command in self.commands:
-            command_instance = eval(repr(command['obj']))
+            # Bandit and pylint would require to use ast.literal_eval, but it 
will not work with objects
+            command_instance = eval(repr(command['obj']))  # nosec pylint: 
disable=eval-used
             self.assertIsInstance(command_instance, transports.Command)
             self.assertEqual(repr(command_instance), repr(command['obj']))
             self.assertEqual(command_instance.command, command['obj'].command)
@@ -100,7 +93,7 @@
                 self.assertEqual(command['obj'], command['command'])
 
             with self.assertRaisesRegexp(ValueError, 'Unable to compare 
instance of'):
-                command['obj'] == 1
+                command['obj'] == 1  # pylint: disable=pointless-statement
 
         self.assertEqual(self.same_command_a, self.same_command_b)
 
@@ -129,7 +122,7 @@
                 self.assertNotEqual(command['obj'], command['command'])
 
             with self.assertRaisesRegexp(ValueError, 'Unable to compare 
instance of'):
-                command['obj'] == 1
+                command['obj'] == 1  # pylint: disable=pointless-statement
 
     def test_timeout_getter(self):
         """Should return the timeout set, None otherwise."""
@@ -226,7 +219,7 @@
         """Accessing a property with an invalid name should raise 
AttributeError."""
         state = transports.State(init=transports.State.failed)
         with self.assertRaisesRegexp(AttributeError, 'object has no 
attribute'):
-            state.invalid_property
+            state.invalid_property  # pylint: disable=pointless-statement
 
     def test_repr(self):
         """A State repr should return its representation that allows to 
recreate the same State instance."""
@@ -309,7 +302,7 @@
     def test_instantiation(self):
         """Raise if instantiated directly, should return an instance of 
BaseWorker if inherited."""
         with self.assertRaises(TypeError):
-            transports.BaseWorker({})
+            transports.BaseWorker({})  # pylint: 
disable=abstract-class-instantiated
 
         self.assertIsInstance(ConcreteBaseWorker({}), transports.BaseWorker)
 
diff --git a/cumin/tests/vulture_whitelist.py b/cumin/tests/vulture_whitelist.py
new file mode 100644
index 0000000..355fecd
--- /dev/null
+++ b/cumin/tests/vulture_whitelist.py
@@ -0,0 +1,16 @@
+"""Vulture whitelist to avoid false positives."""
+
+
+class Whitelist:
+    """Helper class that allows mocking Python objects."""
+
+    def __getattr__(self, _):
+        """Mocking magic method __getattr__."""
+        pass
+
+
+whitelist_logging = Whitelist()
+whitelist_logging.raiseExceptions
+
+whitelist_cli = Whitelist()
+whitelist_cli.run.h
diff --git a/cumin/transports/__init__.py b/cumin/transports/__init__.py
index 783edb8..6460232 100644
--- a/cumin/transports/__init__.py
+++ b/cumin/transports/__init__.py
@@ -204,7 +204,7 @@
         if isinstance(other, int):
             return self._state - other
         elif isinstance(other, State):
-            return self._state - other._state
+            return self._state - other._state  # pylint: 
disable=protected-access
         else:
             raise ValueError("Unable to compare instance of '{other}' with 
State instance".format(other=type(other)))
 
@@ -357,7 +357,8 @@
         value -- The success ratio threshold that must be reached to consider 
the run successful. A float between 0
                  and 1 or None. The specific meaning might change based on the 
chosen transport. [default: 1]
         """
-        if value is not None and (not isinstance(value, float) or not (0.0 <= 
value <= 1.0)):
+        if value is not None and (not isinstance(value, float) or
+                                  not (0.0 <= value <= 1.0)):  # pylint: 
disable=superfluous-parens
             raise WorkerError("success_threshold must be a float beween 0 and 
1, got '{value_type}': {value}".format(
                 value_type=type(value), value=value))
 
diff --git a/cumin/transports/clustershell.py b/cumin/transports/clustershell.py
index 12efac8..0c5b8fd 100644
--- a/cumin/transports/clustershell.py
+++ b/cumin/transports/clustershell.py
@@ -27,14 +27,14 @@
 
         # Set any ClusterShell task options
         for key, value in config.get('clustershell', {}).items():
-            if type(value) == list:
+            if isinstance(value, list):
                 self.task.set_info(key, ' '.join(value))
             else:
                 self.task.set_info(key, value)
 
     def execute(self):
         """Required by BaseWorker."""
-        if len(self.commands) == 0:
+        if not self.commands:
             self.logger.warning('No commands provided')
             return
 
@@ -45,7 +45,7 @@
         first_batch = NodeSet.NodeSet.fromlist(self.hosts[:self.batch_size])
 
         # Instantiate handler
-        self._handler_instance = self.handler(
+        self._handler_instance = self.handler(  # pylint: disable=not-callable
             self.hosts, self.commands, 
success_threshold=self.success_threshold, batch_size=self.batch_size,
             batch_sleep=self.batch_sleep, logger=self.logger, 
first_batch=first_batch)
 
@@ -86,7 +86,7 @@
 
         The available default handlers are defined in DEFAULT_HANDLERS.
         """
-        if type(value) == type and issubclass(value, BaseEventHandler):
+        if isinstance(value, type) and issubclass(value, BaseEventHandler):
             self._handler = value
         elif value in DEFAULT_HANDLERS.keys():
             self._handler = DEFAULT_HANDLERS[value]
@@ -285,7 +285,7 @@
 
         return (log_message, str(nodes_string))
 
-    def _print_report_line(self, message, color=colorama.Fore.RED, 
nodes_string=''):
+    def _print_report_line(self, message, color=colorama.Fore.RED, 
nodes_string=''):  # pylint: disable=no-self-use
         """Helper to print a tqdm-friendly colored status line with 
success/failure ratio and optional list of nodes.
 
         Arguments:
@@ -612,7 +612,7 @@
                 self.logger.debug("skipped timer")
                 return
 
-            if len(pending) > 0:
+            if pending:
                 # This usually happens when executing in batches
                 self.logger.warning("command '{command}' was not executed on: 
{nodes}".format(
                     command=command, nodes=NodeSet.NodeSet.fromlist(pending)))
@@ -627,7 +627,7 @@
                         # Only nodes in pending state will be scheduled for 
the next command
                         node.state.update(State.pending)
         finally:
-                self.lock.release()
+            self.lock.release()
 
         if restart:
             self.start_command(schedule=True)
@@ -775,5 +775,6 @@
             self.return_value = 1
 
 
-worker_class = ClusterShellWorker  # Required by the auto-loader in the 
cumin.transport.Transport factory
+# Required by the auto-loader in the cumin.transport.Transport factory
+worker_class = ClusterShellWorker  # pylint: disable=invalid-name
 DEFAULT_HANDLERS = {'sync': SyncEventHandler, 'async': AsyncEventHandler}  # 
Available default EventHandler classes

-- 
To view, visit https://gerrit.wikimedia.org/r/361040
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Icf2965ec31beb9325c07df8aa3e2d1d71265bc2c
Gerrit-PatchSet: 1
Gerrit-Project: operations/software/cumin
Gerrit-Branch: master
Gerrit-Owner: Volans <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to