Hoist passing through sys.argv at the CLI layer

`flake8.main.cli.main()` is the primary entry point for the command-line
implementation of flake8 (invoked via `__main__` or `console_scripts`).
Therefore, it is reasonable for the entry point to be responsible for
obtaining command line arguments from `sys.argv` there.

Note that `sys.argv[1:]` is necessary in order to strip off the script
name.  Formerly, this was not needed in
`Application.parse_preliminary_options_and_args()`, which was using
`sys.argv[:]` because the result of the argument parsing was just for
determining additional configuration to be loaded.  Then, the *real* CLI
argument parsing was forwarding the original `None` value to
`argparse.ArgumentParser.parse_args()`, which internally was obtaining
arguments as `sys.argv[1:]`.

Additionally, the contract for various argument parsing methods to be
guaranteed a `List[str]`.
This commit is contained in:
Eric N. Vander Weele 2019-08-28 16:20:38 -04:00
parent 7708e5b4ab
commit cf4bc53c12
3 changed files with 23 additions and 9 deletions

View file

@ -97,8 +97,8 @@ class Application(object):
#: The parsed diff information #: The parsed diff information
self.parsed_diff = {} # type: Dict[str, Set[int]] self.parsed_diff = {} # type: Dict[str, Set[int]]
def parse_preliminary_options_and_args(self, argv=None): def parse_preliminary_options_and_args(self, argv):
# type: (Optional[List[str]]) -> None # type: (List[str]) -> None
"""Get preliminary options and args from CLI, pre-plugin-loading. """Get preliminary options and args from CLI, pre-plugin-loading.
We need to know the values of a few standard options and args now, so We need to know the values of a few standard options and args now, so
@ -121,7 +121,7 @@ class Application(object):
# do not need to worry and we can continue. If it is, we successfully # do not need to worry and we can continue. If it is, we successfully
# defer printing the version until just a little bit later. # defer printing the version until just a little bit later.
# Similarly we have to defer printing the help text until later. # Similarly we have to defer printing the help text until later.
args = (argv if argv is not None else sys.argv)[:] args = argv[:]
try: try:
args.remove("--version") args.remove("--version")
except ValueError: except ValueError:
@ -136,8 +136,8 @@ class Application(object):
pass pass
opts, args = self.option_manager.parse_known_args(args) opts, args = self.option_manager.parse_known_args(args)
# parse_known_args includes program name and unknown options as args # parse_known_args includes unknown options as args
args = [a for a in args[1:] if not a.startswith("-")] args = [a for a in args if not a.startswith("-")]
self.prelim_opts, self.prelim_args = opts, args self.prelim_opts, self.prelim_args = opts, args
def exit(self): def exit(self):
@ -344,7 +344,7 @@ class Application(object):
self.formatter.show_statistics(self.guide.stats) self.formatter.show_statistics(self.guide.stats)
def initialize(self, argv): def initialize(self, argv):
# type: (Optional[List[str]]) -> None # type: (List[str]) -> None
"""Initialize the application to be run. """Initialize the application to be run.
This finds the plugins, registers their options, and parses the This finds the plugins, registers their options, and parses the
@ -373,13 +373,13 @@ class Application(object):
self.formatter.stop() self.formatter.stop()
def _run(self, argv): def _run(self, argv):
# type: (Optional[List[str]]) -> None # type: (List[str]) -> None
self.initialize(argv) self.initialize(argv)
self.run_checks() self.run_checks()
self.report() self.report()
def run(self, argv=None): def run(self, argv):
# type: (Optional[List[str]]) -> None # type: (List[str]) -> None
"""Run our application. """Run our application.
This method will also handle KeyboardInterrupt exceptions for the This method will also handle KeyboardInterrupt exceptions for the

View file

@ -1,4 +1,5 @@
"""Command-line implementation of flake8.""" """Command-line implementation of flake8."""
import sys
from typing import List, Optional from typing import List, Optional
from flake8.main import application from flake8.main import application
@ -14,6 +15,9 @@ def main(argv=None):
:param list argv: :param list argv:
The arguments to be passed to the application for parsing. The arguments to be passed to the application for parsing.
""" """
if argv is None:
argv = sys.argv[1:]
app = application.Application() app = application.Application()
app.run(argv) app.run(argv)
app.exit() app.exit()

View file

@ -126,3 +126,13 @@ def test_bug_report_successful(capsys):
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert json.loads(out) assert json.loads(out)
assert err == '' assert err == ''
def test_obtaining_args_from_sys_argv_when_not_explicity_provided(capsys):
"""Test that arguments are obtained from 'sys.argv'."""
with mock.patch('sys.argv', ['flake8', '--help']):
_call_main(None)
out, err = capsys.readouterr()
assert out.startswith('usage: flake8 [options] file file ...\n')
assert err == ''