Skip to content

Commit a3f3841

Browse files
committed
mypy: add cmdline.py and test_cmdline.py
1 parent 09f9188 commit a3f3841

File tree

6 files changed

+166
-115
lines changed

6 files changed

+166
-115
lines changed

coverage/cmdline.py

+53-31
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import textwrap
1313
import traceback
1414

15+
from typing import cast, Any, List, NoReturn, Optional, Tuple
16+
1517
import coverage
1618
from coverage import Coverage
1719
from coverage import env
@@ -235,8 +237,9 @@ class CoverageOptionParser(optparse.OptionParser):
235237
236238
"""
237239

238-
def __init__(self, *args, **kwargs):
239-
super().__init__(add_help_option=False, *args, **kwargs)
240+
def __init__(self, *args: Any, **kwargs: Any) -> None:
241+
kwargs["add_help_option"] = False
242+
super().__init__(*args, **kwargs)
240243
self.set_defaults(
241244
# Keep these arguments alphabetized by their names.
242245
action=None,
@@ -278,19 +281,19 @@ class OptionParserError(Exception):
278281
"""Used to stop the optparse error handler ending the process."""
279282
pass
280283

281-
def parse_args_ok(self, args=None, options=None):
284+
def parse_args_ok(self, args: List[str]) -> Tuple[bool, Optional[optparse.Values], List[str]]:
282285
"""Call optparse.parse_args, but return a triple:
283286
284287
(ok, options, args)
285288
286289
"""
287290
try:
288-
options, args = super().parse_args(args, options)
291+
options, args = super().parse_args(args)
289292
except self.OptionParserError:
290-
return False, None, None
293+
return False, None, []
291294
return True, options, args
292295

293-
def error(self, msg):
296+
def error(self, msg: str) -> NoReturn:
294297
"""Override optparse.error so sys.exit doesn't get called."""
295298
show_help(msg)
296299
raise self.OptionParserError
@@ -299,7 +302,7 @@ def error(self, msg):
299302
class GlobalOptionParser(CoverageOptionParser):
300303
"""Command-line parser for coverage.py global option arguments."""
301304

302-
def __init__(self):
305+
def __init__(self) -> None:
303306
super().__init__()
304307

305308
self.add_options([
@@ -311,14 +314,19 @@ def __init__(self):
311314
class CmdOptionParser(CoverageOptionParser):
312315
"""Parse one of the new-style commands for coverage.py."""
313316

314-
def __init__(self, action, options, defaults=None, usage=None, description=None):
317+
def __init__(
318+
self,
319+
action: str,
320+
options: List[optparse.Option],
321+
description: str,
322+
usage: Optional[str]=None,
323+
):
315324
"""Create an OptionParser for a coverage.py command.
316325
317326
`action` is the slug to put into `options.action`.
318327
`options` is a list of Option's for the command.
319-
`defaults` is a dict of default value for options.
320-
`usage` is the usage string to display in help.
321328
`description` is the description of the command, for the help text.
329+
`usage` is the usage string to display in help.
322330
323331
"""
324332
if usage:
@@ -327,18 +335,18 @@ def __init__(self, action, options, defaults=None, usage=None, description=None)
327335
usage=usage,
328336
description=description,
329337
)
330-
self.set_defaults(action=action, **(defaults or {}))
338+
self.set_defaults(action=action)
331339
self.add_options(options)
332340
self.cmd = action
333341

334-
def __eq__(self, other):
342+
def __eq__(self, other: str) -> bool: # type: ignore[override]
335343
# A convenience equality, so that I can put strings in unit test
336344
# results, and they will compare equal to objects.
337345
return (other == f"<CmdOptionParser:{self.cmd}>")
338346

339-
__hash__ = None # This object doesn't need to be hashed.
347+
__hash__ = None # type: ignore[assignment]
340348

341-
def get_prog_name(self):
349+
def get_prog_name(self) -> str:
342350
"""Override of an undocumented function in optparse.OptionParser."""
343351
program_name = super().get_prog_name()
344352

@@ -540,7 +548,11 @@ def get_prog_name(self):
540548
}
541549

542550

543-
def show_help(error=None, topic=None, parser=None):
551+
def show_help(
552+
error: Optional[str]=None,
553+
topic: Optional[str]=None,
554+
parser: Optional[optparse.OptionParser]=None,
555+
) -> None:
544556
"""Display an error message, or the named topic."""
545557
assert error or topic or parser
546558

@@ -573,6 +585,7 @@ def show_help(error=None, topic=None, parser=None):
573585
print(parser.format_help().strip())
574586
print()
575587
else:
588+
assert topic is not None
576589
help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip()
577590
if help_msg:
578591
print(help_msg.format(**help_params))
@@ -587,11 +600,11 @@ def show_help(error=None, topic=None, parser=None):
587600
class CoverageScript:
588601
"""The command-line interface to coverage.py."""
589602

590-
def __init__(self):
603+
def __init__(self) -> None:
591604
self.global_option = False
592-
self.coverage = None
605+
self.coverage: Coverage
593606

594-
def command_line(self, argv):
607+
def command_line(self, argv: List[str]) -> int:
595608
"""The bulk of the command line interface to coverage.py.
596609
597610
`argv` is the argument list to process.
@@ -606,6 +619,7 @@ def command_line(self, argv):
606619

607620
# The command syntax we parse depends on the first argument. Global
608621
# switch syntax always starts with an option.
622+
parser: Optional[optparse.OptionParser]
609623
self.global_option = argv[0].startswith('-')
610624
if self.global_option:
611625
parser = GlobalOptionParser()
@@ -619,6 +633,7 @@ def command_line(self, argv):
619633
ok, options, args = parser.parse_args_ok(argv)
620634
if not ok:
621635
return ERR
636+
assert options is not None
622637

623638
# Handle help and version.
624639
if self.do_help(options, args, parser):
@@ -740,8 +755,8 @@ def command_line(self, argv):
740755
if options.precision is not None:
741756
self.coverage.set_option("report:precision", options.precision)
742757

743-
fail_under = self.coverage.get_option("report:fail_under")
744-
precision = self.coverage.get_option("report:precision")
758+
fail_under = cast(float, self.coverage.get_option("report:fail_under"))
759+
precision = cast(int, self.coverage.get_option("report:precision"))
745760
if should_fail_under(total, fail_under, precision):
746761
msg = "total of {total} is less than fail-under={fail_under:.{p}f}".format(
747762
total=Numbers(precision=precision).display_covered(total),
@@ -753,7 +768,12 @@ def command_line(self, argv):
753768

754769
return OK
755770

756-
def do_help(self, options, args, parser):
771+
def do_help(
772+
self,
773+
options: optparse.Values,
774+
args: List[str],
775+
parser: optparse.OptionParser,
776+
) -> bool:
757777
"""Deal with help requests.
758778
759779
Return True if it handled the request, False if not.
@@ -770,9 +790,9 @@ def do_help(self, options, args, parser):
770790
if options.action == "help":
771791
if args:
772792
for a in args:
773-
parser = COMMANDS.get(a)
774-
if parser:
775-
show_help(parser=parser)
793+
parser_maybe = COMMANDS.get(a)
794+
if parser_maybe is not None:
795+
show_help(parser=parser_maybe)
776796
else:
777797
show_help(topic=a)
778798
else:
@@ -786,15 +806,15 @@ def do_help(self, options, args, parser):
786806

787807
return False
788808

789-
def do_run(self, options, args):
809+
def do_run(self, options: optparse.Values, args: List[str]) -> int:
790810
"""Implementation of 'coverage run'."""
791811

792812
if not args:
793813
if options.module:
794814
# Specified -m with nothing else.
795815
show_help("No module specified for -m")
796816
return ERR
797-
command_line = self.coverage.get_option("run:command_line")
817+
command_line = cast(str, self.coverage.get_option("run:command_line"))
798818
if command_line is not None:
799819
args = shlex.split(command_line)
800820
if args and args[0] in {"-m", "--module"}:
@@ -845,7 +865,7 @@ def do_run(self, options, args):
845865

846866
return OK
847867

848-
def do_debug(self, args):
868+
def do_debug(self, args: List[str]) -> int:
849869
"""Implementation of 'coverage debug'."""
850870

851871
if not args:
@@ -878,7 +898,7 @@ def do_debug(self, args):
878898
return OK
879899

880900

881-
def unshell_list(s):
901+
def unshell_list(s: str) -> Optional[List[str]]:
882902
"""Turn a command-line argument into a list."""
883903
if not s:
884904
return None
@@ -892,7 +912,7 @@ def unshell_list(s):
892912
return s.split(',')
893913

894914

895-
def unglob_args(args):
915+
def unglob_args(args: List[str]) -> List[str]:
896916
"""Interpret shell wildcards for platforms that need it."""
897917
if env.WINDOWS:
898918
globbed = []
@@ -938,7 +958,7 @@ def unglob_args(args):
938958
}
939959

940960

941-
def main(argv=None):
961+
def main(argv: Optional[List[str]]=None) -> Optional[int]:
942962
"""The main entry point to coverage.py.
943963
944964
This is installed as the script entry point.
@@ -976,7 +996,9 @@ def main(argv=None):
976996
from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error
977997
original_main = main
978998

979-
def main(argv=None): # pylint: disable=function-redefined
999+
def main( # pylint: disable=function-redefined
1000+
argv: Optional[List[str]]=None,
1001+
) -> Optional[int]:
9801002
"""A wrapper around main that profiles."""
9811003
profiler = SimpleLauncher.launch()
9821004
try:

coverage/control.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def current(cls) -> Optional[Coverage]:
111111

112112
def __init__( # pylint: disable=too-many-arguments
113113
self,
114-
data_file: Optional[str]=DEFAULT_DATAFILE, # type: ignore[assignment]
114+
data_file: Optional[Union[str, DefaultValue]]=DEFAULT_DATAFILE,
115115
data_suffix: Optional[Union[str, bool]]=None,
116116
cover_pylib: Optional[bool]=None,
117117
auto_data: bool=False,
@@ -219,7 +219,7 @@ def __init__( # pylint: disable=too-many-arguments
219219
# data_file=None means no disk file at all. data_file missing means
220220
# use the value from the config file.
221221
self._no_disk = data_file is None
222-
if data_file is DEFAULT_DATAFILE:
222+
if isinstance(data_file, DefaultValue):
223223
data_file = None
224224

225225
# This is injectable by tests.

coverage/types.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Protocol: # pylint: disable=missing-class-docstring
2525
## Configuration
2626

2727
# One value read from a config file.
28-
TConfigValue = Optional[Union[bool, int, str, List[str]]]
28+
TConfigValue = Optional[Union[bool, int, float, str, List[str]]]
2929
# An entire config section, mapping option names to values.
3030
TConfigSection = Dict[str, TConfigValue]
3131

tests/coveragetest.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def assert_recent_datetime(self, dt, seconds=10, msg=None):
310310
assert age.total_seconds() >= 0, msg
311311
assert age.total_seconds() <= seconds, msg
312312

313-
def command_line(self, args, ret=OK):
313+
def command_line(self, args: str, ret: int=OK) -> None:
314314
"""Run `args` through the command line.
315315
316316
Use this when you want to run the full coverage machinery, but in the
@@ -467,7 +467,7 @@ def setUp(self):
467467
sys.path.append(nice_file(TESTS_DIR, "zipmods.zip"))
468468

469469

470-
def command_line(args):
470+
def command_line(args: str) -> int:
471471
"""Run `args` through the CoverageScript command line.
472472
473473
Returns the return code from CoverageScript.command_line.

0 commit comments

Comments
 (0)