Skip to content

Commit 4f545de

Browse files
authored
Use more generic types where possible for input parameters (#1584)
* Use more generic types where possible for input parameters Use generic types like the following where possible: - Iterable - Mapping - MutableSequence - Sequence * Made the ArgTokens type alias consistent with the cmd2.Cmd methods using it
1 parent 4530544 commit 4f545de

File tree

7 files changed

+56
-45
lines changed

7 files changed

+56
-45
lines changed

cmd2/argparse_completer.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
defaultdict,
1111
deque,
1212
)
13-
from collections.abc import Sequence
13+
from collections.abc import (
14+
Mapping,
15+
MutableSequence,
16+
Sequence,
17+
)
1418
from typing import (
1519
IO,
1620
TYPE_CHECKING,
@@ -164,13 +168,13 @@ def __init__(
164168
parser: argparse.ArgumentParser,
165169
cmd2_app: 'Cmd',
166170
*,
167-
parent_tokens: dict[str, list[str]] | None = None,
171+
parent_tokens: Mapping[str, MutableSequence[str]] | None = None,
168172
) -> None:
169173
"""Create an ArgparseCompleter.
170174
171175
:param parser: ArgumentParser instance
172176
:param cmd2_app: reference to the Cmd2 application that owns this ArgparseCompleter
173-
:param parent_tokens: optional dictionary mapping parent parsers' arg names to their tokens
177+
:param parent_tokens: optional Mapping of parent parsers' arg names to their tokens
174178
This is only used by ArgparseCompleter when recursing on subcommand parsers
175179
Defaults to None
176180
"""
@@ -216,7 +220,7 @@ def complete(
216220
line: str,
217221
begidx: int,
218222
endidx: int,
219-
tokens: list[str],
223+
tokens: Sequence[str],
220224
*,
221225
cmd_set: CommandSet | None = None,
222226
) -> Completions:
@@ -226,7 +230,7 @@ def complete(
226230
:param line: the current input line with leading whitespace removed
227231
:param begidx: the beginning index of the prefix text
228232
:param endidx: the ending index of the prefix text
229-
:param tokens: list of argument tokens being passed to the parser
233+
:param tokens: Sequence of argument tokens being passed to the parser
230234
:param cmd_set: if completing a command, the CommandSet the command's function belongs to, if applicable.
231235
Defaults to None.
232236
:return: a Completions object
@@ -638,7 +642,7 @@ def _format_completions(self, arg_state: _ArgumentState, completions: Completion
638642
completion_table=capture.get(),
639643
)
640644

641-
def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: list[str]) -> Completions:
645+
def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: Sequence[str]) -> Completions:
642646
"""Supports cmd2's help command in the completion of subcommand names.
643647
644648
:param text: the string prefix we are attempting to match (all matches must begin with it)
@@ -664,7 +668,7 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in
664668
break
665669
return Completions()
666670

667-
def print_help(self, tokens: list[str], file: IO[str] | None = None) -> None:
671+
def print_help(self, tokens: Sequence[str], file: IO[str] | None = None) -> None:
668672
"""Supports cmd2's help command in the printing of help text.
669673
670674
:param tokens: arguments passed to help command

cmd2/cmd2.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
Callable,
4646
Iterable,
4747
Mapping,
48+
MutableSequence,
49+
Sequence,
4850
)
4951
from types import FrameType
5052
from typing import (
@@ -299,14 +301,14 @@ def __init__(
299301
include_ipy: bool = False,
300302
include_py: bool = False,
301303
intro: RenderableType = '',
302-
multiline_commands: list[str] | None = None,
304+
multiline_commands: Iterable[str] | None = None,
303305
persistent_history_file: str = '',
304306
persistent_history_length: int = 1000,
305-
shortcuts: dict[str, str] | None = None,
307+
shortcuts: Mapping[str, str] | None = None,
306308
silence_startup_script: bool = False,
307309
startup_script: str = '',
308310
suggest_similar_command: bool = False,
309-
terminators: list[str] | None = None,
311+
terminators: Iterable[str] | None = None,
310312
) -> None:
311313
"""Easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
312314
@@ -337,24 +339,24 @@ def __init__(
337339
:param include_ipy: should the "ipy" command be included for an embedded IPython shell
338340
:param include_py: should the "py" command be included for an embedded Python shell
339341
:param intro: introduction to display at startup
340-
:param multiline_commands: list of commands allowed to accept multi-line input
342+
:param multiline_commands: Iterable of commands allowed to accept multi-line input
341343
:param persistent_history_file: file path to load a persistent cmd2 command history from
342344
:param persistent_history_length: max number of history items to write
343345
to the persistent history file
344-
:param shortcuts: dictionary containing shortcuts for commands. If not supplied,
346+
:param shortcuts: Mapping containing shortcuts for commands. If not supplied,
345347
then defaults to constants.DEFAULT_SHORTCUTS. If you do not want
346-
any shortcuts, pass an empty dictionary.
348+
any shortcuts, pass None and an empty dictionary will be created.
347349
:param silence_startup_script: if ``True``, then the startup script's output will be
348350
suppressed. Anything written to stderr will still display.
349351
:param startup_script: file path to a script to execute at startup
350352
:param suggest_similar_command: if ``True``, then when a command is not found,
351353
[cmd2.Cmd][] will look for similar commands and suggest them.
352-
:param terminators: list of characters that terminate a command. These are mainly
354+
:param terminators: Iterable of characters that terminate a command. These are mainly
353355
intended for terminating multiline commands, but will also
354356
terminate single-line commands. If not supplied, the default
355357
is a semicolon. If your app only contains single-line commands
356358
and you want terminators to be treated as literals by the parser,
357-
then set this to an empty list.
359+
then set this to None.
358360
"""
359361
# Check if py or ipy need to be disabled in this instance
360362
if not include_py:
@@ -996,7 +998,9 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
996998
f"Could not find argparser for command '{command_name}' needed by subcommand: {method}"
997999
)
9981000

999-
def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser:
1001+
def find_subcommand(
1002+
action: argparse.ArgumentParser, subcmd_names: MutableSequence[str]
1003+
) -> argparse.ArgumentParser:
10001004
if not subcmd_names:
10011005
return action
10021006
cur_subcmd = subcmd_names.pop(0)
@@ -2766,7 +2770,7 @@ def _run_cmdfinalization_hooks(self, stop: bool, statement: Statement | None) ->
27662770

27672771
def runcmds_plus_hooks(
27682772
self,
2769-
cmds: list[HistoryItem] | list[str],
2773+
cmds: Iterable[HistoryItem] | Iterable[str],
27702774
*,
27712775
add_to_history: bool = True,
27722776
stop_on_keyboard_interrupt: bool = False,
@@ -3169,7 +3173,7 @@ def default(self, statement: Statement) -> bool | None:
31693173
self.perror(err_msg, style=None)
31703174
return None
31713175

3172-
def completedefault(self, *_ignored: list[str]) -> Completions:
3176+
def completedefault(self, *_ignored: Sequence[str]) -> Completions:
31733177
"""Call to complete an input line when no command-specific complete_*() method is available.
31743178
31753179
This method is only called for non-argparse-based commands.
@@ -3185,7 +3189,7 @@ def read_input(
31853189
self,
31863190
prompt: str = '',
31873191
*,
3188-
history: list[str] | None = None,
3192+
history: Iterable[str] | None = None,
31893193
completion_mode: utils.CompletionMode = utils.CompletionMode.NONE,
31903194
preserve_quotes: bool = False,
31913195
choices: Iterable[Any] | None = None,
@@ -3198,7 +3202,7 @@ def read_input(
31983202
Also supports completion and up-arrow history while input is being entered.
31993203
32003204
:param prompt: prompt to display to user
3201-
:param history: optional list of strings to use for up-arrow history. If completion_mode is
3205+
:param history: optional Iterable of strings to use for up-arrow history. If completion_mode is
32023206
CompletionMode.COMMANDS and this is None, then cmd2's command list history will
32033207
be used. The passed in history will not be edited. It is the caller's responsibility
32043208
to add the returned input to history if desired. Defaults to None.
@@ -3873,7 +3877,7 @@ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int)
38733877
return self.basic_complete(text, line, begidx, endidx, strs_to_match)
38743878

38753879
def complete_help_subcommands(
3876-
self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]]
3880+
self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Mapping[str, Sequence[str]]
38773881
) -> Completions:
38783882
"""Completes the subcommands argument of help."""
38793883
# Make sure we have a command whose subcommands we will complete
@@ -4014,13 +4018,13 @@ def do_help(self, args: argparse.Namespace) -> None:
40144018
self.perror(err_msg, style=None)
40154019
self.last_result = False
40164020

4017-
def print_topics(self, header: str, cmds: list[str] | None, cmdlen: int, maxcol: int) -> None: # noqa: ARG002
4021+
def print_topics(self, header: str, cmds: Sequence[str] | None, cmdlen: int, maxcol: int) -> None: # noqa: ARG002
40184022
"""Print groups of commands and topics in columns and an optional header.
40194023
40204024
Override of cmd's print_topics() to use Rich.
40214025
40224026
:param header: string to print above commands being printed
4023-
:param cmds: list of topics to print
4027+
:param cmds: Sequence of topics to print
40244028
:param cmdlen: unused, even by cmd's version
40254029
:param maxcol: max number of display columns to fit into
40264030
"""
@@ -4039,7 +4043,7 @@ def print_topics(self, header: str, cmds: list[str] | None, cmdlen: int, maxcol:
40394043
self.columnize(cmds, maxcol)
40404044
self.poutput()
40414045

4042-
def _print_documented_command_topics(self, header: str, cmds: list[str], verbose: bool) -> None:
4046+
def _print_documented_command_topics(self, header: str, cmds: Sequence[str], verbose: bool) -> None:
40434047
"""Print topics which are documented commands, switching between verbose or traditional output."""
40444048
import io
40454049

@@ -4103,14 +4107,14 @@ def _print_documented_command_topics(self, header: str, cmds: list[str], verbose
41034107
self.poutput(category_grid, soft_wrap=False)
41044108
self.poutput()
41054109

4106-
def render_columns(self, str_list: list[str] | None, display_width: int = 80) -> str:
4110+
def render_columns(self, str_list: Sequence[str] | None, display_width: int = 80) -> str:
41074111
"""Render a list of single-line strings as a compact set of columns.
41084112
41094113
This method correctly handles strings containing ANSI style sequences and
41104114
full-width characters (like those used in CJK languages). Each column is
41114115
only as wide as necessary and columns are separated by two spaces.
41124116
4113-
:param str_list: list of single-line strings to display
4117+
:param str_list: Sequence of single-line strings to display
41144118
:param display_width: max number of display columns to fit into
41154119
:return: a string containing the columnized output
41164120
"""
@@ -4162,14 +4166,14 @@ def render_columns(self, str_list: list[str] | None, display_width: int = 80) ->
41624166

41634167
return "\n".join(rows)
41644168

4165-
def columnize(self, str_list: list[str] | None, display_width: int = 80) -> None:
4169+
def columnize(self, str_list: Sequence[str] | None, display_width: int = 80) -> None:
41664170
"""Display a list of single-line strings as a compact set of columns.
41674171
41684172
Override of cmd's columnize() that uses the render_columns() method.
41694173
The method correctly handles strings with ANSI style sequences and
41704174
full-width characters (like those used in CJK languages).
41714175
4172-
:param str_list: list of single-line strings to display
4176+
:param str_list: Sequence of single-line strings to display
41734177
:param display_width: max number of display columns to fit into
41744178
"""
41754179
columnized_strs = self.render_columns(str_list, display_width)
@@ -4220,7 +4224,7 @@ def do_quit(self, _: argparse.Namespace) -> bool | None:
42204224
self.last_result = True
42214225
return True
42224226

4223-
def select(self, opts: str | list[str] | list[tuple[Any, str | None]], prompt: str = 'Your choice? ') -> Any:
4227+
def select(self, opts: str | Iterable[str] | Iterable[tuple[Any, str | None]], prompt: str = 'Your choice? ') -> Any:
42244228
"""Present a numbered menu to the user.
42254229
42264230
Modeled after the bash shell's SELECT. Returns the item chosen.
@@ -4233,7 +4237,7 @@ def select(self, opts: str | list[str] | list[tuple[Any, str | None]], prompt: s
42334237
that the return value can differ from
42344238
the text advertised to the user
42354239
"""
4236-
local_opts: list[str] | list[tuple[Any, str | None]]
4240+
local_opts: Iterable[str] | Iterable[tuple[Any, str | None]]
42374241
if isinstance(opts, str):
42384242
local_opts = cast(list[tuple[Any, str | None]], list(zip(opts.split(), opts.split(), strict=False)))
42394243
else:
@@ -4295,7 +4299,7 @@ def _build_base_set_parser(cls) -> Cmd2ArgumentParser:
42954299
return base_set_parser
42964300

42974301
def complete_set_value(
4298-
self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]]
4302+
self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Mapping[str, Sequence[str]]
42994303
) -> Completions:
43004304
"""Completes the value argument of set."""
43014305
param = arg_tokens['param'][0]

cmd2/completion.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Collection,
88
Iterable,
99
Iterator,
10+
Mapping,
1011
Sequence,
1112
)
1213
from dataclasses import (
@@ -270,7 +271,7 @@ def all_display_numeric(items: Collection[CompletionItem]) -> bool:
270271
#############################################
271272

272273
# Represents the parsed tokens from argparse during completion
273-
ArgTokens: TypeAlias = dict[str, list[str]]
274+
ArgTokens: TypeAlias = Mapping[str, Sequence[str]]
274275

275276
# Unbound choices_provider function types used by argparse-based completion.
276277
# These expect a Cmd or CommandSet instance as the first argument.

cmd2/decorators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ def as_subcommand_to(
354354
| Callable[[CommandParentClass], argparse.ArgumentParser], # Cmd or CommandSet classmethod
355355
*,
356356
help: str | None = None, # noqa: A002
357-
aliases: list[str] | None = None,
357+
aliases: Sequence[str] | None = None,
358358
) -> Callable[[ArgparseCommandFunc[CommandParent]], ArgparseCommandFunc[CommandParent]]:
359359
"""Tag this method as a subcommand to an existing argparse decorated command.
360360

cmd2/parsing.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
from collections.abc import (
77
Iterable,
8+
Mapping,
89
Sequence,
910
)
1011
from dataclasses import (
@@ -284,8 +285,8 @@ def __init__(
284285
self,
285286
terminators: Iterable[str] | None = None,
286287
multiline_commands: Iterable[str] | None = None,
287-
aliases: dict[str, str] | None = None,
288-
shortcuts: dict[str, str] | None = None,
288+
aliases: Mapping[str, str] | None = None,
289+
shortcuts: Mapping[str, str] | None = None,
289290
) -> None:
290291
"""Initialize an instance of StatementParser.
291292
@@ -303,7 +304,7 @@ def __init__(
303304
else:
304305
self.terminators = tuple(terminators)
305306
self.multiline_commands: tuple[str, ...] = tuple(multiline_commands) if multiline_commands is not None else ()
306-
self.aliases: dict[str, str] = aliases if aliases is not None else {}
307+
self.aliases: dict[str, str] = dict(aliases) if aliases is not None else {}
307308

308309
if shortcuts is None:
309310
shortcuts = constants.DEFAULT_SHORTCUTS

cmd2/utils.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from collections.abc import (
1515
Callable,
1616
Iterable,
17+
MutableSequence,
1718
)
1819
from difflib import SequenceMatcher
1920
from enum import Enum
@@ -247,7 +248,7 @@ def natural_sort(list_to_sort: Iterable[str]) -> list[str]:
247248
return sorted(list_to_sort, key=natural_keys)
248249

249250

250-
def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None:
251+
def quote_specific_tokens(tokens: MutableSequence[str], tokens_to_quote: Iterable[str]) -> None:
251252
"""Quote specific tokens in a list.
252253
253254
:param tokens: token list being edited
@@ -258,7 +259,7 @@ def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None
258259
tokens[i] = su.quote(token)
259260

260261

261-
def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> None:
262+
def unquote_specific_tokens(tokens: MutableSequence[str], tokens_to_unquote: Iterable[str]) -> None:
262263
"""Unquote specific tokens in a list.
263264
264265
:param tokens: token list being edited
@@ -291,7 +292,7 @@ def expand_user(token: str) -> str:
291292
return token
292293

293294

294-
def expand_user_in_tokens(tokens: list[str]) -> None:
295+
def expand_user_in_tokens(tokens: MutableSequence[str]) -> None:
295296
"""Call expand_user() on all tokens in a list of strings.
296297
297298
:param tokens: tokens to expand.
@@ -344,12 +345,12 @@ def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> list[str]:
344345
return [f for f in glob.glob(pattern) if os.path.isfile(f) and os.access(f, access)]
345346

346347

347-
def files_from_glob_patterns(patterns: list[str], access: int = os.F_OK) -> list[str]:
348+
def files_from_glob_patterns(patterns: Iterable[str], access: int = os.F_OK) -> list[str]:
348349
"""Return a list of file paths based on a list of glob patterns.
349350
350351
Only files are returned, not directories, and optionally only files for which the user has a specified access to.
351352
352-
:param patterns: list of file names and/or glob patterns
353+
:param patterns: Iterable of file names and/or glob patterns
353354
:param access: file access type to verify (os.* where * is F_OK, R_OK, W_OK, or X_OK)
354355
:return: list of files matching the names and/or glob patterns
355356
"""

0 commit comments

Comments
 (0)