Hello community,

here is the log from the commit of package python-autoflake for 
openSUSE:Factory checked in at 2020-09-07 21:35:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-autoflake (Old)
 and      /work/SRC/openSUSE:Factory/.python-autoflake.new.3399 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-autoflake"

Mon Sep  7 21:35:05 2020 rev:6 rq:832643 version:1.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-autoflake/python-autoflake.changes        
2019-09-16 10:50:36.195170672 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-autoflake.new.3399/python-autoflake.changes  
    2020-09-07 21:35:31.745385570 +0200
@@ -1,0 +2,7 @@
+Mon Aug 31 04:16:47 UTC 2020 - Steve Kowalik <[email protected]>
+
+- Update to 1.4:
+  * No upstream changelog
+- Switch from setup.py test to pytest 
+
+-------------------------------------------------------------------

Old:
----
  autoflake-1.3.1.tar.gz

New:
----
  autoflake-1.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-autoflake.spec ++++++
--- /var/tmp/diff_new_pack.I9S9Jj/_old  2020-09-07 21:35:33.429386350 +0200
+++ /var/tmp/diff_new_pack.I9S9Jj/_new  2020-09-07 21:35:33.429386350 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-autoflake
 #
-# 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,13 +18,13 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-autoflake
-Version:        1.3.1
+Version:        1.4
 Release:        0
 Summary:        Program to removes unused Python imports and variables
 License:        MIT
-Group:          Development/Languages/Python
 URL:            https://github.com/myint/autoflake
 Source:         
https://files.pythonhosted.org/packages/source/a/autoflake/autoflake-%{version}.tar.gz
+BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
@@ -63,7 +63,7 @@
 
 %check
 export $LANG=en_US.UTF-8
-%python_exec setup.py test
+%pytest
 
 %post
 %python_install_alternative autoflake

++++++ autoflake-1.3.1.tar.gz -> autoflake-1.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/autoflake-1.3.1/AUTHORS.rst 
new/autoflake-1.4/AUTHORS.rst
--- old/autoflake-1.3.1/AUTHORS.rst     2019-06-02 17:57:42.000000000 +0200
+++ new/autoflake-1.4/AUTHORS.rst       2019-08-24 19:44:29.000000000 +0200
@@ -13,3 +13,4 @@
 - Nobuhiro Kasai (https://github.com/sh4869)
 - James Curtin (https://github.com/jamescurtin)
 - Sargun Dhillon (https://github.com/sargun)
+- Anton Ogorodnikov (https://github.com/arxell)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/autoflake-1.3.1/PKG-INFO new/autoflake-1.4/PKG-INFO
--- old/autoflake-1.3.1/PKG-INFO        2019-08-04 17:14:15.000000000 +0200
+++ new/autoflake-1.4/PKG-INFO  2020-08-23 03:20:53.520906700 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: autoflake
-Version: 1.3.1
+Version: 1.4
 Summary: Removes unused imports and unused variables
 Home-page: https://github.com/myint/autoflake
 Author: Steven Myint
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/autoflake-1.3.1/autoflake.egg-info/PKG-INFO 
new/autoflake-1.4/autoflake.egg-info/PKG-INFO
--- old/autoflake-1.3.1/autoflake.egg-info/PKG-INFO     2019-08-04 
17:14:15.000000000 +0200
+++ new/autoflake-1.4/autoflake.egg-info/PKG-INFO       2020-08-23 
03:20:53.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: autoflake
-Version: 1.3.1
+Version: 1.4
 Summary: Removes unused imports and unused variables
 Home-page: https://github.com/myint/autoflake
 Author: Steven Myint
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/autoflake-1.3.1/autoflake.py 
new/autoflake-1.4/autoflake.py
--- old/autoflake-1.3.1/autoflake.py    2019-08-04 17:13:52.000000000 +0200
+++ new/autoflake-1.4/autoflake.py      2020-08-23 03:19:47.000000000 +0200
@@ -32,9 +32,11 @@
 import distutils.sysconfig
 import fnmatch
 import io
+import logging
 import os
 import re
 import signal
+import string
 import sys
 import tokenize
 
@@ -43,9 +45,12 @@
 import pyflakes.reporter
 
 
-__version__ = '1.3.1'
+__version__ = '1.4'
 
 
+_LOGGER = logging.getLogger('autoflake')
+_LOGGER.propagate = False
+
 ATOMS = frozenset([tokenize.NAME, tokenize.NUMBER, tokenize.STRING])
 
 EXCEPT_REGEX = re.compile(r'^\s*except [\s,()\w]+ as \w+:$')
@@ -69,17 +74,18 @@
 def standard_paths():
     """Yield paths to standard modules."""
     for is_plat_spec in [True, False]:
+
+        # Yield lib paths.
         path = distutils.sysconfig.get_python_lib(standard_lib=True,
                                                   plat_specific=is_plat_spec)
-
         for name in os.listdir(path):
             yield name
 
-        try:
-            for name in os.listdir(os.path.join(path, 'lib-dynload')):
+        # Yield lib-dynload paths.
+        dynload_path = os.path.join(path, 'lib-dynload')
+        if os.path.isdir(dynload_path):
+            for name in os.listdir(dynload_path):
                 yield name
-        except OSError:  # pragma: no cover
-            pass
 
 
 def standard_package_names():
@@ -88,7 +94,7 @@
         if name.startswith('_') or '-' in name:
             continue
 
-        if '.' in name and name.rsplit('.')[-1] not in ['so', 'py', 'pyc']:
+        if '.' in name and not name.endswith(("so", "py", "pyc")):
             continue
 
         yield name.split('.')[0]
@@ -250,10 +256,6 @@
         if symbol in line:
             return True
 
-    # Ignore doctests.
-    if line.lstrip().startswith('>'):
-        return True
-
     return multiline_statement(line, previous_line)
 
 
@@ -271,6 +273,182 @@
         return True
 
 
+class PendingFix(object):
+    """Allows a rewrite operation to span multiple lines.
+
+    In the main rewrite loop, every time a helper function returns a
+    ``PendingFix`` object instead of a string, this object will be called
+    with the following line.
+    """
+
+    def __init__(self, line):
+        """Analyse and store the first line."""
+        self.accumulator = collections.deque([line])
+
+    def __call__(self, line):
+        """Process line considering the accumulator.
+
+        Return self to keep processing the following lines or a string
+        with the final result of all the lines processed at once.
+        """
+        raise NotImplementedError("Abstract method needs to be overwritten")
+
+
+def _valid_char_in_line(char, line):
+    """Return True if a char appears in the line and is not commented."""
+    comment_index = line.find('#')
+    char_index = line.find(char)
+    valid_char_in_line = (
+        char_index >= 0 and
+        (comment_index > char_index or comment_index < 0)
+    )
+    return valid_char_in_line
+
+
+def _top_module(module_name):
+    """Return the name of the top level module in the hierarchy."""
+    if module_name[0] == '.':
+        return '%LOCAL_MODULE%'
+    return module_name.split('.')[0]
+
+
+def _modules_to_remove(unused_modules, safe_to_remove=SAFE_IMPORTS):
+    """Discard unused modules that are not safe to remove from the list."""
+    return [x for x in unused_modules if _top_module(x) in safe_to_remove]
+
+
+def _segment_module(segment):
+    """Extract the module identifier inside the segment.
+
+    It might be the case the segment does not have a module (e.g. is composed
+    just by a parenthesis or line continuation and whitespace). In this
+    scenario we just keep the segment... These characters are not valid in
+    identifiers, so they will never be contained in the list of unused modules
+    anyway.
+    """
+    return segment.strip(string.whitespace + ',\\()') or segment
+
+
+class FilterMultilineImport(PendingFix):
+    """Remove unused imports from multiline import statements.
+
+    This class handles both the cases: "from imports" and "direct imports".
+
+    Some limitations exist (e.g. imports with comments, lines joined by ``;``,
+    etc). In these cases, the statement is left unchanged to avoid problems.
+    """
+
+    IMPORT_RE = re.compile(r'\bimport\b\s*')
+    INDENTATION_RE = re.compile(r'^\s*')
+    BASE_RE = re.compile(r'\bfrom\s+([^ ]+)')
+    SEGMENT_RE = re.compile(
+        r'([^,\s]+(?:[\s\\]+as[\s\\]+[^,\s]+)?[,\s\\)]*)', re.M)
+    # ^ module + comma + following space (including new line and continuation)
+    IDENTIFIER_RE = re.compile(r'[^,\s]+')
+
+    def __init__(self, line, unused_module=(), remove_all_unused_imports=False,
+                 safe_to_remove=SAFE_IMPORTS, previous_line=''):
+        """Receive the same parameters as ``filter_unused_import``."""
+        self.remove = unused_module
+        self.parenthesized = '(' in line
+        self.from_, imports = self.IMPORT_RE.split(line, maxsplit=1)
+        match = self.BASE_RE.search(self.from_)
+        self.base = match.group(1) if match else None
+        self.give_up = False
+
+        if not remove_all_unused_imports:
+            if self.base and _top_module(self.base) not in safe_to_remove:
+                self.give_up = True
+            else:
+                self.remove = _modules_to_remove(self.remove, safe_to_remove)
+
+        if '\\' in previous_line:
+            # Ignore tricky things like "try: \<new line> import" ...
+            self.give_up = True
+
+        self.analyze(line)
+
+        PendingFix.__init__(self, imports)
+
+    def is_over(self, line=None):
+        """Return True if the multiline import statement is over."""
+        line = line or self.accumulator[-1]
+
+        if self.parenthesized:
+            return _valid_char_in_line(')', line)
+
+        return not _valid_char_in_line('\\', line)
+
+    def analyze(self, line):
+        """Decide if the statement will be fixed or left unchanged."""
+        if any(ch in line for ch in ';:#'):
+            self.give_up = True
+
+    def fix(self, accumulated):
+        """Given a collection of accumulated lines, fix the entire import."""
+        old_imports = ''.join(accumulated)
+        ending = get_line_ending(old_imports)
+        # Split imports into segments that contain the module name +
+        # comma + whitespace and eventual <newline> \ ( ) chars
+        segments = [x for x in self.SEGMENT_RE.findall(old_imports) if x]
+        modules = [_segment_module(x) for x in segments]
+        keep = _filter_imports(modules, self.base, self.remove)
+
+        # Short-circuit if no import was discarded
+        if len(keep) == len(segments):
+            return self.from_ + 'import ' + ''.join(accumulated)
+
+        fixed = ''
+        if keep:
+            # Since it is very difficult to deal with all the line breaks and
+            # continuations, let's use the code layout that already exists and
+            # just replace the module identifiers inside the first N-1 segments
+            # + the last segment
+            templates = list(zip(modules, segments))
+            templates = templates[:len(keep)-1] + templates[-1:]
+            # It is important to keep the last segment, since it might contain
+            # important chars like `)`
+            fixed = ''.join(
+                template.replace(module, keep[i])
+                for i, (module, template) in enumerate(templates)
+            )
+
+            # Fix the edge case: inline parenthesis + just one surviving import
+            if self.parenthesized and any(ch not in fixed for ch in '()'):
+                fixed = fixed.strip(string.whitespace + '()') + ending
+
+        # Replace empty imports with a "pass" statement
+        empty = len(fixed.strip(string.whitespace + '\\(),')) < 1
+        if empty:
+            indentation = self.INDENTATION_RE.search(self.from_).group(0)
+            return indentation + 'pass' + ending
+
+        return self.from_ + 'import ' + fixed
+
+    def __call__(self, line=None):
+        """Accumulate all the lines in the import and then trigger the fix."""
+        if line:
+            self.accumulator.append(line)
+            self.analyze(line)
+        if not self.is_over(line):
+            return self
+        if self.give_up:
+            return self.from_ + 'import ' + ''.join(self.accumulator)
+
+        return self.fix(self.accumulator)
+
+
+def _filter_imports(imports, parent=None, unused_module=()):
+    # We compare full module name (``a.module`` not `module`) to
+    # guarantee the exact same module as detected from pyflakes.
+    sep = '' if parent and parent[-1] == '.' else '.'
+
+    def full_name(name):
+        return name if parent is None else parent + sep + name
+
+    return [x for x in imports if full_name(x) not in unused_module]
+
+
 def filter_from_import(line, unused_module):
     """Parse and filter ``from something import a, b, c``.
 
@@ -282,15 +460,8 @@
     base_module = re.search(pattern=r'\bfrom\s+([^ ]+)',
                             string=indentation).group(1)
 
-    # Create an imported module list with base module name
-    # ex ``from a import b, c as d`` -> ``['a.b', 'a.c as d']``
-    imports = re.split(pattern=r',', string=imports.strip())
-    imports = [base_module + '.' + x.strip() for x in imports]
-
-    # We compare full module name (``a.module`` not `module`) to
-    # guarantee the exact same module as detected from pyflakes.
-    filtered_imports = [x.replace(base_module + '.', '')
-                        for x in imports if x not in unused_module]
+    imports = re.split(pattern=r'\s*,\s*', string=imports.strip())
+    filtered_imports = _filter_imports(imports, base_module, unused_module)
 
     # All of the import in this statement is unused
     if not filtered_imports:
@@ -387,26 +558,32 @@
 
     sio = io.StringIO(source)
     previous_line = ''
+    result = None
     for line_number, line in enumerate(sio.readlines(), start=1):
-        if '#' in line:
-            yield line
+        if isinstance(result, PendingFix):
+            result = result(line)
+        elif '#' in line:
+            result = line
         elif line_number in marked_import_line_numbers:
-            yield filter_unused_import(
+            result = filter_unused_import(
                 line,
                 unused_module=marked_unused_module[line_number],
                 remove_all_unused_imports=remove_all_unused_imports,
                 imports=imports,
                 previous_line=previous_line)
         elif line_number in marked_variable_line_numbers:
-            yield filter_unused_variable(line)
+            result = filter_unused_variable(line)
         elif line_number in marked_key_line_numbers:
-            yield filter_duplicate_key(line, line_messages[line_number],
-                                       line_number, marked_key_line_numbers,
-                                       source)
+            result = filter_duplicate_key(line, line_messages[line_number],
+                                          line_number, marked_key_line_numbers,
+                                          source)
         elif line_number in marked_star_import_line_numbers:
-            yield filter_star_import(line, undefined_names)
+            result = filter_star_import(line, undefined_names)
         else:
-            yield line
+            result = line
+
+        if not isinstance(result, PendingFix):
+            yield result
 
         previous_line = line
 
@@ -428,9 +605,16 @@
 def filter_unused_import(line, unused_module, remove_all_unused_imports,
                          imports, previous_line=''):
     """Return line if used, otherwise return None."""
-    if multiline_import(line, previous_line):
+    # Ignore doctests.
+    if line.lstrip().startswith('>'):
         return line
 
+    if multiline_import(line, previous_line):
+        filt = FilterMultilineImport(line, unused_module,
+                                     remove_all_unused_imports,
+                                     imports, previous_line)
+        return filt()
+
     is_from_import = line.lstrip().startswith('from')
 
     if ',' in line and not is_from_import:
@@ -651,12 +835,15 @@
 
     if original_source != filtered_source:
         if args.check:
-            standard_out.write('Unused imports/variables detected.')
+            standard_out.write(
+                '{filename}: Unused imports/variables detected'.format(
+                    filename=filename))
             sys.exit(1)
         if args.in_place:
             with open_with_encoding(filename, mode='w',
                                     encoding=encoding) as output_file:
                 output_file.write(filtered_source)
+            _LOGGER.info('Fixed %s', filename)
         else:
             diff = get_diff_text(
                 io.StringIO(original_source).readlines(),
@@ -666,6 +853,8 @@
     else:
         if args.check:
             standard_out.write('No issues detected!\n')
+        else:
+            _LOGGER.debug('Clean %s: nothing to fix', filename)
 
 
 def open_with_encoding(filename, encoding, mode='r',
@@ -769,6 +958,7 @@
 def match_file(filename, exclude):
     """Return True if file is okay for modifying/recursing."""
     if is_exclude_file(filename, exclude):
+        _LOGGER.debug('Skipped %s: matched to exclude pattern', filename)
         return False
 
     if not os.path.isdir(filename) and not is_python_file(filename):
@@ -792,6 +982,8 @@
         else:
             if not is_exclude_file(name, exclude):
                 yield name
+            else:
+                _LOGGER.debug('Skipped %s: matched to exclude pattern', name)
 
 
 def _main(argv, standard_out, standard_error):
@@ -832,13 +1024,26 @@
                         help='remove unused variables')
     parser.add_argument('--version', action='version',
                         version='%(prog)s ' + __version__)
+    parser.add_argument('-v', '--verbose', action='count', dest='verbosity',
+                        default=0, help='print more verbose logs (you can '
+                                        'repeat `-v` to make it more verbose)')
     parser.add_argument('files', nargs='+', help='files to format')
 
     args = parser.parse_args(argv[1:])
 
+    if standard_error is None:
+        _LOGGER.addHandler(logging.NullHandler())
+    else:
+        _LOGGER.addHandler(logging.StreamHandler(standard_error))
+        loglevels = [logging.WARNING, logging.INFO, logging.DEBUG]
+        try:
+            loglevel = loglevels[args.verbosity]
+        except IndexError:  # Too much -v
+            loglevel = loglevels[-1]
+        _LOGGER.setLevel(loglevel)
+
     if args.remove_all_unused_imports and args.imports:
-        print('Using both --remove-all and --imports is redundant',
-              file=standard_error)
+        _LOGGER.error('Using both --remove-all and --imports is redundant')
         return 1
 
     if args.exclude:
@@ -852,7 +1057,7 @@
         try:
             fix_file(name, args=args, standard_out=standard_out)
         except IOError as exception:
-            print(unicode(exception), file=standard_error)
+            _LOGGER.error(unicode(exception))
             failure = True
 
     return 1 if failure else 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/autoflake-1.3.1/test_autoflake.py 
new/autoflake-1.4/test_autoflake.py
--- old/autoflake-1.3.1/test_autoflake.py       2019-06-02 17:53:59.000000000 
+0200
+++ new/autoflake-1.4/test_autoflake.py 2020-08-23 03:19:31.000000000 +0200
@@ -6,6 +6,7 @@
 from __future__ import unicode_literals
 
 import contextlib
+import functools
 import io
 import os
 import re
@@ -422,13 +423,12 @@
                 unused_module=['foo.abc', 'foo.subprocess',
                                'foo.math']))
 
-    def test_filter_code_should_ignore_multiline_imports(self):
+    def test_filter_code_multiline_imports(self):
         self.assertEqual(
             r"""\
 import os
 pass
-import os, \
-    math, subprocess
+import os
 os.foo()
 """,
             ''.join(autoflake.filter_code(r"""\
@@ -439,6 +439,39 @@
 os.foo()
 """)))
 
+    def test_filter_code_multiline_from_imports(self):
+        self.assertEqual(
+            r"""\
+import os
+pass
+from os.path import (
+    join,
+)
+join('a', 'b')
+pass
+os.foo()
+from os.path import \
+    isdir
+isdir('42')
+""",
+            ''.join(autoflake.filter_code(r"""\
+import os
+import re
+from os.path import (
+    exists,
+    join,
+)
+join('a', 'b')
+from os.path import \
+    abspath, basename, \
+    commonpath
+os.foo()
+from os.path import \
+    isfile \
+    , isdir
+isdir('42')
+""")))
+
     def test_filter_code_should_ignore_semicolons(self):
         self.assertEqual(
             r"""\
@@ -1624,6 +1657,473 @@
                 process.communicate()[1].decode())
 
 
+class MultilineFromImportTests(unittest.TestCase):
+    def test_is_over(self):
+        filt = autoflake.FilterMultilineImport('from . import (\n')
+        self.assertTrue(filt.is_over('module)\n'))
+        self.assertTrue(filt.is_over('  )\n'))
+        self.assertTrue(filt.is_over('  )  # comment\n'))
+        self.assertTrue(filt.is_over('from module import (a, b)\n'))
+        self.assertFalse(filt.is_over('#  )'))
+        self.assertFalse(filt.is_over('module\n'))
+        self.assertFalse(filt.is_over('module, \\\n'))
+        self.assertFalse(filt.is_over('\n'))
+
+        filt = autoflake.FilterMultilineImport('from . import module, \\\n')
+        self.assertTrue(filt.is_over('module\n'))
+        self.assertTrue(filt.is_over('\n'))
+        self.assertTrue(filt.is_over('m1, m2  # comment with \\\n'))
+        self.assertFalse(filt.is_over('m1, m2 \\\n'))
+        self.assertFalse(filt.is_over('m1, m2 \\  #\n'))
+        self.assertFalse(filt.is_over('m1, m2 \\  # comment with \\\n'))
+        self.assertFalse(filt.is_over('\\\n'))
+
+        # "Multiline" imports that are not really multiline
+        filt = autoflake.FilterMultilineImport('import os; '
+                                               'import math, subprocess')
+        self.assertTrue(filt.is_over())
+
+    unused = ()
+
+    def assert_fix(self, lines, result, remove_all=True):
+        fixer = autoflake.FilterMultilineImport(
+            lines[0],
+            remove_all_unused_imports=remove_all,
+            unused_module=self.unused
+        )
+        fixed = functools.reduce(lambda acc, x: acc(x), lines[1:], fixer())
+        self.assertEqual(fixed, result)
+
+    def test_fix(self):
+        self.unused = ['third_party.lib' + str(x) for x in (1, 3, 4)]
+
+        # Example m0 (isort)
+        self.assert_fix([
+            'from third_party import (lib1, lib2, lib3,\n',
+            '                         lib4, lib5, lib6)\n'
+        ],
+            'from third_party import (lib2, lib5, lib6)\n'
+        )
+
+        # Example m1(isort)
+        self.assert_fix([
+            'from third_party import (lib1,\n',
+            '                         lib2,\n',
+            '                         lib3,\n',
+            '                         lib4,\n',
+            '                         lib5,\n',
+            '                         lib6)\n'
+        ],
+            'from third_party import (lib2,\n'
+            '                         lib5,\n'
+            '                         lib6)\n'
+        )
+
+        # Variation m1(isort)
+        self.assert_fix([
+            'from third_party import (lib1\n',
+            '                        ,lib2\n',
+            '                        ,lib3\n',
+            '                        ,lib4\n',
+            '                        ,lib5\n',
+            '                        ,lib6)\n'
+        ],
+            'from third_party import (lib2\n'
+            '                        ,lib5\n'
+            '                        ,lib6)\n'
+        )
+
+        # Example m2 (isort)
+        self.assert_fix([
+            'from third_party import \\\n',
+            '    lib1, lib2, lib3, \\\n',
+            '    lib4, lib5, lib6\n'
+        ],
+            'from third_party import \\\n'
+            '    lib2, lib5, lib6\n'
+        )
+
+        # Example m3 (isort)
+        self.assert_fix([
+            'from third_party import (\n',
+            '    lib1,\n',
+            '    lib2,\n',
+            '    lib3,\n',
+            '    lib4,\n',
+            '    lib5\n',
+            ')\n'
+        ],
+            'from third_party import (\n'
+            '    lib2,\n'
+            '    lib5\n'
+            ')\n'
+        )
+
+        # Example m4 (isort)
+        self.assert_fix([
+            'from third_party import (\n',
+            '    lib1, lib2, lib3, lib4,\n',
+            '    lib5, lib6)\n'
+        ],
+            'from third_party import (\n'
+            '    lib2, lib5, lib6)\n'
+        )
+
+        # Example m5 (isort)
+        self.assert_fix([
+            'from third_party import (\n',
+            '    lib1, lib2, lib3, lib4,\n',
+            '    lib5, lib6\n',
+            ')\n'
+        ],
+            'from third_party import (\n'
+            '    lib2, lib5, lib6\n'
+            ')\n'
+        )
+
+        # Some Deviations
+        self.assert_fix([
+            'from third_party import (\n',
+            '    lib1\\\n',  # only unused + line continuation
+            '    ,lib2, \n',
+            '    libA\n',  # used import with no commas
+            '    ,lib3, \n',  # leading and trailing commas with unused import
+            '    libB, \n',
+            '    \\\n',  # empty line with continuation
+            '    lib4,\n',  # unused import with comment
+            ')\n'
+        ],
+            'from third_party import (\n'
+            '    lib2\\\n'
+            '    ,libA, \n'
+            '    libB,\n'
+            ')\n',
+        )
+
+        self.assert_fix([
+            'from third_party import (\n',
+            '    lib1\n',
+            ',\n',
+            '    lib2\n',
+            ',\n',
+            '    lib3\n',
+            ',\n',
+            '    lib4\n',
+            ',\n',
+            '    lib5\n',
+            ')\n'
+        ],
+            'from third_party import (\n'
+            '    lib2\n'
+            ',\n'
+            '    lib5\n'
+            ')\n'
+        )
+
+        self.assert_fix([
+            'from third_party import (\n',
+            '    lib1 \\\n',
+            ', \\\n',
+            '    lib2 \\\n',
+            ',\\\n',
+            '    lib3\n',
+            ',\n',
+            '    lib4\n',
+            ',\n',
+            '    lib5 \\\n',
+            ')\n'
+        ],
+            'from third_party import (\n'
+            '    lib2 \\\n'
+            ', \\\n'
+            '    lib5 \\\n'
+            ')\n'
+        )
+
+    def test_indentation(self):
+        # Some weird indentation examples
+        self.unused = ['third_party.lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            '    from third_party import (\n',
+            '            lib1, lib2, lib3, lib4,\n',
+            '    lib5, lib6\n',
+            ')\n'
+        ],
+            '    from third_party import (\n'
+            '            lib2, lib5, lib6\n'
+            ')\n'
+        )
+        self.assert_fix([
+            '\tfrom third_party import \\\n',
+            '\t\tlib1, lib2, lib3, \\\n',
+            '\t\tlib4, lib5, lib6\n'
+        ],
+            '\tfrom third_party import \\\n'
+            '\t\tlib2, lib5, lib6\n'
+        )
+
+    def test_fix_relative(self):
+        # Example m0 (isort)
+        self.unused = ['.lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'from . import (lib1, lib2, lib3,\n',
+            '               lib4, lib5, lib6)\n'
+        ],
+            'from . import (lib2, lib5, lib6)\n'
+        )
+
+        # Example m1(isort)
+        self.unused = ['..lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'from .. import (lib1,\n',
+            '                lib2,\n',
+            '                lib3,\n',
+            '                lib4,\n',
+            '                lib5,\n',
+            '                lib6)\n'
+        ],
+            'from .. import (lib2,\n'
+            '                lib5,\n'
+            '                lib6)\n'
+        )
+
+        # Example m2 (isort)
+        self.unused = ['...lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'from ... import \\\n',
+            '    lib1, lib2, lib3, \\\n',
+            '    lib4, lib5, lib6\n'
+        ],
+            'from ... import \\\n'
+            '    lib2, lib5, lib6\n'
+        )
+
+        # Example m3 (isort)
+        self.unused = ['.parent.lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'from .parent import (\n',
+            '    lib1,\n',
+            '    lib2,\n',
+            '    lib3,\n',
+            '    lib4,\n',
+            '    lib5\n',
+            ')\n'
+        ],
+            'from .parent import (\n'
+            '    lib2,\n'
+            '    lib5\n'
+            ')\n'
+        )
+
+    def test_fix_without_from(self):
+        self.unused = ['lib' + str(x) for x in (1, 3, 4)]
+
+        # Multiline but not "from"
+        self.assert_fix([
+            'import \\\n',
+            '    lib1, lib2, lib3 \\\n',
+            '    ,lib4, lib5, lib6\n'
+        ],
+            'import \\\n'
+            '    lib2, lib5, lib6\n'
+        )
+        self.assert_fix([
+            'import lib1, lib2, lib3, \\\n',
+            '       lib4, lib5, lib6\n'
+        ],
+            'import lib2, lib5, lib6\n'
+        )
+
+        # Problematic example without "from"
+        self.assert_fix([
+            'import \\\n',
+            '    lib1,\\\n',
+            '    lib2, \\\n',
+            '    libA\\\n',  # used import with no commas
+            '    ,lib3, \\\n',  # leading and trailing commas with unused
+            '    libB, \\\n',
+            '    \\  \n',  # empty line with continuation
+            '    lib4\\\n',  # unused import with comment
+            '\n'
+        ],
+            'import \\\n'
+            '    lib2,\\\n'
+            '    libA, \\\n'
+            '    libB\\\n'
+            '\n'
+        )
+
+        self.unused = ['lib{}.x.y.z'.format(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'import \\\n',
+            '    lib1.x.y.z \\',
+            '    , \\\n',
+            '    lib2.x.y.z \\\n',
+            '    , \\\n',
+            '    lib3.x.y.z \\\n',
+            '    , \\\n',
+            '    lib4.x.y.z \\\n',
+            '    , \\\n',
+            '    lib5.x.y.z\n'
+        ],
+            'import \\\n'
+            '    lib2.x.y.z \\'
+            '    , \\\n'
+            '    lib5.x.y.z\n'
+        )
+
+    def test_give_up(self):
+        # Semicolon
+        self.unused = ['lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'import \\\n',
+            '    lib1, lib2, lib3, \\\n',
+            '    lib4, lib5; import lib6\n'
+        ],
+            'import \\\n'
+            '    lib1, lib2, lib3, \\\n'
+            '    lib4, lib5; import lib6\n'
+        )
+        # Comments
+        self.unused = ['.lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'from . import ( # comment\n',
+            '    lib1,\\\n',  # only unused + line continuation
+            '    lib2, \n',
+            '    libA\n',  # used import with no commas
+            '    ,lib3, \n',  # leading and trailing commas with unused import
+            '    libB, \n',
+            '    \\  \n',  # empty line with continuation
+            '    lib4,  # noqa \n',  # unused import with comment
+            ') ; import sys\n'
+        ],
+            'from . import ( # comment\n'
+            '    lib1,\\\n'
+            '    lib2, \n'
+            '    libA\n'
+            '    ,lib3, \n'
+            '    libB, \n'
+            '    \\  \n'
+            '    lib4,  # noqa \n'
+            ') ; import sys\n'
+        )
+
+    def test_just_one_import_used(self):
+        self.unused = ['lib2']
+        self.assert_fix([
+            'import \\\n',
+            '    lib1\n'
+        ],
+            'import \\\n'
+            '    lib1\n'
+        )
+        self.assert_fix([
+            'import \\\n',
+            '    lib2\n'
+        ],
+            'pass\n'
+        )
+        # Example from issue #8
+        self.unused = ['re.subn']
+        self.assert_fix([
+            '\tfrom re import (subn)\n',
+        ],
+            '\tpass\n',
+        )
+
+    def test_just_one_import_left(self):
+        # Examples from issue #8
+        self.unused = ['math.sqrt']
+        self.assert_fix([
+            'from math import (\n',
+            '        sqrt,\n',
+            '        log\n',
+            '    )\n'
+        ],
+            'from math import (\n'
+            '        log\n'
+            '    )\n'
+        )
+        self.unused = ['module.b']
+        self.assert_fix([
+            'from module import (a, b)\n',
+        ],
+            'from module import a\n',
+        )
+        self.assert_fix([
+            'from module import (a,\n',
+            '                    b)\n',
+        ],
+            'from module import a\n',
+        )
+        self.unused = []
+        self.assert_fix([
+            'from re import (subn)\n',
+        ],
+            'from re import (subn)\n',
+        )
+
+    def test_no_empty_imports(self):
+        self.unused = ['lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'import \\\n',
+            '    lib1, lib3, \\\n',
+            '    lib4 \n'
+        ],
+            'pass \n'
+        )
+
+        # Indented parenthesized block
+        self.unused = ['.parent.lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            '\t\tfrom .parent import (\n',
+            '    lib1,\n',
+            '    lib3,\n',
+            '    lib4,\n',
+            ')\n'
+        ],
+            '\t\tpass\n'
+        )
+
+    def test_without_remove_all(self):
+        self.unused = ['lib' + str(x) for x in (1, 3, 4)]
+        self.assert_fix([
+            'import \\\n',
+            '    lib1,\\\n',
+            '    lib3,\\\n',
+            '    lib4\n',
+        ],
+            'import \\\n'
+            '    lib1,\\\n'
+            '    lib3,\\\n'
+            '    lib4\n',
+            remove_all=False
+        )
+
+        self.unused += ['os.path.' + x for x in ('dirname', 'isdir', 'join')]
+        self.assert_fix([
+            'from os.path import (\n',
+            '    dirname,\n',
+            '    isdir,\n',
+            '    join,\n',
+            ')\n'
+        ],
+            'pass\n',
+            remove_all=False
+        )
+        self.assert_fix([
+            'import \\\n',
+            '    os.path.dirname, \\\n',
+            '    lib1, \\\n',
+            '    lib3\n',
+        ],
+            'import \\\n'
+            '    lib1, \\\n'
+            '    lib3\n',
+            remove_all=False
+        )
+
+
 @contextlib.contextmanager
 def temporary_file(contents, directory='.', suffix='.py', prefix=''):
     """Write contents to temporary file and yield it."""


Reply via email to