Skip to content

Commit 50d3029

Browse files
committed
And that's the last of it. Passes mypy.
1 parent d309493 commit 50d3029

File tree

12 files changed

+126
-80
lines changed

12 files changed

+126
-80
lines changed

cmd2/argparse_completer.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
)
1515
from typing import (
1616
Any,
17-
cast,
1817
Dict,
1918
List,
2019
Optional,
2120
Union,
21+
cast,
2222
)
2323

2424
from . import (
@@ -32,6 +32,8 @@
3232
ATTR_NARGS_RANGE,
3333
ATTR_SUPPRESS_TAB_HINT,
3434
ChoicesCallable,
35+
ChoicesProviderFuncBase,
36+
ChoicesProviderFuncWithTokens,
3537
CompletionItem,
3638
generate_range_error,
3739
)
@@ -317,9 +319,11 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
317319
# Handle '--' which tells argparse all remaining arguments are non-flags
318320
elif token == '--' and not skip_remaining_flags:
319321
# Check if there is an unfinished flag
320-
if flag_arg_state is not None \
321-
and isinstance(flag_arg_state.min, int) \
322-
and flag_arg_state.count < flag_arg_state.min:
322+
if (
323+
flag_arg_state is not None
324+
and isinstance(flag_arg_state.min, int)
325+
and flag_arg_state.count < flag_arg_state.min
326+
):
323327
raise _UnfinishedFlagError(flag_arg_state)
324328

325329
# Otherwise end the current flag
@@ -332,9 +336,11 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
332336
if _looks_like_flag(token, self._parser) and not skip_remaining_flags:
333337

334338
# Check if there is an unfinished flag
335-
if flag_arg_state is not None \
336-
and isinstance(flag_arg_state.min, int) \
337-
and flag_arg_state.count < flag_arg_state.min:
339+
if (
340+
flag_arg_state is not None
341+
and isinstance(flag_arg_state.min, int)
342+
and flag_arg_state.count < flag_arg_state.min
343+
):
338344
raise _UnfinishedFlagError(flag_arg_state)
339345

340346
# Reset flag arg state but not positional tracking because flags can be
@@ -438,9 +444,11 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
438444
# the current argument. We will handle the completion of flags that start with only one prefix
439445
# character (-f) at the end.
440446
if _looks_like_flag(text, self._parser) and not skip_remaining_flags:
441-
if flag_arg_state is not None \
442-
and isinstance(flag_arg_state.min, int) \
443-
and flag_arg_state.count < flag_arg_state.min:
447+
if (
448+
flag_arg_state is not None
449+
and isinstance(flag_arg_state.min, int)
450+
and flag_arg_state.count < flag_arg_state.min
451+
):
444452
raise _UnfinishedFlagError(flag_arg_state)
445453
return self._complete_flags(text, line, begidx, endidx, matched_flags)
446454

@@ -692,17 +700,23 @@ def _complete_arg(
692700
# Check if the argument uses a specific tab completion function to provide its choices
693701
if isinstance(arg_choices, ChoicesCallable) and arg_choices.is_completer:
694702
args.extend([text, line, begidx, endidx])
695-
results = arg_choices.to_call(*args, **kwargs) # type: ignore[arg-type]
703+
results = arg_choices.completer(*args, **kwargs) # type: ignore[arg-type]
696704

697705
# Otherwise use basic_complete on the choices
698706
else:
699707
# Check if the choices come from a function
700-
completion_items: List[str]
708+
completion_items: List[str] = []
701709
if isinstance(arg_choices, ChoicesCallable):
702710
if not arg_choices.is_completer:
703-
completion_items = arg_choices.to_call(*args, **kwargs) # type: ignore[arg-type]
704-
else:
705-
completion_items = []
711+
choices_func = arg_choices.choices_provider
712+
if isinstance(choices_func, ChoicesProviderFuncWithTokens):
713+
completion_items = choices_func(*args, **kwargs) # type: ignore[arg-type]
714+
else: # pragma: no cover
715+
# This won't hit because runtime checking doesn't check function argument types and will always
716+
# resolve true above. Mypy, however, does see the difference and gives an error that can't be
717+
# ignored. Mypy issue #5485 discusses this problem
718+
completion_items = choices_func(*args) # type: ignore[arg-type]
719+
# else case is already covered above
706720
else:
707721
completion_items = arg_choices
708722

cmd2/argparse_custom.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
214214
Sequence,
215215
Tuple,
216216
Type,
217-
Union, runtime_checkable,
217+
Union,
218218
)
219219

220220
from . import (
@@ -225,10 +225,12 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
225225
try:
226226
from typing import (
227227
Protocol,
228+
runtime_checkable,
228229
)
229230
except ImportError:
230231
from typing_extensions import ( # type: ignore[misc]
231232
Protocol,
233+
runtime_checkable,
232234
)
233235

234236
############################################################################################################
@@ -359,6 +361,7 @@ def __call__(
359361

360362
CompleterFunc = Union[CompleterFuncBase, CompleterFuncWithTokens]
361363

364+
362365
class ChoicesCallable:
363366
"""
364367
Enables using a callable as the choices provider for an argparse argument.
@@ -377,10 +380,35 @@ def __init__(
377380
:param to_call: the callable object that will be called to provide choices for the argument
378381
"""
379382
self.is_completer = is_completer
380-
if not isinstance(to_call, (CompleterFuncBase, CompleterFuncWithTokens)):
381-
raise ValueError('With is_completer set to true, to_call must be either CompleterFunc, CompleterFuncWithTokens')
383+
if is_completer:
384+
if not isinstance(to_call, (CompleterFuncBase, CompleterFuncWithTokens)): # pragma: no cover
385+
# runtime checking of Protocols do not currently check the parameters of a function.
386+
raise ValueError(
387+
'With is_completer set to true, to_call must be either CompleterFunc, CompleterFuncWithTokens'
388+
)
389+
else:
390+
if not isinstance(to_call, (ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens)): # pragma: no cover
391+
# runtime checking of Protocols do not currently check the parameters of a function.
392+
raise ValueError(
393+
'With is_completer set to false, to_call must be either: '
394+
'ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens'
395+
)
382396
self.to_call = to_call
383397

398+
@property
399+
def completer(self) -> CompleterFunc:
400+
if not isinstance(self.to_call, (CompleterFuncBase, CompleterFuncWithTokens)): # pragma: no cover
401+
# this should've been caught in the constructor, just a backup check
402+
raise ValueError('Function is not a CompleterFunc')
403+
return self.to_call
404+
405+
@property
406+
def choices_provider(self) -> ChoicesProviderFunc:
407+
if not isinstance(self.to_call, (ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens)): # pragma: no cover
408+
# this should've been caught in the constructor, just a backup check
409+
raise ValueError('Function is not a ChoicesProviderFunc')
410+
return self.to_call
411+
384412

385413
def _set_choices_callable(action: argparse.Action, choices_callable: ChoicesCallable) -> None:
386414
"""

cmd2/cmd2.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
DEFAULT_ARGUMENT_PARSER,
8181
ChoicesProviderFunc,
8282
CompleterFunc,
83-
CompleterFuncWithTokens,
8483
CompletionItem,
8584
)
8685
from .clipboard import (
@@ -3706,11 +3705,12 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], p
37063705
| a list of tuples -> interpreted as (value, text), so
37073706
that the return value can differ from
37083707
the text advertised to the user"""
3709-
3710-
local_opts = opts
3708+
local_opts: Union[List[str], List[Tuple[Any, Optional[str]]]]
37113709
if isinstance(opts, str):
3712-
local_opts = list(zip(opts.split(), opts.split()))
3713-
fulloptions = []
3710+
local_opts = cast(List[Tuple[Any, Optional[str]]], list(zip(opts.split(), opts.split())))
3711+
else:
3712+
local_opts = opts
3713+
fulloptions: List[Tuple[Any, Optional[str]]] = []
37143714
for opt in local_opts:
37153715
if isinstance(opt, str):
37163716
fulloptions.append((opt, opt))
@@ -3739,7 +3739,7 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], p
37393739
choice = int(response)
37403740
if choice < 1:
37413741
raise IndexError
3742-
return fulloptions[choice - 1][0]
3742+
return str(fulloptions[choice - 1][0])
37433743
except (ValueError, IndexError):
37443744
self.poutput("{!r} isn't a valid choice. Pick a number between 1 and {}:".format(response, len(fulloptions)))
37453745

@@ -4192,18 +4192,16 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover
41924192
"""
41934193
# Detect whether IPython is installed
41944194
try:
4195-
from IPython import (
4195+
import traitlets.config.loader as TraitletsLoader # type: ignore[import]
4196+
from IPython import ( # type: ignore[import]
41964197
start_ipython,
41974198
)
4198-
from IPython.terminal.interactiveshell import (
4199+
from IPython.terminal.interactiveshell import ( # type: ignore[import]
41994200
TerminalInteractiveShell,
42004201
)
4201-
from IPython.terminal.ipapp import (
4202+
from IPython.terminal.ipapp import ( # type: ignore[import]
42024203
TerminalIPythonApp,
42034204
)
4204-
from traitlets.config.loader import (
4205-
Config as TraitletsConfig,
4206-
)
42074205
except ImportError:
42084206
self.perror("IPython package is not installed")
42094207
return None
@@ -4229,7 +4227,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover
42294227
local_vars['self'] = self
42304228

42314229
# Configure IPython
4232-
config = TraitletsConfig()
4230+
config = TraitletsLoader.Config()
42334231
config.InteractiveShell.banner2 = (
42344232
'Entering an IPython shell. Type exit, quit, or Ctrl-D to exit.\n'
42354233
f'Run CLI commands with: {self.py_bridge_name}("command ...")\n'
@@ -5210,10 +5208,11 @@ def register_cmdfinalization_hook(
52105208
self._validate_cmdfinalization_callable(func)
52115209
self._cmdfinalization_hooks.append(func)
52125210

5213-
def _resolve_func_self(self,
5214-
cmd_support_func: Callable[..., Any],
5215-
cmd_self: Union[CommandSet, 'Cmd', None],
5216-
) -> Optional[object]:
5211+
def _resolve_func_self(
5212+
self,
5213+
cmd_support_func: Callable[..., Any],
5214+
cmd_self: Union[CommandSet, 'Cmd', None],
5215+
) -> Optional[object]:
52175216
"""
52185217
Attempt to resolve a candidate instance to pass as 'self' for an unbound class method that was
52195218
used when defining command's argparse object. Since we restrict registration to only a single CommandSet

cmd2/decorators.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
Dict,
99
List,
1010
Optional,
11+
Sequence,
1112
Tuple,
12-
Union, Sequence, runtime_checkable, Protocol,
13+
Union,
1314
)
1415

1516
from . import (
@@ -106,7 +107,7 @@ def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) ->
106107
"""
107108
index = args.index(search_arg)
108109
args_list = list(args)
109-
args_list[index: index + 1] = replace_arg
110+
args_list[index : index + 1] = replace_arg
110111
return args_list
111112

112113

@@ -134,11 +135,10 @@ def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) ->
134135

135136

136137
def with_argument_list(
137-
func_arg: Optional[ArgListCommandFunc] = None, *, preserve_quotes: bool = False,
138-
) -> Union[
139-
RawCommandFuncOptionalBoolReturn,
140-
Callable[[ArgListCommandFunc], RawCommandFuncOptionalBoolReturn]
141-
]:
138+
func_arg: Optional[ArgListCommandFunc] = None,
139+
*,
140+
preserve_quotes: bool = False,
141+
) -> Union[RawCommandFuncOptionalBoolReturn, Callable[[ArgListCommandFunc], RawCommandFuncOptionalBoolReturn]]:
142142
"""
143143
A decorator to alter the arguments passed to a ``do_*`` method. Default
144144
passes a string of whatever the user typed. With this decorator, the
@@ -166,6 +166,7 @@ def arg_decorator(func: ArgListCommandFunc) -> RawCommandFuncOptionalBoolReturn:
166166
:param func: The defined argument list command function
167167
:return: Function that takes raw input and converts to an argument list to pass to the wrapped function.
168168
"""
169+
169170
@functools.wraps(func)
170171
def cmd_wrapper(*args: Any, **kwargs: Any) -> Optional[bool]:
171172
"""
@@ -182,7 +183,7 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> Optional[bool]:
182183
args_list = _arg_swap(args, statement, parsed_arglist)
183184
return func(*args_list, **kwargs) # type: ignore[call-arg]
184185

185-
command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX):]
186+
command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX) :]
186187
cmd_wrapper.__doc__ = func.__doc__
187188
return cmd_wrapper
188189

cmd2/history.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
)
1010
from typing import (
1111
Callable,
12+
Iterable,
13+
List,
1214
Optional,
13-
Union, List, Iterable, overload,
15+
Union,
16+
overload,
1417
)
1518

1619
import attr
@@ -124,22 +127,19 @@ def _zero_based_index(self, onebased: Union[int, str]) -> int:
124127

125128
@overload
126129
def append(self, new: HistoryItem) -> None:
127-
...
130+
... # pragma: no cover
128131

129132
@overload
130133
def append(self, new: Statement) -> None:
131-
...
134+
... # pragma: no cover
132135

133136
def append(self, new: Union[Statement, HistoryItem]) -> None:
134137
"""Append a new statement to the end of the History list.
135138
136139
:param new: Statement object which will be composed into a HistoryItem
137140
and added to the end of the list
138141
"""
139-
if isinstance(new, Statement):
140-
history_item = HistoryItem(new)
141-
else:
142-
history_item = new
142+
history_item = HistoryItem(new) if isinstance(new, Statement) else new
143143
super(History, self).append(history_item)
144144

145145
def clear(self) -> None:

0 commit comments

Comments
 (0)