Hello community, here is the log from the commit of package python-vulture for openSUSE:Factory checked in at 2020-02-07 15:55:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-vulture (Old) and /work/SRC/openSUSE:Factory/.python-vulture.new.26092 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-vulture" Fri Feb 7 15:55:16 2020 rev:6 rq:770681 version:1.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-vulture/python-vulture.changes 2019-02-20 14:10:39.790952816 +0100 +++ /work/SRC/openSUSE:Factory/.python-vulture.new.26092/python-vulture.changes 2020-02-07 15:56:39.783581845 +0100 @@ -1,0 +2,15 @@ +Thu Feb 6 17:35:31 UTC 2020 - Marketa Calabkova <[email protected]> + +- update to 1.3 + * Detect redundant ‘if’ conditions without ‘else’ blocks. + * Add whitelist for string.Formatter + * Fix tests for Python 3.8 + * Use new Constant AST node under Python 3.8+ + * Add test for f-strings + * Add whitelist for logging module. + * Add sys.excepthook to sys whitelist. + * Add whitelist for ctypes module. + * Check that type annotations are parsed and type comments are ignored + * Support checking files with BOM under Python 2.7 + +------------------------------------------------------------------- Old: ---- vulture-1.0.tar.gz New: ---- vulture-1.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-vulture.spec ++++++ --- /var/tmp/diff_new_pack.oCjARr/_old 2020-02-07 15:56:41.291582614 +0100 +++ /var/tmp/diff_new_pack.oCjARr/_new 2020-02-07 15:56:41.295582616 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-vulture # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-vulture -Version: 1.0 +Version: 1.3 Release: 0 Summary: Python module for finding dead code License: MIT ++++++ vulture-1.0.tar.gz -> vulture-1.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/NEWS.rst new/vulture-1.3/NEWS.rst --- old/vulture-1.0/NEWS.rst 2018-10-23 18:27:25.000000000 +0200 +++ new/vulture-1.3/NEWS.rst 2020-02-03 12:00:25.000000000 +0100 @@ -1,6 +1,28 @@ News ==== +1.3 (2020-02-03) +---------------- +* Detect redundant 'if' conditions without 'else' blocks. +* Add whitelist for ``string.Formatter`` (Joseph Bylund, #183). + + +1.2 (2019-11-22) +---------------- +* Fix tests for Python 3.8 (#166). +* Use new ``Constant`` AST node under Python 3.8+ (#175). +* Add test for f-strings (#177). +* Add whitelist for ``logging`` module. + + +1.1 (2019-09-23) +---------------- +* Add ``sys.excepthook`` to ``sys`` whitelist. +* Add whitelist for ``ctypes`` module. +* Check that type annotations are parsed and type comments are ignored (thanks @kx-chen). +* Support checking files with BOM under Python 2.7 (#170). + + 1.0 (2018-10-23) ---------------- * Add ``--ignore-decorators`` flag (thanks @RJ722). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/PKG-INFO new/vulture-1.3/PKG-INFO --- old/vulture-1.0/PKG-INFO 2018-10-23 18:29:40.000000000 +0200 +++ new/vulture-1.3/PKG-INFO 2020-02-03 12:50:15.000000000 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: vulture -Version: 1.0 +Version: 1.3 Summary: Find dead code Home-page: https://github.com/jendrikseipp/vulture Author: Jendrik Seipp @@ -37,7 +37,7 @@ * tested: tests itself and has complete test coverage * complements pyflakes and has the same output syntax * sorts unused classes and functions by size with ``--sort-by-size`` - * supports Python 2.7 and Python >= 3.4 + * supports Python 2.7 and Python >= 3.5 Installation @@ -62,6 +62,10 @@ The provided arguments may be Python files or directories. For each directory Vulture analyzes all contained `*.py` files. + Vulture assigns each chunk of dead code a confidence value. A confidence + value of 100% means that the code will never be executed. Values below + 100% are only estimates for how likely it is that the code is unused. + After you have found and deleted dead code, run Vulture again, because it may discover more dead code. @@ -209,9 +213,15 @@ Similar programs ---------------- - * Vulture can be used together with *pyflakes* - * The *coverage* module can find unused code more reliably, but requires - all branches of the code to actually be run. + * `pyflakes <https://pypi.org/project/pyflakes/>`_ finds unused imports and + unused local variables (in addition to many other programmatic errors). + * `coverage <https://pypi.org/project/coverage/>`_ finds unused + code more reliably than Vulture, but requires all branches of the code to + actually be run. + * `uncalled <https://pypi.org/project/uncalled/>`_ finds dead code by using + the abstract syntax tree (like Vulture), regular expressions, or both. + * `dead <https://pypi.org/project/dead/>`_ finds dead code by using + the abstract syntax tree (like Vulture). Participate @@ -228,6 +238,28 @@ News ==== + 1.3 (2020-02-03) + ---------------- + * Detect redundant 'if' conditions without 'else' blocks. + * Add whitelist for ``string.Formatter`` (Joseph Bylund, #183). + + + 1.2 (2019-11-22) + ---------------- + * Fix tests for Python 3.8 (#166). + * Use new ``Constant`` AST node under Python 3.8+ (#175). + * Add test for f-strings (#177). + * Add whitelist for ``logging`` module. + + + 1.1 (2019-09-23) + ---------------- + * Add ``sys.excepthook`` to ``sys`` whitelist. + * Add whitelist for ``ctypes`` module. + * Check that type annotations are parsed and type comments are ignored (thanks @kx-chen). + * Support checking files with BOM under Python 2.7 (#170). + + 1.0 (2018-10-23) ---------------- * Add ``--ignore-decorators`` flag (thanks @RJ722). @@ -462,9 +494,10 @@ Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Quality Assurance +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/README.rst new/vulture-1.3/README.rst --- old/vulture-1.0/README.rst 2018-07-31 14:52:23.000000000 +0200 +++ new/vulture-1.3/README.rst 2019-10-29 12:57:15.000000000 +0100 @@ -29,7 +29,7 @@ * tested: tests itself and has complete test coverage * complements pyflakes and has the same output syntax * sorts unused classes and functions by size with ``--sort-by-size`` -* supports Python 2.7 and Python >= 3.4 +* supports Python 2.7 and Python >= 3.5 Installation @@ -54,6 +54,10 @@ The provided arguments may be Python files or directories. For each directory Vulture analyzes all contained `*.py` files. +Vulture assigns each chunk of dead code a confidence value. A confidence +value of 100% means that the code will never be executed. Values below +100% are only estimates for how likely it is that the code is unused. + After you have found and deleted dead code, run Vulture again, because it may discover more dead code. @@ -201,9 +205,15 @@ Similar programs ---------------- -* Vulture can be used together with *pyflakes* -* The *coverage* module can find unused code more reliably, but requires - all branches of the code to actually be run. +* `pyflakes <https://pypi.org/project/pyflakes/>`_ finds unused imports and + unused local variables (in addition to many other programmatic errors). +* `coverage <https://pypi.org/project/coverage/>`_ finds unused + code more reliably than Vulture, but requires all branches of the code to + actually be run. +* `uncalled <https://pypi.org/project/uncalled/>`_ finds dead code by using + the abstract syntax tree (like Vulture), regular expressions, or both. +* `dead <https://pypi.org/project/dead/>`_ finds dead code by using + the abstract syntax tree (like Vulture). Participate diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/TODO.rst new/vulture-1.3/TODO.rst --- old/vulture-1.0/TODO.rst 2018-10-16 23:46:53.000000000 +0200 +++ new/vulture-1.3/TODO.rst 2020-02-03 12:04:37.000000000 +0100 @@ -1,6 +1,10 @@ TODOs ===== +* Convert README.rst and NEWS.rst files to Markdown. +* Detect unused variables in assignment expressions under Python 3.8+. +* Use end_lineno and end_col_offset attributes when running Python 3.8+. + Non-TODOs ========= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/builtins.txt new/vulture-1.3/builtins.txt --- old/vulture-1.0/builtins.txt 2017-08-12 10:04:30.000000000 +0200 +++ new/vulture-1.3/builtins.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,14 +0,0 @@ -python2 = ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip'] - -python3 = ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'] - -print(sorted(set(python2) | set(python3))) - -try: - # Python 3 - import builtins -except ImportError: - # Python 2 - import __builtin__ as builtins - -print(vars(builtins)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/multi.txt new/vulture-1.3/multi.txt --- old/vulture-1.0/multi.txt 2017-07-26 11:21:49.000000000 +0200 +++ new/vulture-1.3/multi.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,8 +0,0 @@ -def test(): - pass - -def test(): - pass - -a = 1 -a = 2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/setup.py new/vulture-1.3/setup.py --- old/vulture-1.0/setup.py 2018-06-30 12:41:55.000000000 +0200 +++ new/vulture-1.3/setup.py 2019-09-30 08:33:42.000000000 +0200 @@ -58,9 +58,9 @@ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Quality Assurance' @@ -68,6 +68,7 @@ entry_points={ 'console_scripts': ['vulture = vulture.core:main'], }, + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', tests_require=['pytest', 'pytest-cov'], cmdclass={'test': PyTest}, packages=setuptools.find_packages(exclude=['tests']), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/__init__.py new/vulture-1.3/tests/__init__.py --- old/vulture-1.0/tests/__init__.py 2018-07-03 10:55:48.000000000 +0200 +++ new/vulture-1.3/tests/__init__.py 2019-09-02 16:16:07.000000000 +0200 @@ -5,6 +5,7 @@ import sys import pytest + from vulture import core DIR = os.path.dirname(os.path.abspath(__file__)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_conditions.py new/vulture-1.3/tests/test_conditions.py --- old/vulture-1.0/tests/test_conditions.py 2018-06-03 22:10:54.000000000 +0200 +++ new/vulture-1.3/tests/test_conditions.py 2019-11-22 21:10:15.000000000 +0100 @@ -35,6 +35,7 @@ def test_true(): check_condition("True", True) check_condition("2", True) + check_condition("'s'", True) check_condition("['foo', 'bar']", True) check_condition("{'a': 1, 'b': 2}", True) @@ -181,3 +182,12 @@ pass """) check_unreachable(v, 9, 4, 'else') + + +def test_redundant_if(v): + v.scan("""\ +if [5]: + pass +""") + print(v.unreachable_code[0].size) + check_unreachable(v, 1, 2, 'if') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_confidence.py new/vulture-1.3/tests/test_confidence.py --- old/vulture-1.0/tests/test_confidence.py 2017-08-25 23:16:11.000000000 +0200 +++ new/vulture-1.3/tests/test_confidence.py 2019-09-02 16:53:32.000000000 +0200 @@ -9,9 +9,9 @@ def check_min_confidence(code, min_confidence, expected): v = core.Vulture(verbose=True) v.scan(code) - detected = dict( - (item.name, item.confidence) - for item in v.get_unused_code(min_confidence=min_confidence)) + detected = { + item.name: item.confidence + for item in v.get_unused_code(min_confidence=min_confidence)} assert detected == expected diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_encoding.py new/vulture-1.3/tests/test_encoding.py --- old/vulture-1.0/tests/test_encoding.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vulture-1.3/tests/test_encoding.py 2019-09-02 12:06:41.000000000 +0200 @@ -0,0 +1,43 @@ +import codecs +import io + +from . import v +assert v # Silence pyflakes. + + +def test_encoding1(v): + v.scan(u"""\ +# -*- coding: utf-8 -*- +pass +""") + assert not v.found_dead_code_or_error + + +def test_encoding2(v): + v.scan(u"""\ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +pass +""") + assert not v.found_dead_code_or_error + + +def test_non_utf8_encoding(v, tmpdir): + code = "" + name = "non_utf8" + non_utf_8_file = str(tmpdir.mkdir(name).join(name + ".py")) + with open(non_utf_8_file, mode='wb') as f: + f.write(codecs.BOM_UTF16_LE) + f.write(code.encode('utf_16_le')) + v.scavenge([f.name]) + assert v.found_dead_code_or_error + + +def test_utf8_with_bom(v, tmpdir): + name = "utf8_bom" + filename = str(tmpdir.mkdir(name).join(name + ".py")) + # utf8_sig prepends the BOM to the file. + with io.open(filename, mode='w', encoding="utf-8-sig") as f: + f.write(u"") + v.scavenge([f.name]) + assert not v.found_dead_code_or_error diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_errors.py new/vulture-1.3/tests/test_errors.py --- old/vulture-1.0/tests/test_errors.py 2018-06-30 12:41:55.000000000 +0200 +++ new/vulture-1.3/tests/test_errors.py 2019-09-02 11:18:11.000000000 +0200 @@ -1,5 +1,3 @@ -import codecs - import pytest from . import v @@ -23,18 +21,3 @@ """) with pytest.raises(ValueError): v.get_unused_code(min_confidence=150) - - -def test_non_utf8_encoding(v, tmpdir): - code = """\ -def foo(): - pass - -foo() -""" - non_utf_8_file = str(tmpdir.mkdir("non_utf8").join("non_utf8.py")) - with open(non_utf_8_file, 'wb') as f: - f.write(codecs.BOM_UTF16_LE) - f.write(code.encode('utf_16_le')) - v.scavenge([f.name]) - assert v.found_dead_code_or_error diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_format_strings.py new/vulture-1.3/tests/test_format_strings.py --- old/vulture-1.0/tests/test_format_strings.py 2017-07-30 19:13:54.000000000 +0200 +++ new/vulture-1.3/tests/test_format_strings.py 2019-11-11 21:34:43.000000000 +0100 @@ -1,3 +1,7 @@ +import sys + +import pytest + from . import check, v assert v # Silence pyflakes. @@ -12,6 +16,18 @@ check(v.used_names, ['a', 'b', 'c', 'd', 'locals']) [email protected]( + sys.version_info < (3, 6), + reason="needs f-string support (added in Python 3.6)") +def test_f_string(v): + v.scan('''\ +f'{a}, {b:0d} {c:<30} {d:.2%} {e()} {f:{width}.{precision}}' +f'{ {x:y for (x, y) in ((1, 2), (3, 4))} }' +''') + check(v.used_names, + ['a', 'b', 'c', 'd', 'e', 'f', 'precision', 'width', 'x', 'y']) + + def test_new_format_string_access(v): v.scan("'{a.b}, {c.d.e} {f[g]} {h[i][j]}'.format(**locals())") check(v.used_names, ['a', 'c', 'f', 'h', 'locals']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_report.py new/vulture-1.3/tests/test_report.py --- old/vulture-1.0/tests/test_report.py 2018-08-17 01:04:57.000000000 +0200 +++ new/vulture-1.3/tests/test_report.py 2019-09-23 22:08:48.000000000 +0200 @@ -1,3 +1,5 @@ +import sys + import pytest from . import v @@ -43,6 +45,8 @@ {filename}:11: unreachable code after 'return' (100% confidence) {filename}:13: unused property 'myprop' (60% confidence) """ + if sys.version_info >= (3, 8): + expected = expected.replace("{filename}:13:", "{filename}:14:") check_report(mock_code, expected) @@ -56,4 +60,6 @@ # unreachable code after 'return' ({filename}:11) _.myprop # unused property ({filename}:13) """ + if sys.version_info >= (3, 8): + expected = expected.replace("{filename}:13)", "{filename}:14)") check_report(mock_code, expected, make_whitelist=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_scavenging.py new/vulture-1.3/tests/test_scavenging.py --- old/vulture-1.0/tests/test_scavenging.py 2018-06-06 13:31:23.000000000 +0200 +++ new/vulture-1.3/tests/test_scavenging.py 2019-10-29 20:41:06.000000000 +0100 @@ -1,3 +1,7 @@ +import sys + +import pytest + from . import check, skip_if_not_has_async, v assert v # Silence pyflakes. @@ -516,23 +520,6 @@ check(v.unused_vars, ['bar']) -def test_encoding1(v): - v.scan(u"""\ -# -*- coding: utf-8 -*- -pass -""") - assert True - - -def test_encoding2(v): - v.scan(u"""\ -#! /usr/bin/env python -# -*- coding: utf-8 -*- -pass -""") - assert True - - def test_multiple_definition(v): v.scan("""\ a = 1 @@ -541,3 +528,48 @@ check(v.defined_vars, ['a', 'a']) check(v.used_names, []) check(v.unused_vars, ['a', 'a']) + + [email protected](sys.version_info < (3, 5), + reason="requires Python 3.5+") +def test_arg_type_annotation(v): + v.scan("""\ +from typing import Iterable + +def f(n: int) -> Iterable[int]: + yield n +""") + + check(v.unused_vars, []) + check(v.unused_funcs, ['f']) + check(v.unused_imports, []) + + [email protected](sys.version_info < (3, 6), + reason="requires Python 3.6+") +def test_var_type_annotation(v): + v.scan("""\ +from typing import List + +x: List[int] = [1] +""") + + check(v.unused_vars, ['x']) + check(v.unused_funcs, []) + check(v.unused_imports, []) + + +def test_type_hint_comments(v): + v.scan("""\ +import typing + +if typing.TYPE_CHECKING: + from typing import List, Text + +def foo(foo_li): + # type: (List[Text]) -> None + for bar in foo_li: + bar.xyz() +""") + + check(v.unused_imports, ['List', 'Text']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_size.py new/vulture-1.3/tests/test_size.py --- old/vulture-1.0/tests/test_size.py 2017-08-15 16:29:38.000000000 +0200 +++ new/vulture-1.3/tests/test_size.py 2019-09-23 22:11:26.000000000 +0200 @@ -36,7 +36,7 @@ assert count_lines(node) == size break else: - assert False, 'Failed to find top-level class "Foo" in code' + raise AssertionError('Failed to find top-level class "Foo" in code') def test_size_basic(): @@ -78,7 +78,10 @@ else: pass """ - check_size(example, 11) + size = 11 + if sys.version_info >= (3, 8): + size = 10 + check_size(example, size) def test_size_while(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tests/test_utils.py new/vulture-1.3/tests/test_utils.py --- old/vulture-1.0/tests/test_utils.py 2018-07-31 14:52:23.000000000 +0200 +++ new/vulture-1.3/tests/test_utils.py 2019-09-02 16:22:34.000000000 +0200 @@ -1,8 +1,9 @@ import ast -from . import skip_if_not_has_async from vulture import utils +from . import skip_if_not_has_async + def check_decorator_names(code, expected_names): decorator_names = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/tox.ini new/vulture-1.3/tox.ini --- old/vulture-1.0/tox.ini 2018-08-17 01:04:57.000000000 +0200 +++ new/vulture-1.3/tox.ini 2020-01-22 19:55:09.000000000 +0100 @@ -1,26 +1,34 @@ [tox] -envlist = cleanup, docs, py27, py34, py35, py36, py37, style +envlist = cleanup, docs, py{27,35,36,37,38}, style skip_missing_interpreters = true # Erase old coverage results, then accumulate them during this tox run. [testenv:cleanup] -deps = coverage -commands = coverage erase +deps = + coverage +commands = + coverage erase [testenv] deps = - coverage - pytest + coverage<5.0.0 + pytest>=3.3 pytest-cov commands = py.test [testenv:docs] deps = - collective.checkdocs - pygments -commands = python setup.py checkdocs + collective.checkdocs + pygments +commands = + python setup.py checkdocs [testenv:style] -deps = flake8 -commands = flake8 setup.py tests/ vulture/ +deps = + flake8 + py36,py37: flake8-2020 + py36,py37: flake8-bugbear + py36,py37: flake8-comprehensions +commands = + flake8 setup.py tests/ vulture/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/core.py new/vulture-1.3/vulture/core.py --- old/vulture-1.0/vulture/core.py 2018-10-23 18:29:40.000000000 +0200 +++ new/vulture-1.3/vulture/core.py 2020-02-03 12:50:14.000000000 +0100 @@ -1,9 +1,8 @@ -#! /usr/bin/env python # -*- coding: utf-8 -*- # # vulture - Find dead code. # -# Copyright (c) 2012-2018 Jendrik Seipp ([email protected]) +# Copyright (c) 2012-2019 Jendrik Seipp ([email protected]) # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the @@ -38,18 +37,14 @@ from vulture import lines from vulture import utils -__version__ = '1.0' +__version__ = '1.3' DEFAULT_CONFIDENCE = 60 -# The ast module in Python 2 trips over "coding" cookies, so strip them. -ENCODING_REGEX = re.compile( - r"^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+).*?$", flags=re.M) - -IGNORED_VARIABLE_NAMES = set(['object', 'self']) +IGNORED_VARIABLE_NAMES = {'object', 'self'} # True and False are NameConstants since Python 3.4. if sys.version_info < (3, 4): - IGNORED_VARIABLE_NAMES |= set(['True', 'False']) + IGNORED_VARIABLE_NAMES |= {'True', 'False'} def _get_unused_items(defined_items, used_names): @@ -103,6 +98,8 @@ """ Hold the name, type and location of defined code. """ + __slots__ = ('name', 'typ', 'filename', 'first_lineno', + 'last_lineno', 'message', 'confidence') def __init__(self, name, typ, filename, first_lineno, last_lineno, message='', @@ -123,10 +120,10 @@ def get_report(self, add_size=False): if add_size: line_format = 'line' if self.size == 1 else 'lines' - size_report = ', {0:d} {1}'.format(self.size, line_format) + size_report = ', {:d} {}'.format(self.size, line_format) else: size_report = '' - return "{0}:{1:d}: {2} ({3}% confidence{4})".format( + return "{}:{:d}: {} ({}% confidence{})".format( utils.format_path(self.filename), self.first_lineno, self.message, self.confidence, size_report) @@ -185,21 +182,21 @@ self.found_dead_code_or_error = False def scan(self, code, filename=''): - code = ENCODING_REGEX.sub("", code, count=1) + code = utils.sanitize_code(code) self.code = code.splitlines() self.filename = filename try: node = ast.parse(code, filename=self.filename) except SyntaxError as err: - text = ' at "{0}"'.format(err.text.strip()) if err.text else '' - print('{0}:{1:d}: {2}{3}'.format( + text = ' at "{}"'.format(err.text.strip()) if err.text else '' + print('{}:{:d}: {}{}'.format( utils.format_path(filename), err.lineno, err.msg, text), file=sys.stderr) self.found_dead_code_or_error = True except (TypeError, ValueError) as err: # Python < 3.5 raises TypeError and Python >= 3.5 raises # ValueError if source contains null bytes. - print('{0}: invalid source code "{1}"'.format( + print('{}: invalid source code "{}"'.format( utils.format_path(filename), err), file=sys.stderr) self.found_dead_code_or_error = True else: @@ -224,7 +221,7 @@ self._log('Scanning:', module) try: module_string = utils.read_file(module) - except utils.VultureInputException as err: + except utils.VultureInputException as err: # noqa: F841 print( 'Error: Could not read file {module} - {err}\n' 'Try to change the encoding to UTF-8.'.format(**locals()), @@ -233,7 +230,7 @@ else: self.scan(module_string, filename=module) - unique_imports = set(item.name for item in self.defined_imports) + unique_imports = {item.name for item in self.defined_imports} for import_name in unique_imports: path = os.path.join('whitelists', import_name) + '_whitelist.py' if exclude_file(path): @@ -344,14 +341,53 @@ last_node=node.body[-1], message="unsatisfiable '{name}' condition".format(**locals()), confidence=100) - else: - else_body = getattr(node, 'orelse') - if utils.condition_is_always_true(node.test) and else_body: + elif utils.condition_is_always_true(node.test): + else_body = node.orelse + if else_body: self._define( self.unreachable_code, 'else', else_body[0], last_node=else_body[-1], message="unreachable 'else' block", confidence=100) + elif name == 'if': + # Redundant if-condition without else block. + self._define( + self.unreachable_code, name, node, + message="redundant if-condition".format(**locals()), + confidence=100) + + def _handle_string(self, s): + """ + Parse variable names in format strings: + + '%(my_var)s' % locals() + '{my_var}'.format(**locals()) + + """ + # Old format strings. + self.used_names |= set(re.findall(r'\%\((\w+)\)', s)) + + def is_identifier(name): + return bool(re.match(r'[a-zA-Z_][a-zA-Z0-9_]*', name)) + + # New format strings. + parser = string.Formatter() + try: + names = [name for _, name, _, _ in parser.parse(s) if name] + except ValueError: + # Invalid format string. + names = [] + + for field_name in names: + # Remove brackets and contents: "a[0][b].c[d].e" -> "a.c.e". + # "a.b.c" -> name = "a", attributes = ["b", "c"] + name_and_attrs = re.sub(r'\[\w*\]', '', field_name).split('.') + name = name_and_attrs[0] + if is_identifier(name): + self.used_names.add(name) + for attr in name_and_attrs[1:]: + if is_identifier(attr): + self.used_attrs.add(attr) def _define(self, collection, name, first_node, last_node=None, message='', confidence=DEFAULT_CONFIDENCE, ignore=None): @@ -439,38 +475,13 @@ elif isinstance(node.ctx, (ast.Param, ast.Store)): self._define_variable(node.id, node) - def visit_Str(self, node): - """ - Parse variable names in format strings: - - '%(my_var)s' % locals() - '{my_var}'.format(**locals()) - - """ - # Old format strings. - self.used_names |= set(re.findall(r'\%\((\w+)\)', node.s)) - - def is_identifier(s): - return bool(re.match(r'[a-zA-Z_][a-zA-Z0-9_]*', s)) - - # New format strings. - parser = string.Formatter() - try: - names = [name for _, name, _, _ in parser.parse(node.s) if name] - except ValueError: - # Invalid format string. - names = [] - - for field_name in names: - # Remove brackets and contents: "a[0][b].c[d].e" -> "a.c.e". - # "a.b.c" -> name = "a", attributes = ["b", "c"] - name_and_attrs = re.sub(r'\[\w*\]', '', field_name).split('.') - name = name_and_attrs[0] - if is_identifier(name): - self.used_names.add(name) - for attr in name_and_attrs[1:]: - if is_identifier(attr): - self.used_attrs.add(attr) + if sys.version_info < (3, 8): + def visit_Str(self, node): + self._handle_string(node.s) + else: + def visit_Constant(self, node): + if isinstance(node.value, str): + self._handle_string(node.value) def visit_While(self, node): self._handle_conditional_node(node, 'while') @@ -525,7 +536,7 @@ return exclude.split(',') usage = "%(prog)s [options] PATH [PATH ...]" - version = "vulture {0}".format(__version__) + version = "vulture {}".format(__version__) glob_help = 'Patterns may contain glob wildcards (*, ?, [abc], [!abc]).' parser = argparse.ArgumentParser(prog='vulture', usage=usage) parser.add_argument( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/lines.py new/vulture-1.3/vulture/lines.py --- old/vulture-1.0/vulture/lines.py 2017-08-15 16:17:27.000000000 +0200 +++ new/vulture-1.3/vulture/lines.py 2019-09-02 16:54:24.000000000 +0200 @@ -15,7 +15,7 @@ list of nodes, we return the last one. """ - ignored_fields = set(['ctx', 'decorator_list', 'names', 'returns']) + ignored_fields = {'ctx', 'decorator_list', 'names', 'returns'} fields = node._fields # The fields of ast.Call are in the wrong order. if isinstance(node, ast.Call): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/utils.py new/vulture-1.3/vulture/utils.py --- old/vulture-1.0/vulture/utils.py 2018-07-31 14:52:23.000000000 +0200 +++ new/vulture-1.3/vulture/utils.py 2019-11-30 18:13:35.000000000 +0100 @@ -1,11 +1,17 @@ import ast import codecs import os +import re import sys import tokenize -# Encoding to use when converting input files to unicode. -ENCODING = 'utf-8' +# Encoding to use when converting input files to unicode. Python 2 trips +# over the BOM, so we use "utf-8-sig" which drops the BOM. +ENCODING = 'utf-8-sig' + +# The ast module in Python 2 trips over "coding" cookies, so strip them. +ENCODING_REGEX = re.compile( + r"^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+).*?$", flags=re.M) class VultureInputException(Exception): @@ -73,7 +79,7 @@ for path in paths: path = os.path.abspath(path) if toplevel and path.endswith('.pyc'): - sys.exit('.pyc files are not supported: {0}'.format(path)) + sys.exit('.pyc files are not supported: {}'.format(path)) if os.path.isfile(path) and (path.endswith('.py') or toplevel): modules.append(path) elif os.path.isdir(path): @@ -82,7 +88,7 @@ for filename in sorted(os.listdir(path))] modules.extend(get_modules(subpaths, toplevel=False)) elif toplevel: - sys.exit('Error: {0} could not be found.'.format(path)) + sys.exit('Error: {} could not be found.'.format(path)) return modules @@ -106,6 +112,10 @@ raise VultureInputException(err) +def sanitize_code(code): + return ENCODING_REGEX.sub("", code, count=1) + + class LoggingList(list): def __init__(self, typ, verbose): self.typ = typ @@ -114,7 +124,7 @@ def append(self, item): if self._verbose: - print('define {0} "{1}"'.format(self.typ, item.name)) + print('define {} "{}"'.format(self.typ, item.name)) list.append(self, item) @@ -126,5 +136,5 @@ def add(self, name): if self._verbose: - print('use {0} "{1}"'.format(self.typ, name)) + print('use {} "{}"'.format(self.typ, name)) set.add(self, name) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/whitelists/argparse_whitelist.py new/vulture-1.3/vulture/whitelists/argparse_whitelist.py --- old/vulture-1.0/vulture/whitelists/argparse_whitelist.py 2018-07-03 10:46:37.000000000 +0200 +++ new/vulture-1.3/vulture/whitelists/argparse_whitelist.py 2019-11-11 19:49:34.000000000 +0100 @@ -1,7 +1,6 @@ import argparse -_ = argparse.ArgumentParser() -_.epilog +argparse.ArgumentParser().epilog -argparse.ArgumentDefaultsHelpFormatter._fill_text -argparse.ArgumentDefaultsHelpFormatter._get_help_string +argparse.ArgumentDefaultsHelpFormatter('prog')._fill_text +argparse.ArgumentDefaultsHelpFormatter('prog')._get_help_string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/whitelists/ast_whitelist.py new/vulture-1.3/vulture/whitelists/ast_whitelist.py --- old/vulture-1.0/vulture/whitelists/ast_whitelist.py 2018-07-03 10:46:37.000000000 +0200 +++ new/vulture-1.3/vulture/whitelists/ast_whitelist.py 2019-11-01 20:22:38.000000000 +0100 @@ -17,6 +17,7 @@ whitelist_node_visitor.visit_Call whitelist_node_visitor.visit_ClassDef whitelist_node_visitor.visit_Compare +whitelist_node_visitor.visit_Constant whitelist_node_visitor.visit_Delete whitelist_node_visitor.visit_Dict whitelist_node_visitor.visit_DictComp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/whitelists/collections_whitelist.py new/vulture-1.3/vulture/whitelists/collections_whitelist.py --- old/vulture-1.0/vulture/whitelists/collections_whitelist.py 2018-07-03 10:46:37.000000000 +0200 +++ new/vulture-1.3/vulture/whitelists/collections_whitelist.py 2019-11-11 19:49:34.000000000 +0100 @@ -1,4 +1,4 @@ import collections # To free memory, the "default_factory" attribute can be set to None. -collections.defaultdict.default_factory +collections.defaultdict().default_factory diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/whitelists/ctypes_whitelist.py new/vulture-1.3/vulture/whitelists/ctypes_whitelist.py --- old/vulture-1.0/vulture/whitelists/ctypes_whitelist.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vulture-1.3/vulture/whitelists/ctypes_whitelist.py 2019-11-02 22:51:32.000000000 +0100 @@ -0,0 +1,8 @@ +from ctypes import _CFuncPtr +from ctypes import _Pointer + +_CFuncPtr.argtypes +_CFuncPtr.errcheck +_CFuncPtr.restype + +_Pointer.contents diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/whitelists/logging_whitelist.py new/vulture-1.3/vulture/whitelists/logging_whitelist.py --- old/vulture-1.0/vulture/whitelists/logging_whitelist.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vulture-1.3/vulture/whitelists/logging_whitelist.py 2019-11-22 20:19:06.000000000 +0100 @@ -0,0 +1,4 @@ +import logging + +logging.Filter.filter +logging.StreamHandler.emit diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/whitelists/string_whitelist.py new/vulture-1.3/vulture/whitelists/string_whitelist.py --- old/vulture-1.0/vulture/whitelists/string_whitelist.py 1970-01-01 01:00:00.000000000 +0100 +++ new/vulture-1.3/vulture/whitelists/string_whitelist.py 2020-01-22 20:06:18.000000000 +0100 @@ -0,0 +1,10 @@ +import string + +string.Formatter.check_unused_args +string.Formatter.convert_field +string.Formatter.format +string.Formatter.format_field +string.Formatter.get_field +string.Formatter.get_value +string.Formatter.parse +string.Formatter.vformat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture/whitelists/sys_whitelist.py new/vulture-1.3/vulture/whitelists/sys_whitelist.py --- old/vulture-1.0/vulture/whitelists/sys_whitelist.py 2018-07-03 10:46:37.000000000 +0200 +++ new/vulture-1.3/vulture/whitelists/sys_whitelist.py 2018-10-29 13:25:40.000000000 +0100 @@ -1,5 +1,7 @@ import sys +sys.excepthook + # Never report redirected streams as unused. sys.stderr sys.stdin diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture.egg-info/PKG-INFO new/vulture-1.3/vulture.egg-info/PKG-INFO --- old/vulture-1.0/vulture.egg-info/PKG-INFO 2018-10-23 18:29:40.000000000 +0200 +++ new/vulture-1.3/vulture.egg-info/PKG-INFO 2020-02-03 12:50:15.000000000 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 1.1 +Metadata-Version: 1.2 Name: vulture -Version: 1.0 +Version: 1.3 Summary: Find dead code Home-page: https://github.com/jendrikseipp/vulture Author: Jendrik Seipp @@ -37,7 +37,7 @@ * tested: tests itself and has complete test coverage * complements pyflakes and has the same output syntax * sorts unused classes and functions by size with ``--sort-by-size`` - * supports Python 2.7 and Python >= 3.4 + * supports Python 2.7 and Python >= 3.5 Installation @@ -62,6 +62,10 @@ The provided arguments may be Python files or directories. For each directory Vulture analyzes all contained `*.py` files. + Vulture assigns each chunk of dead code a confidence value. A confidence + value of 100% means that the code will never be executed. Values below + 100% are only estimates for how likely it is that the code is unused. + After you have found and deleted dead code, run Vulture again, because it may discover more dead code. @@ -209,9 +213,15 @@ Similar programs ---------------- - * Vulture can be used together with *pyflakes* - * The *coverage* module can find unused code more reliably, but requires - all branches of the code to actually be run. + * `pyflakes <https://pypi.org/project/pyflakes/>`_ finds unused imports and + unused local variables (in addition to many other programmatic errors). + * `coverage <https://pypi.org/project/coverage/>`_ finds unused + code more reliably than Vulture, but requires all branches of the code to + actually be run. + * `uncalled <https://pypi.org/project/uncalled/>`_ finds dead code by using + the abstract syntax tree (like Vulture), regular expressions, or both. + * `dead <https://pypi.org/project/dead/>`_ finds dead code by using + the abstract syntax tree (like Vulture). Participate @@ -228,6 +238,28 @@ News ==== + 1.3 (2020-02-03) + ---------------- + * Detect redundant 'if' conditions without 'else' blocks. + * Add whitelist for ``string.Formatter`` (Joseph Bylund, #183). + + + 1.2 (2019-11-22) + ---------------- + * Fix tests for Python 3.8 (#166). + * Use new ``Constant`` AST node under Python 3.8+ (#175). + * Add test for f-strings (#177). + * Add whitelist for ``logging`` module. + + + 1.1 (2019-09-23) + ---------------- + * Add ``sys.excepthook`` to ``sys`` whitelist. + * Add whitelist for ``ctypes`` module. + * Check that type annotations are parsed and type comments are ignored (thanks @kx-chen). + * Support checking files with BOM under Python 2.7 (#170). + + 1.0 (2018-10-23) ---------------- * Add ``--ignore-decorators`` flag (thanks @RJ722). @@ -462,9 +494,10 @@ Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Quality Assurance +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vulture-1.0/vulture.egg-info/SOURCES.txt new/vulture-1.3/vulture.egg-info/SOURCES.txt --- old/vulture-1.0/vulture.egg-info/SOURCES.txt 2018-10-23 18:29:40.000000000 +0200 +++ new/vulture-1.3/vulture.egg-info/SOURCES.txt 2020-02-03 12:50:15.000000000 +0100 @@ -4,14 +4,13 @@ NEWS.rst README.rst TODO.rst -builtins.txt -multi.txt setup.cfg setup.py tox.ini tests/__init__.py tests/test_conditions.py tests/test_confidence.py +tests/test_encoding.py tests/test_errors.py tests/test_format_strings.py tests/test_ignore.py @@ -38,6 +37,9 @@ vulture/whitelists/argparse_whitelist.py vulture/whitelists/ast_whitelist.py vulture/whitelists/collections_whitelist.py +vulture/whitelists/ctypes_whitelist.py +vulture/whitelists/logging_whitelist.py +vulture/whitelists/string_whitelist.py vulture/whitelists/sys_whitelist.py vulture/whitelists/threading_whitelist.py vulture/whitelists/unittest_whitelist.py
