diff options
| author | Eric Lin <anselor@gmail.com> | 2021-04-03 18:41:26 -0400 |
|---|---|---|
| committer | anselor <anselor@gmail.com> | 2021-04-06 14:54:28 -0400 |
| commit | 50d302913328931ecc9f61293892ac8f9aa61890 (patch) | |
| tree | b67f5586861cc57094f63efb27cf628476d7b544 /cmd2 | |
| parent | d309493f29750433fd8bd6158bc194ed46fa0f7f (diff) | |
| download | cmd2-git-50d302913328931ecc9f61293892ac8f9aa61890.tar.gz | |
And that's the last of it. Passes mypy.
Diffstat (limited to 'cmd2')
| -rw-r--r-- | cmd2/argparse_completer.py | 44 | ||||
| -rw-r--r-- | cmd2/argparse_custom.py | 34 | ||||
| -rw-r--r-- | cmd2/cmd2.py | 33 | ||||
| -rw-r--r-- | cmd2/decorators.py | 17 | ||||
| -rw-r--r-- | cmd2/history.py | 14 | ||||
| -rwxr-xr-x | cmd2/parsing.py | 20 | ||||
| -rw-r--r-- | cmd2/py_bridge.py | 3 | ||||
| -rw-r--r-- | cmd2/table_creator.py | 22 | ||||
| -rw-r--r-- | cmd2/transcript.py | 4 | ||||
| -rw-r--r-- | cmd2/utils.py | 1 |
10 files changed, 118 insertions, 74 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 01a9e393..8a6fdea7 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -14,11 +14,11 @@ from collections import ( ) from typing import ( Any, - cast, Dict, List, Optional, Union, + cast, ) from . import ( @@ -32,6 +32,8 @@ from .argparse_custom import ( ATTR_NARGS_RANGE, ATTR_SUPPRESS_TAB_HINT, ChoicesCallable, + ChoicesProviderFuncBase, + ChoicesProviderFuncWithTokens, CompletionItem, generate_range_error, ) @@ -317,9 +319,11 @@ class ArgparseCompleter: # Handle '--' which tells argparse all remaining arguments are non-flags elif token == '--' and not skip_remaining_flags: # Check if there is an unfinished flag - if flag_arg_state is not None \ - and isinstance(flag_arg_state.min, int) \ - and flag_arg_state.count < flag_arg_state.min: + if ( + flag_arg_state is not None + and isinstance(flag_arg_state.min, int) + and flag_arg_state.count < flag_arg_state.min + ): raise _UnfinishedFlagError(flag_arg_state) # Otherwise end the current flag @@ -332,9 +336,11 @@ class ArgparseCompleter: if _looks_like_flag(token, self._parser) and not skip_remaining_flags: # Check if there is an unfinished flag - if flag_arg_state is not None \ - and isinstance(flag_arg_state.min, int) \ - and flag_arg_state.count < flag_arg_state.min: + if ( + flag_arg_state is not None + and isinstance(flag_arg_state.min, int) + and flag_arg_state.count < flag_arg_state.min + ): raise _UnfinishedFlagError(flag_arg_state) # Reset flag arg state but not positional tracking because flags can be @@ -438,9 +444,11 @@ class ArgparseCompleter: # the current argument. We will handle the completion of flags that start with only one prefix # character (-f) at the end. if _looks_like_flag(text, self._parser) and not skip_remaining_flags: - if flag_arg_state is not None \ - and isinstance(flag_arg_state.min, int) \ - and flag_arg_state.count < flag_arg_state.min: + if ( + flag_arg_state is not None + and isinstance(flag_arg_state.min, int) + and flag_arg_state.count < flag_arg_state.min + ): raise _UnfinishedFlagError(flag_arg_state) return self._complete_flags(text, line, begidx, endidx, matched_flags) @@ -692,17 +700,23 @@ class ArgparseCompleter: # Check if the argument uses a specific tab completion function to provide its choices if isinstance(arg_choices, ChoicesCallable) and arg_choices.is_completer: args.extend([text, line, begidx, endidx]) - results = arg_choices.to_call(*args, **kwargs) # type: ignore[arg-type] + results = arg_choices.completer(*args, **kwargs) # type: ignore[arg-type] # Otherwise use basic_complete on the choices else: # Check if the choices come from a function - completion_items: List[str] + completion_items: List[str] = [] if isinstance(arg_choices, ChoicesCallable): if not arg_choices.is_completer: - completion_items = arg_choices.to_call(*args, **kwargs) # type: ignore[arg-type] - else: - completion_items = [] + choices_func = arg_choices.choices_provider + if isinstance(choices_func, ChoicesProviderFuncWithTokens): + completion_items = choices_func(*args, **kwargs) # type: ignore[arg-type] + else: # pragma: no cover + # This won't hit because runtime checking doesn't check function argument types and will always + # resolve true above. Mypy, however, does see the difference and gives an error that can't be + # ignored. Mypy issue #5485 discusses this problem + completion_items = choices_func(*args) # type: ignore[arg-type] + # else case is already covered above else: completion_items = arg_choices diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index c0e76e50..6b1c799d 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -214,7 +214,7 @@ from typing import ( Sequence, Tuple, Type, - Union, runtime_checkable, + Union, ) from . import ( @@ -225,10 +225,12 @@ from . import ( try: from typing import ( Protocol, + runtime_checkable, ) except ImportError: from typing_extensions import ( # type: ignore[misc] Protocol, + runtime_checkable, ) ############################################################################################################ @@ -359,6 +361,7 @@ class CompleterFuncWithTokens(Protocol): CompleterFunc = Union[CompleterFuncBase, CompleterFuncWithTokens] + class ChoicesCallable: """ Enables using a callable as the choices provider for an argparse argument. @@ -377,10 +380,35 @@ class ChoicesCallable: :param to_call: the callable object that will be called to provide choices for the argument """ self.is_completer = is_completer - if not isinstance(to_call, (CompleterFuncBase, CompleterFuncWithTokens)): - raise ValueError('With is_completer set to true, to_call must be either CompleterFunc, CompleterFuncWithTokens') + if is_completer: + if not isinstance(to_call, (CompleterFuncBase, CompleterFuncWithTokens)): # pragma: no cover + # runtime checking of Protocols do not currently check the parameters of a function. + raise ValueError( + 'With is_completer set to true, to_call must be either CompleterFunc, CompleterFuncWithTokens' + ) + else: + if not isinstance(to_call, (ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens)): # pragma: no cover + # runtime checking of Protocols do not currently check the parameters of a function. + raise ValueError( + 'With is_completer set to false, to_call must be either: ' + 'ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens' + ) self.to_call = to_call + @property + def completer(self) -> CompleterFunc: + if not isinstance(self.to_call, (CompleterFuncBase, CompleterFuncWithTokens)): # pragma: no cover + # this should've been caught in the constructor, just a backup check + raise ValueError('Function is not a CompleterFunc') + return self.to_call + + @property + def choices_provider(self) -> ChoicesProviderFunc: + if not isinstance(self.to_call, (ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens)): # pragma: no cover + # this should've been caught in the constructor, just a backup check + raise ValueError('Function is not a ChoicesProviderFunc') + return self.to_call + def _set_choices_callable(action: argparse.Action, choices_callable: ChoicesCallable) -> None: """ diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 30140906..7e437270 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -80,7 +80,6 @@ from .argparse_custom import ( DEFAULT_ARGUMENT_PARSER, ChoicesProviderFunc, CompleterFunc, - CompleterFuncWithTokens, CompletionItem, ) from .clipboard import ( @@ -3706,11 +3705,12 @@ class Cmd(cmd.Cmd): | a list of tuples -> interpreted as (value, text), so that the return value can differ from the text advertised to the user""" - - local_opts = opts + local_opts: Union[List[str], List[Tuple[Any, Optional[str]]]] if isinstance(opts, str): - local_opts = list(zip(opts.split(), opts.split())) - fulloptions = [] + local_opts = cast(List[Tuple[Any, Optional[str]]], list(zip(opts.split(), opts.split()))) + else: + local_opts = opts + fulloptions: List[Tuple[Any, Optional[str]]] = [] for opt in local_opts: if isinstance(opt, str): fulloptions.append((opt, opt)) @@ -3739,7 +3739,7 @@ class Cmd(cmd.Cmd): choice = int(response) if choice < 1: raise IndexError - return fulloptions[choice - 1][0] + return str(fulloptions[choice - 1][0]) except (ValueError, IndexError): self.poutput("{!r} isn't a valid choice. Pick a number between 1 and {}:".format(response, len(fulloptions))) @@ -4192,18 +4192,16 @@ class Cmd(cmd.Cmd): """ # Detect whether IPython is installed try: - from IPython import ( + import traitlets.config.loader as TraitletsLoader # type: ignore[import] + from IPython import ( # type: ignore[import] start_ipython, ) - from IPython.terminal.interactiveshell import ( + from IPython.terminal.interactiveshell import ( # type: ignore[import] TerminalInteractiveShell, ) - from IPython.terminal.ipapp import ( + from IPython.terminal.ipapp import ( # type: ignore[import] TerminalIPythonApp, ) - from traitlets.config.loader import ( - Config as TraitletsConfig, - ) except ImportError: self.perror("IPython package is not installed") return None @@ -4229,7 +4227,7 @@ class Cmd(cmd.Cmd): local_vars['self'] = self # Configure IPython - config = TraitletsConfig() + config = TraitletsLoader.Config() config.InteractiveShell.banner2 = ( 'Entering an IPython shell. Type exit, quit, or Ctrl-D to exit.\n' f'Run CLI commands with: {self.py_bridge_name}("command ...")\n' @@ -5210,10 +5208,11 @@ class Cmd(cmd.Cmd): self._validate_cmdfinalization_callable(func) self._cmdfinalization_hooks.append(func) - def _resolve_func_self(self, - cmd_support_func: Callable[..., Any], - cmd_self: Union[CommandSet, 'Cmd', None], - ) -> Optional[object]: + def _resolve_func_self( + self, + cmd_support_func: Callable[..., Any], + cmd_self: Union[CommandSet, 'Cmd', None], + ) -> Optional[object]: """ Attempt to resolve a candidate instance to pass as 'self' for an unbound class method that was used when defining command's argparse object. Since we restrict registration to only a single CommandSet diff --git a/cmd2/decorators.py b/cmd2/decorators.py index b62acaf8..d5a7a190 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -8,8 +8,9 @@ from typing import ( Dict, List, Optional, + Sequence, Tuple, - Union, Sequence, runtime_checkable, Protocol, + Union, ) from . import ( @@ -106,7 +107,7 @@ def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> """ index = args.index(search_arg) args_list = list(args) - args_list[index: index + 1] = replace_arg + args_list[index : index + 1] = replace_arg return args_list @@ -134,11 +135,10 @@ ArgListCommandFunc = Union[ArgListCommandFuncOptionalBoolReturn, ArgListCommandF def with_argument_list( - func_arg: Optional[ArgListCommandFunc] = None, *, preserve_quotes: bool = False, -) -> Union[ - RawCommandFuncOptionalBoolReturn, - Callable[[ArgListCommandFunc], RawCommandFuncOptionalBoolReturn] -]: + func_arg: Optional[ArgListCommandFunc] = None, + *, + preserve_quotes: bool = False, +) -> Union[RawCommandFuncOptionalBoolReturn, Callable[[ArgListCommandFunc], RawCommandFuncOptionalBoolReturn]]: """ A decorator to alter the arguments passed to a ``do_*`` method. Default passes a string of whatever the user typed. With this decorator, the @@ -166,6 +166,7 @@ def with_argument_list( :param func: The defined argument list command function :return: Function that takes raw input and converts to an argument list to pass to the wrapped function. """ + @functools.wraps(func) def cmd_wrapper(*args: Any, **kwargs: Any) -> Optional[bool]: """ @@ -182,7 +183,7 @@ def with_argument_list( args_list = _arg_swap(args, statement, parsed_arglist) return func(*args_list, **kwargs) # type: ignore[call-arg] - command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX):] + command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX) :] cmd_wrapper.__doc__ = func.__doc__ return cmd_wrapper diff --git a/cmd2/history.py b/cmd2/history.py index 080ac594..c072d2e0 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -9,8 +9,11 @@ from collections import ( ) from typing import ( Callable, + Iterable, + List, Optional, - Union, List, Iterable, overload, + Union, + overload, ) import attr @@ -124,11 +127,11 @@ class History(List[HistoryItem]): @overload def append(self, new: HistoryItem) -> None: - ... + ... # pragma: no cover @overload def append(self, new: Statement) -> None: - ... + ... # pragma: no cover def append(self, new: Union[Statement, HistoryItem]) -> None: """Append a new statement to the end of the History list. @@ -136,10 +139,7 @@ class History(List[HistoryItem]): :param new: Statement object which will be composed into a HistoryItem and added to the end of the list """ - if isinstance(new, Statement): - history_item = HistoryItem(new) - else: - history_item = new + history_item = HistoryItem(new) if isinstance(new, Statement) else new super(History, self).append(history_item) def clear(self) -> None: diff --git a/cmd2/parsing.py b/cmd2/parsing.py index f97b0c64..46d32549 100755 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -5,6 +5,7 @@ import re import shlex from typing import ( + Any, Dict, Iterable, List, @@ -86,7 +87,7 @@ class Macro: @attr.s(auto_attribs=True, frozen=True) -class Statement(str): +class Statement(str): # type: ignore[override] """String subclass with additional attributes to store the results of parsing. The ``cmd`` module in the standard library passes commands around as a @@ -146,7 +147,7 @@ class Statement(str): # if output was redirected, the destination file token (quotes preserved) output_to: str = attr.ib(default='', validator=attr.validators.instance_of(str)) - def __new__(cls, value: object, *pos_args, **kw_args): + def __new__(cls, value: object, *pos_args: Any, **kw_args: Any) -> 'Statement': """Create a new instance of Statement. We must override __new__ because we are subclassing `str` which is @@ -241,18 +242,13 @@ class StatementParser: :param aliases: dictionary containing aliases :param shortcuts: dictionary containing shortcuts """ + self.terminators: Tuple[str, ...] if terminators is None: self.terminators = (constants.MULTILINE_TERMINATOR,) else: self.terminators = tuple(terminators) - if multiline_commands is None: - self.multiline_commands = tuple() - else: - self.multiline_commands = tuple(multiline_commands) - if aliases is None: - self.aliases = dict() - else: - self.aliases = aliases + self.multiline_commands: Tuple[str, ...] = tuple(multiline_commands) if multiline_commands is not None else () + self.aliases: Dict[str, str] = aliases if aliases is not None else {} if shortcuts is None: shortcuts = constants.DEFAULT_SHORTCUTS @@ -315,7 +311,7 @@ class StatementParser: valid = False if not isinstance(word, str): - return False, 'must be a string. Received {} instead'.format(str(type(word))) + return False, 'must be a string. Received {} instead'.format(str(type(word))) # type: ignore[unreachable] if not word: return False, 'cannot be an empty string' @@ -670,7 +666,7 @@ class StatementParser: :param tokens: the tokens as parsed by shlex :return: a new list of tokens, further split using punctuation """ - punctuation = [] + punctuation: List[str] = [] punctuation.extend(self.terminators) punctuation.extend(constants.REDIRECTION_CHARS) diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index a54703fd..6fd00e07 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -17,7 +17,8 @@ from typing import ( NamedTuple, Optional, TextIO, - cast, Union, + Union, + cast, ) from .utils import ( # namedtuple_with_defaults, diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index d9377bc4..80874086 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -23,7 +23,7 @@ from typing import ( Union, ) -from wcwidth import ( +from wcwidth import ( # type: ignore[import] wcwidth, ) @@ -89,7 +89,7 @@ class Column: if width is not None and width < 1: raise ValueError("Column width cannot be less than 1") else: - self.width = width + self.width: int = width if width is not None else -1 self.header_horiz_align = header_horiz_align self.header_vert_align = header_vert_align @@ -138,7 +138,7 @@ class TableCreator: # For headers with the width not yet set, use the width of the # widest line in the header or 1 if the header has no width - if col.width is None: + if col.width < 0: col.width = max(1, ansi.widest_line(col.header)) @staticmethod @@ -218,7 +218,11 @@ class TableCreator: :return: wrapped text """ - def add_word(word_to_add: str, is_last_word: bool): + # MyPy Issue #7057 documents regression requiring nonlocals to be defined earlier + cur_line_width = 0 + total_lines = 0 + + def add_word(word_to_add: str, is_last_word: bool) -> None: """ Called from loop to add a word to the wrapped text @@ -431,7 +435,7 @@ class TableCreator: def __init__(self) -> None: # Data in this cell split into individual lines - self.lines = [] + self.lines: Deque[str] = deque() # Display width of this cell self.width = 0 @@ -705,7 +709,7 @@ class BorderedTable(TableCreator): data_width = sum(col.width for col in self.cols) return base_width + data_width - def generate_table_top_border(self): + def generate_table_top_border(self) -> str: """Generate a border which appears at the top of the header and data section""" pre_line = '╔' + self.padding * '═' @@ -720,7 +724,7 @@ class BorderedTable(TableCreator): row_data=self.empty_data, fill_char='═', pre_line=pre_line, inter_cell=inter_cell, post_line=post_line ) - def generate_header_bottom_border(self): + def generate_header_bottom_border(self) -> str: """Generate a border which appears at the bottom of the header""" pre_line = '╠' + self.padding * '═' @@ -735,7 +739,7 @@ class BorderedTable(TableCreator): row_data=self.empty_data, fill_char='═', pre_line=pre_line, inter_cell=inter_cell, post_line=post_line ) - def generate_row_bottom_border(self): + def generate_row_bottom_border(self) -> str: """Generate a border which appears at the bottom of rows""" pre_line = '╟' + self.padding * '─' @@ -750,7 +754,7 @@ class BorderedTable(TableCreator): row_data=self.empty_data, fill_char='─', pre_line=pre_line, inter_cell=inter_cell, post_line=post_line ) - def generate_table_bottom_border(self): + def generate_table_bottom_border(self) -> str: """Generate a border which appears at the bottom of the table""" pre_line = '╚' + self.padding * '═' diff --git a/cmd2/transcript.py b/cmd2/transcript.py index eec627c8..7ea5b8a9 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -13,10 +13,12 @@ import re import unittest from typing import ( TYPE_CHECKING, + Iterator, List, Optional, + TextIO, Tuple, - cast, Iterator, IO, TextIO, + cast, ) from . import ( diff --git a/cmd2/utils.py b/cmd2/utils.py index 3a7dbc6e..4f3eae7b 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -36,7 +36,6 @@ from . import ( from .argparse_custom import ( ChoicesProviderFunc, CompleterFunc, - CompleterFuncWithTokens, ) if TYPE_CHECKING: # pragma: no cover |
