Skip to content

Commit ad4eff0

Browse files
committed
Add populate_all feature
Closes #16
1 parent 459f5f5 commit ad4eff0

File tree

3 files changed

+100
-5
lines changed

3 files changed

+100
-5
lines changed

README.rst

+2
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ Below is the full listing of options::
112112
this only triggers if there is only one star import in
113113
the file; this is skipped if there are any uses of
114114
`__all__` or `del` in the file
115+
--populate-all populate `__all__` with unused import found in the
116+
code.
115117
--remove-all-unused-imports
116118
remove all unused imports (not just those from the
117119
standard library)

autoflake.py

+55-5
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,8 @@ def break_up_import(line):
294294
def filter_code(source, additional_imports=None,
295295
expand_star_imports=False,
296296
remove_all_unused_imports=False,
297-
remove_unused_variables=False):
297+
remove_unused_variables=False,
298+
populate_all=False):
298299
"""Yield code with unused imports removed."""
299300
imports = SAFE_IMPORTS
300301
if additional_imports:
@@ -335,6 +336,10 @@ def filter_code(source, additional_imports=None,
335336
else:
336337
marked_variable_line_numbers = frozenset()
337338

339+
if populate_all:
340+
marked_import_line_numbers = frozenset()
341+
source = populate_all_with_modules(source, marked_unused_module)
342+
338343
sio = io.StringIO(source)
339344
previous_line = ''
340345
for line_number, line in enumerate(sio.readlines(), start=1):
@@ -478,6 +483,44 @@ def filter_useless_pass(source):
478483
yield line
479484

480485

486+
def populate_all_with_modules(source, marked_unused_module):
487+
all_syntax = re.search('^__all__(.)+\]', source, flags=re.MULTILINE)
488+
if all_syntax:
489+
# If there are existing `__all__`, parse it and append to it
490+
insert_position = all_syntax.span()[0]
491+
end_position = all_syntax.span()[1]
492+
all_modules = all_syntax.group().split('=')[1].strip()
493+
all_modules = ast.literal_eval(all_modules)
494+
else:
495+
# If no existing `__all__`, always append in EOF
496+
insert_position = len(source)
497+
end_position = -1
498+
all_modules = []
499+
500+
for modules in marked_unused_module.values():
501+
# Get the imported name, `a.b.Foo` -> Foo
502+
all_modules += [get_imported_name(name) for name in modules]
503+
504+
new_all_syntax = '__all__ = ' + str(all_modules)
505+
source = source[:insert_position] + new_all_syntax + source[end_position:]
506+
return source
507+
508+
509+
def get_imported_name(module):
510+
"""
511+
Return only imported name from pyflakes full module path
512+
513+
Example:
514+
- `a.b.Foo` -> `Foo`
515+
- `a as b` -> b
516+
"""
517+
if '.' in module:
518+
return str(module.split('.')[-1])
519+
elif ' as ' in module:
520+
return str(module.split(' as ')[-1])
521+
# str() to force python 2 to not use unicode
522+
return str(module)
523+
481524
def get_indentation(line):
482525
"""Return leading whitespace."""
483526
if line.strip():
@@ -497,7 +540,8 @@ def get_line_ending(line):
497540

498541

499542
def fix_code(source, additional_imports=None, expand_star_imports=False,
500-
remove_all_unused_imports=False, remove_unused_variables=False):
543+
remove_all_unused_imports=False, remove_unused_variables=False,
544+
populate_all=False):
501545
"""Return code with all filtering run on it."""
502546
if not source:
503547
return source
@@ -515,9 +559,10 @@ def fix_code(source, additional_imports=None, expand_star_imports=False,
515559
additional_imports=additional_imports,
516560
expand_star_imports=expand_star_imports,
517561
remove_all_unused_imports=remove_all_unused_imports,
518-
remove_unused_variables=remove_unused_variables))))
562+
remove_unused_variables=remove_unused_variables,
563+
populate_all=populate_all))))
519564

520-
if filtered_source == source:
565+
if filtered_source == source or populate_all:
521566
break
522567
source = filtered_source
523568

@@ -537,7 +582,9 @@ def fix_file(filename, args, standard_out):
537582
additional_imports=args.imports.split(',') if args.imports else None,
538583
expand_star_imports=args.expand_star_imports,
539584
remove_all_unused_imports=args.remove_all_unused_imports,
540-
remove_unused_variables=args.remove_unused_variables)
585+
remove_unused_variables=args.remove_unused_variables,
586+
populate_all=args.populate_modules_under_all,
587+
)
541588

542589
if original_source != filtered_source:
543590
if args.in_place:
@@ -692,6 +739,9 @@ def _main(argv, standard_out, standard_error):
692739
'one star import in the file; this is skipped if '
693740
'there are any uses of `__all__` or `del` in the '
694741
'file')
742+
parser.add_argument('--populate-modules-under-all', action='store_true',
743+
help='populate `__all__` with unused import found in '
744+
'the code.')
695745
parser.add_argument('--remove-all-unused-imports', action='store_true',
696746
help='remove all unused imports (not just those from '
697747
'the standard library)')

test_autoflake.py

+43
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,49 @@ def foo():
485485
"""
486486
self.assertEqual(line, ''.join(autoflake.filter_code(line)))
487487

488+
def test_filter_code_populate_all(self):
489+
self.assertEqual("""
490+
import math
491+
import sys
492+
__all__ = ['math', 'sys']
493+
""", ''.join(autoflake.filter_code("""
494+
import math
495+
import sys
496+
""", populate_all=True)))
497+
498+
def test_filter_code_populate_all_appending(self):
499+
self.assertEqual("""
500+
import math
501+
import sys
502+
__all__ = ['math', 'sys']
503+
""", ''.join(autoflake.filter_code("""
504+
import math
505+
import sys
506+
__all__ = ['math']
507+
""", populate_all=True)))
508+
509+
def test_filter_code_populate_all_ignore_comment(self):
510+
self.assertEqual("""
511+
import math
512+
import sys
513+
# __all__ = ['math']
514+
__all__ = ['math', 'sys']
515+
""", ''.join(autoflake.filter_code("""
516+
import math
517+
import sys
518+
# __all__ = ['math']
519+
""", populate_all=True)))
520+
521+
def test_filter_code_populate_all_from_import(self):
522+
self.assertEqual("""
523+
from a.b import Foo
524+
from a.c import Bar
525+
__all__ = ['Foo', 'Bar']
526+
""", ''.join(autoflake.filter_code("""
527+
from a.b import Foo
528+
from a.c import Bar
529+
""", populate_all=True)))
530+
488531
def test_fix_code(self):
489532
self.assertEqual(
490533
"""\

0 commit comments

Comments
 (0)