From 916386aaf2e5cb8ebb017dd63d4e85a8c85d8006 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 18:47:33 -0400 Subject: [PATCH 01/79] Renamed a couple things which needlessely shadowed built-in functions --- cmd2/argparse_custom.py | 14 +++++++------- pyproject.toml | 26 +++++++++++++------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 88676e687..92d930da9 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1069,8 +1069,8 @@ def _format_usage( # End cmd2 customization # build full usage string - format = self._format_actions_usage - action_usage = format(required_options + optionals + positionals, groups) # type: ignore[arg-type] + format_actions = self._format_actions_usage + action_usage = format_actions(required_options + optionals + positionals, groups) # type: ignore[arg-type] usage = ' '.join([s for s in [prog, action_usage] if s]) # wrap the usage parts if it's too long @@ -1080,9 +1080,9 @@ def _format_usage( # break usage into wrappable parts part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' - req_usage = format(required_options, groups) # type: ignore[arg-type] - opt_usage = format(optionals, groups) # type: ignore[arg-type] - pos_usage = format(positionals, groups) # type: ignore[arg-type] + req_usage = format_actions(required_options, groups) # type: ignore[arg-type] + opt_usage = format_actions(optionals, groups) # type: ignore[arg-type] + pos_usage = format_actions(positionals, groups) # type: ignore[arg-type] req_parts = re.findall(part_regexp, req_usage) opt_parts = re.findall(part_regexp, opt_usage) pos_parts = re.findall(part_regexp, pos_usage) @@ -1199,13 +1199,13 @@ def _metavar_formatter( ) -> Callable[[int], Tuple[str, ...]]: metavar = self._determine_metavar(action, default_metavar) - def format(tuple_size: int) -> Tuple[str, ...]: + def format_tuple(tuple_size: int) -> Tuple[str, ...]: if isinstance(metavar, tuple): return metavar else: return (metavar,) * tuple_size - return format + return format_tuple def _format_args(self, action: argparse.Action, default_metavar: Union[str, Tuple[str, ...]]) -> str: """Customized to handle ranged nargs and make other output less verbose""" diff --git a/pyproject.toml b/pyproject.toml index 2ae92f259..1d5475f2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,16 +80,16 @@ disallow_untyped_defs = true exclude = [ "^.git/", "^.venv/", - "^build/", # .build directory - "^docs/", # docs directory + "^build/", # .build directory + "^docs/", # docs directory "^dist/", - "^examples/", # examples directory - "^plugins/*", # plugins directory - "^noxfile\\.py$", # nox config file - "setup\\.py$", # any files named setup.py + "^examples/", # examples directory + "^plugins/*", # plugins directory + "^noxfile\\.py$", # nox config file + "setup\\.py$", # any files named setup.py "^site/", - "^tasks\\.py$", # tasks.py invoke config file - "^tests/", # tests directory + "^tasks\\.py$", # tasks.py invoke config file + "^tests/", # tests directory "^tests_isolated/", # tests_isolated directory ] files = ['.'] @@ -156,7 +156,7 @@ output-format = "full" # McCabe complexity (`C901`) by default. select = [ # https://docs.astral.sh/ruff/rules - # "A", # flake8-builtins (variables or arguments shadowing built-ins) + # "A", # flake8-builtins (variables or arguments shadowing built-ins) "AIR", # Airflow specific warnings # "ANN", # flake8-annotations (missing type annotations for arguments or return types) # "ARG", # flake8-unused-arguments (functions or methods with arguments that are never used) @@ -175,15 +175,15 @@ select = [ # "EM", # flake8-errmsg (warn about exceptions that use string literals that aren't assigned to a variable first) # "ERA", # eradicate (warn about commented-out code) # "EXE", # flake8-executable (warn about files with a shebang present that aren't executable or vice versa) - "F", # Pyflakes (a bunch of common warnings for things like unused imports, imports shadowed by variables, etc) - "FA", # flake8-future-annotations (warn if certain from __future__ imports are used but missing) + "F", # Pyflakes (a bunch of common warnings for things like unused imports, imports shadowed by variables, etc) + "FA", # flake8-future-annotations (warn if certain from __future__ imports are used but missing) "FAST", # FastAPI specific warnings # "FBT", # flake8-boolean-trap (force all boolean arguments passed to functions to be keyword arguments and not positional) # "FIX", # flake8-fixme (warn about lines containing FIXME, TODO, XXX, or HACK) "FLY", # flynt (automatically convert from old school string .format to f-strings) # "FURB", # refurb (A tool for refurbishing and modernizing Python codebases) - "G", # flake8-logging-format (warn about logging statements using outdated string formatting methods) - "I", # isort (sort all import statements in the order established by isort) + "G", # flake8-logging-format (warn about logging statements using outdated string formatting methods) + "I", # isort (sort all import statements in the order established by isort) "ICN", # flake8-import-conventions (force idiomatic import conventions for certain modules typically imported as something else) # "INP", # flake8-no-pep420 (warn about files in the implicit namespace - i.e. force creation of __init__.py files to make packages) "INT", # flake8-gettext (warnings that only apply when you are internationalizing your strings) From cdab42945779fc5c3595a4a3abdc4ec09e8938b4 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 18:50:15 -0400 Subject: [PATCH 02/79] Added make commands for format and lint --- Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Makefile b/Makefile index de2a86955..e97f86035 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,14 @@ check: ## Run code quality tools. @echo "🚀 Static type checking: Running mypy" @uv run mypy +.PHONY: format +format: ## Perform ruff formatting + @uv run ruff format + +.PHONY: lint +lint: ## Perform ruff linting + @uv run ruff check --fix + .PHONY: test test: ## Test the code with pytest. @echo "🚀 Testing code: Running pytest" From b2beaaecbd4e337ac63f8c9669d4caa9fa420b4d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:01:40 -0400 Subject: [PATCH 03/79] Enable ruff DTZ rules and all datetime calls now use an explicit timezone --- cmd2/cmd2.py | 4 ++-- pyproject.toml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 7e34b7dcc..0e0785907 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -2562,7 +2562,7 @@ def onecmd_plus_hooks( redir_saved_state = self._redirect_output(statement) - timestart = datetime.datetime.now() + timestart = datetime.datetime.now(tz=datetime.timezone.utc) # precommand hooks precmd_data = plugin.PrecommandData(statement) @@ -2588,7 +2588,7 @@ def onecmd_plus_hooks( stop = self.postcmd(stop, statement) if self.timing: - self.pfeedback(f'Elapsed: {datetime.datetime.now() - timestart}') + self.pfeedback(f'Elapsed: {datetime.datetime.now(tz=datetime.timezone.utc) - timestart}') finally: # Get sigint protection while we restore stuff with self.sigint_protection: diff --git a/pyproject.toml b/pyproject.toml index 1d5475f2c..42541c09d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,9 +169,9 @@ select = [ # "CPY", # flake8-copyright (warn about missing copyright notice at top of file - currently in preview) # "D", # pydocstyle (warn about things like missing docstrings) # "DOC", # pydoclint (docstring warnings - currently in preview) - "DJ", # flake8-django (Django-specific warnings) - # "DTZ", # flake8-datetimez (warn about datetime calls where no timezone is specified) - "E", # pycodestyle errors (warn about major stylistic issues like mixing spaces and tabs) + "DJ", # flake8-django (Django-specific warnings) + "DTZ", # flake8-datetimez (warn about datetime calls where no timezone is specified) + "E", # pycodestyle errors (warn about major stylistic issues like mixing spaces and tabs) # "EM", # flake8-errmsg (warn about exceptions that use string literals that aren't assigned to a variable first) # "ERA", # eradicate (warn about commented-out code) # "EXE", # flake8-executable (warn about files with a shebang present that aren't executable or vice versa) From 5667864ef2438e39bb936fa39a9f8528310e7b50 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:05:50 -0400 Subject: [PATCH 04/79] Enabled ruff EXE ruleset and fixed files with inconsisentcy between executableness and shebang pressence --- examples/scripts/script.py | 1 - pyproject.toml | 2 +- tests/pyscript/raises_exception.py | 1 - tests/script.py | 1 - tests_isolated/test_commandset/test_categories.py | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/scripts/script.py b/examples/scripts/script.py index 339fbf2c8..5f6f411e3 100644 --- a/examples/scripts/script.py +++ b/examples/scripts/script.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # coding=utf-8 """ Trivial example of a Python script which can be run inside a cmd2 application. diff --git a/pyproject.toml b/pyproject.toml index 42541c09d..732dfcb72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -174,7 +174,7 @@ select = [ "E", # pycodestyle errors (warn about major stylistic issues like mixing spaces and tabs) # "EM", # flake8-errmsg (warn about exceptions that use string literals that aren't assigned to a variable first) # "ERA", # eradicate (warn about commented-out code) - # "EXE", # flake8-executable (warn about files with a shebang present that aren't executable or vice versa) + "EXE", # flake8-executable (warn about files with a shebang present that aren't executable or vice versa) "F", # Pyflakes (a bunch of common warnings for things like unused imports, imports shadowed by variables, etc) "FA", # flake8-future-annotations (warn if certain from __future__ imports are used but missing) "FAST", # FastAPI specific warnings diff --git a/tests/pyscript/raises_exception.py b/tests/pyscript/raises_exception.py index ab4670890..9979b6692 100644 --- a/tests/pyscript/raises_exception.py +++ b/tests/pyscript/raises_exception.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # coding=utf-8 """ Example demonstrating what happens when a Python script raises an exception diff --git a/tests/script.py b/tests/script.py index 339fbf2c8..5f6f411e3 100644 --- a/tests/script.py +++ b/tests/script.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # coding=utf-8 """ Trivial example of a Python script which can be run inside a cmd2 application. diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index 986ae3fa9..533e02e0c 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # coding=utf-8 """ Simple example demonstrating basic CommandSet usage. From 9c035e4d21b7bb0149d1623e9b5f506aa1515da4 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:07:01 -0400 Subject: [PATCH 05/79] Enable ruff FIX ruleset that warns about FIXME or TODO in code --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 732dfcb72..3585c9deb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -179,7 +179,7 @@ select = [ "FA", # flake8-future-annotations (warn if certain from __future__ imports are used but missing) "FAST", # FastAPI specific warnings # "FBT", # flake8-boolean-trap (force all boolean arguments passed to functions to be keyword arguments and not positional) - # "FIX", # flake8-fixme (warn about lines containing FIXME, TODO, XXX, or HACK) + "FIX", # flake8-fixme (warn about lines containing FIXME, TODO, XXX, or HACK) "FLY", # flynt (automatically convert from old school string .format to f-strings) # "FURB", # refurb (A tool for refurbishing and modernizing Python codebases) "G", # flake8-logging-format (warn about logging statements using outdated string formatting methods) From 704c5b0f5bf6311718d5a962a92195a223dcf9c9 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:07:40 -0400 Subject: [PATCH 06/79] Enabled ruff FURB ruleset --- pyproject.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3585c9deb..b5bb4cca2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -179,12 +179,12 @@ select = [ "FA", # flake8-future-annotations (warn if certain from __future__ imports are used but missing) "FAST", # FastAPI specific warnings # "FBT", # flake8-boolean-trap (force all boolean arguments passed to functions to be keyword arguments and not positional) - "FIX", # flake8-fixme (warn about lines containing FIXME, TODO, XXX, or HACK) - "FLY", # flynt (automatically convert from old school string .format to f-strings) - # "FURB", # refurb (A tool for refurbishing and modernizing Python codebases) - "G", # flake8-logging-format (warn about logging statements using outdated string formatting methods) - "I", # isort (sort all import statements in the order established by isort) - "ICN", # flake8-import-conventions (force idiomatic import conventions for certain modules typically imported as something else) + "FIX", # flake8-fixme (warn about lines containing FIXME, TODO, XXX, or HACK) + "FLY", # flynt (automatically convert from old school string .format to f-strings) + "FURB", # refurb (A tool for refurbishing and modernizing Python codebases) + "G", # flake8-logging-format (warn about logging statements using outdated string formatting methods) + "I", # isort (sort all import statements in the order established by isort) + "ICN", # flake8-import-conventions (force idiomatic import conventions for certain modules typically imported as something else) # "INP", # flake8-no-pep420 (warn about files in the implicit namespace - i.e. force creation of __init__.py files to make packages) "INT", # flake8-gettext (warnings that only apply when you are internationalizing your strings) # "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) From 53d97a2c25cdef1f030fbda7700250a68c69a229 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:08:39 -0400 Subject: [PATCH 07/79] Enable ruff ISC ruleset that warns about implict string concatenation --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b5bb4cca2..99a78fb4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -187,7 +187,7 @@ select = [ "ICN", # flake8-import-conventions (force idiomatic import conventions for certain modules typically imported as something else) # "INP", # flake8-no-pep420 (warn about files in the implicit namespace - i.e. force creation of __init__.py files to make packages) "INT", # flake8-gettext (warnings that only apply when you are internationalizing your strings) - # "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) + "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) # "LOG", # flake8-logging (warn about potential logger issues, but very pedantic) # "N", # pep8-naming (force idiomatic naming for classes, functions/methods, and variables/arguments) "NPY", # NumPy specific rules From f0ab9b97abbbb2918471859ca8f522497aa50bcc Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:09:11 -0400 Subject: [PATCH 08/79] Enable ruff LOG ruleset which warns about logger issues --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 99a78fb4a..861e78c54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -188,7 +188,7 @@ select = [ # "INP", # flake8-no-pep420 (warn about files in the implicit namespace - i.e. force creation of __init__.py files to make packages) "INT", # flake8-gettext (warnings that only apply when you are internationalizing your strings) "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) - # "LOG", # flake8-logging (warn about potential logger issues, but very pedantic) + "LOG", # flake8-logging (warn about potential logger issues, but very pedantic) # "N", # pep8-naming (force idiomatic naming for classes, functions/methods, and variables/arguments) "NPY", # NumPy specific rules "PD", # pandas-vet (Pandas specific rules) From e1b2cd89a74348a0388a00b9e029b7019b94cbd4 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:13:25 -0400 Subject: [PATCH 09/79] Enabled ruff PLC ruleset for Pylint naming conventions and exempted one example file --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 861e78c54..af7a60a9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -195,7 +195,7 @@ select = [ # "PERF", # Perflint (warn about performance issues) # "PGH", # pygrep-hooks (force specific rule codes when ignoring type or linter issues on a line) # "PIE", # flake8-pie (eliminate unnecessary use of pass, range starting at 0, etc.) - # "PLC", # Pylint Conventions + "PLC", # Pylint Conventions "PLE", # Pylint Errors # "PLR", # Pylint Refactoring suggestions # "PLW", # Pylint Warnings @@ -250,6 +250,10 @@ per-file-ignores."examples/scripts/*.py" = [ "F821", # Undefined name `app` ] +per-file-ignores."examples/unicode_commands.py" = [ + "PLC2401", # Allow non-ASCII characters in function names +] + per-file-ignores."tests/pyscript/*.py" = [ "F821", # Undefined name `app` ] From a09ca980dcb103b48ec3789b638b5e9c59daf34b Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:16:14 -0400 Subject: [PATCH 10/79] Accepted a few recommended refactorings from ruff PLR ruleset --- cmd2/argparse_custom.py | 13 ++++++----- cmd2/cmd2.py | 48 +++++++++++++++++++---------------------- cmd2/transcript.py | 45 +++++++++++++++++++------------------- cmd2/utils.py | 7 +++--- 4 files changed, 53 insertions(+), 60 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 92d930da9..d721908e0 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -400,13 +400,12 @@ def __init__( 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' - ) + elif 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 diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 0e0785907..b88345ffa 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1944,8 +1944,7 @@ def _display_matches_gnu_readline( for cur_match in matches_to_display: cur_length = ansi.style_aware_wcswidth(cur_match) - if cur_length > longest_match_length: - longest_match_length = cur_length + longest_match_length = max(longest_match_length, cur_length) else: matches_to_display = matches @@ -2106,12 +2105,11 @@ def _perform_completion( completer_func = self.completedefault # type: ignore[assignment] # Not a recognized macro or command + # Check if this command should be run as a shell command + elif self.default_to_shell and command in utils.get_exes_in_path(command): + completer_func = self.path_complete else: - # Check if this command should be run as a shell command - if self.default_to_shell and command in utils.get_exes_in_path(command): - completer_func = self.path_complete - else: - completer_func = self.completedefault # type: ignore[assignment] + completer_func = self.completedefault # type: ignore[assignment] # Otherwise we are completing the command token or performing custom completion else: @@ -3232,25 +3230,24 @@ def restore_readline() -> None: sys.stdout.write(f'{prompt}{line}\n') # Otherwise read from self.stdin + elif self.stdin.isatty(): + # on a tty, print the prompt first, then read the line + self.poutput(prompt, end='') + self.stdout.flush() + line = self.stdin.readline() + if len(line) == 0: + line = 'eof' else: - if self.stdin.isatty(): - # on a tty, print the prompt first, then read the line - self.poutput(prompt, end='') - self.stdout.flush() - line = self.stdin.readline() - if len(line) == 0: - line = 'eof' + # we are reading from a pipe, read the line to see if there is + # anything there, if so, then decide whether to print the + # prompt or not + line = self.stdin.readline() + if len(line): + # we read something, output the prompt and the something + if self.echo: + self.poutput(f'{prompt}{line}') else: - # we are reading from a pipe, read the line to see if there is - # anything there, if so, then decide whether to print the - # prompt or not - line = self.stdin.readline() - if len(line): - # we read something, output the prompt and the something - if self.echo: - self.poutput(f'{prompt}{line}') - else: - line = 'eof' + line = 'eof' return line.rstrip('\r\n') @@ -3660,8 +3657,7 @@ def _macro_create(self, args: argparse.Namespace) -> None: return arg_nums.add(cur_num) - if cur_num > max_arg_num: - max_arg_num = cur_num + max_arg_num = max(max_arg_num, cur_num) arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=False)) diff --git a/cmd2/transcript.py b/cmd2/transcript.py index f4781fd99..d9c2d26e7 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -204,28 +204,27 @@ def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> Tuple[str, # slash at the beginning of the string, so it can't be # escaped. We found it. break - else: - # check if the slash is preceded by a backslash - if s[pos - 1 : pos] == '\\': - # it is. - if in_regex: - # add everything up to the backslash as a - # regular expression - regex += s[start : pos - 1] - # skip the backslash, and add the slash - regex += s[pos] - else: - # add everything up to the backslash as escaped - # plain text - regex += re.escape(s[start : pos - 1]) - # and then add the slash as escaped - # plain text - regex += re.escape(s[pos]) - # update start to show we have handled everything - # before it - start = pos + 1 - # and continue to look + # check if the slash is preceded by a backslash + elif s[pos - 1 : pos] == '\\': + # it is. + if in_regex: + # add everything up to the backslash as a + # regular expression + regex += s[start : pos - 1] + # skip the backslash, and add the slash + regex += s[pos] else: - # slash is not escaped, this is what we are looking for - break + # add everything up to the backslash as escaped + # plain text + regex += re.escape(s[start : pos - 1]) + # and then add the slash as escaped + # plain text + regex += re.escape(s[pos]) + # update start to show we have handled everything + # before it + start = pos + 1 + # and continue to look + else: + # slash is not escaped, this is what we are looking for + break return regex, pos, start diff --git a/cmd2/utils.py b/cmd2/utils.py index 2ad864171..d9c71e9cd 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -1168,11 +1168,10 @@ def do_echo(self, arglist): if isinstance(func, Iterable): for item in func: setattr(item, constants.CMD_ATTR_HELP_CATEGORY, category) + elif inspect.ismethod(func): + setattr(func.__func__, constants.CMD_ATTR_HELP_CATEGORY, category) # type: ignore[attr-defined] else: - if inspect.ismethod(func): - setattr(func.__func__, constants.CMD_ATTR_HELP_CATEGORY, category) # type: ignore[attr-defined] - else: - setattr(func, constants.CMD_ATTR_HELP_CATEGORY, category) + setattr(func, constants.CMD_ATTR_HELP_CATEGORY, category) def get_defining_class(meth: Callable[..., Any]) -> Optional[Type[Any]]: From a13088905775a7153888d0828fae9591cfed3d17 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:17:48 -0400 Subject: [PATCH 11/79] Accepted one automated change from ruff PT ruleset --- tests_isolated/test_commandset/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index c8c6d34b4..23ea7e741 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -191,7 +191,7 @@ def command_sets_app(): return app -@fixture() +@fixture def command_sets_manual(): app = WithCommandSets(auto_load_commands=False) return app From 1733245aa454ede0fd10f5be6fca78a2e7207cba Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:22:42 -0400 Subject: [PATCH 12/79] Unabled ruff PYI ruleset for type hint best practices --- cmd2/argparse_custom.py | 2 +- cmd2/table_creator.py | 7 +++---- cmd2/utils.py | 2 +- pyproject.toml | 8 ++++++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index d721908e0..7823c7a6e 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -264,7 +264,7 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens) ) -def generate_range_error(range_min: int, range_max: Union[int, float]) -> str: +def generate_range_error(range_min: int, range_max: float) -> str: """Generate an error message when the the number of arguments provided is not within the expected range""" err_str = "expected " diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index 285253c82..e850e41ae 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -21,7 +21,6 @@ Optional, Sequence, Tuple, - Union, ) from wcwidth import ( # type: ignore[import] @@ -69,7 +68,7 @@ def __init__( data_horiz_align: HorizontalAlignment = HorizontalAlignment.LEFT, data_vert_align: VerticalAlignment = VerticalAlignment.TOP, style_data_text: bool = True, - max_data_lines: Union[int, float] = constants.INFINITY, + max_data_lines: float = constants.INFINITY, ) -> None: """ Column initializer @@ -156,7 +155,7 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None: col.width = max(1, ansi.widest_line(col.header)) @staticmethod - def _wrap_long_word(word: str, max_width: int, max_lines: Union[int, float], is_last_word: bool) -> Tuple[str, int, int]: + def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> Tuple[str, int, int]: """ Used by _wrap_text() to wrap a long word over multiple lines @@ -220,7 +219,7 @@ def _wrap_long_word(word: str, max_width: int, max_lines: Union[int, float], is_ return wrapped_buf.getvalue(), total_lines, cur_line_width @staticmethod - def _wrap_text(text: str, max_width: int, max_lines: Union[int, float]) -> str: + def _wrap_text(text: str, max_width: int, max_lines: float) -> str: """ Wrap text into lines with a display width no longer than max_width. This function breaks words on whitespace boundaries. If a word is longer than the space remaining on a line, then it will start on a new line. diff --git a/cmd2/utils.py b/cmd2/utils.py index d9c71e9cd..f9d2e7271 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -709,7 +709,7 @@ def __bool__(self) -> bool: def __enter__(self) -> None: self.__count += 1 - def __exit__(self, *args: Any) -> None: + def __exit__(self, *args: object) -> None: self.__count -= 1 if self.__count < 0: raise ValueError("count has gone below 0") diff --git a/pyproject.toml b/pyproject.toml index af7a60a9b..9f246df12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -201,7 +201,7 @@ select = [ # "PLW", # Pylint Warnings # "PT", # flake8-pytest-style (warnings about unit test best practices) # "PTH", # flake8-use-pathlib (force use of pathlib instead of os.path) - # "PYI", # flake8-pyi (warnings related to type hint best practices) + "PYI", # flake8-pyi (warnings related to type hint best practices) # "Q", # flake8-quotes (force double quotes) # "RET", # flake8-return (various warnings related to implicit vs explicit return statements) "RSE", # flake8-raise (warn about unnecessary parentheses on raised exceptions) @@ -238,6 +238,10 @@ per-file-ignores."cmd2/__init__.py" = [ "F401", # Unused import ] +per-file-ignores."cmd2/cmd2.py" = [ + "PYI024", # Use `typing.NamedTuple` instead of `collections.namedtuple` +] + per-file-ignores."docs/conf.py" = [ "F401", # Unused import ] @@ -251,7 +255,7 @@ per-file-ignores."examples/scripts/*.py" = [ ] per-file-ignores."examples/unicode_commands.py" = [ - "PLC2401", # Allow non-ASCII characters in function names + "PLC2401", # non-ASCII characters in function names ] per-file-ignores."tests/pyscript/*.py" = [ From b4f143cf7a9735ce22fafacabdecc23727d81d7a Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:27:05 -0400 Subject: [PATCH 13/79] Accept various automated refactorings from ruff RET ruleset that eliminated else case when the if had a return --- cmd2/argparse_custom.py | 39 +++++++++++++++---------------- cmd2/cmd2.py | 51 ++++++++++++++++++----------------------- cmd2/decorators.py | 5 ++-- cmd2/history.py | 5 ++-- cmd2/parsing.py | 3 +-- cmd2/py_bridge.py | 3 +-- cmd2/rl_utils.py | 16 +++++-------- cmd2/table_creator.py | 8 +++---- cmd2/transcript.py | 39 +++++++++++++++---------------- cmd2/utils.py | 13 ++++------- 10 files changed, 79 insertions(+), 103 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 7823c7a6e..661c79ee6 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -477,7 +477,7 @@ def _action_set_choices_callable(self: argparse.Action, choices_callable: Choice if self.choices is not None: err_msg = "None of the following parameters can be used alongside a choices parameter:\nchoices_provider, completer" raise (TypeError(err_msg)) - elif self.nargs == 0: + if self.nargs == 0: err_msg = ( "None of the following parameters can be used on an action that takes no arguments:\nchoices_provider, completer" ) @@ -1155,24 +1155,22 @@ def _format_action_invocation(self, action: argparse.Action) -> str: (metavar,) = self._metavar_formatter(action, default)(1) return metavar - else: - parts: List[str] = [] + parts: List[str] = [] - # if the Optional doesn't take a value, format is: - # -s, --long - if action.nargs == 0: - parts.extend(action.option_strings) - return ', '.join(parts) + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + return ', '.join(parts) - # Begin cmd2 customization (less verbose) - # if the Optional takes a value, format is: - # -s, --long ARGS - else: - default = self._get_default_metavar_for_optional(action) - args_string = self._format_args(action, default) + # Begin cmd2 customization (less verbose) + # if the Optional takes a value, format is: + # -s, --long ARGS + default = self._get_default_metavar_for_optional(action) + args_string = self._format_args(action, default) - return ', '.join(action.option_strings) + ' ' + args_string - # End cmd2 customization + return ', '.join(action.option_strings) + ' ' + args_string + # End cmd2 customization def _determine_metavar( self, @@ -1201,8 +1199,7 @@ def _metavar_formatter( def format_tuple(tuple_size: int) -> Tuple[str, ...]: if isinstance(metavar, tuple): return metavar - else: - return (metavar,) * tuple_size + return (metavar,) * tuple_size return format_tuple @@ -1223,12 +1220,12 @@ def _format_args(self, action: argparse.Action, default_metavar: Union[str, Tupl # Make this output less verbose. Do not customize the output when metavar is a # tuple of strings. Allow argparse's formatter to handle that instead. - elif isinstance(metavar, str): + if isinstance(metavar, str): if action.nargs == ZERO_OR_MORE: return '[%s [...]]' % metavar_formatter(1) - elif action.nargs == ONE_OR_MORE: + if action.nargs == ONE_OR_MORE: return '%s [...]' % metavar_formatter(1) - elif isinstance(action.nargs, int) and action.nargs > 1: + if isinstance(action.nargs, int) and action.nargs > 1: return '{}{{{}}}'.format('%s' % metavar_formatter(1), action.nargs) return super()._format_args(action, default_metavar) # type: ignore[arg-type] diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index b88345ffa..292bce5bc 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1728,12 +1728,11 @@ def complete_users() -> List[str]: return complete_users() # Otherwise expand the user dir - else: - search_str = os.path.expanduser(search_str) + search_str = os.path.expanduser(search_str) - # Get what we need to restore the original tilde path later - orig_tilde_path = text[:sep_index] - expanded_tilde_path = os.path.expanduser(orig_tilde_path) + # Get what we need to restore the original tilde path later + orig_tilde_path = text[:sep_index] + expanded_tilde_path = os.path.expanduser(orig_tilde_path) # If the search text does not have a directory, then use the cwd elif not os.path.dirname(text): @@ -1804,10 +1803,9 @@ def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, return utils.get_exes_in_path(text) # Otherwise look for executables in the given path - else: - return self.path_complete( - text, line, begidx, endidx, path_filter=lambda path: os.path.isdir(path) or os.access(path, os.X_OK) - ) + return self.path_complete( + text, line, begidx, endidx, path_filter=lambda path: os.path.isdir(path) or os.access(path, os.X_OK) + ) def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> List[str]: """Called by complete() as the first tab completion function for all commands @@ -1878,12 +1876,12 @@ def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, com if do_shell_completion: return self.shell_cmd_complete(text, line, begidx, endidx) - elif do_path_completion: + if do_path_completion: return self.path_complete(text, line, begidx, endidx) # If there were redirection strings anywhere on the command line, then we # are no longer tab completing for the current command - elif has_redirection: + if has_redirection: return [] # Call the command's completer function @@ -2771,9 +2769,8 @@ def combine_rl_history(statement: Statement) -> None: if not statement.command: raise EmptyStatement - else: - # If necessary, update history with completed multiline command. - combine_rl_history(statement) + # If necessary, update history with completed multiline command. + combine_rl_history(statement) return statement @@ -2940,10 +2937,9 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: subproc_stdin.close() new_stdout.close() raise RedirectionError(f'Pipe process exited with code {proc.returncode} before command could run') - else: - redir_saved_state.redirecting = True # type: ignore[unreachable] - cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr) - sys.stdout = self.stdout = new_stdout + redir_saved_state.redirecting = True # type: ignore[unreachable] + cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr) + sys.stdout = self.stdout = new_stdout elif statement.output: if statement.output_to: @@ -3080,14 +3076,13 @@ def default(self, statement: Statement) -> Optional[bool]: # type: ignore[overr self.history.append(statement) return self.do_shell(statement.command_and_args) - else: - err_msg = self.default_error.format(statement.command) - if self.suggest_similar_command and (suggested_command := self._suggest_similar_command(statement.command)): - err_msg += f"\n{self.default_suggestion_message.format(suggested_command)}" + err_msg = self.default_error.format(statement.command) + if self.suggest_similar_command and (suggested_command := self._suggest_similar_command(statement.command)): + err_msg += f"\n{self.default_suggestion_message.format(suggested_command)}" - # Set apply_style to False so styles for default_error and default_suggestion_message are not overridden - self.perror(err_msg, apply_style=False) - return None + # Set apply_style to False so styles for default_error and default_suggestion_message are not overridden + self.perror(err_msg, apply_style=False) + return None def _suggest_similar_command(self, command: str) -> Optional[str]: return suggest_similar(command, self.get_visible_commands()) @@ -5092,8 +5087,7 @@ def _current_script_dir(self) -> Optional[str]: """Accessor to get the current script directory from the _script_dir LIFO queue.""" if self._script_dir: return self._script_dir[-1] - else: - return None + return None run_script_description = ( "Run commands in script file that is encoded as either ASCII or UTF-8 text\n" @@ -5748,5 +5742,4 @@ def _resolve_func_self( # Case 3: There exists exactly 1 CommandSet that is a sub-class match of the function's CommandSet func_self = candidate_sets[0] return func_self - else: - return self + return self diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 343beaa2e..3d1105c64 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -195,8 +195,7 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> Optional[bool]: if callable(func_arg): return arg_decorator(func_arg) - else: - return arg_decorator + return arg_decorator def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: @@ -245,7 +244,7 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: break # Need to save required args so they can be prepended to the subcommand usage - elif action.required: + if action.required: req_args.append(action.dest) diff --git a/cmd2/history.py b/cmd2/history.py index 289615c85..5f9758f96 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -211,10 +211,9 @@ def get(self, index: int) -> HistoryItem: """ if index == 0: raise IndexError('The first command in history is command 1.') - elif index < 0: + if index < 0: return self[index] - else: - return self[index - 1] + return self[index - 1] # This regular expression parses input for the span() method. There are five parts: # diff --git a/cmd2/parsing.py b/cmd2/parsing.py index c42ac22b3..9ab534c99 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -636,8 +636,7 @@ def get_command_arg_list( if preserve_quotes: return to_parse, to_parse.arg_list - else: - return to_parse, to_parse.argv[1:] + return to_parse, to_parse.argv[1:] def _expand(self, line: str) -> str: """Expand aliases and shortcuts""" diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 2955cfa3d..177eed673 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -78,8 +78,7 @@ def __bool__(self) -> bool: return bool(self.data) # Otherwise check if stderr was filled out - else: - return not self.stderr + return not self.stderr class PyBridge: diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index b02aa73a2..c591d29e2 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -187,11 +187,10 @@ def rl_get_point() -> int: # pragma: no cover if rl_type == RlType.GNU: return ctypes.c_int.in_dll(readline_lib, "rl_point").value - elif rl_type == RlType.PYREADLINE: + if rl_type == RlType.PYREADLINE: return int(readline.rl.mode.l_buffer.point) - else: - return 0 + return 0 def rl_get_prompt() -> str: # pragma: no cover @@ -230,8 +229,7 @@ def rl_get_display_prompt() -> str: # pragma: no cover else: prompt = encoded_prompt.decode(encoding='utf-8') return rl_unescape_prompt(prompt) - else: - return rl_get_prompt() + return rl_get_prompt() def rl_set_prompt(prompt: str) -> None: # pragma: no cover @@ -278,8 +276,7 @@ def rl_escape_prompt(prompt: str) -> str: return result - else: - return prompt + return prompt def rl_unescape_prompt(prompt: str) -> str: @@ -302,7 +299,7 @@ def rl_in_search_mode() -> bool: # pragma: no cover readline_state = ctypes.c_int.in_dll(readline_lib, "rl_readline_state").value return bool(IN_SEARCH_MODE & readline_state) - elif rl_type == RlType.PYREADLINE: + if rl_type == RlType.PYREADLINE: from pyreadline3.modes.emacs import ( # type: ignore[import] EmacsMode, ) @@ -317,5 +314,4 @@ def rl_in_search_mode() -> bool: # pragma: no cover readline.rl.mode._process_non_incremental_search_keyevent, ) return readline.rl.mode.process_keyevent_queue[-1] in search_funcs - else: - return False + return False diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index e850e41ae..d28b63a0e 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -98,8 +98,7 @@ def __init__( if width is not None and width < 1: raise ValueError("Column width cannot be less than 1") - else: - self.width: int = width if width is not None else -1 + 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 @@ -1100,10 +1099,9 @@ def apply_data_bg(self, value: Any) -> str: """ if self.row_num % 2 == 0 and self.even_bg is not None: return ansi.style(value, bg=self.even_bg) - elif self.row_num % 2 != 0 and self.odd_bg is not None: + if self.row_num % 2 != 0 and self.odd_bg is not None: return ansi.style(value, bg=self.odd_bg) - else: - return str(value) + return str(value) def generate_data_row(self, row_data: Sequence[Any]) -> str: """ diff --git a/cmd2/transcript.py b/cmd2/transcript.py index d9c2d26e7..309087ad3 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -162,25 +162,24 @@ def _transform_transcript_expected(self, s: str) -> str: # no more slashes, add the rest of the string and bail regex += re.escape(s[start:]) break + # there is a slash, add everything we have found so far + # add stuff before the first slash as plain text + regex += re.escape(s[start:first_slash_pos]) + start = first_slash_pos + 1 + # and go find the next one + (regex, second_slash_pos, start) = self._escaped_find(regex, s, start, True) + if second_slash_pos > 0: + # add everything between the slashes (but not the slashes) + # as a regular expression + regex += s[start:second_slash_pos] + # and change where we start looking for slashed on the + # turn through the loop + start = second_slash_pos + 1 else: - # there is a slash, add everything we have found so far - # add stuff before the first slash as plain text - regex += re.escape(s[start:first_slash_pos]) - start = first_slash_pos + 1 - # and go find the next one - (regex, second_slash_pos, start) = self._escaped_find(regex, s, start, True) - if second_slash_pos > 0: - # add everything between the slashes (but not the slashes) - # as a regular expression - regex += s[start:second_slash_pos] - # and change where we start looking for slashed on the - # turn through the loop - start = second_slash_pos + 1 - else: - # No closing slash, we have to add the first slash, - # and the rest of the text - regex += re.escape(s[start - 1 :]) - break + # No closing slash, we have to add the first slash, + # and the rest of the text + regex += re.escape(s[start - 1 :]) + break return regex @staticmethod @@ -200,12 +199,12 @@ def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> Tuple[str, if pos == -1: # no match, return to caller break - elif pos == 0: + if pos == 0: # slash at the beginning of the string, so it can't be # escaped. We found it. break # check if the slash is preceded by a backslash - elif s[pos - 1 : pos] == '\\': + if s[pos - 1 : pos] == '\\': # it is. if in_regex: # add everything up to the backslash as a diff --git a/cmd2/utils.py b/cmd2/utils.py index f9d2e7271..176503354 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -106,13 +106,12 @@ def to_bool(val: Any) -> bool: if isinstance(val, str): if val.capitalize() == str(True): return True - elif val.capitalize() == str(False): + if val.capitalize() == str(False): return False raise ValueError("must be True or False (case-insensitive)") - elif isinstance(val, bool): + if isinstance(val, bool): return val - else: - return bool(val) + return bool(val) class Settable: @@ -536,8 +535,7 @@ def isatty(self) -> bool: """StdSim only considered an interactive stream if `echo` is True and `inner_stream` is a tty.""" if self.echo: return self.inner_stream.isatty() - else: - return False + return False @property def line_buffering(self) -> bool: @@ -553,8 +551,7 @@ def line_buffering(self) -> bool: def __getattr__(self, item: str) -> Any: if item in self.__dict__: return self.__dict__[item] - else: - return getattr(self.inner_stream, item) + return getattr(self.inner_stream, item) class ByteBuf: From 263aa3ff18c0e89ce346ef28b9747345480eee37 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 19:29:12 -0400 Subject: [PATCH 14/79] Accepted some automated refactorings from the ruff RUF ruleset --- cmd2/cmd2.py | 14 +++++--------- cmd2/parsing.py | 2 +- examples/modular_commands_main.py | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 292bce5bc..86ce611b2 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -944,7 +944,7 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: subcommand_valid, errmsg = self.statement_parser.is_valid_command(subcommand_name, is_subcommand=True) if not subcommand_valid: - raise CommandSetRegistrationError(f'Subcommand {str(subcommand_name)} is not valid: {errmsg}') + raise CommandSetRegistrationError(f'Subcommand {subcommand_name!s} is not valid: {errmsg}') command_tokens = full_command_name.split() command_name = command_tokens[0] @@ -957,13 +957,11 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: command_func = self.cmd_func(command_name) if command_func is None: - raise CommandSetRegistrationError( - f"Could not find command '{command_name}' needed by subcommand: {str(method)}" - ) + raise CommandSetRegistrationError(f"Could not find command '{command_name}' needed by subcommand: {method!s}") command_parser = self._command_parsers.get(command_func) if command_parser is None: raise CommandSetRegistrationError( - f"Could not find argparser for command '{command_name}' needed by subcommand: {str(method)}" + f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}" ) def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser: @@ -1059,15 +1057,13 @@ def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: if command_func is None: # pragma: no cover # This really shouldn't be possible since _register_subcommands would prevent this from happening # but keeping in case it does for some strange reason - raise CommandSetRegistrationError( - f"Could not find command '{command_name}' needed by subcommand: {str(method)}" - ) + raise CommandSetRegistrationError(f"Could not find command '{command_name}' needed by subcommand: {method!s}") command_parser = self._command_parsers.get(command_func) if command_parser is None: # pragma: no cover # This really shouldn't be possible since _register_subcommands would prevent this from happening # but keeping in case it does for some strange reason raise CommandSetRegistrationError( - f"Could not find argparser for command '{command_name}' needed by subcommand: {str(method)}" + f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}" ) for action in command_parser._actions: diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 9ab534c99..598cbe687 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -340,7 +340,7 @@ def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> Tuple[b valid = False if not isinstance(word, str): - return False, f'must be a string. Received {str(type(word))} instead' # type: ignore[unreachable] + return False, f'must be a string. Received {type(word)!s} instead' # type: ignore[unreachable] if not word: return False, 'cannot be an empty string' diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index e544b3db0..740078671 100755 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -18,7 +18,7 @@ from modular_commands.commandset_complex import ( # noqa: F401 CommandSetA, ) -from modular_commands.commandset_custominit import ( # noqa: F401 +from modular_commands.commandset_custominit import ( CustomInitCommandSet, ) From 374a885bc16459a013ff6b0786ba3ac1f4d3c5c0 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 20:19:31 -0400 Subject: [PATCH 15/79] Enable ruff TC type checking ruleset and accept automated refactorings --- cmd2/ansi.py | 2 +- cmd2/argparse_custom.py | 10 ++++----- cmd2/clipboard.py | 2 +- cmd2/cmd2.py | 50 ++++++++++++++++++++--------------------- cmd2/py_bridge.py | 10 ++++----- cmd2/transcript.py | 4 ++-- cmd2/utils.py | 4 ++-- pyproject.toml | 2 +- 8 files changed, 42 insertions(+), 42 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 8242957ae..e73b7c091 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -106,7 +106,7 @@ def style_aware_wcswidth(text: str) -> int: then this function returns -1. Replace tabs with spaces before calling this. """ # Strip ANSI style sequences since they cause wcswidth to return -1 - return cast(int, wcswidth(strip_style(text))) + return cast('int', wcswidth(strip_style(text))) def widest_line(text: str) -> int: diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 661c79ee6..43de7372a 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -455,7 +455,7 @@ def _action_get_choices_callable(self: argparse.Action) -> Optional[ChoicesCalla :param self: argparse Action being queried :return: A ChoicesCallable instance or None if attribute does not exist """ - return cast(Optional[ChoicesCallable], getattr(self, ATTR_CHOICES_CALLABLE, None)) + return cast("Optional[ChoicesCallable]", getattr(self, ATTR_CHOICES_CALLABLE, None)) setattr(argparse.Action, 'get_choices_callable', _action_get_choices_callable) @@ -545,7 +545,7 @@ def _action_get_descriptive_header(self: argparse.Action) -> Optional[str]: :param self: argparse Action being queried :return: The value of descriptive_header or None if attribute does not exist """ - return cast(Optional[str], getattr(self, ATTR_DESCRIPTIVE_HEADER, None)) + return cast("Optional[str]", getattr(self, ATTR_DESCRIPTIVE_HEADER, None)) setattr(argparse.Action, 'get_descriptive_header', _action_get_descriptive_header) @@ -582,7 +582,7 @@ def _action_get_nargs_range(self: argparse.Action) -> Optional[Tuple[int, Union[ :param self: argparse Action being queried :return: The value of nargs_range or None if attribute does not exist """ - return cast(Optional[Tuple[int, Union[int, float]]], getattr(self, ATTR_NARGS_RANGE, None)) + return cast("Optional[Tuple[int, Union[int, float]]]", getattr(self, ATTR_NARGS_RANGE, None)) setattr(argparse.Action, 'get_nargs_range', _action_get_nargs_range) @@ -619,7 +619,7 @@ def _action_get_suppress_tab_hint(self: argparse.Action) -> bool: :param self: argparse Action being queried :return: The value of suppress_tab_hint or False if attribute does not exist """ - return cast(bool, getattr(self, ATTR_SUPPRESS_TAB_HINT, False)) + return cast("bool", getattr(self, ATTR_SUPPRESS_TAB_HINT, False)) setattr(argparse.Action, 'get_suppress_tab_hint', _action_get_suppress_tab_hint) @@ -929,7 +929,7 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti :param self: ArgumentParser being queried :return: An ArgparseCompleter-based class or None if attribute does not exist """ - return cast(Optional[Type['ArgparseCompleter']], getattr(self, ATTR_AP_COMPLETER_TYPE, None)) + return cast("Optional[Type[ArgparseCompleter]]", getattr(self, ATTR_AP_COMPLETER_TYPE, None)) setattr(argparse.ArgumentParser, 'get_ap_completer_type', _ArgumentParser_get_ap_completer_type) diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index 454e3484c..30cb1a670 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -13,7 +13,7 @@ def get_paste_buffer() -> str: :return: contents of the clipboard """ - pb_str = typing.cast(str, pyperclip.paste()) + pb_str = typing.cast("str", pyperclip.paste()) return pb_str diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 86ce611b2..9754d494b 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -183,7 +183,7 @@ ) rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters") - orig_rl_basic_quotes = cast(bytes, ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value) + orig_rl_basic_quotes = cast("bytes", ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value) class _SavedReadlineSettings: @@ -715,7 +715,7 @@ def register_command_set(self, cmdset: CommandSet) -> None: cmdset.on_register(self) methods = cast( - List[Tuple[str, Callable[..., Any]]], + "List[Tuple[str, Callable[..., Any]]]", inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] @@ -978,7 +978,7 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> target_parser = find_subcommand(command_parser, subcommand_names) - subcmd_parser = cast(argparse.ArgumentParser, self._build_parser(cmdset, subcmd_parser_builder)) + subcmd_parser = cast("argparse.ArgumentParser", self._build_parser(cmdset, subcmd_parser_builder)) from .decorators import ( _set_parser_prog, ) @@ -1161,7 +1161,7 @@ def allow_style_type(value: str) -> ansi.AllowStyle: 'Allow ANSI text style sequences in output (valid values: ' f'{ansi.AllowStyle.ALWAYS}, {ansi.AllowStyle.NEVER}, {ansi.AllowStyle.TERMINAL})', self, - choices_provider=cast(ChoicesProviderFunc, get_allow_style_choices), + choices_provider=cast("ChoicesProviderFunc", get_allow_style_choices), ) ) @@ -1953,7 +1953,7 @@ def _display_matches_gnu_readline( # rl_display_match_list() expects matches to be in argv format where # substitution is the first element, followed by the matches, and then a NULL. - strings_array = cast(List[Optional[bytes]], (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) + strings_array = cast("List[Optional[bytes]]", (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) # Copy in the encoded strings and add a NULL to the end strings_array[0] = encoded_substitution @@ -2877,7 +2877,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: # Initialize the redirection saved state redir_saved_state = utils.RedirectionSavedState( - cast(TextIO, self.stdout), sys.stdout, self._cur_pipe_proc_reader, self._redirecting + cast("TextIO", self.stdout), sys.stdout, self._cur_pipe_proc_reader, self._redirecting ) # The ProcReader for this command @@ -2893,7 +2893,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: # Open each side of the pipe subproc_stdin = io.open(read_fd, 'r') - new_stdout: TextIO = cast(TextIO, io.open(write_fd, 'w')) + new_stdout: TextIO = cast("TextIO", io.open(write_fd, 'w')) # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs, # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe @@ -2934,7 +2934,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: new_stdout.close() raise RedirectionError(f'Pipe process exited with code {proc.returncode} before command could run') redir_saved_state.redirecting = True # type: ignore[unreachable] - cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr) + cmd_pipe_proc_reader = utils.ProcReader(proc, cast("TextIO", self.stdout), sys.stderr) sys.stdout = self.stdout = new_stdout elif statement.output: @@ -2944,7 +2944,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: mode = 'a' if statement.output == constants.REDIRECTION_APPEND else 'w' try: # Use line buffering - new_stdout = cast(TextIO, open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1)) + new_stdout = cast("TextIO", open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1)) except OSError as ex: raise RedirectionError(f'Failed to redirect because: {ex}') @@ -2965,7 +2965,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: # no point opening up the temporary file current_paste_buffer = get_paste_buffer() # create a temporary file to store output - new_stdout = cast(TextIO, tempfile.TemporaryFile(mode="w+")) + new_stdout = cast("TextIO", tempfile.TemporaryFile(mode="w+")) redir_saved_state.redirecting = True sys.stdout = self.stdout = new_stdout @@ -2998,8 +2998,8 @@ def _restore_output(self, statement: Statement, saved_redir_state: utils.Redirec pass # Restore the stdout values - self.stdout = cast(TextIO, saved_redir_state.saved_self_stdout) - sys.stdout = cast(TextIO, saved_redir_state.saved_sys_stdout) + self.stdout = cast("TextIO", saved_redir_state.saved_self_stdout) + sys.stdout = cast("TextIO", saved_redir_state.saved_sys_stdout) # Check if we need to wait for the process being piped to if self._cur_pipe_proc_reader is not None: @@ -3025,7 +3025,7 @@ def cmd_func(self, command: str) -> Optional[CommandFunc]: """ func_name = constants.COMMAND_FUNC_PREFIX + command func = getattr(self, func_name, None) - return cast(CommandFunc, func) if callable(func) else None + return cast("CommandFunc", func) if callable(func) else None def onecmd(self, statement: Union[Statement, str], *, add_to_history: bool = True) -> bool: """This executes the actual do_* method for a command. @@ -3285,7 +3285,7 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings: # We don't want this behavior since cmd2 only adds a closing quote when self.allow_closing_quote is True. # To fix this behavior, set readline's rl_basic_quote_characters to NULL. We don't need to worry about setting # rl_completion_suppress_quote since we never declared rl_completer_quote_characters. - readline_settings.basic_quotes = cast(bytes, ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value) + readline_settings.basic_quotes = cast("bytes", ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value) rl_basic_quote_characters.value = None readline_settings.completer = readline.get_completer() @@ -3950,7 +3950,7 @@ def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str cmds_undoc: List[str] = [] cmds_cats: Dict[str, List[str]] = {} for command in visible_commands: - func = cast(CommandFunc, self.cmd_func(command)) + func = cast("CommandFunc", self.cmd_func(command)) has_help_func = False has_parser = func in self._command_parsers @@ -4020,7 +4020,7 @@ def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None: stdout_orig = self.stdout try: # redirect our internal stdout - self.stdout = cast(TextIO, result) + self.stdout = cast("TextIO", result) help_func() finally: # restore internal stdout @@ -4087,7 +4087,7 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], p the text advertised to the user""" local_opts: Union[List[str], List[Tuple[Any, Optional[str]]]] if isinstance(opts, str): - local_opts = cast(List[Tuple[Any, Optional[str]]], list(zip(opts.split(), opts.split()))) + local_opts = cast("List[Tuple[Any, Optional[str]]]", list(zip(opts.split(), opts.split()))) else: local_opts = opts fulloptions: List[Tuple[Any, Optional[str]]] = [] @@ -4287,7 +4287,7 @@ def do_shell(self, args: argparse.Namespace) -> None: **kwargs, ) - proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr) # type: ignore[arg-type] + proc_reader = utils.ProcReader(proc, cast("TextIO", self.stdout), sys.stderr) # type: ignore[arg-type] proc_reader.wait() # Save the return code of the application for use in a pyscript @@ -4346,7 +4346,7 @@ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env: # rlcompleter relies on the default settings of the Python readline module if rl_type == RlType.GNU: cmd2_env.readline_settings.basic_quotes = cast( - bytes, ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value + "bytes", ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value ) rl_basic_quote_characters.value = orig_rl_basic_quotes @@ -4995,8 +4995,8 @@ def _generate_transcript( transcript += command # Use a StdSim object to capture output - stdsim = utils.StdSim(cast(TextIO, self.stdout)) - self.stdout = cast(TextIO, stdsim) + stdsim = utils.StdSim(cast("TextIO", self.stdout)) + self.stdout = cast("TextIO", stdsim) # then run the command and let the output go into our buffer try: @@ -5021,7 +5021,7 @@ def _generate_transcript( with self.sigint_protection: # Restore altered attributes to their original state self.echo = saved_echo - self.stdout = cast(TextIO, saved_stdout) + self.stdout = cast("TextIO", saved_stdout) # Check if all commands ran if commands_run < len(history): @@ -5235,7 +5235,7 @@ class TestMyAppCase(Cmd2TestCase): setattr(self.__class__, 'testfiles', transcripts_expanded) sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main() testcase = TestMyAppCase() - stream = cast(TextIO, utils.StdSim(sys.stderr)) + stream = cast("TextIO", utils.StdSim(sys.stderr)) runner = unittest.TextTestRunner(stream=stream) start_time = time.time() test_results = runner.run(testcase) @@ -5624,7 +5624,7 @@ def register_postloop_hook(self, func: Callable[[], None]) -> None: @classmethod def _validate_postparsing_callable(cls, func: Callable[[plugin.PostparsingData], plugin.PostparsingData]) -> None: """Check parameter and return types for postparsing hooks""" - cls._validate_callable_param_count(cast(Callable[..., Any], func), 1) + cls._validate_callable_param_count(cast("Callable[..., Any]", func), 1) signature = inspect.signature(func) _, param = list(signature.parameters.items())[0] if param.annotation != plugin.PostparsingData: @@ -5646,7 +5646,7 @@ def _validate_prepostcmd_hook( """Check parameter and return types for pre and post command hooks.""" signature = inspect.signature(func) # validate that the callable has the right number of parameters - cls._validate_callable_param_count(cast(Callable[..., Any], func), 1) + cls._validate_callable_param_count(cast("Callable[..., Any]", func), 1) # validate the parameter has the right annotation paramname = list(signature.parameters.keys())[0] param = signature.parameters[paramname] diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 177eed673..366469afe 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -117,7 +117,7 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul echo = self.cmd_echo # This will be used to capture _cmd2_app.stdout and sys.stdout - copy_cmd_stdout = StdSim(cast(Union[TextIO, StdSim], self._cmd2_app.stdout), echo=echo) + copy_cmd_stdout = StdSim(cast('Union[TextIO, StdSim]', self._cmd2_app.stdout), echo=echo) # Pause the storing of stdout until onecmd_plus_hooks enables it copy_cmd_stdout.pause_storage = True @@ -129,9 +129,9 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul stop = False try: - self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout) - with redirect_stdout(cast(IO[str], copy_cmd_stdout)): - with redirect_stderr(cast(IO[str], copy_stderr)): + self._cmd2_app.stdout = cast('TextIO', copy_cmd_stdout) + with redirect_stdout(cast('IO[str]', copy_cmd_stdout)): + with redirect_stderr(cast('IO[str]', copy_stderr)): stop = self._cmd2_app.onecmd_plus_hooks( command, add_to_history=self._add_to_history, @@ -139,7 +139,7 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul ) finally: with self._cmd2_app.sigint_protection: - self._cmd2_app.stdout = cast(IO[str], copy_cmd_stdout.inner_stream) + self._cmd2_app.stdout = cast('IO[str]', copy_cmd_stdout.inner_stream) self.stop = stop or self.stop # Save the result diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 309087ad3..9545b5b97 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -51,7 +51,7 @@ def setUp(self) -> None: # Trap stdout self._orig_stdout = self.cmdapp.stdout - self.cmdapp.stdout = cast(TextIO, utils.StdSim(cast(TextIO, self.cmdapp.stdout))) + self.cmdapp.stdout = cast('TextIO', utils.StdSim(cast('TextIO', self.cmdapp.stdout))) def tearDown(self) -> None: if self.cmdapp: @@ -66,7 +66,7 @@ def runTest(self) -> None: # was testall def _fetchTranscripts(self) -> None: self.transcripts = {} - testfiles = cast(List[str], getattr(self.cmdapp, 'testfiles', [])) + testfiles = cast('List[str]', getattr(self.cmdapp, 'testfiles', [])) for fname in testfiles: tfile = open(fname) self.transcripts[fname] = iter(tfile.readlines()) diff --git a/cmd2/utils.py b/cmd2/utils.py index 176503354..ee1fa20f1 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -164,7 +164,7 @@ def get_bool_choices(_) -> List[str]: # type: ignore[no-untyped-def] return ['true', 'false'] val_type = to_bool - choices_provider = cast(ChoicesProviderFunc, get_bool_choices) + choices_provider = cast('ChoicesProviderFunc', get_bool_choices) self.name = name self.val_type = val_type @@ -1194,7 +1194,7 @@ def get_defining_class(meth: Callable[..., Any]) -> Optional[Type[Any]]: cls = getattr(inspect.getmodule(meth), meth.__qualname__.split('.', 1)[0].rsplit('.', 1)[0]) if isinstance(cls, type): return cls - return cast(type, getattr(meth, '__objclass__', None)) # handle special descriptor objects + return cast('type', getattr(meth, '__objclass__', None)) # handle special descriptor objects class CompletionMode(Enum): diff --git a/pyproject.toml b/pyproject.toml index 9f246df12..30cb494ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -212,7 +212,7 @@ select = [ # "SLOT", # flake8-slots (warn about subclasses that should define __slots__) "T10", # flake8-debugger (check for pdb traces left in Python code) # "T20", # flake8-print (warn about use of `print` or `pprint` - force use of loggers) - # "TC", # flake8-type-checking (type checking warnings) + "TC", # flake8-type-checking (type checking warnings) # "TD", # flake8-todos (force all TODOs to include an author and issue link) "TID", # flake8-tidy-imports (extra import rules to check) # "TRY", # tryceratops (warnings related to exceptions and try/except) From 1dc5cc81da26bcb0d24021fffe392df65e553220 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 20:20:03 -0400 Subject: [PATCH 16/79] Enable ruff TD ruleset --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 30cb494ca..d5e7fac20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -212,8 +212,8 @@ select = [ # "SLOT", # flake8-slots (warn about subclasses that should define __slots__) "T10", # flake8-debugger (check for pdb traces left in Python code) # "T20", # flake8-print (warn about use of `print` or `pprint` - force use of loggers) - "TC", # flake8-type-checking (type checking warnings) - # "TD", # flake8-todos (force all TODOs to include an author and issue link) + "TC", # flake8-type-checking (type checking warnings) + "TD", # flake8-todos (force all TODOs to include an author and issue link) "TID", # flake8-tidy-imports (extra import rules to check) # "TRY", # tryceratops (warnings related to exceptions and try/except) # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) From 99307656142ccdfe6e4a1c37aea82865e26c052f Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 20:22:52 -0400 Subject: [PATCH 17/79] Enable ruff YTT ruleset and fix the one warning --- cmd2/argparse_custom.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 43de7372a..c68859530 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1261,7 +1261,7 @@ def __init__( behavior on this parser. If this is None or not present, then cmd2 will use argparse_completer.DEFAULT_AP_COMPLETER when tab completing this parser's arguments """ - if sys.version_info[1] >= 14: + if sys.version_info >= (3, 14): # Python >= 3.14 so pass new arguments to parent argparse.ArgumentParser class super(Cmd2ArgumentParser, self).__init__( prog=prog, diff --git a/pyproject.toml b/pyproject.toml index d5e7fac20..73fdddb8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -217,8 +217,8 @@ select = [ "TID", # flake8-tidy-imports (extra import rules to check) # "TRY", # tryceratops (warnings related to exceptions and try/except) # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) - "W", # pycodestyle warnings (warn about minor stylistic issues) - # "YTT", # flake8-2020 (checks for misuse of sys.version or sys.version_info) + "W", # pycodestyle warnings (warn about minor stylistic issues) + "YTT", # flake8-2020 (checks for misuse of sys.version or sys.version_info) ] ignore = [ # `uv run ruff rule E501` for a description of that rule From bf519a08f16e74edb63c502a8adbbf4c28d0ab1d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 20:28:54 -0400 Subject: [PATCH 18/79] Applied a number of automated refactorings from ruff UP ruleset --- cmd2/ansi.py | 6 +- cmd2/argparse_custom.py | 70 +++--- cmd2/clipboard.py | 1 - cmd2/cmd2.py | 209 +++++++++--------- cmd2/command_definition.py | 12 +- cmd2/constants.py | 1 - cmd2/decorators.py | 36 ++- cmd2/exceptions.py | 1 - cmd2/history.py | 12 +- cmd2/parsing.py | 40 ++-- cmd2/plugin.py | 1 - cmd2/py_bridge.py | 6 +- cmd2/rl_utils.py | 1 - cmd2/table_creator.py | 20 +- cmd2/transcript.py | 9 +- cmd2/utils.py | 41 ++-- examples/alias_startup.py | 1 - examples/arg_decorators.py | 9 +- examples/arg_print.py | 17 +- examples/argparse_completion.py | 13 +- examples/async_printing.py | 10 +- examples/basic.py | 1 - examples/basic_completion.py | 18 +- examples/cmd_as_argument.py | 1 - examples/colors.py | 1 - examples/custom_parser.py | 3 +- examples/decorator_example.py | 8 +- examples/default_categories.py | 1 - examples/dynamic_commands.py | 3 +- examples/environment.py | 3 +- examples/event_loops.py | 1 - examples/example.py | 1 - examples/exit_code.py | 11 +- examples/first_app.py | 1 - examples/hello_cmd2.py | 1 - examples/help_categories.py | 3 +- examples/hooks.py | 6 +- examples/initialization.py | 1 - examples/migrating.py | 1 - examples/modular_commands/commandset_basic.py | 21 +- .../modular_commands/commandset_custominit.py | 1 - examples/modular_commands_basic.py | 1 - examples/modular_commands_dynamic.py | 1 - examples/modular_commands_main.py | 6 +- examples/modular_subcommands.py | 1 - examples/paged_output.py | 12 +- examples/persistent_history.py | 1 - examples/pirate.py | 9 +- examples/python_scripting.py | 1 - examples/read_input.py | 7 +- examples/remove_builtin_commands.py | 1 - examples/remove_settable.py | 1 - examples/scripts/arg_printer.py | 5 +- examples/scripts/script.py | 1 - examples/subcommands.py | 3 +- examples/table_creation.py | 24 +- examples/unicode_commands.py | 3 +- plugins/template/cmd2_myplugin/__init__.py | 1 - plugins/template/cmd2_myplugin/myplugin.py | 3 +- plugins/template/examples/example.py | 1 - plugins/template/setup.py | 1 - plugins/template/tasks.py | 3 +- plugins/template/tests/test_myplugin.py | 1 - pyproject.toml | 3 +- tests/__init__.py | 1 - tests/conftest.py | 6 +- tests/pyscript/raises_exception.py | 1 - tests/script.py | 1 - tests_isolated/test_commandset/__init__.py | 1 - tests_isolated/test_commandset/conftest.py | 4 +- .../test_commandset/test_categories.py | 1 - 71 files changed, 282 insertions(+), 426 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index e73b7c091..abea54903 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Support for ANSI escape sequences which are used for things like applying style to text, setting the window title, and asynchronous alerts. @@ -12,7 +11,6 @@ from typing import ( IO, Any, - List, Optional, cast, ) @@ -993,10 +991,10 @@ def style( :return: the stylized string """ # List of strings that add style - additions: List[AnsiSequence] = [] + additions: list[AnsiSequence] = [] # List of strings that remove style - removals: List[AnsiSequence] = [] + removals: list[AnsiSequence] = [] # Process the style settings if fg is not None: diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index c68859530..e4a6e8331 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ This module adds capabilities to argparse by patching a few of its functions. It also defines a parser class called Cmd2ArgumentParser which improves error @@ -230,6 +229,7 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens) ZERO_OR_MORE, ArgumentError, ) +from collections.abc import Callable, Iterable, Sequence from gettext import ( gettext, ) @@ -237,17 +237,9 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens) IO, TYPE_CHECKING, Any, - Callable, - Dict, - Iterable, - List, NoReturn, Optional, Protocol, - Sequence, - Set, - Tuple, - Type, Union, cast, runtime_checkable, @@ -325,7 +317,7 @@ class ChoicesProviderFuncBase(Protocol): Function that returns a list of choices in support of tab completion """ - def __call__(self) -> List[str]: ... # pragma: no cover + def __call__(self) -> list[str]: ... # pragma: no cover @runtime_checkable @@ -334,7 +326,7 @@ class ChoicesProviderFuncWithTokens(Protocol): Function that returns a list of choices in support of tab completion and accepts a dictionary of prior arguments. """ - def __call__(self, *, arg_tokens: Dict[str, List[str]] = {}) -> List[str]: ... # pragma: no cover + def __call__(self, *, arg_tokens: dict[str, list[str]] = {}) -> list[str]: ... # pragma: no cover ChoicesProviderFunc = Union[ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens] @@ -352,7 +344,7 @@ def __call__( line: str, begidx: int, endidx: int, - ) -> List[str]: ... # pragma: no cover + ) -> list[str]: ... # pragma: no cover @runtime_checkable @@ -369,8 +361,8 @@ def __call__( begidx: int, endidx: int, *, - arg_tokens: Dict[str, List[str]] = {}, - ) -> List[str]: ... # pragma: no cover + arg_tokens: dict[str, list[str]] = {}, + ) -> list[str]: ... # pragma: no cover CompleterFunc = Union[CompleterFuncBase, CompleterFuncWithTokens] @@ -571,7 +563,7 @@ def _action_set_descriptive_header(self: argparse.Action, descriptive_header: Op ############################################################################################################ # Patch argparse.Action with accessors for nargs_range attribute ############################################################################################################ -def _action_get_nargs_range(self: argparse.Action) -> Optional[Tuple[int, Union[int, float]]]: +def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[int, float]]]: """ Get the nargs_range attribute of an argparse Action. @@ -582,13 +574,13 @@ def _action_get_nargs_range(self: argparse.Action) -> Optional[Tuple[int, Union[ :param self: argparse Action being queried :return: The value of nargs_range or None if attribute does not exist """ - return cast("Optional[Tuple[int, Union[int, float]]]", getattr(self, ATTR_NARGS_RANGE, None)) + return cast("Optional[tuple[int, Union[int, float]]]", getattr(self, ATTR_NARGS_RANGE, None)) setattr(argparse.Action, 'get_nargs_range', _action_get_nargs_range) -def _action_set_nargs_range(self: argparse.Action, nargs_range: Optional[Tuple[int, Union[int, float]]]) -> None: +def _action_set_nargs_range(self: argparse.Action, nargs_range: Optional[tuple[int, Union[int, float]]]) -> None: """ Set the nargs_range attribute of an argparse Action. @@ -646,11 +638,11 @@ def _action_set_suppress_tab_hint(self: argparse.Action, suppress_tab_hint: bool # Allow developers to add custom action attributes ############################################################################################################ -CUSTOM_ACTION_ATTRIBS: Set[str] = set() +CUSTOM_ACTION_ATTRIBS: set[str] = set() _CUSTOM_ATTRIB_PFX = '_attr_' -def register_argparse_argument_parameter(param_name: str, param_type: Optional[Type[Any]]) -> None: +def register_argparse_argument_parameter(param_name: str, param_type: Optional[type[Any]]) -> None: """ Registers a custom argparse argument parameter. @@ -719,7 +711,7 @@ def _action_set_custom_parameter(self: argparse.Action, value: Any) -> None: def _add_argument_wrapper( self: argparse._ActionsContainer, *args: Any, - nargs: Union[int, str, Tuple[int], Tuple[int, int], Tuple[int, float], None] = None, + nargs: Union[int, str, tuple[int], tuple[int, int], tuple[int, float], None] = None, choices_provider: Optional[ChoicesProviderFunc] = None, completer: Optional[CompleterFunc] = None, suppress_tab_hint: bool = False, @@ -770,7 +762,7 @@ def _add_argument_wrapper( nargs_range = None if nargs is not None: - nargs_adjusted: Union[int, str, Tuple[int], Tuple[int, int], Tuple[int, float], None] + nargs_adjusted: Union[int, str, tuple[int], tuple[int, int], tuple[int, float], None] # Check if nargs was given as a range if isinstance(nargs, tuple): # Handle 1-item tuple by setting max to INFINITY @@ -820,7 +812,7 @@ def _add_argument_wrapper( kwargs['nargs'] = nargs_adjusted # Extract registered custom keyword arguments - custom_attribs: Dict[str, Any] = {} + custom_attribs: dict[str, Any] = {} for keyword, value in kwargs.items(): if keyword in CUSTOM_ACTION_ATTRIBS: custom_attribs[keyword] = value @@ -918,7 +910,7 @@ def _match_argument_wrapper(self: argparse.ArgumentParser, action: argparse.Acti ATTR_AP_COMPLETER_TYPE = 'ap_completer_type' -def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[Type['ArgparseCompleter']]: +def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[type['ArgparseCompleter']]: """ Get the ap_completer_type attribute of an argparse ArgumentParser. @@ -929,13 +921,13 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti :param self: ArgumentParser being queried :return: An ArgparseCompleter-based class or None if attribute does not exist """ - return cast("Optional[Type[ArgparseCompleter]]", getattr(self, ATTR_AP_COMPLETER_TYPE, None)) + return cast("Optional[type[ArgparseCompleter]]", getattr(self, ATTR_AP_COMPLETER_TYPE, None)) setattr(argparse.ArgumentParser, 'get_ap_completer_type', _ArgumentParser_get_ap_completer_type) -def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: Type['ArgparseCompleter']) -> None: +def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: type['ArgparseCompleter']) -> None: """ Set the ap_completer_type attribute of an argparse ArgumentParser. @@ -1092,9 +1084,9 @@ def _format_usage( # End cmd2 customization # helper for wrapping lines - def get_lines(parts: List[str], indent: str, prefix: Optional[str] = None) -> List[str]: - lines: List[str] = [] - line: List[str] = [] + def get_lines(parts: list[str], indent: str, prefix: Optional[str] = None) -> list[str]: + lines: list[str] = [] + line: list[str] = [] if prefix is not None: line_len = len(prefix) - 1 else: @@ -1155,7 +1147,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: (metavar,) = self._metavar_formatter(action, default)(1) return metavar - parts: List[str] = [] + parts: list[str] = [] # if the Optional doesn't take a value, format is: # -s, --long @@ -1175,8 +1167,8 @@ def _format_action_invocation(self, action: argparse.Action) -> str: def _determine_metavar( self, action: argparse.Action, - default_metavar: Union[str, Tuple[str, ...]], - ) -> Union[str, Tuple[str, ...]]: + default_metavar: Union[str, tuple[str, ...]], + ) -> Union[str, tuple[str, ...]]: """Custom method to determine what to use as the metavar value of an action""" if action.metavar is not None: result = action.metavar @@ -1192,18 +1184,18 @@ def _determine_metavar( def _metavar_formatter( self, action: argparse.Action, - default_metavar: Union[str, Tuple[str, ...]], - ) -> Callable[[int], Tuple[str, ...]]: + default_metavar: Union[str, tuple[str, ...]], + ) -> Callable[[int], tuple[str, ...]]: metavar = self._determine_metavar(action, default_metavar) - def format_tuple(tuple_size: int) -> Tuple[str, ...]: + def format_tuple(tuple_size: int) -> tuple[str, ...]: if isinstance(metavar, tuple): return metavar return (metavar,) * tuple_size return format_tuple - def _format_args(self, action: argparse.Action, default_metavar: Union[str, Tuple[str, ...]]) -> str: + def _format_args(self, action: argparse.Action, default_metavar: Union[str, tuple[str, ...]]) -> str: """Customized to handle ranged nargs and make other output less verbose""" metavar = self._determine_metavar(action, default_metavar) metavar_formatter = self._metavar_formatter(action, default_metavar) @@ -1241,7 +1233,7 @@ def __init__( description: Optional[str] = None, epilog: Optional[str] = None, parents: Sequence[argparse.ArgumentParser] = (), - formatter_class: Type[argparse.HelpFormatter] = Cmd2HelpFormatter, + formatter_class: type[argparse.HelpFormatter] = Cmd2HelpFormatter, prefix_chars: str = '-', fromfile_prefix_chars: Optional[str] = None, argument_default: Optional[str] = None, @@ -1252,7 +1244,7 @@ def __init__( suggest_on_error: bool = False, color: bool = False, *, - ap_completer_type: Optional[Type['ArgparseCompleter']] = None, + ap_completer_type: Optional[type['ArgparseCompleter']] = None, ) -> None: """ # Custom parameter added by cmd2 @@ -1410,10 +1402,10 @@ def set(self, new_val: Any) -> None: # The default ArgumentParser class for a cmd2 app -DEFAULT_ARGUMENT_PARSER: Type[argparse.ArgumentParser] = Cmd2ArgumentParser +DEFAULT_ARGUMENT_PARSER: type[argparse.ArgumentParser] = Cmd2ArgumentParser -def set_default_argument_parser_type(parser_type: Type[argparse.ArgumentParser]) -> None: +def set_default_argument_parser_type(parser_type: type[argparse.ArgumentParser]) -> None: """ Set the default ArgumentParser class for a cmd2 app. This must be called prior to loading cmd2.py if you want to override the parser for cmd2's built-in commands. See examples/override_parser.py. diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index 30cb1a670..b59e09dc9 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ This module provides basic ability to copy from and paste to the clipboard/pastebuffer. """ diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 9754d494b..07490d057 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1,4 +1,3 @@ -# coding=utf-8 """Variant on standard library's cmd with extra features. To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you @@ -48,6 +47,7 @@ OrderedDict, namedtuple, ) +from collections.abc import Callable, Iterable, Mapping from contextlib import ( redirect_stdout, ) @@ -59,16 +59,8 @@ IO, TYPE_CHECKING, Any, - Callable, - Dict, - Iterable, - List, - Mapping, Optional, - Set, TextIO, - Tuple, - Type, TypeVar, Union, cast, @@ -201,7 +193,7 @@ class _SavedCmd2Env: def __init__(self) -> None: self.readline_settings = _SavedReadlineSettings() self.readline_module: Optional[ModuleType] = None - self.history: List[str] = [] + self.history: list[str] = [] self.sys_stdout: Optional[TextIO] = None self.sys_stdin: Optional[TextIO] = None @@ -230,7 +222,7 @@ def __init__(self, cmd: 'Cmd') -> None: # Keyed by the fully qualified method names. This is more reliable than # the methods themselves, since wrapping a method will change its address. - self._parsers: Dict[str, argparse.ArgumentParser] = {} + self._parsers: dict[str, argparse.ArgumentParser] = {} @staticmethod def _fully_qualified_name(command_method: CommandFunc) -> str: @@ -325,11 +317,11 @@ def __init__( include_py: bool = False, include_ipy: bool = False, allow_cli_args: bool = True, - transcript_files: Optional[List[str]] = None, + transcript_files: Optional[list[str]] = None, allow_redirection: bool = True, - multiline_commands: Optional[List[str]] = None, - terminators: Optional[List[str]] = None, - shortcuts: Optional[Dict[str, str]] = None, + multiline_commands: Optional[list[str]] = None, + terminators: Optional[list[str]] = None, + shortcuts: Optional[dict[str, str]] = None, command_sets: Optional[Iterable[CommandSet]] = None, auto_load_commands: bool = True, allow_clipboard: bool = True, @@ -419,12 +411,12 @@ def __init__( self.max_completion_items = 50 # A dictionary mapping settable names to their Settable instance - self._settables: Dict[str, Settable] = dict() + self._settables: dict[str, Settable] = dict() self._always_prefix_settables: bool = False # CommandSet containers - self._installed_command_sets: Set[CommandSet] = set() - self._cmd_to_command_sets: Dict[str, CommandSet] = {} + self._installed_command_sets: set[CommandSet] = set() + self._cmd_to_command_sets: dict[str, CommandSet] = {} self.build_settables() @@ -446,16 +438,16 @@ def __init__( self.exclude_from_history = ['eof', 'history'] # Dictionary of macro names and their values - self.macros: Dict[str, Macro] = dict() + self.macros: dict[str, Macro] = dict() # Keeps track of typed command history in the Python shell - self._py_history: List[str] = [] + self._py_history: list[str] = [] # The name by which Python environments refer to the PyBridge to call app commands self.py_bridge_name = 'app' # Defines app-specific variables/functions available in Python shells and pyscripts - self.py_locals: Dict[str, Any] = dict() + self.py_locals: dict[str, Any] = dict() # True if running inside a Python shell or pyscript, False otherwise self._in_py = False @@ -468,7 +460,7 @@ def __init__( self.last_result: Any = None # Used by run_script command to store current script dir as a LIFO queue to support _relative_run_script command - self._script_dir: List[str] = [] + self._script_dir: list[str] = [] # Context manager used to protect critical sections in the main thread from stopping due to a KeyboardInterrupt self.sigint_protection = utils.ContextFlag() @@ -499,7 +491,7 @@ def __init__( self.broken_pipe_warning = '' # Commands that will run at the beginning of the command loop - self._startup_commands: List[str] = [] + self._startup_commands: list[str] = [] # If a startup script is provided and exists, then execute it in the startup commands if startup_script: @@ -511,7 +503,7 @@ def __init__( self._startup_commands.append(script_cmd) # Transcript files to run instead of interactive command loop - self._transcript_files: Optional[List[str]] = None + self._transcript_files: Optional[list[str]] = None # Check for command line args if allow_cli_args: @@ -554,7 +546,7 @@ def __init__( # Commands that have been disabled from use. This is to support commands that are only available # during specific states of the application. This dictionary's keys are the command names and its # values are DisabledCommand objects. - self.disabled_commands: Dict[str, DisabledCommand] = dict() + self.disabled_commands: dict[str, DisabledCommand] = dict() # If any command has been categorized, then all other commands that haven't been categorized # will display under this section in the help output. @@ -592,7 +584,7 @@ def __init__( self.formatted_completions = '' # Used by complete() for readline tab completion - self.completion_matches: List[str] = [] + self.completion_matches: list[str] = [] # Use this list if you need to display tab completion suggestions that are different than the actual text # of the matches. For instance, if you are completing strings that contain a common delimiter and you only @@ -600,7 +592,7 @@ def __init__( # still must be returned from your completer function. For an example, look at path_complete() which # uses this to show only the basename of paths as the suggestions. delimiter_complete() also populates # this list. These are ignored if self.formatted_completions is populated. - self.display_matches: List[str] = [] + self.display_matches: list[str] = [] # Used by functions like path_complete() and delimiter_complete() to properly # quote matches that are completed in a delimited fashion @@ -642,7 +634,7 @@ def __init__( # the current command being executed self.current_command: Optional[Statement] = None - def find_commandsets(self, commandset_type: Type[CommandSet], *, subclass_match: bool = False) -> List[CommandSet]: + def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: bool = False) -> list[CommandSet]: """ Find all CommandSets that match the provided CommandSet type. By default, locates a CommandSet that is an exact type match but may optionally return all CommandSets that @@ -671,7 +663,7 @@ def _autoload_commands(self) -> None: all_commandset_defs = CommandSet.__subclasses__() existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets] - def load_commandset_by_type(commandset_types: List[Type[CommandSet]]) -> None: + def load_commandset_by_type(commandset_types: list[type[CommandSet]]) -> None: for cmdset_type in commandset_types: # check if the type has sub-classes. We will only auto-load leaf class types. subclasses = cmdset_type.__subclasses__() @@ -715,7 +707,7 @@ def register_command_set(self, cmdset: CommandSet) -> None: cmdset.on_register(self) methods = cast( - "List[Tuple[str, Callable[..., Any]]]", + "list[tuple[str, Callable[..., Any]]]", inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] @@ -857,7 +849,7 @@ def unregister_command_set(self, cmdset: CommandSet) -> None: cmdset.on_unregister() self._unregister_subcommands(cmdset) - methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers( + methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] and hasattr(meth, '__name__') @@ -903,7 +895,7 @@ def check_parser_uninstallable(parser: argparse.ArgumentParser) -> None: check_parser_uninstallable(subparser) break - methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers( + methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] and hasattr(meth, '__name__') @@ -964,7 +956,7 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}" ) - def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser: + def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser: if not subcmd_names: return action cur_subcmd = subcmd_names.pop(0) @@ -1140,7 +1132,7 @@ def remove_settable(self, name: str) -> None: def build_settables(self) -> None: """Create the dictionary of user-settable parameters""" - def get_allow_style_choices(cli_self: Cmd) -> List[str]: + def get_allow_style_choices(cli_self: Cmd) -> list[str]: """Used to tab complete allow_style values""" return [val.name.lower() for val in ansi.AllowStyle] @@ -1383,7 +1375,7 @@ def _reset_completion_defaults(self) -> None: elif rl_type == RlType.PYREADLINE: readline.rl.mode._display_completions = self._display_matches_pyreadline - def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[List[str], List[str]]: + def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> tuple[list[str], list[str]]: """Used by tab completion functions to get all tokens through the one being completed. :param line: the current input line with leading whitespace removed @@ -1454,7 +1446,7 @@ def basic_complete( begidx: int, endidx: int, match_against: Iterable[str], - ) -> List[str]: + ) -> list[str]: """ Basic tab completion function that matches against a list of strings without considering line contents or cursor position. The args required by this function are defined in the header of Python's cmd.py. @@ -1476,7 +1468,7 @@ def delimiter_complete( endidx: int, match_against: Iterable[str], delimiter: str, - ) -> List[str]: + ) -> list[str]: """ Performs tab completion against a list but each match is split on a delimiter and only the portion of the match being tab completed is shown as the completion suggestions. @@ -1542,10 +1534,10 @@ def flag_based_complete( line: str, begidx: int, endidx: int, - flag_dict: Dict[str, Union[Iterable[str], CompleterFunc]], + flag_dict: dict[str, Union[Iterable[str], CompleterFunc]], *, all_else: Union[None, Iterable[str], CompleterFunc] = None, - ) -> List[str]: + ) -> list[str]: """Tab completes based on a particular flag preceding the token being completed. :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1594,7 +1586,7 @@ def index_based_complete( index_dict: Mapping[int, Union[Iterable[str], CompleterFunc]], *, all_else: Optional[Union[Iterable[str], CompleterFunc]] = None, - ) -> List[str]: + ) -> list[str]: """Tab completes based on a fixed position in the input string. :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1639,7 +1631,7 @@ def index_based_complete( def path_complete( self, text: str, line: str, begidx: int, endidx: int, *, path_filter: Optional[Callable[[str], bool]] = None - ) -> List[str]: + ) -> list[str]: """Performs completion of local file system paths :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1653,7 +1645,7 @@ def path_complete( """ # Used to complete ~ and ~user strings - def complete_users() -> List[str]: + def complete_users() -> list[str]: users = [] # Windows lacks the pwd module so we can't get a list of users. @@ -1779,7 +1771,7 @@ def complete_users() -> List[str]: return matches - def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> List[str]: + def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> list[str]: """Performs completion of executables either in a user's path or a given path :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1803,7 +1795,7 @@ def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, text, line, begidx, endidx, path_filter=lambda path: os.path.isdir(path) or os.access(path, os.X_OK) ) - def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> List[str]: + def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> list[str]: """Called by complete() as the first tab completion function for all commands It determines if it should tab complete for redirection (|, >, >>) or use the completer function for the current command @@ -1884,7 +1876,7 @@ def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, com return compfunc(text, line, begidx, endidx) @staticmethod - def _pad_matches_to_display(matches_to_display: List[str]) -> Tuple[List[str], int]: # pragma: no cover + def _pad_matches_to_display(matches_to_display: list[str]) -> tuple[list[str], int]: # pragma: no cover """Adds padding to the matches being displayed as tab completion suggestions. The default padding of readline/pyreadine is small and not visually appealing especially if matches have spaces. It appears very squished together. @@ -1906,7 +1898,7 @@ def _pad_matches_to_display(matches_to_display: List[str]) -> Tuple[List[str], i return [cur_match + padding for cur_match in matches_to_display], len(padding) def _display_matches_gnu_readline( - self, substitution: str, matches: List[str], longest_match_length: int + self, substitution: str, matches: list[str], longest_match_length: int ) -> None: # pragma: no cover """Prints a match list using GNU readline's rl_display_match_list() @@ -1953,7 +1945,7 @@ def _display_matches_gnu_readline( # rl_display_match_list() expects matches to be in argv format where # substitution is the first element, followed by the matches, and then a NULL. - strings_array = cast("List[Optional[bytes]]", (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) + strings_array = cast("list[Optional[bytes]]", (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) # Copy in the encoded strings and add a NULL to the end strings_array[0] = encoded_substitution @@ -1966,7 +1958,7 @@ def _display_matches_gnu_readline( # Redraw prompt and input line rl_force_redisplay() - def _display_matches_pyreadline(self, matches: List[str]) -> None: # pragma: no cover + def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no cover """Prints a match list using pyreadline3's _display_completions() :param matches: the tab completion matches to display @@ -2002,7 +1994,7 @@ def _display_matches_pyreadline(self, matches: List[str]) -> None: # pragma: no orig_pyreadline_display(matches_to_display) @staticmethod - def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> Type[argparse_completer.ArgparseCompleter]: + def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argparse_completer.ArgparseCompleter]: """ Determine what type of ArgparseCompleter to use on a given parser. If the parser does not have one set, then use argparse_completer.DEFAULT_AP_COMPLETER. @@ -2010,7 +2002,7 @@ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> Type[argpar :param parser: the parser to examine :return: type of ArgparseCompleter """ - Completer = Optional[Type[argparse_completer.ArgparseCompleter]] + Completer = Optional[type[argparse_completer.ArgparseCompleter]] completer_type: Completer = parser.get_ap_completer_type() # type: ignore[attr-defined] if completer_type is None: @@ -2312,15 +2304,15 @@ def in_pyscript(self) -> bool: return self._in_py @property - def aliases(self) -> Dict[str, str]: + def aliases(self) -> dict[str, str]: """Read-only property to access the aliases stored in the StatementParser""" return self.statement_parser.aliases - def get_names(self) -> List[str]: + def get_names(self) -> list[str]: """Return an alphabetized list of names comprising the attributes of the cmd2 class instance.""" return dir(self) - def get_all_commands(self) -> List[str]: + def get_all_commands(self) -> list[str]: """Return a list of all commands""" return [ name[len(constants.COMMAND_FUNC_PREFIX) :] @@ -2328,7 +2320,7 @@ def get_all_commands(self) -> List[str]: if name.startswith(constants.COMMAND_FUNC_PREFIX) and callable(getattr(self, name)) ] - def get_visible_commands(self) -> List[str]: + def get_visible_commands(self) -> list[str]: """Return a list of commands that have not been hidden or disabled""" return [ command @@ -2339,9 +2331,9 @@ def get_visible_commands(self) -> List[str]: # Table displayed when tab completing aliases _alias_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) - def _get_alias_completion_items(self) -> List[CompletionItem]: + def _get_alias_completion_items(self) -> list[CompletionItem]: """Return list of alias names and values as CompletionItems""" - results: List[CompletionItem] = [] + results: list[CompletionItem] = [] for cur_key in self.aliases: row_data = [self.aliases[cur_key]] @@ -2352,9 +2344,9 @@ def _get_alias_completion_items(self) -> List[CompletionItem]: # Table displayed when tab completing macros _macro_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) - def _get_macro_completion_items(self) -> List[CompletionItem]: + def _get_macro_completion_items(self) -> list[CompletionItem]: """Return list of macro names and values as CompletionItems""" - results: List[CompletionItem] = [] + results: list[CompletionItem] = [] for cur_key in self.macros: row_data = [self.macros[cur_key].value] @@ -2365,9 +2357,9 @@ def _get_macro_completion_items(self) -> List[CompletionItem]: # Table displayed when tab completing Settables _settable_completion_table = SimpleTable([Column('Value', width=30), Column('Description', width=60)], divider_char=None) - def _get_settable_completion_items(self) -> List[CompletionItem]: + def _get_settable_completion_items(self) -> list[CompletionItem]: """Return list of Settable names, values, and descriptions as CompletionItems""" - results: List[CompletionItem] = [] + results: list[CompletionItem] = [] for cur_key in self.settables: row_data = [self.settables[cur_key].get_value(), self.settables[cur_key].description] @@ -2375,14 +2367,14 @@ def _get_settable_completion_items(self) -> List[CompletionItem]: return results - def _get_commands_aliases_and_macros_for_completion(self) -> List[str]: + def _get_commands_aliases_and_macros_for_completion(self) -> list[str]: """Return a list of visible commands, aliases, and macros for tab completion""" visible_commands = set(self.get_visible_commands()) alias_names = set(self.aliases) macro_names = set(self.macros) return list(visible_commands | alias_names | macro_names) - def get_help_topics(self) -> List[str]: + def get_help_topics(self) -> list[str]: """Return a list of help topics""" all_topics = [ name[len(constants.HELP_FUNC_PREFIX) :] @@ -2482,7 +2474,7 @@ def postloop(self) -> None: """ pass - def parseline(self, line: str) -> Tuple[str, str, str]: + def parseline(self, line: str) -> tuple[str, str, str]: """Parse the line into a command name and a string containing the arguments. NOTE: This is an override of a parent class method. It is only used by other parent class methods. @@ -2554,7 +2546,7 @@ def onecmd_plus_hooks( redir_saved_state = self._redirect_output(statement) - timestart = datetime.datetime.now(tz=datetime.timezone.utc) + timestart = datetime.datetime.now(tz=datetime.UTC) # precommand hooks precmd_data = plugin.PrecommandData(statement) @@ -2580,7 +2572,7 @@ def onecmd_plus_hooks( stop = self.postcmd(stop, statement) if self.timing: - self.pfeedback(f'Elapsed: {datetime.datetime.now(tz=datetime.timezone.utc) - timestart}') + self.pfeedback(f'Elapsed: {datetime.datetime.now(tz=datetime.UTC) - timestart}') finally: # Get sigint protection while we restore stuff with self.sigint_protection: @@ -2645,7 +2637,7 @@ def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) def runcmds_plus_hooks( self, - cmds: Union[List[HistoryItem], List[str]], + cmds: Union[list[HistoryItem], list[str]], *, add_to_history: bool = True, stop_on_keyboard_interrupt: bool = False, @@ -2872,7 +2864,6 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: :return: A bool telling if an error occurred and a utils.RedirectionSavedState object :raises RedirectionError: if an error occurs trying to pipe or redirect """ - import io import subprocess # Initialize the redirection saved state @@ -2892,13 +2883,13 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: read_fd, write_fd = os.pipe() # Open each side of the pipe - subproc_stdin = io.open(read_fd, 'r') - new_stdout: TextIO = cast("TextIO", io.open(write_fd, 'w')) + subproc_stdin = open(read_fd) + new_stdout: TextIO = cast("TextIO", open(write_fd, 'w')) # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs, # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe # processes close in the right order (most recent first). - kwargs: Dict[str, Any] = dict() + kwargs: dict[str, Any] = dict() if sys.platform == 'win32': kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP else: @@ -3087,7 +3078,7 @@ def read_input( self, prompt: str, *, - history: Optional[List[str]] = None, + history: Optional[list[str]] = None, completion_mode: utils.CompletionMode = utils.CompletionMode.NONE, preserve_quotes: bool = False, choices: Optional[Iterable[Any]] = None, @@ -3128,7 +3119,7 @@ def read_input( """ readline_configured = False saved_completer: Optional[CompleterFunc] = None - saved_history: Optional[List[str]] = None + saved_history: Optional[list[str]] = None def configure_readline() -> None: """Configure readline tab completion and history""" @@ -3509,7 +3500,7 @@ def _alias_list(self, args: argparse.Namespace) -> None: else: to_list = sorted(self.aliases, key=self.default_sort_key) - not_found: List[str] = [] + not_found: list[str] = [] for name in to_list: if name not in self.aliases: not_found.append(name) @@ -3744,7 +3735,7 @@ def _macro_list(self, args: argparse.Namespace) -> None: else: to_list = sorted(self.macros, key=self.default_sort_key) - not_found: List[str] = [] + not_found: list[str] = [] for name in to_list: if name not in self.macros: not_found.append(name) @@ -3766,7 +3757,7 @@ def _macro_list(self, args: argparse.Namespace) -> None: for name in not_found: self.perror(f"Macro '{name}' not found") - def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """Completes the command argument of help""" # Complete token against topics and visible commands @@ -3776,8 +3767,8 @@ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) return self.basic_complete(text, line, begidx, endidx, strs_to_match) def complete_help_subcommands( - self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]] - ) -> List[str]: + self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] + ) -> list[str]: """Completes the subcommands argument of help""" # Make sure we have a command whose subcommands we will complete @@ -3846,7 +3837,7 @@ def do_help(self, args: argparse.Namespace) -> None: self.perror(err_msg, apply_style=False) self.last_result = False - def print_topics(self, header: str, cmds: Optional[List[str]], cmdlen: int, maxcol: int) -> None: + def print_topics(self, header: str, cmds: Optional[list[str]], cmdlen: int, maxcol: int) -> None: """ Print groups of commands and topics in columns and an optional header Override of cmd's print_topics() to handle headers with newlines, ANSI style sequences, and wide characters @@ -3864,7 +3855,7 @@ def print_topics(self, header: str, cmds: Optional[List[str]], cmdlen: int, maxc self.columnize(cmds, maxcol - 1) self.poutput() - def columnize(self, str_list: Optional[List[str]], display_width: int = 80) -> None: + def columnize(self, str_list: Optional[list[str]], display_width: int = 80) -> None: """Display a list of single-line strings as a compact set of columns. Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters @@ -3940,15 +3931,15 @@ def _help_menu(self, verbose: bool = False) -> None: self.print_topics(self.misc_header, help_topics, 15, 80) self.print_topics(self.undoc_header, cmds_undoc, 15, 80) - def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str], List[str]]: + def _build_command_info(self) -> tuple[dict[str, list[str]], list[str], list[str], list[str]]: # Get a sorted list of help topics help_topics = sorted(self.get_help_topics(), key=self.default_sort_key) # Get a sorted list of visible command names visible_commands = sorted(self.get_visible_commands(), key=self.default_sort_key) - cmds_doc: List[str] = [] - cmds_undoc: List[str] = [] - cmds_cats: Dict[str, List[str]] = {} + cmds_doc: list[str] = [] + cmds_undoc: list[str] = [] + cmds_cats: dict[str, list[str]] = {} for command in visible_commands: func = cast("CommandFunc", self.cmd_func(command)) has_help_func = False @@ -3971,7 +3962,7 @@ def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str cmds_undoc.append(command) return cmds_cats, cmds_doc, cmds_undoc, help_topics - def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None: + def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None: """Customized version of print_topics that can switch between verbose or traditional output""" import io @@ -4046,7 +4037,7 @@ def do_shortcuts(self, _: argparse.Namespace) -> None: """List available shortcuts""" # Sort the shortcut tuples by name sorted_shortcuts = sorted(self.statement_parser.shortcuts, key=lambda x: self.default_sort_key(x[0])) - result = "\n".join('{}: {}'.format(sc[0], sc[1]) for sc in sorted_shortcuts) + result = "\n".join(f'{sc[0]}: {sc[1]}' for sc in sorted_shortcuts) self.poutput(f"Shortcuts for other commands:\n{result}") self.last_result = True @@ -4074,7 +4065,7 @@ def do_quit(self, _: argparse.Namespace) -> Optional[bool]: self.last_result = True return True - def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') -> Any: + def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') -> Any: """Presents a numbered menu to the user. Modeled after the bash shell's SELECT. Returns the item chosen. @@ -4085,12 +4076,12 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], p | a list of tuples -> interpreted as (value, text), so that the return value can differ from the text advertised to the user""" - local_opts: Union[List[str], List[Tuple[Any, Optional[str]]]] + local_opts: Union[list[str], list[tuple[Any, Optional[str]]]] if isinstance(opts, str): - local_opts = cast("List[Tuple[Any, Optional[str]]]", list(zip(opts.split(), opts.split()))) + local_opts = cast("list[tuple[Any, Optional[str]]]", list(zip(opts.split(), opts.split()))) else: local_opts = opts - fulloptions: List[Tuple[Any, Optional[str]]] = [] + fulloptions: list[tuple[Any, Optional[str]]] = [] for opt in local_opts: if isinstance(opt, str): fulloptions.append((opt, opt)) @@ -4124,8 +4115,8 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], p self.poutput(f"'{response}' isn't a valid choice. Pick a number between 1 and {len(fulloptions)}:") def complete_set_value( - self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]] - ) -> List[str]: + self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] + ) -> list[str]: """Completes the value argument of set""" param = arg_tokens['param'][0] try: @@ -4216,7 +4207,7 @@ def do_set(self, args: argparse.Namespace) -> None: max_name_width = max([ansi.style_aware_wcswidth(param) for param in to_show]) max_name_width = max(max_name_width, ansi.style_aware_wcswidth(name_label)) - cols: List[Column] = [ + cols: list[Column] = [ Column(name_label, width=max_name_width), Column('Value', width=30), Column('Description', width=60), @@ -4247,7 +4238,7 @@ def do_shell(self, args: argparse.Namespace) -> None: import signal import subprocess - kwargs: Dict[str, Any] = dict() + kwargs: dict[str, Any] = dict() # Set OS-specific parameters if sys.platform.startswith('win'): @@ -4866,7 +4857,7 @@ def _initialize_history(self, hist_file: str) -> None: with open(hist_file, 'rb') as fobj: compressed_bytes = fobj.read() except FileNotFoundError: - compressed_bytes = bytes() + compressed_bytes = b"" except OSError as ex: self.perror(f"Cannot read persistent history file '{hist_file}': {ex}") return @@ -4885,11 +4876,11 @@ def _initialize_history(self, hist_file: str) -> None: try: import lzma as decompress_lib - decompress_exceptions: Tuple[type[Exception]] = (decompress_lib.LZMAError,) + decompress_exceptions: tuple[type[Exception]] = (decompress_lib.LZMAError,) except ModuleNotFoundError: # pragma: no cover import bz2 as decompress_lib # type: ignore[no-redef] - decompress_exceptions: Tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef] + decompress_exceptions: tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef] try: history_json = decompress_lib.decompress(compressed_bytes).decode(encoding='utf-8') @@ -4946,7 +4937,7 @@ def _persist_history(self) -> None: def _generate_transcript( self, - history: Union[List[HistoryItem], List[str]], + history: Union[list[HistoryItem], list[str]], transcript_file: str, *, add_to_history: bool = True, @@ -5070,7 +5061,7 @@ def run_editor(self, file_path: Optional[str] = None) -> None: :raises EnvironmentError: if self.editor is not set """ if not self.editor: - raise EnvironmentError("Please use 'set editor' to specify your text editing program of choice.") + raise OSError("Please use 'set editor' to specify your text editing program of choice.") command = utils.quote_string(os.path.expanduser(self.editor)) if file_path: @@ -5196,7 +5187,7 @@ def do__relative_run_script(self, args: argparse.Namespace) -> Optional[bool]: # self.last_result will be set by do_run_script() return self.do_run_script(utils.quote_string(relative_path)) - def _run_transcript_tests(self, transcript_paths: List[str]) -> None: + def _run_transcript_tests(self, transcript_paths: list[str]) -> None: """Runs transcript tests for provided file(s). This is called when either -t is provided on the command line or the transcript_files argument is provided @@ -5585,12 +5576,12 @@ def cmdloop(self, intro: Optional[str] = None) -> int: # type: ignore[override] ### def _initialize_plugin_system(self) -> None: """Initialize the plugin system""" - self._preloop_hooks: List[Callable[[], None]] = [] - self._postloop_hooks: List[Callable[[], None]] = [] - self._postparsing_hooks: List[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = [] - self._precmd_hooks: List[Callable[[plugin.PrecommandData], plugin.PrecommandData]] = [] - self._postcmd_hooks: List[Callable[[plugin.PostcommandData], plugin.PostcommandData]] = [] - self._cmdfinalization_hooks: List[Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]] = [] + self._preloop_hooks: list[Callable[[], None]] = [] + self._postloop_hooks: list[Callable[[], None]] = [] + self._postparsing_hooks: list[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = [] + self._precmd_hooks: list[Callable[[plugin.PrecommandData], plugin.PrecommandData]] = [] + self._postcmd_hooks: list[Callable[[plugin.PostcommandData], plugin.PostcommandData]] = [] + self._cmdfinalization_hooks: list[Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]] = [] @classmethod def _validate_callable_param_count(cls, func: Callable[..., Any], count: int) -> None: @@ -5641,7 +5632,7 @@ def register_postparsing_hook(self, func: Callable[[plugin.PostparsingData], plu @classmethod def _validate_prepostcmd_hook( - cls, func: Callable[[CommandDataType], CommandDataType], data_type: Type[CommandDataType] + cls, func: Callable[[CommandDataType], CommandDataType], data_type: type[CommandDataType] ) -> None: """Check parameter and return types for pre and post command hooks.""" signature = inspect.signature(func) @@ -5704,7 +5695,7 @@ def _resolve_func_self( :param cmd_self: The `self` associated with the command or subcommand """ # figure out what class the command support function was defined in - func_class: Optional[Type[Any]] = get_defining_class(cmd_support_func) + func_class: Optional[type[Any]] = get_defining_class(cmd_support_func) # Was there a defining class identified? If so, is it a sub-class of CommandSet? if func_class is not None and issubclass(func_class, CommandSet): @@ -5715,7 +5706,7 @@ def _resolve_func_self( # 2. Do any of the registered CommandSets in the Cmd2 application exactly match the type? # 3. Is there a registered CommandSet that is is the only matching subclass? - func_self: Optional[Union[CommandSet, 'Cmd']] + func_self: Optional[Union[CommandSet, Cmd]] # check if the command's CommandSet is a sub-class of the support function's defining class if isinstance(cmd_self, func_class): @@ -5724,7 +5715,7 @@ def _resolve_func_self( else: # Search all registered CommandSets func_self = None - candidate_sets: List[CommandSet] = [] + candidate_sets: list[CommandSet] = [] for installed_cmd_set in self._installed_command_sets: if type(installed_cmd_set) == func_class: # noqa: E721 # Case 2: CommandSet is an exact type match for the function's CommandSet diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index 5bac8ef32..d32d49b10 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -1,15 +1,11 @@ -# coding=utf-8 """ Supports the definition of commands in separate classes to be composed into cmd2.Cmd """ +from collections.abc import Callable, Mapping from typing import ( TYPE_CHECKING, - Callable, - Dict, - Mapping, Optional, - Type, TypeVar, ) @@ -31,7 +27,7 @@ #: Further refinements are needed to define the input parameters CommandFunc = Callable[..., Optional[bool]] -CommandSetType = TypeVar('CommandSetType', bound=Type['CommandSet']) +CommandSetType = TypeVar('CommandSetType', bound=type['CommandSet']) def with_default_category(category: str, *, heritable: bool = True) -> Callable[[CommandSetType], CommandSetType]: @@ -85,7 +81,7 @@ def decorate_class(cls: CommandSetType) -> CommandSetType: return decorate_class -class CommandSet(object): +class CommandSet: """ Base class for defining sets of commands to load in cmd2. @@ -100,7 +96,7 @@ def __init__(self) -> None: # accessed by child classes using the self._cmd property. self.__cmd_internal: Optional[cmd2.Cmd] = None - self._settables: Dict[str, Settable] = {} + self._settables: dict[str, Settable] = {} self._settable_prefix = self.__class__.__name__ @property diff --git a/cmd2/constants.py b/cmd2/constants.py index ebac5da95..b45a73bf6 100644 --- a/cmd2/constants.py +++ b/cmd2/constants.py @@ -1,5 +1,4 @@ # -# coding=utf-8 """This module contains constants used throughout ``cmd2``.""" # Unless documented in https://cmd2.readthedocs.io/en/latest/api/index.html diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 3d1105c64..dcdfb8f1f 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -1,17 +1,11 @@ -# coding=utf-8 """Decorators for ``cmd2`` commands""" import argparse +from collections.abc import Callable, Sequence from typing import ( TYPE_CHECKING, Any, - Callable, - Dict, - List, Optional, - Sequence, - Tuple, - Type, TypeVar, Union, ) @@ -68,7 +62,7 @@ def cat_decorator(func: CommandFunc) -> CommandFunc: CommandParent = TypeVar('CommandParent', bound=Union['cmd2.Cmd', CommandSet]) -CommandParentType = TypeVar('CommandParentType', bound=Union[Type['cmd2.Cmd'], Type[CommandSet]]) +CommandParentType = TypeVar('CommandParentType', bound=Union[type['cmd2.Cmd'], type[CommandSet]]) RawCommandFuncOptionalBoolReturn = Callable[[CommandParent, Union[Statement, str]], Optional[bool]] @@ -79,7 +73,7 @@ def cat_decorator(func: CommandFunc) -> CommandFunc: # in cmd2 command functions/callables. As long as the 2-ple of arguments we expect to be there can be # found we can swap out the statement with each decorator's specific parameters ########################## -def _parse_positionals(args: Tuple[Any, ...]) -> Tuple['cmd2.Cmd', Union[Statement, str]]: +def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Statement, str]]: """ Helper function for cmd2 decorators to inspect the positional arguments until the cmd2.Cmd argument is found Assumes that we will find cmd2.Cmd followed by the command statement object or string. @@ -103,7 +97,7 @@ def _parse_positionals(args: Tuple[Any, ...]) -> Tuple['cmd2.Cmd', Union[Stateme raise TypeError('Expected arguments: cmd: cmd2.Cmd, statement: Union[Statement, str] Not found') -def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> List[Any]: +def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> list[Any]: """ Helper function for cmd2 decorators to swap the Statement parameter with one or more decorator-specific parameters @@ -120,13 +114,13 @@ def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> #: Function signature for a command function that accepts a pre-processed argument list from user input #: and optionally returns a boolean -ArgListCommandFuncOptionalBoolReturn = Callable[[CommandParent, List[str]], Optional[bool]] +ArgListCommandFuncOptionalBoolReturn = Callable[[CommandParent, list[str]], Optional[bool]] #: Function signature for a command function that accepts a pre-processed argument list from user input #: and returns a boolean -ArgListCommandFuncBoolReturn = Callable[[CommandParent, List[str]], bool] +ArgListCommandFuncBoolReturn = Callable[[CommandParent, list[str]], bool] #: Function signature for a command function that accepts a pre-processed argument list from user input #: and returns Nothing -ArgListCommandFuncNoneReturn = Callable[[CommandParent, List[str]], None] +ArgListCommandFuncNoneReturn = Callable[[CommandParent, list[str]], None] #: Aggregate of all accepted function signatures for command functions that accept a pre-processed argument list ArgListCommandFunc = Union[ @@ -208,7 +202,7 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: """ # Set the prog value for this parser parser.prog = prog - req_args: List[str] = [] + req_args: list[str] = [] # Set the prog value for the parser's subcommands for action in parser._actions: @@ -251,17 +245,17 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and optionally return a boolean ArgparseCommandFuncOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace], Optional[bool]] -ArgparseCommandFuncWithUnknownArgsOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace, List[str]], Optional[bool]] +ArgparseCommandFuncWithUnknownArgsOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace, list[str]], Optional[bool]] #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and return a boolean ArgparseCommandFuncBoolReturn = Callable[[CommandParent, argparse.Namespace], bool] -ArgparseCommandFuncWithUnknownArgsBoolReturn = Callable[[CommandParent, argparse.Namespace, List[str]], bool] +ArgparseCommandFuncWithUnknownArgsBoolReturn = Callable[[CommandParent, argparse.Namespace, list[str]], bool] #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and return nothing ArgparseCommandFuncNoneReturn = Callable[[CommandParent, argparse.Namespace], None] -ArgparseCommandFuncWithUnknownArgsNoneReturn = Callable[[CommandParent, argparse.Namespace, List[str]], None] +ArgparseCommandFuncWithUnknownArgsNoneReturn = Callable[[CommandParent, argparse.Namespace, list[str]], None] #: Aggregate of all accepted function signatures for an argparse command function ArgparseCommandFunc = Union[ @@ -343,7 +337,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> RawCommandFuncOpt """ @functools.wraps(func) - def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]: + def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]: """ Command function wrapper which translates command line into argparse Namespace and calls actual command function @@ -376,7 +370,7 @@ def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]: namespace = ns_provider(provider_self if provider_self is not None else cmd2_app) try: - new_args: Union[Tuple[argparse.Namespace], Tuple[argparse.Namespace, List[str]]] + new_args: Union[tuple[argparse.Namespace], tuple[argparse.Namespace, list[str]]] if with_unknown_args: new_args = arg_parser.parse_known_args(parsed_arglist, namespace) else: @@ -421,7 +415,7 @@ def as_subcommand_to( ], *, help: Optional[str] = None, - aliases: Optional[List[str]] = None, + aliases: Optional[list[str]] = None, ) -> Callable[[ArgparseCommandFunc[CommandParent]], ArgparseCommandFunc[CommandParent]]: """ Tag this method as a subcommand to an existing argparse decorated command. @@ -443,7 +437,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> ArgparseCommandFu setattr(func, constants.SUBCMD_ATTR_NAME, subcommand) # Keyword arguments for subparsers.add_parser() - add_parser_kwargs: Dict[str, Any] = dict() + add_parser_kwargs: dict[str, Any] = dict() if help is not None: add_parser_kwargs['help'] = help if aliases: diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index c07100134..4f8a517c2 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -1,4 +1,3 @@ -# coding=utf-8 """Custom exceptions for cmd2""" from typing import ( diff --git a/cmd2/history.py b/cmd2/history.py index 5f9758f96..8b6fc0258 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ History management classes """ @@ -8,15 +7,12 @@ from collections import ( OrderedDict, ) +from collections.abc import Callable, Iterable from dataclasses import ( dataclass, ) from typing import ( Any, - Callable, - Dict, - Iterable, - List, Optional, Union, overload, @@ -132,12 +128,12 @@ def pr(self, idx: int, script: bool = False, expanded: bool = False, verbose: bo return ret_str - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Utility method to convert this HistoryItem into a dictionary for use in persistent JSON history files""" return {HistoryItem._statement_field: self.statement.to_dict()} @staticmethod - def from_dict(source_dict: Dict[str, Any]) -> 'HistoryItem': + def from_dict(source_dict: dict[str, Any]) -> 'HistoryItem': """ Utility method to restore a HistoryItem from a dictionary @@ -149,7 +145,7 @@ def from_dict(source_dict: Dict[str, Any]) -> 'HistoryItem': return HistoryItem(Statement.from_dict(statement_dict)) -class History(List[HistoryItem]): +class History(list[HistoryItem]): """A list of [HistoryItem][cmd2.history.HistoryItem] objects with additional methods for searching and managing the list. diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 598cbe687..bd115585b 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -1,20 +1,16 @@ # -# -*- coding: utf-8 -*- """Statement parsing classes for cmd2""" import re import shlex +from collections.abc import Iterable from dataclasses import ( dataclass, field, ) from typing import ( Any, - Dict, - Iterable, - List, Optional, - Tuple, Union, ) @@ -27,7 +23,7 @@ ) -def shlex_split(str_to_split: str) -> List[str]: +def shlex_split(str_to_split: str) -> list[str]: """ A wrapper around shlex.split() that uses cmd2's preferred arguments. This allows other classes to easily call split() the same way StatementParser does. @@ -85,7 +81,7 @@ class Macro: minimum_arg_count: int # Used to fill in argument placeholders in the macro - arg_list: List[MacroArg] = field(default_factory=list) + arg_list: list[MacroArg] = field(default_factory=list) @dataclass(frozen=True) @@ -129,7 +125,7 @@ class Statement(str): # type: ignore[override] command: str = '' # list of arguments to the command, not including any output redirection or terminators; quoted args remain quoted - arg_list: List[str] = field(default_factory=list) + arg_list: list[str] = field(default_factory=list) # if the command is a multiline command, the name of the command, otherwise empty multiline_command: str = '' @@ -206,7 +202,7 @@ def expanded_command_line(self) -> str: return self.command_and_args + self.post_command @property - def argv(self) -> List[str]: + def argv(self) -> list[str]: """a list of arguments a-la ``sys.argv``. The first element of the list is the command after shortcut and macro @@ -225,12 +221,12 @@ def argv(self) -> List[str]: return rtn - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Utility method to convert this Statement into a dictionary for use in persistent JSON history files""" return self.__dict__.copy() @staticmethod - def from_dict(source_dict: Dict[str, Any]) -> 'Statement': + def from_dict(source_dict: dict[str, Any]) -> 'Statement': """ Utility method to restore a Statement from a dictionary @@ -258,8 +254,8 @@ def __init__( self, terminators: Optional[Iterable[str]] = None, multiline_commands: Optional[Iterable[str]] = None, - aliases: Optional[Dict[str, str]] = None, - shortcuts: Optional[Dict[str, str]] = None, + aliases: Optional[dict[str, str]] = None, + shortcuts: Optional[dict[str, str]] = None, ) -> None: """Initialize an instance of StatementParser. @@ -271,13 +267,13 @@ def __init__( :param aliases: dictionary containing aliases :param shortcuts: dictionary containing shortcuts """ - self.terminators: Tuple[str, ...] + self.terminators: tuple[str, ...] if terminators is None: self.terminators = (constants.MULTILINE_TERMINATOR,) else: self.terminators = tuple(terminators) - 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 {} + 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 @@ -318,7 +314,7 @@ def __init__( expr = rf'\A\s*(\S*?)({second_group})' self._command_pattern = re.compile(expr) - def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> Tuple[bool, str]: + def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> tuple[bool, str]: """Determine whether a word is a valid name for a command. Commands cannot include redirection characters, whitespace, @@ -369,7 +365,7 @@ def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> Tuple[b errmsg = '' return valid, errmsg - def tokenize(self, line: str) -> List[str]: + def tokenize(self, line: str) -> list[str]: """ Lex a string into a list of tokens. Shortcuts and aliases are expanded and comments are removed. @@ -607,7 +603,7 @@ def parse_command_only(self, rawinput: str) -> Statement: def get_command_arg_list( self, command_name: str, to_parse: Union[Statement, str], preserve_quotes: bool - ) -> Tuple[Statement, List[str]]: + ) -> tuple[Statement, list[str]]: """ Convenience method used by the argument parsing decorators. @@ -675,7 +671,7 @@ def _expand(self, line: str) -> str: return line @staticmethod - def _command_and_args(tokens: List[str]) -> Tuple[str, str]: + def _command_and_args(tokens: list[str]) -> tuple[str, str]: """Given a list of tokens, return a tuple of the command and the args as a string. """ @@ -690,7 +686,7 @@ def _command_and_args(tokens: List[str]) -> Tuple[str, str]: return command, args - def split_on_punctuation(self, tokens: List[str]) -> List[str]: + def split_on_punctuation(self, tokens: list[str]) -> list[str]: """Further splits tokens from a command line using punctuation characters. Punctuation characters are treated as word breaks when they are in @@ -700,7 +696,7 @@ def split_on_punctuation(self, tokens: List[str]) -> List[str]: :param tokens: the tokens as parsed by shlex :return: a new list of tokens, further split using punctuation """ - punctuation: List[str] = [] + punctuation: list[str] = [] punctuation.extend(self.terminators) punctuation.extend(constants.REDIRECTION_CHARS) diff --git a/cmd2/plugin.py b/cmd2/plugin.py index e7e2c6863..a3fed2856 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -1,5 +1,4 @@ # -# coding=utf-8 """Classes for the cmd2 plugin system""" from dataclasses import ( diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 366469afe..0936a74c6 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Bridges calls made inside of a Python environment to the Cmd2 host app while maintaining a reasonable degree of isolation between the two. @@ -13,7 +12,6 @@ IO, TYPE_CHECKING, Any, - List, NamedTuple, Optional, TextIO, @@ -98,9 +96,9 @@ def __init__(self, cmd2_app: 'cmd2.Cmd', *, add_to_history: bool = True) -> None # Tells if any of the commands run via __call__ returned True for stop self.stop = False - def __dir__(self) -> List[str]: + def __dir__(self) -> list[str]: """Return a custom set of attribute names""" - attributes: List[str] = [] + attributes: list[str] = [] attributes.insert(0, 'cmd_echo') return attributes diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index c591d29e2..4f98bb023 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Imports the proper Readline for the platform and provides utility functions for it """ diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index d28b63a0e..ae3534e52 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ cmd2 table creation API This API is built upon two core classes: Column and TableCreator @@ -11,16 +10,13 @@ from collections import ( deque, ) +from collections.abc import Sequence from enum import ( Enum, ) from typing import ( Any, - Deque, - List, Optional, - Sequence, - Tuple, ) from wcwidth import ( # type: ignore[import] @@ -154,7 +150,7 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None: col.width = max(1, ansi.widest_line(col.header)) @staticmethod - def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> Tuple[str, int, int]: + def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> tuple[str, int, int]: """ Used by _wrap_text() to wrap a long word over multiple lines @@ -380,7 +376,7 @@ def add_word(word_to_add: str, is_last_word: bool) -> None: return wrapped_buf.getvalue() - def _generate_cell_lines(self, cell_data: Any, is_header: bool, col: Column, fill_char: str) -> Tuple[Deque[str], int]: + def _generate_cell_lines(self, cell_data: Any, is_header: bool, col: Column, fill_char: str) -> tuple[deque[str], int]: """ Generate the lines of a table cell @@ -452,7 +448,7 @@ class Cell: def __init__(self) -> None: # Data in this cell split into individual lines - self.lines: Deque[str] = deque() + self.lines: deque[str] = deque() # Display width of this cell self.width = 0 @@ -652,7 +648,7 @@ def generate_header(self) -> str: inter_cell = self.apply_header_bg(self.column_spacing * SPACE) # Apply background color to header text in Columns which allow it - to_display: List[Any] = [] + to_display: list[Any] = [] for col in self.cols: if col.style_header_text: to_display.append(self.apply_header_bg(col.header)) @@ -692,7 +688,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: inter_cell = self.apply_data_bg(self.column_spacing * SPACE) # Apply background color to data text in Columns which allow it - to_display: List[Any] = [] + to_display: list[Any] = [] for index, col in enumerate(self.cols): if col.style_data_text: to_display.append(self.apply_data_bg(row_data[index])) @@ -947,7 +943,7 @@ def generate_header(self) -> str: post_line = self.apply_header_bg(self.padding * SPACE) + self.apply_border_color('║') # Apply background color to header text in Columns which allow it - to_display: List[Any] = [] + to_display: list[Any] = [] for col in self.cols: if col.style_header_text: to_display.append(self.apply_header_bg(col.header)) @@ -991,7 +987,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: post_line = self.apply_data_bg(self.padding * SPACE) + self.apply_border_color('║') # Apply background color to data text in Columns which allow it - to_display: List[Any] = [] + to_display: list[Any] = [] for index, col in enumerate(self.cols): if col.style_data_text: to_display.append(self.apply_data_bg(row_data[index])) diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 9545b5b97..7a08480f2 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -1,5 +1,4 @@ # -# -*- coding: utf-8 -*- """Machinery for running and validating transcripts. If the user wants to run a transcript (see docs/transcript.rst), @@ -12,13 +11,11 @@ class is used in cmd2.py::run_transcript_tests() import re import unittest +from collections.abc import Iterator from typing import ( TYPE_CHECKING, - Iterator, - List, Optional, TextIO, - Tuple, cast, ) @@ -66,7 +63,7 @@ def runTest(self) -> None: # was testall def _fetchTranscripts(self) -> None: self.transcripts = {} - testfiles = cast('List[str]', getattr(self.cmdapp, 'testfiles', [])) + testfiles = cast('list[str]', getattr(self.cmdapp, 'testfiles', [])) for fname in testfiles: tfile = open(fname) self.transcripts[fname] = iter(tfile.readlines()) @@ -183,7 +180,7 @@ def _transform_transcript_expected(self, s: str) -> str: return regex @staticmethod - def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> Tuple[str, int, int]: + def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> tuple[str, int, int]: """Find the next slash in {s} after {start} that is not preceded by a backslash. If we find an escaped slash, add everything up to and including it to regex, diff --git a/cmd2/utils.py b/cmd2/utils.py index ee1fa20f1..6504e0525 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -1,4 +1,3 @@ -# coding=utf-8 """Shared utility functions""" import argparse @@ -13,6 +12,7 @@ import sys import threading import unicodedata +from collections.abc import Callable, Iterable from difflib import ( SequenceMatcher, ) @@ -22,13 +22,8 @@ from typing import ( TYPE_CHECKING, Any, - Callable, - Dict, - Iterable, - List, Optional, TextIO, - Type, TypeVar, Union, cast, @@ -120,7 +115,7 @@ class Settable: def __init__( self, name: str, - val_type: Union[Type[Any], Callable[[Any], Any]], + val_type: Union[type[Any], Callable[[Any], Any]], description: str, settable_object: object, *, @@ -159,7 +154,7 @@ def __init__( """ if val_type is bool: - def get_bool_choices(_) -> List[str]: # type: ignore[no-untyped-def] + def get_bool_choices(_) -> list[str]: # type: ignore[no-untyped-def] """Used to tab complete lowercase boolean values""" return ['true', 'false'] @@ -230,7 +225,7 @@ def is_text_file(file_path: str) -> bool: return valid_text_file -def remove_duplicates(list_to_prune: List[_T]) -> List[_T]: +def remove_duplicates(list_to_prune: list[_T]) -> list[_T]: """Removes duplicates from a list while preserving order of the items. :param list_to_prune: the list being pruned of duplicates @@ -252,7 +247,7 @@ def norm_fold(astr: str) -> str: return unicodedata.normalize('NFC', astr).casefold() -def alphabetical_sort(list_to_sort: Iterable[str]) -> List[str]: +def alphabetical_sort(list_to_sort: Iterable[str]) -> list[str]: """Sorts a list of strings alphabetically. For example: ['a1', 'A11', 'A2', 'a22', 'a3'] @@ -279,7 +274,7 @@ def try_int_or_force_to_lower_case(input_str: str) -> Union[int, str]: return norm_fold(input_str) -def natural_keys(input_str: str) -> List[Union[int, str]]: +def natural_keys(input_str: str) -> list[Union[int, str]]: """ Converts a string into a list of integers and strings to support natural sorting (see natural_sort). @@ -290,7 +285,7 @@ def natural_keys(input_str: str) -> List[Union[int, str]]: return [try_int_or_force_to_lower_case(substr) for substr in re.split(r'(\d+)', input_str)] -def natural_sort(list_to_sort: Iterable[str]) -> List[str]: +def natural_sort(list_to_sort: Iterable[str]) -> list[str]: """ Sorts a list of strings case insensitively as well as numerically. @@ -306,7 +301,7 @@ def natural_sort(list_to_sort: Iterable[str]) -> List[str]: return sorted(list_to_sort, key=natural_keys) -def quote_specific_tokens(tokens: List[str], tokens_to_quote: List[str]) -> None: +def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None: """ Quote specific tokens in a list @@ -318,7 +313,7 @@ def quote_specific_tokens(tokens: List[str], tokens_to_quote: List[str]) -> None tokens[i] = quote_string(token) -def unquote_specific_tokens(tokens: List[str], tokens_to_unquote: List[str]) -> None: +def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> None: """ Unquote specific tokens in a list @@ -352,7 +347,7 @@ def expand_user(token: str) -> str: return token -def expand_user_in_tokens(tokens: List[str]) -> None: +def expand_user_in_tokens(tokens: list[str]) -> None: """ Call expand_user() on all tokens in a list of strings :param tokens: tokens to expand @@ -394,7 +389,7 @@ def find_editor() -> Optional[str]: return editor -def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> List[str]: +def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> list[str]: """Return a list of file paths based on a glob pattern. Only files are returned, not directories, and optionally only files for which the user has a specified access to. @@ -406,7 +401,7 @@ def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> List[str]: return [f for f in glob.glob(pattern) if os.path.isfile(f) and os.access(f, access)] -def files_from_glob_patterns(patterns: List[str], access: int = os.F_OK) -> List[str]: +def files_from_glob_patterns(patterns: list[str], access: int = os.F_OK) -> list[str]: """Return a list of file paths based on a list of glob patterns. Only files are returned, not directories, and optionally only files for which the user has a specified access to. @@ -422,7 +417,7 @@ def files_from_glob_patterns(patterns: List[str], access: int = os.F_OK) -> List return files -def get_exes_in_path(starts_with: str) -> List[str]: +def get_exes_in_path(starts_with: str) -> list[str]: """Returns names of executables in a user's path :param starts_with: what the exes should start with. leave blank for all exes in path. @@ -741,7 +736,7 @@ def __init__( self.saved_redirecting = saved_redirecting -def _remove_overridden_styles(styles_to_parse: List[str]) -> List[str]: +def _remove_overridden_styles(styles_to_parse: list[str]) -> list[str]: """ Utility function for align_text() / truncate_line() which filters a style list down to only those which would still be in effect if all were processed in order. @@ -762,7 +757,7 @@ class StyleState: def __init__(self) -> None: # Contains styles still in effect, keyed by their index in styles_to_parse - self.style_dict: Dict[int, str] = dict() + self.style_dict: dict[int, str] = dict() # Indexes into style_dict self.reset_all: Optional[int] = None @@ -900,7 +895,7 @@ def align_text( # ANSI style sequences that may affect subsequent lines will be cancelled by the fill_char's style. # To avoid this, we save styles which are still in effect so we can restore them when beginning the next line. # This also allows lines to be used independently and still have their style. TableCreator does this. - previous_styles: List[str] = [] + previous_styles: list[str] = [] for index, line in enumerate(lines): if index > 0: @@ -1111,7 +1106,7 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: return truncated_buf.getvalue() -def get_styles_dict(text: str) -> Dict[int, str]: +def get_styles_dict(text: str) -> dict[int, str]: """ Return an OrderedDict containing all ANSI style sequences found in a string @@ -1171,7 +1166,7 @@ def do_echo(self, arglist): setattr(func, constants.CMD_ATTR_HELP_CATEGORY, category) -def get_defining_class(meth: Callable[..., Any]) -> Optional[Type[Any]]: +def get_defining_class(meth: Callable[..., Any]) -> Optional[type[Any]]: """ Attempts to resolve the class that defined a method. diff --git a/examples/alias_startup.py b/examples/alias_startup.py index 1ad493ffa..e84278658 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating the following: 1) How to add custom command aliases using the alias command 2) How to run an initialization script at startup diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index e42960b1c..916358179 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """An example demonstrating how use one of cmd2's argument parsing decorators""" import argparse @@ -27,7 +26,7 @@ def do_fsize(self, args: argparse.Namespace) -> None: try: size = os.path.getsize(expanded_path) except OSError as ex: - self.perror("Error retrieving size: {}".format(ex)) + self.perror(f"Error retrieving size: {ex}") return if args.unit == 'KB': @@ -39,8 +38,8 @@ def do_fsize(self, args: argparse.Namespace) -> None: size = round(size, 2) if args.comma: - size = '{:,}'.format(size) - self.poutput('{} {}'.format(size, args.unit)) + size = f'{size:,}' + self.poutput(f'{size} {args.unit}') # do_pow parser pow_parser = cmd2.Cmd2ArgumentParser() @@ -54,7 +53,7 @@ def do_pow(self, args: argparse.Namespace) -> None: :param args: argparse arguments """ - self.poutput('{} ** {} == {}'.format(args.base, args.exponent, args.base**args.exponent)) + self.poutput(f'{args.base} ** {args.exponent} == {args.base**args.exponent}') if __name__ == '__main__': diff --git a/examples/arg_print.py b/examples/arg_print.py index 2cade9e42..26ccac9ca 100755 --- a/examples/arg_print.py +++ b/examples/arg_print.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating the following: 1) How arguments and options get parsed and passed to commands 2) How to change what syntax gets parsed as a comment and stripped from the arguments @@ -24,20 +23,20 @@ def __init__(self): def do_aprint(self, statement): """Print the argument string this basic command is called with.""" - self.poutput('aprint was called with argument: {!r}'.format(statement)) - self.poutput('statement.raw = {!r}'.format(statement.raw)) - self.poutput('statement.argv = {!r}'.format(statement.argv)) - self.poutput('statement.command = {!r}'.format(statement.command)) + self.poutput(f'aprint was called with argument: {statement!r}') + self.poutput(f'statement.raw = {statement.raw!r}') + self.poutput(f'statement.argv = {statement.argv!r}') + self.poutput(f'statement.command = {statement.command!r}') @cmd2.with_argument_list def do_lprint(self, arglist): """Print the argument list this basic command is called with.""" - self.poutput('lprint was called with the following list of arguments: {!r}'.format(arglist)) + self.poutput(f'lprint was called with the following list of arguments: {arglist!r}') @cmd2.with_argument_list(preserve_quotes=True) def do_rprint(self, arglist): """Print the argument list this basic command is called with (with quotes preserved).""" - self.poutput('rprint was called with the following list of arguments: {!r}'.format(arglist)) + self.poutput(f'rprint was called with the following list of arguments: {arglist!r}') oprint_parser = cmd2.Cmd2ArgumentParser() oprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') @@ -48,7 +47,7 @@ def do_rprint(self, arglist): @cmd2.with_argparser(oprint_parser) def do_oprint(self, args): """Print the options and argument list this options command was called with.""" - self.poutput('oprint was called with the following\n\toptions: {!r}'.format(args)) + self.poutput(f'oprint was called with the following\n\toptions: {args!r}') pprint_parser = cmd2.Cmd2ArgumentParser() pprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') @@ -58,7 +57,7 @@ def do_oprint(self, args): @cmd2.with_argparser(pprint_parser, with_unknown_args=True) def do_pprint(self, args, unknown): """Print the options and argument list this options command was called with.""" - self.poutput('oprint was called with the following\n\toptions: {!r}\n\targuments: {}'.format(args, unknown)) + self.poutput(f'oprint was called with the following\n\toptions: {args!r}\n\targuments: {unknown}') if __name__ == '__main__': diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index e0ad101b9..84082a6d3 100755 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -1,14 +1,9 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple example demonstrating how to integrate tab completion with argparse-based commands. """ import argparse -from typing import ( - Dict, - List, -) from cmd2 import ( Cmd, @@ -28,11 +23,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - def choices_provider(self) -> List[str]: + def choices_provider(self) -> list[str]: """A choices provider is useful when the choice list is based on instance data of your application""" return self.sport_item_strs - def choices_completion_error(self) -> List[str]: + def choices_completion_error(self) -> list[str]: """ CompletionErrors can be raised if an error occurs while tab completing. @@ -44,14 +39,14 @@ def choices_completion_error(self) -> List[str]: return self.sport_item_strs raise CompletionError("debug must be true") - def choices_completion_item(self) -> List[CompletionItem]: + def choices_completion_item(self) -> list[CompletionItem]: """Return CompletionItem instead of strings. These give more context to what's being tab completed.""" fancy_item = "These things can\ncontain newlines and\n" fancy_item += ansi.style("styled text!!", fg=ansi.Fg.LIGHT_YELLOW, underline=True) items = {1: "My item", 2: "Another item", 3: "Yet another item", 4: fancy_item} return [CompletionItem(item_id, description) for item_id, description in items.items()] - def choices_arg_tokens(self, arg_tokens: Dict[str, List[str]]) -> List[str]: + def choices_arg_tokens(self, arg_tokens: dict[str, list[str]]) -> list[str]: """ If a choices or completer function/method takes a value called arg_tokens, then it will be passed a dictionary that maps the command line tokens up through the one being completed diff --git a/examples/async_printing.py b/examples/async_printing.py index e94ee89a0..8520bc130 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple example demonstrating an application that asynchronously prints alerts, updates the prompt and changes the window title @@ -8,9 +7,6 @@ import random import threading import time -from typing import ( - List, -) import cmd2 from cmd2 import ( @@ -89,7 +85,7 @@ def do_stop_alerts(self, _): else: print("The alert thread is already stopped") - def _get_alerts(self) -> List[str]: + def _get_alerts(self) -> list[str]: """ Reports alerts :return: the list of alerts @@ -114,7 +110,7 @@ def _get_alerts(self) -> List[str]: for i in range(0, rand_num): self._alert_count += 1 - alerts.append("Alert {}".format(self._alert_count)) + alerts.append(f"Alert {self._alert_count}") self._next_alert_time = 0 @@ -187,7 +183,7 @@ def _alerter_thread_func(self) -> None: if alert_str: # new_prompt is an optional parameter to async_alert() self.async_alert(alert_str, new_prompt) - new_title = "Alerts Printed: {}".format(self._alert_count) + new_title = f"Alerts Printed: {self._alert_count}" self.set_window_title(new_title) # Otherwise check if the prompt needs to be updated or refreshed diff --git a/examples/basic.py b/examples/basic.py index 6ce4d2838..f84009b2b 100755 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example demonstrating the following: 1) How to add a command 2) How to add help for that command diff --git a/examples/basic_completion.py b/examples/basic_completion.py index c713f2b0d..e2288f4bf 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple example demonstrating how to enable tab completion by assigning a completer function to do_* commands. This also demonstrates capabilities of the following completer features included with cmd2: @@ -14,9 +13,6 @@ """ import functools -from typing import ( - List, -) import cmd2 @@ -44,9 +40,9 @@ def do_flag_based(self, statement: cmd2.Statement): -s, --sport [completes sports] -p, --path [completes local file system paths] """ - self.poutput("Args: {}".format(statement.args)) + self.poutput(f"Args: {statement.args}") - def complete_flag_based(self, text, line, begidx, endidx) -> List[str]: + def complete_flag_based(self, text, line, begidx, endidx) -> list[str]: """Completion function for do_flag_based""" flag_dict = { # Tab complete food items after -f and --food flags in command line @@ -64,9 +60,9 @@ def complete_flag_based(self, text, line, begidx, endidx) -> List[str]: def do_index_based(self, statement: cmd2.Statement): """Tab completes first 3 arguments using index_based_complete""" - self.poutput("Args: {}".format(statement.args)) + self.poutput(f"Args: {statement.args}") - def complete_index_based(self, text, line, begidx, endidx) -> List[str]: + def complete_index_based(self, text, line, begidx, endidx) -> list[str]: """Completion function for do_index_based""" index_dict = { 1: food_item_strs, # Tab complete food items at index 1 in command line @@ -78,16 +74,16 @@ def complete_index_based(self, text, line, begidx, endidx) -> List[str]: def do_delimiter_complete(self, statement: cmd2.Statement): """Tab completes files from a list using delimiter_complete""" - self.poutput("Args: {}".format(statement.args)) + self.poutput(f"Args: {statement.args}") # Use a partialmethod to set arguments to delimiter_complete complete_delimiter_complete = functools.partialmethod(cmd2.Cmd.delimiter_complete, match_against=file_strs, delimiter='/') def do_raise_error(self, statement: cmd2.Statement): """Demonstrates effect of raising CompletionError""" - self.poutput("Args: {}".format(statement.args)) + self.poutput(f"Args: {statement.args}") - def complete_raise_error(self, text, line, begidx, endidx) -> List[str]: + def complete_raise_error(self, text, line, begidx, endidx) -> list[str]: """ CompletionErrors can be raised if an error occurs while tab completing. diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index 75d53ed73..a9f5b896f 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2. diff --git a/examples/colors.py b/examples/colors.py index 34f16da2c..28b0c6707 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2. Demonstrating colorized output. diff --git a/examples/custom_parser.py b/examples/custom_parser.py index 94df3b054..fd74f5b56 100644 --- a/examples/custom_parser.py +++ b/examples/custom_parser.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Defines the CustomParser used with override_parser.py example """ @@ -35,7 +34,7 @@ def error(self, message: str) -> None: # Format errors with style_warning() formatted_message = ansi.style_warning(formatted_message) - self.exit(2, '{}\n\n'.format(formatted_message)) + self.exit(2, f'{formatted_message}\n\n') # Now set the default parser for a cmd2 app diff --git a/examples/decorator_example.py b/examples/decorator_example.py index ea8fd3b50..4c80436f8 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A sample application showing how to use cmd2's argparse decorators to process command line arguments for your application. @@ -12,9 +11,6 @@ """ import argparse -from typing import ( - List, -) import cmd2 @@ -71,12 +67,12 @@ def do_tag(self, args: argparse.Namespace): # The Namespace always includes the Statement object created when parsing the command line statement = args.cmd2_statement.get() - self.poutput("The command line you ran was: {}".format(statement.command_and_args)) + self.poutput(f"The command line you ran was: {statement.command_and_args}") self.poutput("It generated this tag:") self.poutput('<{0}>{1}'.format(args.tag, ' '.join(args.content))) @cmd2.with_argument_list - def do_tagg(self, arglist: List[str]): + def do_tagg(self, arglist: list[str]): """version of creating an html tag using arglist instead of argparser""" if len(arglist) >= 2: tag = arglist[0] diff --git a/examples/default_categories.py b/examples/default_categories.py index 0fd485ae3..3d531931f 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ diff --git a/examples/dynamic_commands.py b/examples/dynamic_commands.py index 82dde732d..ef5c520b1 100755 --- a/examples/dynamic_commands.py +++ b/examples/dynamic_commands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example demonstrating how do_* commands can be created in a loop.""" import functools @@ -41,7 +40,7 @@ def send_text(self, args: cmd2.Statement, *, text: str): def text_help(self, *, text: str): """Deal with printing help for the dynamically added commands.""" - self.poutput("Simulate sending {!r} to a server and printing the response".format(text)) + self.poutput(f"Simulate sending {text!r} to a server and printing the response") if __name__ == '__main__': diff --git a/examples/environment.py b/examples/environment.py index 1bb9812be..38c0f0bd6 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2 demonstrating customized environment parameters """ @@ -22,7 +21,7 @@ def __init__(self): def do_sunbathe(self, arg): """Attempt to sunbathe.""" if self.degrees_c < 20: - result = "It's {} C - are you a penguin?".format(self.degrees_c) + result = f"It's {self.degrees_c} C - are you a penguin?" elif not self.sunny: result = 'Too dim.' else: diff --git a/examples/event_loops.py b/examples/event_loops.py index e5435181a..8f627577b 100755 --- a/examples/event_loops.py +++ b/examples/event_loops.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A sample application for integrating cmd2 with external event loops. This is an example of how to use cmd2 in a way so that cmd2 doesn't own the inner event loop of your application. diff --git a/examples/example.py b/examples/example.py index 2ff64d747..0ce4d9bb8 100755 --- a/examples/example.py +++ b/examples/example.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2. diff --git a/examples/exit_code.py b/examples/exit_code.py index d8e538ced..ccf4b8f21 100755 --- a/examples/exit_code.py +++ b/examples/exit_code.py @@ -1,11 +1,6 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application.""" -from typing import ( - List, -) - import cmd2 @@ -16,7 +11,7 @@ def __init__(self): super().__init__() @cmd2.with_argument_list - def do_exit(self, arg_list: List[str]) -> bool: + def do_exit(self, arg_list: list[str]) -> bool: """Exit the application with an optional exit code. Usage: exit [exit_code] @@ -27,7 +22,7 @@ def do_exit(self, arg_list: List[str]) -> bool: try: self.exit_code = int(arg_list[0]) except ValueError: - self.perror("{} isn't a valid integer exit code".format(arg_list[0])) + self.perror(f"{arg_list[0]} isn't a valid integer exit code") self.exit_code = 1 return True @@ -38,5 +33,5 @@ def do_exit(self, arg_list: List[str]) -> bool: app = ReplWithExitCode() sys_exit_code = app.cmdloop() - app.poutput('{!r} exiting with code: {}'.format(sys.argv[0], sys_exit_code)) + app.poutput(f'{sys.argv[0]!r} exiting with code: {sys_exit_code}') sys.exit(sys_exit_code) diff --git a/examples/first_app.py b/examples/first_app.py index 57a76f708..f19cdec92 100755 --- a/examples/first_app.py +++ b/examples/first_app.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple application using cmd2 which demonstrates 8 key features: diff --git a/examples/hello_cmd2.py b/examples/hello_cmd2.py index a67205834..76ff55c75 100755 --- a/examples/hello_cmd2.py +++ b/examples/hello_cmd2.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ This is intended to be a completely bare-bones cmd2 application suitable for rapid testing and debugging. """ diff --git a/examples/help_categories.py b/examples/help_categories.py index 5c349422c..4790b915f 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for tagging categories on commands. @@ -162,7 +161,7 @@ def do_version(self, _): @cmd2.with_category("Command Management") def do_disable_commands(self, _): """Disable the Application Management commands""" - message_to_print = "{} is not available while {} commands are disabled".format(COMMAND_NAME, self.CMD_CAT_APP_MGMT) + message_to_print = f"{COMMAND_NAME} is not available while {self.CMD_CAT_APP_MGMT} commands are disabled" self.disable_category(self.CMD_CAT_APP_MGMT, message_to_print) self.poutput("The Application Management commands have been disabled") diff --git a/examples/hooks.py b/examples/hooks.py index 97b90739d..683b75474 100755 --- a/examples/hooks.py +++ b/examples/hooks.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2 demonstrating how to use hooks. @@ -10,9 +9,6 @@ """ import re -from typing import ( - List, -) import cmd2 @@ -102,7 +98,7 @@ def proof_hook(self, data: cmd2.plugin.PostcommandData) -> cmd2.plugin.Postcomma return data @cmd2.with_argument_list - def do_list(self, arglist: List[str]) -> None: + def do_list(self, arglist: list[str]) -> None: """Generate a list of 10 numbers.""" if arglist: first = arglist[0] diff --git a/examples/initialization.py b/examples/initialization.py index 8cdf07341..ea9d88589 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example cmd2 application demonstrating the following: 1) Colorizing/stylizing output 2) Using multiline commands diff --git a/examples/migrating.py b/examples/migrating.py index 199b78db7..590e9ee20 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample cmd application that shows how to trivially migrate a cmd application to use cmd2. """ diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index 8587b98d7..7e17ac024 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -1,12 +1,7 @@ -# coding=utf-8 """ A simple example demonstrating a loadable command set """ -from typing import ( - List, -) - from cmd2 import ( CommandSet, CompletionError, @@ -37,9 +32,9 @@ def do_flag_based(self, statement: Statement) -> None: -s, --sport [completes sports] -p, --path [completes local file system paths] """ - self._cmd.poutput("Args: {}".format(statement.args)) + self._cmd.poutput(f"Args: {statement.args}") - def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """Completion function for do_flag_based""" flag_dict = { # Tab complete food items after -f and --food flags in command line @@ -57,9 +52,9 @@ def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> def do_index_based(self, statement: Statement) -> None: """Tab completes first 3 arguments using index_based_complete""" - self._cmd.poutput("Args: {}".format(statement.args)) + self._cmd.poutput(f"Args: {statement.args}") - def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """Completion function for do_index_based""" index_dict = { 1: self.food_item_strs, # Tab complete food items at index 1 in command line @@ -71,16 +66,16 @@ def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) - def do_delimiter_complete(self, statement: Statement) -> None: """Tab completes files from a list using delimiter_complete""" - self._cmd.poutput("Args: {}".format(statement.args)) + self._cmd.poutput(f"Args: {statement.args}") - def complete_delimiter_complete(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_delimiter_complete(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return self._cmd.delimiter_complete(text, line, begidx, endidx, match_against=self.file_strs, delimiter='/') def do_raise_error(self, statement: Statement) -> None: """Demonstrates effect of raising CompletionError""" - self._cmd.poutput("Args: {}".format(statement.args)) + self._cmd.poutput(f"Args: {statement.args}") - def complete_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """ CompletionErrors can be raised if an error occurs while tab completing. diff --git a/examples/modular_commands/commandset_custominit.py b/examples/modular_commands/commandset_custominit.py index a3f4f59ad..5ef2beca4 100644 --- a/examples/modular_commands/commandset_custominit.py +++ b/examples/modular_commands/commandset_custominit.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ A simple example demonstrating a loadable command set """ diff --git a/examples/modular_commands_basic.py b/examples/modular_commands_basic.py index 4d5f83cee..37cad3192 100755 --- a/examples/modular_commands_basic.py +++ b/examples/modular_commands_basic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ diff --git a/examples/modular_commands_dynamic.py b/examples/modular_commands_dynamic.py index 8264c068f..b68e6f577 100755 --- a/examples/modular_commands_dynamic.py +++ b/examples/modular_commands_dynamic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """ Simple example demonstrating dynamic CommandSet loading and unloading. diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 740078671..25138db83 100755 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -1,14 +1,12 @@ #!/usr/bin/env python -# coding=utf-8 """ A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators with examples of how to integrate tab completion with argparse-based commands. """ import argparse +from collections.abc import Iterable from typing import ( - Iterable, - List, Optional, ) @@ -35,7 +33,7 @@ def __init__(self, command_sets: Optional[Iterable[CommandSet]] = None): super().__init__(command_sets=command_sets) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - def choices_provider(self) -> List[str]: + def choices_provider(self) -> list[str]: """A choices provider is useful when the choice list is based on instance data of your application""" return self.sport_item_strs diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index 14d117814..ef340f526 100755 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example demonstrating modular subcommand loading through CommandSets In this example, there are loadable CommandSets defined. Each CommandSet has 1 subcommand defined that will be diff --git a/examples/paged_output.py b/examples/paged_output.py index 0f7173b2e..9aed7e193 100755 --- a/examples/paged_output.py +++ b/examples/paged_output.py @@ -1,11 +1,7 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating the using paged output via the ppaged() method.""" import os -from typing import ( - List, -) import cmd2 @@ -20,14 +16,14 @@ def page_file(self, file_path: str, chop: bool = False): """Helper method to prevent having too much duplicated code.""" filename = os.path.expanduser(file_path) try: - with open(filename, 'r') as f: + with open(filename) as f: text = f.read() self.ppaged(text, chop=chop) except OSError as ex: - self.pexcept('Error reading {!r}: {}'.format(filename, ex)) + self.pexcept(f'Error reading {filename!r}: {ex}') @cmd2.with_argument_list - def do_page_wrap(self, args: List[str]): + def do_page_wrap(self, args: list[str]): """Read in a text file and display its output in a pager, wrapping long lines if they don't fit. Usage: page_wrap @@ -40,7 +36,7 @@ def do_page_wrap(self, args: List[str]): complete_page_wrap = cmd2.Cmd.path_complete @cmd2.with_argument_list - def do_page_truncate(self, args: List[str]): + def do_page_truncate(self, args: list[str]): """Read in a text file and display its output in a pager, truncating long lines if they don't fit. Truncated lines can still be accessed by scrolling to the right using the arrow keys. diff --git a/examples/persistent_history.py b/examples/persistent_history.py index ab4b89f2b..c185370cf 100755 --- a/examples/persistent_history.py +++ b/examples/persistent_history.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """This example demonstrates how to enable persistent readline history in your cmd2 application. This will allow end users of your cmd2-based application to use the arrow keys and Ctrl+r in a manner which persists diff --git a/examples/pirate.py b/examples/pirate.py index 8c443d368..1e59e5fe6 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ This example is adapted from the pirate8.py example created by Catherine Devlin and presented as part of her PyCon 2010 talk. @@ -46,7 +45,7 @@ def precmd(self, line): def postcmd(self, stop, line): """Runs right before a command is about to return.""" if self.gold != self.initial_gold: - self.poutput('Now we gots {0} doubloons'.format(self.gold)) + self.poutput(f'Now we gots {self.gold} doubloons') if self.gold < 0: self.poutput("Off to debtorrr's prison.") self.exit_code = 1 @@ -65,7 +64,7 @@ def do_drink(self, arg): self.gold -= int(arg) except ValueError: if arg: - self.poutput('''What's "{0}"? I'll take rrrum.'''.format(arg)) + self.poutput(f'''What's "{arg}"? I'll take rrrum.''') self.gold -= 1 def do_quit(self, arg): @@ -88,7 +87,7 @@ def do_yo(self, args): chant = ['yo'] + ['ho'] * args.ho separator = ', ' if args.commas else ' ' chant = separator.join(chant) - self.poutput('{0} and a bottle of {1}'.format(chant, args.beverage)) + self.poutput(f'{chant} and a bottle of {args.beverage}') if __name__ == '__main__': @@ -97,5 +96,5 @@ def do_yo(self, args): # Create an instance of the Pirate derived class and enter the REPL with cmdloop(). pirate = Pirate() sys_exit_code = pirate.cmdloop() - print('Exiting with code: {!r}'.format(sys_exit_code)) + print(f'Exiting with code: {sys_exit_code!r}') sys.exit(sys_exit_code) diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 688ef52ce..1943e214e 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A sample application for how Python scripting can provide conditional control flow of a cmd2 application. diff --git a/examples/read_input.py b/examples/read_input.py index bfc43380b..a8404bac1 100755 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -1,13 +1,8 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion """ -from typing import ( - List, -) - import cmd2 EXAMPLE_COMMANDS = "Example Commands" @@ -64,7 +59,7 @@ def do_custom_choices(self, _) -> None: else: self.custom_history.append(input_str) - def choices_provider(self) -> List[str]: + def choices_provider(self) -> list[str]: """Example choices provider function""" return ["from_provider_1", "from_provider_2", "from_provider_3"] diff --git a/examples/remove_builtin_commands.py b/examples/remove_builtin_commands.py index 67541a848..a511a4944 100755 --- a/examples/remove_builtin_commands.py +++ b/examples/remove_builtin_commands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating how to remove unused commands. Commands can be removed from help menu and tab completion by appending their command name to the hidden_commands list. diff --git a/examples/remove_settable.py b/examples/remove_settable.py index fad671cbe..191f033d7 100755 --- a/examples/remove_settable.py +++ b/examples/remove_settable.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2 demonstrating how to remove one of the built-in runtime settable parameters. """ diff --git a/examples/scripts/arg_printer.py b/examples/scripts/arg_printer.py index 924e269ac..aca0f0031 100755 --- a/examples/scripts/arg_printer.py +++ b/examples/scripts/arg_printer.py @@ -1,8 +1,7 @@ #!/usr/bin/env python -# coding=utf-8 import os import sys -print("Running Python script {!r} which was called with {} arguments".format(os.path.basename(sys.argv[0]), len(sys.argv) - 1)) +print(f"Running Python script {os.path.basename(sys.argv[0])!r} which was called with {len(sys.argv) - 1} arguments") for i, arg in enumerate(sys.argv[1:]): - print("arg {}: {!r}".format(i + 1, arg)) + print(f"arg {i + 1}: {arg!r}") diff --git a/examples/scripts/script.py b/examples/scripts/script.py index 5f6f411e3..a14f7660d 100644 --- a/examples/scripts/script.py +++ b/examples/scripts/script.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Trivial example of a Python script which can be run inside a cmd2 application. """ diff --git a/examples/subcommands.py b/examples/subcommands.py index 455768e38..532b9f0f8 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example demonstrating how to use Argparse to support subcommands. @@ -82,7 +81,7 @@ def base_bar(self, args): def base_sport(self, args): """sport subcommand of base command""" - self.poutput('Sport is {}'.format(args.sport)) + self.poutput(f'Sport is {args.sport}') # Set handler functions for the subcommands parser_foo.set_defaults(func=base_foo) diff --git a/examples/table_creation.py b/examples/table_creation.py index 0849a0b2c..3f7040ab2 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -1,12 +1,10 @@ #!/usr/bin/env python -# coding=utf-8 """Examples of using the cmd2 table creation API""" import functools import sys from typing import ( Any, - List, ) from cmd2 import ( @@ -37,7 +35,7 @@ def __init__(self, val: float) -> None: def __str__(self) -> str: """Returns the value in dollar currency form (e.g. $100.22)""" - return "${:,.2f}".format(self.val) + return f"${self.val:,.2f}" class Relative: @@ -63,8 +61,8 @@ def __init__(self, name: str, birthday: str, place_of_birth: str) -> None: self.name = name self.birthday = birthday self.place_of_birth = place_of_birth - self.books: List[Book] = [] - self.relatives: List[Relative] = [] + self.books: list[Book] = [] + self.relatives: list[Relative] = [] def ansi_print(text): @@ -76,7 +74,7 @@ def basic_tables(): """Demonstrates basic examples of the table classes""" # Table data which demonstrates handling of wrapping and text styles - data_list: List[List[Any]] = list() + data_list: list[list[Any]] = list() data_list.append(["Billy Smith", "123 Sesame St.\nFake Town, USA 33445", DollarFormatter(100333.03)]) data_list.append( [ @@ -96,7 +94,7 @@ def basic_tables(): data_list.append(["John Jones", "9235 Highway 32\n" + green("Greenville") + ", SC 29604", DollarFormatter(82987.71)]) # Table Columns (width does not account for any borders or padding which may be added) - columns: List[Column] = list() + columns: list[Column] = list() columns.append(Column("Name", width=20)) columns.append(Column("Address", width=38)) columns.append( @@ -123,7 +121,7 @@ def nested_tables(): """ # Create data for this example - author_data: List[Author] = [] + author_data: list[Author] = [] author_1 = Author("Frank Herbert", "10/08/1920", "Tacoma, Washington") author_1.books.append(Book("Dune", "1965")) author_1.books.append(Book("Dune Messiah", "1969")) @@ -159,7 +157,7 @@ def nested_tables(): # Define table which presents Author data fields vertically with no header. # This will be nested in the parent table's first column. - author_columns: List[Column] = list() + author_columns: list[Column] = list() author_columns.append(Column("", width=14)) author_columns.append(Column("", width=20)) @@ -174,7 +172,7 @@ def nested_tables(): # Define AlternatingTable for books checked out by people in the first table. # This will be nested in the parent table's second column. - books_columns: List[Column] = list() + books_columns: list[Column] = list() books_columns.append(Column(ansi.style("Title", bold=True), width=25)) books_columns.append( Column( @@ -196,7 +194,7 @@ def nested_tables(): # Define BorderedTable for relatives of the author # This will be nested in the parent table's third column. - relative_columns: List[Column] = list() + relative_columns: list[Column] = list() relative_columns.append(Column(ansi.style("Name", bold=True), width=25)) relative_columns.append(Column(ansi.style("Relationship", bold=True), width=12)) @@ -220,7 +218,7 @@ def nested_tables(): ) # Define parent AlternatingTable which contains Author and Book tables - parent_tbl_columns: List[Column] = list() + parent_tbl_columns: list[Column] = list() # All of the nested tables already have background colors. Set style_data_text # to False so the parent AlternatingTable does not apply background color to them. @@ -242,7 +240,7 @@ def nested_tables(): ) # Construct the tables - parent_table_data: List[List[Any]] = [] + parent_table_data: list[list[Any]] = [] for row, author in enumerate(author_data, start=1): # First build the author table and color it based on row number author_tbl = even_author_tbl if row % 2 == 0 else odd_author_tbl diff --git a/examples/unicode_commands.py b/examples/unicode_commands.py index 6c76a76e7..ac2cafd39 100755 --- a/examples/unicode_commands.py +++ b/examples/unicode_commands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating support for unicode command names.""" import math @@ -16,7 +15,7 @@ def __init__(self): def do_𝛑print(self, _): """This command prints 𝛑 to 5 decimal places.""" - self.poutput("𝛑 = {0:.6}".format(math.pi)) + self.poutput(f"𝛑 = {math.pi:.6}") def do_你好(self, arg): """This command says hello in Chinese (Mandarin).""" diff --git a/plugins/template/cmd2_myplugin/__init__.py b/plugins/template/cmd2_myplugin/__init__.py index a35976b75..e157ee7a4 100644 --- a/plugins/template/cmd2_myplugin/__init__.py +++ b/plugins/template/cmd2_myplugin/__init__.py @@ -1,5 +1,4 @@ # -# coding=utf-8 """Description of myplugin An overview of what myplugin does. diff --git a/plugins/template/cmd2_myplugin/myplugin.py b/plugins/template/cmd2_myplugin/myplugin.py index 8397e3706..7d36e1d1b 100644 --- a/plugins/template/cmd2_myplugin/myplugin.py +++ b/plugins/template/cmd2_myplugin/myplugin.py @@ -1,11 +1,10 @@ # -# coding=utf-8 """An example cmd2 plugin""" import functools +from collections.abc import Callable from typing import ( TYPE_CHECKING, - Callable, ) import cmd2 diff --git a/plugins/template/examples/example.py b/plugins/template/examples/example.py index b071b5f84..997334906 100644 --- a/plugins/template/examples/example.py +++ b/plugins/template/examples/example.py @@ -1,5 +1,4 @@ # -# coding=utf-8 import cmd2_myplugin diff --git a/plugins/template/setup.py b/plugins/template/setup.py index 126f8dde8..59b064d5a 100644 --- a/plugins/template/setup.py +++ b/plugins/template/setup.py @@ -1,5 +1,4 @@ # -# coding=utf-8 import os diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index ca1058c88..6043d62b5 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -1,5 +1,4 @@ # -# -*- coding: utf-8 -*- """Development related tasks to be run with 'invoke'""" import os @@ -20,7 +19,7 @@ def rmrf(items, verbose=True): for item in items: if verbose: - print("Removing {}".format(item)) + print(f"Removing {item}") shutil.rmtree(item, ignore_errors=True) # rmtree doesn't remove bare files try: diff --git a/plugins/template/tests/test_myplugin.py b/plugins/template/tests/test_myplugin.py index 06ca25670..30c42f9f1 100644 --- a/plugins/template/tests/test_myplugin.py +++ b/plugins/template/tests/test_myplugin.py @@ -1,5 +1,4 @@ # -# coding=utf-8 import cmd2_myplugin diff --git a/pyproject.toml b/pyproject.toml index 73fdddb8c..25b244f9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -216,12 +216,13 @@ select = [ "TD", # flake8-todos (force all TODOs to include an author and issue link) "TID", # flake8-tidy-imports (extra import rules to check) # "TRY", # tryceratops (warnings related to exceptions and try/except) - # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) + # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) "W", # pycodestyle warnings (warn about minor stylistic issues) "YTT", # flake8-2020 (checks for misuse of sys.version or sys.version_info) ] ignore = [ # `uv run ruff rule E501` for a description of that rule + "UP007", # Use X | Y for type annotations (requires Python 3.10 or newer) ] # Allow fix for all enabled rules (when `--fix`) is provided. diff --git a/tests/__init__.py b/tests/__init__.py index 037f3866e..6e9bbe36f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,2 @@ # -# -*- coding: utf-8 -*- # diff --git a/tests/conftest.py b/tests/conftest.py index 644ae7cca..bbd332962 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Cmd2 unit/functional testing """ @@ -10,7 +9,6 @@ redirect_stdout, ) from typing import ( - List, Optional, Union, ) @@ -32,7 +30,7 @@ def verify_help_text( - cmd2_app: cmd2.Cmd, help_output: Union[str, List[str]], verbose_strings: Optional[List[str]] = None + cmd2_app: cmd2.Cmd, help_output: Union[str, list[str]], verbose_strings: Optional[list[str]] = None ) -> None: """This function verifies that all expected commands are present in the help text. @@ -195,7 +193,7 @@ def get_endidx(): return app.complete(text, 0) -def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser: +def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser: if not subcmd_names: return action cur_subcmd = subcmd_names.pop(0) diff --git a/tests/pyscript/raises_exception.py b/tests/pyscript/raises_exception.py index 9979b6692..0df499d9b 100644 --- a/tests/pyscript/raises_exception.py +++ b/tests/pyscript/raises_exception.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Example demonstrating what happens when a Python script raises an exception """ diff --git a/tests/script.py b/tests/script.py index 5f6f411e3..a14f7660d 100644 --- a/tests/script.py +++ b/tests/script.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Trivial example of a Python script which can be run inside a cmd2 application. """ diff --git a/tests_isolated/test_commandset/__init__.py b/tests_isolated/test_commandset/__init__.py index 037f3866e..6e9bbe36f 100644 --- a/tests_isolated/test_commandset/__init__.py +++ b/tests_isolated/test_commandset/__init__.py @@ -1,3 +1,2 @@ # -# -*- coding: utf-8 -*- # diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 23ea7e741..353bf907d 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Cmd2 unit/functional testing """ @@ -9,7 +8,6 @@ redirect_stdout, ) from typing import ( - List, Optional, Union, ) @@ -34,7 +32,7 @@ def verify_help_text( - cmd2_app: cmd2.Cmd, help_output: Union[str, List[str]], verbose_strings: Optional[List[str]] = None + cmd2_app: cmd2.Cmd, help_output: Union[str, list[str]], verbose_strings: Optional[list[str]] = None ) -> None: """This function verifies that all expected commands are present in the help text. diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index 533e02e0c..bdbc4efec 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ From 25ecd04daba5d4a360077ce4ca0ca118b0fb8b01 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 20:51:01 -0400 Subject: [PATCH 19/79] Revert "Applied a number of automated refactorings from ruff UP ruleset" This reverts commit bf519a08f16e74edb63c502a8adbbf4c28d0ab1d. --- cmd2/ansi.py | 6 +- cmd2/argparse_custom.py | 70 +++--- cmd2/clipboard.py | 1 + cmd2/cmd2.py | 209 +++++++++--------- cmd2/command_definition.py | 12 +- cmd2/constants.py | 1 + cmd2/decorators.py | 36 +-- cmd2/exceptions.py | 1 + cmd2/history.py | 12 +- cmd2/parsing.py | 40 ++-- cmd2/plugin.py | 1 + cmd2/py_bridge.py | 6 +- cmd2/rl_utils.py | 1 + cmd2/table_creator.py | 20 +- cmd2/transcript.py | 9 +- cmd2/utils.py | 41 ++-- examples/alias_startup.py | 1 + examples/arg_decorators.py | 9 +- examples/arg_print.py | 17 +- examples/argparse_completion.py | 13 +- examples/async_printing.py | 10 +- examples/basic.py | 1 + examples/basic_completion.py | 18 +- examples/cmd_as_argument.py | 1 + examples/colors.py | 1 + examples/custom_parser.py | 3 +- examples/decorator_example.py | 8 +- examples/default_categories.py | 1 + examples/dynamic_commands.py | 3 +- examples/environment.py | 3 +- examples/event_loops.py | 1 + examples/example.py | 1 + examples/exit_code.py | 11 +- examples/first_app.py | 1 + examples/hello_cmd2.py | 1 + examples/help_categories.py | 3 +- examples/hooks.py | 6 +- examples/initialization.py | 1 + examples/migrating.py | 1 + examples/modular_commands/commandset_basic.py | 21 +- .../modular_commands/commandset_custominit.py | 1 + examples/modular_commands_basic.py | 1 + examples/modular_commands_dynamic.py | 1 + examples/modular_commands_main.py | 6 +- examples/modular_subcommands.py | 1 + examples/paged_output.py | 12 +- examples/persistent_history.py | 1 + examples/pirate.py | 9 +- examples/python_scripting.py | 1 + examples/read_input.py | 7 +- examples/remove_builtin_commands.py | 1 + examples/remove_settable.py | 1 + examples/scripts/arg_printer.py | 5 +- examples/scripts/script.py | 1 + examples/subcommands.py | 3 +- examples/table_creation.py | 24 +- examples/unicode_commands.py | 3 +- plugins/template/cmd2_myplugin/__init__.py | 1 + plugins/template/cmd2_myplugin/myplugin.py | 3 +- plugins/template/examples/example.py | 1 + plugins/template/setup.py | 1 + plugins/template/tasks.py | 3 +- plugins/template/tests/test_myplugin.py | 1 + pyproject.toml | 3 +- tests/__init__.py | 1 + tests/conftest.py | 6 +- tests/pyscript/raises_exception.py | 1 + tests/script.py | 1 + tests_isolated/test_commandset/__init__.py | 1 + tests_isolated/test_commandset/conftest.py | 4 +- .../test_commandset/test_categories.py | 1 + 71 files changed, 426 insertions(+), 282 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index abea54903..e73b7c091 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Support for ANSI escape sequences which are used for things like applying style to text, setting the window title, and asynchronous alerts. @@ -11,6 +12,7 @@ from typing import ( IO, Any, + List, Optional, cast, ) @@ -991,10 +993,10 @@ def style( :return: the stylized string """ # List of strings that add style - additions: list[AnsiSequence] = [] + additions: List[AnsiSequence] = [] # List of strings that remove style - removals: list[AnsiSequence] = [] + removals: List[AnsiSequence] = [] # Process the style settings if fg is not None: diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index e4a6e8331..c68859530 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ This module adds capabilities to argparse by patching a few of its functions. It also defines a parser class called Cmd2ArgumentParser which improves error @@ -229,7 +230,6 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens) ZERO_OR_MORE, ArgumentError, ) -from collections.abc import Callable, Iterable, Sequence from gettext import ( gettext, ) @@ -237,9 +237,17 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens) IO, TYPE_CHECKING, Any, + Callable, + Dict, + Iterable, + List, NoReturn, Optional, Protocol, + Sequence, + Set, + Tuple, + Type, Union, cast, runtime_checkable, @@ -317,7 +325,7 @@ class ChoicesProviderFuncBase(Protocol): Function that returns a list of choices in support of tab completion """ - def __call__(self) -> list[str]: ... # pragma: no cover + def __call__(self) -> List[str]: ... # pragma: no cover @runtime_checkable @@ -326,7 +334,7 @@ class ChoicesProviderFuncWithTokens(Protocol): Function that returns a list of choices in support of tab completion and accepts a dictionary of prior arguments. """ - def __call__(self, *, arg_tokens: dict[str, list[str]] = {}) -> list[str]: ... # pragma: no cover + def __call__(self, *, arg_tokens: Dict[str, List[str]] = {}) -> List[str]: ... # pragma: no cover ChoicesProviderFunc = Union[ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens] @@ -344,7 +352,7 @@ def __call__( line: str, begidx: int, endidx: int, - ) -> list[str]: ... # pragma: no cover + ) -> List[str]: ... # pragma: no cover @runtime_checkable @@ -361,8 +369,8 @@ def __call__( begidx: int, endidx: int, *, - arg_tokens: dict[str, list[str]] = {}, - ) -> list[str]: ... # pragma: no cover + arg_tokens: Dict[str, List[str]] = {}, + ) -> List[str]: ... # pragma: no cover CompleterFunc = Union[CompleterFuncBase, CompleterFuncWithTokens] @@ -563,7 +571,7 @@ def _action_set_descriptive_header(self: argparse.Action, descriptive_header: Op ############################################################################################################ # Patch argparse.Action with accessors for nargs_range attribute ############################################################################################################ -def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[int, float]]]: +def _action_get_nargs_range(self: argparse.Action) -> Optional[Tuple[int, Union[int, float]]]: """ Get the nargs_range attribute of an argparse Action. @@ -574,13 +582,13 @@ def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[ :param self: argparse Action being queried :return: The value of nargs_range or None if attribute does not exist """ - return cast("Optional[tuple[int, Union[int, float]]]", getattr(self, ATTR_NARGS_RANGE, None)) + return cast("Optional[Tuple[int, Union[int, float]]]", getattr(self, ATTR_NARGS_RANGE, None)) setattr(argparse.Action, 'get_nargs_range', _action_get_nargs_range) -def _action_set_nargs_range(self: argparse.Action, nargs_range: Optional[tuple[int, Union[int, float]]]) -> None: +def _action_set_nargs_range(self: argparse.Action, nargs_range: Optional[Tuple[int, Union[int, float]]]) -> None: """ Set the nargs_range attribute of an argparse Action. @@ -638,11 +646,11 @@ def _action_set_suppress_tab_hint(self: argparse.Action, suppress_tab_hint: bool # Allow developers to add custom action attributes ############################################################################################################ -CUSTOM_ACTION_ATTRIBS: set[str] = set() +CUSTOM_ACTION_ATTRIBS: Set[str] = set() _CUSTOM_ATTRIB_PFX = '_attr_' -def register_argparse_argument_parameter(param_name: str, param_type: Optional[type[Any]]) -> None: +def register_argparse_argument_parameter(param_name: str, param_type: Optional[Type[Any]]) -> None: """ Registers a custom argparse argument parameter. @@ -711,7 +719,7 @@ def _action_set_custom_parameter(self: argparse.Action, value: Any) -> None: def _add_argument_wrapper( self: argparse._ActionsContainer, *args: Any, - nargs: Union[int, str, tuple[int], tuple[int, int], tuple[int, float], None] = None, + nargs: Union[int, str, Tuple[int], Tuple[int, int], Tuple[int, float], None] = None, choices_provider: Optional[ChoicesProviderFunc] = None, completer: Optional[CompleterFunc] = None, suppress_tab_hint: bool = False, @@ -762,7 +770,7 @@ def _add_argument_wrapper( nargs_range = None if nargs is not None: - nargs_adjusted: Union[int, str, tuple[int], tuple[int, int], tuple[int, float], None] + nargs_adjusted: Union[int, str, Tuple[int], Tuple[int, int], Tuple[int, float], None] # Check if nargs was given as a range if isinstance(nargs, tuple): # Handle 1-item tuple by setting max to INFINITY @@ -812,7 +820,7 @@ def _add_argument_wrapper( kwargs['nargs'] = nargs_adjusted # Extract registered custom keyword arguments - custom_attribs: dict[str, Any] = {} + custom_attribs: Dict[str, Any] = {} for keyword, value in kwargs.items(): if keyword in CUSTOM_ACTION_ATTRIBS: custom_attribs[keyword] = value @@ -910,7 +918,7 @@ def _match_argument_wrapper(self: argparse.ArgumentParser, action: argparse.Acti ATTR_AP_COMPLETER_TYPE = 'ap_completer_type' -def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[type['ArgparseCompleter']]: +def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[Type['ArgparseCompleter']]: """ Get the ap_completer_type attribute of an argparse ArgumentParser. @@ -921,13 +929,13 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti :param self: ArgumentParser being queried :return: An ArgparseCompleter-based class or None if attribute does not exist """ - return cast("Optional[type[ArgparseCompleter]]", getattr(self, ATTR_AP_COMPLETER_TYPE, None)) + return cast("Optional[Type[ArgparseCompleter]]", getattr(self, ATTR_AP_COMPLETER_TYPE, None)) setattr(argparse.ArgumentParser, 'get_ap_completer_type', _ArgumentParser_get_ap_completer_type) -def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: type['ArgparseCompleter']) -> None: +def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: Type['ArgparseCompleter']) -> None: """ Set the ap_completer_type attribute of an argparse ArgumentParser. @@ -1084,9 +1092,9 @@ def _format_usage( # End cmd2 customization # helper for wrapping lines - def get_lines(parts: list[str], indent: str, prefix: Optional[str] = None) -> list[str]: - lines: list[str] = [] - line: list[str] = [] + def get_lines(parts: List[str], indent: str, prefix: Optional[str] = None) -> List[str]: + lines: List[str] = [] + line: List[str] = [] if prefix is not None: line_len = len(prefix) - 1 else: @@ -1147,7 +1155,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: (metavar,) = self._metavar_formatter(action, default)(1) return metavar - parts: list[str] = [] + parts: List[str] = [] # if the Optional doesn't take a value, format is: # -s, --long @@ -1167,8 +1175,8 @@ def _format_action_invocation(self, action: argparse.Action) -> str: def _determine_metavar( self, action: argparse.Action, - default_metavar: Union[str, tuple[str, ...]], - ) -> Union[str, tuple[str, ...]]: + default_metavar: Union[str, Tuple[str, ...]], + ) -> Union[str, Tuple[str, ...]]: """Custom method to determine what to use as the metavar value of an action""" if action.metavar is not None: result = action.metavar @@ -1184,18 +1192,18 @@ def _determine_metavar( def _metavar_formatter( self, action: argparse.Action, - default_metavar: Union[str, tuple[str, ...]], - ) -> Callable[[int], tuple[str, ...]]: + default_metavar: Union[str, Tuple[str, ...]], + ) -> Callable[[int], Tuple[str, ...]]: metavar = self._determine_metavar(action, default_metavar) - def format_tuple(tuple_size: int) -> tuple[str, ...]: + def format_tuple(tuple_size: int) -> Tuple[str, ...]: if isinstance(metavar, tuple): return metavar return (metavar,) * tuple_size return format_tuple - def _format_args(self, action: argparse.Action, default_metavar: Union[str, tuple[str, ...]]) -> str: + def _format_args(self, action: argparse.Action, default_metavar: Union[str, Tuple[str, ...]]) -> str: """Customized to handle ranged nargs and make other output less verbose""" metavar = self._determine_metavar(action, default_metavar) metavar_formatter = self._metavar_formatter(action, default_metavar) @@ -1233,7 +1241,7 @@ def __init__( description: Optional[str] = None, epilog: Optional[str] = None, parents: Sequence[argparse.ArgumentParser] = (), - formatter_class: type[argparse.HelpFormatter] = Cmd2HelpFormatter, + formatter_class: Type[argparse.HelpFormatter] = Cmd2HelpFormatter, prefix_chars: str = '-', fromfile_prefix_chars: Optional[str] = None, argument_default: Optional[str] = None, @@ -1244,7 +1252,7 @@ def __init__( suggest_on_error: bool = False, color: bool = False, *, - ap_completer_type: Optional[type['ArgparseCompleter']] = None, + ap_completer_type: Optional[Type['ArgparseCompleter']] = None, ) -> None: """ # Custom parameter added by cmd2 @@ -1402,10 +1410,10 @@ def set(self, new_val: Any) -> None: # The default ArgumentParser class for a cmd2 app -DEFAULT_ARGUMENT_PARSER: type[argparse.ArgumentParser] = Cmd2ArgumentParser +DEFAULT_ARGUMENT_PARSER: Type[argparse.ArgumentParser] = Cmd2ArgumentParser -def set_default_argument_parser_type(parser_type: type[argparse.ArgumentParser]) -> None: +def set_default_argument_parser_type(parser_type: Type[argparse.ArgumentParser]) -> None: """ Set the default ArgumentParser class for a cmd2 app. This must be called prior to loading cmd2.py if you want to override the parser for cmd2's built-in commands. See examples/override_parser.py. diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index b59e09dc9..30cb1a670 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ This module provides basic ability to copy from and paste to the clipboard/pastebuffer. """ diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 07490d057..9754d494b 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1,3 +1,4 @@ +# coding=utf-8 """Variant on standard library's cmd with extra features. To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you @@ -47,7 +48,6 @@ OrderedDict, namedtuple, ) -from collections.abc import Callable, Iterable, Mapping from contextlib import ( redirect_stdout, ) @@ -59,8 +59,16 @@ IO, TYPE_CHECKING, Any, + Callable, + Dict, + Iterable, + List, + Mapping, Optional, + Set, TextIO, + Tuple, + Type, TypeVar, Union, cast, @@ -193,7 +201,7 @@ class _SavedCmd2Env: def __init__(self) -> None: self.readline_settings = _SavedReadlineSettings() self.readline_module: Optional[ModuleType] = None - self.history: list[str] = [] + self.history: List[str] = [] self.sys_stdout: Optional[TextIO] = None self.sys_stdin: Optional[TextIO] = None @@ -222,7 +230,7 @@ def __init__(self, cmd: 'Cmd') -> None: # Keyed by the fully qualified method names. This is more reliable than # the methods themselves, since wrapping a method will change its address. - self._parsers: dict[str, argparse.ArgumentParser] = {} + self._parsers: Dict[str, argparse.ArgumentParser] = {} @staticmethod def _fully_qualified_name(command_method: CommandFunc) -> str: @@ -317,11 +325,11 @@ def __init__( include_py: bool = False, include_ipy: bool = False, allow_cli_args: bool = True, - transcript_files: Optional[list[str]] = None, + transcript_files: Optional[List[str]] = None, allow_redirection: bool = True, - multiline_commands: Optional[list[str]] = None, - terminators: Optional[list[str]] = None, - shortcuts: Optional[dict[str, str]] = None, + multiline_commands: Optional[List[str]] = None, + terminators: Optional[List[str]] = None, + shortcuts: Optional[Dict[str, str]] = None, command_sets: Optional[Iterable[CommandSet]] = None, auto_load_commands: bool = True, allow_clipboard: bool = True, @@ -411,12 +419,12 @@ def __init__( self.max_completion_items = 50 # A dictionary mapping settable names to their Settable instance - self._settables: dict[str, Settable] = dict() + self._settables: Dict[str, Settable] = dict() self._always_prefix_settables: bool = False # CommandSet containers - self._installed_command_sets: set[CommandSet] = set() - self._cmd_to_command_sets: dict[str, CommandSet] = {} + self._installed_command_sets: Set[CommandSet] = set() + self._cmd_to_command_sets: Dict[str, CommandSet] = {} self.build_settables() @@ -438,16 +446,16 @@ def __init__( self.exclude_from_history = ['eof', 'history'] # Dictionary of macro names and their values - self.macros: dict[str, Macro] = dict() + self.macros: Dict[str, Macro] = dict() # Keeps track of typed command history in the Python shell - self._py_history: list[str] = [] + self._py_history: List[str] = [] # The name by which Python environments refer to the PyBridge to call app commands self.py_bridge_name = 'app' # Defines app-specific variables/functions available in Python shells and pyscripts - self.py_locals: dict[str, Any] = dict() + self.py_locals: Dict[str, Any] = dict() # True if running inside a Python shell or pyscript, False otherwise self._in_py = False @@ -460,7 +468,7 @@ def __init__( self.last_result: Any = None # Used by run_script command to store current script dir as a LIFO queue to support _relative_run_script command - self._script_dir: list[str] = [] + self._script_dir: List[str] = [] # Context manager used to protect critical sections in the main thread from stopping due to a KeyboardInterrupt self.sigint_protection = utils.ContextFlag() @@ -491,7 +499,7 @@ def __init__( self.broken_pipe_warning = '' # Commands that will run at the beginning of the command loop - self._startup_commands: list[str] = [] + self._startup_commands: List[str] = [] # If a startup script is provided and exists, then execute it in the startup commands if startup_script: @@ -503,7 +511,7 @@ def __init__( self._startup_commands.append(script_cmd) # Transcript files to run instead of interactive command loop - self._transcript_files: Optional[list[str]] = None + self._transcript_files: Optional[List[str]] = None # Check for command line args if allow_cli_args: @@ -546,7 +554,7 @@ def __init__( # Commands that have been disabled from use. This is to support commands that are only available # during specific states of the application. This dictionary's keys are the command names and its # values are DisabledCommand objects. - self.disabled_commands: dict[str, DisabledCommand] = dict() + self.disabled_commands: Dict[str, DisabledCommand] = dict() # If any command has been categorized, then all other commands that haven't been categorized # will display under this section in the help output. @@ -584,7 +592,7 @@ def __init__( self.formatted_completions = '' # Used by complete() for readline tab completion - self.completion_matches: list[str] = [] + self.completion_matches: List[str] = [] # Use this list if you need to display tab completion suggestions that are different than the actual text # of the matches. For instance, if you are completing strings that contain a common delimiter and you only @@ -592,7 +600,7 @@ def __init__( # still must be returned from your completer function. For an example, look at path_complete() which # uses this to show only the basename of paths as the suggestions. delimiter_complete() also populates # this list. These are ignored if self.formatted_completions is populated. - self.display_matches: list[str] = [] + self.display_matches: List[str] = [] # Used by functions like path_complete() and delimiter_complete() to properly # quote matches that are completed in a delimited fashion @@ -634,7 +642,7 @@ def __init__( # the current command being executed self.current_command: Optional[Statement] = None - def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: bool = False) -> list[CommandSet]: + def find_commandsets(self, commandset_type: Type[CommandSet], *, subclass_match: bool = False) -> List[CommandSet]: """ Find all CommandSets that match the provided CommandSet type. By default, locates a CommandSet that is an exact type match but may optionally return all CommandSets that @@ -663,7 +671,7 @@ def _autoload_commands(self) -> None: all_commandset_defs = CommandSet.__subclasses__() existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets] - def load_commandset_by_type(commandset_types: list[type[CommandSet]]) -> None: + def load_commandset_by_type(commandset_types: List[Type[CommandSet]]) -> None: for cmdset_type in commandset_types: # check if the type has sub-classes. We will only auto-load leaf class types. subclasses = cmdset_type.__subclasses__() @@ -707,7 +715,7 @@ def register_command_set(self, cmdset: CommandSet) -> None: cmdset.on_register(self) methods = cast( - "list[tuple[str, Callable[..., Any]]]", + "List[Tuple[str, Callable[..., Any]]]", inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] @@ -849,7 +857,7 @@ def unregister_command_set(self, cmdset: CommandSet) -> None: cmdset.on_unregister() self._unregister_subcommands(cmdset) - methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers( + methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] and hasattr(meth, '__name__') @@ -895,7 +903,7 @@ def check_parser_uninstallable(parser: argparse.ArgumentParser) -> None: check_parser_uninstallable(subparser) break - methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers( + methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] and hasattr(meth, '__name__') @@ -956,7 +964,7 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}" ) - def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser: + def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser: if not subcmd_names: return action cur_subcmd = subcmd_names.pop(0) @@ -1132,7 +1140,7 @@ def remove_settable(self, name: str) -> None: def build_settables(self) -> None: """Create the dictionary of user-settable parameters""" - def get_allow_style_choices(cli_self: Cmd) -> list[str]: + def get_allow_style_choices(cli_self: Cmd) -> List[str]: """Used to tab complete allow_style values""" return [val.name.lower() for val in ansi.AllowStyle] @@ -1375,7 +1383,7 @@ def _reset_completion_defaults(self) -> None: elif rl_type == RlType.PYREADLINE: readline.rl.mode._display_completions = self._display_matches_pyreadline - def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> tuple[list[str], list[str]]: + def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[List[str], List[str]]: """Used by tab completion functions to get all tokens through the one being completed. :param line: the current input line with leading whitespace removed @@ -1446,7 +1454,7 @@ def basic_complete( begidx: int, endidx: int, match_against: Iterable[str], - ) -> list[str]: + ) -> List[str]: """ Basic tab completion function that matches against a list of strings without considering line contents or cursor position. The args required by this function are defined in the header of Python's cmd.py. @@ -1468,7 +1476,7 @@ def delimiter_complete( endidx: int, match_against: Iterable[str], delimiter: str, - ) -> list[str]: + ) -> List[str]: """ Performs tab completion against a list but each match is split on a delimiter and only the portion of the match being tab completed is shown as the completion suggestions. @@ -1534,10 +1542,10 @@ def flag_based_complete( line: str, begidx: int, endidx: int, - flag_dict: dict[str, Union[Iterable[str], CompleterFunc]], + flag_dict: Dict[str, Union[Iterable[str], CompleterFunc]], *, all_else: Union[None, Iterable[str], CompleterFunc] = None, - ) -> list[str]: + ) -> List[str]: """Tab completes based on a particular flag preceding the token being completed. :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1586,7 +1594,7 @@ def index_based_complete( index_dict: Mapping[int, Union[Iterable[str], CompleterFunc]], *, all_else: Optional[Union[Iterable[str], CompleterFunc]] = None, - ) -> list[str]: + ) -> List[str]: """Tab completes based on a fixed position in the input string. :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1631,7 +1639,7 @@ def index_based_complete( def path_complete( self, text: str, line: str, begidx: int, endidx: int, *, path_filter: Optional[Callable[[str], bool]] = None - ) -> list[str]: + ) -> List[str]: """Performs completion of local file system paths :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1645,7 +1653,7 @@ def path_complete( """ # Used to complete ~ and ~user strings - def complete_users() -> list[str]: + def complete_users() -> List[str]: users = [] # Windows lacks the pwd module so we can't get a list of users. @@ -1771,7 +1779,7 @@ def complete_users() -> list[str]: return matches - def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> list[str]: + def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> List[str]: """Performs completion of executables either in a user's path or a given path :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1795,7 +1803,7 @@ def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, text, line, begidx, endidx, path_filter=lambda path: os.path.isdir(path) or os.access(path, os.X_OK) ) - def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> list[str]: + def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> List[str]: """Called by complete() as the first tab completion function for all commands It determines if it should tab complete for redirection (|, >, >>) or use the completer function for the current command @@ -1876,7 +1884,7 @@ def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, com return compfunc(text, line, begidx, endidx) @staticmethod - def _pad_matches_to_display(matches_to_display: list[str]) -> tuple[list[str], int]: # pragma: no cover + def _pad_matches_to_display(matches_to_display: List[str]) -> Tuple[List[str], int]: # pragma: no cover """Adds padding to the matches being displayed as tab completion suggestions. The default padding of readline/pyreadine is small and not visually appealing especially if matches have spaces. It appears very squished together. @@ -1898,7 +1906,7 @@ def _pad_matches_to_display(matches_to_display: list[str]) -> tuple[list[str], i return [cur_match + padding for cur_match in matches_to_display], len(padding) def _display_matches_gnu_readline( - self, substitution: str, matches: list[str], longest_match_length: int + self, substitution: str, matches: List[str], longest_match_length: int ) -> None: # pragma: no cover """Prints a match list using GNU readline's rl_display_match_list() @@ -1945,7 +1953,7 @@ def _display_matches_gnu_readline( # rl_display_match_list() expects matches to be in argv format where # substitution is the first element, followed by the matches, and then a NULL. - strings_array = cast("list[Optional[bytes]]", (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) + strings_array = cast("List[Optional[bytes]]", (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) # Copy in the encoded strings and add a NULL to the end strings_array[0] = encoded_substitution @@ -1958,7 +1966,7 @@ def _display_matches_gnu_readline( # Redraw prompt and input line rl_force_redisplay() - def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no cover + def _display_matches_pyreadline(self, matches: List[str]) -> None: # pragma: no cover """Prints a match list using pyreadline3's _display_completions() :param matches: the tab completion matches to display @@ -1994,7 +2002,7 @@ def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no orig_pyreadline_display(matches_to_display) @staticmethod - def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argparse_completer.ArgparseCompleter]: + def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> Type[argparse_completer.ArgparseCompleter]: """ Determine what type of ArgparseCompleter to use on a given parser. If the parser does not have one set, then use argparse_completer.DEFAULT_AP_COMPLETER. @@ -2002,7 +2010,7 @@ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argpar :param parser: the parser to examine :return: type of ArgparseCompleter """ - Completer = Optional[type[argparse_completer.ArgparseCompleter]] + Completer = Optional[Type[argparse_completer.ArgparseCompleter]] completer_type: Completer = parser.get_ap_completer_type() # type: ignore[attr-defined] if completer_type is None: @@ -2304,15 +2312,15 @@ def in_pyscript(self) -> bool: return self._in_py @property - def aliases(self) -> dict[str, str]: + def aliases(self) -> Dict[str, str]: """Read-only property to access the aliases stored in the StatementParser""" return self.statement_parser.aliases - def get_names(self) -> list[str]: + def get_names(self) -> List[str]: """Return an alphabetized list of names comprising the attributes of the cmd2 class instance.""" return dir(self) - def get_all_commands(self) -> list[str]: + def get_all_commands(self) -> List[str]: """Return a list of all commands""" return [ name[len(constants.COMMAND_FUNC_PREFIX) :] @@ -2320,7 +2328,7 @@ def get_all_commands(self) -> list[str]: if name.startswith(constants.COMMAND_FUNC_PREFIX) and callable(getattr(self, name)) ] - def get_visible_commands(self) -> list[str]: + def get_visible_commands(self) -> List[str]: """Return a list of commands that have not been hidden or disabled""" return [ command @@ -2331,9 +2339,9 @@ def get_visible_commands(self) -> list[str]: # Table displayed when tab completing aliases _alias_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) - def _get_alias_completion_items(self) -> list[CompletionItem]: + def _get_alias_completion_items(self) -> List[CompletionItem]: """Return list of alias names and values as CompletionItems""" - results: list[CompletionItem] = [] + results: List[CompletionItem] = [] for cur_key in self.aliases: row_data = [self.aliases[cur_key]] @@ -2344,9 +2352,9 @@ def _get_alias_completion_items(self) -> list[CompletionItem]: # Table displayed when tab completing macros _macro_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) - def _get_macro_completion_items(self) -> list[CompletionItem]: + def _get_macro_completion_items(self) -> List[CompletionItem]: """Return list of macro names and values as CompletionItems""" - results: list[CompletionItem] = [] + results: List[CompletionItem] = [] for cur_key in self.macros: row_data = [self.macros[cur_key].value] @@ -2357,9 +2365,9 @@ def _get_macro_completion_items(self) -> list[CompletionItem]: # Table displayed when tab completing Settables _settable_completion_table = SimpleTable([Column('Value', width=30), Column('Description', width=60)], divider_char=None) - def _get_settable_completion_items(self) -> list[CompletionItem]: + def _get_settable_completion_items(self) -> List[CompletionItem]: """Return list of Settable names, values, and descriptions as CompletionItems""" - results: list[CompletionItem] = [] + results: List[CompletionItem] = [] for cur_key in self.settables: row_data = [self.settables[cur_key].get_value(), self.settables[cur_key].description] @@ -2367,14 +2375,14 @@ def _get_settable_completion_items(self) -> list[CompletionItem]: return results - def _get_commands_aliases_and_macros_for_completion(self) -> list[str]: + def _get_commands_aliases_and_macros_for_completion(self) -> List[str]: """Return a list of visible commands, aliases, and macros for tab completion""" visible_commands = set(self.get_visible_commands()) alias_names = set(self.aliases) macro_names = set(self.macros) return list(visible_commands | alias_names | macro_names) - def get_help_topics(self) -> list[str]: + def get_help_topics(self) -> List[str]: """Return a list of help topics""" all_topics = [ name[len(constants.HELP_FUNC_PREFIX) :] @@ -2474,7 +2482,7 @@ def postloop(self) -> None: """ pass - def parseline(self, line: str) -> tuple[str, str, str]: + def parseline(self, line: str) -> Tuple[str, str, str]: """Parse the line into a command name and a string containing the arguments. NOTE: This is an override of a parent class method. It is only used by other parent class methods. @@ -2546,7 +2554,7 @@ def onecmd_plus_hooks( redir_saved_state = self._redirect_output(statement) - timestart = datetime.datetime.now(tz=datetime.UTC) + timestart = datetime.datetime.now(tz=datetime.timezone.utc) # precommand hooks precmd_data = plugin.PrecommandData(statement) @@ -2572,7 +2580,7 @@ def onecmd_plus_hooks( stop = self.postcmd(stop, statement) if self.timing: - self.pfeedback(f'Elapsed: {datetime.datetime.now(tz=datetime.UTC) - timestart}') + self.pfeedback(f'Elapsed: {datetime.datetime.now(tz=datetime.timezone.utc) - timestart}') finally: # Get sigint protection while we restore stuff with self.sigint_protection: @@ -2637,7 +2645,7 @@ def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) def runcmds_plus_hooks( self, - cmds: Union[list[HistoryItem], list[str]], + cmds: Union[List[HistoryItem], List[str]], *, add_to_history: bool = True, stop_on_keyboard_interrupt: bool = False, @@ -2864,6 +2872,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: :return: A bool telling if an error occurred and a utils.RedirectionSavedState object :raises RedirectionError: if an error occurs trying to pipe or redirect """ + import io import subprocess # Initialize the redirection saved state @@ -2883,13 +2892,13 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: read_fd, write_fd = os.pipe() # Open each side of the pipe - subproc_stdin = open(read_fd) - new_stdout: TextIO = cast("TextIO", open(write_fd, 'w')) + subproc_stdin = io.open(read_fd, 'r') + new_stdout: TextIO = cast("TextIO", io.open(write_fd, 'w')) # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs, # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe # processes close in the right order (most recent first). - kwargs: dict[str, Any] = dict() + kwargs: Dict[str, Any] = dict() if sys.platform == 'win32': kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP else: @@ -3078,7 +3087,7 @@ def read_input( self, prompt: str, *, - history: Optional[list[str]] = None, + history: Optional[List[str]] = None, completion_mode: utils.CompletionMode = utils.CompletionMode.NONE, preserve_quotes: bool = False, choices: Optional[Iterable[Any]] = None, @@ -3119,7 +3128,7 @@ def read_input( """ readline_configured = False saved_completer: Optional[CompleterFunc] = None - saved_history: Optional[list[str]] = None + saved_history: Optional[List[str]] = None def configure_readline() -> None: """Configure readline tab completion and history""" @@ -3500,7 +3509,7 @@ def _alias_list(self, args: argparse.Namespace) -> None: else: to_list = sorted(self.aliases, key=self.default_sort_key) - not_found: list[str] = [] + not_found: List[str] = [] for name in to_list: if name not in self.aliases: not_found.append(name) @@ -3735,7 +3744,7 @@ def _macro_list(self, args: argparse.Namespace) -> None: else: to_list = sorted(self.macros, key=self.default_sort_key) - not_found: list[str] = [] + not_found: List[str] = [] for name in to_list: if name not in self.macros: not_found.append(name) @@ -3757,7 +3766,7 @@ def _macro_list(self, args: argparse.Namespace) -> None: for name in not_found: self.perror(f"Macro '{name}' not found") - def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: + def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: """Completes the command argument of help""" # Complete token against topics and visible commands @@ -3767,8 +3776,8 @@ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) return self.basic_complete(text, line, begidx, endidx, strs_to_match) def complete_help_subcommands( - self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] - ) -> list[str]: + self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]] + ) -> List[str]: """Completes the subcommands argument of help""" # Make sure we have a command whose subcommands we will complete @@ -3837,7 +3846,7 @@ def do_help(self, args: argparse.Namespace) -> None: self.perror(err_msg, apply_style=False) self.last_result = False - def print_topics(self, header: str, cmds: Optional[list[str]], cmdlen: int, maxcol: int) -> None: + def print_topics(self, header: str, cmds: Optional[List[str]], cmdlen: int, maxcol: int) -> None: """ Print groups of commands and topics in columns and an optional header Override of cmd's print_topics() to handle headers with newlines, ANSI style sequences, and wide characters @@ -3855,7 +3864,7 @@ def print_topics(self, header: str, cmds: Optional[list[str]], cmdlen: int, maxc self.columnize(cmds, maxcol - 1) self.poutput() - def columnize(self, str_list: Optional[list[str]], display_width: int = 80) -> None: + def columnize(self, str_list: Optional[List[str]], display_width: int = 80) -> None: """Display a list of single-line strings as a compact set of columns. Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters @@ -3931,15 +3940,15 @@ def _help_menu(self, verbose: bool = False) -> None: self.print_topics(self.misc_header, help_topics, 15, 80) self.print_topics(self.undoc_header, cmds_undoc, 15, 80) - def _build_command_info(self) -> tuple[dict[str, list[str]], list[str], list[str], list[str]]: + def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str], List[str]]: # Get a sorted list of help topics help_topics = sorted(self.get_help_topics(), key=self.default_sort_key) # Get a sorted list of visible command names visible_commands = sorted(self.get_visible_commands(), key=self.default_sort_key) - cmds_doc: list[str] = [] - cmds_undoc: list[str] = [] - cmds_cats: dict[str, list[str]] = {} + cmds_doc: List[str] = [] + cmds_undoc: List[str] = [] + cmds_cats: Dict[str, List[str]] = {} for command in visible_commands: func = cast("CommandFunc", self.cmd_func(command)) has_help_func = False @@ -3962,7 +3971,7 @@ def _build_command_info(self) -> tuple[dict[str, list[str]], list[str], list[str cmds_undoc.append(command) return cmds_cats, cmds_doc, cmds_undoc, help_topics - def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None: + def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None: """Customized version of print_topics that can switch between verbose or traditional output""" import io @@ -4037,7 +4046,7 @@ def do_shortcuts(self, _: argparse.Namespace) -> None: """List available shortcuts""" # Sort the shortcut tuples by name sorted_shortcuts = sorted(self.statement_parser.shortcuts, key=lambda x: self.default_sort_key(x[0])) - result = "\n".join(f'{sc[0]}: {sc[1]}' for sc in sorted_shortcuts) + result = "\n".join('{}: {}'.format(sc[0], sc[1]) for sc in sorted_shortcuts) self.poutput(f"Shortcuts for other commands:\n{result}") self.last_result = True @@ -4065,7 +4074,7 @@ def do_quit(self, _: argparse.Namespace) -> Optional[bool]: self.last_result = True return True - def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') -> Any: + def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') -> Any: """Presents a numbered menu to the user. Modeled after the bash shell's SELECT. Returns the item chosen. @@ -4076,12 +4085,12 @@ def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], p | a list of tuples -> interpreted as (value, text), so that the return value can differ from the text advertised to the user""" - local_opts: Union[list[str], list[tuple[Any, Optional[str]]]] + local_opts: Union[List[str], List[Tuple[Any, Optional[str]]]] if isinstance(opts, str): - local_opts = cast("list[tuple[Any, Optional[str]]]", list(zip(opts.split(), opts.split()))) + local_opts = cast("List[Tuple[Any, Optional[str]]]", list(zip(opts.split(), opts.split()))) else: local_opts = opts - fulloptions: list[tuple[Any, Optional[str]]] = [] + fulloptions: List[Tuple[Any, Optional[str]]] = [] for opt in local_opts: if isinstance(opt, str): fulloptions.append((opt, opt)) @@ -4115,8 +4124,8 @@ def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], p self.poutput(f"'{response}' isn't a valid choice. Pick a number between 1 and {len(fulloptions)}:") def complete_set_value( - self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] - ) -> list[str]: + self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]] + ) -> List[str]: """Completes the value argument of set""" param = arg_tokens['param'][0] try: @@ -4207,7 +4216,7 @@ def do_set(self, args: argparse.Namespace) -> None: max_name_width = max([ansi.style_aware_wcswidth(param) for param in to_show]) max_name_width = max(max_name_width, ansi.style_aware_wcswidth(name_label)) - cols: list[Column] = [ + cols: List[Column] = [ Column(name_label, width=max_name_width), Column('Value', width=30), Column('Description', width=60), @@ -4238,7 +4247,7 @@ def do_shell(self, args: argparse.Namespace) -> None: import signal import subprocess - kwargs: dict[str, Any] = dict() + kwargs: Dict[str, Any] = dict() # Set OS-specific parameters if sys.platform.startswith('win'): @@ -4857,7 +4866,7 @@ def _initialize_history(self, hist_file: str) -> None: with open(hist_file, 'rb') as fobj: compressed_bytes = fobj.read() except FileNotFoundError: - compressed_bytes = b"" + compressed_bytes = bytes() except OSError as ex: self.perror(f"Cannot read persistent history file '{hist_file}': {ex}") return @@ -4876,11 +4885,11 @@ def _initialize_history(self, hist_file: str) -> None: try: import lzma as decompress_lib - decompress_exceptions: tuple[type[Exception]] = (decompress_lib.LZMAError,) + decompress_exceptions: Tuple[type[Exception]] = (decompress_lib.LZMAError,) except ModuleNotFoundError: # pragma: no cover import bz2 as decompress_lib # type: ignore[no-redef] - decompress_exceptions: tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef] + decompress_exceptions: Tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef] try: history_json = decompress_lib.decompress(compressed_bytes).decode(encoding='utf-8') @@ -4937,7 +4946,7 @@ def _persist_history(self) -> None: def _generate_transcript( self, - history: Union[list[HistoryItem], list[str]], + history: Union[List[HistoryItem], List[str]], transcript_file: str, *, add_to_history: bool = True, @@ -5061,7 +5070,7 @@ def run_editor(self, file_path: Optional[str] = None) -> None: :raises EnvironmentError: if self.editor is not set """ if not self.editor: - raise OSError("Please use 'set editor' to specify your text editing program of choice.") + raise EnvironmentError("Please use 'set editor' to specify your text editing program of choice.") command = utils.quote_string(os.path.expanduser(self.editor)) if file_path: @@ -5187,7 +5196,7 @@ def do__relative_run_script(self, args: argparse.Namespace) -> Optional[bool]: # self.last_result will be set by do_run_script() return self.do_run_script(utils.quote_string(relative_path)) - def _run_transcript_tests(self, transcript_paths: list[str]) -> None: + def _run_transcript_tests(self, transcript_paths: List[str]) -> None: """Runs transcript tests for provided file(s). This is called when either -t is provided on the command line or the transcript_files argument is provided @@ -5576,12 +5585,12 @@ def cmdloop(self, intro: Optional[str] = None) -> int: # type: ignore[override] ### def _initialize_plugin_system(self) -> None: """Initialize the plugin system""" - self._preloop_hooks: list[Callable[[], None]] = [] - self._postloop_hooks: list[Callable[[], None]] = [] - self._postparsing_hooks: list[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = [] - self._precmd_hooks: list[Callable[[plugin.PrecommandData], plugin.PrecommandData]] = [] - self._postcmd_hooks: list[Callable[[plugin.PostcommandData], plugin.PostcommandData]] = [] - self._cmdfinalization_hooks: list[Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]] = [] + self._preloop_hooks: List[Callable[[], None]] = [] + self._postloop_hooks: List[Callable[[], None]] = [] + self._postparsing_hooks: List[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = [] + self._precmd_hooks: List[Callable[[plugin.PrecommandData], plugin.PrecommandData]] = [] + self._postcmd_hooks: List[Callable[[plugin.PostcommandData], plugin.PostcommandData]] = [] + self._cmdfinalization_hooks: List[Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]] = [] @classmethod def _validate_callable_param_count(cls, func: Callable[..., Any], count: int) -> None: @@ -5632,7 +5641,7 @@ def register_postparsing_hook(self, func: Callable[[plugin.PostparsingData], plu @classmethod def _validate_prepostcmd_hook( - cls, func: Callable[[CommandDataType], CommandDataType], data_type: type[CommandDataType] + cls, func: Callable[[CommandDataType], CommandDataType], data_type: Type[CommandDataType] ) -> None: """Check parameter and return types for pre and post command hooks.""" signature = inspect.signature(func) @@ -5695,7 +5704,7 @@ def _resolve_func_self( :param cmd_self: The `self` associated with the command or subcommand """ # figure out what class the command support function was defined in - func_class: Optional[type[Any]] = get_defining_class(cmd_support_func) + func_class: Optional[Type[Any]] = get_defining_class(cmd_support_func) # Was there a defining class identified? If so, is it a sub-class of CommandSet? if func_class is not None and issubclass(func_class, CommandSet): @@ -5706,7 +5715,7 @@ def _resolve_func_self( # 2. Do any of the registered CommandSets in the Cmd2 application exactly match the type? # 3. Is there a registered CommandSet that is is the only matching subclass? - func_self: Optional[Union[CommandSet, Cmd]] + func_self: Optional[Union[CommandSet, 'Cmd']] # check if the command's CommandSet is a sub-class of the support function's defining class if isinstance(cmd_self, func_class): @@ -5715,7 +5724,7 @@ def _resolve_func_self( else: # Search all registered CommandSets func_self = None - candidate_sets: list[CommandSet] = [] + candidate_sets: List[CommandSet] = [] for installed_cmd_set in self._installed_command_sets: if type(installed_cmd_set) == func_class: # noqa: E721 # Case 2: CommandSet is an exact type match for the function's CommandSet diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index d32d49b10..5bac8ef32 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -1,11 +1,15 @@ +# coding=utf-8 """ Supports the definition of commands in separate classes to be composed into cmd2.Cmd """ -from collections.abc import Callable, Mapping from typing import ( TYPE_CHECKING, + Callable, + Dict, + Mapping, Optional, + Type, TypeVar, ) @@ -27,7 +31,7 @@ #: Further refinements are needed to define the input parameters CommandFunc = Callable[..., Optional[bool]] -CommandSetType = TypeVar('CommandSetType', bound=type['CommandSet']) +CommandSetType = TypeVar('CommandSetType', bound=Type['CommandSet']) def with_default_category(category: str, *, heritable: bool = True) -> Callable[[CommandSetType], CommandSetType]: @@ -81,7 +85,7 @@ def decorate_class(cls: CommandSetType) -> CommandSetType: return decorate_class -class CommandSet: +class CommandSet(object): """ Base class for defining sets of commands to load in cmd2. @@ -96,7 +100,7 @@ def __init__(self) -> None: # accessed by child classes using the self._cmd property. self.__cmd_internal: Optional[cmd2.Cmd] = None - self._settables: dict[str, Settable] = {} + self._settables: Dict[str, Settable] = {} self._settable_prefix = self.__class__.__name__ @property diff --git a/cmd2/constants.py b/cmd2/constants.py index b45a73bf6..ebac5da95 100644 --- a/cmd2/constants.py +++ b/cmd2/constants.py @@ -1,4 +1,5 @@ # +# coding=utf-8 """This module contains constants used throughout ``cmd2``.""" # Unless documented in https://cmd2.readthedocs.io/en/latest/api/index.html diff --git a/cmd2/decorators.py b/cmd2/decorators.py index dcdfb8f1f..3d1105c64 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -1,11 +1,17 @@ +# coding=utf-8 """Decorators for ``cmd2`` commands""" import argparse -from collections.abc import Callable, Sequence from typing import ( TYPE_CHECKING, Any, + Callable, + Dict, + List, Optional, + Sequence, + Tuple, + Type, TypeVar, Union, ) @@ -62,7 +68,7 @@ def cat_decorator(func: CommandFunc) -> CommandFunc: CommandParent = TypeVar('CommandParent', bound=Union['cmd2.Cmd', CommandSet]) -CommandParentType = TypeVar('CommandParentType', bound=Union[type['cmd2.Cmd'], type[CommandSet]]) +CommandParentType = TypeVar('CommandParentType', bound=Union[Type['cmd2.Cmd'], Type[CommandSet]]) RawCommandFuncOptionalBoolReturn = Callable[[CommandParent, Union[Statement, str]], Optional[bool]] @@ -73,7 +79,7 @@ def cat_decorator(func: CommandFunc) -> CommandFunc: # in cmd2 command functions/callables. As long as the 2-ple of arguments we expect to be there can be # found we can swap out the statement with each decorator's specific parameters ########################## -def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Statement, str]]: +def _parse_positionals(args: Tuple[Any, ...]) -> Tuple['cmd2.Cmd', Union[Statement, str]]: """ Helper function for cmd2 decorators to inspect the positional arguments until the cmd2.Cmd argument is found Assumes that we will find cmd2.Cmd followed by the command statement object or string. @@ -97,7 +103,7 @@ def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Stateme raise TypeError('Expected arguments: cmd: cmd2.Cmd, statement: Union[Statement, str] Not found') -def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> list[Any]: +def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> List[Any]: """ Helper function for cmd2 decorators to swap the Statement parameter with one or more decorator-specific parameters @@ -114,13 +120,13 @@ def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> #: Function signature for a command function that accepts a pre-processed argument list from user input #: and optionally returns a boolean -ArgListCommandFuncOptionalBoolReturn = Callable[[CommandParent, list[str]], Optional[bool]] +ArgListCommandFuncOptionalBoolReturn = Callable[[CommandParent, List[str]], Optional[bool]] #: Function signature for a command function that accepts a pre-processed argument list from user input #: and returns a boolean -ArgListCommandFuncBoolReturn = Callable[[CommandParent, list[str]], bool] +ArgListCommandFuncBoolReturn = Callable[[CommandParent, List[str]], bool] #: Function signature for a command function that accepts a pre-processed argument list from user input #: and returns Nothing -ArgListCommandFuncNoneReturn = Callable[[CommandParent, list[str]], None] +ArgListCommandFuncNoneReturn = Callable[[CommandParent, List[str]], None] #: Aggregate of all accepted function signatures for command functions that accept a pre-processed argument list ArgListCommandFunc = Union[ @@ -202,7 +208,7 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: """ # Set the prog value for this parser parser.prog = prog - req_args: list[str] = [] + req_args: List[str] = [] # Set the prog value for the parser's subcommands for action in parser._actions: @@ -245,17 +251,17 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and optionally return a boolean ArgparseCommandFuncOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace], Optional[bool]] -ArgparseCommandFuncWithUnknownArgsOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace, list[str]], Optional[bool]] +ArgparseCommandFuncWithUnknownArgsOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace, List[str]], Optional[bool]] #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and return a boolean ArgparseCommandFuncBoolReturn = Callable[[CommandParent, argparse.Namespace], bool] -ArgparseCommandFuncWithUnknownArgsBoolReturn = Callable[[CommandParent, argparse.Namespace, list[str]], bool] +ArgparseCommandFuncWithUnknownArgsBoolReturn = Callable[[CommandParent, argparse.Namespace, List[str]], bool] #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and return nothing ArgparseCommandFuncNoneReturn = Callable[[CommandParent, argparse.Namespace], None] -ArgparseCommandFuncWithUnknownArgsNoneReturn = Callable[[CommandParent, argparse.Namespace, list[str]], None] +ArgparseCommandFuncWithUnknownArgsNoneReturn = Callable[[CommandParent, argparse.Namespace, List[str]], None] #: Aggregate of all accepted function signatures for an argparse command function ArgparseCommandFunc = Union[ @@ -337,7 +343,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> RawCommandFuncOpt """ @functools.wraps(func) - def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]: + def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]: """ Command function wrapper which translates command line into argparse Namespace and calls actual command function @@ -370,7 +376,7 @@ def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]: namespace = ns_provider(provider_self if provider_self is not None else cmd2_app) try: - new_args: Union[tuple[argparse.Namespace], tuple[argparse.Namespace, list[str]]] + new_args: Union[Tuple[argparse.Namespace], Tuple[argparse.Namespace, List[str]]] if with_unknown_args: new_args = arg_parser.parse_known_args(parsed_arglist, namespace) else: @@ -415,7 +421,7 @@ def as_subcommand_to( ], *, help: Optional[str] = None, - aliases: Optional[list[str]] = None, + aliases: Optional[List[str]] = None, ) -> Callable[[ArgparseCommandFunc[CommandParent]], ArgparseCommandFunc[CommandParent]]: """ Tag this method as a subcommand to an existing argparse decorated command. @@ -437,7 +443,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> ArgparseCommandFu setattr(func, constants.SUBCMD_ATTR_NAME, subcommand) # Keyword arguments for subparsers.add_parser() - add_parser_kwargs: dict[str, Any] = dict() + add_parser_kwargs: Dict[str, Any] = dict() if help is not None: add_parser_kwargs['help'] = help if aliases: diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index 4f8a517c2..c07100134 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -1,3 +1,4 @@ +# coding=utf-8 """Custom exceptions for cmd2""" from typing import ( diff --git a/cmd2/history.py b/cmd2/history.py index 8b6fc0258..5f9758f96 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ History management classes """ @@ -7,12 +8,15 @@ from collections import ( OrderedDict, ) -from collections.abc import Callable, Iterable from dataclasses import ( dataclass, ) from typing import ( Any, + Callable, + Dict, + Iterable, + List, Optional, Union, overload, @@ -128,12 +132,12 @@ def pr(self, idx: int, script: bool = False, expanded: bool = False, verbose: bo return ret_str - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> Dict[str, Any]: """Utility method to convert this HistoryItem into a dictionary for use in persistent JSON history files""" return {HistoryItem._statement_field: self.statement.to_dict()} @staticmethod - def from_dict(source_dict: dict[str, Any]) -> 'HistoryItem': + def from_dict(source_dict: Dict[str, Any]) -> 'HistoryItem': """ Utility method to restore a HistoryItem from a dictionary @@ -145,7 +149,7 @@ def from_dict(source_dict: dict[str, Any]) -> 'HistoryItem': return HistoryItem(Statement.from_dict(statement_dict)) -class History(list[HistoryItem]): +class History(List[HistoryItem]): """A list of [HistoryItem][cmd2.history.HistoryItem] objects with additional methods for searching and managing the list. diff --git a/cmd2/parsing.py b/cmd2/parsing.py index bd115585b..598cbe687 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -1,16 +1,20 @@ # +# -*- coding: utf-8 -*- """Statement parsing classes for cmd2""" import re import shlex -from collections.abc import Iterable from dataclasses import ( dataclass, field, ) from typing import ( Any, + Dict, + Iterable, + List, Optional, + Tuple, Union, ) @@ -23,7 +27,7 @@ ) -def shlex_split(str_to_split: str) -> list[str]: +def shlex_split(str_to_split: str) -> List[str]: """ A wrapper around shlex.split() that uses cmd2's preferred arguments. This allows other classes to easily call split() the same way StatementParser does. @@ -81,7 +85,7 @@ class Macro: minimum_arg_count: int # Used to fill in argument placeholders in the macro - arg_list: list[MacroArg] = field(default_factory=list) + arg_list: List[MacroArg] = field(default_factory=list) @dataclass(frozen=True) @@ -125,7 +129,7 @@ class Statement(str): # type: ignore[override] command: str = '' # list of arguments to the command, not including any output redirection or terminators; quoted args remain quoted - arg_list: list[str] = field(default_factory=list) + arg_list: List[str] = field(default_factory=list) # if the command is a multiline command, the name of the command, otherwise empty multiline_command: str = '' @@ -202,7 +206,7 @@ def expanded_command_line(self) -> str: return self.command_and_args + self.post_command @property - def argv(self) -> list[str]: + def argv(self) -> List[str]: """a list of arguments a-la ``sys.argv``. The first element of the list is the command after shortcut and macro @@ -221,12 +225,12 @@ def argv(self) -> list[str]: return rtn - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> Dict[str, Any]: """Utility method to convert this Statement into a dictionary for use in persistent JSON history files""" return self.__dict__.copy() @staticmethod - def from_dict(source_dict: dict[str, Any]) -> 'Statement': + def from_dict(source_dict: Dict[str, Any]) -> 'Statement': """ Utility method to restore a Statement from a dictionary @@ -254,8 +258,8 @@ def __init__( self, terminators: Optional[Iterable[str]] = None, multiline_commands: Optional[Iterable[str]] = None, - aliases: Optional[dict[str, str]] = None, - shortcuts: Optional[dict[str, str]] = None, + aliases: Optional[Dict[str, str]] = None, + shortcuts: Optional[Dict[str, str]] = None, ) -> None: """Initialize an instance of StatementParser. @@ -267,13 +271,13 @@ def __init__( :param aliases: dictionary containing aliases :param shortcuts: dictionary containing shortcuts """ - self.terminators: tuple[str, ...] + self.terminators: Tuple[str, ...] if terminators is None: self.terminators = (constants.MULTILINE_TERMINATOR,) else: self.terminators = tuple(terminators) - 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 {} + 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 @@ -314,7 +318,7 @@ def __init__( expr = rf'\A\s*(\S*?)({second_group})' self._command_pattern = re.compile(expr) - def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> tuple[bool, str]: + def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> Tuple[bool, str]: """Determine whether a word is a valid name for a command. Commands cannot include redirection characters, whitespace, @@ -365,7 +369,7 @@ def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> tuple[b errmsg = '' return valid, errmsg - def tokenize(self, line: str) -> list[str]: + def tokenize(self, line: str) -> List[str]: """ Lex a string into a list of tokens. Shortcuts and aliases are expanded and comments are removed. @@ -603,7 +607,7 @@ def parse_command_only(self, rawinput: str) -> Statement: def get_command_arg_list( self, command_name: str, to_parse: Union[Statement, str], preserve_quotes: bool - ) -> tuple[Statement, list[str]]: + ) -> Tuple[Statement, List[str]]: """ Convenience method used by the argument parsing decorators. @@ -671,7 +675,7 @@ def _expand(self, line: str) -> str: return line @staticmethod - def _command_and_args(tokens: list[str]) -> tuple[str, str]: + def _command_and_args(tokens: List[str]) -> Tuple[str, str]: """Given a list of tokens, return a tuple of the command and the args as a string. """ @@ -686,7 +690,7 @@ def _command_and_args(tokens: list[str]) -> tuple[str, str]: return command, args - def split_on_punctuation(self, tokens: list[str]) -> list[str]: + def split_on_punctuation(self, tokens: List[str]) -> List[str]: """Further splits tokens from a command line using punctuation characters. Punctuation characters are treated as word breaks when they are in @@ -696,7 +700,7 @@ def split_on_punctuation(self, tokens: list[str]) -> list[str]: :param tokens: the tokens as parsed by shlex :return: a new list of tokens, further split using punctuation """ - punctuation: list[str] = [] + punctuation: List[str] = [] punctuation.extend(self.terminators) punctuation.extend(constants.REDIRECTION_CHARS) diff --git a/cmd2/plugin.py b/cmd2/plugin.py index a3fed2856..e7e2c6863 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -1,4 +1,5 @@ # +# coding=utf-8 """Classes for the cmd2 plugin system""" from dataclasses import ( diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 0936a74c6..366469afe 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Bridges calls made inside of a Python environment to the Cmd2 host app while maintaining a reasonable degree of isolation between the two. @@ -12,6 +13,7 @@ IO, TYPE_CHECKING, Any, + List, NamedTuple, Optional, TextIO, @@ -96,9 +98,9 @@ def __init__(self, cmd2_app: 'cmd2.Cmd', *, add_to_history: bool = True) -> None # Tells if any of the commands run via __call__ returned True for stop self.stop = False - def __dir__(self) -> list[str]: + def __dir__(self) -> List[str]: """Return a custom set of attribute names""" - attributes: list[str] = [] + attributes: List[str] = [] attributes.insert(0, 'cmd_echo') return attributes diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 4f98bb023..c591d29e2 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Imports the proper Readline for the platform and provides utility functions for it """ diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index ae3534e52..d28b63a0e 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ cmd2 table creation API This API is built upon two core classes: Column and TableCreator @@ -10,13 +11,16 @@ from collections import ( deque, ) -from collections.abc import Sequence from enum import ( Enum, ) from typing import ( Any, + Deque, + List, Optional, + Sequence, + Tuple, ) from wcwidth import ( # type: ignore[import] @@ -150,7 +154,7 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None: col.width = max(1, ansi.widest_line(col.header)) @staticmethod - def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> tuple[str, int, int]: + def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> Tuple[str, int, int]: """ Used by _wrap_text() to wrap a long word over multiple lines @@ -376,7 +380,7 @@ def add_word(word_to_add: str, is_last_word: bool) -> None: return wrapped_buf.getvalue() - def _generate_cell_lines(self, cell_data: Any, is_header: bool, col: Column, fill_char: str) -> tuple[deque[str], int]: + def _generate_cell_lines(self, cell_data: Any, is_header: bool, col: Column, fill_char: str) -> Tuple[Deque[str], int]: """ Generate the lines of a table cell @@ -448,7 +452,7 @@ class Cell: def __init__(self) -> None: # Data in this cell split into individual lines - self.lines: deque[str] = deque() + self.lines: Deque[str] = deque() # Display width of this cell self.width = 0 @@ -648,7 +652,7 @@ def generate_header(self) -> str: inter_cell = self.apply_header_bg(self.column_spacing * SPACE) # Apply background color to header text in Columns which allow it - to_display: list[Any] = [] + to_display: List[Any] = [] for col in self.cols: if col.style_header_text: to_display.append(self.apply_header_bg(col.header)) @@ -688,7 +692,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: inter_cell = self.apply_data_bg(self.column_spacing * SPACE) # Apply background color to data text in Columns which allow it - to_display: list[Any] = [] + to_display: List[Any] = [] for index, col in enumerate(self.cols): if col.style_data_text: to_display.append(self.apply_data_bg(row_data[index])) @@ -943,7 +947,7 @@ def generate_header(self) -> str: post_line = self.apply_header_bg(self.padding * SPACE) + self.apply_border_color('║') # Apply background color to header text in Columns which allow it - to_display: list[Any] = [] + to_display: List[Any] = [] for col in self.cols: if col.style_header_text: to_display.append(self.apply_header_bg(col.header)) @@ -987,7 +991,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: post_line = self.apply_data_bg(self.padding * SPACE) + self.apply_border_color('║') # Apply background color to data text in Columns which allow it - to_display: list[Any] = [] + to_display: List[Any] = [] for index, col in enumerate(self.cols): if col.style_data_text: to_display.append(self.apply_data_bg(row_data[index])) diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 7a08480f2..9545b5b97 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -1,4 +1,5 @@ # +# -*- coding: utf-8 -*- """Machinery for running and validating transcripts. If the user wants to run a transcript (see docs/transcript.rst), @@ -11,11 +12,13 @@ class is used in cmd2.py::run_transcript_tests() import re import unittest -from collections.abc import Iterator from typing import ( TYPE_CHECKING, + Iterator, + List, Optional, TextIO, + Tuple, cast, ) @@ -63,7 +66,7 @@ def runTest(self) -> None: # was testall def _fetchTranscripts(self) -> None: self.transcripts = {} - testfiles = cast('list[str]', getattr(self.cmdapp, 'testfiles', [])) + testfiles = cast('List[str]', getattr(self.cmdapp, 'testfiles', [])) for fname in testfiles: tfile = open(fname) self.transcripts[fname] = iter(tfile.readlines()) @@ -180,7 +183,7 @@ def _transform_transcript_expected(self, s: str) -> str: return regex @staticmethod - def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> tuple[str, int, int]: + def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> Tuple[str, int, int]: """Find the next slash in {s} after {start} that is not preceded by a backslash. If we find an escaped slash, add everything up to and including it to regex, diff --git a/cmd2/utils.py b/cmd2/utils.py index 6504e0525..ee1fa20f1 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -1,3 +1,4 @@ +# coding=utf-8 """Shared utility functions""" import argparse @@ -12,7 +13,6 @@ import sys import threading import unicodedata -from collections.abc import Callable, Iterable from difflib import ( SequenceMatcher, ) @@ -22,8 +22,13 @@ from typing import ( TYPE_CHECKING, Any, + Callable, + Dict, + Iterable, + List, Optional, TextIO, + Type, TypeVar, Union, cast, @@ -115,7 +120,7 @@ class Settable: def __init__( self, name: str, - val_type: Union[type[Any], Callable[[Any], Any]], + val_type: Union[Type[Any], Callable[[Any], Any]], description: str, settable_object: object, *, @@ -154,7 +159,7 @@ def __init__( """ if val_type is bool: - def get_bool_choices(_) -> list[str]: # type: ignore[no-untyped-def] + def get_bool_choices(_) -> List[str]: # type: ignore[no-untyped-def] """Used to tab complete lowercase boolean values""" return ['true', 'false'] @@ -225,7 +230,7 @@ def is_text_file(file_path: str) -> bool: return valid_text_file -def remove_duplicates(list_to_prune: list[_T]) -> list[_T]: +def remove_duplicates(list_to_prune: List[_T]) -> List[_T]: """Removes duplicates from a list while preserving order of the items. :param list_to_prune: the list being pruned of duplicates @@ -247,7 +252,7 @@ def norm_fold(astr: str) -> str: return unicodedata.normalize('NFC', astr).casefold() -def alphabetical_sort(list_to_sort: Iterable[str]) -> list[str]: +def alphabetical_sort(list_to_sort: Iterable[str]) -> List[str]: """Sorts a list of strings alphabetically. For example: ['a1', 'A11', 'A2', 'a22', 'a3'] @@ -274,7 +279,7 @@ def try_int_or_force_to_lower_case(input_str: str) -> Union[int, str]: return norm_fold(input_str) -def natural_keys(input_str: str) -> list[Union[int, str]]: +def natural_keys(input_str: str) -> List[Union[int, str]]: """ Converts a string into a list of integers and strings to support natural sorting (see natural_sort). @@ -285,7 +290,7 @@ def natural_keys(input_str: str) -> list[Union[int, str]]: return [try_int_or_force_to_lower_case(substr) for substr in re.split(r'(\d+)', input_str)] -def natural_sort(list_to_sort: Iterable[str]) -> list[str]: +def natural_sort(list_to_sort: Iterable[str]) -> List[str]: """ Sorts a list of strings case insensitively as well as numerically. @@ -301,7 +306,7 @@ def natural_sort(list_to_sort: Iterable[str]) -> list[str]: return sorted(list_to_sort, key=natural_keys) -def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None: +def quote_specific_tokens(tokens: List[str], tokens_to_quote: List[str]) -> None: """ Quote specific tokens in a list @@ -313,7 +318,7 @@ def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None tokens[i] = quote_string(token) -def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> None: +def unquote_specific_tokens(tokens: List[str], tokens_to_unquote: List[str]) -> None: """ Unquote specific tokens in a list @@ -347,7 +352,7 @@ def expand_user(token: str) -> str: return token -def expand_user_in_tokens(tokens: list[str]) -> None: +def expand_user_in_tokens(tokens: List[str]) -> None: """ Call expand_user() on all tokens in a list of strings :param tokens: tokens to expand @@ -389,7 +394,7 @@ def find_editor() -> Optional[str]: return editor -def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> list[str]: +def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> List[str]: """Return a list of file paths based on a glob pattern. Only files are returned, not directories, and optionally only files for which the user has a specified access to. @@ -401,7 +406,7 @@ def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> list[str]: return [f for f in glob.glob(pattern) if os.path.isfile(f) and os.access(f, access)] -def files_from_glob_patterns(patterns: list[str], access: int = os.F_OK) -> list[str]: +def files_from_glob_patterns(patterns: List[str], access: int = os.F_OK) -> List[str]: """Return a list of file paths based on a list of glob patterns. Only files are returned, not directories, and optionally only files for which the user has a specified access to. @@ -417,7 +422,7 @@ def files_from_glob_patterns(patterns: list[str], access: int = os.F_OK) -> list return files -def get_exes_in_path(starts_with: str) -> list[str]: +def get_exes_in_path(starts_with: str) -> List[str]: """Returns names of executables in a user's path :param starts_with: what the exes should start with. leave blank for all exes in path. @@ -736,7 +741,7 @@ def __init__( self.saved_redirecting = saved_redirecting -def _remove_overridden_styles(styles_to_parse: list[str]) -> list[str]: +def _remove_overridden_styles(styles_to_parse: List[str]) -> List[str]: """ Utility function for align_text() / truncate_line() which filters a style list down to only those which would still be in effect if all were processed in order. @@ -757,7 +762,7 @@ class StyleState: def __init__(self) -> None: # Contains styles still in effect, keyed by their index in styles_to_parse - self.style_dict: dict[int, str] = dict() + self.style_dict: Dict[int, str] = dict() # Indexes into style_dict self.reset_all: Optional[int] = None @@ -895,7 +900,7 @@ def align_text( # ANSI style sequences that may affect subsequent lines will be cancelled by the fill_char's style. # To avoid this, we save styles which are still in effect so we can restore them when beginning the next line. # This also allows lines to be used independently and still have their style. TableCreator does this. - previous_styles: list[str] = [] + previous_styles: List[str] = [] for index, line in enumerate(lines): if index > 0: @@ -1106,7 +1111,7 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: return truncated_buf.getvalue() -def get_styles_dict(text: str) -> dict[int, str]: +def get_styles_dict(text: str) -> Dict[int, str]: """ Return an OrderedDict containing all ANSI style sequences found in a string @@ -1166,7 +1171,7 @@ def do_echo(self, arglist): setattr(func, constants.CMD_ATTR_HELP_CATEGORY, category) -def get_defining_class(meth: Callable[..., Any]) -> Optional[type[Any]]: +def get_defining_class(meth: Callable[..., Any]) -> Optional[Type[Any]]: """ Attempts to resolve the class that defined a method. diff --git a/examples/alias_startup.py b/examples/alias_startup.py index e84278658..1ad493ffa 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """A simple example demonstrating the following: 1) How to add custom command aliases using the alias command 2) How to run an initialization script at startup diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index 916358179..e42960b1c 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """An example demonstrating how use one of cmd2's argument parsing decorators""" import argparse @@ -26,7 +27,7 @@ def do_fsize(self, args: argparse.Namespace) -> None: try: size = os.path.getsize(expanded_path) except OSError as ex: - self.perror(f"Error retrieving size: {ex}") + self.perror("Error retrieving size: {}".format(ex)) return if args.unit == 'KB': @@ -38,8 +39,8 @@ def do_fsize(self, args: argparse.Namespace) -> None: size = round(size, 2) if args.comma: - size = f'{size:,}' - self.poutput(f'{size} {args.unit}') + size = '{:,}'.format(size) + self.poutput('{} {}'.format(size, args.unit)) # do_pow parser pow_parser = cmd2.Cmd2ArgumentParser() @@ -53,7 +54,7 @@ def do_pow(self, args: argparse.Namespace) -> None: :param args: argparse arguments """ - self.poutput(f'{args.base} ** {args.exponent} == {args.base**args.exponent}') + self.poutput('{} ** {} == {}'.format(args.base, args.exponent, args.base**args.exponent)) if __name__ == '__main__': diff --git a/examples/arg_print.py b/examples/arg_print.py index 26ccac9ca..2cade9e42 100755 --- a/examples/arg_print.py +++ b/examples/arg_print.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """A simple example demonstrating the following: 1) How arguments and options get parsed and passed to commands 2) How to change what syntax gets parsed as a comment and stripped from the arguments @@ -23,20 +24,20 @@ def __init__(self): def do_aprint(self, statement): """Print the argument string this basic command is called with.""" - self.poutput(f'aprint was called with argument: {statement!r}') - self.poutput(f'statement.raw = {statement.raw!r}') - self.poutput(f'statement.argv = {statement.argv!r}') - self.poutput(f'statement.command = {statement.command!r}') + self.poutput('aprint was called with argument: {!r}'.format(statement)) + self.poutput('statement.raw = {!r}'.format(statement.raw)) + self.poutput('statement.argv = {!r}'.format(statement.argv)) + self.poutput('statement.command = {!r}'.format(statement.command)) @cmd2.with_argument_list def do_lprint(self, arglist): """Print the argument list this basic command is called with.""" - self.poutput(f'lprint was called with the following list of arguments: {arglist!r}') + self.poutput('lprint was called with the following list of arguments: {!r}'.format(arglist)) @cmd2.with_argument_list(preserve_quotes=True) def do_rprint(self, arglist): """Print the argument list this basic command is called with (with quotes preserved).""" - self.poutput(f'rprint was called with the following list of arguments: {arglist!r}') + self.poutput('rprint was called with the following list of arguments: {!r}'.format(arglist)) oprint_parser = cmd2.Cmd2ArgumentParser() oprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') @@ -47,7 +48,7 @@ def do_rprint(self, arglist): @cmd2.with_argparser(oprint_parser) def do_oprint(self, args): """Print the options and argument list this options command was called with.""" - self.poutput(f'oprint was called with the following\n\toptions: {args!r}') + self.poutput('oprint was called with the following\n\toptions: {!r}'.format(args)) pprint_parser = cmd2.Cmd2ArgumentParser() pprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') @@ -57,7 +58,7 @@ def do_oprint(self, args): @cmd2.with_argparser(pprint_parser, with_unknown_args=True) def do_pprint(self, args, unknown): """Print the options and argument list this options command was called with.""" - self.poutput(f'oprint was called with the following\n\toptions: {args!r}\n\targuments: {unknown}') + self.poutput('oprint was called with the following\n\toptions: {!r}\n\targuments: {}'.format(args, unknown)) if __name__ == '__main__': diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index 84082a6d3..e0ad101b9 100755 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -1,9 +1,14 @@ #!/usr/bin/env python +# coding=utf-8 """ A simple example demonstrating how to integrate tab completion with argparse-based commands. """ import argparse +from typing import ( + Dict, + List, +) from cmd2 import ( Cmd, @@ -23,11 +28,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - def choices_provider(self) -> list[str]: + def choices_provider(self) -> List[str]: """A choices provider is useful when the choice list is based on instance data of your application""" return self.sport_item_strs - def choices_completion_error(self) -> list[str]: + def choices_completion_error(self) -> List[str]: """ CompletionErrors can be raised if an error occurs while tab completing. @@ -39,14 +44,14 @@ def choices_completion_error(self) -> list[str]: return self.sport_item_strs raise CompletionError("debug must be true") - def choices_completion_item(self) -> list[CompletionItem]: + def choices_completion_item(self) -> List[CompletionItem]: """Return CompletionItem instead of strings. These give more context to what's being tab completed.""" fancy_item = "These things can\ncontain newlines and\n" fancy_item += ansi.style("styled text!!", fg=ansi.Fg.LIGHT_YELLOW, underline=True) items = {1: "My item", 2: "Another item", 3: "Yet another item", 4: fancy_item} return [CompletionItem(item_id, description) for item_id, description in items.items()] - def choices_arg_tokens(self, arg_tokens: dict[str, list[str]]) -> list[str]: + def choices_arg_tokens(self, arg_tokens: Dict[str, List[str]]) -> List[str]: """ If a choices or completer function/method takes a value called arg_tokens, then it will be passed a dictionary that maps the command line tokens up through the one being completed diff --git a/examples/async_printing.py b/examples/async_printing.py index 8520bc130..e94ee89a0 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A simple example demonstrating an application that asynchronously prints alerts, updates the prompt and changes the window title @@ -7,6 +8,9 @@ import random import threading import time +from typing import ( + List, +) import cmd2 from cmd2 import ( @@ -85,7 +89,7 @@ def do_stop_alerts(self, _): else: print("The alert thread is already stopped") - def _get_alerts(self) -> list[str]: + def _get_alerts(self) -> List[str]: """ Reports alerts :return: the list of alerts @@ -110,7 +114,7 @@ def _get_alerts(self) -> list[str]: for i in range(0, rand_num): self._alert_count += 1 - alerts.append(f"Alert {self._alert_count}") + alerts.append("Alert {}".format(self._alert_count)) self._next_alert_time = 0 @@ -183,7 +187,7 @@ def _alerter_thread_func(self) -> None: if alert_str: # new_prompt is an optional parameter to async_alert() self.async_alert(alert_str, new_prompt) - new_title = f"Alerts Printed: {self._alert_count}" + new_title = "Alerts Printed: {}".format(self._alert_count) self.set_window_title(new_title) # Otherwise check if the prompt needs to be updated or refreshed diff --git a/examples/basic.py b/examples/basic.py index f84009b2b..6ce4d2838 100755 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """A simple example demonstrating the following: 1) How to add a command 2) How to add help for that command diff --git a/examples/basic_completion.py b/examples/basic_completion.py index e2288f4bf..c713f2b0d 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A simple example demonstrating how to enable tab completion by assigning a completer function to do_* commands. This also demonstrates capabilities of the following completer features included with cmd2: @@ -13,6 +14,9 @@ """ import functools +from typing import ( + List, +) import cmd2 @@ -40,9 +44,9 @@ def do_flag_based(self, statement: cmd2.Statement): -s, --sport [completes sports] -p, --path [completes local file system paths] """ - self.poutput(f"Args: {statement.args}") + self.poutput("Args: {}".format(statement.args)) - def complete_flag_based(self, text, line, begidx, endidx) -> list[str]: + def complete_flag_based(self, text, line, begidx, endidx) -> List[str]: """Completion function for do_flag_based""" flag_dict = { # Tab complete food items after -f and --food flags in command line @@ -60,9 +64,9 @@ def complete_flag_based(self, text, line, begidx, endidx) -> list[str]: def do_index_based(self, statement: cmd2.Statement): """Tab completes first 3 arguments using index_based_complete""" - self.poutput(f"Args: {statement.args}") + self.poutput("Args: {}".format(statement.args)) - def complete_index_based(self, text, line, begidx, endidx) -> list[str]: + def complete_index_based(self, text, line, begidx, endidx) -> List[str]: """Completion function for do_index_based""" index_dict = { 1: food_item_strs, # Tab complete food items at index 1 in command line @@ -74,16 +78,16 @@ def complete_index_based(self, text, line, begidx, endidx) -> list[str]: def do_delimiter_complete(self, statement: cmd2.Statement): """Tab completes files from a list using delimiter_complete""" - self.poutput(f"Args: {statement.args}") + self.poutput("Args: {}".format(statement.args)) # Use a partialmethod to set arguments to delimiter_complete complete_delimiter_complete = functools.partialmethod(cmd2.Cmd.delimiter_complete, match_against=file_strs, delimiter='/') def do_raise_error(self, statement: cmd2.Statement): """Demonstrates effect of raising CompletionError""" - self.poutput(f"Args: {statement.args}") + self.poutput("Args: {}".format(statement.args)) - def complete_raise_error(self, text, line, begidx, endidx) -> list[str]: + def complete_raise_error(self, text, line, begidx, endidx) -> List[str]: """ CompletionErrors can be raised if an error occurs while tab completing. diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index a9f5b896f..75d53ed73 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A sample application for cmd2. diff --git a/examples/colors.py b/examples/colors.py index 28b0c6707..34f16da2c 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A sample application for cmd2. Demonstrating colorized output. diff --git a/examples/custom_parser.py b/examples/custom_parser.py index fd74f5b56..94df3b054 100644 --- a/examples/custom_parser.py +++ b/examples/custom_parser.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Defines the CustomParser used with override_parser.py example """ @@ -34,7 +35,7 @@ def error(self, message: str) -> None: # Format errors with style_warning() formatted_message = ansi.style_warning(formatted_message) - self.exit(2, f'{formatted_message}\n\n') + self.exit(2, '{}\n\n'.format(formatted_message)) # Now set the default parser for a cmd2 app diff --git a/examples/decorator_example.py b/examples/decorator_example.py index 4c80436f8..ea8fd3b50 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """A sample application showing how to use cmd2's argparse decorators to process command line arguments for your application. @@ -11,6 +12,9 @@ """ import argparse +from typing import ( + List, +) import cmd2 @@ -67,12 +71,12 @@ def do_tag(self, args: argparse.Namespace): # The Namespace always includes the Statement object created when parsing the command line statement = args.cmd2_statement.get() - self.poutput(f"The command line you ran was: {statement.command_and_args}") + self.poutput("The command line you ran was: {}".format(statement.command_and_args)) self.poutput("It generated this tag:") self.poutput('<{0}>{1}'.format(args.tag, ' '.join(args.content))) @cmd2.with_argument_list - def do_tagg(self, arglist: list[str]): + def do_tagg(self, arglist: List[str]): """version of creating an html tag using arglist instead of argparser""" if len(arglist) >= 2: tag = arglist[0] diff --git a/examples/default_categories.py b/examples/default_categories.py index 3d531931f..0fd485ae3 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ diff --git a/examples/dynamic_commands.py b/examples/dynamic_commands.py index ef5c520b1..82dde732d 100755 --- a/examples/dynamic_commands.py +++ b/examples/dynamic_commands.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """A simple example demonstrating how do_* commands can be created in a loop.""" import functools @@ -40,7 +41,7 @@ def send_text(self, args: cmd2.Statement, *, text: str): def text_help(self, *, text: str): """Deal with printing help for the dynamically added commands.""" - self.poutput(f"Simulate sending {text!r} to a server and printing the response") + self.poutput("Simulate sending {!r} to a server and printing the response".format(text)) if __name__ == '__main__': diff --git a/examples/environment.py b/examples/environment.py index 38c0f0bd6..1bb9812be 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A sample application for cmd2 demonstrating customized environment parameters """ @@ -21,7 +22,7 @@ def __init__(self): def do_sunbathe(self, arg): """Attempt to sunbathe.""" if self.degrees_c < 20: - result = f"It's {self.degrees_c} C - are you a penguin?" + result = "It's {} C - are you a penguin?".format(self.degrees_c) elif not self.sunny: result = 'Too dim.' else: diff --git a/examples/event_loops.py b/examples/event_loops.py index 8f627577b..e5435181a 100755 --- a/examples/event_loops.py +++ b/examples/event_loops.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """A sample application for integrating cmd2 with external event loops. This is an example of how to use cmd2 in a way so that cmd2 doesn't own the inner event loop of your application. diff --git a/examples/example.py b/examples/example.py index 0ce4d9bb8..2ff64d747 100755 --- a/examples/example.py +++ b/examples/example.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A sample application for cmd2. diff --git a/examples/exit_code.py b/examples/exit_code.py index ccf4b8f21..d8e538ced 100755 --- a/examples/exit_code.py +++ b/examples/exit_code.py @@ -1,6 +1,11 @@ #!/usr/bin/env python +# coding=utf-8 """A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application.""" +from typing import ( + List, +) + import cmd2 @@ -11,7 +16,7 @@ def __init__(self): super().__init__() @cmd2.with_argument_list - def do_exit(self, arg_list: list[str]) -> bool: + def do_exit(self, arg_list: List[str]) -> bool: """Exit the application with an optional exit code. Usage: exit [exit_code] @@ -22,7 +27,7 @@ def do_exit(self, arg_list: list[str]) -> bool: try: self.exit_code = int(arg_list[0]) except ValueError: - self.perror(f"{arg_list[0]} isn't a valid integer exit code") + self.perror("{} isn't a valid integer exit code".format(arg_list[0])) self.exit_code = 1 return True @@ -33,5 +38,5 @@ def do_exit(self, arg_list: list[str]) -> bool: app = ReplWithExitCode() sys_exit_code = app.cmdloop() - app.poutput(f'{sys.argv[0]!r} exiting with code: {sys_exit_code}') + app.poutput('{!r} exiting with code: {}'.format(sys.argv[0], sys_exit_code)) sys.exit(sys_exit_code) diff --git a/examples/first_app.py b/examples/first_app.py index f19cdec92..57a76f708 100755 --- a/examples/first_app.py +++ b/examples/first_app.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A simple application using cmd2 which demonstrates 8 key features: diff --git a/examples/hello_cmd2.py b/examples/hello_cmd2.py index 76ff55c75..a67205834 100755 --- a/examples/hello_cmd2.py +++ b/examples/hello_cmd2.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ This is intended to be a completely bare-bones cmd2 application suitable for rapid testing and debugging. """ diff --git a/examples/help_categories.py b/examples/help_categories.py index 4790b915f..5c349422c 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A sample application for tagging categories on commands. @@ -161,7 +162,7 @@ def do_version(self, _): @cmd2.with_category("Command Management") def do_disable_commands(self, _): """Disable the Application Management commands""" - message_to_print = f"{COMMAND_NAME} is not available while {self.CMD_CAT_APP_MGMT} commands are disabled" + message_to_print = "{} is not available while {} commands are disabled".format(COMMAND_NAME, self.CMD_CAT_APP_MGMT) self.disable_category(self.CMD_CAT_APP_MGMT, message_to_print) self.poutput("The Application Management commands have been disabled") diff --git a/examples/hooks.py b/examples/hooks.py index 683b75474..97b90739d 100755 --- a/examples/hooks.py +++ b/examples/hooks.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A sample application for cmd2 demonstrating how to use hooks. @@ -9,6 +10,9 @@ """ import re +from typing import ( + List, +) import cmd2 @@ -98,7 +102,7 @@ def proof_hook(self, data: cmd2.plugin.PostcommandData) -> cmd2.plugin.Postcomma return data @cmd2.with_argument_list - def do_list(self, arglist: list[str]) -> None: + def do_list(self, arglist: List[str]) -> None: """Generate a list of 10 numbers.""" if arglist: first = arglist[0] diff --git a/examples/initialization.py b/examples/initialization.py index ea9d88589..8cdf07341 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """A simple example cmd2 application demonstrating the following: 1) Colorizing/stylizing output 2) Using multiline commands diff --git a/examples/migrating.py b/examples/migrating.py index 590e9ee20..199b78db7 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A sample cmd application that shows how to trivially migrate a cmd application to use cmd2. """ diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index 7e17ac024..8587b98d7 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -1,7 +1,12 @@ +# coding=utf-8 """ A simple example demonstrating a loadable command set """ +from typing import ( + List, +) + from cmd2 import ( CommandSet, CompletionError, @@ -32,9 +37,9 @@ def do_flag_based(self, statement: Statement) -> None: -s, --sport [completes sports] -p, --path [completes local file system paths] """ - self._cmd.poutput(f"Args: {statement.args}") + self._cmd.poutput("Args: {}".format(statement.args)) - def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: + def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: """Completion function for do_flag_based""" flag_dict = { # Tab complete food items after -f and --food flags in command line @@ -52,9 +57,9 @@ def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> def do_index_based(self, statement: Statement) -> None: """Tab completes first 3 arguments using index_based_complete""" - self._cmd.poutput(f"Args: {statement.args}") + self._cmd.poutput("Args: {}".format(statement.args)) - def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: + def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: """Completion function for do_index_based""" index_dict = { 1: self.food_item_strs, # Tab complete food items at index 1 in command line @@ -66,16 +71,16 @@ def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) - def do_delimiter_complete(self, statement: Statement) -> None: """Tab completes files from a list using delimiter_complete""" - self._cmd.poutput(f"Args: {statement.args}") + self._cmd.poutput("Args: {}".format(statement.args)) - def complete_delimiter_complete(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: + def complete_delimiter_complete(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: return self._cmd.delimiter_complete(text, line, begidx, endidx, match_against=self.file_strs, delimiter='/') def do_raise_error(self, statement: Statement) -> None: """Demonstrates effect of raising CompletionError""" - self._cmd.poutput(f"Args: {statement.args}") + self._cmd.poutput("Args: {}".format(statement.args)) - def complete_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: + def complete_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: """ CompletionErrors can be raised if an error occurs while tab completing. diff --git a/examples/modular_commands/commandset_custominit.py b/examples/modular_commands/commandset_custominit.py index 5ef2beca4..a3f4f59ad 100644 --- a/examples/modular_commands/commandset_custominit.py +++ b/examples/modular_commands/commandset_custominit.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ A simple example demonstrating a loadable command set """ diff --git a/examples/modular_commands_basic.py b/examples/modular_commands_basic.py index 37cad3192..4d5f83cee 100755 --- a/examples/modular_commands_basic.py +++ b/examples/modular_commands_basic.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ diff --git a/examples/modular_commands_dynamic.py b/examples/modular_commands_dynamic.py index b68e6f577..8264c068f 100755 --- a/examples/modular_commands_dynamic.py +++ b/examples/modular_commands_dynamic.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """ Simple example demonstrating dynamic CommandSet loading and unloading. diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 25138db83..740078671 100755 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -1,12 +1,14 @@ #!/usr/bin/env python +# coding=utf-8 """ A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators with examples of how to integrate tab completion with argparse-based commands. """ import argparse -from collections.abc import Iterable from typing import ( + Iterable, + List, Optional, ) @@ -33,7 +35,7 @@ def __init__(self, command_sets: Optional[Iterable[CommandSet]] = None): super().__init__(command_sets=command_sets) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - def choices_provider(self) -> list[str]: + def choices_provider(self) -> List[str]: """A choices provider is useful when the choice list is based on instance data of your application""" return self.sport_item_strs diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index ef340f526..14d117814 100755 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """A simple example demonstrating modular subcommand loading through CommandSets In this example, there are loadable CommandSets defined. Each CommandSet has 1 subcommand defined that will be diff --git a/examples/paged_output.py b/examples/paged_output.py index 9aed7e193..0f7173b2e 100755 --- a/examples/paged_output.py +++ b/examples/paged_output.py @@ -1,7 +1,11 @@ #!/usr/bin/env python +# coding=utf-8 """A simple example demonstrating the using paged output via the ppaged() method.""" import os +from typing import ( + List, +) import cmd2 @@ -16,14 +20,14 @@ def page_file(self, file_path: str, chop: bool = False): """Helper method to prevent having too much duplicated code.""" filename = os.path.expanduser(file_path) try: - with open(filename) as f: + with open(filename, 'r') as f: text = f.read() self.ppaged(text, chop=chop) except OSError as ex: - self.pexcept(f'Error reading {filename!r}: {ex}') + self.pexcept('Error reading {!r}: {}'.format(filename, ex)) @cmd2.with_argument_list - def do_page_wrap(self, args: list[str]): + def do_page_wrap(self, args: List[str]): """Read in a text file and display its output in a pager, wrapping long lines if they don't fit. Usage: page_wrap @@ -36,7 +40,7 @@ def do_page_wrap(self, args: list[str]): complete_page_wrap = cmd2.Cmd.path_complete @cmd2.with_argument_list - def do_page_truncate(self, args: list[str]): + def do_page_truncate(self, args: List[str]): """Read in a text file and display its output in a pager, truncating long lines if they don't fit. Truncated lines can still be accessed by scrolling to the right using the arrow keys. diff --git a/examples/persistent_history.py b/examples/persistent_history.py index c185370cf..ab4b89f2b 100755 --- a/examples/persistent_history.py +++ b/examples/persistent_history.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """This example demonstrates how to enable persistent readline history in your cmd2 application. This will allow end users of your cmd2-based application to use the arrow keys and Ctrl+r in a manner which persists diff --git a/examples/pirate.py b/examples/pirate.py index 1e59e5fe6..8c443d368 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ This example is adapted from the pirate8.py example created by Catherine Devlin and presented as part of her PyCon 2010 talk. @@ -45,7 +46,7 @@ def precmd(self, line): def postcmd(self, stop, line): """Runs right before a command is about to return.""" if self.gold != self.initial_gold: - self.poutput(f'Now we gots {self.gold} doubloons') + self.poutput('Now we gots {0} doubloons'.format(self.gold)) if self.gold < 0: self.poutput("Off to debtorrr's prison.") self.exit_code = 1 @@ -64,7 +65,7 @@ def do_drink(self, arg): self.gold -= int(arg) except ValueError: if arg: - self.poutput(f'''What's "{arg}"? I'll take rrrum.''') + self.poutput('''What's "{0}"? I'll take rrrum.'''.format(arg)) self.gold -= 1 def do_quit(self, arg): @@ -87,7 +88,7 @@ def do_yo(self, args): chant = ['yo'] + ['ho'] * args.ho separator = ', ' if args.commas else ' ' chant = separator.join(chant) - self.poutput(f'{chant} and a bottle of {args.beverage}') + self.poutput('{0} and a bottle of {1}'.format(chant, args.beverage)) if __name__ == '__main__': @@ -96,5 +97,5 @@ def do_yo(self, args): # Create an instance of the Pirate derived class and enter the REPL with cmdloop(). pirate = Pirate() sys_exit_code = pirate.cmdloop() - print(f'Exiting with code: {sys_exit_code!r}') + print('Exiting with code: {!r}'.format(sys_exit_code)) sys.exit(sys_exit_code) diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 1943e214e..688ef52ce 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """A sample application for how Python scripting can provide conditional control flow of a cmd2 application. diff --git a/examples/read_input.py b/examples/read_input.py index a8404bac1..bfc43380b 100755 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -1,8 +1,13 @@ #!/usr/bin/env python +# coding=utf-8 """ A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion """ +from typing import ( + List, +) + import cmd2 EXAMPLE_COMMANDS = "Example Commands" @@ -59,7 +64,7 @@ def do_custom_choices(self, _) -> None: else: self.custom_history.append(input_str) - def choices_provider(self) -> list[str]: + def choices_provider(self) -> List[str]: """Example choices provider function""" return ["from_provider_1", "from_provider_2", "from_provider_3"] diff --git a/examples/remove_builtin_commands.py b/examples/remove_builtin_commands.py index a511a4944..67541a848 100755 --- a/examples/remove_builtin_commands.py +++ b/examples/remove_builtin_commands.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """A simple example demonstrating how to remove unused commands. Commands can be removed from help menu and tab completion by appending their command name to the hidden_commands list. diff --git a/examples/remove_settable.py b/examples/remove_settable.py index 191f033d7..fad671cbe 100755 --- a/examples/remove_settable.py +++ b/examples/remove_settable.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ A sample application for cmd2 demonstrating how to remove one of the built-in runtime settable parameters. """ diff --git a/examples/scripts/arg_printer.py b/examples/scripts/arg_printer.py index aca0f0031..924e269ac 100755 --- a/examples/scripts/arg_printer.py +++ b/examples/scripts/arg_printer.py @@ -1,7 +1,8 @@ #!/usr/bin/env python +# coding=utf-8 import os import sys -print(f"Running Python script {os.path.basename(sys.argv[0])!r} which was called with {len(sys.argv) - 1} arguments") +print("Running Python script {!r} which was called with {} arguments".format(os.path.basename(sys.argv[0]), len(sys.argv) - 1)) for i, arg in enumerate(sys.argv[1:]): - print(f"arg {i + 1}: {arg!r}") + print("arg {}: {!r}".format(i + 1, arg)) diff --git a/examples/scripts/script.py b/examples/scripts/script.py index a14f7660d..5f6f411e3 100644 --- a/examples/scripts/script.py +++ b/examples/scripts/script.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Trivial example of a Python script which can be run inside a cmd2 application. """ diff --git a/examples/subcommands.py b/examples/subcommands.py index 532b9f0f8..455768e38 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# coding=utf-8 """A simple example demonstrating how to use Argparse to support subcommands. @@ -81,7 +82,7 @@ def base_bar(self, args): def base_sport(self, args): """sport subcommand of base command""" - self.poutput(f'Sport is {args.sport}') + self.poutput('Sport is {}'.format(args.sport)) # Set handler functions for the subcommands parser_foo.set_defaults(func=base_foo) diff --git a/examples/table_creation.py b/examples/table_creation.py index 3f7040ab2..0849a0b2c 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -1,10 +1,12 @@ #!/usr/bin/env python +# coding=utf-8 """Examples of using the cmd2 table creation API""" import functools import sys from typing import ( Any, + List, ) from cmd2 import ( @@ -35,7 +37,7 @@ def __init__(self, val: float) -> None: def __str__(self) -> str: """Returns the value in dollar currency form (e.g. $100.22)""" - return f"${self.val:,.2f}" + return "${:,.2f}".format(self.val) class Relative: @@ -61,8 +63,8 @@ def __init__(self, name: str, birthday: str, place_of_birth: str) -> None: self.name = name self.birthday = birthday self.place_of_birth = place_of_birth - self.books: list[Book] = [] - self.relatives: list[Relative] = [] + self.books: List[Book] = [] + self.relatives: List[Relative] = [] def ansi_print(text): @@ -74,7 +76,7 @@ def basic_tables(): """Demonstrates basic examples of the table classes""" # Table data which demonstrates handling of wrapping and text styles - data_list: list[list[Any]] = list() + data_list: List[List[Any]] = list() data_list.append(["Billy Smith", "123 Sesame St.\nFake Town, USA 33445", DollarFormatter(100333.03)]) data_list.append( [ @@ -94,7 +96,7 @@ def basic_tables(): data_list.append(["John Jones", "9235 Highway 32\n" + green("Greenville") + ", SC 29604", DollarFormatter(82987.71)]) # Table Columns (width does not account for any borders or padding which may be added) - columns: list[Column] = list() + columns: List[Column] = list() columns.append(Column("Name", width=20)) columns.append(Column("Address", width=38)) columns.append( @@ -121,7 +123,7 @@ def nested_tables(): """ # Create data for this example - author_data: list[Author] = [] + author_data: List[Author] = [] author_1 = Author("Frank Herbert", "10/08/1920", "Tacoma, Washington") author_1.books.append(Book("Dune", "1965")) author_1.books.append(Book("Dune Messiah", "1969")) @@ -157,7 +159,7 @@ def nested_tables(): # Define table which presents Author data fields vertically with no header. # This will be nested in the parent table's first column. - author_columns: list[Column] = list() + author_columns: List[Column] = list() author_columns.append(Column("", width=14)) author_columns.append(Column("", width=20)) @@ -172,7 +174,7 @@ def nested_tables(): # Define AlternatingTable for books checked out by people in the first table. # This will be nested in the parent table's second column. - books_columns: list[Column] = list() + books_columns: List[Column] = list() books_columns.append(Column(ansi.style("Title", bold=True), width=25)) books_columns.append( Column( @@ -194,7 +196,7 @@ def nested_tables(): # Define BorderedTable for relatives of the author # This will be nested in the parent table's third column. - relative_columns: list[Column] = list() + relative_columns: List[Column] = list() relative_columns.append(Column(ansi.style("Name", bold=True), width=25)) relative_columns.append(Column(ansi.style("Relationship", bold=True), width=12)) @@ -218,7 +220,7 @@ def nested_tables(): ) # Define parent AlternatingTable which contains Author and Book tables - parent_tbl_columns: list[Column] = list() + parent_tbl_columns: List[Column] = list() # All of the nested tables already have background colors. Set style_data_text # to False so the parent AlternatingTable does not apply background color to them. @@ -240,7 +242,7 @@ def nested_tables(): ) # Construct the tables - parent_table_data: list[list[Any]] = [] + parent_table_data: List[List[Any]] = [] for row, author in enumerate(author_data, start=1): # First build the author table and color it based on row number author_tbl = even_author_tbl if row % 2 == 0 else odd_author_tbl diff --git a/examples/unicode_commands.py b/examples/unicode_commands.py index ac2cafd39..6c76a76e7 100755 --- a/examples/unicode_commands.py +++ b/examples/unicode_commands.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """A simple example demonstrating support for unicode command names.""" import math @@ -15,7 +16,7 @@ def __init__(self): def do_𝛑print(self, _): """This command prints 𝛑 to 5 decimal places.""" - self.poutput(f"𝛑 = {math.pi:.6}") + self.poutput("𝛑 = {0:.6}".format(math.pi)) def do_你好(self, arg): """This command says hello in Chinese (Mandarin).""" diff --git a/plugins/template/cmd2_myplugin/__init__.py b/plugins/template/cmd2_myplugin/__init__.py index e157ee7a4..a35976b75 100644 --- a/plugins/template/cmd2_myplugin/__init__.py +++ b/plugins/template/cmd2_myplugin/__init__.py @@ -1,4 +1,5 @@ # +# coding=utf-8 """Description of myplugin An overview of what myplugin does. diff --git a/plugins/template/cmd2_myplugin/myplugin.py b/plugins/template/cmd2_myplugin/myplugin.py index 7d36e1d1b..8397e3706 100644 --- a/plugins/template/cmd2_myplugin/myplugin.py +++ b/plugins/template/cmd2_myplugin/myplugin.py @@ -1,10 +1,11 @@ # +# coding=utf-8 """An example cmd2 plugin""" import functools -from collections.abc import Callable from typing import ( TYPE_CHECKING, + Callable, ) import cmd2 diff --git a/plugins/template/examples/example.py b/plugins/template/examples/example.py index 997334906..b071b5f84 100644 --- a/plugins/template/examples/example.py +++ b/plugins/template/examples/example.py @@ -1,4 +1,5 @@ # +# coding=utf-8 import cmd2_myplugin diff --git a/plugins/template/setup.py b/plugins/template/setup.py index 59b064d5a..126f8dde8 100644 --- a/plugins/template/setup.py +++ b/plugins/template/setup.py @@ -1,4 +1,5 @@ # +# coding=utf-8 import os diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index 6043d62b5..ca1058c88 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -1,4 +1,5 @@ # +# -*- coding: utf-8 -*- """Development related tasks to be run with 'invoke'""" import os @@ -19,7 +20,7 @@ def rmrf(items, verbose=True): for item in items: if verbose: - print(f"Removing {item}") + print("Removing {}".format(item)) shutil.rmtree(item, ignore_errors=True) # rmtree doesn't remove bare files try: diff --git a/plugins/template/tests/test_myplugin.py b/plugins/template/tests/test_myplugin.py index 30c42f9f1..06ca25670 100644 --- a/plugins/template/tests/test_myplugin.py +++ b/plugins/template/tests/test_myplugin.py @@ -1,4 +1,5 @@ # +# coding=utf-8 import cmd2_myplugin diff --git a/pyproject.toml b/pyproject.toml index 25b244f9a..73fdddb8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -216,13 +216,12 @@ select = [ "TD", # flake8-todos (force all TODOs to include an author and issue link) "TID", # flake8-tidy-imports (extra import rules to check) # "TRY", # tryceratops (warnings related to exceptions and try/except) - # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) + # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) "W", # pycodestyle warnings (warn about minor stylistic issues) "YTT", # flake8-2020 (checks for misuse of sys.version or sys.version_info) ] ignore = [ # `uv run ruff rule E501` for a description of that rule - "UP007", # Use X | Y for type annotations (requires Python 3.10 or newer) ] # Allow fix for all enabled rules (when `--fix`) is provided. diff --git a/tests/__init__.py b/tests/__init__.py index 6e9bbe36f..037f3866e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,2 +1,3 @@ # +# -*- coding: utf-8 -*- # diff --git a/tests/conftest.py b/tests/conftest.py index bbd332962..644ae7cca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Cmd2 unit/functional testing """ @@ -9,6 +10,7 @@ redirect_stdout, ) from typing import ( + List, Optional, Union, ) @@ -30,7 +32,7 @@ def verify_help_text( - cmd2_app: cmd2.Cmd, help_output: Union[str, list[str]], verbose_strings: Optional[list[str]] = None + cmd2_app: cmd2.Cmd, help_output: Union[str, List[str]], verbose_strings: Optional[List[str]] = None ) -> None: """This function verifies that all expected commands are present in the help text. @@ -193,7 +195,7 @@ def get_endidx(): return app.complete(text, 0) -def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser: +def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser: if not subcmd_names: return action cur_subcmd = subcmd_names.pop(0) diff --git a/tests/pyscript/raises_exception.py b/tests/pyscript/raises_exception.py index 0df499d9b..9979b6692 100644 --- a/tests/pyscript/raises_exception.py +++ b/tests/pyscript/raises_exception.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Example demonstrating what happens when a Python script raises an exception """ diff --git a/tests/script.py b/tests/script.py index a14f7660d..5f6f411e3 100644 --- a/tests/script.py +++ b/tests/script.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Trivial example of a Python script which can be run inside a cmd2 application. """ diff --git a/tests_isolated/test_commandset/__init__.py b/tests_isolated/test_commandset/__init__.py index 6e9bbe36f..037f3866e 100644 --- a/tests_isolated/test_commandset/__init__.py +++ b/tests_isolated/test_commandset/__init__.py @@ -1,2 +1,3 @@ # +# -*- coding: utf-8 -*- # diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 353bf907d..23ea7e741 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Cmd2 unit/functional testing """ @@ -8,6 +9,7 @@ redirect_stdout, ) from typing import ( + List, Optional, Union, ) @@ -32,7 +34,7 @@ def verify_help_text( - cmd2_app: cmd2.Cmd, help_output: Union[str, list[str]], verbose_strings: Optional[list[str]] = None + cmd2_app: cmd2.Cmd, help_output: Union[str, List[str]], verbose_strings: Optional[List[str]] = None ) -> None: """This function verifies that all expected commands are present in the help text. diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index bdbc4efec..533e02e0c 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ From 6111972d36d7ebe947506c5ee27a85ae5302ecad Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 20:55:20 -0400 Subject: [PATCH 20/79] Add a ruff ignore rule for something requiring Python >= 3.10 --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 73fdddb8c..ee5eccab9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,6 +222,7 @@ select = [ ] ignore = [ # `uv run ruff rule E501` for a description of that rule + "UP007", # Use X | Y for type annotations (requires Python 3.10 or newer) ] # Allow fix for all enabled rules (when `--fix`) is provided. From 9007cc79dc2899089d79e64043aad4f0ca6fa816 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 22 May 2025 21:01:19 -0400 Subject: [PATCH 21/79] Apply refactorings from ruff UP ruleset that are safe for Python 3.9+ --- cmd2/ansi.py | 6 +- cmd2/argparse_custom.py | 70 +++--- cmd2/clipboard.py | 1 - cmd2/cmd2.py | 205 +++++++++--------- cmd2/command_definition.py | 12 +- cmd2/constants.py | 1 - cmd2/decorators.py | 36 ++- cmd2/exceptions.py | 1 - cmd2/history.py | 12 +- cmd2/parsing.py | 40 ++-- cmd2/plugin.py | 1 - cmd2/py_bridge.py | 6 +- cmd2/rl_utils.py | 1 - cmd2/table_creator.py | 20 +- cmd2/transcript.py | 9 +- cmd2/utils.py | 41 ++-- examples/alias_startup.py | 1 - examples/arg_decorators.py | 9 +- examples/arg_print.py | 17 +- examples/argparse_completion.py | 13 +- examples/async_printing.py | 10 +- examples/basic.py | 1 - examples/basic_completion.py | 18 +- examples/cmd_as_argument.py | 1 - examples/colors.py | 1 - examples/custom_parser.py | 3 +- examples/decorator_example.py | 8 +- examples/default_categories.py | 1 - examples/dynamic_commands.py | 3 +- examples/environment.py | 3 +- examples/event_loops.py | 1 - examples/example.py | 1 - examples/exit_code.py | 11 +- examples/first_app.py | 1 - examples/hello_cmd2.py | 1 - examples/help_categories.py | 3 +- examples/hooks.py | 6 +- examples/initialization.py | 1 - examples/migrating.py | 1 - examples/modular_commands/commandset_basic.py | 21 +- .../modular_commands/commandset_custominit.py | 1 - examples/modular_commands_basic.py | 1 - examples/modular_commands_dynamic.py | 1 - examples/modular_commands_main.py | 6 +- examples/modular_subcommands.py | 1 - examples/paged_output.py | 12 +- examples/persistent_history.py | 1 - examples/pirate.py | 9 +- examples/python_scripting.py | 1 - examples/read_input.py | 7 +- examples/remove_builtin_commands.py | 1 - examples/remove_settable.py | 1 - examples/scripts/arg_printer.py | 5 +- examples/scripts/script.py | 1 - examples/subcommands.py | 3 +- examples/table_creation.py | 24 +- examples/unicode_commands.py | 3 +- plugins/template/cmd2_myplugin/__init__.py | 1 - plugins/template/cmd2_myplugin/myplugin.py | 3 +- plugins/template/examples/example.py | 1 - plugins/template/setup.py | 1 - plugins/template/tasks.py | 3 +- plugins/template/tests/test_myplugin.py | 1 - pyproject.toml | 5 +- tests/__init__.py | 1 - tests/conftest.py | 6 +- tests/pyscript/raises_exception.py | 1 - tests/script.py | 1 - tests_isolated/test_commandset/__init__.py | 1 - tests_isolated/test_commandset/conftest.py | 4 +- .../test_commandset/test_categories.py | 1 - 71 files changed, 281 insertions(+), 425 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index e73b7c091..abea54903 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Support for ANSI escape sequences which are used for things like applying style to text, setting the window title, and asynchronous alerts. @@ -12,7 +11,6 @@ from typing import ( IO, Any, - List, Optional, cast, ) @@ -993,10 +991,10 @@ def style( :return: the stylized string """ # List of strings that add style - additions: List[AnsiSequence] = [] + additions: list[AnsiSequence] = [] # List of strings that remove style - removals: List[AnsiSequence] = [] + removals: list[AnsiSequence] = [] # Process the style settings if fg is not None: diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index c68859530..e4a6e8331 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ This module adds capabilities to argparse by patching a few of its functions. It also defines a parser class called Cmd2ArgumentParser which improves error @@ -230,6 +229,7 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens) ZERO_OR_MORE, ArgumentError, ) +from collections.abc import Callable, Iterable, Sequence from gettext import ( gettext, ) @@ -237,17 +237,9 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens) IO, TYPE_CHECKING, Any, - Callable, - Dict, - Iterable, - List, NoReturn, Optional, Protocol, - Sequence, - Set, - Tuple, - Type, Union, cast, runtime_checkable, @@ -325,7 +317,7 @@ class ChoicesProviderFuncBase(Protocol): Function that returns a list of choices in support of tab completion """ - def __call__(self) -> List[str]: ... # pragma: no cover + def __call__(self) -> list[str]: ... # pragma: no cover @runtime_checkable @@ -334,7 +326,7 @@ class ChoicesProviderFuncWithTokens(Protocol): Function that returns a list of choices in support of tab completion and accepts a dictionary of prior arguments. """ - def __call__(self, *, arg_tokens: Dict[str, List[str]] = {}) -> List[str]: ... # pragma: no cover + def __call__(self, *, arg_tokens: dict[str, list[str]] = {}) -> list[str]: ... # pragma: no cover ChoicesProviderFunc = Union[ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens] @@ -352,7 +344,7 @@ def __call__( line: str, begidx: int, endidx: int, - ) -> List[str]: ... # pragma: no cover + ) -> list[str]: ... # pragma: no cover @runtime_checkable @@ -369,8 +361,8 @@ def __call__( begidx: int, endidx: int, *, - arg_tokens: Dict[str, List[str]] = {}, - ) -> List[str]: ... # pragma: no cover + arg_tokens: dict[str, list[str]] = {}, + ) -> list[str]: ... # pragma: no cover CompleterFunc = Union[CompleterFuncBase, CompleterFuncWithTokens] @@ -571,7 +563,7 @@ def _action_set_descriptive_header(self: argparse.Action, descriptive_header: Op ############################################################################################################ # Patch argparse.Action with accessors for nargs_range attribute ############################################################################################################ -def _action_get_nargs_range(self: argparse.Action) -> Optional[Tuple[int, Union[int, float]]]: +def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[int, float]]]: """ Get the nargs_range attribute of an argparse Action. @@ -582,13 +574,13 @@ def _action_get_nargs_range(self: argparse.Action) -> Optional[Tuple[int, Union[ :param self: argparse Action being queried :return: The value of nargs_range or None if attribute does not exist """ - return cast("Optional[Tuple[int, Union[int, float]]]", getattr(self, ATTR_NARGS_RANGE, None)) + return cast("Optional[tuple[int, Union[int, float]]]", getattr(self, ATTR_NARGS_RANGE, None)) setattr(argparse.Action, 'get_nargs_range', _action_get_nargs_range) -def _action_set_nargs_range(self: argparse.Action, nargs_range: Optional[Tuple[int, Union[int, float]]]) -> None: +def _action_set_nargs_range(self: argparse.Action, nargs_range: Optional[tuple[int, Union[int, float]]]) -> None: """ Set the nargs_range attribute of an argparse Action. @@ -646,11 +638,11 @@ def _action_set_suppress_tab_hint(self: argparse.Action, suppress_tab_hint: bool # Allow developers to add custom action attributes ############################################################################################################ -CUSTOM_ACTION_ATTRIBS: Set[str] = set() +CUSTOM_ACTION_ATTRIBS: set[str] = set() _CUSTOM_ATTRIB_PFX = '_attr_' -def register_argparse_argument_parameter(param_name: str, param_type: Optional[Type[Any]]) -> None: +def register_argparse_argument_parameter(param_name: str, param_type: Optional[type[Any]]) -> None: """ Registers a custom argparse argument parameter. @@ -719,7 +711,7 @@ def _action_set_custom_parameter(self: argparse.Action, value: Any) -> None: def _add_argument_wrapper( self: argparse._ActionsContainer, *args: Any, - nargs: Union[int, str, Tuple[int], Tuple[int, int], Tuple[int, float], None] = None, + nargs: Union[int, str, tuple[int], tuple[int, int], tuple[int, float], None] = None, choices_provider: Optional[ChoicesProviderFunc] = None, completer: Optional[CompleterFunc] = None, suppress_tab_hint: bool = False, @@ -770,7 +762,7 @@ def _add_argument_wrapper( nargs_range = None if nargs is not None: - nargs_adjusted: Union[int, str, Tuple[int], Tuple[int, int], Tuple[int, float], None] + nargs_adjusted: Union[int, str, tuple[int], tuple[int, int], tuple[int, float], None] # Check if nargs was given as a range if isinstance(nargs, tuple): # Handle 1-item tuple by setting max to INFINITY @@ -820,7 +812,7 @@ def _add_argument_wrapper( kwargs['nargs'] = nargs_adjusted # Extract registered custom keyword arguments - custom_attribs: Dict[str, Any] = {} + custom_attribs: dict[str, Any] = {} for keyword, value in kwargs.items(): if keyword in CUSTOM_ACTION_ATTRIBS: custom_attribs[keyword] = value @@ -918,7 +910,7 @@ def _match_argument_wrapper(self: argparse.ArgumentParser, action: argparse.Acti ATTR_AP_COMPLETER_TYPE = 'ap_completer_type' -def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[Type['ArgparseCompleter']]: +def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[type['ArgparseCompleter']]: """ Get the ap_completer_type attribute of an argparse ArgumentParser. @@ -929,13 +921,13 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti :param self: ArgumentParser being queried :return: An ArgparseCompleter-based class or None if attribute does not exist """ - return cast("Optional[Type[ArgparseCompleter]]", getattr(self, ATTR_AP_COMPLETER_TYPE, None)) + return cast("Optional[type[ArgparseCompleter]]", getattr(self, ATTR_AP_COMPLETER_TYPE, None)) setattr(argparse.ArgumentParser, 'get_ap_completer_type', _ArgumentParser_get_ap_completer_type) -def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: Type['ArgparseCompleter']) -> None: +def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: type['ArgparseCompleter']) -> None: """ Set the ap_completer_type attribute of an argparse ArgumentParser. @@ -1092,9 +1084,9 @@ def _format_usage( # End cmd2 customization # helper for wrapping lines - def get_lines(parts: List[str], indent: str, prefix: Optional[str] = None) -> List[str]: - lines: List[str] = [] - line: List[str] = [] + def get_lines(parts: list[str], indent: str, prefix: Optional[str] = None) -> list[str]: + lines: list[str] = [] + line: list[str] = [] if prefix is not None: line_len = len(prefix) - 1 else: @@ -1155,7 +1147,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: (metavar,) = self._metavar_formatter(action, default)(1) return metavar - parts: List[str] = [] + parts: list[str] = [] # if the Optional doesn't take a value, format is: # -s, --long @@ -1175,8 +1167,8 @@ def _format_action_invocation(self, action: argparse.Action) -> str: def _determine_metavar( self, action: argparse.Action, - default_metavar: Union[str, Tuple[str, ...]], - ) -> Union[str, Tuple[str, ...]]: + default_metavar: Union[str, tuple[str, ...]], + ) -> Union[str, tuple[str, ...]]: """Custom method to determine what to use as the metavar value of an action""" if action.metavar is not None: result = action.metavar @@ -1192,18 +1184,18 @@ def _determine_metavar( def _metavar_formatter( self, action: argparse.Action, - default_metavar: Union[str, Tuple[str, ...]], - ) -> Callable[[int], Tuple[str, ...]]: + default_metavar: Union[str, tuple[str, ...]], + ) -> Callable[[int], tuple[str, ...]]: metavar = self._determine_metavar(action, default_metavar) - def format_tuple(tuple_size: int) -> Tuple[str, ...]: + def format_tuple(tuple_size: int) -> tuple[str, ...]: if isinstance(metavar, tuple): return metavar return (metavar,) * tuple_size return format_tuple - def _format_args(self, action: argparse.Action, default_metavar: Union[str, Tuple[str, ...]]) -> str: + def _format_args(self, action: argparse.Action, default_metavar: Union[str, tuple[str, ...]]) -> str: """Customized to handle ranged nargs and make other output less verbose""" metavar = self._determine_metavar(action, default_metavar) metavar_formatter = self._metavar_formatter(action, default_metavar) @@ -1241,7 +1233,7 @@ def __init__( description: Optional[str] = None, epilog: Optional[str] = None, parents: Sequence[argparse.ArgumentParser] = (), - formatter_class: Type[argparse.HelpFormatter] = Cmd2HelpFormatter, + formatter_class: type[argparse.HelpFormatter] = Cmd2HelpFormatter, prefix_chars: str = '-', fromfile_prefix_chars: Optional[str] = None, argument_default: Optional[str] = None, @@ -1252,7 +1244,7 @@ def __init__( suggest_on_error: bool = False, color: bool = False, *, - ap_completer_type: Optional[Type['ArgparseCompleter']] = None, + ap_completer_type: Optional[type['ArgparseCompleter']] = None, ) -> None: """ # Custom parameter added by cmd2 @@ -1410,10 +1402,10 @@ def set(self, new_val: Any) -> None: # The default ArgumentParser class for a cmd2 app -DEFAULT_ARGUMENT_PARSER: Type[argparse.ArgumentParser] = Cmd2ArgumentParser +DEFAULT_ARGUMENT_PARSER: type[argparse.ArgumentParser] = Cmd2ArgumentParser -def set_default_argument_parser_type(parser_type: Type[argparse.ArgumentParser]) -> None: +def set_default_argument_parser_type(parser_type: type[argparse.ArgumentParser]) -> None: """ Set the default ArgumentParser class for a cmd2 app. This must be called prior to loading cmd2.py if you want to override the parser for cmd2's built-in commands. See examples/override_parser.py. diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index 30cb1a670..b59e09dc9 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ This module provides basic ability to copy from and paste to the clipboard/pastebuffer. """ diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 9754d494b..e6259a0cb 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1,4 +1,3 @@ -# coding=utf-8 """Variant on standard library's cmd with extra features. To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you @@ -48,6 +47,7 @@ OrderedDict, namedtuple, ) +from collections.abc import Callable, Iterable, Mapping from contextlib import ( redirect_stdout, ) @@ -59,16 +59,8 @@ IO, TYPE_CHECKING, Any, - Callable, - Dict, - Iterable, - List, - Mapping, Optional, - Set, TextIO, - Tuple, - Type, TypeVar, Union, cast, @@ -201,7 +193,7 @@ class _SavedCmd2Env: def __init__(self) -> None: self.readline_settings = _SavedReadlineSettings() self.readline_module: Optional[ModuleType] = None - self.history: List[str] = [] + self.history: list[str] = [] self.sys_stdout: Optional[TextIO] = None self.sys_stdin: Optional[TextIO] = None @@ -230,7 +222,7 @@ def __init__(self, cmd: 'Cmd') -> None: # Keyed by the fully qualified method names. This is more reliable than # the methods themselves, since wrapping a method will change its address. - self._parsers: Dict[str, argparse.ArgumentParser] = {} + self._parsers: dict[str, argparse.ArgumentParser] = {} @staticmethod def _fully_qualified_name(command_method: CommandFunc) -> str: @@ -325,11 +317,11 @@ def __init__( include_py: bool = False, include_ipy: bool = False, allow_cli_args: bool = True, - transcript_files: Optional[List[str]] = None, + transcript_files: Optional[list[str]] = None, allow_redirection: bool = True, - multiline_commands: Optional[List[str]] = None, - terminators: Optional[List[str]] = None, - shortcuts: Optional[Dict[str, str]] = None, + multiline_commands: Optional[list[str]] = None, + terminators: Optional[list[str]] = None, + shortcuts: Optional[dict[str, str]] = None, command_sets: Optional[Iterable[CommandSet]] = None, auto_load_commands: bool = True, allow_clipboard: bool = True, @@ -419,12 +411,12 @@ def __init__( self.max_completion_items = 50 # A dictionary mapping settable names to their Settable instance - self._settables: Dict[str, Settable] = dict() + self._settables: dict[str, Settable] = dict() self._always_prefix_settables: bool = False # CommandSet containers - self._installed_command_sets: Set[CommandSet] = set() - self._cmd_to_command_sets: Dict[str, CommandSet] = {} + self._installed_command_sets: set[CommandSet] = set() + self._cmd_to_command_sets: dict[str, CommandSet] = {} self.build_settables() @@ -446,16 +438,16 @@ def __init__( self.exclude_from_history = ['eof', 'history'] # Dictionary of macro names and their values - self.macros: Dict[str, Macro] = dict() + self.macros: dict[str, Macro] = dict() # Keeps track of typed command history in the Python shell - self._py_history: List[str] = [] + self._py_history: list[str] = [] # The name by which Python environments refer to the PyBridge to call app commands self.py_bridge_name = 'app' # Defines app-specific variables/functions available in Python shells and pyscripts - self.py_locals: Dict[str, Any] = dict() + self.py_locals: dict[str, Any] = dict() # True if running inside a Python shell or pyscript, False otherwise self._in_py = False @@ -468,7 +460,7 @@ def __init__( self.last_result: Any = None # Used by run_script command to store current script dir as a LIFO queue to support _relative_run_script command - self._script_dir: List[str] = [] + self._script_dir: list[str] = [] # Context manager used to protect critical sections in the main thread from stopping due to a KeyboardInterrupt self.sigint_protection = utils.ContextFlag() @@ -499,7 +491,7 @@ def __init__( self.broken_pipe_warning = '' # Commands that will run at the beginning of the command loop - self._startup_commands: List[str] = [] + self._startup_commands: list[str] = [] # If a startup script is provided and exists, then execute it in the startup commands if startup_script: @@ -511,7 +503,7 @@ def __init__( self._startup_commands.append(script_cmd) # Transcript files to run instead of interactive command loop - self._transcript_files: Optional[List[str]] = None + self._transcript_files: Optional[list[str]] = None # Check for command line args if allow_cli_args: @@ -554,7 +546,7 @@ def __init__( # Commands that have been disabled from use. This is to support commands that are only available # during specific states of the application. This dictionary's keys are the command names and its # values are DisabledCommand objects. - self.disabled_commands: Dict[str, DisabledCommand] = dict() + self.disabled_commands: dict[str, DisabledCommand] = dict() # If any command has been categorized, then all other commands that haven't been categorized # will display under this section in the help output. @@ -592,7 +584,7 @@ def __init__( self.formatted_completions = '' # Used by complete() for readline tab completion - self.completion_matches: List[str] = [] + self.completion_matches: list[str] = [] # Use this list if you need to display tab completion suggestions that are different than the actual text # of the matches. For instance, if you are completing strings that contain a common delimiter and you only @@ -600,7 +592,7 @@ def __init__( # still must be returned from your completer function. For an example, look at path_complete() which # uses this to show only the basename of paths as the suggestions. delimiter_complete() also populates # this list. These are ignored if self.formatted_completions is populated. - self.display_matches: List[str] = [] + self.display_matches: list[str] = [] # Used by functions like path_complete() and delimiter_complete() to properly # quote matches that are completed in a delimited fashion @@ -642,7 +634,7 @@ def __init__( # the current command being executed self.current_command: Optional[Statement] = None - def find_commandsets(self, commandset_type: Type[CommandSet], *, subclass_match: bool = False) -> List[CommandSet]: + def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: bool = False) -> list[CommandSet]: """ Find all CommandSets that match the provided CommandSet type. By default, locates a CommandSet that is an exact type match but may optionally return all CommandSets that @@ -671,7 +663,7 @@ def _autoload_commands(self) -> None: all_commandset_defs = CommandSet.__subclasses__() existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets] - def load_commandset_by_type(commandset_types: List[Type[CommandSet]]) -> None: + def load_commandset_by_type(commandset_types: list[type[CommandSet]]) -> None: for cmdset_type in commandset_types: # check if the type has sub-classes. We will only auto-load leaf class types. subclasses = cmdset_type.__subclasses__() @@ -715,7 +707,7 @@ def register_command_set(self, cmdset: CommandSet) -> None: cmdset.on_register(self) methods = cast( - "List[Tuple[str, Callable[..., Any]]]", + "list[tuple[str, Callable[..., Any]]]", inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] @@ -857,7 +849,7 @@ def unregister_command_set(self, cmdset: CommandSet) -> None: cmdset.on_unregister() self._unregister_subcommands(cmdset) - methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers( + methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] and hasattr(meth, '__name__') @@ -903,7 +895,7 @@ def check_parser_uninstallable(parser: argparse.ArgumentParser) -> None: check_parser_uninstallable(subparser) break - methods: List[Tuple[str, Callable[..., Any]]] = inspect.getmembers( + methods: list[tuple[str, Callable[..., Any]]] = inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] and hasattr(meth, '__name__') @@ -964,7 +956,7 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: f"Could not find argparser for command '{command_name}' needed by subcommand: {method!s}" ) - def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser: + def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser: if not subcmd_names: return action cur_subcmd = subcmd_names.pop(0) @@ -1140,7 +1132,7 @@ def remove_settable(self, name: str) -> None: def build_settables(self) -> None: """Create the dictionary of user-settable parameters""" - def get_allow_style_choices(cli_self: Cmd) -> List[str]: + def get_allow_style_choices(cli_self: Cmd) -> list[str]: """Used to tab complete allow_style values""" return [val.name.lower() for val in ansi.AllowStyle] @@ -1383,7 +1375,7 @@ def _reset_completion_defaults(self) -> None: elif rl_type == RlType.PYREADLINE: readline.rl.mode._display_completions = self._display_matches_pyreadline - def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[List[str], List[str]]: + def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> tuple[list[str], list[str]]: """Used by tab completion functions to get all tokens through the one being completed. :param line: the current input line with leading whitespace removed @@ -1454,7 +1446,7 @@ def basic_complete( begidx: int, endidx: int, match_against: Iterable[str], - ) -> List[str]: + ) -> list[str]: """ Basic tab completion function that matches against a list of strings without considering line contents or cursor position. The args required by this function are defined in the header of Python's cmd.py. @@ -1476,7 +1468,7 @@ def delimiter_complete( endidx: int, match_against: Iterable[str], delimiter: str, - ) -> List[str]: + ) -> list[str]: """ Performs tab completion against a list but each match is split on a delimiter and only the portion of the match being tab completed is shown as the completion suggestions. @@ -1542,10 +1534,10 @@ def flag_based_complete( line: str, begidx: int, endidx: int, - flag_dict: Dict[str, Union[Iterable[str], CompleterFunc]], + flag_dict: dict[str, Union[Iterable[str], CompleterFunc]], *, all_else: Union[None, Iterable[str], CompleterFunc] = None, - ) -> List[str]: + ) -> list[str]: """Tab completes based on a particular flag preceding the token being completed. :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1594,7 +1586,7 @@ def index_based_complete( index_dict: Mapping[int, Union[Iterable[str], CompleterFunc]], *, all_else: Optional[Union[Iterable[str], CompleterFunc]] = None, - ) -> List[str]: + ) -> list[str]: """Tab completes based on a fixed position in the input string. :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1639,7 +1631,7 @@ def index_based_complete( def path_complete( self, text: str, line: str, begidx: int, endidx: int, *, path_filter: Optional[Callable[[str], bool]] = None - ) -> List[str]: + ) -> list[str]: """Performs completion of local file system paths :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1653,7 +1645,7 @@ def path_complete( """ # Used to complete ~ and ~user strings - def complete_users() -> List[str]: + def complete_users() -> list[str]: users = [] # Windows lacks the pwd module so we can't get a list of users. @@ -1779,7 +1771,7 @@ def complete_users() -> List[str]: return matches - def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> List[str]: + def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> list[str]: """Performs completion of executables either in a user's path or a given path :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1803,7 +1795,7 @@ def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, text, line, begidx, endidx, path_filter=lambda path: os.path.isdir(path) or os.access(path, os.X_OK) ) - def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> List[str]: + def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> list[str]: """Called by complete() as the first tab completion function for all commands It determines if it should tab complete for redirection (|, >, >>) or use the completer function for the current command @@ -1884,7 +1876,7 @@ def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, com return compfunc(text, line, begidx, endidx) @staticmethod - def _pad_matches_to_display(matches_to_display: List[str]) -> Tuple[List[str], int]: # pragma: no cover + def _pad_matches_to_display(matches_to_display: list[str]) -> tuple[list[str], int]: # pragma: no cover """Adds padding to the matches being displayed as tab completion suggestions. The default padding of readline/pyreadine is small and not visually appealing especially if matches have spaces. It appears very squished together. @@ -1906,7 +1898,7 @@ def _pad_matches_to_display(matches_to_display: List[str]) -> Tuple[List[str], i return [cur_match + padding for cur_match in matches_to_display], len(padding) def _display_matches_gnu_readline( - self, substitution: str, matches: List[str], longest_match_length: int + self, substitution: str, matches: list[str], longest_match_length: int ) -> None: # pragma: no cover """Prints a match list using GNU readline's rl_display_match_list() @@ -1953,7 +1945,7 @@ def _display_matches_gnu_readline( # rl_display_match_list() expects matches to be in argv format where # substitution is the first element, followed by the matches, and then a NULL. - strings_array = cast("List[Optional[bytes]]", (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) + strings_array = cast("list[Optional[bytes]]", (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) # Copy in the encoded strings and add a NULL to the end strings_array[0] = encoded_substitution @@ -1966,7 +1958,7 @@ def _display_matches_gnu_readline( # Redraw prompt and input line rl_force_redisplay() - def _display_matches_pyreadline(self, matches: List[str]) -> None: # pragma: no cover + def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no cover """Prints a match list using pyreadline3's _display_completions() :param matches: the tab completion matches to display @@ -2002,7 +1994,7 @@ def _display_matches_pyreadline(self, matches: List[str]) -> None: # pragma: no orig_pyreadline_display(matches_to_display) @staticmethod - def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> Type[argparse_completer.ArgparseCompleter]: + def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argparse_completer.ArgparseCompleter]: """ Determine what type of ArgparseCompleter to use on a given parser. If the parser does not have one set, then use argparse_completer.DEFAULT_AP_COMPLETER. @@ -2010,7 +2002,7 @@ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> Type[argpar :param parser: the parser to examine :return: type of ArgparseCompleter """ - Completer = Optional[Type[argparse_completer.ArgparseCompleter]] + Completer = Optional[type[argparse_completer.ArgparseCompleter]] completer_type: Completer = parser.get_ap_completer_type() # type: ignore[attr-defined] if completer_type is None: @@ -2312,15 +2304,15 @@ def in_pyscript(self) -> bool: return self._in_py @property - def aliases(self) -> Dict[str, str]: + def aliases(self) -> dict[str, str]: """Read-only property to access the aliases stored in the StatementParser""" return self.statement_parser.aliases - def get_names(self) -> List[str]: + def get_names(self) -> list[str]: """Return an alphabetized list of names comprising the attributes of the cmd2 class instance.""" return dir(self) - def get_all_commands(self) -> List[str]: + def get_all_commands(self) -> list[str]: """Return a list of all commands""" return [ name[len(constants.COMMAND_FUNC_PREFIX) :] @@ -2328,7 +2320,7 @@ def get_all_commands(self) -> List[str]: if name.startswith(constants.COMMAND_FUNC_PREFIX) and callable(getattr(self, name)) ] - def get_visible_commands(self) -> List[str]: + def get_visible_commands(self) -> list[str]: """Return a list of commands that have not been hidden or disabled""" return [ command @@ -2339,9 +2331,9 @@ def get_visible_commands(self) -> List[str]: # Table displayed when tab completing aliases _alias_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) - def _get_alias_completion_items(self) -> List[CompletionItem]: + def _get_alias_completion_items(self) -> list[CompletionItem]: """Return list of alias names and values as CompletionItems""" - results: List[CompletionItem] = [] + results: list[CompletionItem] = [] for cur_key in self.aliases: row_data = [self.aliases[cur_key]] @@ -2352,9 +2344,9 @@ def _get_alias_completion_items(self) -> List[CompletionItem]: # Table displayed when tab completing macros _macro_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) - def _get_macro_completion_items(self) -> List[CompletionItem]: + def _get_macro_completion_items(self) -> list[CompletionItem]: """Return list of macro names and values as CompletionItems""" - results: List[CompletionItem] = [] + results: list[CompletionItem] = [] for cur_key in self.macros: row_data = [self.macros[cur_key].value] @@ -2365,9 +2357,9 @@ def _get_macro_completion_items(self) -> List[CompletionItem]: # Table displayed when tab completing Settables _settable_completion_table = SimpleTable([Column('Value', width=30), Column('Description', width=60)], divider_char=None) - def _get_settable_completion_items(self) -> List[CompletionItem]: + def _get_settable_completion_items(self) -> list[CompletionItem]: """Return list of Settable names, values, and descriptions as CompletionItems""" - results: List[CompletionItem] = [] + results: list[CompletionItem] = [] for cur_key in self.settables: row_data = [self.settables[cur_key].get_value(), self.settables[cur_key].description] @@ -2375,14 +2367,14 @@ def _get_settable_completion_items(self) -> List[CompletionItem]: return results - def _get_commands_aliases_and_macros_for_completion(self) -> List[str]: + def _get_commands_aliases_and_macros_for_completion(self) -> list[str]: """Return a list of visible commands, aliases, and macros for tab completion""" visible_commands = set(self.get_visible_commands()) alias_names = set(self.aliases) macro_names = set(self.macros) return list(visible_commands | alias_names | macro_names) - def get_help_topics(self) -> List[str]: + def get_help_topics(self) -> list[str]: """Return a list of help topics""" all_topics = [ name[len(constants.HELP_FUNC_PREFIX) :] @@ -2482,7 +2474,7 @@ def postloop(self) -> None: """ pass - def parseline(self, line: str) -> Tuple[str, str, str]: + def parseline(self, line: str) -> tuple[str, str, str]: """Parse the line into a command name and a string containing the arguments. NOTE: This is an override of a parent class method. It is only used by other parent class methods. @@ -2645,7 +2637,7 @@ def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) def runcmds_plus_hooks( self, - cmds: Union[List[HistoryItem], List[str]], + cmds: Union[list[HistoryItem], list[str]], *, add_to_history: bool = True, stop_on_keyboard_interrupt: bool = False, @@ -2872,7 +2864,6 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: :return: A bool telling if an error occurred and a utils.RedirectionSavedState object :raises RedirectionError: if an error occurs trying to pipe or redirect """ - import io import subprocess # Initialize the redirection saved state @@ -2892,13 +2883,13 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: read_fd, write_fd = os.pipe() # Open each side of the pipe - subproc_stdin = io.open(read_fd, 'r') - new_stdout: TextIO = cast("TextIO", io.open(write_fd, 'w')) + subproc_stdin = open(read_fd) + new_stdout: TextIO = cast("TextIO", open(write_fd, 'w')) # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs, # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe # processes close in the right order (most recent first). - kwargs: Dict[str, Any] = dict() + kwargs: dict[str, Any] = dict() if sys.platform == 'win32': kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP else: @@ -3087,7 +3078,7 @@ def read_input( self, prompt: str, *, - history: Optional[List[str]] = None, + history: Optional[list[str]] = None, completion_mode: utils.CompletionMode = utils.CompletionMode.NONE, preserve_quotes: bool = False, choices: Optional[Iterable[Any]] = None, @@ -3128,7 +3119,7 @@ def read_input( """ readline_configured = False saved_completer: Optional[CompleterFunc] = None - saved_history: Optional[List[str]] = None + saved_history: Optional[list[str]] = None def configure_readline() -> None: """Configure readline tab completion and history""" @@ -3509,7 +3500,7 @@ def _alias_list(self, args: argparse.Namespace) -> None: else: to_list = sorted(self.aliases, key=self.default_sort_key) - not_found: List[str] = [] + not_found: list[str] = [] for name in to_list: if name not in self.aliases: not_found.append(name) @@ -3744,7 +3735,7 @@ def _macro_list(self, args: argparse.Namespace) -> None: else: to_list = sorted(self.macros, key=self.default_sort_key) - not_found: List[str] = [] + not_found: list[str] = [] for name in to_list: if name not in self.macros: not_found.append(name) @@ -3766,7 +3757,7 @@ def _macro_list(self, args: argparse.Namespace) -> None: for name in not_found: self.perror(f"Macro '{name}' not found") - def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """Completes the command argument of help""" # Complete token against topics and visible commands @@ -3776,8 +3767,8 @@ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) return self.basic_complete(text, line, begidx, endidx, strs_to_match) def complete_help_subcommands( - self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]] - ) -> List[str]: + self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] + ) -> list[str]: """Completes the subcommands argument of help""" # Make sure we have a command whose subcommands we will complete @@ -3846,7 +3837,7 @@ def do_help(self, args: argparse.Namespace) -> None: self.perror(err_msg, apply_style=False) self.last_result = False - def print_topics(self, header: str, cmds: Optional[List[str]], cmdlen: int, maxcol: int) -> None: + def print_topics(self, header: str, cmds: Optional[list[str]], cmdlen: int, maxcol: int) -> None: """ Print groups of commands and topics in columns and an optional header Override of cmd's print_topics() to handle headers with newlines, ANSI style sequences, and wide characters @@ -3864,7 +3855,7 @@ def print_topics(self, header: str, cmds: Optional[List[str]], cmdlen: int, maxc self.columnize(cmds, maxcol - 1) self.poutput() - def columnize(self, str_list: Optional[List[str]], display_width: int = 80) -> None: + def columnize(self, str_list: Optional[list[str]], display_width: int = 80) -> None: """Display a list of single-line strings as a compact set of columns. Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters @@ -3940,15 +3931,15 @@ def _help_menu(self, verbose: bool = False) -> None: self.print_topics(self.misc_header, help_topics, 15, 80) self.print_topics(self.undoc_header, cmds_undoc, 15, 80) - def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str], List[str]]: + def _build_command_info(self) -> tuple[dict[str, list[str]], list[str], list[str], list[str]]: # Get a sorted list of help topics help_topics = sorted(self.get_help_topics(), key=self.default_sort_key) # Get a sorted list of visible command names visible_commands = sorted(self.get_visible_commands(), key=self.default_sort_key) - cmds_doc: List[str] = [] - cmds_undoc: List[str] = [] - cmds_cats: Dict[str, List[str]] = {} + cmds_doc: list[str] = [] + cmds_undoc: list[str] = [] + cmds_cats: dict[str, list[str]] = {} for command in visible_commands: func = cast("CommandFunc", self.cmd_func(command)) has_help_func = False @@ -3971,7 +3962,7 @@ def _build_command_info(self) -> Tuple[Dict[str, List[str]], List[str], List[str cmds_undoc.append(command) return cmds_cats, cmds_doc, cmds_undoc, help_topics - def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None: + def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None: """Customized version of print_topics that can switch between verbose or traditional output""" import io @@ -4046,7 +4037,7 @@ def do_shortcuts(self, _: argparse.Namespace) -> None: """List available shortcuts""" # Sort the shortcut tuples by name sorted_shortcuts = sorted(self.statement_parser.shortcuts, key=lambda x: self.default_sort_key(x[0])) - result = "\n".join('{}: {}'.format(sc[0], sc[1]) for sc in sorted_shortcuts) + result = "\n".join(f'{sc[0]}: {sc[1]}' for sc in sorted_shortcuts) self.poutput(f"Shortcuts for other commands:\n{result}") self.last_result = True @@ -4074,7 +4065,7 @@ def do_quit(self, _: argparse.Namespace) -> Optional[bool]: self.last_result = True return True - def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') -> Any: + def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], prompt: str = 'Your choice? ') -> Any: """Presents a numbered menu to the user. Modeled after the bash shell's SELECT. Returns the item chosen. @@ -4085,12 +4076,12 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], p | a list of tuples -> interpreted as (value, text), so that the return value can differ from the text advertised to the user""" - local_opts: Union[List[str], List[Tuple[Any, Optional[str]]]] + local_opts: Union[list[str], list[tuple[Any, Optional[str]]]] if isinstance(opts, str): - local_opts = cast("List[Tuple[Any, Optional[str]]]", list(zip(opts.split(), opts.split()))) + local_opts = cast("list[tuple[Any, Optional[str]]]", list(zip(opts.split(), opts.split()))) else: local_opts = opts - fulloptions: List[Tuple[Any, Optional[str]]] = [] + fulloptions: list[tuple[Any, Optional[str]]] = [] for opt in local_opts: if isinstance(opt, str): fulloptions.append((opt, opt)) @@ -4124,8 +4115,8 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]], p self.poutput(f"'{response}' isn't a valid choice. Pick a number between 1 and {len(fulloptions)}:") def complete_set_value( - self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]] - ) -> List[str]: + self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] + ) -> list[str]: """Completes the value argument of set""" param = arg_tokens['param'][0] try: @@ -4216,7 +4207,7 @@ def do_set(self, args: argparse.Namespace) -> None: max_name_width = max([ansi.style_aware_wcswidth(param) for param in to_show]) max_name_width = max(max_name_width, ansi.style_aware_wcswidth(name_label)) - cols: List[Column] = [ + cols: list[Column] = [ Column(name_label, width=max_name_width), Column('Value', width=30), Column('Description', width=60), @@ -4247,7 +4238,7 @@ def do_shell(self, args: argparse.Namespace) -> None: import signal import subprocess - kwargs: Dict[str, Any] = dict() + kwargs: dict[str, Any] = dict() # Set OS-specific parameters if sys.platform.startswith('win'): @@ -4866,7 +4857,7 @@ def _initialize_history(self, hist_file: str) -> None: with open(hist_file, 'rb') as fobj: compressed_bytes = fobj.read() except FileNotFoundError: - compressed_bytes = bytes() + compressed_bytes = b"" except OSError as ex: self.perror(f"Cannot read persistent history file '{hist_file}': {ex}") return @@ -4885,11 +4876,11 @@ def _initialize_history(self, hist_file: str) -> None: try: import lzma as decompress_lib - decompress_exceptions: Tuple[type[Exception]] = (decompress_lib.LZMAError,) + decompress_exceptions: tuple[type[Exception]] = (decompress_lib.LZMAError,) except ModuleNotFoundError: # pragma: no cover import bz2 as decompress_lib # type: ignore[no-redef] - decompress_exceptions: Tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef] + decompress_exceptions: tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef] try: history_json = decompress_lib.decompress(compressed_bytes).decode(encoding='utf-8') @@ -4946,7 +4937,7 @@ def _persist_history(self) -> None: def _generate_transcript( self, - history: Union[List[HistoryItem], List[str]], + history: Union[list[HistoryItem], list[str]], transcript_file: str, *, add_to_history: bool = True, @@ -5070,7 +5061,7 @@ def run_editor(self, file_path: Optional[str] = None) -> None: :raises EnvironmentError: if self.editor is not set """ if not self.editor: - raise EnvironmentError("Please use 'set editor' to specify your text editing program of choice.") + raise OSError("Please use 'set editor' to specify your text editing program of choice.") command = utils.quote_string(os.path.expanduser(self.editor)) if file_path: @@ -5196,7 +5187,7 @@ def do__relative_run_script(self, args: argparse.Namespace) -> Optional[bool]: # self.last_result will be set by do_run_script() return self.do_run_script(utils.quote_string(relative_path)) - def _run_transcript_tests(self, transcript_paths: List[str]) -> None: + def _run_transcript_tests(self, transcript_paths: list[str]) -> None: """Runs transcript tests for provided file(s). This is called when either -t is provided on the command line or the transcript_files argument is provided @@ -5585,12 +5576,12 @@ def cmdloop(self, intro: Optional[str] = None) -> int: # type: ignore[override] ### def _initialize_plugin_system(self) -> None: """Initialize the plugin system""" - self._preloop_hooks: List[Callable[[], None]] = [] - self._postloop_hooks: List[Callable[[], None]] = [] - self._postparsing_hooks: List[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = [] - self._precmd_hooks: List[Callable[[plugin.PrecommandData], plugin.PrecommandData]] = [] - self._postcmd_hooks: List[Callable[[plugin.PostcommandData], plugin.PostcommandData]] = [] - self._cmdfinalization_hooks: List[Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]] = [] + self._preloop_hooks: list[Callable[[], None]] = [] + self._postloop_hooks: list[Callable[[], None]] = [] + self._postparsing_hooks: list[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = [] + self._precmd_hooks: list[Callable[[plugin.PrecommandData], plugin.PrecommandData]] = [] + self._postcmd_hooks: list[Callable[[plugin.PostcommandData], plugin.PostcommandData]] = [] + self._cmdfinalization_hooks: list[Callable[[plugin.CommandFinalizationData], plugin.CommandFinalizationData]] = [] @classmethod def _validate_callable_param_count(cls, func: Callable[..., Any], count: int) -> None: @@ -5641,7 +5632,7 @@ def register_postparsing_hook(self, func: Callable[[plugin.PostparsingData], plu @classmethod def _validate_prepostcmd_hook( - cls, func: Callable[[CommandDataType], CommandDataType], data_type: Type[CommandDataType] + cls, func: Callable[[CommandDataType], CommandDataType], data_type: type[CommandDataType] ) -> None: """Check parameter and return types for pre and post command hooks.""" signature = inspect.signature(func) @@ -5704,7 +5695,7 @@ def _resolve_func_self( :param cmd_self: The `self` associated with the command or subcommand """ # figure out what class the command support function was defined in - func_class: Optional[Type[Any]] = get_defining_class(cmd_support_func) + func_class: Optional[type[Any]] = get_defining_class(cmd_support_func) # Was there a defining class identified? If so, is it a sub-class of CommandSet? if func_class is not None and issubclass(func_class, CommandSet): @@ -5715,7 +5706,7 @@ def _resolve_func_self( # 2. Do any of the registered CommandSets in the Cmd2 application exactly match the type? # 3. Is there a registered CommandSet that is is the only matching subclass? - func_self: Optional[Union[CommandSet, 'Cmd']] + func_self: Optional[Union[CommandSet, Cmd]] # check if the command's CommandSet is a sub-class of the support function's defining class if isinstance(cmd_self, func_class): @@ -5724,7 +5715,7 @@ def _resolve_func_self( else: # Search all registered CommandSets func_self = None - candidate_sets: List[CommandSet] = [] + candidate_sets: list[CommandSet] = [] for installed_cmd_set in self._installed_command_sets: if type(installed_cmd_set) == func_class: # noqa: E721 # Case 2: CommandSet is an exact type match for the function's CommandSet diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index 5bac8ef32..d32d49b10 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -1,15 +1,11 @@ -# coding=utf-8 """ Supports the definition of commands in separate classes to be composed into cmd2.Cmd """ +from collections.abc import Callable, Mapping from typing import ( TYPE_CHECKING, - Callable, - Dict, - Mapping, Optional, - Type, TypeVar, ) @@ -31,7 +27,7 @@ #: Further refinements are needed to define the input parameters CommandFunc = Callable[..., Optional[bool]] -CommandSetType = TypeVar('CommandSetType', bound=Type['CommandSet']) +CommandSetType = TypeVar('CommandSetType', bound=type['CommandSet']) def with_default_category(category: str, *, heritable: bool = True) -> Callable[[CommandSetType], CommandSetType]: @@ -85,7 +81,7 @@ def decorate_class(cls: CommandSetType) -> CommandSetType: return decorate_class -class CommandSet(object): +class CommandSet: """ Base class for defining sets of commands to load in cmd2. @@ -100,7 +96,7 @@ def __init__(self) -> None: # accessed by child classes using the self._cmd property. self.__cmd_internal: Optional[cmd2.Cmd] = None - self._settables: Dict[str, Settable] = {} + self._settables: dict[str, Settable] = {} self._settable_prefix = self.__class__.__name__ @property diff --git a/cmd2/constants.py b/cmd2/constants.py index ebac5da95..b45a73bf6 100644 --- a/cmd2/constants.py +++ b/cmd2/constants.py @@ -1,5 +1,4 @@ # -# coding=utf-8 """This module contains constants used throughout ``cmd2``.""" # Unless documented in https://cmd2.readthedocs.io/en/latest/api/index.html diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 3d1105c64..dcdfb8f1f 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -1,17 +1,11 @@ -# coding=utf-8 """Decorators for ``cmd2`` commands""" import argparse +from collections.abc import Callable, Sequence from typing import ( TYPE_CHECKING, Any, - Callable, - Dict, - List, Optional, - Sequence, - Tuple, - Type, TypeVar, Union, ) @@ -68,7 +62,7 @@ def cat_decorator(func: CommandFunc) -> CommandFunc: CommandParent = TypeVar('CommandParent', bound=Union['cmd2.Cmd', CommandSet]) -CommandParentType = TypeVar('CommandParentType', bound=Union[Type['cmd2.Cmd'], Type[CommandSet]]) +CommandParentType = TypeVar('CommandParentType', bound=Union[type['cmd2.Cmd'], type[CommandSet]]) RawCommandFuncOptionalBoolReturn = Callable[[CommandParent, Union[Statement, str]], Optional[bool]] @@ -79,7 +73,7 @@ def cat_decorator(func: CommandFunc) -> CommandFunc: # in cmd2 command functions/callables. As long as the 2-ple of arguments we expect to be there can be # found we can swap out the statement with each decorator's specific parameters ########################## -def _parse_positionals(args: Tuple[Any, ...]) -> Tuple['cmd2.Cmd', Union[Statement, str]]: +def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Statement, str]]: """ Helper function for cmd2 decorators to inspect the positional arguments until the cmd2.Cmd argument is found Assumes that we will find cmd2.Cmd followed by the command statement object or string. @@ -103,7 +97,7 @@ def _parse_positionals(args: Tuple[Any, ...]) -> Tuple['cmd2.Cmd', Union[Stateme raise TypeError('Expected arguments: cmd: cmd2.Cmd, statement: Union[Statement, str] Not found') -def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> List[Any]: +def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> list[Any]: """ Helper function for cmd2 decorators to swap the Statement parameter with one or more decorator-specific parameters @@ -120,13 +114,13 @@ def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> #: Function signature for a command function that accepts a pre-processed argument list from user input #: and optionally returns a boolean -ArgListCommandFuncOptionalBoolReturn = Callable[[CommandParent, List[str]], Optional[bool]] +ArgListCommandFuncOptionalBoolReturn = Callable[[CommandParent, list[str]], Optional[bool]] #: Function signature for a command function that accepts a pre-processed argument list from user input #: and returns a boolean -ArgListCommandFuncBoolReturn = Callable[[CommandParent, List[str]], bool] +ArgListCommandFuncBoolReturn = Callable[[CommandParent, list[str]], bool] #: Function signature for a command function that accepts a pre-processed argument list from user input #: and returns Nothing -ArgListCommandFuncNoneReturn = Callable[[CommandParent, List[str]], None] +ArgListCommandFuncNoneReturn = Callable[[CommandParent, list[str]], None] #: Aggregate of all accepted function signatures for command functions that accept a pre-processed argument list ArgListCommandFunc = Union[ @@ -208,7 +202,7 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: """ # Set the prog value for this parser parser.prog = prog - req_args: List[str] = [] + req_args: list[str] = [] # Set the prog value for the parser's subcommands for action in parser._actions: @@ -251,17 +245,17 @@ def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and optionally return a boolean ArgparseCommandFuncOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace], Optional[bool]] -ArgparseCommandFuncWithUnknownArgsOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace, List[str]], Optional[bool]] +ArgparseCommandFuncWithUnknownArgsOptionalBoolReturn = Callable[[CommandParent, argparse.Namespace, list[str]], Optional[bool]] #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and return a boolean ArgparseCommandFuncBoolReturn = Callable[[CommandParent, argparse.Namespace], bool] -ArgparseCommandFuncWithUnknownArgsBoolReturn = Callable[[CommandParent, argparse.Namespace, List[str]], bool] +ArgparseCommandFuncWithUnknownArgsBoolReturn = Callable[[CommandParent, argparse.Namespace, list[str]], bool] #: Function signatures for command functions that use an argparse.ArgumentParser to process user input #: and return nothing ArgparseCommandFuncNoneReturn = Callable[[CommandParent, argparse.Namespace], None] -ArgparseCommandFuncWithUnknownArgsNoneReturn = Callable[[CommandParent, argparse.Namespace, List[str]], None] +ArgparseCommandFuncWithUnknownArgsNoneReturn = Callable[[CommandParent, argparse.Namespace, list[str]], None] #: Aggregate of all accepted function signatures for an argparse command function ArgparseCommandFunc = Union[ @@ -343,7 +337,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> RawCommandFuncOpt """ @functools.wraps(func) - def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]: + def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]: """ Command function wrapper which translates command line into argparse Namespace and calls actual command function @@ -376,7 +370,7 @@ def cmd_wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Optional[bool]: namespace = ns_provider(provider_self if provider_self is not None else cmd2_app) try: - new_args: Union[Tuple[argparse.Namespace], Tuple[argparse.Namespace, List[str]]] + new_args: Union[tuple[argparse.Namespace], tuple[argparse.Namespace, list[str]]] if with_unknown_args: new_args = arg_parser.parse_known_args(parsed_arglist, namespace) else: @@ -421,7 +415,7 @@ def as_subcommand_to( ], *, help: Optional[str] = None, - aliases: Optional[List[str]] = None, + aliases: Optional[list[str]] = None, ) -> Callable[[ArgparseCommandFunc[CommandParent]], ArgparseCommandFunc[CommandParent]]: """ Tag this method as a subcommand to an existing argparse decorated command. @@ -443,7 +437,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> ArgparseCommandFu setattr(func, constants.SUBCMD_ATTR_NAME, subcommand) # Keyword arguments for subparsers.add_parser() - add_parser_kwargs: Dict[str, Any] = dict() + add_parser_kwargs: dict[str, Any] = dict() if help is not None: add_parser_kwargs['help'] = help if aliases: diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index c07100134..4f8a517c2 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -1,4 +1,3 @@ -# coding=utf-8 """Custom exceptions for cmd2""" from typing import ( diff --git a/cmd2/history.py b/cmd2/history.py index 5f9758f96..8b6fc0258 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ History management classes """ @@ -8,15 +7,12 @@ from collections import ( OrderedDict, ) +from collections.abc import Callable, Iterable from dataclasses import ( dataclass, ) from typing import ( Any, - Callable, - Dict, - Iterable, - List, Optional, Union, overload, @@ -132,12 +128,12 @@ def pr(self, idx: int, script: bool = False, expanded: bool = False, verbose: bo return ret_str - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Utility method to convert this HistoryItem into a dictionary for use in persistent JSON history files""" return {HistoryItem._statement_field: self.statement.to_dict()} @staticmethod - def from_dict(source_dict: Dict[str, Any]) -> 'HistoryItem': + def from_dict(source_dict: dict[str, Any]) -> 'HistoryItem': """ Utility method to restore a HistoryItem from a dictionary @@ -149,7 +145,7 @@ def from_dict(source_dict: Dict[str, Any]) -> 'HistoryItem': return HistoryItem(Statement.from_dict(statement_dict)) -class History(List[HistoryItem]): +class History(list[HistoryItem]): """A list of [HistoryItem][cmd2.history.HistoryItem] objects with additional methods for searching and managing the list. diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 598cbe687..bd115585b 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -1,20 +1,16 @@ # -# -*- coding: utf-8 -*- """Statement parsing classes for cmd2""" import re import shlex +from collections.abc import Iterable from dataclasses import ( dataclass, field, ) from typing import ( Any, - Dict, - Iterable, - List, Optional, - Tuple, Union, ) @@ -27,7 +23,7 @@ ) -def shlex_split(str_to_split: str) -> List[str]: +def shlex_split(str_to_split: str) -> list[str]: """ A wrapper around shlex.split() that uses cmd2's preferred arguments. This allows other classes to easily call split() the same way StatementParser does. @@ -85,7 +81,7 @@ class Macro: minimum_arg_count: int # Used to fill in argument placeholders in the macro - arg_list: List[MacroArg] = field(default_factory=list) + arg_list: list[MacroArg] = field(default_factory=list) @dataclass(frozen=True) @@ -129,7 +125,7 @@ class Statement(str): # type: ignore[override] command: str = '' # list of arguments to the command, not including any output redirection or terminators; quoted args remain quoted - arg_list: List[str] = field(default_factory=list) + arg_list: list[str] = field(default_factory=list) # if the command is a multiline command, the name of the command, otherwise empty multiline_command: str = '' @@ -206,7 +202,7 @@ def expanded_command_line(self) -> str: return self.command_and_args + self.post_command @property - def argv(self) -> List[str]: + def argv(self) -> list[str]: """a list of arguments a-la ``sys.argv``. The first element of the list is the command after shortcut and macro @@ -225,12 +221,12 @@ def argv(self) -> List[str]: return rtn - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Utility method to convert this Statement into a dictionary for use in persistent JSON history files""" return self.__dict__.copy() @staticmethod - def from_dict(source_dict: Dict[str, Any]) -> 'Statement': + def from_dict(source_dict: dict[str, Any]) -> 'Statement': """ Utility method to restore a Statement from a dictionary @@ -258,8 +254,8 @@ def __init__( self, terminators: Optional[Iterable[str]] = None, multiline_commands: Optional[Iterable[str]] = None, - aliases: Optional[Dict[str, str]] = None, - shortcuts: Optional[Dict[str, str]] = None, + aliases: Optional[dict[str, str]] = None, + shortcuts: Optional[dict[str, str]] = None, ) -> None: """Initialize an instance of StatementParser. @@ -271,13 +267,13 @@ def __init__( :param aliases: dictionary containing aliases :param shortcuts: dictionary containing shortcuts """ - self.terminators: Tuple[str, ...] + self.terminators: tuple[str, ...] if terminators is None: self.terminators = (constants.MULTILINE_TERMINATOR,) else: self.terminators = tuple(terminators) - 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 {} + 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 @@ -318,7 +314,7 @@ def __init__( expr = rf'\A\s*(\S*?)({second_group})' self._command_pattern = re.compile(expr) - def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> Tuple[bool, str]: + def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> tuple[bool, str]: """Determine whether a word is a valid name for a command. Commands cannot include redirection characters, whitespace, @@ -369,7 +365,7 @@ def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> Tuple[b errmsg = '' return valid, errmsg - def tokenize(self, line: str) -> List[str]: + def tokenize(self, line: str) -> list[str]: """ Lex a string into a list of tokens. Shortcuts and aliases are expanded and comments are removed. @@ -607,7 +603,7 @@ def parse_command_only(self, rawinput: str) -> Statement: def get_command_arg_list( self, command_name: str, to_parse: Union[Statement, str], preserve_quotes: bool - ) -> Tuple[Statement, List[str]]: + ) -> tuple[Statement, list[str]]: """ Convenience method used by the argument parsing decorators. @@ -675,7 +671,7 @@ def _expand(self, line: str) -> str: return line @staticmethod - def _command_and_args(tokens: List[str]) -> Tuple[str, str]: + def _command_and_args(tokens: list[str]) -> tuple[str, str]: """Given a list of tokens, return a tuple of the command and the args as a string. """ @@ -690,7 +686,7 @@ def _command_and_args(tokens: List[str]) -> Tuple[str, str]: return command, args - def split_on_punctuation(self, tokens: List[str]) -> List[str]: + def split_on_punctuation(self, tokens: list[str]) -> list[str]: """Further splits tokens from a command line using punctuation characters. Punctuation characters are treated as word breaks when they are in @@ -700,7 +696,7 @@ def split_on_punctuation(self, tokens: List[str]) -> List[str]: :param tokens: the tokens as parsed by shlex :return: a new list of tokens, further split using punctuation """ - punctuation: List[str] = [] + punctuation: list[str] = [] punctuation.extend(self.terminators) punctuation.extend(constants.REDIRECTION_CHARS) diff --git a/cmd2/plugin.py b/cmd2/plugin.py index e7e2c6863..a3fed2856 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -1,5 +1,4 @@ # -# coding=utf-8 """Classes for the cmd2 plugin system""" from dataclasses import ( diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 366469afe..0936a74c6 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Bridges calls made inside of a Python environment to the Cmd2 host app while maintaining a reasonable degree of isolation between the two. @@ -13,7 +12,6 @@ IO, TYPE_CHECKING, Any, - List, NamedTuple, Optional, TextIO, @@ -98,9 +96,9 @@ def __init__(self, cmd2_app: 'cmd2.Cmd', *, add_to_history: bool = True) -> None # Tells if any of the commands run via __call__ returned True for stop self.stop = False - def __dir__(self) -> List[str]: + def __dir__(self) -> list[str]: """Return a custom set of attribute names""" - attributes: List[str] = [] + attributes: list[str] = [] attributes.insert(0, 'cmd_echo') return attributes diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index c591d29e2..4f98bb023 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Imports the proper Readline for the platform and provides utility functions for it """ diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index d28b63a0e..ae3534e52 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ cmd2 table creation API This API is built upon two core classes: Column and TableCreator @@ -11,16 +10,13 @@ from collections import ( deque, ) +from collections.abc import Sequence from enum import ( Enum, ) from typing import ( Any, - Deque, - List, Optional, - Sequence, - Tuple, ) from wcwidth import ( # type: ignore[import] @@ -154,7 +150,7 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None: col.width = max(1, ansi.widest_line(col.header)) @staticmethod - def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> Tuple[str, int, int]: + def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> tuple[str, int, int]: """ Used by _wrap_text() to wrap a long word over multiple lines @@ -380,7 +376,7 @@ def add_word(word_to_add: str, is_last_word: bool) -> None: return wrapped_buf.getvalue() - def _generate_cell_lines(self, cell_data: Any, is_header: bool, col: Column, fill_char: str) -> Tuple[Deque[str], int]: + def _generate_cell_lines(self, cell_data: Any, is_header: bool, col: Column, fill_char: str) -> tuple[deque[str], int]: """ Generate the lines of a table cell @@ -452,7 +448,7 @@ class Cell: def __init__(self) -> None: # Data in this cell split into individual lines - self.lines: Deque[str] = deque() + self.lines: deque[str] = deque() # Display width of this cell self.width = 0 @@ -652,7 +648,7 @@ def generate_header(self) -> str: inter_cell = self.apply_header_bg(self.column_spacing * SPACE) # Apply background color to header text in Columns which allow it - to_display: List[Any] = [] + to_display: list[Any] = [] for col in self.cols: if col.style_header_text: to_display.append(self.apply_header_bg(col.header)) @@ -692,7 +688,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: inter_cell = self.apply_data_bg(self.column_spacing * SPACE) # Apply background color to data text in Columns which allow it - to_display: List[Any] = [] + to_display: list[Any] = [] for index, col in enumerate(self.cols): if col.style_data_text: to_display.append(self.apply_data_bg(row_data[index])) @@ -947,7 +943,7 @@ def generate_header(self) -> str: post_line = self.apply_header_bg(self.padding * SPACE) + self.apply_border_color('║') # Apply background color to header text in Columns which allow it - to_display: List[Any] = [] + to_display: list[Any] = [] for col in self.cols: if col.style_header_text: to_display.append(self.apply_header_bg(col.header)) @@ -991,7 +987,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: post_line = self.apply_data_bg(self.padding * SPACE) + self.apply_border_color('║') # Apply background color to data text in Columns which allow it - to_display: List[Any] = [] + to_display: list[Any] = [] for index, col in enumerate(self.cols): if col.style_data_text: to_display.append(self.apply_data_bg(row_data[index])) diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 9545b5b97..7a08480f2 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -1,5 +1,4 @@ # -# -*- coding: utf-8 -*- """Machinery for running and validating transcripts. If the user wants to run a transcript (see docs/transcript.rst), @@ -12,13 +11,11 @@ class is used in cmd2.py::run_transcript_tests() import re import unittest +from collections.abc import Iterator from typing import ( TYPE_CHECKING, - Iterator, - List, Optional, TextIO, - Tuple, cast, ) @@ -66,7 +63,7 @@ def runTest(self) -> None: # was testall def _fetchTranscripts(self) -> None: self.transcripts = {} - testfiles = cast('List[str]', getattr(self.cmdapp, 'testfiles', [])) + testfiles = cast('list[str]', getattr(self.cmdapp, 'testfiles', [])) for fname in testfiles: tfile = open(fname) self.transcripts[fname] = iter(tfile.readlines()) @@ -183,7 +180,7 @@ def _transform_transcript_expected(self, s: str) -> str: return regex @staticmethod - def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> Tuple[str, int, int]: + def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> tuple[str, int, int]: """Find the next slash in {s} after {start} that is not preceded by a backslash. If we find an escaped slash, add everything up to and including it to regex, diff --git a/cmd2/utils.py b/cmd2/utils.py index ee1fa20f1..6504e0525 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -1,4 +1,3 @@ -# coding=utf-8 """Shared utility functions""" import argparse @@ -13,6 +12,7 @@ import sys import threading import unicodedata +from collections.abc import Callable, Iterable from difflib import ( SequenceMatcher, ) @@ -22,13 +22,8 @@ from typing import ( TYPE_CHECKING, Any, - Callable, - Dict, - Iterable, - List, Optional, TextIO, - Type, TypeVar, Union, cast, @@ -120,7 +115,7 @@ class Settable: def __init__( self, name: str, - val_type: Union[Type[Any], Callable[[Any], Any]], + val_type: Union[type[Any], Callable[[Any], Any]], description: str, settable_object: object, *, @@ -159,7 +154,7 @@ def __init__( """ if val_type is bool: - def get_bool_choices(_) -> List[str]: # type: ignore[no-untyped-def] + def get_bool_choices(_) -> list[str]: # type: ignore[no-untyped-def] """Used to tab complete lowercase boolean values""" return ['true', 'false'] @@ -230,7 +225,7 @@ def is_text_file(file_path: str) -> bool: return valid_text_file -def remove_duplicates(list_to_prune: List[_T]) -> List[_T]: +def remove_duplicates(list_to_prune: list[_T]) -> list[_T]: """Removes duplicates from a list while preserving order of the items. :param list_to_prune: the list being pruned of duplicates @@ -252,7 +247,7 @@ def norm_fold(astr: str) -> str: return unicodedata.normalize('NFC', astr).casefold() -def alphabetical_sort(list_to_sort: Iterable[str]) -> List[str]: +def alphabetical_sort(list_to_sort: Iterable[str]) -> list[str]: """Sorts a list of strings alphabetically. For example: ['a1', 'A11', 'A2', 'a22', 'a3'] @@ -279,7 +274,7 @@ def try_int_or_force_to_lower_case(input_str: str) -> Union[int, str]: return norm_fold(input_str) -def natural_keys(input_str: str) -> List[Union[int, str]]: +def natural_keys(input_str: str) -> list[Union[int, str]]: """ Converts a string into a list of integers and strings to support natural sorting (see natural_sort). @@ -290,7 +285,7 @@ def natural_keys(input_str: str) -> List[Union[int, str]]: return [try_int_or_force_to_lower_case(substr) for substr in re.split(r'(\d+)', input_str)] -def natural_sort(list_to_sort: Iterable[str]) -> List[str]: +def natural_sort(list_to_sort: Iterable[str]) -> list[str]: """ Sorts a list of strings case insensitively as well as numerically. @@ -306,7 +301,7 @@ def natural_sort(list_to_sort: Iterable[str]) -> List[str]: return sorted(list_to_sort, key=natural_keys) -def quote_specific_tokens(tokens: List[str], tokens_to_quote: List[str]) -> None: +def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None: """ Quote specific tokens in a list @@ -318,7 +313,7 @@ def quote_specific_tokens(tokens: List[str], tokens_to_quote: List[str]) -> None tokens[i] = quote_string(token) -def unquote_specific_tokens(tokens: List[str], tokens_to_unquote: List[str]) -> None: +def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> None: """ Unquote specific tokens in a list @@ -352,7 +347,7 @@ def expand_user(token: str) -> str: return token -def expand_user_in_tokens(tokens: List[str]) -> None: +def expand_user_in_tokens(tokens: list[str]) -> None: """ Call expand_user() on all tokens in a list of strings :param tokens: tokens to expand @@ -394,7 +389,7 @@ def find_editor() -> Optional[str]: return editor -def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> List[str]: +def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> list[str]: """Return a list of file paths based on a glob pattern. Only files are returned, not directories, and optionally only files for which the user has a specified access to. @@ -406,7 +401,7 @@ def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> List[str]: return [f for f in glob.glob(pattern) if os.path.isfile(f) and os.access(f, access)] -def files_from_glob_patterns(patterns: List[str], access: int = os.F_OK) -> List[str]: +def files_from_glob_patterns(patterns: list[str], access: int = os.F_OK) -> list[str]: """Return a list of file paths based on a list of glob patterns. Only files are returned, not directories, and optionally only files for which the user has a specified access to. @@ -422,7 +417,7 @@ def files_from_glob_patterns(patterns: List[str], access: int = os.F_OK) -> List return files -def get_exes_in_path(starts_with: str) -> List[str]: +def get_exes_in_path(starts_with: str) -> list[str]: """Returns names of executables in a user's path :param starts_with: what the exes should start with. leave blank for all exes in path. @@ -741,7 +736,7 @@ def __init__( self.saved_redirecting = saved_redirecting -def _remove_overridden_styles(styles_to_parse: List[str]) -> List[str]: +def _remove_overridden_styles(styles_to_parse: list[str]) -> list[str]: """ Utility function for align_text() / truncate_line() which filters a style list down to only those which would still be in effect if all were processed in order. @@ -762,7 +757,7 @@ class StyleState: def __init__(self) -> None: # Contains styles still in effect, keyed by their index in styles_to_parse - self.style_dict: Dict[int, str] = dict() + self.style_dict: dict[int, str] = dict() # Indexes into style_dict self.reset_all: Optional[int] = None @@ -900,7 +895,7 @@ def align_text( # ANSI style sequences that may affect subsequent lines will be cancelled by the fill_char's style. # To avoid this, we save styles which are still in effect so we can restore them when beginning the next line. # This also allows lines to be used independently and still have their style. TableCreator does this. - previous_styles: List[str] = [] + previous_styles: list[str] = [] for index, line in enumerate(lines): if index > 0: @@ -1111,7 +1106,7 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: return truncated_buf.getvalue() -def get_styles_dict(text: str) -> Dict[int, str]: +def get_styles_dict(text: str) -> dict[int, str]: """ Return an OrderedDict containing all ANSI style sequences found in a string @@ -1171,7 +1166,7 @@ def do_echo(self, arglist): setattr(func, constants.CMD_ATTR_HELP_CATEGORY, category) -def get_defining_class(meth: Callable[..., Any]) -> Optional[Type[Any]]: +def get_defining_class(meth: Callable[..., Any]) -> Optional[type[Any]]: """ Attempts to resolve the class that defined a method. diff --git a/examples/alias_startup.py b/examples/alias_startup.py index 1ad493ffa..e84278658 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating the following: 1) How to add custom command aliases using the alias command 2) How to run an initialization script at startup diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index e42960b1c..916358179 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """An example demonstrating how use one of cmd2's argument parsing decorators""" import argparse @@ -27,7 +26,7 @@ def do_fsize(self, args: argparse.Namespace) -> None: try: size = os.path.getsize(expanded_path) except OSError as ex: - self.perror("Error retrieving size: {}".format(ex)) + self.perror(f"Error retrieving size: {ex}") return if args.unit == 'KB': @@ -39,8 +38,8 @@ def do_fsize(self, args: argparse.Namespace) -> None: size = round(size, 2) if args.comma: - size = '{:,}'.format(size) - self.poutput('{} {}'.format(size, args.unit)) + size = f'{size:,}' + self.poutput(f'{size} {args.unit}') # do_pow parser pow_parser = cmd2.Cmd2ArgumentParser() @@ -54,7 +53,7 @@ def do_pow(self, args: argparse.Namespace) -> None: :param args: argparse arguments """ - self.poutput('{} ** {} == {}'.format(args.base, args.exponent, args.base**args.exponent)) + self.poutput(f'{args.base} ** {args.exponent} == {args.base**args.exponent}') if __name__ == '__main__': diff --git a/examples/arg_print.py b/examples/arg_print.py index 2cade9e42..26ccac9ca 100755 --- a/examples/arg_print.py +++ b/examples/arg_print.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating the following: 1) How arguments and options get parsed and passed to commands 2) How to change what syntax gets parsed as a comment and stripped from the arguments @@ -24,20 +23,20 @@ def __init__(self): def do_aprint(self, statement): """Print the argument string this basic command is called with.""" - self.poutput('aprint was called with argument: {!r}'.format(statement)) - self.poutput('statement.raw = {!r}'.format(statement.raw)) - self.poutput('statement.argv = {!r}'.format(statement.argv)) - self.poutput('statement.command = {!r}'.format(statement.command)) + self.poutput(f'aprint was called with argument: {statement!r}') + self.poutput(f'statement.raw = {statement.raw!r}') + self.poutput(f'statement.argv = {statement.argv!r}') + self.poutput(f'statement.command = {statement.command!r}') @cmd2.with_argument_list def do_lprint(self, arglist): """Print the argument list this basic command is called with.""" - self.poutput('lprint was called with the following list of arguments: {!r}'.format(arglist)) + self.poutput(f'lprint was called with the following list of arguments: {arglist!r}') @cmd2.with_argument_list(preserve_quotes=True) def do_rprint(self, arglist): """Print the argument list this basic command is called with (with quotes preserved).""" - self.poutput('rprint was called with the following list of arguments: {!r}'.format(arglist)) + self.poutput(f'rprint was called with the following list of arguments: {arglist!r}') oprint_parser = cmd2.Cmd2ArgumentParser() oprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') @@ -48,7 +47,7 @@ def do_rprint(self, arglist): @cmd2.with_argparser(oprint_parser) def do_oprint(self, args): """Print the options and argument list this options command was called with.""" - self.poutput('oprint was called with the following\n\toptions: {!r}'.format(args)) + self.poutput(f'oprint was called with the following\n\toptions: {args!r}') pprint_parser = cmd2.Cmd2ArgumentParser() pprint_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') @@ -58,7 +57,7 @@ def do_oprint(self, args): @cmd2.with_argparser(pprint_parser, with_unknown_args=True) def do_pprint(self, args, unknown): """Print the options and argument list this options command was called with.""" - self.poutput('oprint was called with the following\n\toptions: {!r}\n\targuments: {}'.format(args, unknown)) + self.poutput(f'oprint was called with the following\n\toptions: {args!r}\n\targuments: {unknown}') if __name__ == '__main__': diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index e0ad101b9..84082a6d3 100755 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -1,14 +1,9 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple example demonstrating how to integrate tab completion with argparse-based commands. """ import argparse -from typing import ( - Dict, - List, -) from cmd2 import ( Cmd, @@ -28,11 +23,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - def choices_provider(self) -> List[str]: + def choices_provider(self) -> list[str]: """A choices provider is useful when the choice list is based on instance data of your application""" return self.sport_item_strs - def choices_completion_error(self) -> List[str]: + def choices_completion_error(self) -> list[str]: """ CompletionErrors can be raised if an error occurs while tab completing. @@ -44,14 +39,14 @@ def choices_completion_error(self) -> List[str]: return self.sport_item_strs raise CompletionError("debug must be true") - def choices_completion_item(self) -> List[CompletionItem]: + def choices_completion_item(self) -> list[CompletionItem]: """Return CompletionItem instead of strings. These give more context to what's being tab completed.""" fancy_item = "These things can\ncontain newlines and\n" fancy_item += ansi.style("styled text!!", fg=ansi.Fg.LIGHT_YELLOW, underline=True) items = {1: "My item", 2: "Another item", 3: "Yet another item", 4: fancy_item} return [CompletionItem(item_id, description) for item_id, description in items.items()] - def choices_arg_tokens(self, arg_tokens: Dict[str, List[str]]) -> List[str]: + def choices_arg_tokens(self, arg_tokens: dict[str, list[str]]) -> list[str]: """ If a choices or completer function/method takes a value called arg_tokens, then it will be passed a dictionary that maps the command line tokens up through the one being completed diff --git a/examples/async_printing.py b/examples/async_printing.py index e94ee89a0..8520bc130 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple example demonstrating an application that asynchronously prints alerts, updates the prompt and changes the window title @@ -8,9 +7,6 @@ import random import threading import time -from typing import ( - List, -) import cmd2 from cmd2 import ( @@ -89,7 +85,7 @@ def do_stop_alerts(self, _): else: print("The alert thread is already stopped") - def _get_alerts(self) -> List[str]: + def _get_alerts(self) -> list[str]: """ Reports alerts :return: the list of alerts @@ -114,7 +110,7 @@ def _get_alerts(self) -> List[str]: for i in range(0, rand_num): self._alert_count += 1 - alerts.append("Alert {}".format(self._alert_count)) + alerts.append(f"Alert {self._alert_count}") self._next_alert_time = 0 @@ -187,7 +183,7 @@ def _alerter_thread_func(self) -> None: if alert_str: # new_prompt is an optional parameter to async_alert() self.async_alert(alert_str, new_prompt) - new_title = "Alerts Printed: {}".format(self._alert_count) + new_title = f"Alerts Printed: {self._alert_count}" self.set_window_title(new_title) # Otherwise check if the prompt needs to be updated or refreshed diff --git a/examples/basic.py b/examples/basic.py index 6ce4d2838..f84009b2b 100755 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example demonstrating the following: 1) How to add a command 2) How to add help for that command diff --git a/examples/basic_completion.py b/examples/basic_completion.py index c713f2b0d..e2288f4bf 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple example demonstrating how to enable tab completion by assigning a completer function to do_* commands. This also demonstrates capabilities of the following completer features included with cmd2: @@ -14,9 +13,6 @@ """ import functools -from typing import ( - List, -) import cmd2 @@ -44,9 +40,9 @@ def do_flag_based(self, statement: cmd2.Statement): -s, --sport [completes sports] -p, --path [completes local file system paths] """ - self.poutput("Args: {}".format(statement.args)) + self.poutput(f"Args: {statement.args}") - def complete_flag_based(self, text, line, begidx, endidx) -> List[str]: + def complete_flag_based(self, text, line, begidx, endidx) -> list[str]: """Completion function for do_flag_based""" flag_dict = { # Tab complete food items after -f and --food flags in command line @@ -64,9 +60,9 @@ def complete_flag_based(self, text, line, begidx, endidx) -> List[str]: def do_index_based(self, statement: cmd2.Statement): """Tab completes first 3 arguments using index_based_complete""" - self.poutput("Args: {}".format(statement.args)) + self.poutput(f"Args: {statement.args}") - def complete_index_based(self, text, line, begidx, endidx) -> List[str]: + def complete_index_based(self, text, line, begidx, endidx) -> list[str]: """Completion function for do_index_based""" index_dict = { 1: food_item_strs, # Tab complete food items at index 1 in command line @@ -78,16 +74,16 @@ def complete_index_based(self, text, line, begidx, endidx) -> List[str]: def do_delimiter_complete(self, statement: cmd2.Statement): """Tab completes files from a list using delimiter_complete""" - self.poutput("Args: {}".format(statement.args)) + self.poutput(f"Args: {statement.args}") # Use a partialmethod to set arguments to delimiter_complete complete_delimiter_complete = functools.partialmethod(cmd2.Cmd.delimiter_complete, match_against=file_strs, delimiter='/') def do_raise_error(self, statement: cmd2.Statement): """Demonstrates effect of raising CompletionError""" - self.poutput("Args: {}".format(statement.args)) + self.poutput(f"Args: {statement.args}") - def complete_raise_error(self, text, line, begidx, endidx) -> List[str]: + def complete_raise_error(self, text, line, begidx, endidx) -> list[str]: """ CompletionErrors can be raised if an error occurs while tab completing. diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index 75d53ed73..a9f5b896f 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2. diff --git a/examples/colors.py b/examples/colors.py index 34f16da2c..28b0c6707 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2. Demonstrating colorized output. diff --git a/examples/custom_parser.py b/examples/custom_parser.py index 94df3b054..fd74f5b56 100644 --- a/examples/custom_parser.py +++ b/examples/custom_parser.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Defines the CustomParser used with override_parser.py example """ @@ -35,7 +34,7 @@ def error(self, message: str) -> None: # Format errors with style_warning() formatted_message = ansi.style_warning(formatted_message) - self.exit(2, '{}\n\n'.format(formatted_message)) + self.exit(2, f'{formatted_message}\n\n') # Now set the default parser for a cmd2 app diff --git a/examples/decorator_example.py b/examples/decorator_example.py index ea8fd3b50..4c80436f8 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A sample application showing how to use cmd2's argparse decorators to process command line arguments for your application. @@ -12,9 +11,6 @@ """ import argparse -from typing import ( - List, -) import cmd2 @@ -71,12 +67,12 @@ def do_tag(self, args: argparse.Namespace): # The Namespace always includes the Statement object created when parsing the command line statement = args.cmd2_statement.get() - self.poutput("The command line you ran was: {}".format(statement.command_and_args)) + self.poutput(f"The command line you ran was: {statement.command_and_args}") self.poutput("It generated this tag:") self.poutput('<{0}>{1}'.format(args.tag, ' '.join(args.content))) @cmd2.with_argument_list - def do_tagg(self, arglist: List[str]): + def do_tagg(self, arglist: list[str]): """version of creating an html tag using arglist instead of argparser""" if len(arglist) >= 2: tag = arglist[0] diff --git a/examples/default_categories.py b/examples/default_categories.py index 0fd485ae3..3d531931f 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ diff --git a/examples/dynamic_commands.py b/examples/dynamic_commands.py index 82dde732d..ef5c520b1 100755 --- a/examples/dynamic_commands.py +++ b/examples/dynamic_commands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example demonstrating how do_* commands can be created in a loop.""" import functools @@ -41,7 +40,7 @@ def send_text(self, args: cmd2.Statement, *, text: str): def text_help(self, *, text: str): """Deal with printing help for the dynamically added commands.""" - self.poutput("Simulate sending {!r} to a server and printing the response".format(text)) + self.poutput(f"Simulate sending {text!r} to a server and printing the response") if __name__ == '__main__': diff --git a/examples/environment.py b/examples/environment.py index 1bb9812be..38c0f0bd6 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2 demonstrating customized environment parameters """ @@ -22,7 +21,7 @@ def __init__(self): def do_sunbathe(self, arg): """Attempt to sunbathe.""" if self.degrees_c < 20: - result = "It's {} C - are you a penguin?".format(self.degrees_c) + result = f"It's {self.degrees_c} C - are you a penguin?" elif not self.sunny: result = 'Too dim.' else: diff --git a/examples/event_loops.py b/examples/event_loops.py index e5435181a..8f627577b 100755 --- a/examples/event_loops.py +++ b/examples/event_loops.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A sample application for integrating cmd2 with external event loops. This is an example of how to use cmd2 in a way so that cmd2 doesn't own the inner event loop of your application. diff --git a/examples/example.py b/examples/example.py index 2ff64d747..0ce4d9bb8 100755 --- a/examples/example.py +++ b/examples/example.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2. diff --git a/examples/exit_code.py b/examples/exit_code.py index d8e538ced..ccf4b8f21 100755 --- a/examples/exit_code.py +++ b/examples/exit_code.py @@ -1,11 +1,6 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application.""" -from typing import ( - List, -) - import cmd2 @@ -16,7 +11,7 @@ def __init__(self): super().__init__() @cmd2.with_argument_list - def do_exit(self, arg_list: List[str]) -> bool: + def do_exit(self, arg_list: list[str]) -> bool: """Exit the application with an optional exit code. Usage: exit [exit_code] @@ -27,7 +22,7 @@ def do_exit(self, arg_list: List[str]) -> bool: try: self.exit_code = int(arg_list[0]) except ValueError: - self.perror("{} isn't a valid integer exit code".format(arg_list[0])) + self.perror(f"{arg_list[0]} isn't a valid integer exit code") self.exit_code = 1 return True @@ -38,5 +33,5 @@ def do_exit(self, arg_list: List[str]) -> bool: app = ReplWithExitCode() sys_exit_code = app.cmdloop() - app.poutput('{!r} exiting with code: {}'.format(sys.argv[0], sys_exit_code)) + app.poutput(f'{sys.argv[0]!r} exiting with code: {sys_exit_code}') sys.exit(sys_exit_code) diff --git a/examples/first_app.py b/examples/first_app.py index 57a76f708..f19cdec92 100755 --- a/examples/first_app.py +++ b/examples/first_app.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple application using cmd2 which demonstrates 8 key features: diff --git a/examples/hello_cmd2.py b/examples/hello_cmd2.py index a67205834..76ff55c75 100755 --- a/examples/hello_cmd2.py +++ b/examples/hello_cmd2.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ This is intended to be a completely bare-bones cmd2 application suitable for rapid testing and debugging. """ diff --git a/examples/help_categories.py b/examples/help_categories.py index 5c349422c..4790b915f 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for tagging categories on commands. @@ -162,7 +161,7 @@ def do_version(self, _): @cmd2.with_category("Command Management") def do_disable_commands(self, _): """Disable the Application Management commands""" - message_to_print = "{} is not available while {} commands are disabled".format(COMMAND_NAME, self.CMD_CAT_APP_MGMT) + message_to_print = f"{COMMAND_NAME} is not available while {self.CMD_CAT_APP_MGMT} commands are disabled" self.disable_category(self.CMD_CAT_APP_MGMT, message_to_print) self.poutput("The Application Management commands have been disabled") diff --git a/examples/hooks.py b/examples/hooks.py index 97b90739d..683b75474 100755 --- a/examples/hooks.py +++ b/examples/hooks.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2 demonstrating how to use hooks. @@ -10,9 +9,6 @@ """ import re -from typing import ( - List, -) import cmd2 @@ -102,7 +98,7 @@ def proof_hook(self, data: cmd2.plugin.PostcommandData) -> cmd2.plugin.Postcomma return data @cmd2.with_argument_list - def do_list(self, arglist: List[str]) -> None: + def do_list(self, arglist: list[str]) -> None: """Generate a list of 10 numbers.""" if arglist: first = arglist[0] diff --git a/examples/initialization.py b/examples/initialization.py index 8cdf07341..ea9d88589 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example cmd2 application demonstrating the following: 1) Colorizing/stylizing output 2) Using multiline commands diff --git a/examples/migrating.py b/examples/migrating.py index 199b78db7..590e9ee20 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample cmd application that shows how to trivially migrate a cmd application to use cmd2. """ diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index 8587b98d7..7e17ac024 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -1,12 +1,7 @@ -# coding=utf-8 """ A simple example demonstrating a loadable command set """ -from typing import ( - List, -) - from cmd2 import ( CommandSet, CompletionError, @@ -37,9 +32,9 @@ def do_flag_based(self, statement: Statement) -> None: -s, --sport [completes sports] -p, --path [completes local file system paths] """ - self._cmd.poutput("Args: {}".format(statement.args)) + self._cmd.poutput(f"Args: {statement.args}") - def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """Completion function for do_flag_based""" flag_dict = { # Tab complete food items after -f and --food flags in command line @@ -57,9 +52,9 @@ def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> def do_index_based(self, statement: Statement) -> None: """Tab completes first 3 arguments using index_based_complete""" - self._cmd.poutput("Args: {}".format(statement.args)) + self._cmd.poutput(f"Args: {statement.args}") - def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """Completion function for do_index_based""" index_dict = { 1: self.food_item_strs, # Tab complete food items at index 1 in command line @@ -71,16 +66,16 @@ def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) - def do_delimiter_complete(self, statement: Statement) -> None: """Tab completes files from a list using delimiter_complete""" - self._cmd.poutput("Args: {}".format(statement.args)) + self._cmd.poutput(f"Args: {statement.args}") - def complete_delimiter_complete(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_delimiter_complete(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return self._cmd.delimiter_complete(text, line, begidx, endidx, match_against=self.file_strs, delimiter='/') def do_raise_error(self, statement: Statement) -> None: """Demonstrates effect of raising CompletionError""" - self._cmd.poutput("Args: {}".format(statement.args)) + self._cmd.poutput(f"Args: {statement.args}") - def complete_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """ CompletionErrors can be raised if an error occurs while tab completing. diff --git a/examples/modular_commands/commandset_custominit.py b/examples/modular_commands/commandset_custominit.py index a3f4f59ad..5ef2beca4 100644 --- a/examples/modular_commands/commandset_custominit.py +++ b/examples/modular_commands/commandset_custominit.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ A simple example demonstrating a loadable command set """ diff --git a/examples/modular_commands_basic.py b/examples/modular_commands_basic.py index 4d5f83cee..37cad3192 100755 --- a/examples/modular_commands_basic.py +++ b/examples/modular_commands_basic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ diff --git a/examples/modular_commands_dynamic.py b/examples/modular_commands_dynamic.py index 8264c068f..b68e6f577 100755 --- a/examples/modular_commands_dynamic.py +++ b/examples/modular_commands_dynamic.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """ Simple example demonstrating dynamic CommandSet loading and unloading. diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 740078671..25138db83 100755 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -1,14 +1,12 @@ #!/usr/bin/env python -# coding=utf-8 """ A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators with examples of how to integrate tab completion with argparse-based commands. """ import argparse +from collections.abc import Iterable from typing import ( - Iterable, - List, Optional, ) @@ -35,7 +33,7 @@ def __init__(self, command_sets: Optional[Iterable[CommandSet]] = None): super().__init__(command_sets=command_sets) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - def choices_provider(self) -> List[str]: + def choices_provider(self) -> list[str]: """A choices provider is useful when the choice list is based on instance data of your application""" return self.sport_item_strs diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index 14d117814..ef340f526 100755 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example demonstrating modular subcommand loading through CommandSets In this example, there are loadable CommandSets defined. Each CommandSet has 1 subcommand defined that will be diff --git a/examples/paged_output.py b/examples/paged_output.py index 0f7173b2e..9aed7e193 100755 --- a/examples/paged_output.py +++ b/examples/paged_output.py @@ -1,11 +1,7 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating the using paged output via the ppaged() method.""" import os -from typing import ( - List, -) import cmd2 @@ -20,14 +16,14 @@ def page_file(self, file_path: str, chop: bool = False): """Helper method to prevent having too much duplicated code.""" filename = os.path.expanduser(file_path) try: - with open(filename, 'r') as f: + with open(filename) as f: text = f.read() self.ppaged(text, chop=chop) except OSError as ex: - self.pexcept('Error reading {!r}: {}'.format(filename, ex)) + self.pexcept(f'Error reading {filename!r}: {ex}') @cmd2.with_argument_list - def do_page_wrap(self, args: List[str]): + def do_page_wrap(self, args: list[str]): """Read in a text file and display its output in a pager, wrapping long lines if they don't fit. Usage: page_wrap @@ -40,7 +36,7 @@ def do_page_wrap(self, args: List[str]): complete_page_wrap = cmd2.Cmd.path_complete @cmd2.with_argument_list - def do_page_truncate(self, args: List[str]): + def do_page_truncate(self, args: list[str]): """Read in a text file and display its output in a pager, truncating long lines if they don't fit. Truncated lines can still be accessed by scrolling to the right using the arrow keys. diff --git a/examples/persistent_history.py b/examples/persistent_history.py index ab4b89f2b..c185370cf 100755 --- a/examples/persistent_history.py +++ b/examples/persistent_history.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """This example demonstrates how to enable persistent readline history in your cmd2 application. This will allow end users of your cmd2-based application to use the arrow keys and Ctrl+r in a manner which persists diff --git a/examples/pirate.py b/examples/pirate.py index 8c443d368..1e59e5fe6 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ This example is adapted from the pirate8.py example created by Catherine Devlin and presented as part of her PyCon 2010 talk. @@ -46,7 +45,7 @@ def precmd(self, line): def postcmd(self, stop, line): """Runs right before a command is about to return.""" if self.gold != self.initial_gold: - self.poutput('Now we gots {0} doubloons'.format(self.gold)) + self.poutput(f'Now we gots {self.gold} doubloons') if self.gold < 0: self.poutput("Off to debtorrr's prison.") self.exit_code = 1 @@ -65,7 +64,7 @@ def do_drink(self, arg): self.gold -= int(arg) except ValueError: if arg: - self.poutput('''What's "{0}"? I'll take rrrum.'''.format(arg)) + self.poutput(f'''What's "{arg}"? I'll take rrrum.''') self.gold -= 1 def do_quit(self, arg): @@ -88,7 +87,7 @@ def do_yo(self, args): chant = ['yo'] + ['ho'] * args.ho separator = ', ' if args.commas else ' ' chant = separator.join(chant) - self.poutput('{0} and a bottle of {1}'.format(chant, args.beverage)) + self.poutput(f'{chant} and a bottle of {args.beverage}') if __name__ == '__main__': @@ -97,5 +96,5 @@ def do_yo(self, args): # Create an instance of the Pirate derived class and enter the REPL with cmdloop(). pirate = Pirate() sys_exit_code = pirate.cmdloop() - print('Exiting with code: {!r}'.format(sys_exit_code)) + print(f'Exiting with code: {sys_exit_code!r}') sys.exit(sys_exit_code) diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 688ef52ce..1943e214e 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A sample application for how Python scripting can provide conditional control flow of a cmd2 application. diff --git a/examples/read_input.py b/examples/read_input.py index bfc43380b..a8404bac1 100755 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -1,13 +1,8 @@ #!/usr/bin/env python -# coding=utf-8 """ A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion """ -from typing import ( - List, -) - import cmd2 EXAMPLE_COMMANDS = "Example Commands" @@ -64,7 +59,7 @@ def do_custom_choices(self, _) -> None: else: self.custom_history.append(input_str) - def choices_provider(self) -> List[str]: + def choices_provider(self) -> list[str]: """Example choices provider function""" return ["from_provider_1", "from_provider_2", "from_provider_3"] diff --git a/examples/remove_builtin_commands.py b/examples/remove_builtin_commands.py index 67541a848..a511a4944 100755 --- a/examples/remove_builtin_commands.py +++ b/examples/remove_builtin_commands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating how to remove unused commands. Commands can be removed from help menu and tab completion by appending their command name to the hidden_commands list. diff --git a/examples/remove_settable.py b/examples/remove_settable.py index fad671cbe..191f033d7 100755 --- a/examples/remove_settable.py +++ b/examples/remove_settable.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """ A sample application for cmd2 demonstrating how to remove one of the built-in runtime settable parameters. """ diff --git a/examples/scripts/arg_printer.py b/examples/scripts/arg_printer.py index 924e269ac..aca0f0031 100755 --- a/examples/scripts/arg_printer.py +++ b/examples/scripts/arg_printer.py @@ -1,8 +1,7 @@ #!/usr/bin/env python -# coding=utf-8 import os import sys -print("Running Python script {!r} which was called with {} arguments".format(os.path.basename(sys.argv[0]), len(sys.argv) - 1)) +print(f"Running Python script {os.path.basename(sys.argv[0])!r} which was called with {len(sys.argv) - 1} arguments") for i, arg in enumerate(sys.argv[1:]): - print("arg {}: {!r}".format(i + 1, arg)) + print(f"arg {i + 1}: {arg!r}") diff --git a/examples/scripts/script.py b/examples/scripts/script.py index 5f6f411e3..a14f7660d 100644 --- a/examples/scripts/script.py +++ b/examples/scripts/script.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Trivial example of a Python script which can be run inside a cmd2 application. """ diff --git a/examples/subcommands.py b/examples/subcommands.py index 455768e38..532b9f0f8 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# coding=utf-8 """A simple example demonstrating how to use Argparse to support subcommands. @@ -82,7 +81,7 @@ def base_bar(self, args): def base_sport(self, args): """sport subcommand of base command""" - self.poutput('Sport is {}'.format(args.sport)) + self.poutput(f'Sport is {args.sport}') # Set handler functions for the subcommands parser_foo.set_defaults(func=base_foo) diff --git a/examples/table_creation.py b/examples/table_creation.py index 0849a0b2c..3f7040ab2 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -1,12 +1,10 @@ #!/usr/bin/env python -# coding=utf-8 """Examples of using the cmd2 table creation API""" import functools import sys from typing import ( Any, - List, ) from cmd2 import ( @@ -37,7 +35,7 @@ def __init__(self, val: float) -> None: def __str__(self) -> str: """Returns the value in dollar currency form (e.g. $100.22)""" - return "${:,.2f}".format(self.val) + return f"${self.val:,.2f}" class Relative: @@ -63,8 +61,8 @@ def __init__(self, name: str, birthday: str, place_of_birth: str) -> None: self.name = name self.birthday = birthday self.place_of_birth = place_of_birth - self.books: List[Book] = [] - self.relatives: List[Relative] = [] + self.books: list[Book] = [] + self.relatives: list[Relative] = [] def ansi_print(text): @@ -76,7 +74,7 @@ def basic_tables(): """Demonstrates basic examples of the table classes""" # Table data which demonstrates handling of wrapping and text styles - data_list: List[List[Any]] = list() + data_list: list[list[Any]] = list() data_list.append(["Billy Smith", "123 Sesame St.\nFake Town, USA 33445", DollarFormatter(100333.03)]) data_list.append( [ @@ -96,7 +94,7 @@ def basic_tables(): data_list.append(["John Jones", "9235 Highway 32\n" + green("Greenville") + ", SC 29604", DollarFormatter(82987.71)]) # Table Columns (width does not account for any borders or padding which may be added) - columns: List[Column] = list() + columns: list[Column] = list() columns.append(Column("Name", width=20)) columns.append(Column("Address", width=38)) columns.append( @@ -123,7 +121,7 @@ def nested_tables(): """ # Create data for this example - author_data: List[Author] = [] + author_data: list[Author] = [] author_1 = Author("Frank Herbert", "10/08/1920", "Tacoma, Washington") author_1.books.append(Book("Dune", "1965")) author_1.books.append(Book("Dune Messiah", "1969")) @@ -159,7 +157,7 @@ def nested_tables(): # Define table which presents Author data fields vertically with no header. # This will be nested in the parent table's first column. - author_columns: List[Column] = list() + author_columns: list[Column] = list() author_columns.append(Column("", width=14)) author_columns.append(Column("", width=20)) @@ -174,7 +172,7 @@ def nested_tables(): # Define AlternatingTable for books checked out by people in the first table. # This will be nested in the parent table's second column. - books_columns: List[Column] = list() + books_columns: list[Column] = list() books_columns.append(Column(ansi.style("Title", bold=True), width=25)) books_columns.append( Column( @@ -196,7 +194,7 @@ def nested_tables(): # Define BorderedTable for relatives of the author # This will be nested in the parent table's third column. - relative_columns: List[Column] = list() + relative_columns: list[Column] = list() relative_columns.append(Column(ansi.style("Name", bold=True), width=25)) relative_columns.append(Column(ansi.style("Relationship", bold=True), width=12)) @@ -220,7 +218,7 @@ def nested_tables(): ) # Define parent AlternatingTable which contains Author and Book tables - parent_tbl_columns: List[Column] = list() + parent_tbl_columns: list[Column] = list() # All of the nested tables already have background colors. Set style_data_text # to False so the parent AlternatingTable does not apply background color to them. @@ -242,7 +240,7 @@ def nested_tables(): ) # Construct the tables - parent_table_data: List[List[Any]] = [] + parent_table_data: list[list[Any]] = [] for row, author in enumerate(author_data, start=1): # First build the author table and color it based on row number author_tbl = even_author_tbl if row % 2 == 0 else odd_author_tbl diff --git a/examples/unicode_commands.py b/examples/unicode_commands.py index 6c76a76e7..ac2cafd39 100755 --- a/examples/unicode_commands.py +++ b/examples/unicode_commands.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 """A simple example demonstrating support for unicode command names.""" import math @@ -16,7 +15,7 @@ def __init__(self): def do_𝛑print(self, _): """This command prints 𝛑 to 5 decimal places.""" - self.poutput("𝛑 = {0:.6}".format(math.pi)) + self.poutput(f"𝛑 = {math.pi:.6}") def do_你好(self, arg): """This command says hello in Chinese (Mandarin).""" diff --git a/plugins/template/cmd2_myplugin/__init__.py b/plugins/template/cmd2_myplugin/__init__.py index a35976b75..e157ee7a4 100644 --- a/plugins/template/cmd2_myplugin/__init__.py +++ b/plugins/template/cmd2_myplugin/__init__.py @@ -1,5 +1,4 @@ # -# coding=utf-8 """Description of myplugin An overview of what myplugin does. diff --git a/plugins/template/cmd2_myplugin/myplugin.py b/plugins/template/cmd2_myplugin/myplugin.py index 8397e3706..7d36e1d1b 100644 --- a/plugins/template/cmd2_myplugin/myplugin.py +++ b/plugins/template/cmd2_myplugin/myplugin.py @@ -1,11 +1,10 @@ # -# coding=utf-8 """An example cmd2 plugin""" import functools +from collections.abc import Callable from typing import ( TYPE_CHECKING, - Callable, ) import cmd2 diff --git a/plugins/template/examples/example.py b/plugins/template/examples/example.py index b071b5f84..997334906 100644 --- a/plugins/template/examples/example.py +++ b/plugins/template/examples/example.py @@ -1,5 +1,4 @@ # -# coding=utf-8 import cmd2_myplugin diff --git a/plugins/template/setup.py b/plugins/template/setup.py index 126f8dde8..59b064d5a 100644 --- a/plugins/template/setup.py +++ b/plugins/template/setup.py @@ -1,5 +1,4 @@ # -# coding=utf-8 import os diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index ca1058c88..6043d62b5 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -1,5 +1,4 @@ # -# -*- coding: utf-8 -*- """Development related tasks to be run with 'invoke'""" import os @@ -20,7 +19,7 @@ def rmrf(items, verbose=True): for item in items: if verbose: - print("Removing {}".format(item)) + print(f"Removing {item}") shutil.rmtree(item, ignore_errors=True) # rmtree doesn't remove bare files try: diff --git a/plugins/template/tests/test_myplugin.py b/plugins/template/tests/test_myplugin.py index 06ca25670..30c42f9f1 100644 --- a/plugins/template/tests/test_myplugin.py +++ b/plugins/template/tests/test_myplugin.py @@ -1,5 +1,4 @@ # -# coding=utf-8 import cmd2_myplugin diff --git a/pyproject.toml b/pyproject.toml index ee5eccab9..e93d09b0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -216,13 +216,14 @@ select = [ "TD", # flake8-todos (force all TODOs to include an author and issue link) "TID", # flake8-tidy-imports (extra import rules to check) # "TRY", # tryceratops (warnings related to exceptions and try/except) - # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) + # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) "W", # pycodestyle warnings (warn about minor stylistic issues) "YTT", # flake8-2020 (checks for misuse of sys.version or sys.version_info) ] ignore = [ # `uv run ruff rule E501` for a description of that rule - "UP007", # Use X | Y for type annotations (requires Python 3.10 or newer) + "UP007", # Use X | Y for type annotations (requires Python 3.10+) + "UP017", # Use datetime.UTC alias (requires Python 3.11+) ] # Allow fix for all enabled rules (when `--fix`) is provided. diff --git a/tests/__init__.py b/tests/__init__.py index 037f3866e..6e9bbe36f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,2 @@ # -# -*- coding: utf-8 -*- # diff --git a/tests/conftest.py b/tests/conftest.py index 644ae7cca..bbd332962 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Cmd2 unit/functional testing """ @@ -10,7 +9,6 @@ redirect_stdout, ) from typing import ( - List, Optional, Union, ) @@ -32,7 +30,7 @@ def verify_help_text( - cmd2_app: cmd2.Cmd, help_output: Union[str, List[str]], verbose_strings: Optional[List[str]] = None + cmd2_app: cmd2.Cmd, help_output: Union[str, list[str]], verbose_strings: Optional[list[str]] = None ) -> None: """This function verifies that all expected commands are present in the help text. @@ -195,7 +193,7 @@ def get_endidx(): return app.complete(text, 0) -def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser: +def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser: if not subcmd_names: return action cur_subcmd = subcmd_names.pop(0) diff --git a/tests/pyscript/raises_exception.py b/tests/pyscript/raises_exception.py index 9979b6692..0df499d9b 100644 --- a/tests/pyscript/raises_exception.py +++ b/tests/pyscript/raises_exception.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Example demonstrating what happens when a Python script raises an exception """ diff --git a/tests/script.py b/tests/script.py index 5f6f411e3..a14f7660d 100644 --- a/tests/script.py +++ b/tests/script.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Trivial example of a Python script which can be run inside a cmd2 application. """ diff --git a/tests_isolated/test_commandset/__init__.py b/tests_isolated/test_commandset/__init__.py index 037f3866e..6e9bbe36f 100644 --- a/tests_isolated/test_commandset/__init__.py +++ b/tests_isolated/test_commandset/__init__.py @@ -1,3 +1,2 @@ # -# -*- coding: utf-8 -*- # diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 23ea7e741..353bf907d 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Cmd2 unit/functional testing """ @@ -9,7 +8,6 @@ redirect_stdout, ) from typing import ( - List, Optional, Union, ) @@ -34,7 +32,7 @@ def verify_help_text( - cmd2_app: cmd2.Cmd, help_output: Union[str, List[str]], verbose_strings: Optional[List[str]] = None + cmd2_app: cmd2.Cmd, help_output: Union[str, list[str]], verbose_strings: Optional[list[str]] = None ) -> None: """This function verifies that all expected commands are present in the help text. diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index 533e02e0c..bdbc4efec 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -1,4 +1,3 @@ -# coding=utf-8 """ Simple example demonstrating basic CommandSet usage. """ From 7cad725bf8013c315583a72f7bc6edfa5cd19cd7 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 18:57:13 -0400 Subject: [PATCH 22/79] Revert "Enable ruff TC type checking ruleset and accept automated refactorings" This reverts commit 374a885bc16459a013ff6b0786ba3ac1f4d3c5c0. --- cmd2/ansi.py | 2 +- cmd2/clipboard.py | 2 +- cmd2/py_bridge.py | 10 +++++----- cmd2/utils.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index abea54903..20221599a 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -104,7 +104,7 @@ def style_aware_wcswidth(text: str) -> int: then this function returns -1. Replace tabs with spaces before calling this. """ # Strip ANSI style sequences since they cause wcswidth to return -1 - return cast('int', wcswidth(strip_style(text))) + return cast(int, wcswidth(strip_style(text))) def widest_line(text: str) -> int: diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index b59e09dc9..45a44cbb1 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -12,7 +12,7 @@ def get_paste_buffer() -> str: :return: contents of the clipboard """ - pb_str = typing.cast("str", pyperclip.paste()) + pb_str = typing.cast(str, pyperclip.paste()) return pb_str diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 0936a74c6..a0476ca1c 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -115,7 +115,7 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul echo = self.cmd_echo # This will be used to capture _cmd2_app.stdout and sys.stdout - copy_cmd_stdout = StdSim(cast('Union[TextIO, StdSim]', self._cmd2_app.stdout), echo=echo) + copy_cmd_stdout = StdSim(cast(Union[TextIO, StdSim], self._cmd2_app.stdout), echo=echo) # Pause the storing of stdout until onecmd_plus_hooks enables it copy_cmd_stdout.pause_storage = True @@ -127,9 +127,9 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul stop = False try: - self._cmd2_app.stdout = cast('TextIO', copy_cmd_stdout) - with redirect_stdout(cast('IO[str]', copy_cmd_stdout)): - with redirect_stderr(cast('IO[str]', copy_stderr)): + self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout) + with redirect_stdout(cast(IO[str], copy_cmd_stdout)): + with redirect_stderr(cast(IO[str], copy_stderr)): stop = self._cmd2_app.onecmd_plus_hooks( command, add_to_history=self._add_to_history, @@ -137,7 +137,7 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul ) finally: with self._cmd2_app.sigint_protection: - self._cmd2_app.stdout = cast('IO[str]', copy_cmd_stdout.inner_stream) + self._cmd2_app.stdout = cast(IO[str], copy_cmd_stdout.inner_stream) self.stop = stop or self.stop # Save the result diff --git a/cmd2/utils.py b/cmd2/utils.py index 6504e0525..611c81c60 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -159,7 +159,7 @@ def get_bool_choices(_) -> list[str]: # type: ignore[no-untyped-def] return ['true', 'false'] val_type = to_bool - choices_provider = cast('ChoicesProviderFunc', get_bool_choices) + choices_provider = cast(ChoicesProviderFunc, get_bool_choices) self.name = name self.val_type = val_type @@ -1189,7 +1189,7 @@ def get_defining_class(meth: Callable[..., Any]) -> Optional[type[Any]]: cls = getattr(inspect.getmodule(meth), meth.__qualname__.split('.', 1)[0].rsplit('.', 1)[0]) if isinstance(cls, type): return cls - return cast('type', getattr(meth, '__objclass__', None)) # handle special descriptor objects + return cast(type, getattr(meth, '__objclass__', None)) # handle special descriptor objects class CompletionMode(Enum): From 5490ed6f1d7fe745b291fae4481c8d9dd412d816 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 19:13:38 -0400 Subject: [PATCH 23/79] Finish reverting ruff rule TC006 changes and added that to the ignore list This rule forced every use of typing inside a cast to be quoted. In reality, it is only forward reference types which needed this. --- cmd2/argparse_custom.py | 10 ++++----- cmd2/cmd2.py | 50 ++++++++++++++++++++--------------------- cmd2/transcript.py | 4 ++-- pyproject.toml | 1 + 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index e4a6e8331..db18db462 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -447,7 +447,7 @@ def _action_get_choices_callable(self: argparse.Action) -> Optional[ChoicesCalla :param self: argparse Action being queried :return: A ChoicesCallable instance or None if attribute does not exist """ - return cast("Optional[ChoicesCallable]", getattr(self, ATTR_CHOICES_CALLABLE, None)) + return cast(Optional[ChoicesCallable], getattr(self, ATTR_CHOICES_CALLABLE, None)) setattr(argparse.Action, 'get_choices_callable', _action_get_choices_callable) @@ -537,7 +537,7 @@ def _action_get_descriptive_header(self: argparse.Action) -> Optional[str]: :param self: argparse Action being queried :return: The value of descriptive_header or None if attribute does not exist """ - return cast("Optional[str]", getattr(self, ATTR_DESCRIPTIVE_HEADER, None)) + return cast(Optional[str], getattr(self, ATTR_DESCRIPTIVE_HEADER, None)) setattr(argparse.Action, 'get_descriptive_header', _action_get_descriptive_header) @@ -574,7 +574,7 @@ def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[ :param self: argparse Action being queried :return: The value of nargs_range or None if attribute does not exist """ - return cast("Optional[tuple[int, Union[int, float]]]", getattr(self, ATTR_NARGS_RANGE, None)) + return cast(Optional[tuple[int, Union[int, float]]], getattr(self, ATTR_NARGS_RANGE, None)) setattr(argparse.Action, 'get_nargs_range', _action_get_nargs_range) @@ -611,7 +611,7 @@ def _action_get_suppress_tab_hint(self: argparse.Action) -> bool: :param self: argparse Action being queried :return: The value of suppress_tab_hint or False if attribute does not exist """ - return cast("bool", getattr(self, ATTR_SUPPRESS_TAB_HINT, False)) + return cast(bool, getattr(self, ATTR_SUPPRESS_TAB_HINT, False)) setattr(argparse.Action, 'get_suppress_tab_hint', _action_get_suppress_tab_hint) @@ -921,7 +921,7 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti :param self: ArgumentParser being queried :return: An ArgparseCompleter-based class or None if attribute does not exist """ - return cast("Optional[type[ArgparseCompleter]]", getattr(self, ATTR_AP_COMPLETER_TYPE, None)) + return cast(Optional[type['ArgparseCompleter']], getattr(self, ATTR_AP_COMPLETER_TYPE, None)) setattr(argparse.ArgumentParser, 'get_ap_completer_type', _ArgumentParser_get_ap_completer_type) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index e6259a0cb..13b60e61f 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -175,7 +175,7 @@ ) rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters") - orig_rl_basic_quotes = cast("bytes", ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value) + orig_rl_basic_quotes = cast(bytes, ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value) class _SavedReadlineSettings: @@ -707,7 +707,7 @@ def register_command_set(self, cmdset: CommandSet) -> None: cmdset.on_register(self) methods = cast( - "list[tuple[str, Callable[..., Any]]]", + list[tuple[str, Callable[..., Any]]], inspect.getmembers( cmdset, predicate=lambda meth: isinstance(meth, Callable) # type: ignore[arg-type] @@ -970,7 +970,7 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> target_parser = find_subcommand(command_parser, subcommand_names) - subcmd_parser = cast("argparse.ArgumentParser", self._build_parser(cmdset, subcmd_parser_builder)) + subcmd_parser = cast(argparse.ArgumentParser, self._build_parser(cmdset, subcmd_parser_builder)) from .decorators import ( _set_parser_prog, ) @@ -1153,7 +1153,7 @@ def allow_style_type(value: str) -> ansi.AllowStyle: 'Allow ANSI text style sequences in output (valid values: ' f'{ansi.AllowStyle.ALWAYS}, {ansi.AllowStyle.NEVER}, {ansi.AllowStyle.TERMINAL})', self, - choices_provider=cast("ChoicesProviderFunc", get_allow_style_choices), + choices_provider=cast(ChoicesProviderFunc, get_allow_style_choices), ) ) @@ -1945,7 +1945,7 @@ def _display_matches_gnu_readline( # rl_display_match_list() expects matches to be in argv format where # substitution is the first element, followed by the matches, and then a NULL. - strings_array = cast("list[Optional[bytes]]", (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) + strings_array = cast(list[Optional[bytes]], (ctypes.c_char_p * (1 + len(encoded_matches) + 1))()) # Copy in the encoded strings and add a NULL to the end strings_array[0] = encoded_substitution @@ -2868,7 +2868,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: # Initialize the redirection saved state redir_saved_state = utils.RedirectionSavedState( - cast("TextIO", self.stdout), sys.stdout, self._cur_pipe_proc_reader, self._redirecting + cast(TextIO, self.stdout), sys.stdout, self._cur_pipe_proc_reader, self._redirecting ) # The ProcReader for this command @@ -2884,7 +2884,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: # Open each side of the pipe subproc_stdin = open(read_fd) - new_stdout: TextIO = cast("TextIO", open(write_fd, 'w')) + new_stdout: TextIO = cast(TextIO, open(write_fd, 'w')) # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs, # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe @@ -2925,7 +2925,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: new_stdout.close() raise RedirectionError(f'Pipe process exited with code {proc.returncode} before command could run') redir_saved_state.redirecting = True # type: ignore[unreachable] - cmd_pipe_proc_reader = utils.ProcReader(proc, cast("TextIO", self.stdout), sys.stderr) + cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr) sys.stdout = self.stdout = new_stdout elif statement.output: @@ -2935,7 +2935,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: mode = 'a' if statement.output == constants.REDIRECTION_APPEND else 'w' try: # Use line buffering - new_stdout = cast("TextIO", open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1)) + new_stdout = cast(TextIO, open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1)) except OSError as ex: raise RedirectionError(f'Failed to redirect because: {ex}') @@ -2956,7 +2956,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: # no point opening up the temporary file current_paste_buffer = get_paste_buffer() # create a temporary file to store output - new_stdout = cast("TextIO", tempfile.TemporaryFile(mode="w+")) + new_stdout = cast(TextIO, tempfile.TemporaryFile(mode="w+")) redir_saved_state.redirecting = True sys.stdout = self.stdout = new_stdout @@ -2989,8 +2989,8 @@ def _restore_output(self, statement: Statement, saved_redir_state: utils.Redirec pass # Restore the stdout values - self.stdout = cast("TextIO", saved_redir_state.saved_self_stdout) - sys.stdout = cast("TextIO", saved_redir_state.saved_sys_stdout) + self.stdout = cast(TextIO, saved_redir_state.saved_self_stdout) + sys.stdout = cast(TextIO, saved_redir_state.saved_sys_stdout) # Check if we need to wait for the process being piped to if self._cur_pipe_proc_reader is not None: @@ -3016,7 +3016,7 @@ def cmd_func(self, command: str) -> Optional[CommandFunc]: """ func_name = constants.COMMAND_FUNC_PREFIX + command func = getattr(self, func_name, None) - return cast("CommandFunc", func) if callable(func) else None + return cast(CommandFunc, func) if callable(func) else None def onecmd(self, statement: Union[Statement, str], *, add_to_history: bool = True) -> bool: """This executes the actual do_* method for a command. @@ -3276,7 +3276,7 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings: # We don't want this behavior since cmd2 only adds a closing quote when self.allow_closing_quote is True. # To fix this behavior, set readline's rl_basic_quote_characters to NULL. We don't need to worry about setting # rl_completion_suppress_quote since we never declared rl_completer_quote_characters. - readline_settings.basic_quotes = cast("bytes", ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value) + readline_settings.basic_quotes = cast(bytes, ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value) rl_basic_quote_characters.value = None readline_settings.completer = readline.get_completer() @@ -3941,7 +3941,7 @@ def _build_command_info(self) -> tuple[dict[str, list[str]], list[str], list[str cmds_undoc: list[str] = [] cmds_cats: dict[str, list[str]] = {} for command in visible_commands: - func = cast("CommandFunc", self.cmd_func(command)) + func = cast(CommandFunc, self.cmd_func(command)) has_help_func = False has_parser = func in self._command_parsers @@ -4011,7 +4011,7 @@ def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None: stdout_orig = self.stdout try: # redirect our internal stdout - self.stdout = cast("TextIO", result) + self.stdout = cast(TextIO, result) help_func() finally: # restore internal stdout @@ -4078,7 +4078,7 @@ def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], p the text advertised to the user""" local_opts: Union[list[str], list[tuple[Any, Optional[str]]]] if isinstance(opts, str): - local_opts = cast("list[tuple[Any, Optional[str]]]", list(zip(opts.split(), opts.split()))) + local_opts = cast(list[tuple[Any, Optional[str]]], list(zip(opts.split(), opts.split()))) else: local_opts = opts fulloptions: list[tuple[Any, Optional[str]]] = [] @@ -4278,7 +4278,7 @@ def do_shell(self, args: argparse.Namespace) -> None: **kwargs, ) - proc_reader = utils.ProcReader(proc, cast("TextIO", self.stdout), sys.stderr) # type: ignore[arg-type] + proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr) # type: ignore[arg-type] proc_reader.wait() # Save the return code of the application for use in a pyscript @@ -4337,7 +4337,7 @@ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env: # rlcompleter relies on the default settings of the Python readline module if rl_type == RlType.GNU: cmd2_env.readline_settings.basic_quotes = cast( - "bytes", ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value + bytes, ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value ) rl_basic_quote_characters.value = orig_rl_basic_quotes @@ -4986,8 +4986,8 @@ def _generate_transcript( transcript += command # Use a StdSim object to capture output - stdsim = utils.StdSim(cast("TextIO", self.stdout)) - self.stdout = cast("TextIO", stdsim) + stdsim = utils.StdSim(cast(TextIO, self.stdout)) + self.stdout = cast(TextIO, stdsim) # then run the command and let the output go into our buffer try: @@ -5012,7 +5012,7 @@ def _generate_transcript( with self.sigint_protection: # Restore altered attributes to their original state self.echo = saved_echo - self.stdout = cast("TextIO", saved_stdout) + self.stdout = cast(TextIO, saved_stdout) # Check if all commands ran if commands_run < len(history): @@ -5226,7 +5226,7 @@ class TestMyAppCase(Cmd2TestCase): setattr(self.__class__, 'testfiles', transcripts_expanded) sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main() testcase = TestMyAppCase() - stream = cast("TextIO", utils.StdSim(sys.stderr)) + stream = cast(TextIO, utils.StdSim(sys.stderr)) runner = unittest.TextTestRunner(stream=stream) start_time = time.time() test_results = runner.run(testcase) @@ -5615,7 +5615,7 @@ def register_postloop_hook(self, func: Callable[[], None]) -> None: @classmethod def _validate_postparsing_callable(cls, func: Callable[[plugin.PostparsingData], plugin.PostparsingData]) -> None: """Check parameter and return types for postparsing hooks""" - cls._validate_callable_param_count(cast("Callable[..., Any]", func), 1) + cls._validate_callable_param_count(cast(Callable[..., Any], func), 1) signature = inspect.signature(func) _, param = list(signature.parameters.items())[0] if param.annotation != plugin.PostparsingData: @@ -5637,7 +5637,7 @@ def _validate_prepostcmd_hook( """Check parameter and return types for pre and post command hooks.""" signature = inspect.signature(func) # validate that the callable has the right number of parameters - cls._validate_callable_param_count(cast("Callable[..., Any]", func), 1) + cls._validate_callable_param_count(cast(Callable[..., Any], func), 1) # validate the parameter has the right annotation paramname = list(signature.parameters.keys())[0] param = signature.parameters[paramname] diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 7a08480f2..1d453b2c8 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -48,7 +48,7 @@ def setUp(self) -> None: # Trap stdout self._orig_stdout = self.cmdapp.stdout - self.cmdapp.stdout = cast('TextIO', utils.StdSim(cast('TextIO', self.cmdapp.stdout))) + self.cmdapp.stdout = cast(TextIO, utils.StdSim(cast(TextIO, self.cmdapp.stdout))) def tearDown(self) -> None: if self.cmdapp: @@ -63,7 +63,7 @@ def runTest(self) -> None: # was testall def _fetchTranscripts(self) -> None: self.transcripts = {} - testfiles = cast('list[str]', getattr(self.cmdapp, 'testfiles', [])) + testfiles = cast(list[str], getattr(self.cmdapp, 'testfiles', [])) for fname in testfiles: tfile = open(fname) self.transcripts[fname] = iter(tfile.readlines()) diff --git a/pyproject.toml b/pyproject.toml index e93d09b0c..ac1d7b0f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,6 +222,7 @@ select = [ ] ignore = [ # `uv run ruff rule E501` for a description of that rule + "TC006", # Add quotes to type expression in typing.cast() (not needed except for forward references) "UP007", # Use X | Y for type annotations (requires Python 3.10+) "UP017", # Use datetime.UTC alias (requires Python 3.11+) ] From 0c35da805fc82c7ce2e314737181147a9dad1515 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 19:28:59 -0400 Subject: [PATCH 24/79] Enabled ruff PERF ruleset for performance checking Also: - Converted a couple loops to list or dictionary comprehensions for performance - Added a "make typecheck" command for running mypy by itself --- Makefile | 4 ++++ cmd2/argparse_custom.py | 5 +---- cmd2/parsing.py | 3 +-- pyproject.toml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index e97f86035..52de0b91d 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,10 @@ format: ## Perform ruff formatting lint: ## Perform ruff linting @uv run ruff check --fix +.PHONY: typecheck +typecheck: ## Perform type checking + @uv run mypy + .PHONY: test test: ## Test the code with pytest. @echo "🚀 Testing code: Running pytest" diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index db18db462..4797bc94b 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -812,10 +812,7 @@ def _add_argument_wrapper( kwargs['nargs'] = nargs_adjusted # Extract registered custom keyword arguments - custom_attribs: dict[str, Any] = {} - for keyword, value in kwargs.items(): - if keyword in CUSTOM_ACTION_ATTRIBS: - custom_attribs[keyword] = value + custom_attribs = {keyword: value for keyword, value in kwargs.items() if keyword in CUSTOM_ACTION_ATTRIBS} for keyword in custom_attribs: del kwargs[keyword] diff --git a/cmd2/parsing.py b/cmd2/parsing.py index bd115585b..9089a4fea 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -214,8 +214,7 @@ def argv(self) -> list[str]: """ if self.command: rtn = [utils.strip_quotes(self.command)] - for cur_token in self.arg_list: - rtn.append(utils.strip_quotes(cur_token)) + rtn.extend(utils.strip_quotes(cur_token) for cur_token in self.arg_list) else: rtn = [] diff --git a/pyproject.toml b/pyproject.toml index ac1d7b0f4..2d1d3a615 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -190,9 +190,9 @@ select = [ "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) "LOG", # flake8-logging (warn about potential logger issues, but very pedantic) # "N", # pep8-naming (force idiomatic naming for classes, functions/methods, and variables/arguments) - "NPY", # NumPy specific rules - "PD", # pandas-vet (Pandas specific rules) - # "PERF", # Perflint (warn about performance issues) + "NPY", # NumPy specific rules + "PD", # pandas-vet (Pandas specific rules) + "PERF", # Perflint (warn about performance issues) # "PGH", # pygrep-hooks (force specific rule codes when ignoring type or linter issues on a line) # "PIE", # flake8-pie (eliminate unnecessary use of pass, range starting at 0, etc.) "PLC", # Pylint Conventions From 336502aeb2b1e2da0b2ad177ff63d9a20009836b Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 19:39:21 -0400 Subject: [PATCH 25/79] Enabled ruff C4 ruleset and applied fixes --- cmd2/argparse_custom.py | 6 +++--- cmd2/cmd2.py | 12 ++++++------ cmd2/decorators.py | 2 +- cmd2/table_creator.py | 2 +- cmd2/utils.py | 2 +- examples/table_creation.py | 12 ++++++------ pyproject.toml | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 4797bc94b..ec7815fc1 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1031,15 +1031,15 @@ def _format_usage( # if usage is specified, use that if usage is not None: - usage %= dict(prog=self._prog) + usage %= {"prog": self._prog} # if no optionals or positionals are available, usage is just prog elif not actions: - usage = '%(prog)s' % dict(prog=self._prog) + usage = '%(prog)s' % {"prog": self._prog} # if optionals and positionals are available, calculate usage else: - prog = '%(prog)s' % dict(prog=self._prog) + prog = '%(prog)s' % {"prog": self._prog} # split optionals from positionals optionals = [] diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 13b60e61f..d70264b8f 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -411,7 +411,7 @@ def __init__( self.max_completion_items = 50 # A dictionary mapping settable names to their Settable instance - self._settables: dict[str, Settable] = dict() + self._settables: dict[str, Settable] = {} self._always_prefix_settables: bool = False # CommandSet containers @@ -438,7 +438,7 @@ def __init__( self.exclude_from_history = ['eof', 'history'] # Dictionary of macro names and their values - self.macros: dict[str, Macro] = dict() + self.macros: dict[str, Macro] = {} # Keeps track of typed command history in the Python shell self._py_history: list[str] = [] @@ -447,7 +447,7 @@ def __init__( self.py_bridge_name = 'app' # Defines app-specific variables/functions available in Python shells and pyscripts - self.py_locals: dict[str, Any] = dict() + self.py_locals: dict[str, Any] = {} # True if running inside a Python shell or pyscript, False otherwise self._in_py = False @@ -546,7 +546,7 @@ def __init__( # Commands that have been disabled from use. This is to support commands that are only available # during specific states of the application. This dictionary's keys are the command names and its # values are DisabledCommand objects. - self.disabled_commands: dict[str, DisabledCommand] = dict() + self.disabled_commands: dict[str, DisabledCommand] = {} # If any command has been categorized, then all other commands that haven't been categorized # will display under this section in the help output. @@ -2889,7 +2889,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: # Create pipe process in a separate group to isolate our signals from it. If a Ctrl-C event occurs, # our sigint handler will forward it only to the most recent pipe process. This makes sure pipe # processes close in the right order (most recent first). - kwargs: dict[str, Any] = dict() + kwargs: dict[str, Any] = {} if sys.platform == 'win32': kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP else: @@ -4238,7 +4238,7 @@ def do_shell(self, args: argparse.Namespace) -> None: import signal import subprocess - kwargs: dict[str, Any] = dict() + kwargs: dict[str, Any] = {} # Set OS-specific parameters if sys.platform.startswith('win'): diff --git a/cmd2/decorators.py b/cmd2/decorators.py index dcdfb8f1f..29d3a5876 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -437,7 +437,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> ArgparseCommandFu setattr(func, constants.SUBCMD_ATTR_NAME, subcommand) # Keyword arguments for subparsers.add_parser() - add_parser_kwargs: dict[str, Any] = dict() + add_parser_kwargs: dict[str, Any] = {} if help is not None: add_parser_kwargs['help'] = help if aliases: diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index ae3534e52..04b44adef 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -476,7 +476,7 @@ def __init__(self) -> None: total_lines = 0 # Generate the cells for this row - cells = list() + cells = [] for col_index, col in enumerate(self.cols): cell = Cell() diff --git a/cmd2/utils.py b/cmd2/utils.py index 611c81c60..a117491a9 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -757,7 +757,7 @@ class StyleState: def __init__(self) -> None: # Contains styles still in effect, keyed by their index in styles_to_parse - self.style_dict: dict[int, str] = dict() + self.style_dict: dict[int, str] = {} # Indexes into style_dict self.reset_all: Optional[int] = None diff --git a/examples/table_creation.py b/examples/table_creation.py index 3f7040ab2..aea30c459 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -74,7 +74,7 @@ def basic_tables(): """Demonstrates basic examples of the table classes""" # Table data which demonstrates handling of wrapping and text styles - data_list: list[list[Any]] = list() + data_list: list[list[Any]] = [] data_list.append(["Billy Smith", "123 Sesame St.\nFake Town, USA 33445", DollarFormatter(100333.03)]) data_list.append( [ @@ -94,7 +94,7 @@ def basic_tables(): data_list.append(["John Jones", "9235 Highway 32\n" + green("Greenville") + ", SC 29604", DollarFormatter(82987.71)]) # Table Columns (width does not account for any borders or padding which may be added) - columns: list[Column] = list() + columns: list[Column] = [] columns.append(Column("Name", width=20)) columns.append(Column("Address", width=38)) columns.append( @@ -157,7 +157,7 @@ def nested_tables(): # Define table which presents Author data fields vertically with no header. # This will be nested in the parent table's first column. - author_columns: list[Column] = list() + author_columns: list[Column] = [] author_columns.append(Column("", width=14)) author_columns.append(Column("", width=20)) @@ -172,7 +172,7 @@ def nested_tables(): # Define AlternatingTable for books checked out by people in the first table. # This will be nested in the parent table's second column. - books_columns: list[Column] = list() + books_columns: list[Column] = [] books_columns.append(Column(ansi.style("Title", bold=True), width=25)) books_columns.append( Column( @@ -194,7 +194,7 @@ def nested_tables(): # Define BorderedTable for relatives of the author # This will be nested in the parent table's third column. - relative_columns: list[Column] = list() + relative_columns: list[Column] = [] relative_columns.append(Column(ansi.style("Name", bold=True), width=25)) relative_columns.append(Column(ansi.style("Relationship", bold=True), width=12)) @@ -218,7 +218,7 @@ def nested_tables(): ) # Define parent AlternatingTable which contains Author and Book tables - parent_tbl_columns: list[Column] = list() + parent_tbl_columns: list[Column] = [] # All of the nested tables already have background colors. Set style_data_text # to False so the parent AlternatingTable does not apply background color to them. diff --git a/pyproject.toml b/pyproject.toml index 2d1d3a615..f0d636c2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,7 +163,7 @@ select = [ "ASYNC", # flake8-async (async await bugs) # "B", # flake8-bugbear (various likely bugs and design issues) # "BLE", # flake8-blind-except (force more specific exception types than just Exception) - # "C4", # flake8-comprehensions (warn about things that could be written as a comprehensions but aren't) + "C4", # flake8-comprehensions (warn about things that could be written as a comprehensions but aren't) "C90", # McCabe cyclomatic complexity (warn about functions that are too complex) # "COM", # flake8-commas (forces commas at the end of every type of iterable/container # "CPY", # flake8-copyright (warn about missing copyright notice at top of file - currently in preview) From d82f21592e690e8457a85f3eb566591d81d2a50f Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 19:46:29 -0400 Subject: [PATCH 26/79] Add ignore all ruff lint rules that can interfere with ruff format --- pyproject.toml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f0d636c2f..f81e88785 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,9 +222,22 @@ select = [ ] ignore = [ # `uv run ruff rule E501` for a description of that rule - "TC006", # Add quotes to type expression in typing.cast() (not needed except for forward references) - "UP007", # Use X | Y for type annotations (requires Python 3.10+) - "UP017", # Use datetime.UTC alias (requires Python 3.11+) + "COM812", # Conflicts with ruff format (see https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules) + "COM819", # Conflicts with ruff format + "D206", # Conflicts with ruff format + "D300", # Conflicts with ruff format + "E111", # Conflicts with ruff format + "E114", # Conflicts with ruff format + "E117", # Conflicts with ruff format + "ISC002", # Conflicts with ruff format + "Q000", # Conflicts with ruff format + "Q001", # Conflicts with ruff format + "Q002", # Conflicts with ruff format + "Q003", # Conflicts with ruff format + "TC006", # Add quotes to type expression in typing.cast() (not needed except for forward references) + "UP007", # Use X | Y for type annotations (requires Python 3.10+) + "UP017", # Use datetime.UTC alias (requires Python 3.11+) + "W191", # Conflicts with ruff format ] # Allow fix for all enabled rules (when `--fix`) is provided. From 97f921d175a085170c4e8be4549a434224b940ce Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 20:27:24 -0400 Subject: [PATCH 27/79] Enabled ruff A ruleset for finding symbols shadowing built-in functions Enabled an excemption for decorators.py since the as_subcommand_to dectorator has a `help` argument shadowing the python built-in help function, but I didn't want to change the name due to an impact on existing code. --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f81e88785..0b1a2a1ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,7 +156,7 @@ output-format = "full" # McCabe complexity (`C901`) by default. select = [ # https://docs.astral.sh/ruff/rules - # "A", # flake8-builtins (variables or arguments shadowing built-ins) + "A", # flake8-builtins (variables or arguments shadowing built-ins) "AIR", # Airflow specific warnings # "ANN", # flake8-annotations (missing type annotations for arguments or return types) # "ARG", # flake8-unused-arguments (functions or methods with arguments that are never used) @@ -258,6 +258,10 @@ per-file-ignores."cmd2/cmd2.py" = [ "PYI024", # Use `typing.NamedTuple` instead of `collections.namedtuple` ] +per-file-ignores."cmd2/decorators.py" = [ + "A002", # `as_subcommand_to` function argument `help` is shadowing a Python builtin +] + per-file-ignores."docs/conf.py" = [ "F401", # Unused import ] From 96731e887ff6eacf3e944f42ed5cd48680ef301a Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 20:29:34 -0400 Subject: [PATCH 28/79] Moved per-file exemption for A002 to per-line --- cmd2/decorators.py | 2 +- pyproject.toml | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 29d3a5876..fc0d34601 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -414,7 +414,7 @@ def as_subcommand_to( Callable[[CommandParentType], argparse.ArgumentParser], # Cmd or CommandSet classmethod ], *, - help: Optional[str] = None, + help: Optional[str] = None, # noqa: A002 aliases: Optional[list[str]] = None, ) -> Callable[[ArgparseCommandFunc[CommandParent]], ArgparseCommandFunc[CommandParent]]: """ diff --git a/pyproject.toml b/pyproject.toml index 0b1a2a1ba..a52824c50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -258,10 +258,6 @@ per-file-ignores."cmd2/cmd2.py" = [ "PYI024", # Use `typing.NamedTuple` instead of `collections.namedtuple` ] -per-file-ignores."cmd2/decorators.py" = [ - "A002", # `as_subcommand_to` function argument `help` is shadowing a Python builtin -] - per-file-ignores."docs/conf.py" = [ "F401", # Unused import ] From b82c8e9b32202060284e03f91a8a12b8aa012b40 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 20:33:27 -0400 Subject: [PATCH 29/79] Eliminated a couple per-file ruff lint exemptions and replaced one with a single line exemption --- cmd2/cmd2.py | 2 +- pyproject.toml | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index d70264b8f..ae2f13899 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -199,7 +199,7 @@ def __init__(self) -> None: # Contains data about a disabled command which is used to restore its original functions when the command is enabled -DisabledCommand = namedtuple('DisabledCommand', ['command_function', 'help_function', 'completer_function']) +DisabledCommand = namedtuple('DisabledCommand', ['command_function', 'help_function', 'completer_function']) # noqa: PYI024 if TYPE_CHECKING: # pragma: no cover diff --git a/pyproject.toml b/pyproject.toml index a52824c50..d6aae04c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -254,14 +254,6 @@ per-file-ignores."cmd2/__init__.py" = [ "F401", # Unused import ] -per-file-ignores."cmd2/cmd2.py" = [ - "PYI024", # Use `typing.NamedTuple` instead of `collections.namedtuple` -] - -per-file-ignores."docs/conf.py" = [ - "F401", # Unused import -] - per-file-ignores."examples/override_parser.py" = [ "E402", # Module level import not at top of file ] From 27d1ed9fbb98fd179a78b1dbe5ceac6a27f857af Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 20:37:47 -0400 Subject: [PATCH 30/79] Enabled ruff COM ruleset for forcing commas at the end of container lists --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d6aae04c1..e38fcb4d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -165,7 +165,7 @@ select = [ # "BLE", # flake8-blind-except (force more specific exception types than just Exception) "C4", # flake8-comprehensions (warn about things that could be written as a comprehensions but aren't) "C90", # McCabe cyclomatic complexity (warn about functions that are too complex) - # "COM", # flake8-commas (forces commas at the end of every type of iterable/container + "COM", # flake8-commas (forces commas at the end of every type of iterable/container # "CPY", # flake8-copyright (warn about missing copyright notice at top of file - currently in preview) # "D", # pydocstyle (warn about things like missing docstrings) # "DOC", # pydoclint (docstring warnings - currently in preview) From 7f0a47f88e9b1ee1398717bfd8cd2a726d45cb90 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 20:47:38 -0400 Subject: [PATCH 31/79] Enabled ruff INP ruleset for warning about modules in an implicit namespace Exempted some stuff in examples/, plugins/, and tests/ --- pyproject.toml | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e38fcb4d1..4008f227a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -185,10 +185,10 @@ select = [ "G", # flake8-logging-format (warn about logging statements using outdated string formatting methods) "I", # isort (sort all import statements in the order established by isort) "ICN", # flake8-import-conventions (force idiomatic import conventions for certain modules typically imported as something else) - # "INP", # flake8-no-pep420 (warn about files in the implicit namespace - i.e. force creation of __init__.py files to make packages) - "INT", # flake8-gettext (warnings that only apply when you are internationalizing your strings) - "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) - "LOG", # flake8-logging (warn about potential logger issues, but very pedantic) + "INP", # flake8-no-pep420 (warn about files in the implicit namespace - i.e. force creation of __init__.py files to make packages) + "INT", # flake8-gettext (warnings that only apply when you are internationalizing your strings) + "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) + "LOG", # flake8-logging (warn about potential logger issues, but very pedantic) # "N", # pep8-naming (force idiomatic naming for classes, functions/methods, and variables/arguments) "NPY", # NumPy specific rules "PD", # pandas-vet (Pandas specific rules) @@ -254,6 +254,10 @@ per-file-ignores."cmd2/__init__.py" = [ "F401", # Unused import ] +per-file-ignores."examples/*.py" = [ + "INP001", # Module is part of an implicit namespace +] + per-file-ignores."examples/override_parser.py" = [ "E402", # Module level import not at top of file ] @@ -266,10 +270,23 @@ per-file-ignores."examples/unicode_commands.py" = [ "PLC2401", # non-ASCII characters in function names ] +per-file-ignores."plugins/ext_test/*.py" = [ + "INP001", # Module is part of an implicit namespace +] + +per-file-ignores."plugins/template/*.py" = [ + "INP001", # Module is part of an implicit namespace +] + per-file-ignores."tests/pyscript/*.py" = [ "F821", # Undefined name `app` ] +per-file-ignores."tests/pyscript/raises_exception.py" = [ + "INP001", # Module is part of an implicit namespace +] + + [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "preserve" From 43d6867262e502ea5bf421625c9899f1e80fd582 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 20:54:27 -0400 Subject: [PATCH 32/79] Removed unnecessary stuff from top of cmd2/__init__.py --- cmd2/__init__.py | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/cmd2/__init__.py b/cmd2/__init__.py index 8f1f030ea..e512a1966 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -1,11 +1,7 @@ -# -# -*- coding: utf-8 -*- -# flake8: noqa F401 """This simply imports certain things for backwards compatibility.""" -import sys - import importlib.metadata as importlib_metadata +import sys try: __version__ = importlib_metadata.version(__name__) @@ -13,14 +9,17 @@ # package is not installed pass +# Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER. +# Do this before loading cmd2.Cmd class so its commands use the custom parser. +import argparse from typing import List from .ansi import ( - Cursor, Bg, - Fg, + Cursor, EightBitBg, EightBitFg, + Fg, RgbBg, RgbFg, TextStyle, @@ -34,22 +33,18 @@ set_default_argument_parser_type, ) -# Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER. -# Do this before loading cmd2.Cmd class so its commands use the custom parser. -import argparse - cmd2_parser_module = getattr(argparse, 'cmd2_parser_module', None) if cmd2_parser_module is not None: import importlib importlib.import_module(cmd2_parser_module) +from . import plugin from .argparse_completer import set_default_ap_completer_type - from .cmd2 import Cmd from .command_definition import CommandSet, with_default_category from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS -from .decorators import with_argument_list, with_argparser, with_category, as_subcommand_to +from .decorators import as_subcommand_to, with_argparser, with_argument_list, with_category from .exceptions import ( Cmd2ArgparseError, CommandSetRegistrationError, @@ -57,11 +52,9 @@ PassThroughException, SkipPostcommandHooks, ) -from . import plugin from .parsing import Statement from .py_bridge import CommandResult -from .utils import categorize, CompletionMode, CustomCompletionSettings, Settable - +from .utils import CompletionMode, CustomCompletionSettings, Settable, categorize __all__: List[str] = [ 'COMMAND_NAME', From 4049c1dbeeae778a7f29c8586e6036e0cbba81ec Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 21:38:47 -0400 Subject: [PATCH 33/79] Removed stuff from top of argparse_completer that was preventing ruff flake8 stuff from running in that file and fixed issues --- cmd2/argparse_completer.py | 47 +++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 8dd543c20..cb591bc86 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -1,6 +1,3 @@ -# coding=utf-8 -# flake8: noqa C901 -# NOTE: Ignoring flake8 cyclomatic complexity in this file """ This module defines the ArgparseCompleter class which provides argparse-based tab completion to cmd2 apps. See the header of argparse_custom.py for instructions on how to use these features. @@ -14,8 +11,6 @@ ) from typing import ( TYPE_CHECKING, - Dict, - List, Optional, Type, Union, @@ -172,7 +167,7 @@ class ArgparseCompleter: """Automatic command line tab completion based on argparse parameters""" def __init__( - self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: Optional[Dict[str, List[str]]] = None + self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: Optional[dict[str, list[str]]] = None ) -> None: """ Create an ArgparseCompleter @@ -187,7 +182,7 @@ def __init__( self._cmd2_app = cmd2_app if parent_tokens is None: - parent_tokens = dict() + parent_tokens = {} self._parent_tokens = parent_tokens self._flags = [] # all flags in this command @@ -213,8 +208,8 @@ def __init__( self._subcommand_action = action def complete( - self, text: str, line: str, begidx: int, endidx: int, tokens: List[str], *, cmd_set: Optional[CommandSet] = None - ) -> List[str]: + self, text: str, line: str, begidx: int, endidx: int, tokens: list[str], *, cmd_set: Optional[CommandSet] = None + ) -> list[str]: """ Complete text using argparse metadata @@ -245,13 +240,13 @@ def complete( flag_arg_state: Optional[_ArgumentState] = None # Non-reusable flags that we've parsed - matched_flags: List[str] = [] + matched_flags: list[str] = [] # Keeps track of arguments we've seen and any tokens they consumed - consumed_arg_values: Dict[str, List[str]] = dict() # dict(arg_name -> List[tokens]) + consumed_arg_values: dict[str, list[str]] = {} # dict(arg_name -> list[tokens]) # Completed mutually exclusive groups - completed_mutex_groups: Dict[argparse._MutuallyExclusiveGroup, argparse.Action] = dict() + completed_mutex_groups: dict[argparse._MutuallyExclusiveGroup, argparse.Action] = {} def consume_argument(arg_state: _ArgumentState) -> None: """Consuming token as an argument""" @@ -507,7 +502,7 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: return completion_results - def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matched_flags: List[str]) -> List[str]: + def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matched_flags: list[str]) -> list[str]: """Tab completion routine for a parsers unused flags""" # Build a list of flags that can be tab completed @@ -524,7 +519,7 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matche matches = self._cmd2_app.basic_complete(text, line, begidx, endidx, match_against) # Build a dictionary linking actions with their matched flag names - matched_actions: Dict[argparse.Action, List[str]] = dict() + matched_actions: dict[argparse.Action, list[str]] = {} for flag in matches: action = self._flag_to_action[flag] matched_actions.setdefault(action, []) @@ -541,14 +536,14 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matche return matches - def _format_completions(self, arg_state: _ArgumentState, completions: Union[List[str], List[CompletionItem]]) -> List[str]: + def _format_completions(self, arg_state: _ArgumentState, completions: Union[list[str], list[CompletionItem]]) -> list[str]: """Format CompletionItems into hint table""" # Nothing to do if we don't have at least 2 completions which are all CompletionItems if len(completions) < 2 or not all(isinstance(c, CompletionItem) for c in completions): - return cast(List[str], completions) + return cast(list[str], completions) - completion_items = cast(List[CompletionItem], completions) + completion_items = cast(list[CompletionItem], completions) # Check if the data being completed have a numerical type all_nums = all(isinstance(c.orig_value, numbers.Number) for c in completion_items) @@ -599,7 +594,7 @@ def _format_completions(self, arg_state: _ArgumentState, completions: Union[List item.description = item.description.replace('\t', four_spaces) desc_width = max(widest_line(item.description), desc_width) - cols = list() + cols = [] dest_alignment = HorizontalAlignment.RIGHT if all_nums else HorizontalAlignment.LEFT cols.append( Column( @@ -616,9 +611,9 @@ def _format_completions(self, arg_state: _ArgumentState, completions: Union[List self._cmd2_app.formatted_completions = hint_table.generate_table(table_data, row_spacing=0) # Return sorted list of completions - return cast(List[str], completions) + return cast(list[str], completions) - def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: List[str]) -> List[str]: + def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: list[str]) -> list[str]: """ Supports cmd2's help command in the completion of subcommand names :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -626,7 +621,7 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in :param begidx: the beginning index of the prefix text :param endidx: the ending index of the prefix text :param tokens: arguments passed to command/subcommand - :return: List of subcommand completions + :return: list of subcommand completions """ # If our parser has subcommands, we must examine the tokens and check if they are subcommands # If so, we will let the subcommand's parser handle the rest of the tokens via another ArgparseCompleter. @@ -645,7 +640,7 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in break return [] - def format_help(self, tokens: List[str]) -> str: + def format_help(self, tokens: list[str]) -> str: """ Supports cmd2's help command in the retrieval of help text :param tokens: arguments passed to help command @@ -672,17 +667,17 @@ def _complete_arg( begidx: int, endidx: int, arg_state: _ArgumentState, - consumed_arg_values: Dict[str, List[str]], + consumed_arg_values: dict[str, list[str]], *, cmd_set: Optional[CommandSet] = None, - ) -> List[str]: + ) -> list[str]: """ Tab completion routine for an argparse argument :return: list of completions :raises CompletionError: if the completer or choices function this calls raises one """ # Check if the arg provides choices to the user - arg_choices: Union[List[str], ChoicesCallable] + arg_choices: Union[list[str], ChoicesCallable] if arg_state.action.choices is not None: arg_choices = list(arg_state.action.choices) if not arg_choices: @@ -739,7 +734,7 @@ def _complete_arg( # 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: choices_func = arg_choices.choices_provider From 6d00a49774d5a0e386bd85ccce93f77b602a2e65 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 22:05:36 -0400 Subject: [PATCH 34/79] Bunch of random clenaups and fixes --- cmd2/argparse_custom.py | 4 ++-- cmd2/cmd2.py | 4 ++-- cmd2/constants.py | 1 - cmd2/parsing.py | 1 - cmd2/plugin.py | 1 - cmd2/transcript.py | 1 - examples/modular_commands/commandset_complex.py | 2 -- examples/override_parser.py | 2 -- examples/scripts/conditional.py | 2 -- examples/scripts/save_help_text.py | 6 +----- plugins/tasks.py | 5 +---- pyproject.toml | 11 ++++------- tasks.py | 9 +++------ tests/pyscript/echo.py | 1 - tests/pyscript/environment.py | 1 - tests/pyscript/help.py | 1 - tests/pyscript/py_locals.py | 1 - tests/pyscript/pyscript_dir.py | 1 - tests/pyscript/recursive.py | 3 --- tests/pyscript/self_in_py.py | 1 - tests/pyscript/stdout_capture.py | 1 - tests/pyscript/stop.py | 1 - tests/test_ansi.py | 2 -- tests/test_argparse.py | 2 -- tests/test_argparse_completer.py | 2 -- tests/test_argparse_custom.py | 1 - tests/test_cmd2.py | 2 -- tests/test_completion.py | 2 -- tests/test_history.py | 2 -- tests/test_parsing.py | 2 -- tests/test_plugin.py | 2 -- tests/test_run_pyscript.py | 2 -- tests/test_table_creator.py | 2 -- tests/test_transcript.py | 2 -- tests/test_utils.py | 2 -- tests/test_utils_defining_class.py | 2 -- .../test_commandset/test_argparse_subcommands.py | 2 -- tests_isolated/test_commandset/test_commandset.py | 2 -- 38 files changed, 13 insertions(+), 78 deletions(-) mode change 100755 => 100644 tests/test_cmd2.py mode change 100755 => 100644 tests/test_completion.py mode change 100755 => 100644 tests/test_history.py mode change 100755 => 100644 tests/test_parsing.py diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index ec7815fc1..2cc5e31e7 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -977,7 +977,7 @@ def _ArgumentParser_check_value(self: argparse.ArgumentParser, action: argparse. ############################################################################################################ -def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str) -> None: # type: ignore +def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str) -> None: # type: ignore[type-arg] """ Removes a sub-parser from a sub-parsers group. Used to remove subcommands from a parser. @@ -1289,7 +1289,7 @@ def __init__( self.set_ap_completer_type(ap_completer_type) # type: ignore[attr-defined] - def add_subparsers(self, **kwargs: Any) -> argparse._SubParsersAction: # type: ignore + def add_subparsers(self, **kwargs: Any) -> argparse._SubParsersAction: # type: ignore[type-arg] """ Custom override. Sets a default title if one was not given. diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index ae2f13899..577d57c50 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -4599,7 +4599,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover # Allow users to install ipython from a cmd2 prompt when needed and still have ipy command work try: - start_ipython # noqa F823 + start_ipython # noqa: F823 except NameError: from IPython import start_ipython # type: ignore[import] @@ -4636,7 +4636,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover local_vars['self'] = self # Configure IPython - config = TraitletsLoader.Config() # type: ignore + 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' diff --git a/cmd2/constants.py b/cmd2/constants.py index b45a73bf6..3306f6fc8 100644 --- a/cmd2/constants.py +++ b/cmd2/constants.py @@ -1,4 +1,3 @@ -# """This module contains constants used throughout ``cmd2``.""" # Unless documented in https://cmd2.readthedocs.io/en/latest/api/index.html diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 9089a4fea..11654072d 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -1,4 +1,3 @@ -# """Statement parsing classes for cmd2""" import re diff --git a/cmd2/plugin.py b/cmd2/plugin.py index a3fed2856..bae35befe 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -1,4 +1,3 @@ -# """Classes for the cmd2 plugin system""" from dataclasses import ( diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 1d453b2c8..98a250f21 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -1,4 +1,3 @@ -# """Machinery for running and validating transcripts. If the user wants to run a transcript (see docs/transcript.rst), diff --git a/examples/modular_commands/commandset_complex.py b/examples/modular_commands/commandset_complex.py index 7ab84ac3f..c01979c94 100644 --- a/examples/modular_commands/commandset_complex.py +++ b/examples/modular_commands/commandset_complex.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Test CommandSet """ diff --git a/examples/override_parser.py b/examples/override_parser.py index 0ae279e70..f08e0dc30 100755 --- a/examples/override_parser.py +++ b/examples/override_parser.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# coding=utf-8 -# flake8: noqa F402 """ The standard parser used by cmd2 built-in commands is Cmd2ArgumentParser. The following code shows how to override it with your own parser class. diff --git a/examples/scripts/conditional.py b/examples/scripts/conditional.py index dd3adcca0..8fe1c426f 100644 --- a/examples/scripts/conditional.py +++ b/examples/scripts/conditional.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa F821 """ This is a Python script intended to be used with the "python_scripting.py" cmd2 example application. diff --git a/examples/scripts/save_help_text.py b/examples/scripts/save_help_text.py index b8ba9624d..ff787368f 100644 --- a/examples/scripts/save_help_text.py +++ b/examples/scripts/save_help_text.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa F821 """ A cmd2 script that saves the help text for every command, subcommand, and topic to a file. This is meant to be run within a cmd2 session using run_pyscript. @@ -29,9 +27,7 @@ def get_sub_commands(parser: argparse.ArgumentParser) -> List[str]: sub_cmds.append(sub_cmd) # Look for nested subcommands - for nested_sub_cmd in get_sub_commands(sub_cmd_parser): - sub_cmds.append('{} {}'.format(sub_cmd, nested_sub_cmd)) - + sub_cmds.extend(f'{sub_cmd} {nested_sub_cmd}' for nested_sub_cmd in get_sub_commands(sub_cmd_parser)) break sub_cmds.sort() diff --git a/plugins/tasks.py b/plugins/tasks.py index 2717ecf4f..352c8c9bf 100644 --- a/plugins/tasks.py +++ b/plugins/tasks.py @@ -1,6 +1,3 @@ -# -# coding=utf-8 -# flake8: noqa E302 """Development related tasks to be run with 'invoke'. Make sure you satisfy the following Python module requirements if you are trying to publish a release to PyPI: @@ -147,7 +144,7 @@ def lint(context): # ruff formatter @invoke.task(pre=[ext_test_tasks.format]) -def format(context): +def format(context): # noqa: A001 """Run formatter""" with context.cd(TASK_ROOT_STR): context.run("ruff format --check") diff --git a/pyproject.toml b/pyproject.toml index 4008f227a..918a29ed8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -193,7 +193,7 @@ select = [ "NPY", # NumPy specific rules "PD", # pandas-vet (Pandas specific rules) "PERF", # Perflint (warn about performance issues) - # "PGH", # pygrep-hooks (force specific rule codes when ignoring type or linter issues on a line) + # "PGH", # pygrep-hooks (force specific rule codes when ignoring type or linter issues on a line) # "PIE", # flake8-pie (eliminate unnecessary use of pass, range starting at 0, etc.) "PLC", # Pylint Conventions "PLE", # Pylint Errors @@ -270,16 +270,13 @@ per-file-ignores."examples/unicode_commands.py" = [ "PLC2401", # non-ASCII characters in function names ] -per-file-ignores."plugins/ext_test/*.py" = [ - "INP001", # Module is part of an implicit namespace -] - -per-file-ignores."plugins/template/*.py" = [ +per-file-ignores."plugins/*.py" = [ "INP001", # Module is part of an implicit namespace ] per-file-ignores."tests/pyscript/*.py" = [ - "F821", # Undefined name `app` + "F821", # Undefined name `app` + "INP001", # Module is part of an implicit namespace ] per-file-ignores."tests/pyscript/raises_exception.py" = [ diff --git a/tasks.py b/tasks.py index 6d95b3503..215fb9b36 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,3 @@ -# -# coding=utf-8 -# flake8: noqa E302 """Development related tasks to be run with 'invoke'. Make sure you satisfy the following Python module requirements if you are trying to publish a release to PyPI: @@ -74,8 +71,8 @@ def pytest(context, junit=False, pty=True, base=False, isolated=False): context.run(tests_cmd, pty=pty) if isolated: for root, dirnames, _ in os.walk(str(TASK_ROOT / 'tests_isolated')): - for dir in dirnames: - if dir.startswith('test_'): + for dir_name in dirnames: + if dir_name.startswith('test_'): context.run(command_str + ' tests_isolated/' + dir) @@ -235,7 +232,7 @@ def lint(context): # ruff fast formatter @invoke.task() -def format(context): +def format(context): # noqa: A001 """Run ruff format --check""" with context.cd(TASK_ROOT_STR): context.run("ruff format --check") diff --git a/tests/pyscript/echo.py b/tests/pyscript/echo.py index d95e19dbc..c5999355a 100644 --- a/tests/pyscript/echo.py +++ b/tests/pyscript/echo.py @@ -1,4 +1,3 @@ -# flake8: noqa F821 # Tests echo argument to app() app.cmd_echo = False diff --git a/tests/pyscript/environment.py b/tests/pyscript/environment.py index f1182c829..f703b0e8f 100644 --- a/tests/pyscript/environment.py +++ b/tests/pyscript/environment.py @@ -1,4 +1,3 @@ -# flake8: noqa F821 # Tests that cmd2 populates __name__, __file__, and sets sys.path[0] to our directory import os import sys diff --git a/tests/pyscript/help.py b/tests/pyscript/help.py index 2e69d79f6..480c6cd70 100644 --- a/tests/pyscript/help.py +++ b/tests/pyscript/help.py @@ -1,4 +1,3 @@ -# flake8: noqa F821 app.cmd_echo = True app('help') diff --git a/tests/pyscript/py_locals.py b/tests/pyscript/py_locals.py index 16cb69262..aa1e0893d 100644 --- a/tests/pyscript/py_locals.py +++ b/tests/pyscript/py_locals.py @@ -1,4 +1,3 @@ -# flake8: noqa F821 # Tests how much a pyscript can affect cmd2.Cmd.py_locals del [locals()["test_var"]] diff --git a/tests/pyscript/pyscript_dir.py b/tests/pyscript/pyscript_dir.py index 81814d70b..14a70a316 100644 --- a/tests/pyscript/pyscript_dir.py +++ b/tests/pyscript/pyscript_dir.py @@ -1,4 +1,3 @@ -# flake8: noqa F821 out = dir(app) out.sort() print(out) diff --git a/tests/pyscript/recursive.py b/tests/pyscript/recursive.py index 206f356cb..3805d027b 100644 --- a/tests/pyscript/recursive.py +++ b/tests/pyscript/recursive.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# coding=utf-8 -# flake8: noqa F821 """ Example demonstrating that calling run_pyscript recursively inside another Python script isn't allowed """ diff --git a/tests/pyscript/self_in_py.py b/tests/pyscript/self_in_py.py index f0f6271a9..ee26293f6 100644 --- a/tests/pyscript/self_in_py.py +++ b/tests/pyscript/self_in_py.py @@ -1,4 +1,3 @@ -# flake8: noqa F821 # Tests self_in_py in pyscripts if 'self' in globals(): print("I see self") diff --git a/tests/pyscript/stdout_capture.py b/tests/pyscript/stdout_capture.py index 4aa78d537..5cc0cf3a4 100644 --- a/tests/pyscript/stdout_capture.py +++ b/tests/pyscript/stdout_capture.py @@ -1,4 +1,3 @@ -# flake8: noqa F821 # This script demonstrates when output of a command finalization hook is captured by a pyscript app() call import sys diff --git a/tests/pyscript/stop.py b/tests/pyscript/stop.py index 1578b057e..31b587bd2 100644 --- a/tests/pyscript/stop.py +++ b/tests/pyscript/stop.py @@ -1,4 +1,3 @@ -# flake8: noqa F821 app.cmd_echo = True app('help') diff --git a/tests/test_ansi.py b/tests/test_ansi.py index 65ec68a9d..13e9c856e 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Unit testing for cmd2/ansi.py module """ diff --git a/tests/test_argparse.py b/tests/test_argparse.py index f800c84a6..ad86ba81b 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Cmd2 testing for argument parsing """ diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 1f9178f88..7bca96fbd 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Unit/functional testing for argparse completer in cmd2 """ diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index 06d7b7d3d..1e764b0de 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -1,4 +1,3 @@ -# flake8: noqa E302 """ Unit/functional testing for argparse customizations in cmd2 """ diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py old mode 100755 new mode 100644 index 721be0a2f..cdc555569 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Cmd2 unit/functional testing """ diff --git a/tests/test_completion.py b/tests/test_completion.py old mode 100755 new mode 100644 index cd2a2af08..3bbbd6298 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Unit/functional testing for readline tab completion functions in the cmd2.py module. diff --git a/tests/test_history.py b/tests/test_history.py old mode 100755 new mode 100644 index 284112d23..0410d745e --- a/tests/test_history.py +++ b/tests/test_history.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Test history functions of cmd2 """ diff --git a/tests/test_parsing.py b/tests/test_parsing.py old mode 100755 new mode 100644 index e3d42d7c7..9f99198db --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Test the parsing logic in parsing.py """ diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 5a625b724..8eedf45d7 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Test plugin infrastructure and hooks. """ diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index 594a30b75..5af739981 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Unit/functional testing for run_pytest in cmd2 """ diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py index fbbdfbc4a..0d2291db6 100644 --- a/tests/test_table_creator.py +++ b/tests/test_table_creator.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E501 """ Unit testing for cmd2/table_creator.py module """ diff --git a/tests/test_transcript.py b/tests/test_transcript.py index 986221ff2..9d261345b 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Cmd2 functional testing based on transcript """ diff --git a/tests/test_utils.py b/tests/test_utils.py index a173f7f45..c44537a57 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Unit testing for cmd2/utils.py module. """ diff --git a/tests/test_utils_defining_class.py b/tests/test_utils_defining_class.py index 8b6ede8bf..8d25a550a 100644 --- a/tests/test_utils_defining_class.py +++ b/tests/test_utils_defining_class.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Unit testing for get_defining_class in cmd2/utils.py module. """ diff --git a/tests_isolated/test_commandset/test_argparse_subcommands.py b/tests_isolated/test_commandset/test_argparse_subcommands.py index 3a9ab5955..ad6b2828a 100644 --- a/tests_isolated/test_commandset/test_argparse_subcommands.py +++ b/tests_isolated/test_commandset/test_argparse_subcommands.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ reproduces test_argparse.py except with SubCommands """ diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 89c982f39..aed935886 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -1,5 +1,3 @@ -# coding=utf-8 -# flake8: noqa E302 """ Test CommandSet """ From 7f824e299982761a9b2efff59d98fcc06601f40b Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 22:07:07 -0400 Subject: [PATCH 35/79] Applied a few more fixes --- tests/test_history.py | 2 +- tests/test_table_creator.py | 14 +++++++------- tests/test_utils.py | 2 +- tests_isolated/test_commandset/test_commandset.py | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_history.py b/tests/test_history.py index 0410d745e..9b1b31ddc 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -668,7 +668,7 @@ def test_history_output_file(): run_cmd(app, 'history -o "{}"'.format(fname)) assert app.last_result is True - expected = normalize('\n'.join(['help', 'shortcuts', 'help history', 'alias create my_alias history;'])) + expected = normalize('help\nshortcuts\nhelp history\nalias create my_alias history;') with open(fname) as f: content = normalize(f.read()) assert content == expected diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py index 0d2291db6..c3f1d1881 100644 --- a/tests/test_table_creator.py +++ b/tests/test_table_creator.py @@ -230,7 +230,7 @@ def test_wrap_long_word(): 'Name Col 2 ') # Test data row - row_data = list() + row_data = [] # Long word should start on the first line (style should not affect width) row_data.append(ansi.style("LongerThan10", fg=Fg.GREEN)) @@ -267,7 +267,7 @@ def test_wrap_long_word_max_data_lines(): columns = [column_1, column_2, column_3, column_4] tc = TableCreator(columns) - row_data = list() + row_data = [] # This long word will exactly fit the last line and it's the final word in the text. No ellipsis should appear. row_data.append("LongerThan10FitsLast") @@ -341,7 +341,7 @@ def test_simple_table_creation(): column_1 = Column("Col 1", width=16) column_2 = Column("Col 2", width=16) - row_data = list() + row_data = [] row_data.append(["Col 1 Row 1", "Col 2 Row 1"]) row_data.append(["Col 1 Row 2", "Col 2 Row 2"]) @@ -495,7 +495,7 @@ def test_simple_table_width(): column_1 = Column("Col 1", width=16) column_2 = Column("Col 2", width=16) - row_data = list() + row_data = [] row_data.append(["Col 1 Row 1", "Col 2 Row 1"]) row_data.append(["Col 1 Row 2", "Col 2 Row 2"]) @@ -518,7 +518,7 @@ def test_bordered_table_creation(): column_1 = Column("Col 1", width=15) column_2 = Column("Col 2", width=15) - row_data = list() + row_data = [] row_data.append(["Col 1 Row 1", "Col 2 Row 1"]) row_data.append(["Col 1 Row 2", "Col 2 Row 2"]) @@ -637,7 +637,7 @@ def test_bordered_table_width(): column_1 = Column("Col 1", width=15) column_2 = Column("Col 2", width=15) - row_data = list() + row_data = [] row_data.append(["Col 1 Row 1", "Col 2 Row 1"]) row_data.append(["Col 1 Row 2", "Col 2 Row 2"]) @@ -660,7 +660,7 @@ def test_alternating_table_creation(): column_1 = Column("Col 1", width=15) column_2 = Column("Col 2", width=15) - row_data = list() + row_data = [] row_data.append(["Col 1 Row 1", "Col 2 Row 1"]) row_data.append(["Col 1 Row 2", "Col 2 Row 2"]) diff --git a/tests/test_utils.py b/tests/test_utils.py index c44537a57..ba71814bc 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -272,7 +272,7 @@ def pr_none(): # Start a long running process so we have time to run tests on it before it finishes # Put the new process into a separate group so its signal are isolated from ours - kwargs = dict() + kwargs = {} if sys.platform.startswith('win'): command = 'timeout -t 5 /nobreak' kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index aed935886..9e49d8bb5 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -1100,10 +1100,10 @@ def __init__(self): # verify the settable shows up out, err = run_cmd(app, 'set') - any(['arbitrary_value' in line and '5' in line for line in out]) + any('arbitrary_value' in line and '5' in line for line in out) out, err = run_cmd(app, 'set arbitrary_value') - any(['arbitrary_value' in line and '5' in line for line in out]) + any('arbitrary_value' in line and '5' in line for line in out) # change the value and verify the value changed out, err = run_cmd(app, 'set arbitrary_value 10') @@ -1113,7 +1113,7 @@ def __init__(self): """ assert out == normalize(expected) out, err = run_cmd(app, 'set arbitrary_value') - any(['arbitrary_value' in line and '10' in line for line in out]) + any('arbitrary_value' in line and '10' in line for line in out) # can't add to cmd2 now because commandset already has this settable with pytest.raises(KeyError): @@ -1172,10 +1172,10 @@ def __init__(self): assert 'some.arbitrary_value' in app.settables.keys() out, err = run_cmd(app, 'set') - any(['some.arbitrary_value' in line and '5' in line for line in out]) + any('some.arbitrary_value' in line and '5' in line for line in out) out, err = run_cmd(app, 'set some.arbitrary_value') - any(['some.arbitrary_value' in line and '5' in line for line in out]) + any('some.arbitrary_value' in line and '5' in line for line in out) # verify registering a commandset with duplicate prefix and settable names fails with pytest.raises(CommandSetRegistrationError): From a727f5a24b97805c4e708984eb680c5a67812cc5 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 22:14:26 -0400 Subject: [PATCH 36/79] Enabled ruff PGH ruleset for pygrep-hooks In the process removed a lot of file header noqa stuff which was preventing ruff from working properly in those files which dated back to flake8. Then fixed any issues in those files. --- plugins/ext_test/tasks.py | 5 +---- pyproject.toml | 10 +++++----- tests/test_utils_defining_class.py | 3 ++- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/plugins/ext_test/tasks.py b/plugins/ext_test/tasks.py index 54b0e3791..e72801882 100644 --- a/plugins/ext_test/tasks.py +++ b/plugins/ext_test/tasks.py @@ -1,6 +1,3 @@ -# -# coding=utf-8 -# flake8: noqa E302 """Development related tasks to be run with 'invoke'. Make sure you satisfy the following Python module requirements if you are trying to publish a release to PyPI: @@ -205,7 +202,7 @@ def lint(context): @invoke.task -def format(context): +def format(context): # noqa: A001 """Run ruff format --check""" with context.cd(TASK_ROOT_STR): context.run("ruff format --check") diff --git a/pyproject.toml b/pyproject.toml index 918a29ed8..994d3d261 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -193,7 +193,7 @@ select = [ "NPY", # NumPy specific rules "PD", # pandas-vet (Pandas specific rules) "PERF", # Perflint (warn about performance issues) - # "PGH", # pygrep-hooks (force specific rule codes when ignoring type or linter issues on a line) + "PGH", # pygrep-hooks (force specific rule codes when ignoring type or linter issues on a line) # "PIE", # flake8-pie (eliminate unnecessary use of pass, range starting at 0, etc.) "PLC", # Pylint Conventions "PLE", # Pylint Errors @@ -274,12 +274,12 @@ per-file-ignores."plugins/*.py" = [ "INP001", # Module is part of an implicit namespace ] -per-file-ignores."tests/pyscript/*.py" = [ - "F821", # Undefined name `app` - "INP001", # Module is part of an implicit namespace +per-file-ignores."tests/*.py" = [ + "E501", # Line too long ] -per-file-ignores."tests/pyscript/raises_exception.py" = [ +per-file-ignores."tests/pyscript/*.py" = [ + "F821", # Undefined name `app` "INP001", # Module is part of an implicit namespace ] diff --git a/tests/test_utils_defining_class.py b/tests/test_utils_defining_class.py index 8d25a550a..5c313c8c5 100644 --- a/tests/test_utils_defining_class.py +++ b/tests/test_utils_defining_class.py @@ -25,7 +25,8 @@ def child_function(self): def lambda1(): return 1 - lambda2 = (lambda: lambda: 2)() + def lambda2(): + return 2 @classmethod def class_method(cls): From ada27c5ee616ca6184f767e90e66240fa05d7289 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 22:17:43 -0400 Subject: [PATCH 37/79] Fix bug in tasks.py do to renaming a built-in to prevent shadowing --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 215fb9b36..3eb01316a 100644 --- a/tasks.py +++ b/tasks.py @@ -73,7 +73,7 @@ def pytest(context, junit=False, pty=True, base=False, isolated=False): for root, dirnames, _ in os.walk(str(TASK_ROOT / 'tests_isolated')): for dir_name in dirnames: if dir_name.startswith('test_'): - context.run(command_str + ' tests_isolated/' + dir) + context.run(command_str + ' tests_isolated/' + dir_name) namespace.add_task(pytest) From 1a4b13c5fd57de291a8e486d5feb3b1889f890a1 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 22:27:58 -0400 Subject: [PATCH 38/79] Removed unnecessary use of pass --- cmd2/ansi.py | 2 -- cmd2/cmd2.py | 2 -- cmd2/command_definition.py | 2 -- cmd2/exceptions.py | 7 ------- examples/alias_startup.py | 1 - examples/async_printing.py | 2 +- examples/default_categories.py | 1 - plugins/tasks.py | 9 --------- plugins/template/tasks.py | 1 - pyproject.toml | 6 +++--- tasks.py | 1 - tests/test_argparse_completer.py | 6 ++---- tests/test_cmd2.py | 7 ------- tests/test_completion.py | 1 - tests/test_plugin.py | 10 ---------- tests/test_transcript.py | 1 - tests_isolated/test_commandset/test_commandset.py | 1 - 17 files changed, 6 insertions(+), 54 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 20221599a..70f1b293c 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -211,13 +211,11 @@ def __radd__(self, other: Any) -> str: class FgColor(AnsiSequence): """Base class for ANSI Sequences which set foreground text color""" - pass class BgColor(AnsiSequence): """Base class for ANSI Sequences which set background text color""" - pass #################################################################################### diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 577d57c50..7bd34231d 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -2463,7 +2463,6 @@ def preloop(self) -> None: to run hooks before the command loop begins. See [Hooks](../features/hooks.md) for more information. """ - pass def postloop(self) -> None: """Hook method executed once when the [cmd2.Cmd.cmdloop][] method is about to return. @@ -2472,7 +2471,6 @@ def postloop(self) -> None: to run hooks after the command loop completes. See [Hooks](../features/hooks.md) for more information. """ - pass def parseline(self, line: str) -> tuple[str, str, str]: """Parse the line into a command name and a string containing the arguments. diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index d32d49b10..fc891490f 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -136,14 +136,12 @@ def on_registered(self) -> None: Subclasses can override this to perform custom steps related to the newly added commands (e.g. setting them to a disabled state). """ - pass def on_unregister(self) -> None: """ Called by ``cmd2.Cmd`` as the first step to unregistering a CommandSet. Subclasses can override this to perform any cleanup steps which require their commands being registered in the CLI. """ - pass def on_unregistered(self) -> None: """ diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index 4f8a517c2..97ec3185b 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -15,7 +15,6 @@ class SkipPostcommandHooks(Exception): hooks, but not bad enough to print the exception to the user. """ - pass class Cmd2ArgparseError(SkipPostcommandHooks): @@ -26,7 +25,6 @@ class Cmd2ArgparseError(SkipPostcommandHooks): after parsing fails, just return instead of raising an exception. """ - pass class CommandSetRegistrationError(Exception): @@ -35,7 +33,6 @@ class CommandSetRegistrationError(Exception): from a cmd2 application. """ - pass class CompletionError(Exception): @@ -86,22 +83,18 @@ def __init__(self, *args: Any, wrapped_ex: BaseException) -> None: class Cmd2ShlexError(Exception): """Raised when shlex fails to parse a command line string in StatementParser""" - pass class EmbeddedConsoleExit(SystemExit): """Custom exception class for use with the py command.""" - pass class EmptyStatement(Exception): """Custom exception class for handling behavior when the user just presses .""" - pass class RedirectionError(Exception): """Custom exception class for when redirecting or piping output fails""" - pass diff --git a/examples/alias_startup.py b/examples/alias_startup.py index e84278658..408ffd16f 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -18,7 +18,6 @@ def __init__(self): def do_nothing(self, args): """This command does nothing and produces no output.""" - pass if __name__ == '__main__': diff --git a/examples/async_printing.py b/examples/async_printing.py index 8520bc130..3f2883c0b 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -108,7 +108,7 @@ def _get_alerts(self) -> list[str]: if rand_num > 2: return [] - for i in range(0, rand_num): + for i in range(rand_num): self._alert_count += 1 alerts.append(f"Alert {self._alert_count}") diff --git a/examples/default_categories.py b/examples/default_categories.py index 3d531931f..0d9728f68 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -14,7 +14,6 @@ class MyBaseCommandSet(CommandSet): """Defines a default category for all sub-class CommandSets""" - pass class ChildInheritsParentCategories(MyBaseCommandSet): diff --git a/plugins/tasks.py b/plugins/tasks.py index 352c8c9bf..14345dcbc 100644 --- a/plugins/tasks.py +++ b/plugins/tasks.py @@ -39,7 +39,6 @@ @invoke.task() def pytest(_): """Run tests and code coverage using pytest""" - pass namespace.add_task(pytest) @@ -48,7 +47,6 @@ def pytest(_): @invoke.task(pre=[ext_test_tasks.pytest_clean]) def pytest_clean(_): """Remove pytest cache and code coverage files and directories""" - pass namespace_clean.add_task(pytest_clean, 'pytest') @@ -57,7 +55,6 @@ def pytest_clean(_): @invoke.task(pre=[ext_test_tasks.mypy]) def mypy(_): """Run mypy optional static type checker""" - pass namespace.add_task(mypy) @@ -67,7 +64,6 @@ def mypy(_): def mypy_clean(_): """Remove mypy cache directory""" # pylint: disable=unused-argument - pass namespace_clean.add_task(mypy_clean, 'mypy') @@ -85,7 +81,6 @@ def mypy_clean(_): @invoke.task(pre=[ext_test_tasks.build_clean]) def build_clean(_): """Remove the build directory""" - pass namespace_clean.add_task(build_clean, 'build') @@ -94,7 +89,6 @@ def build_clean(_): @invoke.task(pre=[ext_test_tasks.dist_clean]) def dist_clean(_): """Remove the dist directory""" - pass namespace_clean.add_task(dist_clean, 'dist') @@ -108,7 +102,6 @@ def dist_clean(_): def clean_all(_): """Run all clean tasks""" # pylint: disable=unused-argument - pass namespace_clean.add_task(clean_all, 'all') @@ -117,7 +110,6 @@ def clean_all(_): @invoke.task(pre=[clean_all], post=[ext_test_tasks.sdist]) def sdist(_): """Create a source distribution""" - pass namespace.add_task(sdist) @@ -126,7 +118,6 @@ def sdist(_): @invoke.task(pre=[clean_all], post=[ext_test_tasks.wheel]) def wheel(_): """Build a wheel distribution""" - pass namespace.add_task(wheel) diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index 6043d62b5..4fa76c683 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -160,7 +160,6 @@ def bytecode_clean(context): def clean_all(context): """Run all clean tasks""" # pylint: disable=unused-argument - pass namespace_clean.add_task(clean_all, 'all') diff --git a/pyproject.toml b/pyproject.toml index 994d3d261..abbe5a63f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -194,9 +194,9 @@ select = [ "PD", # pandas-vet (Pandas specific rules) "PERF", # Perflint (warn about performance issues) "PGH", # pygrep-hooks (force specific rule codes when ignoring type or linter issues on a line) - # "PIE", # flake8-pie (eliminate unnecessary use of pass, range starting at 0, etc.) - "PLC", # Pylint Conventions - "PLE", # Pylint Errors + "PIE", # flake8-pie (eliminate unnecessary use of pass, range starting at 0, etc.) + "PLC", # Pylint Conventions + "PLE", # Pylint Errors # "PLR", # Pylint Refactoring suggestions # "PLW", # Pylint Warnings # "PT", # flake8-pytest-style (warnings about unit test best practices) diff --git a/tasks.py b/tasks.py index 3eb01316a..bc0306d13 100644 --- a/tasks.py +++ b/tasks.py @@ -260,7 +260,6 @@ def ruff_clean(context): def clean_all(_): """Run all clean tasks""" # pylint: disable=unused-argument - pass namespace_clean.add_task(clean_all, 'all') diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 7bca96fbd..838d1b7c9 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -130,7 +130,7 @@ def choices_provider(self) -> List[str]: def completion_item_method(self) -> List[CompletionItem]: """Choices method that returns CompletionItems""" items = [] - for i in range(0, 10): + for i in range(10): main_str = 'main_str{}'.format(i) items.append(CompletionItem(main_str, description='blah blah')) return items @@ -775,7 +775,7 @@ def test_completion_items(ac_app): ) def test_max_completion_items(ac_app, num_aliases, show_description): # Create aliases - for i in range(0, num_aliases): + for i in range(num_aliases): run_cmd(ac_app, 'alias create fake_alias{} help'.format(i)) assert len(ac_app.aliases) == num_aliases @@ -1232,7 +1232,6 @@ def __init__(self): @with_argparser(default_completer_parser) def do_default_completer(self, args: argparse.Namespace) -> None: """Test command""" - pass # Parser that's used to test setting a custom completer at the parser level custom_completer_parser = Cmd2ArgumentParser( @@ -1243,7 +1242,6 @@ def do_default_completer(self, args: argparse.Namespace) -> None: @with_argparser(custom_completer_parser) def do_custom_completer(self, args: argparse.Namespace) -> None: """Test command""" - pass # Test as_subcommand_to decorator with custom completer top_parser = Cmd2ArgumentParser(description="Top Command") diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index cdc555569..183b54ade 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1186,14 +1186,12 @@ def __init__(self, *args, **kwargs): def do_squat(self, arg): """This docstring help will never be shown because the help_squat method overrides it.""" - pass def help_squat(self): self.stdout.write('This command does diddly squat...\n') def do_edit(self, arg): """This overrides the edit command and does nothing.""" - pass # This command will be in the "undocumented" section of the help menu def do_undoc(self, arg): @@ -1206,14 +1204,12 @@ def do_multiline_docstr(self, arg): and there are no tabs """ - pass parser_cmd_parser = cmd2.Cmd2ArgumentParser(description="This is the description.") @cmd2.with_argparser(parser_cmd_parser) def do_parser_cmd(self, args): """This is the docstring.""" - pass @pytest.fixture @@ -1268,7 +1264,6 @@ def __init__(self, *args, **kwargs): @cmd2.with_category('Some Category') def do_diddly(self, arg): """This command does diddly""" - pass # This command will be in the "Some Category" section of the help menu even though it has no docstring @cmd2.with_category("Some Category") @@ -1277,14 +1272,12 @@ def do_cat_nodoc(self, arg): def do_squat(self, arg): """This docstring help will never be shown because the help_squat method overrides it.""" - pass def help_squat(self): self.stdout.write('This command does diddly squat...\n') def do_edit(self, arg): """This overrides the edit command and does nothing.""" - pass cmd2.categorize((do_squat, do_edit), 'Custom Category') diff --git a/tests/test_completion.py b/tests/test_completion.py index 3bbbd6298..751188020 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -107,7 +107,6 @@ def complete_test_multiline(self, text, line, begidx, endidx): def do_test_no_completer(self, args): """Completing this should result in completedefault() being called""" - pass def complete_foo_val(self, text, line, begidx, endidx, arg_tokens): """Supports unit testing cmd2.Cmd2.complete_set_val to confirm it passes all tokens in the set command""" diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 8eedf45d7..d7abe443d 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -49,11 +49,9 @@ def prepost_hook_two(self) -> None: def prepost_hook_too_many_parameters(self, param) -> None: """A preloop or postloop hook with too many parameters""" - pass def prepost_hook_with_wrong_return_annotation(self) -> bool: """A preloop or postloop hook with incorrect return type""" - pass ### # @@ -93,23 +91,18 @@ def postparse_hook_exception(self, data: cmd2.plugin.PostparsingData) -> cmd2.pl def postparse_hook_too_many_parameters(self, data1, data2) -> cmd2.plugin.PostparsingData: """A postparsing hook with too many parameters""" - pass def postparse_hook_undeclared_parameter_annotation(self, data) -> cmd2.plugin.PostparsingData: """A postparsing hook with an undeclared parameter type""" - pass def postparse_hook_wrong_parameter_annotation(self, data: str) -> cmd2.plugin.PostparsingData: """A postparsing hook with the wrong parameter type""" - pass def postparse_hook_undeclared_return_annotation(self, data: cmd2.plugin.PostparsingData): """A postparsing hook with an undeclared return type""" - pass def postparse_hook_wrong_return_annotation(self, data: cmd2.plugin.PostparsingData) -> str: """A postparsing hook with the wrong return type""" - pass ### # @@ -138,7 +131,6 @@ def precmd_hook_exception(self, data: plugin.PrecommandData) -> plugin.Precomman def precmd_hook_not_enough_parameters(self) -> plugin.PrecommandData: """A precommand hook with no parameters""" - pass def precmd_hook_too_many_parameters(self, one: plugin.PrecommandData, two: str) -> plugin.PrecommandData: """A precommand hook with too many parameters""" @@ -181,7 +173,6 @@ def postcmd_hook_exception(self, data: plugin.PostcommandData) -> plugin.Postcom def postcmd_hook_not_enough_parameters(self) -> plugin.PostcommandData: """A precommand hook with no parameters""" - pass def postcmd_hook_too_many_parameters(self, one: plugin.PostcommandData, two: str) -> plugin.PostcommandData: """A precommand hook with too many parameters""" @@ -247,7 +238,6 @@ def cmdfinalization_hook_passthrough_exception( def cmdfinalization_hook_not_enough_parameters(self) -> plugin.CommandFinalizationData: """A command finalization hook with no parameters.""" - pass def cmdfinalization_hook_too_many_parameters( self, one: plugin.CommandFinalizationData, two: str diff --git a/tests/test_transcript.py b/tests/test_transcript.py index 9d261345b..f8d1c90d1 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -88,7 +88,6 @@ def do_mumble(self, opts, arg): def do_nothing(self, statement): """Do nothing and output nothing""" - pass def do_keyboard_interrupt(self, _): raise KeyboardInterrupt('Interrupting this command') diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 9e49d8bb5..093eda64f 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -1009,7 +1009,6 @@ def __init__(self, *args, **kwargs): @cmd2.with_argparser(cut_parser) def do_cut(self, ns: argparse.Namespace): """Cut something""" - pass banana_parser = cmd2.Cmd2ArgumentParser() banana_parser.add_argument('direction', choices=['discs', 'lengthwise']) From 47a52ad7a8731f658e0c96e4fdad005e2b575582 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 22:35:11 -0400 Subject: [PATCH 39/79] Enable ruff PIE ruleset and fix related issues --- cmd2/ansi.py | 2 -- cmd2/exceptions.py | 7 ------- examples/default_categories.py | 1 - plugins/ext_test/tasks.py | 1 - tests/test_argparse.py | 6 +++--- tests/test_argparse_completer.py | 6 +++--- .../test_commandset/test_argparse_subcommands.py | 6 +++--- 7 files changed, 9 insertions(+), 20 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 70f1b293c..f75ef186c 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -212,12 +212,10 @@ class FgColor(AnsiSequence): """Base class for ANSI Sequences which set foreground text color""" - class BgColor(AnsiSequence): """Base class for ANSI Sequences which set background text color""" - #################################################################################### # Implementations intended for direct use #################################################################################### diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index 97ec3185b..e11590ac7 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -16,7 +16,6 @@ class SkipPostcommandHooks(Exception): """ - class Cmd2ArgparseError(SkipPostcommandHooks): """ A ``SkipPostcommandHooks`` exception for when a command fails to parse its arguments. @@ -26,7 +25,6 @@ class Cmd2ArgparseError(SkipPostcommandHooks): """ - class CommandSetRegistrationError(Exception): """ Exception that can be thrown when an error occurs while a CommandSet is being added or removed @@ -34,7 +32,6 @@ class CommandSetRegistrationError(Exception): """ - class CompletionError(Exception): """ Raised during tab completion operations to report any sort of error you want printed. This can also be used @@ -84,17 +81,13 @@ class Cmd2ShlexError(Exception): """Raised when shlex fails to parse a command line string in StatementParser""" - class EmbeddedConsoleExit(SystemExit): """Custom exception class for use with the py command.""" - class EmptyStatement(Exception): """Custom exception class for handling behavior when the user just presses .""" - class RedirectionError(Exception): """Custom exception class for when redirecting or piping output fails""" - diff --git a/examples/default_categories.py b/examples/default_categories.py index 0d9728f68..256db4867 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -15,7 +15,6 @@ class MyBaseCommandSet(CommandSet): """Defines a default category for all sub-class CommandSets""" - class ChildInheritsParentCategories(MyBaseCommandSet): """ This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category' diff --git a/plugins/ext_test/tasks.py b/plugins/ext_test/tasks.py index e72801882..8de8d3969 100644 --- a/plugins/ext_test/tasks.py +++ b/plugins/ext_test/tasks.py @@ -144,7 +144,6 @@ def dist_clean(context): def clean_all(context): """Run all clean tasks""" # pylint: disable=unused-argument - pass namespace_clean.add_task(clean_all, 'all') diff --git a/tests/test_argparse.py b/tests/test_argparse.py index ad86ba81b..b6042dc23 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -293,9 +293,9 @@ def base_helpless(self, args): # This subcommand has aliases and no help text. It exists to prevent changes to _set_parser_prog() which # use an approach which relies on action._choices_actions list. See comment in that function for more # details. - parser_bar = base_subparsers.add_parser('helpless', aliases=['helpless_1', 'helpless_2']) - parser_bar.add_argument('z', help='string') - parser_bar.set_defaults(func=base_bar) + parser_helpless = base_subparsers.add_parser('helpless', aliases=['helpless_1', 'helpless_2']) + parser_helpless.add_argument('z', help='string') + parser_helpless.set_defaults(func=base_bar) @cmd2.with_argparser(base_parser) def do_base(self, args): diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 838d1b7c9..395738376 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -1263,10 +1263,10 @@ def _subcmd_no_custom(self, args: argparse.Namespace) -> None: pass # Parser for a subcommand with a custom completer type - custom_completer_parser = Cmd2ArgumentParser(description="Custom completer", ap_completer_type=CustomCompleter) - custom_completer_parser.add_argument('--myflag', complete_when_ready=True) + custom_completer_parser2 = Cmd2ArgumentParser(description="Custom completer", ap_completer_type=CustomCompleter) + custom_completer_parser2.add_argument('--myflag', complete_when_ready=True) - @cmd2.as_subcommand_to('top', 'custom', custom_completer_parser, help="custom completer") + @cmd2.as_subcommand_to('top', 'custom', custom_completer_parser2, help="custom completer") def _subcmd_custom(self, args: argparse.Namespace) -> None: pass diff --git a/tests_isolated/test_commandset/test_argparse_subcommands.py b/tests_isolated/test_commandset/test_argparse_subcommands.py index ad6b2828a..de68e5d94 100644 --- a/tests_isolated/test_commandset/test_argparse_subcommands.py +++ b/tests_isolated/test_commandset/test_argparse_subcommands.py @@ -50,9 +50,9 @@ def base_helpless(self, args): # This subcommand has aliases and no help text. It exists to prevent changes to _set_parser_prog() which # use an approach which relies on action._choices_actions list. See comment in that function for more # details. - parser_bar = base_subparsers.add_parser('helpless', aliases=['helpless_1', 'helpless_2']) - parser_bar.add_argument('z', help='string') - parser_bar.set_defaults(func=base_bar) + parser_helpless = base_subparsers.add_parser('helpless', aliases=['helpless_1', 'helpless_2']) + parser_helpless.add_argument('z', help='string') + parser_helpless.set_defaults(func=base_bar) @cmd2.with_argparser(base_parser) def do_base(self, args): From bbba0406988af2d60b98389a2040873746dd400d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 23:18:51 -0400 Subject: [PATCH 40/79] Enabled ruff PLW ruleset for PyLint warnings --- cmd2/argparse_completer.py | 2 +- cmd2/argparse_custom.py | 2 +- cmd2/cmd2.py | 4 ++-- cmd2/decorators.py | 2 +- cmd2/parsing.py | 5 +++-- cmd2/table_creator.py | 1 - cmd2/utils.py | 10 ++++++---- examples/async_printing.py | 4 ---- pyproject.toml | 13 +++++++++++-- 9 files changed, 25 insertions(+), 18 deletions(-) diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index cb591bc86..ac77160d4 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -775,5 +775,5 @@ def set_default_ap_completer_type(completer_type: Type[ArgparseCompleter]) -> No :param completer_type: Type that is a subclass of ArgparseCompleter. """ - global DEFAULT_AP_COMPLETER + global DEFAULT_AP_COMPLETER # noqa: PLW0603 DEFAULT_AP_COMPLETER = completer_type diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 2cc5e31e7..34e85b0b3 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1407,5 +1407,5 @@ def set_default_argument_parser_type(parser_type: type[argparse.ArgumentParser]) Set the default ArgumentParser class for a cmd2 app. This must be called prior to loading cmd2.py if you want to override the parser for cmd2's built-in commands. See examples/override_parser.py. """ - global DEFAULT_ARGUMENT_PARSER + global DEFAULT_ARGUMENT_PARSER # noqa: PLW0603 DEFAULT_ARGUMENT_PARSER = parser_type diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 7bd34231d..6dcb50efa 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -2653,7 +2653,7 @@ def runcmds_plus_hooks( """ for line in cmds: if isinstance(line, HistoryItem): - line = line.raw + line = line.raw # noqa: PLW2901 if self.echo: self.poutput(f'{self.prompt}{line}') @@ -4974,7 +4974,7 @@ def _generate_transcript( first = True command = '' if isinstance(history_item, HistoryItem): - history_item = history_item.raw + history_item = history_item.raw # noqa: PLW2901 for line in history_item.splitlines(): if first: command += f"{self.prompt}{line}\n" diff --git a/cmd2/decorators.py b/cmd2/decorators.py index fc0d34601..5f1b4f7a6 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -87,7 +87,7 @@ def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Stateme if isinstance(arg, (Cmd, CommandSet)) and len(args) > pos + 1: if isinstance(arg, CommandSet): - arg = arg._cmd + arg = arg._cmd # noqa: PLW2901 next_arg = args[pos + 1] if isinstance(next_arg, (Statement, str)): return arg, args[pos + 1] diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 11654072d..aea514dc1 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -660,11 +660,12 @@ def _expand(self, line: str) -> str: if line.startswith(shortcut): # If the next character after the shortcut isn't a space, then insert one shortcut_len = len(shortcut) + effective_expansion = expansion if len(line) == shortcut_len or line[shortcut_len] != ' ': - expansion += ' ' + effective_expansion += ' ' # Expand the shortcut - line = line.replace(shortcut, expansion, 1) + line = line.replace(shortcut, effective_expansion, 1) break return line diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index 04b44adef..340be3df0 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -894,7 +894,6 @@ def generate_row_bottom_border(self) -> str: if self.column_borders: inter_cell += '┼' inter_cell += self.padding * '─' - inter_cell = inter_cell post_line = self.padding * '─' + '╢' diff --git a/cmd2/utils.py b/cmd2/utils.py index a117491a9..d4e686655 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -376,12 +376,14 @@ def find_editor() -> Optional[str]: else: paths = [p for p in env_path.split(os.path.pathsep) if not os.path.islink(p)] - for editor, path in itertools.product(editors, paths): - editor_path = os.path.join(path, editor) + for possible_editor, path in itertools.product(editors, paths): + editor_path = os.path.join(path, possible_editor) if os.path.isfile(editor_path) and os.access(editor_path, os.X_OK): if sys.platform[:3] == 'win': # Remove extension from Windows file names - editor = os.path.splitext(editor)[0] + editor = os.path.splitext(possible_editor)[0] + else: + editor = possible_editor break else: editor = None @@ -902,7 +904,7 @@ def align_text( text_buf.write('\n') if truncate: - line = truncate_line(line, width) + line = truncate_line(line, width) # noqa: PLW2901 line_width = ansi.style_aware_wcswidth(line) if line_width == -1: diff --git a/examples/async_printing.py b/examples/async_printing.py index 3f2883c0b..5989f7329 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -90,8 +90,6 @@ def _get_alerts(self) -> list[str]: Reports alerts :return: the list of alerts """ - global ALERTS - cur_time = time.monotonic() if cur_time < self._next_alert_time: return [] @@ -121,8 +119,6 @@ def _generate_alert_str(self) -> str: Combines alerts into one string that can be printed to the terminal :return: the alert string """ - global ALERTS - alert_str = '' alerts = self._get_alerts() diff --git a/pyproject.toml b/pyproject.toml index abbe5a63f..9c8441405 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -198,7 +198,7 @@ select = [ "PLC", # Pylint Conventions "PLE", # Pylint Errors # "PLR", # Pylint Refactoring suggestions - # "PLW", # Pylint Warnings + "PLW", # Pylint Warnings # "PT", # flake8-pytest-style (warnings about unit test best practices) # "PTH", # flake8-use-pathlib (force use of pathlib instead of os.path) "PYI", # flake8-pyi (warnings related to type hint best practices) @@ -255,7 +255,8 @@ per-file-ignores."cmd2/__init__.py" = [ ] per-file-ignores."examples/*.py" = [ - "INP001", # Module is part of an implicit namespace + "INP001", # Module is part of an implicit namespace + "PLW2901", # loop variable overwritten inside loop ] per-file-ignores."examples/override_parser.py" = [ @@ -278,11 +279,19 @@ per-file-ignores."tests/*.py" = [ "E501", # Line too long ] +per-file-ignores."tests/test_argparse.py" = [ + "PLW2901", # loop variable overwritten inside loop +] + per-file-ignores."tests/pyscript/*.py" = [ "F821", # Undefined name `app` "INP001", # Module is part of an implicit namespace ] +per-file-ignores."tests_isolated/test_commandset/test_commandset.py" = [ + "PLW0603", # Using the global statement to update {name} is discouraged +] + [tool.ruff.format] # Like Black, use double quotes for strings. From 35dc644d9dc27f564f6c4cc75132541d29b25da2 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 23:21:17 -0400 Subject: [PATCH 41/79] Disable framework-specific ruff rulesets for Airflow, Django, FastAPI, Numpy, and Pandas cmd2 doesn't use any of these frameworks, so no point leaving them enabled. --- pyproject.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c8441405..0dfb4750c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,8 +156,8 @@ output-format = "full" # McCabe complexity (`C901`) by default. select = [ # https://docs.astral.sh/ruff/rules - "A", # flake8-builtins (variables or arguments shadowing built-ins) - "AIR", # Airflow specific warnings + "A", # flake8-builtins (variables or arguments shadowing built-ins) + # "AIR", # Airflow specific warnings # "ANN", # flake8-annotations (missing type annotations for arguments or return types) # "ARG", # flake8-unused-arguments (functions or methods with arguments that are never used) "ASYNC", # flake8-async (async await bugs) @@ -169,15 +169,15 @@ select = [ # "CPY", # flake8-copyright (warn about missing copyright notice at top of file - currently in preview) # "D", # pydocstyle (warn about things like missing docstrings) # "DOC", # pydoclint (docstring warnings - currently in preview) - "DJ", # flake8-django (Django-specific warnings) + # "DJ", # flake8-django (Django-specific warnings) "DTZ", # flake8-datetimez (warn about datetime calls where no timezone is specified) "E", # pycodestyle errors (warn about major stylistic issues like mixing spaces and tabs) # "EM", # flake8-errmsg (warn about exceptions that use string literals that aren't assigned to a variable first) # "ERA", # eradicate (warn about commented-out code) - "EXE", # flake8-executable (warn about files with a shebang present that aren't executable or vice versa) - "F", # Pyflakes (a bunch of common warnings for things like unused imports, imports shadowed by variables, etc) - "FA", # flake8-future-annotations (warn if certain from __future__ imports are used but missing) - "FAST", # FastAPI specific warnings + "EXE", # flake8-executable (warn about files with a shebang present that aren't executable or vice versa) + "F", # Pyflakes (a bunch of common warnings for things like unused imports, imports shadowed by variables, etc) + "FA", # flake8-future-annotations (warn if certain from __future__ imports are used but missing) + # "FAST", # FastAPI specific warnings # "FBT", # flake8-boolean-trap (force all boolean arguments passed to functions to be keyword arguments and not positional) "FIX", # flake8-fixme (warn about lines containing FIXME, TODO, XXX, or HACK) "FLY", # flynt (automatically convert from old school string .format to f-strings) @@ -190,8 +190,8 @@ select = [ "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) "LOG", # flake8-logging (warn about potential logger issues, but very pedantic) # "N", # pep8-naming (force idiomatic naming for classes, functions/methods, and variables/arguments) - "NPY", # NumPy specific rules - "PD", # pandas-vet (Pandas specific rules) + # "NPY", # NumPy specific rules + # "PD", # pandas-vet (Pandas specific rules) "PERF", # Perflint (warn about performance issues) "PGH", # pygrep-hooks (force specific rule codes when ignoring type or linter issues on a line) "PIE", # flake8-pie (eliminate unnecessary use of pass, range starting at 0, etc.) From 9d717951c2b4de0d1656857fbd6a6b0d4cea3cd4 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 23:25:12 -0400 Subject: [PATCH 42/79] Applied automated fixes from ruff PT pytest ruleset --- cmd2/transcript.py | 8 +- pyproject.toml | 2 +- tests/test_ansi.py | 4 +- tests/test_argparse_completer.py | 40 +++++---- tests/test_argparse_custom.py | 2 +- tests/test_cmd2.py | 19 ++-- tests/test_completion.py | 148 +++++++++++++++++++------------ tests/test_parsing.py | 52 ++++++----- tests/test_run_pyscript.py | 3 +- tests/test_transcript.py | 4 +- 10 files changed, 166 insertions(+), 116 deletions(-) diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 98a250f21..6d5c4de8b 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -108,9 +108,9 @@ def _test_transcript(self, fname: str, transcript: Iterator[str]) -> None: # Read the expected result from transcript if ansi.strip_style(line).startswith(self.cmdapp.visible_prompt): message = f'\nFile {fname}, line {line_num}\nCommand was:\n{command}\nExpected: (nothing)\nGot:\n{result}\n' - self.assertTrue(not (result.strip()), message) + assert not result.strip(), message # If the command signaled the application to quit there should be no more commands - self.assertFalse(stop, stop_msg) + assert not stop, stop_msg continue expected_parts = [] while not ansi.strip_style(line).startswith(self.cmdapp.visible_prompt): @@ -124,13 +124,13 @@ def _test_transcript(self, fname: str, transcript: Iterator[str]) -> None: if stop: # This should only be hit if the command that set stop to True had output text - self.assertTrue(finished, stop_msg) + assert finished, stop_msg # transform the expected text into a valid regular expression expected = ''.join(expected_parts) expected = self._transform_transcript_expected(expected) message = f'\nFile {fname}, line {line_num}\nCommand was:\n{command}\nExpected:\n{expected}\nGot:\n{result}\n' - self.assertTrue(re.match(expected, result, re.MULTILINE | re.DOTALL), message) + assert re.match(expected, result, re.MULTILINE | re.DOTALL), message def _transform_transcript_expected(self, s: str) -> str: r"""Parse the string with slashed regexes into a valid regex. diff --git a/pyproject.toml b/pyproject.toml index 0dfb4750c..87e01c061 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,7 +199,7 @@ select = [ "PLE", # Pylint Errors # "PLR", # Pylint Refactoring suggestions "PLW", # Pylint Warnings - # "PT", # flake8-pytest-style (warnings about unit test best practices) + # "PT", # flake8-pytest-style (warnings about unit test best practices) # "PTH", # flake8-use-pathlib (force use of pathlib instead of os.path) "PYI", # flake8-pyi (warnings related to type hint best practices) # "Q", # flake8-quotes (force double quotes) diff --git a/tests/test_ansi.py b/tests/test_ansi.py index 13e9c856e..c5c1252dc 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -148,7 +148,7 @@ def test_set_title(): @pytest.mark.parametrize( - 'cols, prompt, line, cursor, msg, expected', + ('cols', 'prompt', 'line', 'cursor', 'msg', 'expected'), [ ( 127, @@ -230,7 +230,7 @@ def test_sequence_str_building(ansi_sequence): @pytest.mark.parametrize( - 'r, g, b, valid', + ('r', 'g', 'b', 'valid'), [ (0, 0, 0, True), (255, 255, 255, True), diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 395738376..a010bad17 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -361,7 +361,7 @@ def test_bad_subcommand_help(ac_app): @pytest.mark.parametrize( - 'command, text, completions', + ('command', 'text', 'completions'), [ ('', 'mus', ['music ']), ('music', 'cre', ['create ']), @@ -388,7 +388,7 @@ def test_complete_help(ac_app, command, text, completions): @pytest.mark.parametrize( - 'subcommand, text, completions', + ('subcommand', 'text', 'completions'), [('create', '', ['jazz', 'rock']), ('create', 'ja', ['jazz ']), ('create', 'foo', []), ('creab', 'ja', [])], ) def test_subcommand_completions(ac_app, subcommand, text, completions): @@ -406,7 +406,7 @@ def test_subcommand_completions(ac_app, subcommand, text, completions): @pytest.mark.parametrize( - 'command_and_args, text, completion_matches, display_matches', + ('command_and_args', 'text', 'completion_matches', 'display_matches'), [ # Complete all flags (suppressed will not show) ( @@ -547,13 +547,12 @@ def test_autcomp_flag_completion(ac_app, command_and_args, text, completion_matc else: assert first_match is None - assert ac_app.completion_matches == sorted( - completion_matches, key=ac_app.default_sort_key - ) and ac_app.display_matches == sorted(display_matches, key=ac_app.default_sort_key) + assert ac_app.completion_matches == sorted(completion_matches, key=ac_app.default_sort_key) + assert ac_app.display_matches == sorted(display_matches, key=ac_app.default_sort_key) @pytest.mark.parametrize( - 'flag, text, completions', + ('flag', 'text', 'completions'), [ ('-l', '', ArgparseCompleterTester.static_choices_list), ('--list', 's', ['static', 'stop']), @@ -588,7 +587,7 @@ def test_autocomp_flag_choices_completion(ac_app, flag, text, completions): @pytest.mark.parametrize( - 'pos, text, completions', + ('pos', 'text', 'completions'), [ (1, '', ArgparseCompleterTester.static_choices_list), (1, 's', ['static', 'stop']), @@ -639,11 +638,12 @@ def test_flag_sorting(ac_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, ac_app) - assert first_match is not None and ac_app.completion_matches == option_strings + assert first_match is not None + assert ac_app.completion_matches == option_strings @pytest.mark.parametrize( - 'flag, text, completions', + ('flag', 'text', 'completions'), [('-c', '', ArgparseCompleterTester.completions_for_flag), ('--completer', 'f', ['flag', 'fairly'])], ) def test_autocomp_flag_completers(ac_app, flag, text, completions): @@ -661,7 +661,7 @@ def test_autocomp_flag_completers(ac_app, flag, text, completions): @pytest.mark.parametrize( - 'pos, text, completions', + ('pos', 'text', 'completions'), [ (1, '', ArgparseCompleterTester.completions_for_pos_1), (1, 'p', ['positional_1', 'probably']), @@ -763,7 +763,7 @@ def test_completion_items(ac_app): @pytest.mark.parametrize( - 'num_aliases, show_description', + ('num_aliases', 'show_description'), [ # The number of completion results determines if the description field of CompletionItems gets displayed # in the tab completions. The count must be greater than 1 and less than ac_app.max_completion_items, @@ -803,7 +803,7 @@ def test_max_completion_items(ac_app, num_aliases, show_description): @pytest.mark.parametrize( - 'args, completions', + ('args', 'completions'), [ # Flag with nargs = 2 ('--set_value', ArgparseCompleterTester.set_value_choices), @@ -869,7 +869,7 @@ def test_autcomp_nargs(ac_app, args, completions): @pytest.mark.parametrize( - 'command_and_args, text, is_error', + ('command_and_args', 'text', 'is_error'), [ # Flag is finished before moving on ('hint --flag foo --', '', False), @@ -986,7 +986,7 @@ def test_completion_items_descriptive_header(ac_app): @pytest.mark.parametrize( - 'command_and_args, text, has_hint', + ('command_and_args', 'text', 'has_hint'), [ # Normal cases ('hint', '', True), @@ -1045,7 +1045,7 @@ def test_autocomp_hint_no_help_text(ac_app, capsys): @pytest.mark.parametrize( - 'args, text', + ('args', 'text'), [ # Exercise a flag arg and choices function that raises a CompletionError ('--choice ', 'choice'), @@ -1066,7 +1066,7 @@ def test_completion_error(ac_app, capsys, args, text): @pytest.mark.parametrize( - 'command_and_args, completions', + ('command_and_args', 'completions'), [ # Exercise a choices function that receives arg_tokens dictionary ('arg_tokens choice subcmd', ['choice', 'subcmd']), @@ -1092,7 +1092,7 @@ def test_arg_tokens(ac_app, command_and_args, completions): @pytest.mark.parametrize( - 'command_and_args, text, output_contains, first_match', + ('command_and_args', 'text', 'output_contains', 'first_match'), [ # Group isn't done. Hint will show for optional positional and no completions returned ('mutex', '', 'the optional positional', None), @@ -1188,7 +1188,9 @@ def test_complete_command_help_no_tokens(ac_app): assert not completions -@pytest.mark.parametrize('flag, completions', [('--provider', standalone_choices), ('--completer', standalone_completions)]) +@pytest.mark.parametrize( + ('flag', 'completions'), [('--provider', standalone_choices), ('--completer', standalone_completions)] +) def test_complete_standalone(ac_app, flag, completions): text = '' line = 'standalone {} {}'.format(flag, text) diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index 1e764b0de..16105daaa 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -49,7 +49,7 @@ def fake_func(): @pytest.mark.parametrize( - 'kwargs, is_valid', + ('kwargs', 'is_valid'), [ ({'choices_provider': fake_func}, True), ({'completer': fake_func}, True), diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 183b54ade..1ce04f62d 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -221,7 +221,7 @@ def test_set_no_settables(base_app): @pytest.mark.parametrize( - 'new_val, is_valid, expected', + ('new_val', 'is_valid', 'expected'), [ (ansi.AllowStyle.NEVER, True, ansi.AllowStyle.NEVER), ('neVeR', True, ansi.AllowStyle.NEVER), @@ -391,7 +391,8 @@ def test_run_script_with_empty_file(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'empty.txt') out, err = run_cmd(base_app, 'run_script {}'.format(filename)) - assert not out and not err + assert not out + assert not err assert base_app.last_result is True @@ -741,7 +742,8 @@ def test_pipe_to_shell(base_app): command = 'help help | wc' out, err = run_cmd(base_app, command) - assert out and not err + assert out + assert not err def test_pipe_to_shell_and_redirect(base_app): @@ -755,7 +757,8 @@ def test_pipe_to_shell_and_redirect(base_app): command = 'help help | wc > {}'.format(filename) out, err = run_cmd(base_app, command) - assert not out and not err + assert not out + assert not err assert os.path.exists(filename) os.remove(filename) @@ -807,7 +810,8 @@ def test_get_paste_buffer_exception(base_app, mocker, capsys): out, err = capsys.readouterr() assert out == '' # this just checks that cmd2 is surfacing whatever error gets raised by pyperclip.paste - assert 'ValueError' in err and 'foo' in err + assert 'ValueError' in err + assert 'foo' in err def test_allow_clipboard_initializer(base_app): @@ -1493,7 +1497,7 @@ def test_select_uneven_list_of_tuples(select_app, monkeypatch): @pytest.mark.parametrize( - 'selection, type_str', + ('selection', 'type_str'), [ ('1', ""), ('2', ""), @@ -2889,7 +2893,8 @@ def test_disable_and_enable_category(disable_commands_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, disable_commands_app) - assert first_match is not None and disable_commands_app.completion_matches == ['result '] + assert first_match is not None + assert disable_commands_app.completion_matches == ['result '] # has_no_helper_funcs had no completer originally, so there should be no results text = '' diff --git a/tests/test_completion.py b/tests/test_completion.py index 751188020..7324582d6 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -144,7 +144,8 @@ def test_complete_command_single(cmd2_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == ['help '] + assert first_match is not None + assert cmd2_app.completion_matches == ['help '] def test_complete_empty_arg(cmd2_app): @@ -156,7 +157,8 @@ def test_complete_empty_arg(cmd2_app): expected = sorted(cmd2_app.get_visible_commands(), key=cmd2_app.default_sort_key) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected def test_complete_bogus_command(cmd2_app): @@ -167,7 +169,8 @@ def test_complete_bogus_command(cmd2_app): expected = ['default '] first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected def test_complete_exception(cmd2_app, capsys): @@ -199,7 +202,8 @@ def test_complete_macro(base_app, request): expected = [text + 'cript.py', text + 'cript.txt', text + 'cripts' + os.path.sep] first_match = complete_tester(text, line, begidx, endidx, base_app) - assert first_match is not None and base_app.completion_matches == expected + assert first_match is not None + assert base_app.completion_matches == expected def test_default_sort_key(cmd2_app): @@ -212,13 +216,15 @@ def test_default_sort_key(cmd2_app): cmd2_app.default_sort_key = cmd2.Cmd.ALPHABETICAL_SORT_KEY expected = ['1', '11', '2'] first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected # Now switch to natural sorting cmd2_app.default_sort_key = cmd2.Cmd.NATURAL_SORT_KEY expected = ['1', '2', '11'] first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected def test_cmd2_command_completion_multiple(cmd2_app): @@ -246,7 +252,8 @@ def test_cmd2_help_completion_single(cmd2_app): first_match = complete_tester(text, line, begidx, endidx, cmd2_app) # It is at end of line, so extra space is present - assert first_match is not None and cmd2_app.completion_matches == ['help '] + assert first_match is not None + assert cmd2_app.completion_matches == ['help '] def test_cmd2_help_completion_multiple(cmd2_app): @@ -256,7 +263,8 @@ def test_cmd2_help_completion_multiple(cmd2_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == ['help', 'history'] + assert first_match is not None + assert cmd2_app.completion_matches == ['help', 'history'] def test_cmd2_help_completion_nomatch(cmd2_app): @@ -315,7 +323,9 @@ def test_shell_command_completion_shortcut(cmd2_app): begidx = 0 first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected and cmd2_app.display_matches == expected_display + assert first_match is not None + assert cmd2_app.completion_matches == expected + assert cmd2_app.display_matches == expected_display def test_shell_command_completion_doesnt_match_wildcards(cmd2_app): @@ -345,7 +355,8 @@ def test_shell_command_completion_multiple(cmd2_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and expected in cmd2_app.completion_matches + assert first_match is not None + assert expected in cmd2_app.completion_matches def test_shell_command_completion_nomatch(cmd2_app): @@ -378,7 +389,8 @@ def test_shell_command_completion_does_path_completion_when_after_command(cmd2_a begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == [text + '.py '] + assert first_match is not None + assert cmd2_app.completion_matches == [text + '.py '] def test_shell_command_complete_in_path(cmd2_app, request): @@ -394,7 +406,8 @@ def test_shell_command_complete_in_path(cmd2_app, request): # we expect to see the scripts dir among the results expected = os.path.join(test_dir, 'scripts' + os.path.sep) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and expected in cmd2_app.completion_matches + assert first_match is not None + assert expected in cmd2_app.completion_matches def test_path_completion_single_end(cmd2_app, request): @@ -454,7 +467,8 @@ def test_default_to_shell_completion(cmd2_app, request): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == [text + '.py '] + assert first_match is not None + assert cmd2_app.completion_matches == [text + '.py '] def test_path_completion_no_text(cmd2_app): @@ -804,7 +818,8 @@ def test_add_opening_quote_basic_no_text(cmd2_app): # The whole list will be returned with no opening quotes added first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == sorted(food_item_strs, key=cmd2_app.default_sort_key) + assert first_match is not None + assert cmd2_app.completion_matches == sorted(food_item_strs, key=cmd2_app.default_sort_key) def test_add_opening_quote_basic_nothing_added(cmd2_app): @@ -814,7 +829,8 @@ def test_add_opening_quote_basic_nothing_added(cmd2_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == ['Pizza', 'Potato'] + assert first_match is not None + assert cmd2_app.completion_matches == ['Pizza', 'Potato'] def test_add_opening_quote_basic_quote_added(cmd2_app): @@ -825,7 +841,8 @@ def test_add_opening_quote_basic_quote_added(cmd2_app): expected = sorted(['"Ham', '"Ham Sandwich'], key=cmd2_app.default_sort_key) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected def test_add_opening_quote_basic_single_quote_added(cmd2_app): @@ -836,7 +853,8 @@ def test_add_opening_quote_basic_single_quote_added(cmd2_app): expected = ["'Cheese \"Pizza\"' "] first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected def test_add_opening_quote_basic_text_is_common_prefix(cmd2_app): @@ -848,7 +866,8 @@ def test_add_opening_quote_basic_text_is_common_prefix(cmd2_app): expected = sorted(['"Ham', '"Ham Sandwich'], key=cmd2_app.default_sort_key) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected def test_add_opening_quote_delimited_no_text(cmd2_app): @@ -859,7 +878,8 @@ def test_add_opening_quote_delimited_no_text(cmd2_app): # The whole list will be returned with no opening quotes added first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == sorted(delimited_strs, key=cmd2_app.default_sort_key) + assert first_match is not None + assert cmd2_app.completion_matches == sorted(delimited_strs, key=cmd2_app.default_sort_key) def test_add_opening_quote_delimited_nothing_added(cmd2_app): @@ -872,11 +892,9 @@ def test_add_opening_quote_delimited_nothing_added(cmd2_app): expected_display = sorted(['other user', 'user'], key=cmd2_app.default_sort_key) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert ( - first_match is not None - and cmd2_app.completion_matches == expected_matches - and cmd2_app.display_matches == expected_display - ) + assert first_match is not None + assert cmd2_app.completion_matches == expected_matches + assert cmd2_app.display_matches == expected_display def test_add_opening_quote_delimited_quote_added(cmd2_app): @@ -889,11 +907,9 @@ def test_add_opening_quote_delimited_quote_added(cmd2_app): expected_display = sorted(['file.txt', 'file space.txt'], key=cmd2_app.default_sort_key) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert ( - first_match is not None - and os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix - and cmd2_app.display_matches == expected_display - ) + assert first_match is not None + assert os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix + assert cmd2_app.display_matches == expected_display def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app): @@ -907,11 +923,9 @@ def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app): expected_display = sorted(['file.txt', 'file space.txt'], key=cmd2_app.default_sort_key) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert ( - first_match is not None - and os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix - and cmd2_app.display_matches == expected_display - ) + assert first_match is not None + assert os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix + assert cmd2_app.display_matches == expected_display def test_add_opening_quote_delimited_space_in_prefix(cmd2_app): @@ -925,11 +939,9 @@ def test_add_opening_quote_delimited_space_in_prefix(cmd2_app): expected_display = ['maps', 'tests'] first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert ( - first_match is not None - and os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix - and cmd2_app.display_matches == expected_display - ) + assert first_match is not None + assert os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix + assert cmd2_app.display_matches == expected_display def test_no_completer(cmd2_app): @@ -940,7 +952,8 @@ def test_no_completer(cmd2_app): expected = ['default '] first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected def test_wordbreak_in_command(cmd2_app): @@ -950,7 +963,8 @@ def test_wordbreak_in_command(cmd2_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is None and not cmd2_app.completion_matches + assert first_match is None + assert not cmd2_app.completion_matches def test_complete_multiline_on_single_line(cmd2_app): @@ -962,7 +976,8 @@ def test_complete_multiline_on_single_line(cmd2_app): expected = sorted(sport_item_strs, key=cmd2_app.default_sort_key) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected def test_complete_multiline_on_multiple_lines(cmd2_app): @@ -978,7 +993,8 @@ def test_complete_multiline_on_multiple_lines(cmd2_app): expected = sorted(['Bat', 'Basket', 'Basketball'], key=cmd2_app.default_sort_key) first_match = complete_tester(text, line, begidx, endidx, cmd2_app) - assert first_match is not None and cmd2_app.completion_matches == expected + assert first_match is not None + assert cmd2_app.completion_matches == expected # Used by redirect_complete tests @@ -990,7 +1006,7 @@ class RedirCompType(enum.Enum): @pytest.mark.parametrize( - 'line, comp_type', + ('line', 'comp_type'), [ ('fake', RedirCompType.DEFAULT), ('fake arg', RedirCompType.DEFAULT), @@ -1092,7 +1108,8 @@ def test_cmd2_subcommand_completion_single_end(sc_app): first_match = complete_tester(text, line, begidx, endidx, sc_app) # It is at end of line, so extra space is present - assert first_match is not None and sc_app.completion_matches == ['foo '] + assert first_match is not None + assert sc_app.completion_matches == ['foo '] def test_cmd2_subcommand_completion_multiple(sc_app): @@ -1102,7 +1119,8 @@ def test_cmd2_subcommand_completion_multiple(sc_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, sc_app) - assert first_match is not None and sc_app.completion_matches == ['bar', 'foo', 'sport'] + assert first_match is not None + assert sc_app.completion_matches == ['bar', 'foo', 'sport'] def test_cmd2_subcommand_completion_nomatch(sc_app): @@ -1124,7 +1142,8 @@ def test_help_subcommand_completion_single(sc_app): first_match = complete_tester(text, line, begidx, endidx, sc_app) # It is at end of line, so extra space is present - assert first_match is not None and sc_app.completion_matches == ['base '] + assert first_match is not None + assert sc_app.completion_matches == ['base '] def test_help_subcommand_completion_multiple(sc_app): @@ -1134,7 +1153,8 @@ def test_help_subcommand_completion_multiple(sc_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, sc_app) - assert first_match is not None and sc_app.completion_matches == ['bar', 'foo', 'sport'] + assert first_match is not None + assert sc_app.completion_matches == ['bar', 'foo', 'sport'] def test_help_subcommand_completion_nomatch(sc_app): @@ -1157,7 +1177,8 @@ def test_subcommand_tab_completion(sc_app): first_match = complete_tester(text, line, begidx, endidx, sc_app) # It is at end of line, so extra space is present - assert first_match is not None and sc_app.completion_matches == ['Football '] + assert first_match is not None + assert sc_app.completion_matches == ['Football '] def test_subcommand_tab_completion_with_no_completer(sc_app): @@ -1180,7 +1201,9 @@ def test_subcommand_tab_completion_space_in_text(sc_app): first_match = complete_tester(text, line, begidx, endidx, sc_app) - assert first_match is not None and sc_app.completion_matches == ['Ball" '] and sc_app.display_matches == ['Space Ball'] + assert first_match is not None + assert sc_app.completion_matches == ['Ball" '] + assert sc_app.display_matches == ['Space Ball'] #################################################### @@ -1257,7 +1280,8 @@ def test_subcmd_with_unknown_completion_single_end(scu_app): print('first_match: {}'.format(first_match)) # It is at end of line, so extra space is present - assert first_match is not None and scu_app.completion_matches == ['foo '] + assert first_match is not None + assert scu_app.completion_matches == ['foo '] def test_subcmd_with_unknown_completion_multiple(scu_app): @@ -1267,7 +1291,8 @@ def test_subcmd_with_unknown_completion_multiple(scu_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, scu_app) - assert first_match is not None and scu_app.completion_matches == ['bar', 'foo', 'sport'] + assert first_match is not None + assert scu_app.completion_matches == ['bar', 'foo', 'sport'] def test_subcmd_with_unknown_completion_nomatch(scu_app): @@ -1289,7 +1314,8 @@ def test_help_subcommand_completion_single_scu(scu_app): first_match = complete_tester(text, line, begidx, endidx, scu_app) # It is at end of line, so extra space is present - assert first_match is not None and scu_app.completion_matches == ['base '] + assert first_match is not None + assert scu_app.completion_matches == ['base '] def test_help_subcommand_completion_multiple_scu(scu_app): @@ -1299,7 +1325,8 @@ def test_help_subcommand_completion_multiple_scu(scu_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, scu_app) - assert first_match is not None and scu_app.completion_matches == ['bar', 'foo', 'sport'] + assert first_match is not None + assert scu_app.completion_matches == ['bar', 'foo', 'sport'] def test_help_subcommand_completion_with_flags_before_command(scu_app): @@ -1309,7 +1336,8 @@ def test_help_subcommand_completion_with_flags_before_command(scu_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, scu_app) - assert first_match is not None and scu_app.completion_matches == ['bar', 'foo', 'sport'] + assert first_match is not None + assert scu_app.completion_matches == ['bar', 'foo', 'sport'] def test_complete_help_subcommands_with_blank_command(scu_app): @@ -1319,7 +1347,8 @@ def test_complete_help_subcommands_with_blank_command(scu_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, scu_app) - assert first_match is None and not scu_app.completion_matches + assert first_match is None + assert not scu_app.completion_matches def test_help_subcommand_completion_nomatch_scu(scu_app): @@ -1342,7 +1371,8 @@ def test_subcommand_tab_completion_scu(scu_app): first_match = complete_tester(text, line, begidx, endidx, scu_app) # It is at end of line, so extra space is present - assert first_match is not None and scu_app.completion_matches == ['Football '] + assert first_match is not None + assert scu_app.completion_matches == ['Football '] def test_subcommand_tab_completion_with_no_completer_scu(scu_app): @@ -1365,4 +1395,6 @@ def test_subcommand_tab_completion_space_in_text_scu(scu_app): first_match = complete_tester(text, line, begidx, endidx, scu_app) - assert first_match is not None and scu_app.completion_matches == ['Ball" '] and scu_app.display_matches == ['Space Ball'] + assert first_match is not None + assert scu_app.completion_matches == ['Ball" '] + assert scu_app.display_matches == ['Space Ball'] diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 9f99198db..cfe22c4a0 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -79,7 +79,7 @@ def test_parse_empty_string_default(default_parser): @pytest.mark.parametrize( - 'line,tokens', + ('line', 'tokens'), [ ('command', ['command']), (constants.COMMENT_CHAR + 'comment', []), @@ -97,7 +97,7 @@ def test_tokenize_default(default_parser, line, tokens): @pytest.mark.parametrize( - 'line,tokens', + ('line', 'tokens'), [ ('command', ['command']), ('# comment', []), @@ -123,7 +123,8 @@ def test_tokenize_unclosed_quotes(parser): @pytest.mark.parametrize( - 'tokens,command,args', [([], '', ''), (['command'], 'command', ''), (['command', 'arg1', 'arg2'], 'command', 'arg1 arg2')] + ('tokens', 'command', 'args'), + [([], '', ''), (['command'], 'command', ''), (['command', 'arg1', 'arg2'], 'command', 'arg1 arg2')], ) def test_command_and_args(parser, tokens, command, args): (parsed_command, parsed_args) = parser._command_and_args(tokens) @@ -157,7 +158,7 @@ def test_parse_single_word(parser, line): @pytest.mark.parametrize( - 'line,terminator', + ('line', 'terminator'), [ ('termbare;', ';'), ('termbare ;', ';'), @@ -176,7 +177,7 @@ def test_parse_word_plus_terminator(parser, line, terminator): @pytest.mark.parametrize( - 'line,terminator', + ('line', 'terminator'), [ ('termbare; suffx', ';'), ('termbare ;suffx', ';'), @@ -290,7 +291,7 @@ def test_parse_complex_pipe(parser): @pytest.mark.parametrize( - 'line,output', + ('line', 'output'), [ ('help > out.txt', '>'), ('help>out.txt', '>'), @@ -515,7 +516,7 @@ def test_parse_redirect_inside_terminator(parser): @pytest.mark.parametrize( - 'line,terminator', + ('line', 'terminator'), [ ('multiline with | inside;', ';'), ('multiline with | inside ;', ';'), @@ -563,7 +564,7 @@ def test_parse_basic_multiline_command(parser): @pytest.mark.parametrize( - 'line,terminator', + ('line', 'terminator'), [ ('multiline has > inside;', ';'), ('multiline has > inside;;;', ';'), @@ -595,7 +596,7 @@ def test_parse_multiline_terminated_by_empty_line(parser): @pytest.mark.parametrize( - 'line,terminator', + ('line', 'terminator'), [ ('multiline command "with\nembedded newline";', ';'), ('multiline command "with\nembedded newline";;;', ';'), @@ -675,7 +676,7 @@ def test_empty_statement_raises_exception(): @pytest.mark.parametrize( - 'line,command,args', + ('line', 'command', 'args'), [ ('helpalias', 'help', ''), ('helpalias mycommand', 'help', 'mycommand'), @@ -704,7 +705,7 @@ def test_parse_alias_on_multiline_command(parser): @pytest.mark.parametrize( - 'line,output', + ('line', 'output'), [ ('helpalias > out.txt', '>'), ('helpalias>out.txt', '>'), @@ -862,7 +863,7 @@ def test_parse_command_only_unclosed_quote(parser): @pytest.mark.parametrize( - 'line,args', + ('line', 'args'), [ ('helpalias > out.txt', '> out.txt'), ('helpalias>out.txt', '>out.txt'), @@ -987,39 +988,48 @@ def test_statement_as_dict(parser): def test_is_valid_command_invalid(mocker, parser): # Non-string command valid, errmsg = parser.is_valid_command(5) - assert not valid and 'must be a string' in errmsg + assert not valid + assert 'must be a string' in errmsg mock = mocker.MagicMock() valid, errmsg = parser.is_valid_command(mock) - assert not valid and 'must be a string' in errmsg + assert not valid + assert 'must be a string' in errmsg # Empty command valid, errmsg = parser.is_valid_command('') - assert not valid and 'cannot be an empty string' in errmsg + assert not valid + assert 'cannot be an empty string' in errmsg # Start with the comment character valid, errmsg = parser.is_valid_command(constants.COMMENT_CHAR) - assert not valid and 'cannot start with the comment character' in errmsg + assert not valid + assert 'cannot start with the comment character' in errmsg # Starts with shortcut valid, errmsg = parser.is_valid_command('!ls') - assert not valid and 'cannot start with a shortcut' in errmsg + assert not valid + assert 'cannot start with a shortcut' in errmsg # Contains whitespace valid, errmsg = parser.is_valid_command('shell ls') - assert not valid and 'cannot contain: whitespace, quotes,' in errmsg + assert not valid + assert 'cannot contain: whitespace, quotes,' in errmsg # Contains a quote valid, errmsg = parser.is_valid_command('"shell"') - assert not valid and 'cannot contain: whitespace, quotes,' in errmsg + assert not valid + assert 'cannot contain: whitespace, quotes,' in errmsg # Contains a redirector valid, errmsg = parser.is_valid_command('>shell') - assert not valid and 'cannot contain: whitespace, quotes,' in errmsg + assert not valid + assert 'cannot contain: whitespace, quotes,' in errmsg # Contains a terminator valid, errmsg = parser.is_valid_command(';shell') - assert not valid and 'cannot contain: whitespace, quotes,' in errmsg + assert not valid + assert 'cannot contain: whitespace, quotes,' in errmsg def test_is_valid_command_valid(parser): diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index 5af739981..a7db63bb0 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -103,7 +103,8 @@ def test_run_pyscript_help(base_app, request): python_script = os.path.join(test_dir, 'pyscript', 'help.py') out1, err1 = run_cmd(base_app, 'help') out2, err2 = run_cmd(base_app, 'run_pyscript {}'.format(python_script)) - assert out1 and out1 == out2 + assert out1 + assert out1 == out2 def test_scripts_add_to_history(base_app, request): diff --git a/tests/test_transcript.py b/tests/test_transcript.py index f8d1c90d1..9cce1f54e 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -106,7 +106,7 @@ def test_commands_at_invocation(): @pytest.mark.parametrize( - 'filename,feedback_to_output', + ('filename', 'feedback_to_output'), [ ('bol_eol.txt', False), ('characterclass.txt', False), @@ -253,7 +253,7 @@ def test_generate_transcript_stop(capsys): @pytest.mark.parametrize( - 'expected, transformed', + ('expected', 'transformed'), [ # strings with zero or one slash or with escaped slashes means no regular # expression present, so the result should just be what re.escape returns. From bab4e673b65b6be20784c620dc7d2d38f655c23d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 23:27:37 -0400 Subject: [PATCH 43/79] isort fixes --- plugins/ext_test/examples/example.py | 4 ++-- plugins/ext_test/tests/test_ext_test.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/ext_test/examples/example.py b/plugins/ext_test/examples/example.py index 7dbb6677e..a0b5bdf37 100644 --- a/plugins/ext_test/examples/example.py +++ b/plugins/ext_test/examples/example.py @@ -1,11 +1,11 @@ # # coding=utf-8 # import cmd2 -import cmd2_ext_test - import cmd2 import cmd2.py_bridge +import cmd2_ext_test + class Example(cmd2.Cmd): """An class to show how to use a plugin""" diff --git a/plugins/ext_test/tests/test_ext_test.py b/plugins/ext_test/tests/test_ext_test.py index 037157f10..82816dfbd 100644 --- a/plugins/ext_test/tests/test_ext_test.py +++ b/plugins/ext_test/tests/test_ext_test.py @@ -1,14 +1,14 @@ # # coding=utf-8 -import cmd2_ext_test import pytest - from cmd2 import ( CommandResult, cmd2, ) +import cmd2_ext_test + ###### # # define a class which implements a simple cmd2 application From 72ff51c1f9fd8a3e8858b6d5f58e76d6358590d1 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Fri, 23 May 2025 23:49:28 -0400 Subject: [PATCH 44/79] Fixed a bunch of unit test best practices warnings from ruff PT ruleset --- plugins/ext_test/examples/example.py | 4 ++-- plugins/ext_test/tests/test_ext_test.py | 4 ++-- tests/conftest.py | 6 ++--- tests/test_ansi.py | 15 +++++++----- tests/test_argparse_custom.py | 32 ++++++++++++------------- tests/test_cmd2.py | 4 ++-- 6 files changed, 33 insertions(+), 32 deletions(-) diff --git a/plugins/ext_test/examples/example.py b/plugins/ext_test/examples/example.py index a0b5bdf37..7dbb6677e 100644 --- a/plugins/ext_test/examples/example.py +++ b/plugins/ext_test/examples/example.py @@ -1,11 +1,11 @@ # # coding=utf-8 # import cmd2 +import cmd2_ext_test + import cmd2 import cmd2.py_bridge -import cmd2_ext_test - class Example(cmd2.Cmd): """An class to show how to use a plugin""" diff --git a/plugins/ext_test/tests/test_ext_test.py b/plugins/ext_test/tests/test_ext_test.py index 82816dfbd..037157f10 100644 --- a/plugins/ext_test/tests/test_ext_test.py +++ b/plugins/ext_test/tests/test_ext_test.py @@ -1,14 +1,14 @@ # # coding=utf-8 +import cmd2_ext_test import pytest + from cmd2 import ( CommandResult, cmd2, ) -import cmd2_ext_test - ###### # # define a class which implements a simple cmd2 application diff --git a/tests/conftest.py b/tests/conftest.py index bbd332962..87d9f1fa6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,9 +16,7 @@ mock, ) -from pytest import ( - fixture, -) +import pytest import cmd2 from cmd2.rl_utils import ( @@ -151,7 +149,7 @@ def run_cmd(app, cmd): return normalize(out), normalize(err) -@fixture +@pytest.fixture def base_app(): return cmd2.Cmd(include_py=True, include_ipy=True) diff --git a/tests/test_ansi.py b/tests/test_ansi.py index c5c1252dc..e5bf5fe8b 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -179,11 +179,12 @@ def test_clear_screen(): assert ansi.clear_screen(clear_type) == f"{ansi.CSI}{clear_type}J" clear_type = -1 - with pytest.raises(ValueError): + expected_err = "clear_type must in an integer from 0 to 3" + with pytest.raises(ValueError, match=expected_err): ansi.clear_screen(clear_type) clear_type = 4 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=expected_err): ansi.clear_screen(clear_type) @@ -192,11 +193,12 @@ def test_clear_line(): assert ansi.clear_line(clear_type) == f"{ansi.CSI}{clear_type}K" clear_type = -1 - with pytest.raises(ValueError): + expected_err = "clear_type must in an integer from 0 to 2" + with pytest.raises(ValueError, match=expected_err): ansi.clear_line(clear_type) clear_type = 3 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=expected_err): ansi.clear_line(clear_type) @@ -247,9 +249,10 @@ def test_rgb_bounds(r, g, b, valid): ansi.RgbFg(r, g, b) ansi.RgbBg(r, g, b) else: - with pytest.raises(ValueError): + expected_err = "RGB values must be integers in the range of 0 to 255" + with pytest.raises(ValueError, match=expected_err): ansi.RgbFg(r, g, b) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=expected_err): ansi.RgbBg(r, g, b) diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index 16105daaa..bcb7e5a80 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -58,26 +58,26 @@ def fake_func(): ) def test_apcustom_choices_callable_count(kwargs, is_valid): parser = Cmd2ArgumentParser() - try: + if is_valid: parser.add_argument('name', **kwargs) - assert is_valid - except ValueError as ex: - assert not is_valid - assert 'Only one of the following parameters' in str(ex) + else: + expected_err = 'Only one of the following parameters' + with pytest.raises(ValueError, match=expected_err): + parser.add_argument('name', **kwargs) @pytest.mark.parametrize('kwargs', [({'choices_provider': fake_func}), ({'completer': fake_func})]) def test_apcustom_no_choices_callables_alongside_choices(kwargs): + parser = Cmd2ArgumentParser() with pytest.raises(TypeError) as excinfo: - parser = Cmd2ArgumentParser() parser.add_argument('name', choices=['my', 'choices', 'list'], **kwargs) assert 'None of the following parameters can be used alongside a choices parameter' in str(excinfo.value) @pytest.mark.parametrize('kwargs', [({'choices_provider': fake_func}), ({'completer': fake_func})]) def test_apcustom_no_choices_callables_when_nargs_is_0(kwargs): + parser = Cmd2ArgumentParser() with pytest.raises(TypeError) as excinfo: - parser = Cmd2ArgumentParser() parser.add_argument('--name', action='store_true', **kwargs) assert 'None of the following parameters can be used on an action that takes no arguments' in str(excinfo.value) @@ -126,24 +126,24 @@ def test_apcustom_nargs_range_validation(cust_app): ], ) def test_apcustom_narg_invalid_tuples(nargs_tuple): - with pytest.raises(ValueError) as excinfo: - parser = Cmd2ArgumentParser() + parser = Cmd2ArgumentParser() + expected_err = 'Ranged values for nargs must be a tuple of 1 or 2 integers' + with pytest.raises(ValueError, match=expected_err): parser.add_argument('invalid_tuple', nargs=nargs_tuple) - assert 'Ranged values for nargs must be a tuple of 1 or 2 integers' in str(excinfo.value) def test_apcustom_narg_tuple_order(): - with pytest.raises(ValueError) as excinfo: - parser = Cmd2ArgumentParser() + parser = Cmd2ArgumentParser() + expected_err = 'Invalid nargs range. The first value must be less than the second' + with pytest.raises(ValueError, match=expected_err): parser.add_argument('invalid_tuple', nargs=(2, 1)) - assert 'Invalid nargs range. The first value must be less than the second' in str(excinfo.value) def test_apcustom_narg_tuple_negative(): - with pytest.raises(ValueError) as excinfo: - parser = Cmd2ArgumentParser() + parser = Cmd2ArgumentParser() + expected_err = 'Negative numbers are invalid for nargs range' + with pytest.raises(ValueError, match=expected_err): parser.add_argument('invalid_tuple', nargs=(-1, 1)) - assert 'Negative numbers are invalid for nargs range' in str(excinfo.value) def test_apcustom_narg_tuple_zero_base(): diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 1ce04f62d..042f6b119 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -146,9 +146,9 @@ def test_base_shortcuts(base_app): def test_command_starts_with_shortcut(): - with pytest.raises(ValueError) as excinfo: + expected_err = "Invalid command name 'help'" + with pytest.raises(ValueError, match=expected_err): cmd2.Cmd(shortcuts={'help': 'fake'}) - assert "Invalid command name 'help'" in str(excinfo.value) def test_base_set(base_app): From 1c6207561e9893f83c02ff0f3757f9d183418294 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 00:52:12 -0400 Subject: [PATCH 45/79] Enabled ruff PT ruleset for pytest best practices --- .../ext_test/cmd2_ext_test/cmd2_ext_test.py | 3 +- pyproject.toml | 2 +- tests/test_cmd2.py | 13 +++--- tests/test_history.py | 12 +++-- tests/test_plugin.py | 4 +- tests/test_table_creator.py | 45 +++++++------------ tests/test_utils.py | 14 +++--- tests_isolated/test_commandset/conftest.py | 10 ++--- .../test_commandset/test_commandset.py | 5 ++- 9 files changed, 50 insertions(+), 58 deletions(-) diff --git a/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py b/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py index c176bb788..d3df9e250 100644 --- a/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py +++ b/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py @@ -39,7 +39,8 @@ def app_cmd(self, command: str, echo: Optional[bool] = None) -> cmd2.CommandResu :param echo: Flag whether the command's output should be echoed to stdout/stderr :return: A CommandResult object that captures stdout, stderr, and the command's result object """ - assert isinstance(self, cmd2.Cmd) and isinstance(self, ExternalTestMixin) + assert isinstance(self, cmd2.Cmd) + assert isinstance(self, ExternalTestMixin) try: self._in_py = True diff --git a/pyproject.toml b/pyproject.toml index 87e01c061..049db803e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,7 +199,7 @@ select = [ "PLE", # Pylint Errors # "PLR", # Pylint Refactoring suggestions "PLW", # Pylint Warnings - # "PT", # flake8-pytest-style (warnings about unit test best practices) + "PT", # flake8-pytest-style (warnings about unit test best practices) # "PTH", # flake8-use-pathlib (force use of pathlib instead of os.path) "PYI", # flake8-pyi (warnings related to type hint best practices) # "Q", # flake8-quotes (force double quotes) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 042f6b119..628d58602 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -622,15 +622,16 @@ def test_passthrough_exception_in_command(base_app): """Test raising a PassThroughException in a command""" import types + expected_err = "Pass me up" + def do_passthrough(self, _): - wrapped_ex = OSError("Pass me up") + wrapped_ex = OSError(expected_err) raise exceptions.PassThroughException(wrapped_ex=wrapped_ex) setattr(base_app, 'do_passthrough', types.MethodType(do_passthrough, base_app)) - with pytest.raises(OSError) as excinfo: + with pytest.raises(OSError, match=expected_err): base_app.onecmd_plus_hooks('passthrough') - assert 'Pass me up' in str(excinfo.value) def test_output_redirection(base_app): @@ -1033,7 +1034,7 @@ def test_cmdloop_without_rawinput(): expected = app.intro + '\n' - with pytest.raises(OSError): + with pytest.raises(OSError): # noqa: PT011 app.cmdloop() out = app.stdout.getvalue() assert out == expected @@ -1794,11 +1795,11 @@ def test_commandresult_falsy(commandresult_app): def test_is_text_file_bad_input(base_app): # Test with a non-existent file - with pytest.raises(OSError): + with pytest.raises(FileNotFoundError): utils.is_text_file('does_not_exist.txt') # Test with a directory - with pytest.raises(OSError): + with pytest.raises(IsADirectoryError): utils.is_text_file('.') diff --git a/tests/test_history.py b/tests/test_history.py index 9b1b31ddc..58123a5d7 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -225,8 +225,9 @@ def test_history_class_span(hist): assert span[3].statement.raw == 'third' value_errors = ['fred', 'fred:joe', '2', '-2', 'a..b', '2 ..', '1 : 3', '1:0', '0:3'] + expected_err = "History indices must be positive or negative integers, and may not be zero." for tryit in value_errors: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=expected_err): hist.span(tryit) @@ -275,8 +276,9 @@ def test_persisted_history_span(persisted_hist): assert span[5].statement.raw == 'fifth' value_errors = ['fred', 'fred:joe', '2', '-2', 'a..b', '2 ..', '1 : 3', '1:0', '0:3'] + expected_err = "History indices must be positive or negative integers, and may not be zero." for tryit in value_errors: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=expected_err): persisted_hist.span(tryit) @@ -358,7 +360,8 @@ def test_history_from_json(hist): invalid_ver_json = hist.to_json() History._history_version = backed_up_ver - with pytest.raises(ValueError): + expected_err = "Unsupported history file version: BAD_VERSION. This application uses version 1.0.0." + with pytest.raises(ValueError, match=expected_err): hist.from_json(invalid_ver_json) @@ -652,7 +655,8 @@ def test_history_with_span_index_error(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'help history') run_cmd(base_app, '!ls -hal :') - with pytest.raises(ValueError): + expected_err = "History indices must be positive or negative integers, and may not be zero." + with pytest.raises(ValueError, match=expected_err): base_app.onecmd('history "hal :"') diff --git a/tests/test_plugin.py b/tests/test_plugin.py index d7abe443d..8991ad902 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -949,9 +949,9 @@ def test_cmdfinalization_hook_passthrough_exception(): app = PluggedApp() app.register_cmdfinalization_hook(app.cmdfinalization_hook_passthrough_exception) - with pytest.raises(OSError) as excinfo: + expected_err = "Pass me up" + with pytest.raises(OSError, match=expected_err): app.onecmd_plus_hooks('say hello') - assert 'Pass me up' in str(excinfo.value) assert app.called_cmdfinalization == 1 diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py index c3f1d1881..1e9cf5e17 100644 --- a/tests/test_table_creator.py +++ b/tests/test_table_creator.py @@ -27,18 +27,16 @@ def test_column_creation(): # Width less than 1 - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Column width cannot be less than 1"): Column("Column 1", width=0) - assert "Column width cannot be less than 1" in str(excinfo.value) # Width specified c = Column("header", width=20) assert c.width == 20 # max_data_lines less than 1 - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Max data lines cannot be less than 1"): Column("Column 1", max_data_lines=0) - assert "Max data lines cannot be less than 1" in str(excinfo.value) # No width specified, blank label c = Column("") @@ -311,15 +309,13 @@ def test_generate_row_exceptions(): # Unprintable characters for arg in ['fill_char', 'pre_line', 'inter_cell', 'post_line']: kwargs = {arg: '\n'} - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match=f"{arg} contains an unprintable character"): tc.generate_row(row_data=row_data, is_header=False, **kwargs) - assert "{} contains an unprintable character".format(arg) in str(excinfo.value) # Data with too many columns row_data = ['Data 1', 'Extra Column'] - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Length of row_data must match length of cols"): tc.generate_row(row_data=row_data, is_header=False) - assert "Length of row_data must match length of cols" in str(excinfo.value) def test_tabs(): @@ -332,9 +328,8 @@ def test_tabs(): row = tc.generate_row(row_data, is_header=True, fill_char='\t', pre_line='\t', inter_cell='\t', post_line='\t') assert row == ' Col 1 Col 2 ' - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Tab width cannot be less than 1" ): TableCreator([column_1, column_2], tab_width=0) - assert "Tab width cannot be less than 1" in str(excinfo.value) def test_simple_table_creation(): @@ -439,24 +434,20 @@ def test_simple_table_creation(): ) # Invalid column spacing - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Column spacing cannot be less than 0"): SimpleTable([column_1, column_2], column_spacing=-1) - assert "Column spacing cannot be less than 0" in str(excinfo.value) # Invalid divider character - with pytest.raises(TypeError) as excinfo: + with pytest.raises(TypeError, match="Divider character must be exactly one character long"): SimpleTable([column_1, column_2], divider_char='too long') - assert "Divider character must be exactly one character long" in str(excinfo.value) - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Divider character is an unprintable character"): SimpleTable([column_1, column_2], divider_char='\n') - assert "Divider character is an unprintable character" in str(excinfo.value) # Invalid row spacing st = SimpleTable([column_1, column_2]) - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Row spacing cannot be less than 0"): st.generate_table(row_data, row_spacing=-1) - assert "Row spacing cannot be less than 0" in str(excinfo.value) # Test header and data colors st = SimpleTable([column_1, column_2], divider_char=None, header_bg=Bg.GREEN, data_bg=Bg.LIGHT_BLUE) @@ -487,9 +478,8 @@ def test_simple_table_width(): assert SimpleTable.base_width(num_cols) == (num_cols - 1) * 2 # Invalid num_cols value - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Column count cannot be less than 1"): SimpleTable.base_width(0) - assert "Column count cannot be less than 1" in str(excinfo.value) # Total width column_1 = Column("Col 1", width=16) @@ -509,9 +499,8 @@ def test_simple_generate_data_row_exceptions(): # Data with too many columns row_data = ['Data 1', 'Extra Column'] - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Length of row_data must match length of cols"): tc.generate_data_row(row_data=row_data) - assert "Length of row_data must match length of cols" in str(excinfo.value) def test_bordered_table_creation(): @@ -573,9 +562,8 @@ def test_bordered_table_creation(): ) # Invalid padding - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Padding cannot be less than 0"): BorderedTable([column_1, column_2], padding=-1) - assert "Padding cannot be less than 0" in str(excinfo.value) # Test border, header, and data colors bt = BorderedTable([column_1, column_2], border_fg=Fg.LIGHT_YELLOW, border_bg=Bg.WHITE, @@ -629,9 +617,8 @@ def test_bordered_table_width(): assert BorderedTable.base_width(3, padding=3) == 22 # Invalid num_cols value - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Column count cannot be less than 1"): BorderedTable.base_width(0) - assert "Column count cannot be less than 1" in str(excinfo.value) # Total width column_1 = Column("Col 1", width=15) @@ -651,9 +638,8 @@ def test_bordered_generate_data_row_exceptions(): # Data with too many columns row_data = ['Data 1', 'Extra Column'] - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Length of row_data must match length of cols"): tc.generate_data_row(row_data=row_data) - assert "Length of row_data must match length of cols" in str(excinfo.value) def test_alternating_table_creation(): @@ -711,9 +697,8 @@ def test_alternating_table_creation(): ) # Invalid padding - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError, match="Padding cannot be less than 0"): AlternatingTable([column_1, column_2], padding=-1) - assert "Padding cannot be less than 0" in str(excinfo.value) # Test border, header, and data colors at = AlternatingTable([column_1, column_2], border_fg=Fg.LIGHT_YELLOW, border_bg=Bg.WHITE, diff --git a/tests/test_utils.py b/tests/test_utils.py index ba71814bc..6cc3d719d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -329,7 +329,7 @@ def test_context_flag_bool(context_flag): def test_context_flag_exit_err(context_flag): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="count has gone below 0"): context_flag.__exit__() @@ -423,14 +423,14 @@ def test_truncate_line_already_fits(): def test_truncate_line_with_newline(): line = 'fo\no' max_width = 2 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="text contains an unprintable character"): cu.truncate_line(line, max_width) def test_truncate_line_width_is_too_small(): line = 'foo' max_width = 0 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="max_width must be at least 1"): cu.truncate_line(line, max_width) @@ -548,7 +548,7 @@ def test_align_text_width_is_too_small(): text = 'foo' fill_char = '-' width = 0 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="width must be at least 1"): cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width) @@ -564,7 +564,7 @@ def test_align_text_fill_char_is_newline(): text = 'foo' fill_char = '\n' width = 5 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Fill character is an unprintable character"): cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width) @@ -613,7 +613,7 @@ def test_align_text_has_unprintable(): text = 'foo\x02' fill_char = '-' width = 5 - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Text to align contains an unprintable character"): cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width) @@ -830,7 +830,7 @@ def test_to_bool_str_false(): def test_to_bool_str_invalid(): - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 cu.to_bool('other') diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 353bf907d..b1375ae14 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -15,12 +15,10 @@ mock, ) +import pytest from cmd2_ext_test import ( ExternalTestMixin, ) -from pytest import ( - fixture, -) import cmd2 from cmd2.rl_utils import ( @@ -134,7 +132,7 @@ def run_cmd(app, cmd): return normalize(out), normalize(err) -@fixture +@pytest.fixture def base_app(): return cmd2.Cmd() @@ -183,13 +181,13 @@ def __init__(self, *args, **kwargs): super(WithCommandSets, self).__init__(*args, **kwargs) -@fixture +@pytest.fixture def command_sets_app(): app = WithCommandSets() return app -@fixture +@pytest.fixture def command_sets_manual(): app = WithCommandSets(auto_load_commands=False) return app diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 093eda64f..950ed7737 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -1149,7 +1149,10 @@ def __init__(self): cmdset_nopfx = WithSettablesNoPrefix() app.register_command_set(cmdset_nopfx) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="Cannot force settable prefixes. CommandSet WithSettablesNoPrefix does not have a settable prefix defined.", + ): app.always_prefix_settables = True app.unregister_command_set(cmdset_nopfx) From a2da48a5083d274bdc31ac4a009f4e9896c16f9c Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 00:58:37 -0400 Subject: [PATCH 46/79] Try to fix failing test on Windows --- tests/test_cmd2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 628d58602..12c57b615 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1796,7 +1796,7 @@ def test_commandresult_falsy(commandresult_app): def test_is_text_file_bad_input(base_app): # Test with a non-existent file with pytest.raises(FileNotFoundError): - utils.is_text_file('does_not_exist.txt') + utils.is_text_file('./does_not_exist.txt') # Test with a directory with pytest.raises(IsADirectoryError): From 05dc6fbf381596765ff40829cab520a3fc2dff60 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 01:10:25 -0400 Subject: [PATCH 47/79] Try to fix path on Windows so test passes --- tests/test_cmd2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 12c57b615..8ab43228d 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -5,6 +5,7 @@ import builtins import io import os +import pathlib import signal import sys import tempfile @@ -1795,8 +1796,11 @@ def test_commandresult_falsy(commandresult_app): def test_is_text_file_bad_input(base_app): # Test with a non-existent file + home_dir = pathlib.Path.home() + file_path = home_dir / 'does_not_exist' + file_str = f'{file_path}' with pytest.raises(FileNotFoundError): - utils.is_text_file('./does_not_exist.txt') + utils.is_text_file(file_str) # Test with a directory with pytest.raises(IsADirectoryError): From c54c9eaa2a2484d6425e520881b31eef81be0ab9 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 01:18:31 -0400 Subject: [PATCH 48/79] Allow two different types of exceptions for one test since windows test runners have a different one --- tests/test_cmd2.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 8ab43228d..3aafb8ee4 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -5,7 +5,6 @@ import builtins import io import os -import pathlib import signal import sys import tempfile @@ -1795,12 +1794,9 @@ def test_commandresult_falsy(commandresult_app): def test_is_text_file_bad_input(base_app): - # Test with a non-existent file - home_dir = pathlib.Path.home() - file_path = home_dir / 'does_not_exist' - file_str = f'{file_path}' - with pytest.raises(FileNotFoundError): - utils.is_text_file(file_str) + # Test with a non-existent file - on GitHub Actions Windows test runners, we get a PermissionError + with pytest.raises((FileNotFoundError, PermissionError)): + utils.is_text_file('does_not_exist.txt') # Test with a directory with pytest.raises(IsADirectoryError): From 2e308080e14b63c9e15ead8246f86daa3113dfd2 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 01:25:51 -0400 Subject: [PATCH 49/79] Split test with if for windows vs other --- tests/test_cmd2.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 3aafb8ee4..11c0e6d65 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1795,8 +1795,15 @@ def test_commandresult_falsy(commandresult_app): def test_is_text_file_bad_input(base_app): # Test with a non-existent file - on GitHub Actions Windows test runners, we get a PermissionError - with pytest.raises((FileNotFoundError, PermissionError)): - utils.is_text_file('does_not_exist.txt') + file_name = 'does_not_exist.txt' + if sys.platform.startswith('win'): + # For Windows, depending on setup you might get a FileNotFoundError or a PermissionError + with pytest.raises(OSError): # noqa: PT011 + utils.is_text_file(file_name) + else: + # For Linux or macOS you should reliably get a FileNotFoundError + with pytest.raises(FileNotFoundError): + utils.is_text_file(file_name) # Test with a directory with pytest.raises(IsADirectoryError): From 9184b5ff50a2d9f5db9f68d01c4ded04e7b7c5c6 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 01:30:40 -0400 Subject: [PATCH 50/79] Split problematic test into two separate tests and skip one on Windows --- tests/test_cmd2.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 11c0e6d65..f9a55351d 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1793,18 +1793,14 @@ def test_commandresult_falsy(commandresult_app): assert commandresult_app.last_result == cmd2.CommandResult('', arg) -def test_is_text_file_bad_input(base_app): - # Test with a non-existent file - on GitHub Actions Windows test runners, we get a PermissionError - file_name = 'does_not_exist.txt' - if sys.platform.startswith('win'): - # For Windows, depending on setup you might get a FileNotFoundError or a PermissionError - with pytest.raises(OSError): # noqa: PT011 - utils.is_text_file(file_name) - else: - # For Linux or macOS you should reliably get a FileNotFoundError - with pytest.raises(FileNotFoundError): - utils.is_text_file(file_name) +@pytest.mark.skipif(sys.platform.startswith('win'), reason="Test is unreliable on GitHub Actions Windows runners") +def test_is_text_file_bad_no_exist(base_app): + # Test with a non-existent file + with pytest.raises(FileNotFoundError): + utils.is_text_file('does_not_exist.txt') + +def test_is_text_file_bad_is_dir(base_app): # Test with a directory with pytest.raises(IsADirectoryError): utils.is_text_file('.') From 74b21c81bc381eae5517b23b01b45e3cf34f7d44 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 01:33:18 -0400 Subject: [PATCH 51/79] Recombine test and skip both parts on Windows --- tests/test_cmd2.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index f9a55351d..1cc5504fc 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1793,14 +1793,12 @@ def test_commandresult_falsy(commandresult_app): assert commandresult_app.last_result == cmd2.CommandResult('', arg) -@pytest.mark.skipif(sys.platform.startswith('win'), reason="Test is unreliable on GitHub Actions Windows runners") -def test_is_text_file_bad_no_exist(base_app): +@pytest.mark.skipif(sys.platform.startswith('win'), reason="Test is problematic on GitHub Actions Windows runners") +def test_is_text_file_bad_input(base_app): # Test with a non-existent file with pytest.raises(FileNotFoundError): utils.is_text_file('does_not_exist.txt') - -def test_is_text_file_bad_is_dir(base_app): # Test with a directory with pytest.raises(IsADirectoryError): utils.is_text_file('.') From 884758d35c93ebef257e0dba8004869b087e7f0d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 01:47:16 -0400 Subject: [PATCH 52/79] Cleaned up remaining import of Dict and List from Typing --- cmd2/__init__.py | 3 +- cmd2/ansi.py | 4 +-- cmd2/exceptions.py | 4 +-- cmd2/plugin.py | 4 +-- cmd2/rl_utils.py | 4 +-- .../modular_commands/commandset_complex.py | 9 ++--- examples/modular_commands_main.py | 4 +-- examples/scripts/save_help_text.py | 7 ++-- examples/table_creation.py | 4 +-- plugins/template/cmd2_myplugin/myplugin.py | 4 +-- tests/test_argparse.py | 4 +-- tests/test_argparse_completer.py | 34 ++++++++----------- tests/test_utils.py | 6 +--- .../test_commandset/test_categories.py | 4 +-- .../test_commandset/test_commandset.py | 15 ++++---- 15 files changed, 38 insertions(+), 72 deletions(-) diff --git a/cmd2/__init__.py b/cmd2/__init__.py index e512a1966..d3a0c7211 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -12,7 +12,6 @@ # Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER. # Do this before loading cmd2.Cmd class so its commands use the custom parser. import argparse -from typing import List from .ansi import ( Bg, @@ -56,7 +55,7 @@ from .py_bridge import CommandResult from .utils import CompletionMode, CustomCompletionSettings, Settable, categorize -__all__: List[str] = [ +__all__: list[str] = [ 'COMMAND_NAME', 'DEFAULT_SHORTCUTS', # ANSI Exports diff --git a/cmd2/ansi.py b/cmd2/ansi.py index f75ef186c..f5765bfde 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -986,10 +986,10 @@ def style( :raises TypeError: if bg isn't None or a subclass of BgColor :return: the stylized string """ - # List of strings that add style + # list of strings that add style additions: list[AnsiSequence] = [] - # List of strings that remove style + # list of strings that remove style removals: list[AnsiSequence] = [] # Process the style settings diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index e11590ac7..98adff191 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -1,8 +1,6 @@ """Custom exceptions for cmd2""" -from typing import ( - Any, -) +from typing import Any ############################################################################################################ # The following exceptions are part of the public API diff --git a/cmd2/plugin.py b/cmd2/plugin.py index bae35befe..e6f5ac7d1 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -3,9 +3,7 @@ from dataclasses import ( dataclass, ) -from typing import ( - Optional, -) +from typing import Optional from .parsing import ( Statement, diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 4f98bb023..7f3e452a8 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -6,9 +6,7 @@ from enum import ( Enum, ) -from typing import ( - Union, -) +from typing import Union ######################################################################################################################### # NOTE ON LIBEDIT: diff --git a/examples/modular_commands/commandset_complex.py b/examples/modular_commands/commandset_complex.py index c01979c94..e0acb621d 100644 --- a/examples/modular_commands/commandset_complex.py +++ b/examples/modular_commands/commandset_complex.py @@ -3,9 +3,6 @@ """ import argparse -from typing import ( - List, -) import cmd2 @@ -23,7 +20,7 @@ def do_banana(self, statement: cmd2.Statement): cranberry_parser.add_argument('arg1', choices=['lemonade', 'juice', 'sauce']) @cmd2.with_argparser(cranberry_parser, with_unknown_args=True) - def do_cranberry(self, ns: argparse.Namespace, unknown: List[str]): + def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]): self._cmd.poutput('Cranberry {}!!'.format(ns.arg1)) if unknown and len(unknown): self._cmd.poutput('Unknown: ' + ', '.join(['{}'] * len(unknown)).format(*unknown)) @@ -34,12 +31,12 @@ def help_cranberry(self): @cmd2.with_argument_list @cmd2.with_category('Also Alone') - def do_durian(self, args: List[str]): + def do_durian(self, args: list[str]): """Durian Command""" self._cmd.poutput('{} Arguments: '.format(len(args))) self._cmd.poutput(', '.join(['{}'] * len(args)).format(*args)) - def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return self._cmd.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting']) elderberry_parser = cmd2.Cmd2ArgumentParser() diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 25138db83..202eedc57 100755 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -6,9 +6,7 @@ import argparse from collections.abc import Iterable -from typing import ( - Optional, -) +from typing import Optional from modular_commands.commandset_basic import ( # noqa: F401 BasicCompletionCommandSet, diff --git a/examples/scripts/save_help_text.py b/examples/scripts/save_help_text.py index ff787368f..5312d86d4 100644 --- a/examples/scripts/save_help_text.py +++ b/examples/scripts/save_help_text.py @@ -6,15 +6,12 @@ import argparse import os import sys -from typing import ( - List, - TextIO, -) +from typing import TextIO ASTERISKS = "********************************************************" -def get_sub_commands(parser: argparse.ArgumentParser) -> List[str]: +def get_sub_commands(parser: argparse.ArgumentParser) -> list[str]: """Get a list of subcommands for an ArgumentParser""" sub_cmds = [] diff --git a/examples/table_creation.py b/examples/table_creation.py index aea30c459..6181e390c 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -3,9 +3,7 @@ import functools import sys -from typing import ( - Any, -) +from typing import Any from cmd2 import ( EightBitBg, diff --git a/plugins/template/cmd2_myplugin/myplugin.py b/plugins/template/cmd2_myplugin/myplugin.py index 7d36e1d1b..b5a91ca24 100644 --- a/plugins/template/cmd2_myplugin/myplugin.py +++ b/plugins/template/cmd2_myplugin/myplugin.py @@ -3,9 +3,7 @@ import functools from collections.abc import Callable -from typing import ( - TYPE_CHECKING, -) +from typing import TYPE_CHECKING import cmd2 diff --git a/tests/test_argparse.py b/tests/test_argparse.py index b6042dc23..251f083f6 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -3,9 +3,7 @@ """ import argparse -from typing import ( - Optional, -) +from typing import Optional import pytest diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index a010bad17..3d26ad428 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -4,11 +4,7 @@ import argparse import numbers -from typing import ( - Dict, - List, - cast, -) +from typing import cast import pytest @@ -37,11 +33,11 @@ standalone_completions = ['standalone', 'completer'] -def standalone_choice_provider(cli: cmd2.Cmd) -> List[str]: +def standalone_choice_provider(cli: cmd2.Cmd) -> list[str]: return standalone_choices -def standalone_completer(cli: cmd2.Cmd, text: str, line: str, begidx: int, endidx: int) -> List[str]: +def standalone_completer(cli: cmd2.Cmd, text: str, line: str, begidx: int, endidx: int) -> list[str]: return cli.basic_complete(text, line, begidx, endidx, standalone_completions) @@ -113,7 +109,7 @@ def do_pos_and_flag(self, args: argparse.Namespace) -> None: TUPLE_METAVAR = ('arg1', 'others') CUSTOM_DESC_HEADER = "Custom Header" - # Lists used in our tests (there is a mix of sorted and unsorted on purpose) + # lists used in our tests (there is a mix of sorted and unsorted on purpose) non_negative_num_choices = [1, 2, 3, 0.5, 22] num_choices = [-1, 1, -2, 2.5, 0, -12] static_choices_list = ['static', 'choices', 'stop', 'here'] @@ -123,11 +119,11 @@ def do_pos_and_flag(self, args: argparse.Namespace) -> None: # This tests that CompletionItems created with numerical values are sorted as numbers. num_completion_items = [CompletionItem(5, "Five"), CompletionItem(1.5, "One.Five"), CompletionItem(2, "Five")] - def choices_provider(self) -> List[str]: + def choices_provider(self) -> list[str]: """Method that provides choices""" return self.choices_from_provider - def completion_item_method(self) -> List[CompletionItem]: + def completion_item_method(self) -> list[CompletionItem]: """Choices method that returns CompletionItems""" items = [] for i in range(10): @@ -189,13 +185,13 @@ def do_choices(self, args: argparse.Namespace) -> None: completions_for_pos_1 = ['completions', 'positional_1', 'probably', 'missed', 'spot'] completions_for_pos_2 = ['completions', 'positional_2', 'probably', 'missed', 'me'] - def flag_completer(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def flag_completer(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return self.basic_complete(text, line, begidx, endidx, self.completions_for_flag) - def pos_1_completer(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def pos_1_completer(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return self.basic_complete(text, line, begidx, endidx, self.completions_for_pos_1) - def pos_2_completer(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def pos_2_completer(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return self.basic_complete(text, line, begidx, endidx, self.completions_for_pos_2) completer_parser = Cmd2ArgumentParser() @@ -263,11 +259,11 @@ def do_hint(self, args: argparse.Namespace) -> None: ############################################################################################################ # Begin code related to CompletionError ############################################################################################################ - def completer_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def completer_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """Raises CompletionError""" raise CompletionError('completer broke something') - def choice_raise_error(self) -> List[str]: + def choice_raise_error(self) -> list[str]: """Raises CompletionError""" raise CompletionError('choice broke something') @@ -282,13 +278,13 @@ def do_raise_completion_error(self, args: argparse.Namespace) -> None: ############################################################################################################ # Begin code related to receiving arg_tokens ############################################################################################################ - def choices_takes_arg_tokens(self, arg_tokens: Dict[str, List[str]]) -> List[str]: + def choices_takes_arg_tokens(self, arg_tokens: dict[str, list[str]]) -> list[str]: """Choices function that receives arg_tokens from ArgparseCompleter""" return [arg_tokens['parent_arg'][0], arg_tokens['subcommand'][0]] def completer_takes_arg_tokens( - self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Dict[str, List[str]] - ) -> List[str]: + self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] + ) -> list[str]: """Completer function that receives arg_tokens from ArgparseCompleter""" match_against = [arg_tokens['parent_arg'][0], arg_tokens['subcommand'][0]] return self.basic_complete(text, line, begidx, endidx, match_against) @@ -1204,7 +1200,7 @@ def test_complete_standalone(ac_app, flag, completions): # Custom ArgparseCompleter-based class class CustomCompleter(argparse_completer.ArgparseCompleter): - def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matched_flags: List[str]) -> List[str]: + def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matched_flags: list[str]) -> list[str]: """Override so flags with 'complete_when_ready' set to True will complete only when app is ready""" # Find flags which should not be completed and place them in matched_flags diff --git a/tests/test_utils.py b/tests/test_utils.py index 6cc3d719d..5511cd00d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -334,10 +334,6 @@ def test_context_flag_exit_err(context_flag): def test_remove_overridden_styles(): - from typing import ( - List, - ) - from cmd2 import ( Bg, EightBitBg, @@ -348,7 +344,7 @@ def test_remove_overridden_styles(): TextStyle, ) - def make_strs(styles_list: List[ansi.AnsiSequence]) -> List[str]: + def make_strs(styles_list: list[ansi.AnsiSequence]) -> list[str]: return [str(s) for s in styles_list] # Test Reset All diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index bdbc4efec..58d886e21 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -2,9 +2,7 @@ Simple example demonstrating basic CommandSet usage. """ -from typing import ( - Any, -) +from typing import Any import cmd2 from cmd2 import ( diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 950ed7737..967e7d9ce 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -4,9 +4,6 @@ import argparse import signal -from typing import ( - List, -) import pytest @@ -59,7 +56,7 @@ def do_banana(self, statement: cmd2.Statement): cranberry_parser.add_argument('arg1', choices=['lemonade', 'juice', 'sauce']) @cmd2.with_argparser(cranberry_parser, with_unknown_args=True) - def do_cranberry(self, ns: argparse.Namespace, unknown: List[str]): + def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]): self._cmd.poutput('Cranberry {}!!'.format(ns.arg1)) if unknown and len(unknown): self._cmd.poutput('Unknown: ' + ', '.join(['{}'] * len(unknown)).format(*unknown)) @@ -70,13 +67,13 @@ def help_cranberry(self): @cmd2.with_argument_list @cmd2.with_category('Also Alone') - def do_durian(self, args: List[str]): + def do_durian(self, args: list[str]): """Durian Command""" self._cmd.poutput('{} Arguments: '.format(len(args))) self._cmd.poutput(', '.join(['{}'] * len(args)).format(*args)) self._cmd.last_result = {'args': args} - def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return self._cmd.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting']) elderberry_parser = cmd2.Cmd2ArgumentParser() @@ -502,7 +499,7 @@ def __init__(self, dummy): def do_arugula(self, _: cmd2.Statement): self._cmd.poutput('Arugula') - def complete_style_arg(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_style_arg(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return ['quartered', 'diced'] bokchoy_parser = cmd2.Cmd2ArgumentParser() @@ -740,7 +737,7 @@ def cut_banana(self, ns: argparse.Namespace): """Cut banana""" self.poutput('cutting banana: ' + ns.direction) - def complete_style_arg(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_style_arg(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return ['quartered', 'diced'] bokchoy_parser = cmd2.Cmd2ArgumentParser() @@ -795,7 +792,7 @@ def __init__(self, dummy): """dummy variable prevents this from being autoloaded in other tests""" super(SupportFuncProvider, self).__init__() - def complete_states(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: + def complete_states(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: assert self is complete_states_expected_self return self._cmd.basic_complete(text, line, begidx, endidx, self.states) From 2c56efaf05d768b2b69361e944c9b7c7143093b5 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 01:53:34 -0400 Subject: [PATCH 53/79] Enable ruff Q ruleset for quote checking, though many rules are ignored to prevent conflict with ruff formatter --- cmd2/cmd2.py | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 6dcb50efa..f298222c8 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -3488,7 +3488,7 @@ def _alias_delete(self, args: argparse.Namespace) -> None: @as_subcommand_to('alias', 'list', alias_list_parser, help=alias_list_help) def _alias_list(self, args: argparse.Namespace) -> None: """List some or all aliases as 'alias create' commands""" - self.last_result = {} # Dict[alias_name, alias_value] + self.last_result = {} # dict[alias_name, alias_value] tokens_to_quote = constants.REDIRECTION_TOKENS tokens_to_quote.extend(self.statement_parser.terminators) @@ -3723,7 +3723,7 @@ def _macro_delete(self, args: argparse.Namespace) -> None: @as_subcommand_to('macro', 'list', macro_list_parser, help=macro_list_help) def _macro_list(self, args: argparse.Namespace) -> None: """List some or all macros as 'macro create' commands""" - self.last_result = {} # Dict[macro_name, macro_value] + self.last_result = {} # dict[macro_name, macro_value] tokens_to_quote = constants.REDIRECTION_TOKENS tokens_to_quote.extend(self.statement_parser.terminators) @@ -4215,7 +4215,7 @@ def do_set(self, args: argparse.Namespace) -> None: self.poutput(table.generate_header()) # Build the table and populate self.last_result - self.last_result = {} # Dict[settable_name, settable_value] + self.last_result = {} # dict[settable_name, settable_value] for param in sorted(to_show, key=self.default_sort_key): settable = self.settables[param] diff --git a/pyproject.toml b/pyproject.toml index 049db803e..9cad3d68d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -202,7 +202,7 @@ select = [ "PT", # flake8-pytest-style (warnings about unit test best practices) # "PTH", # flake8-use-pathlib (force use of pathlib instead of os.path) "PYI", # flake8-pyi (warnings related to type hint best practices) - # "Q", # flake8-quotes (force double quotes) + "Q", # flake8-quotes (force double quotes) # "RET", # flake8-return (various warnings related to implicit vs explicit return statements) "RSE", # flake8-raise (warn about unnecessary parentheses on raised exceptions) # "RUF", # Ruff-specific rules (miscellaneous grab bag of lint checks specific to Ruff) From d503ef9af52a80e47cedd6fc3cd75dd20a411b66 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 02:06:21 -0400 Subject: [PATCH 54/79] Enabled ruff SLOT ruleset --- cmd2/argparse_custom.py | 2 +- cmd2/parsing.py | 2 +- pyproject.toml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 34e85b0b3..79d865155 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -275,7 +275,7 @@ def generate_range_error(range_min: int, range_max: float) -> str: return err_str -class CompletionItem(str): +class CompletionItem(str): # noqa: SLOT000 """ Completion item with descriptive text attached diff --git a/cmd2/parsing.py b/cmd2/parsing.py index aea514dc1..b189e8f40 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -84,7 +84,7 @@ class Macro: @dataclass(frozen=True) -class Statement(str): # type: ignore[override] +class Statement(str): # type: ignore[override] # noqa: SLOT000 """String subclass with additional attributes to store the results of parsing. The ``cmd`` module in the standard library passes commands around as a diff --git a/pyproject.toml b/pyproject.toml index 9cad3d68d..3cfe0c943 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -200,7 +200,7 @@ select = [ # "PLR", # Pylint Refactoring suggestions "PLW", # Pylint Warnings "PT", # flake8-pytest-style (warnings about unit test best practices) - # "PTH", # flake8-use-pathlib (force use of pathlib instead of os.path) + # "PTH", # flake8-use-pathlib (force use of pathlib instead of os.path) "PYI", # flake8-pyi (warnings related to type hint best practices) "Q", # flake8-quotes (force double quotes) # "RET", # flake8-return (various warnings related to implicit vs explicit return statements) @@ -209,8 +209,8 @@ select = [ # "S", # flake8-bandit (security oriented checks, but extremely pedantic - do not attempt to apply to unit test files) # "SIM", # flake8-simplify (rules to attempt to simplify code) # "SLF", # flake8-self (warn when protected members are accessed outside of a class or file) - # "SLOT", # flake8-slots (warn about subclasses that should define __slots__) - "T10", # flake8-debugger (check for pdb traces left in Python code) + "SLOT", # flake8-slots (warn about subclasses that should define __slots__) + "T10", # flake8-debugger (check for pdb traces left in Python code) # "T20", # flake8-print (warn about use of `print` or `pprint` - force use of loggers) "TC", # flake8-type-checking (type checking warnings) "TD", # flake8-todos (force all TODOs to include an author and issue link) From c9fa34d605158f4206dbf6238c9d6c6ae55c1a47 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 02:16:07 -0400 Subject: [PATCH 55/79] Do a bunch of automated refactoring using ruff UP ruleset to do things like replace .format() with f-strings --- cmd2/argparse_completer.py | 19 +- .../modular_commands/commandset_complex.py | 6 +- examples/scripts/conditional.py | 16 +- examples/scripts/save_help_text.py | 14 +- tasks.py | 10 +- tests/pyscript/environment.py | 6 +- tests/test_argparse.py | 6 +- tests/test_argparse_completer.py | 62 +++---- tests/test_cmd2.py | 148 +++++++-------- tests/test_completion.py | 174 +++++++++--------- tests/test_history.py | 4 +- tests/test_parsing.py | 2 +- tests/test_run_pyscript.py | 34 ++-- tests/test_transcript.py | 8 +- tests/test_utils_defining_class.py | 2 +- .../test_commandset/test_commandset.py | 36 ++-- 16 files changed, 272 insertions(+), 275 deletions(-) diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index ac77160d4..48d1fd05a 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -12,7 +12,6 @@ from typing import ( TYPE_CHECKING, Optional, - Type, Union, cast, ) @@ -143,11 +142,9 @@ def __init__(self, flag_arg_state: _ArgumentState) -> None: CompletionError which occurs when the user has not finished the current flag :param flag_arg_state: information about the unfinished flag action """ - error = "Error: argument {}: {} ({} entered)".format( - argparse._get_action_name(flag_arg_state.action), - generate_range_error(cast(int, flag_arg_state.min), cast(Union[int, float], flag_arg_state.max)), - flag_arg_state.count, - ) + arg = f'{argparse._get_action_name(flag_arg_state.action)}' + err = f'{generate_range_error(cast(int, flag_arg_state.min), cast(Union[int, float], flag_arg_state.max))}' + error = f"Error: argument {arg}: {err} ({flag_arg_state.count} entered)" super().__init__(error) @@ -272,9 +269,9 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: if arg_action == completer_action: return - error = "Error: argument {}: not allowed with argument {}".format( - argparse._get_action_name(arg_action), argparse._get_action_name(completer_action) - ) + arg_str = f'{argparse._get_action_name(arg_action)}' + completer_str = f'{argparse._get_action_name(completer_action)}' + error = f"Error: argument {arg_str}: not allowed with argument {completer_str}" raise CompletionError(error) # Mark that this action completed the group @@ -766,10 +763,10 @@ def _complete_arg( # The default ArgparseCompleter class for a cmd2 app -DEFAULT_AP_COMPLETER: Type[ArgparseCompleter] = ArgparseCompleter +DEFAULT_AP_COMPLETER: type[ArgparseCompleter] = ArgparseCompleter -def set_default_ap_completer_type(completer_type: Type[ArgparseCompleter]) -> None: +def set_default_ap_completer_type(completer_type: type[ArgparseCompleter]) -> None: """ Set the default ArgparseCompleter class for a cmd2 app. diff --git a/examples/modular_commands/commandset_complex.py b/examples/modular_commands/commandset_complex.py index e0acb621d..140f68e8e 100644 --- a/examples/modular_commands/commandset_complex.py +++ b/examples/modular_commands/commandset_complex.py @@ -21,7 +21,7 @@ def do_banana(self, statement: cmd2.Statement): @cmd2.with_argparser(cranberry_parser, with_unknown_args=True) def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]): - self._cmd.poutput('Cranberry {}!!'.format(ns.arg1)) + self._cmd.poutput(f'Cranberry {ns.arg1}!!') if unknown and len(unknown): self._cmd.poutput('Unknown: ' + ', '.join(['{}'] * len(unknown)).format(*unknown)) self._cmd.last_result = {'arg1': ns.arg1, 'unknown': unknown} @@ -33,7 +33,7 @@ def help_cranberry(self): @cmd2.with_category('Also Alone') def do_durian(self, args: list[str]): """Durian Command""" - self._cmd.poutput('{} Arguments: '.format(len(args))) + self._cmd.poutput(f'{len(args)} Arguments: ') self._cmd.poutput(', '.join(['{}'] * len(args)).format(*args)) def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: @@ -45,4 +45,4 @@ def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> lis @cmd2.with_category('Alone') @cmd2.with_argparser(elderberry_parser) def do_elderberry(self, ns: argparse.Namespace): - self._cmd.poutput('Elderberry {}!!'.format(ns.arg1)) + self._cmd.poutput(f'Elderberry {ns.arg1}!!') diff --git a/examples/scripts/conditional.py b/examples/scripts/conditional.py index 8fe1c426f..ca993ca66 100644 --- a/examples/scripts/conditional.py +++ b/examples/scripts/conditional.py @@ -14,36 +14,36 @@ if len(sys.argv) > 1: directory = sys.argv[1] - print('Using specified directory: {!r}'.format(directory)) + print(f'Using specified directory: {directory!r}') else: directory = 'foobar' - print('Using default directory: {!r}'.format(directory)) + print(f'Using default directory: {directory!r}') # Keep track of where we stared original_dir = os.getcwd() # Try to change to the specified directory -result = app('cd {}'.format(directory)) +result = app(f'cd {directory}') # Conditionally do something based on the results of the last command if result: print(f"STDOUT: {result.stdout}\n") print(f"STDERR: {result.stderr}\n") - print('\nContents of directory {!r}:'.format(directory)) + print(f'\nContents of directory {directory!r}:') result = app('dir -l') print(f"STDOUT: {result.stdout}\n") print(f"STDERR: {result.stderr}\n") - print('{}\n'.format(result.data)) + print(f'{result.data}\n') # Change back to where we were - print('Changing back to original directory: {!r}'.format(original_dir)) - app('cd {}'.format(original_dir)) + print(f'Changing back to original directory: {original_dir!r}') + app(f'cd {original_dir}') else: # cd command failed, print a warning - print('Failed to change directory to {!r}'.format(directory)) + print(f'Failed to change directory to {directory!r}') print(f"STDOUT: {result.stdout}\n") print(f"STDERR: {result.stderr}\n") diff --git a/examples/scripts/save_help_text.py b/examples/scripts/save_help_text.py index 5312d86d4..e037ced00 100644 --- a/examples/scripts/save_help_text.py +++ b/examples/scripts/save_help_text.py @@ -43,10 +43,10 @@ def add_help_to_file(item: str, outfile: TextIO, is_command: bool) -> None: else: label = "TOPIC" - header = '{}\n{}: {}\n{}\n'.format(ASTERISKS, label, item, ASTERISKS) + header = f'{ASTERISKS}\n{label}: {item}\n{ASTERISKS}\n' outfile.write(header) - result = app('help {}'.format(item)) + result = app(f'help {item}') outfile.write(result.stdout) @@ -60,7 +60,7 @@ def main() -> None: # Make sure the user passed in an output file if len(sys.argv) != 2: - print("Usage: {} ".format(os.path.basename(sys.argv[0]))) + print(f"Usage: {os.path.basename(sys.argv[0])} ") return # Open the output file @@ -68,11 +68,11 @@ def main() -> None: try: outfile = open(outfile_path, 'w') except OSError as e: - print("Error opening {} because: {}".format(outfile_path, e)) + print(f"Error opening {outfile_path} because: {e}") return # Write the help summary - header = '{0}\nSUMMARY\n{0}\n'.format(ASTERISKS) + header = f'{ASTERISKS}\nSUMMARY\n{ASTERISKS}\n' outfile.write(header) result = app('help -v') @@ -91,11 +91,11 @@ def main() -> None: if is_command: # Add any subcommands for subcmd in get_sub_commands(getattr(self.cmd_func(item), 'argparser', None)): - full_cmd = '{} {}'.format(item, subcmd) + full_cmd = f'{item} {subcmd}' add_help_to_file(full_cmd, outfile, is_command) outfile.close() - print("Output written to {}".format(outfile_path)) + print(f"Output written to {outfile_path}") # Run main function diff --git a/tasks.py b/tasks.py index bc0306d13..683f67334 100644 --- a/tasks.py +++ b/tasks.py @@ -30,7 +30,7 @@ def rmrf(items, verbose=True): for item in items: if verbose: - print("Removing {}".format(item)) + print(f"Removing {item}") shutil.rmtree(item, ignore_errors=True) # rmtree doesn't remove bare files try: @@ -270,8 +270,8 @@ def tag(context, name, message=''): """Add a Git tag and push it to origin""" # If a tag was provided on the command-line, then add a Git tag and push it to origin if name: - context.run('git tag -a {} -m {!r}'.format(name, message)) - context.run('git push origin {}'.format(name)) + context.run(f'git tag -a {name} -m {message!r}') + context.run(f'git push origin {name}') namespace.add_task(tag) @@ -288,10 +288,10 @@ def validatetag(context): ver_regex = re.compile(r'(\d+)\.(\d+)\.(\d+)') match = ver_regex.fullmatch(git_tag) if match is None: - print('Tag {!r} does not appear to be a valid version number'.format(git_tag)) + print(f'Tag {git_tag!r} does not appear to be a valid version number') sys.exit(-1) else: - print('Tag {!r} appears to be a valid version number'.format(git_tag)) + print(f'Tag {git_tag!r} appears to be a valid version number') namespace.add_task(validatetag) diff --git a/tests/pyscript/environment.py b/tests/pyscript/environment.py index f703b0e8f..758c85002 100644 --- a/tests/pyscript/environment.py +++ b/tests/pyscript/environment.py @@ -5,16 +5,16 @@ app.cmd_echo = True if __name__ != '__main__': - print("Error: __name__ is: {}".format(__name__)) + print(f"Error: __name__ is: {__name__}") quit() if __file__ != sys.argv[0]: - print("Error: __file__ is: {}".format(__file__)) + print(f"Error: __file__ is: {__file__}") quit() our_dir = os.path.dirname(os.path.abspath(__file__)) if our_dir != sys.path[0]: - print("Error: our_dir is: {}".format(our_dir)) + print(f"Error: our_dir is: {our_dir}") quit() print("PASSED") diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 251f083f6..18a6662e7 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -70,7 +70,7 @@ def do_tag(self, args): @cmd2.with_argparser(cmd2.Cmd2ArgumentParser(), ns_provider=namespace_provider) def do_test_argparse_ns(self, args): - self.stdout.write('{}'.format(args.custom_stuff)) + self.stdout.write(f'{args.custom_stuff}') @cmd2.with_argument_list def do_arglist(self, arglist, *, keyword_arg: Optional[str] = None): @@ -84,7 +84,7 @@ def do_arglist(self, arglist, *, keyword_arg: Optional[str] = None): @cmd2.with_argument_list(preserve_quotes=True) def do_preservelist(self, arglist): - self.stdout.write('{}'.format(arglist)) + self.stdout.write(f'{arglist}') @classmethod def _speak_parser_builder(cls) -> cmd2.Cmd2ArgumentParser: @@ -120,7 +120,7 @@ def do_test_argparse_with_list_quotes(self, args, extra): @cmd2.with_argparser(cmd2.Cmd2ArgumentParser(), ns_provider=namespace_provider, with_unknown_args=True) def do_test_argparse_with_list_ns(self, args, extra): - self.stdout.write('{}'.format(args.custom_stuff)) + self.stdout.write(f'{args.custom_stuff}') @pytest.fixture diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 3d26ad428..654683335 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -127,7 +127,7 @@ def completion_item_method(self) -> list[CompletionItem]: """Choices method that returns CompletionItems""" items = [] for i in range(10): - main_str = 'main_str{}'.format(i) + main_str = f'main_str{i}' items.append(CompletionItem(main_str, description='blah blah')) return items @@ -343,8 +343,8 @@ def ac_app(): @pytest.mark.parametrize('command', ['music', 'music create', 'music create rock', 'music create jazz']) def test_help(ac_app, command): - out1, err1 = run_cmd(ac_app, '{} -h'.format(command)) - out2, err2 = run_cmd(ac_app, 'help {}'.format(command)) + out1, err1 = run_cmd(ac_app, f'{command} -h') + out2, err2 = run_cmd(ac_app, f'help {command}') assert out1 == out2 @@ -370,7 +370,7 @@ def test_bad_subcommand_help(ac_app): ], ) def test_complete_help(ac_app, command, text, completions): - line = 'help {} {}'.format(command, text) + line = f'help {command} {text}' endidx = len(line) begidx = endidx - len(text) @@ -388,7 +388,7 @@ def test_complete_help(ac_app, command, text, completions): [('create', '', ['jazz', 'rock']), ('create', 'ja', ['jazz ']), ('create', 'foo', []), ('creab', 'ja', [])], ) def test_subcommand_completions(ac_app, subcommand, text, completions): - line = 'music {} {}'.format(subcommand, text) + line = f'music {subcommand} {text}' endidx = len(line) begidx = endidx - len(text) @@ -533,7 +533,7 @@ def test_subcommand_completions(ac_app, subcommand, text, completions): ], ) def test_autcomp_flag_completion(ac_app, command_and_args, text, completion_matches, display_matches): - line = '{} {}'.format(command_and_args, text) + line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -562,7 +562,7 @@ def test_autcomp_flag_completion(ac_app, command_and_args, text, completion_matc ], ) def test_autocomp_flag_choices_completion(ac_app, flag, text, completions): - line = 'choices {} {}'.format(flag, text) + line = f'choices {flag} {text}' endidx = len(line) begidx = endidx - len(text) @@ -629,7 +629,7 @@ def test_flag_sorting(ac_app): option_strings.sort(key=ac_app.default_sort_key) text = '-' - line = 'choices arg1 arg2 arg3 {}'.format(text) + line = f'choices arg1 arg2 arg3 {text}' endidx = len(line) begidx = endidx - len(text) @@ -643,7 +643,7 @@ def test_flag_sorting(ac_app): [('-c', '', ArgparseCompleterTester.completions_for_flag), ('--completer', 'f', ['flag', 'fairly'])], ) def test_autocomp_flag_completers(ac_app, flag, text, completions): - line = 'completer {} {}'.format(flag, text) + line = f'completer {flag} {text}' endidx = len(line) begidx = endidx - len(text) @@ -690,7 +690,7 @@ def test_autocomp_blank_token(ac_app): # Blank flag arg will be consumed. Therefore we expect to be completing the first positional. text = '' - line = 'completer -c {} {}'.format(blank, text) + line = f'completer -c {blank} {text}' endidx = len(line) begidx = endidx - len(text) @@ -701,7 +701,7 @@ def test_autocomp_blank_token(ac_app): # Blank arg for first positional will be consumed. Therefore we expect to be completing the second positional. text = '' - line = 'completer {} {}'.format(blank, text) + line = f'completer {blank} {text}' endidx = len(line) begidx = endidx - len(text) @@ -714,7 +714,7 @@ def test_autocomp_blank_token(ac_app): def test_completion_items(ac_app): # First test CompletionItems created from strings text = '' - line = 'choices --completion_items {}'.format(text) + line = f'choices --completion_items {text}' endidx = len(line) begidx = endidx - len(text) @@ -736,7 +736,7 @@ def test_completion_items(ac_app): # Now test CompletionItems created from numbers text = '' - line = 'choices --num_completion_items {}'.format(text) + line = f'choices --num_completion_items {text}' endidx = len(line) begidx = endidx - len(text) @@ -772,12 +772,12 @@ def test_completion_items(ac_app): def test_max_completion_items(ac_app, num_aliases, show_description): # Create aliases for i in range(num_aliases): - run_cmd(ac_app, 'alias create fake_alias{} help'.format(i)) + run_cmd(ac_app, f'alias create fake_alias{i} help') assert len(ac_app.aliases) == num_aliases text = 'fake_alias' - line = 'alias list {}'.format(text) + line = f'alias list {text}' endidx = len(line) begidx = endidx - len(text) @@ -851,7 +851,7 @@ def test_max_completion_items(ac_app, num_aliases, show_description): ) def test_autcomp_nargs(ac_app, args, completions): text = '' - line = 'nargs {} {}'.format(args, text) + line = f'nargs {args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -898,7 +898,7 @@ def test_autcomp_nargs(ac_app, args, completions): ], ) def test_unfinished_flag_error(ac_app, command_and_args, text, is_error, capsys): - line = '{} {}'.format(command_and_args, text) + line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -911,7 +911,7 @@ def test_unfinished_flag_error(ac_app, command_and_args, text, is_error, capsys) def test_completion_items_arg_header(ac_app): # Test when metavar is None text = '' - line = 'choices --desc_header {}'.format(text) + line = f'choices --desc_header {text}' endidx = len(line) begidx = endidx - len(text) @@ -920,7 +920,7 @@ def test_completion_items_arg_header(ac_app): # Test when metavar is a string text = '' - line = 'choices --no_header {}'.format(text) + line = f'choices --no_header {text}' endidx = len(line) begidx = endidx - len(text) @@ -929,7 +929,7 @@ def test_completion_items_arg_header(ac_app): # Test when metavar is a tuple text = '' - line = 'choices --tuple_metavar {}'.format(text) + line = f'choices --tuple_metavar {text}' endidx = len(line) begidx = endidx - len(text) @@ -938,7 +938,7 @@ def test_completion_items_arg_header(ac_app): assert ac_app.TUPLE_METAVAR[0].upper() in normalize(ac_app.formatted_completions)[0] text = '' - line = 'choices --tuple_metavar token_1 {}'.format(text) + line = f'choices --tuple_metavar token_1 {text}' endidx = len(line) begidx = endidx - len(text) @@ -947,7 +947,7 @@ def test_completion_items_arg_header(ac_app): assert ac_app.TUPLE_METAVAR[1].upper() in normalize(ac_app.formatted_completions)[0] text = '' - line = 'choices --tuple_metavar token_1 token_2 {}'.format(text) + line = f'choices --tuple_metavar token_1 token_2 {text}' endidx = len(line) begidx = endidx - len(text) @@ -964,7 +964,7 @@ def test_completion_items_descriptive_header(ac_app): # This argument provided a descriptive header text = '' - line = 'choices --desc_header {}'.format(text) + line = f'choices --desc_header {text}' endidx = len(line) begidx = endidx - len(text) @@ -973,7 +973,7 @@ def test_completion_items_descriptive_header(ac_app): # This argument did not provide a descriptive header, so it should be DEFAULT_DESCRIPTIVE_HEADER text = '' - line = 'choices --no_header {}'.format(text) + line = f'choices --no_header {text}' endidx = len(line) begidx = endidx - len(text) @@ -1008,7 +1008,7 @@ def test_completion_items_descriptive_header(ac_app): ], ) def test_autocomp_hint(ac_app, command_and_args, text, has_hint, capsys): - line = '{} {}'.format(command_and_args, text) + line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -1022,7 +1022,7 @@ def test_autocomp_hint(ac_app, command_and_args, text, has_hint, capsys): def test_autocomp_hint_no_help_text(ac_app, capsys): text = '' - line = 'hint foo {}'.format(text) + line = f'hint foo {text}' endidx = len(line) begidx = endidx - len(text) @@ -1050,7 +1050,7 @@ def test_autocomp_hint_no_help_text(ac_app, capsys): ], ) def test_completion_error(ac_app, capsys, args, text): - line = 'raise_completion_error {} {}'.format(args, text) + line = f'raise_completion_error {args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -1058,7 +1058,7 @@ def test_completion_error(ac_app, capsys, args, text): out, err = capsys.readouterr() assert first_match is None - assert "{} broke something".format(text) in out + assert f"{text} broke something" in out @pytest.mark.parametrize( @@ -1074,7 +1074,7 @@ def test_completion_error(ac_app, capsys, args, text): ) def test_arg_tokens(ac_app, command_and_args, completions): text = '' - line = '{} {}'.format(command_and_args, text) + line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -1111,7 +1111,7 @@ def test_arg_tokens(ac_app, command_and_args, completions): ], ) def test_complete_mutex_group(ac_app, command_and_args, text, output_contains, first_match, capsys): - line = '{} {}'.format(command_and_args, text) + line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -1189,7 +1189,7 @@ def test_complete_command_help_no_tokens(ac_app): ) def test_complete_standalone(ac_app, flag, completions): text = '' - line = 'standalone {} {}'.format(flag, text) + line = f'standalone {flag} {text}' endidx = len(line) begidx = endidx - len(text) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 1cc5504fc..a82c647a9 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -237,7 +237,7 @@ def test_set_allow_style(base_app, new_val, is_valid, expected): ansi.allow_style = ansi.AllowStyle.TERMINAL # Use the set command to alter it - out, err = run_cmd(base_app, 'set allow_style {}'.format(new_val)) + out, err = run_cmd(base_app, f'set allow_style {new_val}') assert base_app.last_result is is_valid # Verify the results @@ -347,7 +347,7 @@ def test_run_script(base_app, request): assert base_app._current_script_dir is None # Get output out the script - script_out, script_err = run_cmd(base_app, 'run_script {}'.format(filename)) + script_out, script_err = run_cmd(base_app, f'run_script {filename}') assert base_app.last_result is True assert base_app._script_dir == [] @@ -382,7 +382,7 @@ def test_run_script_with_invalid_file(base_app, request): # Path is a directory test_dir = os.path.dirname(request.module.__file__) - out, err = run_cmd(base_app, 'run_script {}'.format(test_dir)) + out, err = run_cmd(base_app, f'run_script {test_dir}') assert "Problem accessing script from " in err[0] assert base_app.last_result is False @@ -390,7 +390,7 @@ def test_run_script_with_invalid_file(base_app, request): def test_run_script_with_empty_file(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'empty.txt') - out, err = run_cmd(base_app, 'run_script {}'.format(filename)) + out, err = run_cmd(base_app, f'run_script {filename}') assert not out assert not err assert base_app.last_result is True @@ -399,7 +399,7 @@ def test_run_script_with_empty_file(base_app, request): def test_run_script_with_binary_file(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'binary.bin') - out, err = run_cmd(base_app, 'run_script {}'.format(filename)) + out, err = run_cmd(base_app, f'run_script {filename}') assert "is not an ASCII or UTF-8 encoded text file" in err[0] assert base_app.last_result is False @@ -410,7 +410,7 @@ def test_run_script_with_python_file(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'pyscript', 'stop.py') - out, err = run_cmd(base_app, 'run_script {}'.format(filename)) + out, err = run_cmd(base_app, f'run_script {filename}') assert "appears to be a Python file" in err[0] assert base_app.last_result is False @@ -423,7 +423,7 @@ def test_run_script_with_utf8_file(base_app, request): assert base_app._current_script_dir is None # Get output out the script - script_out, script_err = run_cmd(base_app, 'run_script {}'.format(filename)) + script_out, script_err = run_cmd(base_app, f'run_script {filename}') assert base_app.last_result is True assert base_app._script_dir == [] @@ -545,7 +545,7 @@ def test_relative_run_script(base_app, request): assert base_app._current_script_dir is None # Get output out the script - script_out, script_err = run_cmd(base_app, '_relative_run_script {}'.format(filename)) + script_out, script_err = run_cmd(base_app, f'_relative_run_script {filename}') assert base_app.last_result is True assert base_app._script_dir == [] @@ -573,7 +573,7 @@ def test_relative_run_script_with_odd_file_names(base_app, file_name, monkeypatc run_script_mock = mock.MagicMock(name='do_run_script') monkeypatch.setattr("cmd2.Cmd.do_run_script", run_script_mock) - run_cmd(base_app, "_relative_run_script {}".format(utils.quote_string(file_name))) + run_cmd(base_app, f"_relative_run_script {utils.quote_string(file_name)}") run_script_mock.assert_called_once_with(utils.quote_string(file_name)) @@ -597,7 +597,7 @@ def hook(self: cmd2.Cmd, data: plugin.CommandFinalizationData) -> plugin.Command hook_app = HookApp() test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') - out, err = run_cmd(hook_app, 'run_script {}'.format(filename)) + out, err = run_cmd(hook_app, f'run_script {filename}') assert "WE ARE IN SCRIPT" in out[-1] @@ -640,13 +640,13 @@ def test_output_redirection(base_app): try: # Verify that writing to a file works - run_cmd(base_app, 'help > {}'.format(filename)) + run_cmd(base_app, f'help > {filename}') with open(filename) as f: content = f.read() verify_help_text(base_app, content) # Verify that appending to a file also works - run_cmd(base_app, 'help history >> {}'.format(filename)) + run_cmd(base_app, f'help history >> {filename}') with open(filename) as f: appended_content = f.read() assert appended_content.startswith(content) @@ -660,10 +660,10 @@ def test_output_redirection(base_app): def test_output_redirection_to_nonexistent_directory(base_app): filename = '~/fakedir/this_does_not_exist.txt' - out, err = run_cmd(base_app, 'help > {}'.format(filename)) + out, err = run_cmd(base_app, f'help > {filename}') assert 'Failed to redirect' in err[0] - out, err = run_cmd(base_app, 'help >> {}'.format(filename)) + out, err = run_cmd(base_app, f'help >> {filename}') assert 'Failed to redirect' in err[0] @@ -676,10 +676,10 @@ def test_output_redirection_to_too_long_filename(base_app): 'whfieuwfhieufhiuewhfeiuwfhiuefhueiwhfw' ) - out, err = run_cmd(base_app, 'help > {}'.format(filename)) + out, err = run_cmd(base_app, f'help > {filename}') assert 'Failed to redirect' in err[0] - out, err = run_cmd(base_app, 'help >> {}'.format(filename)) + out, err = run_cmd(base_app, f'help >> {filename}') assert 'Failed to redirect' in err[0] @@ -690,7 +690,7 @@ def test_feedback_to_output_true(base_app): os.close(f) try: - run_cmd(base_app, 'help > {}'.format(filename)) + run_cmd(base_app, f'help > {filename}') with open(filename) as f: content = f.readlines() assert content[-1].startswith('Elapsed: ') @@ -707,7 +707,7 @@ def test_feedback_to_output_false(base_app): os.close(f) try: - out, err = run_cmd(base_app, 'help > {}'.format(filename)) + out, err = run_cmd(base_app, f'help > {filename}') with open(filename) as f: content = f.readlines() @@ -726,7 +726,7 @@ def test_disallow_redirection(base_app): filename = 'test_allow_redirect.txt' # Verify output wasn't redirected - out, err = run_cmd(base_app, 'help > {}'.format(filename)) + out, err = run_cmd(base_app, f'help > {filename}') verify_help_text(base_app, out) # Verify that no file got created @@ -751,11 +751,11 @@ def test_pipe_to_shell_and_redirect(base_app): filename = 'out.txt' if sys.platform == "win32": # Windows - command = 'help | sort > {}'.format(filename) + command = f'help | sort > {filename}' else: # Mac and Linux # Get help on help and pipe it's output to the input of the word count shell command - command = 'help help | wc > {}'.format(filename) + command = f'help help | wc > {filename}' out, err = run_cmd(base_app, command) assert not out @@ -855,10 +855,10 @@ def _expected_no_editor_error(): expected_exception = 'EnvironmentError' expected_text = normalize( - """ -EXCEPTION of type '{}' occurred with message: Please use 'set editor' to specify your text editing program of choice. + f""" +EXCEPTION of type '{expected_exception}' occurred with message: Please use 'set editor' to specify your text editing program of choice. To enable full traceback, run the following command: 'set debug true' -""".format(expected_exception) +""" ) return expected_text @@ -917,7 +917,7 @@ def test_edit_file(base_app, request, monkeypatch): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') - run_cmd(base_app, 'edit {}'.format(filename)) + run_cmd(base_app, f'edit {filename}') # We think we have an editor, so should expect a Popen call m.assert_called_once() @@ -932,8 +932,8 @@ def test_edit_file_with_odd_file_names(base_app, file_name, monkeypatch): base_app.editor = 'fooedit' file_name = utils.quote_string('nothingweird.py') - run_cmd(base_app, "edit {}".format(utils.quote_string(file_name))) - shell_mock.assert_called_once_with('"fooedit" {}'.format(utils.quote_string(file_name))) + run_cmd(base_app, f"edit {utils.quote_string(file_name)}") + shell_mock.assert_called_once_with(f'"fooedit" {utils.quote_string(file_name)}') def test_edit_file_with_spaces(base_app, request, monkeypatch): @@ -947,7 +947,7 @@ def test_edit_file_with_spaces(base_app, request, monkeypatch): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'my commands.txt') - run_cmd(base_app, 'edit "{}"'.format(filename)) + run_cmd(base_app, f'edit "{filename}"') # We think we have an editor, so should expect a Popen call m.assert_called_once() @@ -1322,7 +1322,7 @@ def do_study(self, arg): """Learn something, with a selection of subjects to choose from.""" # Pass in a list of strings for selections subject = self.select(['math', 'science'], 'Subject? ') - result = 'Good luck learning {}!\n'.format(subject) + result = f'Good luck learning {subject}!\n' self.stdout.write(result) def do_procrastinate(self, arg): @@ -1331,14 +1331,14 @@ def do_procrastinate(self, arg): leisure_activity = self.select( [('Netflix and chill', 'Netflix'), ('YouTube', 'WebSurfing')], 'How would you like to procrastinate? ' ) - result = 'Have fun procrasinating with {}!\n'.format(leisure_activity) + result = f'Have fun procrasinating with {leisure_activity}!\n' self.stdout.write(result) def do_play(self, arg): """Play your favorite musical instrument.""" # Pass in an uneven list of tuples for selections instrument = self.select([('Guitar', 'Electric Guitar'), ('Drums',)], 'Instrument? ') - result = 'Charm us with the {}...\n'.format(instrument) + result = f'Charm us with the {instrument}...\n' self.stdout.write(result) def do_return_type(self, arg): @@ -1360,13 +1360,13 @@ def test_select_options(select_app, monkeypatch): monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) food = 'bacon' - out, err = run_cmd(select_app, "eat {}".format(food)) + out, err = run_cmd(select_app, f"eat {food}") expected = normalize( - """ + f""" 1. sweet 2. salty -{} with salty sauce, yum! -""".format(food) +{food} with salty sauce, yum! +""" ) # Make sure our mock was called with the expected arguments @@ -1385,14 +1385,14 @@ def test_select_invalid_option_too_big(select_app, monkeypatch): monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) food = 'fish' - out, err = run_cmd(select_app, "eat {}".format(food)) + out, err = run_cmd(select_app, f"eat {food}") expected = normalize( - """ + f""" 1. sweet 2. salty '3' isn't a valid choice. Pick a number between 1 and 2: -{} with sweet sauce, yum! -""".format(food) +{food} with sweet sauce, yum! +""" ) # Make sure our mock was called exactly twice with the expected arguments @@ -1414,14 +1414,14 @@ def test_select_invalid_option_too_small(select_app, monkeypatch): monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) food = 'fish' - out, err = run_cmd(select_app, "eat {}".format(food)) + out, err = run_cmd(select_app, f"eat {food}") expected = normalize( - """ + f""" 1. sweet 2. salty '0' isn't a valid choice. Pick a number between 1 and 2: -{} with sweet sauce, yum! -""".format(food) +{food} with sweet sauce, yum! +""" ) # Make sure our mock was called exactly twice with the expected arguments @@ -1512,12 +1512,12 @@ def test_select_return_type(select_app, monkeypatch, selection, type_str): out, err = run_cmd(select_app, "return_type") expected = normalize( - """ + f""" 1. Integer 2. String 3. Method -The return type is {} -""".format(type_str) +The return type is {type_str} +""" ) # Make sure our mock was called with the expected arguments @@ -1533,7 +1533,7 @@ def test_select_eof(select_app, monkeypatch): monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) food = 'fish' - out, err = run_cmd(select_app, "eat {}".format(food)) + out, err = run_cmd(select_app, f"eat {food}") # Make sure our mock was called exactly twice with the expected arguments arg = 'Sauce? ' @@ -1617,7 +1617,7 @@ def test_multiline_complete_statement_without_terminator(multiline_app): command = 'orate' args = 'hello world' - line = '{} {}'.format(command, args) + line = f'{command} {args}' statement = multiline_app._complete_statement(line) assert statement == args assert statement.command == command @@ -1773,22 +1773,22 @@ def commandresult_app(): def test_commandresult_truthy(commandresult_app): arg = 'foo' - run_cmd(commandresult_app, 'affirmative {}'.format(arg)) + run_cmd(commandresult_app, f'affirmative {arg}') assert commandresult_app.last_result assert commandresult_app.last_result == cmd2.CommandResult(arg, data=True) - run_cmd(commandresult_app, 'affirmative_no_data {}'.format(arg)) + run_cmd(commandresult_app, f'affirmative_no_data {arg}') assert commandresult_app.last_result assert commandresult_app.last_result == cmd2.CommandResult(arg) def test_commandresult_falsy(commandresult_app): arg = 'bar' - run_cmd(commandresult_app, 'negative {}'.format(arg)) + run_cmd(commandresult_app, f'negative {arg}') assert not commandresult_app.last_result assert commandresult_app.last_result == cmd2.CommandResult(arg, data=False) - run_cmd(commandresult_app, 'negative_no_data {}'.format(arg)) + run_cmd(commandresult_app, f'negative_no_data {arg}') assert not commandresult_app.last_result assert commandresult_app.last_result == cmd2.CommandResult('', arg) @@ -1824,7 +1824,7 @@ def test_echo(capsys): app.runcmds_plus_hooks(commands) out, err = capsys.readouterr() - assert out.startswith('{}{}\n'.format(app.prompt, commands[0]) + HELP_HISTORY.split()[0]) + assert out.startswith(f'{app.prompt}{commands[0]}\n' + HELP_HISTORY.split()[0]) def test_read_input_rawinput_true(capsys, monkeypatch): @@ -1882,7 +1882,7 @@ def test_read_input_rawinput_true(capsys, monkeypatch): line = app.read_input(prompt_str) out, err = capsys.readouterr() assert line == input_str - assert out == "{}{}\n".format(prompt_str, input_str) + assert out == f"{prompt_str}{input_str}\n" # echo False app.echo = False @@ -1900,7 +1900,7 @@ def make_app(isatty: bool, empty_input: bool = False): """Make a cmd2 app with a custom stdin""" app_input_str = '' if empty_input else input_str - fakein = io.StringIO('{}'.format(app_input_str)) + fakein = io.StringIO(f'{app_input_str}') fakein.isatty = mock.MagicMock(name='isatty', return_value=isatty) new_app = cmd2.Cmd(stdin=fakein) @@ -1927,7 +1927,7 @@ def make_app(isatty: bool, empty_input: bool = False): line = app.read_input(prompt_str) out, err = capsys.readouterr() assert line == input_str - assert out == "{}{}\n".format(prompt_str, input_str) + assert out == f"{prompt_str}{input_str}\n" # isatty is False, echo is False app = make_app(isatty=False) @@ -2124,7 +2124,7 @@ def test_alias_create_with_quoted_tokens(base_app): @pytest.mark.parametrize('alias_name', invalid_command_name) def test_alias_create_invalid_name(base_app, alias_name, capsys): - out, err = run_cmd(base_app, 'alias create {} help'.format(alias_name)) + out, err = run_cmd(base_app, f'alias create {alias_name} help') assert "Invalid alias name" in err[0] assert base_app.last_result is False @@ -2137,8 +2137,8 @@ def test_alias_create_with_command_name(base_app): def test_alias_create_with_macro_name(base_app): macro = "my_macro" - run_cmd(base_app, 'macro create {} help'.format(macro)) - out, err = run_cmd(base_app, 'alias create {} help'.format(macro)) + run_cmd(base_app, f'macro create {macro} help') + out, err = run_cmd(base_app, f'alias create {macro} help') assert "Alias cannot have the same name as a macro" in err[0] assert base_app.last_result is False @@ -2192,8 +2192,8 @@ def test_alias_delete_no_name(base_app): def test_multiple_aliases(base_app): alias1 = 'h1' alias2 = 'h2' - run_cmd(base_app, 'alias create {} help'.format(alias1)) - run_cmd(base_app, 'alias create {} help -v'.format(alias2)) + run_cmd(base_app, f'alias create {alias1} help') + run_cmd(base_app, f'alias create {alias2} help -v') out, err = run_cmd(base_app, alias1) verify_help_text(base_app, out) @@ -2260,7 +2260,7 @@ def test_macro_create_with_quoted_tokens(base_app): @pytest.mark.parametrize('macro_name', invalid_command_name) def test_macro_create_invalid_name(base_app, macro_name): - out, err = run_cmd(base_app, 'macro create {} help'.format(macro_name)) + out, err = run_cmd(base_app, f'macro create {macro_name} help') assert "Invalid macro name" in err[0] assert base_app.last_result is False @@ -2273,8 +2273,8 @@ def test_macro_create_with_command_name(base_app): def test_macro_create_with_alias_name(base_app): macro = "my_macro" - run_cmd(base_app, 'alias create {} help'.format(macro)) - out, err = run_cmd(base_app, 'macro create {} help'.format(macro)) + run_cmd(base_app, f'alias create {macro} help') + out, err = run_cmd(base_app, f'macro create {macro} help') assert "Macro cannot have the same name as an alias" in err[0] assert base_app.last_result is False @@ -2398,8 +2398,8 @@ def test_macro_delete_no_name(base_app): def test_multiple_macros(base_app): macro1 = 'h1' macro2 = 'h2' - run_cmd(base_app, 'macro create {} help'.format(macro1)) - run_cmd(base_app, 'macro create {} help -v'.format(macro2)) + run_cmd(base_app, f'macro create {macro1} help') + run_cmd(base_app, f'macro create {macro2} help -v') out, err = run_cmd(base_app, macro1) verify_help_text(base_app, out) @@ -2617,7 +2617,7 @@ def do_exit(self, arg_list) -> bool: try: self.exit_code = int(arg_list[0]) except ValueError: - self.perror("{} isn't a valid integer exit code".format(arg_list[0])) + self.perror(f"{arg_list[0]} isn't a valid integer exit code") self.exit_code = 1 # Return True to stop the command loop @@ -2625,7 +2625,7 @@ def do_exit(self, arg_list) -> bool: def postloop(self) -> None: """Hook method executed once when the cmdloop() method is about to return.""" - self.poutput('exiting with code: {}'.format(self.exit_code)) + self.poutput(f'exiting with code: {self.exit_code}') @pytest.fixture @@ -2846,7 +2846,7 @@ def test_disable_and_enable_category(disable_commands_app): # Make sure neither function completes text = '' - line = 'has_helper_funcs {}'.format(text) + line = f'has_helper_funcs {text}' endidx = len(line) begidx = endidx - len(text) @@ -2854,7 +2854,7 @@ def test_disable_and_enable_category(disable_commands_app): assert first_match is None text = '' - line = 'has_no_helper_funcs {}'.format(text) + line = f'has_no_helper_funcs {text}' endidx = len(line) begidx = endidx - len(text) @@ -2890,7 +2890,7 @@ def test_disable_and_enable_category(disable_commands_app): # has_helper_funcs should complete now text = '' - line = 'has_helper_funcs {}'.format(text) + line = f'has_helper_funcs {text}' endidx = len(line) begidx = endidx - len(text) @@ -2900,7 +2900,7 @@ def test_disable_and_enable_category(disable_commands_app): # has_no_helper_funcs had no completer originally, so there should be no results text = '' - line = 'has_no_helper_funcs {}'.format(text) + line = f'has_no_helper_funcs {text}' endidx = len(line) begidx = endidx - len(text) @@ -2957,7 +2957,7 @@ def test_disabled_command_not_in_history(disable_commands_app): def test_disabled_message_command_name(disable_commands_app): - message_to_print = '{} is currently disabled'.format(COMMAND_NAME) + message_to_print = f'{COMMAND_NAME} is currently disabled' disable_commands_app.disable_command('has_helper_funcs', message_to_print) out, err = run_cmd(disable_commands_app, 'has_helper_funcs') @@ -2993,7 +2993,7 @@ def test_startup_script_with_odd_file_names(startup_script): app = cmd2.Cmd(allow_cli_args=False, startup_script=startup_script) assert len(app._startup_commands) == 1 - assert app._startup_commands[0] == "run_script {}".format(utils.quote_string(os.path.abspath(startup_script))) + assert app._startup_commands[0] == f"run_script {utils.quote_string(os.path.abspath(startup_script))}" # Restore os.path.exists os.path.exists = saved_exists diff --git a/tests/test_completion.py b/tests/test_completion.py index 7324582d6..c0ac26d25 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -150,7 +150,7 @@ def test_complete_command_single(cmd2_app): def test_complete_empty_arg(cmd2_app): text = '' - line = 'help {}'.format(text) + line = f'help {text}' endidx = len(line) begidx = endidx - len(text) @@ -163,7 +163,7 @@ def test_complete_empty_arg(cmd2_app): def test_complete_bogus_command(cmd2_app): text = '' - line = 'fizbuzz {}'.format(text) + line = f'fizbuzz {text}' endidx = len(line) begidx = endidx - len(text) @@ -175,7 +175,7 @@ def test_complete_bogus_command(cmd2_app): def test_complete_exception(cmd2_app, capsys): text = '' - line = 'test_raise_exception {}'.format(text) + line = f'test_raise_exception {text}' endidx = len(line) begidx = endidx - len(text) @@ -195,7 +195,7 @@ def test_complete_macro(base_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 's') - line = 'fake {}'.format(text) + line = f'fake {text}' endidx = len(line) begidx = endidx - len(text) @@ -208,7 +208,7 @@ def test_complete_macro(base_app, request): def test_default_sort_key(cmd2_app): text = '' - line = 'test_sort_key {}'.format(text) + line = f'test_sort_key {text}' endidx = len(line) begidx = endidx - len(text) @@ -245,7 +245,7 @@ def test_cmd2_command_completion_nomatch(cmd2_app): def test_cmd2_help_completion_single(cmd2_app): text = 'he' - line = 'help {}'.format(text) + line = f'help {text}' endidx = len(line) begidx = endidx - len(text) @@ -258,7 +258,7 @@ def test_cmd2_help_completion_single(cmd2_app): def test_cmd2_help_completion_multiple(cmd2_app): text = 'h' - line = 'help {}'.format(text) + line = f'help {text}' endidx = len(line) begidx = endidx - len(text) @@ -269,7 +269,7 @@ def test_cmd2_help_completion_multiple(cmd2_app): def test_cmd2_help_completion_nomatch(cmd2_app): text = 'fakecommand' - line = 'help {}'.format(text) + line = f'help {text}' endidx = len(line) begidx = endidx - len(text) @@ -334,7 +334,7 @@ def test_shell_command_completion_doesnt_match_wildcards(cmd2_app): else: text = 'e*' - line = 'shell {}'.format(text) + line = f'shell {text}' endidx = len(line) begidx = endidx - len(text) @@ -350,7 +350,7 @@ def test_shell_command_completion_multiple(cmd2_app): text = 'l' expected = 'ls' - line = 'shell {}'.format(text) + line = f'shell {text}' endidx = len(line) begidx = endidx - len(text) @@ -361,7 +361,7 @@ def test_shell_command_completion_multiple(cmd2_app): def test_shell_command_completion_nomatch(cmd2_app): text = 'zzzz' - line = 'shell {}'.format(text) + line = f'shell {text}' endidx = len(line) begidx = endidx - len(text) @@ -371,7 +371,7 @@ def test_shell_command_completion_nomatch(cmd2_app): def test_shell_command_completion_doesnt_complete_when_just_shell(cmd2_app): text = '' - line = 'shell {}'.format(text) + line = f'shell {text}' endidx = len(line) begidx = endidx - len(text) @@ -383,7 +383,7 @@ def test_shell_command_completion_does_path_completion_when_after_command(cmd2_a test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'conftest') - line = 'shell cat {}'.format(text) + line = f'shell cat {text}' endidx = len(line) begidx = endidx - len(text) @@ -397,7 +397,7 @@ def test_shell_command_complete_in_path(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 's') - line = 'shell {}'.format(text) + line = f'shell {text}' endidx = len(line) begidx = endidx - len(text) @@ -414,7 +414,7 @@ def test_path_completion_single_end(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'conftest') - line = 'shell cat {}'.format(text) + line = f'shell cat {text}' endidx = len(line) begidx = endidx - len(text) @@ -426,7 +426,7 @@ def test_path_completion_multiple(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 's') - line = 'shell cat {}'.format(text) + line = f'shell cat {text}' endidx = len(line) begidx = endidx - len(text) @@ -440,7 +440,7 @@ def test_path_completion_nomatch(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'fakepath') - line = 'shell cat {}'.format(text) + line = f'shell cat {text}' endidx = len(line) begidx = endidx - len(text) @@ -461,7 +461,7 @@ def test_default_to_shell_completion(cmd2_app, request): # Make sure the command is on the testing system assert command in utils.get_exes_in_path(command) - line = '{} {}'.format(command, text) + line = f'{command} {text}' endidx = len(line) begidx = endidx - len(text) @@ -474,14 +474,14 @@ def test_default_to_shell_completion(cmd2_app, request): def test_path_completion_no_text(cmd2_app): # Run path complete with no search text which should show what's in cwd text = '' - line = 'shell ls {}'.format(text) + line = f'shell ls {text}' endidx = len(line) begidx = endidx - len(text) completions_no_text = cmd2_app.path_complete(text, line, begidx, endidx) # Run path complete with path set to the CWD text = os.getcwd() + os.path.sep - line = 'shell ls {}'.format(text) + line = f'shell ls {text}' endidx = len(line) begidx = endidx - len(text) @@ -496,14 +496,14 @@ def test_path_completion_no_text(cmd2_app): def test_path_completion_no_path(cmd2_app): # Run path complete with search text that isn't preceded by a path. This should use CWD as the path. text = 'p' - line = 'shell ls {}'.format(text) + line = f'shell ls {text}' endidx = len(line) begidx = endidx - len(text) completions_no_text = cmd2_app.path_complete(text, line, begidx, endidx) # Run path complete with path set to the CWD text = os.getcwd() + os.path.sep + text - line = 'shell ls {}'.format(text) + line = f'shell ls {text}' endidx = len(line) begidx = endidx - len(text) @@ -522,7 +522,7 @@ def test_path_completion_cwd_is_root_dir(cmd2_app): os.chdir(os.path.sep) text = '' - line = 'shell ls {}'.format(text) + line = f'shell ls {text}' endidx = len(line) begidx = endidx - len(text) completions = cmd2_app.path_complete(text, line, begidx, endidx) @@ -538,7 +538,7 @@ def test_path_completion_doesnt_match_wildcards(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c*') - line = 'shell cat {}'.format(text) + line = f'shell cat {text}' endidx = len(line) begidx = endidx - len(text) @@ -552,8 +552,8 @@ def test_path_completion_complete_user(cmd2_app): user = getpass.getuser() - text = '~{}'.format(user) - line = 'shell fake {}'.format(text) + text = f'~{user}' + line = f'shell fake {text}' endidx = len(line) begidx = endidx - len(text) completions = cmd2_app.path_complete(text, line, begidx, endidx) @@ -570,15 +570,15 @@ def test_path_completion_user_path_expansion(cmd2_app): cmd = 'ls' # Use a ~ which will be expanded into the user's home directory - text = '~{}'.format(os.path.sep) - line = 'shell {} {}'.format(cmd, text) + text = f'~{os.path.sep}' + line = f'shell {cmd} {text}' endidx = len(line) begidx = endidx - len(text) completions_tilde_slash = [match.replace(text, '', 1) for match in cmd2_app.path_complete(text, line, begidx, endidx)] # Run path complete on the user's home directory text = os.path.expanduser('~') + os.path.sep - line = 'shell {} {}'.format(cmd, text) + line = f'shell {cmd} {text}' endidx = len(line) begidx = endidx - len(text) completions_home = [match.replace(text, '', 1) for match in cmd2_app.path_complete(text, line, begidx, endidx)] @@ -590,7 +590,7 @@ def test_path_completion_directories_only(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 's') - line = 'shell cat {}'.format(text) + line = f'shell cat {text}' endidx = len(line) begidx = endidx - len(text) @@ -602,7 +602,7 @@ def test_path_completion_directories_only(cmd2_app, request): def test_basic_completion_single(cmd2_app): text = 'Pi' - line = 'list_food -f {}'.format(text) + line = f'list_food -f {text}' endidx = len(line) begidx = endidx - len(text) @@ -611,7 +611,7 @@ def test_basic_completion_single(cmd2_app): def test_basic_completion_multiple(cmd2_app): text = '' - line = 'list_food -f {}'.format(text) + line = f'list_food -f {text}' endidx = len(line) begidx = endidx - len(text) @@ -621,7 +621,7 @@ def test_basic_completion_multiple(cmd2_app): def test_basic_completion_nomatch(cmd2_app): text = 'q' - line = 'list_food -f {}'.format(text) + line = f'list_food -f {text}' endidx = len(line) begidx = endidx - len(text) @@ -630,7 +630,7 @@ def test_basic_completion_nomatch(cmd2_app): def test_delimiter_completion(cmd2_app): text = '/home/' - line = 'run_script {}'.format(text) + line = f'run_script {text}' endidx = len(line) begidx = endidx - len(text) @@ -645,7 +645,7 @@ def test_delimiter_completion(cmd2_app): def test_flag_based_completion_single(cmd2_app): text = 'Pi' - line = 'list_food -f {}'.format(text) + line = f'list_food -f {text}' endidx = len(line) begidx = endidx - len(text) @@ -654,7 +654,7 @@ def test_flag_based_completion_single(cmd2_app): def test_flag_based_completion_multiple(cmd2_app): text = '' - line = 'list_food -f {}'.format(text) + line = f'list_food -f {text}' endidx = len(line) begidx = endidx - len(text) @@ -664,7 +664,7 @@ def test_flag_based_completion_multiple(cmd2_app): def test_flag_based_completion_nomatch(cmd2_app): text = 'q' - line = 'list_food -f {}'.format(text) + line = f'list_food -f {text}' endidx = len(line) begidx = endidx - len(text) @@ -675,7 +675,7 @@ def test_flag_based_default_completer(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c') - line = 'list_food {}'.format(text) + line = f'list_food {text}' endidx = len(line) begidx = endidx - len(text) @@ -689,7 +689,7 @@ def test_flag_based_callable_completer(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c') - line = 'list_food -o {}'.format(text) + line = f'list_food -o {text}' endidx = len(line) begidx = endidx - len(text) @@ -700,7 +700,7 @@ def test_flag_based_callable_completer(cmd2_app, request): def test_index_based_completion_single(cmd2_app): text = 'Foo' - line = 'command Pizza {}'.format(text) + line = f'command Pizza {text}' endidx = len(line) begidx = endidx - len(text) @@ -709,7 +709,7 @@ def test_index_based_completion_single(cmd2_app): def test_index_based_completion_multiple(cmd2_app): text = '' - line = 'command Pizza {}'.format(text) + line = f'command Pizza {text}' endidx = len(line) begidx = endidx - len(text) @@ -719,7 +719,7 @@ def test_index_based_completion_multiple(cmd2_app): def test_index_based_completion_nomatch(cmd2_app): text = 'q' - line = 'command {}'.format(text) + line = f'command {text}' endidx = len(line) begidx = endidx - len(text) assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) == [] @@ -729,7 +729,7 @@ def test_index_based_default_completer(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c') - line = 'command Pizza Bat Computer {}'.format(text) + line = f'command Pizza Bat Computer {text}' endidx = len(line) begidx = endidx - len(text) @@ -743,7 +743,7 @@ def test_index_based_callable_completer(cmd2_app, request): test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c') - line = 'command Pizza Bat {}'.format(text) + line = f'command Pizza Bat {text}' endidx = len(line) begidx = endidx - len(text) @@ -754,7 +754,7 @@ def test_index_based_callable_completer(cmd2_app, request): def test_tokens_for_completion_quoted(cmd2_app): text = 'Pi' - line = 'list_food "{}"'.format(text) + line = f'list_food "{text}"' endidx = len(line) begidx = endidx @@ -768,7 +768,7 @@ def test_tokens_for_completion_quoted(cmd2_app): def test_tokens_for_completion_unclosed_quote(cmd2_app): text = 'Pi' - line = 'list_food "{}'.format(text) + line = f'list_food "{text}' endidx = len(line) begidx = endidx - len(text) @@ -783,7 +783,7 @@ def test_tokens_for_completion_unclosed_quote(cmd2_app): def test_tokens_for_completion_punctuation(cmd2_app): """Test that redirectors and terminators are word delimiters""" text = 'file' - line = 'command | < ;>>{}'.format(text) + line = f'command | < ;>>{text}' endidx = len(line) begidx = endidx - len(text) @@ -798,7 +798,7 @@ def test_tokens_for_completion_punctuation(cmd2_app): def test_tokens_for_completion_quoted_punctuation(cmd2_app): """Test that quoted punctuation characters are not word delimiters""" text = '>file' - line = 'command "{}'.format(text) + line = f'command "{text}' endidx = len(line) begidx = endidx - len(text) @@ -812,7 +812,7 @@ def test_tokens_for_completion_quoted_punctuation(cmd2_app): def test_add_opening_quote_basic_no_text(cmd2_app): text = '' - line = 'test_basic {}'.format(text) + line = f'test_basic {text}' endidx = len(line) begidx = endidx - len(text) @@ -824,7 +824,7 @@ def test_add_opening_quote_basic_no_text(cmd2_app): def test_add_opening_quote_basic_nothing_added(cmd2_app): text = 'P' - line = 'test_basic {}'.format(text) + line = f'test_basic {text}' endidx = len(line) begidx = endidx - len(text) @@ -835,7 +835,7 @@ def test_add_opening_quote_basic_nothing_added(cmd2_app): def test_add_opening_quote_basic_quote_added(cmd2_app): text = 'Ha' - line = 'test_basic {}'.format(text) + line = f'test_basic {text}' endidx = len(line) begidx = endidx - len(text) @@ -847,7 +847,7 @@ def test_add_opening_quote_basic_quote_added(cmd2_app): def test_add_opening_quote_basic_single_quote_added(cmd2_app): text = 'Ch' - line = 'test_basic {}'.format(text) + line = f'test_basic {text}' endidx = len(line) begidx = endidx - len(text) @@ -860,7 +860,7 @@ def test_add_opening_quote_basic_single_quote_added(cmd2_app): def test_add_opening_quote_basic_text_is_common_prefix(cmd2_app): # This tests when the text entered is the same as the common prefix of the matches text = 'Ham' - line = 'test_basic {}'.format(text) + line = f'test_basic {text}' endidx = len(line) begidx = endidx - len(text) @@ -872,7 +872,7 @@ def test_add_opening_quote_basic_text_is_common_prefix(cmd2_app): def test_add_opening_quote_delimited_no_text(cmd2_app): text = '' - line = 'test_delimited {}'.format(text) + line = f'test_delimited {text}' endidx = len(line) begidx = endidx - len(text) @@ -884,7 +884,7 @@ def test_add_opening_quote_delimited_no_text(cmd2_app): def test_add_opening_quote_delimited_nothing_added(cmd2_app): text = '/ho' - line = 'test_delimited {}'.format(text) + line = f'test_delimited {text}' endidx = len(line) begidx = endidx - len(text) @@ -899,7 +899,7 @@ def test_add_opening_quote_delimited_nothing_added(cmd2_app): def test_add_opening_quote_delimited_quote_added(cmd2_app): text = '/home/user/fi' - line = 'test_delimited {}'.format(text) + line = f'test_delimited {text}' endidx = len(line) begidx = endidx - len(text) @@ -915,7 +915,7 @@ def test_add_opening_quote_delimited_quote_added(cmd2_app): def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app): # This tests when the text entered is the same as the common prefix of the matches text = '/home/user/file' - line = 'test_delimited {}'.format(text) + line = f'test_delimited {text}' endidx = len(line) begidx = endidx - len(text) @@ -931,7 +931,7 @@ def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app): def test_add_opening_quote_delimited_space_in_prefix(cmd2_app): # This test when a space appears before the part of the string that is the display match text = '/home/oth' - line = 'test_delimited {}'.format(text) + line = f'test_delimited {text}' endidx = len(line) begidx = endidx - len(text) @@ -946,7 +946,7 @@ def test_add_opening_quote_delimited_space_in_prefix(cmd2_app): def test_no_completer(cmd2_app): text = '' - line = 'test_no_completer {}'.format(text) + line = f'test_no_completer {text}' endidx = len(line) begidx = endidx - len(text) @@ -958,7 +958,7 @@ def test_no_completer(cmd2_app): def test_wordbreak_in_command(cmd2_app): text = '' - line = '"{}'.format(text) + line = f'"{text}' endidx = len(line) begidx = endidx - len(text) @@ -969,7 +969,7 @@ def test_wordbreak_in_command(cmd2_app): def test_complete_multiline_on_single_line(cmd2_app): text = '' - line = 'test_multiline {}'.format(text) + line = f'test_multiline {text}' endidx = len(line) begidx = endidx - len(text) @@ -986,7 +986,7 @@ def test_complete_multiline_on_multiple_lines(cmd2_app): cmd2_app._multiline_in_progress = "test_multiline\n" text = 'Ba' - line = '{}'.format(text) + line = f'{text}' endidx = len(line) begidx = endidx - len(text) @@ -1045,7 +1045,7 @@ def test_redirect_complete(cmd2_app, monkeypatch, line, comp_type): default_complete_mock = mock.MagicMock(name='fake_completer') text = '' - line = '{} {}'.format(line, text) + line = f'{line} {text}' endidx = len(line) begidx = endidx - len(text) @@ -1070,7 +1070,7 @@ def test_redirect_complete(cmd2_app, monkeypatch, line, comp_type): def test_complete_set_value(cmd2_app): text = '' - line = 'set foo {}'.format(text) + line = f'set foo {text}' endidx = len(line) begidx = endidx - len(text) @@ -1081,7 +1081,7 @@ def test_complete_set_value(cmd2_app): def test_complete_set_value_invalid_settable(cmd2_app, capsys): text = '' - line = 'set fake {}'.format(text) + line = f'set fake {text}' endidx = len(line) begidx = endidx - len(text) @@ -1101,7 +1101,7 @@ def sc_app(): def test_cmd2_subcommand_completion_single_end(sc_app): text = 'f' - line = 'base {}'.format(text) + line = f'base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1114,7 +1114,7 @@ def test_cmd2_subcommand_completion_single_end(sc_app): def test_cmd2_subcommand_completion_multiple(sc_app): text = '' - line = 'base {}'.format(text) + line = f'base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1125,7 +1125,7 @@ def test_cmd2_subcommand_completion_multiple(sc_app): def test_cmd2_subcommand_completion_nomatch(sc_app): text = 'z' - line = 'base {}'.format(text) + line = f'base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1135,7 +1135,7 @@ def test_cmd2_subcommand_completion_nomatch(sc_app): def test_help_subcommand_completion_single(sc_app): text = 'base' - line = 'help {}'.format(text) + line = f'help {text}' endidx = len(line) begidx = endidx - len(text) @@ -1148,7 +1148,7 @@ def test_help_subcommand_completion_single(sc_app): def test_help_subcommand_completion_multiple(sc_app): text = '' - line = 'help base {}'.format(text) + line = f'help base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1159,7 +1159,7 @@ def test_help_subcommand_completion_multiple(sc_app): def test_help_subcommand_completion_nomatch(sc_app): text = 'z' - line = 'help base {}'.format(text) + line = f'help base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1170,7 +1170,7 @@ def test_help_subcommand_completion_nomatch(sc_app): def test_subcommand_tab_completion(sc_app): # This makes sure the correct completer for the sport subcommand is called text = 'Foot' - line = 'base sport {}'.format(text) + line = f'base sport {text}' endidx = len(line) begidx = endidx - len(text) @@ -1185,7 +1185,7 @@ def test_subcommand_tab_completion_with_no_completer(sc_app): # This tests what happens when a subcommand has no completer # In this case, the foo subcommand has no completer defined text = 'Foot' - line = 'base foo {}'.format(text) + line = f'base foo {text}' endidx = len(line) begidx = endidx - len(text) @@ -1195,7 +1195,7 @@ def test_subcommand_tab_completion_with_no_completer(sc_app): def test_subcommand_tab_completion_space_in_text(sc_app): text = 'B' - line = 'base sport "Space {}'.format(text) + line = f'base sport "Space {text}' endidx = len(line) begidx = endidx - len(text) @@ -1229,7 +1229,7 @@ def base_bar(self, args): def base_sport(self, args): """sport subcommand of base command""" - self.poutput('Sport is {}'.format(args.sport)) + self.poutput(f'Sport is {args.sport}') # create the top-level parser for the base command base_parser = cmd2.Cmd2ArgumentParser() @@ -1271,13 +1271,13 @@ def scu_app(): def test_subcmd_with_unknown_completion_single_end(scu_app): text = 'f' - line = 'base {}'.format(text) + line = f'base {text}' endidx = len(line) begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, scu_app) - print('first_match: {}'.format(first_match)) + print(f'first_match: {first_match}') # It is at end of line, so extra space is present assert first_match is not None @@ -1286,7 +1286,7 @@ def test_subcmd_with_unknown_completion_single_end(scu_app): def test_subcmd_with_unknown_completion_multiple(scu_app): text = '' - line = 'base {}'.format(text) + line = f'base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1297,7 +1297,7 @@ def test_subcmd_with_unknown_completion_multiple(scu_app): def test_subcmd_with_unknown_completion_nomatch(scu_app): text = 'z' - line = 'base {}'.format(text) + line = f'base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1307,7 +1307,7 @@ def test_subcmd_with_unknown_completion_nomatch(scu_app): def test_help_subcommand_completion_single_scu(scu_app): text = 'base' - line = 'help {}'.format(text) + line = f'help {text}' endidx = len(line) begidx = endidx - len(text) @@ -1320,7 +1320,7 @@ def test_help_subcommand_completion_single_scu(scu_app): def test_help_subcommand_completion_multiple_scu(scu_app): text = '' - line = 'help base {}'.format(text) + line = f'help base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1331,7 +1331,7 @@ def test_help_subcommand_completion_multiple_scu(scu_app): def test_help_subcommand_completion_with_flags_before_command(scu_app): text = '' - line = 'help -h -v base {}'.format(text) + line = f'help -h -v base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1342,7 +1342,7 @@ def test_help_subcommand_completion_with_flags_before_command(scu_app): def test_complete_help_subcommands_with_blank_command(scu_app): text = '' - line = 'help "" {}'.format(text) + line = f'help "" {text}' endidx = len(line) begidx = endidx - len(text) @@ -1353,7 +1353,7 @@ def test_complete_help_subcommands_with_blank_command(scu_app): def test_help_subcommand_completion_nomatch_scu(scu_app): text = 'z' - line = 'help base {}'.format(text) + line = f'help base {text}' endidx = len(line) begidx = endidx - len(text) @@ -1364,7 +1364,7 @@ def test_help_subcommand_completion_nomatch_scu(scu_app): def test_subcommand_tab_completion_scu(scu_app): # This makes sure the correct completer for the sport subcommand is called text = 'Foot' - line = 'base sport {}'.format(text) + line = f'base sport {text}' endidx = len(line) begidx = endidx - len(text) @@ -1379,7 +1379,7 @@ def test_subcommand_tab_completion_with_no_completer_scu(scu_app): # This tests what happens when a subcommand has no completer # In this case, the foo subcommand has no completer defined text = 'Foot' - line = 'base foo {}'.format(text) + line = f'base foo {text}' endidx = len(line) begidx = endidx - len(text) @@ -1389,7 +1389,7 @@ def test_subcommand_tab_completion_with_no_completer_scu(scu_app): def test_subcommand_tab_completion_space_in_text_scu(scu_app): text = 'B' - line = 'base sport "Space {}'.format(text) + line = f'base sport "Space {text}' endidx = len(line) begidx = endidx - len(text) diff --git a/tests/test_history.py b/tests/test_history.py index 58123a5d7..012c23c7d 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -669,7 +669,7 @@ def test_history_output_file(): fd, fname = tempfile.mkstemp(prefix='', suffix='.txt') os.close(fd) - run_cmd(app, 'history -o "{}"'.format(fname)) + run_cmd(app, f'history -o "{fname}"') assert app.last_result is True expected = normalize('help\nshortcuts\nhelp history\nalias create my_alias history;') @@ -684,7 +684,7 @@ def test_history_bad_output_file(base_app): run_cmd(base_app, 'help history') fname = os.path.join(os.path.sep, "fake", "fake", "fake") - out, err = run_cmd(base_app, 'history -o "{}"'.format(fname)) + out, err = run_cmd(base_app, f'history -o "{fname}"') assert not out assert "Error saving" in err[0] diff --git a/tests/test_parsing.py b/tests/test_parsing.py index cfe22c4a0..812abb872 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -317,7 +317,7 @@ def test_parse_redirect(parser, line, output): ], ) # without dashes # with dashes in path def test_parse_redirect_with_args(parser, dest): - line = 'output into > {}'.format(dest) + line = f'output into > {dest}' statement = parser.parse(line) assert statement.command == 'output' assert statement == 'into' diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index a7db63bb0..741762a3c 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -34,7 +34,7 @@ def test_run_pyscript(base_app, request): python_script = os.path.join(test_dir, 'script.py') expected = 'This is a python script running ...' - out, err = run_cmd(base_app, "run_pyscript {}".format(python_script)) + out, err = run_cmd(base_app, f"run_pyscript {python_script}") assert expected in out assert base_app.last_result is True @@ -44,14 +44,14 @@ def test_run_pyscript_recursive_not_allowed(base_app, request): python_script = os.path.join(test_dir, 'pyscript', 'recursive.py') expected = 'Recursively entering interactive Python shells is not allowed' - out, err = run_cmd(base_app, "run_pyscript {}".format(python_script)) + out, err = run_cmd(base_app, f"run_pyscript {python_script}") assert err[0] == expected assert base_app.last_result is False def test_run_pyscript_with_nonexist_file(base_app): python_script = 'does_not_exist.py' - out, err = run_cmd(base_app, "run_pyscript {}".format(python_script)) + out, err = run_cmd(base_app, f"run_pyscript {python_script}") assert "Error reading script file" in err[0] assert base_app.last_result is False @@ -62,7 +62,7 @@ def test_run_pyscript_with_non_python_file(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'help.txt') - out, err = run_cmd(base_app, 'run_pyscript {}'.format(filename)) + out, err = run_cmd(base_app, f'run_pyscript {filename}') assert "does not have a .py extension" in err[0] assert base_app.last_result is False @@ -77,16 +77,16 @@ def test_run_pyscript_with_odd_file_names(base_app, python_script): input_mock = mock.MagicMock(name='input', return_value='1') builtins.input = input_mock - out, err = run_cmd(base_app, "run_pyscript {}".format(utils.quote_string(python_script))) + out, err = run_cmd(base_app, f"run_pyscript {utils.quote_string(python_script)}") err = ''.join(err) - assert "Error reading script file '{}'".format(python_script) in err + assert f"Error reading script file '{python_script}'" in err assert base_app.last_result is False def test_run_pyscript_with_exception(base_app, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'raises_exception.py') - out, err = run_cmd(base_app, "run_pyscript {}".format(python_script)) + out, err = run_cmd(base_app, f"run_pyscript {python_script}") assert err[0].startswith('Traceback') assert "TypeError: unsupported operand type(s) for +: 'int' and 'str'" in err[-1] assert base_app.last_result is True @@ -102,7 +102,7 @@ def test_run_pyscript_help(base_app, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'help.py') out1, err1 = run_cmd(base_app, 'help') - out2, err2 = run_cmd(base_app, 'run_pyscript {}'.format(python_script)) + out2, err2 = run_cmd(base_app, f'run_pyscript {python_script}') assert out1 assert out1 == out2 @@ -132,7 +132,7 @@ def test_run_pyscript_dir(base_app, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'pyscript_dir.py') - out, err = run_cmd(base_app, 'run_pyscript {}'.format(python_script)) + out, err = run_cmd(base_app, f'run_pyscript {python_script}') assert out[0] == "['cmd_echo']" @@ -140,7 +140,7 @@ def test_run_pyscript_stdout_capture(base_app, request): base_app.register_cmdfinalization_hook(cmdfinalization_hook) test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'stdout_capture.py') - out, err = run_cmd(base_app, 'run_pyscript {} {}'.format(python_script, HOOK_OUTPUT)) + out, err = run_cmd(base_app, f'run_pyscript {python_script} {HOOK_OUTPUT}') assert out[0] == "PASSED" assert out[1] == "PASSED" @@ -152,19 +152,19 @@ def test_run_pyscript_stop(base_app, request): # help.py doesn't run any commands that return True for stop python_script = os.path.join(test_dir, 'pyscript', 'help.py') - stop = base_app.onecmd_plus_hooks('run_pyscript {}'.format(python_script)) + stop = base_app.onecmd_plus_hooks(f'run_pyscript {python_script}') assert not stop # stop.py runs the quit command which does return True for stop python_script = os.path.join(test_dir, 'pyscript', 'stop.py') - stop = base_app.onecmd_plus_hooks('run_pyscript {}'.format(python_script)) + stop = base_app.onecmd_plus_hooks(f'run_pyscript {python_script}') assert stop def test_run_pyscript_environment(base_app, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'environment.py') - out, err = run_cmd(base_app, 'run_pyscript {}'.format(python_script)) + out, err = run_cmd(base_app, f'run_pyscript {python_script}') assert out[0] == "PASSED" @@ -175,12 +175,12 @@ def test_run_pyscript_self_in_py(base_app, request): # Set self_in_py to True and make sure we see self base_app.self_in_py = True - out, err = run_cmd(base_app, 'run_pyscript {}'.format(python_script)) + out, err = run_cmd(base_app, f'run_pyscript {python_script}') assert 'I see self' in out[0] # Set self_in_py to False and make sure we can't see self base_app.self_in_py = False - out, err = run_cmd(base_app, 'run_pyscript {}'.format(python_script)) + out, err = run_cmd(base_app, f'run_pyscript {python_script}') assert 'I do not see self' in out[0] @@ -196,7 +196,7 @@ def test_run_pyscript_py_locals(base_app, request): # this object should be editable from the py environment. base_app.py_locals['my_list'] = [] - run_cmd(base_app, 'run_pyscript {}'.format(python_script)) + run_cmd(base_app, f'run_pyscript {python_script}') # test_var should still exist assert base_app.py_locals['test_var'] == 5 @@ -208,7 +208,7 @@ def test_run_pyscript_py_locals(base_app, request): def test_run_pyscript_app_echo(base_app, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'echo.py') - out, err = run_cmd(base_app, 'run_pyscript {}'.format(python_script)) + out, err = run_cmd(base_app, f'run_pyscript {python_script}') # Only the edit help text should have been echoed to pytest's stdout assert out[0] == "Usage: edit [-h] [file_path]" diff --git a/tests/test_transcript.py b/tests/test_transcript.py index 9cce1f54e..fbc4135f3 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -171,7 +171,7 @@ def test_history_transcript(): os.close(fd) # tell the history command to create a transcript - run_cmd(app, 'history -t "{}"'.format(history_fname)) + run_cmd(app, f'history -t "{history_fname}"') # read in the transcript created by the history command with open(history_fname) as f: @@ -188,7 +188,7 @@ def test_history_transcript_bad_path(mocker): # Bad directory history_fname = '~/fakedir/this_does_not_exist.txt' - out, err = run_cmd(app, 'history -t "{}"'.format(history_fname)) + out, err = run_cmd(app, f'history -t "{history_fname}"') assert "is not a directory" in err[0] # Cause os.open to fail and make sure error gets printed @@ -196,7 +196,7 @@ def test_history_transcript_bad_path(mocker): mock_remove.side_effect = OSError history_fname = 'outfile.txt' - out, err = run_cmd(app, 'history -t "{}"'.format(history_fname)) + out, err = run_cmd(app, f'history -t "{history_fname}"') assert "Error saving transcript file" in err[0] @@ -212,7 +212,7 @@ def test_run_script_record_transcript(base_app, request): os.close(fd) # Execute the run_script command with the -t option to generate a transcript - run_cmd(base_app, 'run_script {} -t {}'.format(filename, transcript_fname)) + run_cmd(base_app, f'run_script {filename} -t {transcript_fname}') assert base_app._script_dir == [] assert base_app._current_script_dir is None diff --git a/tests/test_utils_defining_class.py b/tests/test_utils_defining_class.py index 5c313c8c5..e31d6e29b 100644 --- a/tests/test_utils_defining_class.py +++ b/tests/test_utils_defining_class.py @@ -7,7 +7,7 @@ import cmd2.utils as cu -class ParentClass(object): +class ParentClass: def func_with_overrides(self): pass diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 967e7d9ce..108c1b5a9 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -57,7 +57,7 @@ def do_banana(self, statement: cmd2.Statement): @cmd2.with_argparser(cranberry_parser, with_unknown_args=True) def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]): - self._cmd.poutput('Cranberry {}!!'.format(ns.arg1)) + self._cmd.poutput(f'Cranberry {ns.arg1}!!') if unknown and len(unknown): self._cmd.poutput('Unknown: ' + ', '.join(['{}'] * len(unknown)).format(*unknown)) self._cmd.last_result = {'arg1': ns.arg1, 'unknown': unknown} @@ -69,7 +69,7 @@ def help_cranberry(self): @cmd2.with_category('Also Alone') def do_durian(self, args: list[str]): """Durian Command""" - self._cmd.poutput('{} Arguments: '.format(len(args))) + self._cmd.poutput(f'{len(args)} Arguments: ') self._cmd.poutput(', '.join(['{}'] * len(args)).format(*args)) self._cmd.last_result = {'args': args} @@ -82,7 +82,7 @@ def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> lis @cmd2.with_category('Alone') @cmd2.with_argparser(elderberry_parser) def do_elderberry(self, ns: argparse.Namespace): - self._cmd.poutput('Elderberry {}!!'.format(ns.arg1)) + self._cmd.poutput(f'Elderberry {ns.arg1}!!') self._cmd.last_result = {'arg1': ns.arg1} # Test that CommandSet with as_subcommand_to decorator successfully loads @@ -548,7 +548,7 @@ def test_subcommands(command_sets_manual): assert 'Fruits' in cmds_cats text = '' - line = 'cut {}'.format(text) + line = f'cut {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) @@ -561,7 +561,7 @@ def test_subcommands(command_sets_manual): assert 'cutting banana: discs' in cmd_result.stdout text = '' - line = 'cut bokchoy {}'.format(text) + line = f'cut bokchoy {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) @@ -593,7 +593,7 @@ def test_subcommands(command_sets_manual): assert 'Fruits' in cmds_cats text = '' - line = 'cut {}'.format(text) + line = f'cut {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) @@ -603,7 +603,7 @@ def test_subcommands(command_sets_manual): assert ['banana', 'bananer', 'bokchoy'] == command_sets_manual.completion_matches text = '' - line = 'cut bokchoy {}'.format(text) + line = f'cut bokchoy {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) @@ -759,7 +759,7 @@ def test_static_subcommands(static_subcommands_app): assert 'Fruits' in cmds_cats text = '' - line = 'cut {}'.format(text) + line = f'cut {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, static_subcommands_app) @@ -769,7 +769,7 @@ def test_static_subcommands(static_subcommands_app): assert ['banana', 'bananer', 'bokchoy'] == static_subcommands_app.completion_matches text = '' - line = 'cut bokchoy {}'.format(text) + line = f'cut bokchoy {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, static_subcommands_app) @@ -805,7 +805,7 @@ class SupportFuncUserSubclass1(SupportFuncProvider): @cmd2.with_argparser(parser) def do_user_sub1(self, ns: argparse.Namespace): - self._cmd.poutput('something {}'.format(ns.state)) + self._cmd.poutput(f'something {ns.state}') class SupportFuncUserSubclass2(SupportFuncProvider): @@ -816,7 +816,7 @@ class SupportFuncUserSubclass2(SupportFuncProvider): @cmd2.with_argparser(parser) def do_user_sub2(self, ns: argparse.Namespace): - self._cmd.poutput('something {}'.format(ns.state)) + self._cmd.poutput(f'something {ns.state}') class SupportFuncUserUnrelated(cmd2.CommandSet): @@ -831,7 +831,7 @@ def __init__(self, dummy): @cmd2.with_argparser(parser) def do_user_unrelated(self, ns: argparse.Namespace): - self._cmd.poutput('something {}'.format(ns.state)) + self._cmd.poutput(f'something {ns.state}') def test_cross_commandset_completer(command_sets_manual, capsys): @@ -857,7 +857,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys): command_sets_manual.register_command_set(user_sub2) text = '' - line = 'user_sub1 {}'.format(text) + line = f'user_sub1 {text}' endidx = len(line) begidx = endidx complete_states_expected_self = user_sub1 @@ -883,7 +883,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys): command_sets_manual.register_command_set(user_unrelated) text = '' - line = 'user_unrelated {}'.format(text) + line = f'user_unrelated {text}' endidx = len(line) begidx = endidx complete_states_expected_self = func_provider @@ -906,7 +906,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys): command_sets_manual.register_command_set(user_unrelated) text = '' - line = 'user_unrelated {}'.format(text) + line = f'user_unrelated {text}' endidx = len(line) begidx = endidx complete_states_expected_self = user_sub1 @@ -928,7 +928,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys): command_sets_manual.register_command_set(user_unrelated) text = '' - line = 'user_unrelated {}'.format(text) + line = f'user_unrelated {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) @@ -951,7 +951,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys): command_sets_manual.register_command_set(user_unrelated) text = '' - line = 'user_unrelated {}'.format(text) + line = f'user_unrelated {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) @@ -985,7 +985,7 @@ def test_path_complete(command_sets_manual): command_sets_manual.register_command_set(test_set) text = '' - line = 'path {}'.format(text) + line = f'path {text}' endidx = len(line) begidx = endidx first_match = complete_tester(text, line, begidx, endidx, command_sets_manual) From e15c209ff20036a98833bfff8831a4a19c759ab6 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 02:22:07 -0400 Subject: [PATCH 56/79] Automated refactoring to replace ancient print % formatting with f-strings amd the like --- cmd2/history.py | 4 +-- examples/cmd_as_argument.py | 2 +- examples/colors.py | 2 +- examples/decorator_example.py | 2 +- examples/default_categories.py | 2 +- examples/example.py | 2 +- examples/first_app.py | 2 +- examples/modular_commands_basic.py | 2 +- examples/subcommands.py | 2 +- tests/test_argparse.py | 8 ++--- tests/test_argparse_completer.py | 2 +- tests/test_cmd2.py | 18 ++++------- tests/test_completion.py | 2 +- tests/test_transcript.py | 2 +- tests/test_utils_defining_class.py | 2 +- tests_isolated/test_commandset/conftest.py | 2 +- .../test_argparse_subcommands.py | 6 ++-- .../test_commandset/test_categories.py | 4 +-- .../test_commandset/test_commandset.py | 32 +++++++++---------- 19 files changed, 46 insertions(+), 52 deletions(-) diff --git a/cmd2/history.py b/cmd2/history.py index 8b6fc0258..1a7c5a628 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -165,7 +165,7 @@ class to gain access to the historical record. _history_items_field = 'history_items' def __init__(self, seq: Iterable[HistoryItem] = ()) -> None: - super(History, self).__init__(seq) + super().__init__(seq) self.session_start_index = 0 def start_session(self) -> None: @@ -192,7 +192,7 @@ def append(self, new: Union[Statement, HistoryItem]) -> None: and added to the end of the list """ history_item = HistoryItem(new) if isinstance(new, Statement) else new - super(History, self).append(history_item) + super().append(history_item) def clear(self) -> None: """Remove all items from the History list.""" diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index a9f5b896f..9f2176267 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -49,7 +49,7 @@ def do_speak(self, args): words = [] for word in args.words: if args.piglatin: - word = '%s%say' % (word[1:], word[0]) + word = f'{word[1:]}{word[0]}ay' if args.shout: word = word.upper() words.append(word) diff --git a/examples/colors.py b/examples/colors.py index 28b0c6707..22d146fcd 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -63,7 +63,7 @@ def do_speak(self, args): words = [] for word in args.words: if args.piglatin: - word = '%s%say' % (word[1:], word[0]) + word = f'{word[1:]}{word[0]}ay' if args.shout: word = word.upper() words.append(word) diff --git a/examples/decorator_example.py b/examples/decorator_example.py index 4c80436f8..daae235f8 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -46,7 +46,7 @@ def do_speak(self, args: argparse.Namespace): words = [] for word in args.words: if args.piglatin: - word = '%s%say' % (word[1:], word[0]) + word = f'{word[1:]}{word[0]}ay' if args.shout: word = word.upper() words.append(word) diff --git a/examples/default_categories.py b/examples/default_categories.py index 256db4867..dcec6fd67 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -75,7 +75,7 @@ class ExampleApp(cmd2.Cmd): """ def __init__(self): - super(ExampleApp, self).__init__() + super().__init__() def do_something(self, arg): self.poutput('this is the something command') diff --git a/examples/example.py b/examples/example.py index 0ce4d9bb8..afa5d90e2 100755 --- a/examples/example.py +++ b/examples/example.py @@ -45,7 +45,7 @@ def do_speak(self, args): words = [] for word in args.words: if args.piglatin: - word = '%s%say' % (word[1:], word[0]) + word = f'{word[1:]}{word[0]}ay' if args.shout: word = word.upper() words.append(word) diff --git a/examples/first_app.py b/examples/first_app.py index f19cdec92..d32b96f37 100755 --- a/examples/first_app.py +++ b/examples/first_app.py @@ -39,7 +39,7 @@ def do_speak(self, args): words = [] for word in args.words: if args.piglatin: - word = '%s%say' % (word[1:], word[0]) + word = f'{word[1:]}{word[0]}ay' if args.shout: word = word.upper() words.append(word) diff --git a/examples/modular_commands_basic.py b/examples/modular_commands_basic.py index 37cad3192..12eca4073 100755 --- a/examples/modular_commands_basic.py +++ b/examples/modular_commands_basic.py @@ -28,7 +28,7 @@ class ExampleApp(cmd2.Cmd): """ def __init__(self): - super(ExampleApp, self).__init__() + super().__init__() def do_something(self, arg): self.poutput('this is the something command') diff --git a/examples/subcommands.py b/examples/subcommands.py index 532b9f0f8..bff9f2c86 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -77,7 +77,7 @@ def base_foo(self, args): def base_bar(self, args): """bar subcommand of base command""" - self.poutput('((%s))' % args.z) + self.poutput(f'(({args.z}))') def base_sport(self, args): """sport subcommand of base command""" diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 18a6662e7..150f61694 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -47,7 +47,7 @@ def do_say(self, args, *, keyword_arg: Optional[str] = None): if word is None: word = '' if args.piglatin: - word = '%s%say' % (word[1:], word[0]) + word = f'{word[1:]}{word[0]}ay' if args.shout: word = word.upper() words.append(word) @@ -102,7 +102,7 @@ def do_speak(self, args, extra, *, keyword_arg: Optional[str] = None): if word is None: word = '' if args.piglatin: - word = '%s%say' % (word[1:], word[0]) + word = f'{word[1:]}{word[0]}ay' if args.shout: word = word.upper() words.append(word) @@ -266,11 +266,11 @@ def base_foo(self, args): def base_bar(self, args): """bar subcommand of base command""" - self.poutput('((%s))' % args.z) + self.poutput(f'(({args.z}))') def base_helpless(self, args): """helpless subcommand of base command""" - self.poutput('((%s))' % args.z) + self.poutput(f'(({args.z}))') # create the top-level parser for the base command base_parser = cmd2.Cmd2ArgumentParser() diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 654683335..db891acb3 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -1210,7 +1210,7 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matche if action.get_complete_when_ready() is True and not app.is_ready: matched_flags.append(flag) - return super(CustomCompleter, self)._complete_flags(text, line, begidx, endidx, matched_flags) + return super()._complete_flags(text, line, begidx, endidx, matched_flags) # Add a custom argparse action attribute diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index a82c647a9..c4589a586 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -477,17 +477,14 @@ def test_run_script_nested_run_scripts(base_app, request): assert base_app.last_result is True # Check that the right commands were executed. - expected = ( - """ -%s + expected = f""" +{initial_run} _relative_run_script precmds.txt set allow_style Always help shortcuts _relative_run_script postcmds.txt set allow_style Never""" - % initial_run - ) out, err = run_cmd(base_app, 'history -s') assert out == normalize(expected) @@ -498,16 +495,13 @@ def test_runcmds_plus_hooks(base_app, request): postfilepath = os.path.join(test_dir, 'scripts', 'postcmds.txt') base_app.runcmds_plus_hooks(['run_script ' + prefilepath, 'help', 'shortcuts', 'run_script ' + postfilepath]) - expected = """ -run_script %s + expected = f""" +run_script {prefilepath} set allow_style Always help shortcuts -run_script %s -set allow_style Never""" % ( - prefilepath, - postfilepath, - ) +run_script {postfilepath} +set allow_style Never""" out, err = run_cmd(base_app, 'history -s') assert out == normalize(expected) diff --git a/tests/test_completion.py b/tests/test_completion.py index c0ac26d25..8667a4acc 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -1225,7 +1225,7 @@ def base_foo(self, args): def base_bar(self, args): """bar subcommand of base command""" - self.poutput('((%s))' % args.z) + self.poutput(f'(({args.z}))') def base_sport(self, args): """sport subcommand of base command""" diff --git a/tests/test_transcript.py b/tests/test_transcript.py index fbc4135f3..4372bd6a8 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -53,7 +53,7 @@ def do_speak(self, opts, arg): """Repeats what you tell me to.""" arg = ' '.join(arg) if opts.piglatin: - arg = '%s%say' % (arg[1:], arg[0]) + arg = f'{arg[1:]}{arg[0]}ay' if opts.shout: arg = arg.upper() repetitions = opts.repeat or 1 diff --git a/tests/test_utils_defining_class.py b/tests/test_utils_defining_class.py index e31d6e29b..bf92385bb 100644 --- a/tests/test_utils_defining_class.py +++ b/tests/test_utils_defining_class.py @@ -17,7 +17,7 @@ def parent_only_func(self, param1, param2): class ChildClass(ParentClass): def func_with_overrides(self): - super(ChildClass, self).func_with_overrides() + super().func_with_overrides() def child_function(self): pass diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index b1375ae14..03e17e731 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -178,7 +178,7 @@ class WithCommandSets(ExternalTestMixin, cmd2.Cmd): """Class for testing custom help_* methods which override docstring help.""" def __init__(self, *args, **kwargs): - super(WithCommandSets, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @pytest.fixture diff --git a/tests_isolated/test_commandset/test_argparse_subcommands.py b/tests_isolated/test_commandset/test_argparse_subcommands.py index de68e5d94..f7b218576 100644 --- a/tests_isolated/test_commandset/test_argparse_subcommands.py +++ b/tests_isolated/test_commandset/test_argparse_subcommands.py @@ -16,7 +16,7 @@ class SubcommandSet(cmd2.CommandSet): """Example cmd2 application where we a base command which has a couple subcommands.""" def __init__(self, dummy): - super(SubcommandSet, self).__init__() + super().__init__() # subcommand functions for the base command def base_foo(self, args): @@ -25,11 +25,11 @@ def base_foo(self, args): def base_bar(self, args): """bar subcommand of base command""" - self._cmd.poutput('((%s))' % args.z) + self._cmd.poutput(f'(({args.z}))') def base_helpless(self, args): """helpless subcommand of base command""" - self._cmd.poutput('((%s))' % args.z) + self._cmd.poutput(f'(({args.z}))') # create the top-level parser for the base command base_parser = cmd2.Cmd2ArgumentParser() diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index 58d886e21..30b525580 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -16,7 +16,7 @@ class MyBaseCommandSet(CommandSet): """Defines a default category for all sub-class CommandSets""" def __init__(self, _: Any): - super(MyBaseCommandSet, self).__init__() + super().__init__() class ChildInheritsParentCategories(MyBaseCommandSet): @@ -79,7 +79,7 @@ class ExampleApp(cmd2.Cmd): """ def __init__(self): - super(ExampleApp, self).__init__(auto_load_commands=False) + super().__init__(auto_load_commands=False) def do_something(self, arg): self.poutput('this is the something command') diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 108c1b5a9..2205ec56b 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -384,7 +384,7 @@ def test_load_commandset_errors(command_sets_manual, capsys): class LoadableBase(cmd2.CommandSet): def __init__(self, dummy): - super(LoadableBase, self).__init__() + super().__init__() self._dummy = dummy # prevents autoload self._cut_called = False @@ -444,7 +444,7 @@ def stir_pasta(self, ns: argparse.Namespace): class LoadableBadBase(cmd2.CommandSet): def __init__(self, dummy): - super(LoadableBadBase, self).__init__() + super().__init__() self._dummy = dummy # prevents autoload def do_cut(self, ns: argparse.Namespace): @@ -462,7 +462,7 @@ def do_cut(self, ns: argparse.Namespace): @cmd2.with_default_category('Fruits') class LoadableFruits(cmd2.CommandSet): def __init__(self, dummy): - super(LoadableFruits, self).__init__() + super().__init__() self._dummy = dummy # prevents autoload def do_apple(self, _: cmd2.Statement): @@ -479,7 +479,7 @@ def cut_banana(self, ns: argparse.Namespace): class LoadablePastaStir(cmd2.CommandSet): def __init__(self, dummy): - super(LoadablePastaStir, self).__init__() + super().__init__() self._dummy = dummy # prevents autoload stir_pasta_vigor_parser = cmd2.Cmd2ArgumentParser() @@ -493,7 +493,7 @@ def stir_pasta_vigorously(self, ns: argparse.Namespace): @cmd2.with_default_category('Vegetables') class LoadableVegetables(cmd2.CommandSet): def __init__(self, dummy): - super(LoadableVegetables, self).__init__() + super().__init__() self._dummy = dummy # prevents autoload def do_arugula(self, _: cmd2.Statement): @@ -679,7 +679,7 @@ def test_nested_subcommands(command_sets_manual): class BadNestedSubcommands(cmd2.CommandSet): def __init__(self, dummy): - super(BadNestedSubcommands, self).__init__() + super().__init__() self._dummy = dummy # prevents autoload stir_pasta_vigor_parser = cmd2.Cmd2ArgumentParser() @@ -712,7 +712,7 @@ class AppWithSubCommands(cmd2.Cmd): """Class for testing usage of `as_subcommand_to` decorator directly in a Cmd2 subclass.""" def __init__(self, *args, **kwargs): - super(AppWithSubCommands, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) cut_parser = cmd2.Cmd2ArgumentParser() cut_subparsers = cut_parser.add_subparsers(title='item', help='item to cut') @@ -790,7 +790,7 @@ class SupportFuncProvider(cmd2.CommandSet): def __init__(self, dummy): """dummy variable prevents this from being autoloaded in other tests""" - super(SupportFuncProvider, self).__init__() + super().__init__() def complete_states(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: assert self is complete_states_expected_self @@ -824,7 +824,7 @@ class SupportFuncUserUnrelated(cmd2.CommandSet): def __init__(self, dummy): """dummy variable prevents this from being autoloaded in other tests""" - super(SupportFuncUserUnrelated, self).__init__() + super().__init__() parser = cmd2.Cmd2ArgumentParser() parser.add_argument('state', type=str, completer=SupportFuncProvider.complete_states) @@ -969,7 +969,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys): class CommandSetWithPathComplete(cmd2.CommandSet): def __init__(self, dummy): """dummy variable prevents this from being autoloaded in other tests""" - super(CommandSetWithPathComplete, self).__init__() + super().__init__() parser = cmd2.Cmd2ArgumentParser() parser.add_argument('path', nargs='+', help='paths', completer=cmd2.Cmd.path_complete) @@ -998,7 +998,7 @@ class BadSubcommandApp(cmd2.Cmd): """Class for testing usage of `as_subcommand_to` decorator directly in a Cmd2 subclass.""" def __init__(self, *args, **kwargs): - super(BadSubcommandApp, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) cut_parser = cmd2.Cmd2ArgumentParser() cut_subparsers = cut_parser.add_subparsers(title='item', help='item to cut') @@ -1028,7 +1028,7 @@ def __init__(self): # Declare a CommandSet with a settable of some arbitrary property class WithSettablesA(CommandSetBase): def __init__(self): - super(WithSettablesA, self).__init__() + super().__init__() self._arbitrary = Arbitrary() self._settable_prefix = 'addon' @@ -1047,7 +1047,7 @@ def __init__(self): # Declare a CommandSet with an empty settable prefix class WithSettablesNoPrefix(CommandSetBase): def __init__(self): - super(WithSettablesNoPrefix, self).__init__() + super().__init__() self._arbitrary = Arbitrary() self._settable_prefix = '' @@ -1066,7 +1066,7 @@ def __init__(self): # Declare a commandset with duplicate settable name class WithSettablesB(CommandSetBase): def __init__(self): - super(WithSettablesB, self).__init__() + super().__init__() self._arbitrary = Arbitrary() self._settable_prefix = 'some' @@ -1200,7 +1200,7 @@ class NsProviderSet(cmd2.CommandSet): # CommandSet which implements a namespace provider def __init__(self, dummy): # Use dummy argument so this won't be autoloaded by other tests - super(NsProviderSet, self).__init__() + super().__init__() def ns_provider(self) -> argparse.Namespace: ns = argparse.Namespace() @@ -1213,7 +1213,7 @@ class NsProviderApp(cmd2.Cmd): # Used to test namespace providers in CommandSets def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - super(NsProviderApp, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @cmd2.with_argparser(cmd2.Cmd2ArgumentParser(), ns_provider=NsProviderSet.ns_provider) def do_test_ns(self, args: argparse.Namespace) -> None: From 916802d824ffd89de94794836169ef046d45388d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 02:42:59 -0400 Subject: [PATCH 57/79] Enable ruff UP ruleset for pyupgrade --- cmd2/argparse_custom.py | 6 +++--- cmd2/cmd2.py | 2 +- plugins/ext_test/cmd2_ext_test/__init__.py | 2 -- plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py | 2 -- plugins/ext_test/examples/example.py | 3 --- plugins/ext_test/setup.py | 3 --- plugins/ext_test/tasks.py | 2 +- plugins/ext_test/tests/test_ext_test.py | 3 --- pyproject.toml | 8 +++++++- 9 files changed, 12 insertions(+), 19 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 79d865155..d11b36f0d 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -283,7 +283,7 @@ class CompletionItem(str): # noqa: SLOT000 """ def __new__(cls, value: object, *args: Any, **kwargs: Any) -> 'CompletionItem': - return super(CompletionItem, cls).__new__(cls, value) + return super().__new__(cls, value) def __init__(self, value: object, description: str = '', *args: Any) -> None: """ @@ -1252,7 +1252,7 @@ def __init__( """ if sys.version_info >= (3, 14): # Python >= 3.14 so pass new arguments to parent argparse.ArgumentParser class - super(Cmd2ArgumentParser, self).__init__( + super().__init__( prog=prog, usage=usage, description=description, @@ -1271,7 +1271,7 @@ def __init__( ) else: # Python < 3.14, so don't pass new arguments to parent argparse.ArgumentParser class - super(Cmd2ArgumentParser, self).__init__( + super().__init__( prog=prog, usage=usage, description=description, diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index f298222c8..eb4cf6091 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -4089,7 +4089,7 @@ def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], p except IndexError: fulloptions.append((opt[0], opt[0])) for idx, (_, text) in enumerate(fulloptions): - self.poutput(' %2d. %s' % (idx + 1, text)) + self.poutput(' %2d. %s' % (idx + 1, text)) # noqa: UP031 while True: try: diff --git a/plugins/ext_test/cmd2_ext_test/__init__.py b/plugins/ext_test/cmd2_ext_test/__init__.py index b154f0ed7..94796e7b3 100644 --- a/plugins/ext_test/cmd2_ext_test/__init__.py +++ b/plugins/ext_test/cmd2_ext_test/__init__.py @@ -1,5 +1,3 @@ -# -# coding=utf-8 """cmd2 External Python Testing Mixin Allows developers to exercise their cmd2 application using the PyScript interface diff --git a/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py b/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py index d3df9e250..fc2c43fe5 100644 --- a/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py +++ b/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py @@ -1,5 +1,3 @@ -# -# coding=utf-8 """External test interface plugin""" from typing import ( diff --git a/plugins/ext_test/examples/example.py b/plugins/ext_test/examples/example.py index 7dbb6677e..11e370450 100644 --- a/plugins/ext_test/examples/example.py +++ b/plugins/ext_test/examples/example.py @@ -1,6 +1,3 @@ -# -# coding=utf-8 -# import cmd2 import cmd2_ext_test import cmd2 diff --git a/plugins/ext_test/setup.py b/plugins/ext_test/setup.py index 42c6d8a91..a0e97ece9 100644 --- a/plugins/ext_test/setup.py +++ b/plugins/ext_test/setup.py @@ -1,6 +1,3 @@ -# -# coding=utf-8 - import os import setuptools diff --git a/plugins/ext_test/tasks.py b/plugins/ext_test/tasks.py index 8de8d3969..73c35433e 100644 --- a/plugins/ext_test/tasks.py +++ b/plugins/ext_test/tasks.py @@ -24,7 +24,7 @@ def rmrf(items, verbose=True): for item in items: if verbose: - print("Removing {}".format(item)) + print(f"Removing {item}") shutil.rmtree(item, ignore_errors=True) # rmtree doesn't remove bare files try: diff --git a/plugins/ext_test/tests/test_ext_test.py b/plugins/ext_test/tests/test_ext_test.py index 037157f10..df9216d8d 100644 --- a/plugins/ext_test/tests/test_ext_test.py +++ b/plugins/ext_test/tests/test_ext_test.py @@ -1,6 +1,3 @@ -# -# coding=utf-8 - import cmd2_ext_test import pytest diff --git a/pyproject.toml b/pyproject.toml index 3cfe0c943..f0892e834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -216,7 +216,7 @@ select = [ "TD", # flake8-todos (force all TODOs to include an author and issue link) "TID", # flake8-tidy-imports (extra import rules to check) # "TRY", # tryceratops (warnings related to exceptions and try/except) - # "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) + "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) "W", # pycodestyle warnings (warn about minor stylistic issues) "YTT", # flake8-2020 (checks for misuse of sys.version or sys.version_info) ] @@ -237,6 +237,8 @@ ignore = [ "TC006", # Add quotes to type expression in typing.cast() (not needed except for forward references) "UP007", # Use X | Y for type annotations (requires Python 3.10+) "UP017", # Use datetime.UTC alias (requires Python 3.11+) + "UP036", # Version block is outdated for minimum Python version (requires ruff target_version set to minimum supported) + "UP038", # Use X | Y in {} call instead of (X, Y) - deprecated due to poor performance (requires Python 3.10+) "W191", # Conflicts with ruff format ] @@ -254,6 +256,10 @@ per-file-ignores."cmd2/__init__.py" = [ "F401", # Unused import ] +per-file-ignores."cmd2/argparse_custom.py" = [ + "UP031", # Use format specifiers instead of percent format (auto fix is unsafe) +] + per-file-ignores."examples/*.py" = [ "INP001", # Module is part of an implicit namespace "PLW2901", # loop variable overwritten inside loop From 881a9855446dea34bf1e0d611f7d79c849f3e397 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 12:12:44 -0400 Subject: [PATCH 58/79] Automatically applied return type of None for functions/methods that don't return anything --- examples/alias_startup.py | 4 +- examples/arg_decorators.py | 2 +- examples/arg_print.py | 12 +- examples/argparse_completion.py | 2 +- examples/async_printing.py | 4 +- examples/basic.py | 6 +- examples/basic_completion.py | 10 +- examples/cmd_as_argument.py | 6 +- examples/colors.py | 6 +- examples/decorator_example.py | 8 +- examples/default_categories.py | 16 +- examples/dynamic_commands.py | 6 +- examples/environment.py | 6 +- examples/event_loops.py | 2 +- examples/example.py | 6 +- examples/exit_code.py | 2 +- examples/first_app.py | 4 +- examples/help_categories.py | 46 +- examples/hooks.py | 2 +- examples/initialization.py | 6 +- examples/migrating.py | 6 +- .../modular_commands/commandset_complex.py | 12 +- .../modular_commands/commandset_custominit.py | 6 +- examples/modular_commands_basic.py | 10 +- examples/modular_commands_dynamic.py | 18 +- examples/modular_commands_main.py | 2 +- examples/modular_subcommands.py | 20 +- examples/paged_output.py | 8 +- examples/persistent_history.py | 2 +- examples/pirate.py | 12 +- examples/python_scripting.py | 8 +- examples/remove_builtin_commands.py | 2 +- examples/remove_settable.py | 2 +- examples/subcommands.py | 12 +- examples/table_creation.py | 6 +- examples/unicode_commands.py | 6 +- plugins/tasks.py | 22 +- plugins/template/cmd2_myplugin/myplugin.py | 6 +- plugins/template/examples/example.py | 4 +- plugins/template/noxfile.py | 2 +- plugins/template/tasks.py | 24 +- plugins/template/tests/test_myplugin.py | 8 +- pyproject.toml | 2 +- tasks.py | 44 +- tests/test_ansi.py | 48 +- tests/test_argparse.py | 96 ++-- tests/test_argparse_completer.py | 66 +-- tests/test_argparse_custom.py | 44 +- tests/test_cmd2.py | 475 +++++++++--------- tests/test_completion.py | 201 ++++---- tests/test_history.py | 102 ++-- tests/test_parsing.py | 130 ++--- tests/test_plugin.py | 115 ++--- tests/test_run_pyscript.py | 32 +- tests/test_table_creator.py | 34 +- tests/test_transcript.py | 29 +- tests/test_utils.py | 160 +++--- tests/test_utils_defining_class.py | 20 +- tests_isolated/test_commandset/conftest.py | 2 +- .../test_argparse_subcommands.py | 22 +- .../test_commandset/test_categories.py | 20 +- .../test_commandset/test_commandset.py | 128 ++--- 62 files changed, 1064 insertions(+), 1060 deletions(-) diff --git a/examples/alias_startup.py b/examples/alias_startup.py index 408ffd16f..cac5a265f 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -12,11 +12,11 @@ class AliasAndStartup(cmd2.Cmd): """Example cmd2 application where we create commands that just print the arguments they are called with.""" - def __init__(self): + def __init__(self) -> None: alias_script = os.path.join(os.path.dirname(__file__), '.cmd2rc') super().__init__(startup_script=alias_script) - def do_nothing(self, args): + def do_nothing(self, args) -> None: """This command does nothing and produces no output.""" diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index 916358179..ec0cddd79 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -8,7 +8,7 @@ class ArgparsingApp(cmd2.Cmd): - def __init__(self): + def __init__(self) -> None: super().__init__(include_ipy=True) self.intro = 'cmd2 has awesome decorators to make it easy to use Argparse to parse command arguments' diff --git a/examples/arg_print.py b/examples/arg_print.py index 26ccac9ca..1ca73b753 100755 --- a/examples/arg_print.py +++ b/examples/arg_print.py @@ -15,13 +15,13 @@ class ArgumentAndOptionPrinter(cmd2.Cmd): """Example cmd2 application where we create commands that just print the arguments they are called with.""" - def __init__(self): + def __init__(self) -> None: # Create command shortcuts which are typically 1 character abbreviations which can be used in place of a command shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) shortcuts.update({'$': 'aprint', '%': 'oprint'}) super().__init__(shortcuts=shortcuts) - def do_aprint(self, statement): + def do_aprint(self, statement) -> None: """Print the argument string this basic command is called with.""" self.poutput(f'aprint was called with argument: {statement!r}') self.poutput(f'statement.raw = {statement.raw!r}') @@ -29,12 +29,12 @@ def do_aprint(self, statement): self.poutput(f'statement.command = {statement.command!r}') @cmd2.with_argument_list - def do_lprint(self, arglist): + def do_lprint(self, arglist) -> None: """Print the argument list this basic command is called with.""" self.poutput(f'lprint was called with the following list of arguments: {arglist!r}') @cmd2.with_argument_list(preserve_quotes=True) - def do_rprint(self, arglist): + def do_rprint(self, arglist) -> None: """Print the argument list this basic command is called with (with quotes preserved).""" self.poutput(f'rprint was called with the following list of arguments: {arglist!r}') @@ -45,7 +45,7 @@ def do_rprint(self, arglist): oprint_parser.add_argument('words', nargs='+', help='words to print') @cmd2.with_argparser(oprint_parser) - def do_oprint(self, args): + def do_oprint(self, args) -> None: """Print the options and argument list this options command was called with.""" self.poutput(f'oprint was called with the following\n\toptions: {args!r}') @@ -55,7 +55,7 @@ def do_oprint(self, args): pprint_parser.add_argument('-r', '--repeat', type=int, help='output [n] times') @cmd2.with_argparser(pprint_parser, with_unknown_args=True) - def do_pprint(self, args, unknown): + def do_pprint(self, args, unknown) -> None: """Print the options and argument list this options command was called with.""" self.poutput(f'oprint was called with the following\n\toptions: {args!r}\n\targuments: {unknown}') diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index 84082a6d3..df3c31551 100755 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -19,7 +19,7 @@ class ArgparseCompletion(Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] diff --git a/examples/async_printing.py b/examples/async_printing.py index 5989f7329..d1137c3fe 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -68,7 +68,7 @@ def _postloop_hook(self) -> None: if self._alerter_thread.is_alive(): self._alerter_thread.join() - def do_start_alerts(self, _): + def do_start_alerts(self, _) -> None: """Starts the alerter thread""" if self._alerter_thread.is_alive(): print("The alert thread is already started") @@ -77,7 +77,7 @@ def do_start_alerts(self, _): self._alerter_thread = threading.Thread(name='alerter', target=self._alerter_thread_func) self._alerter_thread.start() - def do_stop_alerts(self, _): + def do_stop_alerts(self, _) -> None: """Stops the alerter thread""" self._stop_event.set() if self._alerter_thread.is_alive(): diff --git a/examples/basic.py b/examples/basic.py index f84009b2b..e088f3820 100755 --- a/examples/basic.py +++ b/examples/basic.py @@ -19,7 +19,7 @@ class BasicApp(cmd2.Cmd): CUSTOM_CATEGORY = 'My Custom Commands' - def __init__(self): + def __init__(self) -> None: super().__init__( multiline_commands=['echo'], persistent_history_file='cmd2_history.dat', @@ -36,12 +36,12 @@ def __init__(self): self.default_category = 'cmd2 Built-in Commands' @cmd2.with_category(CUSTOM_CATEGORY) - def do_intro(self, _): + def do_intro(self, _) -> None: """Display the intro banner""" self.poutput(self.intro) @cmd2.with_category(CUSTOM_CATEGORY) - def do_echo(self, arg): + def do_echo(self, arg) -> None: """Example of a multiline command""" self.poutput(arg) diff --git a/examples/basic_completion.py b/examples/basic_completion.py index e2288f4bf..0c451077c 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -31,10 +31,10 @@ class BasicCompletion(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def do_flag_based(self, statement: cmd2.Statement): + def do_flag_based(self, statement: cmd2.Statement) -> None: """Tab completes arguments based on a preceding flag using flag_based_complete -f, --food [completes food items] -s, --sport [completes sports] @@ -58,7 +58,7 @@ def complete_flag_based(self, text, line, begidx, endidx) -> list[str]: return self.flag_based_complete(text, line, begidx, endidx, flag_dict=flag_dict) - def do_index_based(self, statement: cmd2.Statement): + def do_index_based(self, statement: cmd2.Statement) -> None: """Tab completes first 3 arguments using index_based_complete""" self.poutput(f"Args: {statement.args}") @@ -72,14 +72,14 @@ def complete_index_based(self, text, line, begidx, endidx) -> list[str]: return self.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) - def do_delimiter_complete(self, statement: cmd2.Statement): + def do_delimiter_complete(self, statement: cmd2.Statement) -> None: """Tab completes files from a list using delimiter_complete""" self.poutput(f"Args: {statement.args}") # Use a partialmethod to set arguments to delimiter_complete complete_delimiter_complete = functools.partialmethod(cmd2.Cmd.delimiter_complete, match_against=file_strs, delimiter='/') - def do_raise_error(self, statement: cmd2.Statement): + def do_raise_error(self, statement: cmd2.Statement) -> None: """Demonstrates effect of raising CompletionError""" self.poutput(f"Args: {statement.args}") diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index 9f2176267..2cb7691c0 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -26,7 +26,7 @@ class CmdLineApp(cmd2.Cmd): MUMBLE_FIRST = ['so', 'like', 'well'] MUMBLE_LAST = ['right?'] - def __init__(self): + def __init__(self) -> None: shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) shortcuts.update({'&': 'speak'}) # Set include_ipy to True to enable the "ipy" command which runs an interactive IPython shell @@ -44,7 +44,7 @@ def __init__(self): speak_parser.add_argument('words', nargs='+', help='words to say') @cmd2.with_argparser(speak_parser) - def do_speak(self, args): + def do_speak(self, args) -> None: """Repeats what you tell me to.""" words = [] for word in args.words: @@ -66,7 +66,7 @@ def do_speak(self, args): mumble_parser.add_argument('words', nargs='+', help='words to say') @cmd2.with_argparser(mumble_parser) - def do_mumble(self, args): + def do_mumble(self, args) -> None: """Mumbles what you tell me to.""" repetitions = args.repeat or 1 for i in range(min(repetitions, self.maxrepeats)): diff --git a/examples/colors.py b/examples/colors.py index 22d146fcd..792173b86 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -36,7 +36,7 @@ class CmdLineApp(cmd2.Cmd): """Example cmd2 application demonstrating colorized output.""" - def __init__(self): + def __init__(self) -> None: # Set include_ipy to True to enable the "ipy" command which runs an interactive IPython shell super().__init__(include_ipy=True) @@ -58,7 +58,7 @@ def __init__(self): speak_parser.add_argument('words', nargs='+', help='words to say') @cmd2.with_argparser(speak_parser) - def do_speak(self, args): + def do_speak(self, args) -> None: """Repeats what you tell me to.""" words = [] for word in args.words: @@ -78,7 +78,7 @@ def do_speak(self, args): # .poutput handles newlines, and accommodates output redirection too self.poutput(output_str) - def do_timetravel(self, _): + def do_timetravel(self, _) -> None: """A command which always generates an error message, to demonstrate custom error colors""" self.perror('Mr. Fusion failed to start. Could not energize flux capacitor.') diff --git a/examples/decorator_example.py b/examples/decorator_example.py index daae235f8..ce48b4ba6 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -18,7 +18,7 @@ class CmdLineApp(cmd2.Cmd): """Example cmd2 application.""" - def __init__(self, ip_addr=None, port=None, transcript_files=None): + def __init__(self, ip_addr=None, port=None, transcript_files=None) -> None: shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) shortcuts.update({'&': 'speak'}) super().__init__(transcript_files=transcript_files, multiline_commands=['orate'], shortcuts=shortcuts) @@ -41,7 +41,7 @@ def __init__(self, ip_addr=None, port=None, transcript_files=None): speak_parser.add_argument('words', nargs='+', help='words to say') @cmd2.with_argparser(speak_parser) - def do_speak(self, args: argparse.Namespace): + def do_speak(self, args: argparse.Namespace) -> None: """Repeats what you tell me to.""" words = [] for word in args.words: @@ -62,7 +62,7 @@ def do_speak(self, args: argparse.Namespace): tag_parser.add_argument('content', nargs='+', help='content to surround with tag') @cmd2.with_argparser(tag_parser) - def do_tag(self, args: argparse.Namespace): + def do_tag(self, args: argparse.Namespace) -> None: """create an html tag""" # The Namespace always includes the Statement object created when parsing the command line statement = args.cmd2_statement.get() @@ -72,7 +72,7 @@ def do_tag(self, args: argparse.Namespace): self.poutput('<{0}>{1}'.format(args.tag, ' '.join(args.content))) @cmd2.with_argument_list - def do_tagg(self, arglist: list[str]): + def do_tagg(self, arglist: list[str]) -> None: """version of creating an html tag using arglist instead of argparser""" if len(arglist) >= 2: tag = arglist[0] diff --git a/examples/default_categories.py b/examples/default_categories.py index dcec6fd67..3a26a45ff 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -20,10 +20,10 @@ class ChildInheritsParentCategories(MyBaseCommandSet): This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category' """ - def do_hello(self, _: cmd2.Statement): + def do_hello(self, _: cmd2.Statement) -> None: self._cmd.poutput('Hello') - def do_world(self, _: cmd2.Statement): + def do_world(self, _: cmd2.Statement) -> None: self._cmd.poutput('World') @@ -34,7 +34,7 @@ class ChildOverridesParentCategoriesNonHeritable(MyBaseCommandSet): CommandSet will not inherit this category and will, instead, inherit 'Default Category' """ - def do_goodbye(self, _: cmd2.Statement): + def do_goodbye(self, _: cmd2.Statement) -> None: self._cmd.poutput('Goodbye') @@ -44,7 +44,7 @@ class GrandchildInheritsGrandparentCategory(ChildOverridesParentCategoriesNonHer by the grandparent class. """ - def do_aloha(self, _: cmd2.Statement): + def do_aloha(self, _: cmd2.Statement) -> None: self._cmd.poutput('Aloha') @@ -55,7 +55,7 @@ class ChildOverridesParentCategories(MyBaseCommandSet): category declaration. """ - def do_bonjour(self, _: cmd2.Statement): + def do_bonjour(self, _: cmd2.Statement) -> None: self._cmd.poutput('Bonjour') @@ -65,7 +65,7 @@ class GrandchildInheritsHeritable(ChildOverridesParentCategories): CommandSet will be categorized under 'Heritable Category' """ - def do_monde(self, _: cmd2.Statement): + def do_monde(self, _: cmd2.Statement) -> None: self._cmd.poutput('Monde') @@ -74,10 +74,10 @@ class ExampleApp(cmd2.Cmd): Example to demonstrate heritable default categories """ - def __init__(self): + def __init__(self) -> None: super().__init__() - def do_something(self, arg): + def do_something(self, arg) -> None: self.poutput('this is the something command') diff --git a/examples/dynamic_commands.py b/examples/dynamic_commands.py index ef5c520b1..f7740da79 100755 --- a/examples/dynamic_commands.py +++ b/examples/dynamic_commands.py @@ -16,7 +16,7 @@ class CommandsInLoop(cmd2.Cmd): """Example of dynamically adding do_* commands.""" - def __init__(self): + def __init__(self) -> None: # Add dynamic commands before calling cmd2.Cmd's init since it validates command names for command in COMMAND_LIST: # Create command function and add help category to it @@ -34,11 +34,11 @@ def __init__(self): super().__init__(include_ipy=True) - def send_text(self, args: cmd2.Statement, *, text: str): + def send_text(self, args: cmd2.Statement, *, text: str) -> None: """Simulate sending text to a server and printing the response.""" self.poutput(text.capitalize()) - def text_help(self, *, text: str): + def text_help(self, *, text: str) -> None: """Deal with printing help for the dynamically added commands.""" self.poutput(f"Simulate sending {text!r} to a server and printing the response") diff --git a/examples/environment.py b/examples/environment.py index 38c0f0bd6..352ca9d0e 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -9,7 +9,7 @@ class EnvironmentApp(cmd2.Cmd): """Example cmd2 application.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self.degrees_c = 22 self.sunny = False @@ -18,7 +18,7 @@ def __init__(self): ) self.add_settable(cmd2.Settable('sunny', bool, 'Is it sunny outside?', self)) - def do_sunbathe(self, arg): + def do_sunbathe(self, arg) -> None: """Attempt to sunbathe.""" if self.degrees_c < 20: result = f"It's {self.degrees_c} C - are you a penguin?" @@ -28,7 +28,7 @@ def do_sunbathe(self, arg): result = 'UV is bad for your skin.' self.poutput(result) - def _onchange_degrees_c(self, param_name, old, new): + def _onchange_degrees_c(self, param_name, old, new) -> None: # if it's over 40C, it's gotta be sunny, right? if new > 40: self.sunny = True diff --git a/examples/event_loops.py b/examples/event_loops.py index 8f627577b..aca434207 100755 --- a/examples/event_loops.py +++ b/examples/event_loops.py @@ -12,7 +12,7 @@ class Cmd2EventBased(cmd2.Cmd): """Basic example of how to run cmd2 without it controlling the main loop.""" - def __init__(self): + def __init__(self) -> None: super().__init__() # ... your class code here ... diff --git a/examples/example.py b/examples/example.py index afa5d90e2..51e35289d 100755 --- a/examples/example.py +++ b/examples/example.py @@ -24,7 +24,7 @@ class CmdLineApp(cmd2.Cmd): MUMBLE_FIRST = ['so', 'like', 'well'] MUMBLE_LAST = ['right?'] - def __init__(self): + def __init__(self) -> None: shortcuts = cmd2.DEFAULT_SHORTCUTS shortcuts.update({'&': 'speak'}) super().__init__(multiline_commands=['orate'], shortcuts=shortcuts) @@ -40,7 +40,7 @@ def __init__(self): speak_parser.add_argument('words', nargs='+', help='words to say') @cmd2.with_argparser(speak_parser) - def do_speak(self, args): + def do_speak(self, args) -> None: """Repeats what you tell me to.""" words = [] for word in args.words: @@ -62,7 +62,7 @@ def do_speak(self, args): mumble_parser.add_argument('words', nargs='+', help='words to say') @cmd2.with_argparser(mumble_parser) - def do_mumble(self, args): + def do_mumble(self, args) -> None: """Mumbles what you tell me to.""" repetitions = args.repeat or 1 for _ in range(min(repetitions, self.maxrepeats)): diff --git a/examples/exit_code.py b/examples/exit_code.py index ccf4b8f21..58877d2ad 100755 --- a/examples/exit_code.py +++ b/examples/exit_code.py @@ -7,7 +7,7 @@ class ReplWithExitCode(cmd2.Cmd): """Example cmd2 application where we can specify an exit code when existing.""" - def __init__(self): + def __init__(self) -> None: super().__init__() @cmd2.with_argument_list diff --git a/examples/first_app.py b/examples/first_app.py index d32b96f37..f52cb6116 100755 --- a/examples/first_app.py +++ b/examples/first_app.py @@ -18,7 +18,7 @@ class FirstApp(cmd2.Cmd): """A simple cmd2 application.""" - def __init__(self): + def __init__(self) -> None: shortcuts = cmd2.DEFAULT_SHORTCUTS shortcuts.update({'&': 'speak'}) super().__init__(multiline_commands=['orate'], shortcuts=shortcuts) @@ -34,7 +34,7 @@ def __init__(self): speak_parser.add_argument('words', nargs='+', help='words to say') @cmd2.with_argparser(speak_parser) - def do_speak(self, args): + def do_speak(self, args) -> None: """Repeats what you tell me to.""" words = [] for word in args.words: diff --git a/examples/help_categories.py b/examples/help_categories.py index 4790b915f..35f333942 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -33,10 +33,10 @@ class HelpCategories(cmd2.Cmd): CMD_CAT_APP_MGMT = 'Application Management' CMD_CAT_SERVER_INFO = 'Server Information' - def __init__(self): + def __init__(self) -> None: super().__init__() - def do_connect(self, _): + def do_connect(self, _) -> None: """Connect command""" self.poutput('Connect') @@ -44,15 +44,15 @@ def do_connect(self, _): cmd2.categorize(do_connect, CMD_CAT_CONNECTING) @cmd2.with_category(CMD_CAT_CONNECTING) - def do_which(self, _): + def do_which(self, _) -> None: """Which command""" self.poutput('Which') - def do_list(self, _): + def do_list(self, _) -> None: """List command""" self.poutput('List') - def do_deploy(self, _): + def do_deploy(self, _) -> None: """Deploy command""" self.poutput('Deploy') @@ -63,15 +63,15 @@ def do_deploy(self, _): @my_decorator @cmd2.with_argparser(start_parser) - def do_start(self, _): + def do_start(self, _) -> None: """Start command""" self.poutput('Start') - def do_sessions(self, _): + def do_sessions(self, _) -> None: """Sessions command""" self.poutput('Sessions') - def do_redeploy(self, _): + def do_redeploy(self, _) -> None: """Redeploy command""" self.poutput('Redeploy') @@ -83,23 +83,23 @@ def do_redeploy(self, _): @cmd2.with_argparser(restart_parser) @cmd2.with_category(CMD_CAT_APP_MGMT) @my_decorator - def do_restart(self, _): + def do_restart(self, _) -> None: """Restart command""" self.poutput('Restart') - def do_expire(self, _): + def do_expire(self, _) -> None: """Expire command""" self.poutput('Expire') - def do_undeploy(self, _): + def do_undeploy(self, _) -> None: """Undeploy command""" self.poutput('Undeploy') - def do_stop(self, _): + def do_stop(self, _) -> None: """Stop command""" self.poutput('Stop') - def do_findleakers(self, _): + def do_findleakers(self, _) -> None: """Find Leakers command""" self.poutput('Find Leakers') @@ -109,23 +109,23 @@ def do_findleakers(self, _): CMD_CAT_APP_MGMT, ) - def do_resources(self, _): + def do_resources(self, _) -> None: """Resources command""" self.poutput('Resources') - def do_status(self, _): + def do_status(self, _) -> None: """Status command""" self.poutput('Status') - def do_serverinfo(self, _): + def do_serverinfo(self, _) -> None: """Server Info command""" self.poutput('Server Info') - def do_thread_dump(self, _): + def do_thread_dump(self, _) -> None: """Thread Dump command""" self.poutput('Thread Dump') - def do_sslconnectorciphers(self, _): + def do_sslconnectorciphers(self, _) -> None: """ SSL Connector Ciphers command is an example of a command that contains multiple lines of help information for the user. Each line of help in a @@ -136,7 +136,7 @@ def do_sslconnectorciphers(self, _): """ self.poutput('SSL Connector Ciphers') - def do_vminfo(self, _): + def do_vminfo(self, _) -> None: """VM Info command""" self.poutput('VM Info') @@ -150,23 +150,23 @@ def do_vminfo(self, _): # The following command functions don't have the HELP_CATEGORY attribute set # and show up in the 'Other' group - def do_config(self, _): + def do_config(self, _) -> None: """Config command""" self.poutput('Config') - def do_version(self, _): + def do_version(self, _) -> None: """Version command""" self.poutput(cmd2.__version__) @cmd2.with_category("Command Management") - def do_disable_commands(self, _): + def do_disable_commands(self, _) -> None: """Disable the Application Management commands""" message_to_print = f"{COMMAND_NAME} is not available while {self.CMD_CAT_APP_MGMT} commands are disabled" self.disable_category(self.CMD_CAT_APP_MGMT, message_to_print) self.poutput("The Application Management commands have been disabled") @cmd2.with_category("Command Management") - def do_enable_commands(self, _): + def do_enable_commands(self, _) -> None: """Enable the Application Management commands""" self.enable_category(self.CMD_CAT_APP_MGMT) self.poutput("The Application Management commands have been enabled") diff --git a/examples/hooks.py b/examples/hooks.py index 683b75474..e019b3e0b 100755 --- a/examples/hooks.py +++ b/examples/hooks.py @@ -40,7 +40,7 @@ class CmdLineApp(cmd2.Cmd): # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist # default_to_shell = True - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) # register four hooks diff --git a/examples/initialization.py b/examples/initialization.py index ea9d88589..3267a3258 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -23,7 +23,7 @@ class BasicApp(cmd2.Cmd): CUSTOM_CATEGORY = 'My Custom Commands' - def __init__(self): + def __init__(self) -> None: super().__init__( multiline_commands=['echo'], persistent_history_file='cmd2_history.dat', @@ -56,12 +56,12 @@ def __init__(self): ) @cmd2.with_category(CUSTOM_CATEGORY) - def do_intro(self, _): + def do_intro(self, _) -> None: """Display the intro banner""" self.poutput(self.intro) @cmd2.with_category(CUSTOM_CATEGORY) - def do_echo(self, arg): + def do_echo(self, arg) -> None: """Example of a multiline command""" fg_color = Fg[self.foreground_color.upper()] self.poutput(style(arg, fg=fg_color)) diff --git a/examples/migrating.py b/examples/migrating.py index 590e9ee20..5d57f1ba2 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -15,20 +15,20 @@ class CmdLineApp(cmd.Cmd): MUMBLE_FIRST = ['so', 'like', 'well'] MUMBLE_LAST = ['right?'] - def do_exit(self, line): + def do_exit(self, line) -> bool: """Exit the application""" return True do_EOF = do_exit do_quit = do_exit - def do_speak(self, line): + def do_speak(self, line) -> None: """Repeats what you tell me to.""" print(line, file=self.stdout) do_say = do_speak - def do_mumble(self, line): + def do_mumble(self, line) -> None: """Mumbles what you tell me to.""" words = line.split(' ') output = [] diff --git a/examples/modular_commands/commandset_complex.py b/examples/modular_commands/commandset_complex.py index 140f68e8e..1cc76e3a8 100644 --- a/examples/modular_commands/commandset_complex.py +++ b/examples/modular_commands/commandset_complex.py @@ -9,10 +9,10 @@ @cmd2.with_default_category('Fruits') class CommandSetA(cmd2.CommandSet): - def do_apple(self, statement: cmd2.Statement): + def do_apple(self, statement: cmd2.Statement) -> None: self._cmd.poutput('Apple!') - def do_banana(self, statement: cmd2.Statement): + def do_banana(self, statement: cmd2.Statement) -> None: """Banana Command""" self._cmd.poutput('Banana!!') @@ -20,18 +20,18 @@ def do_banana(self, statement: cmd2.Statement): cranberry_parser.add_argument('arg1', choices=['lemonade', 'juice', 'sauce']) @cmd2.with_argparser(cranberry_parser, with_unknown_args=True) - def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]): + def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]) -> None: self._cmd.poutput(f'Cranberry {ns.arg1}!!') if unknown and len(unknown): self._cmd.poutput('Unknown: ' + ', '.join(['{}'] * len(unknown)).format(*unknown)) self._cmd.last_result = {'arg1': ns.arg1, 'unknown': unknown} - def help_cranberry(self): + def help_cranberry(self) -> None: self._cmd.stdout.write('This command does diddly squat...\n') @cmd2.with_argument_list @cmd2.with_category('Also Alone') - def do_durian(self, args: list[str]): + def do_durian(self, args: list[str]) -> None: """Durian Command""" self._cmd.poutput(f'{len(args)} Arguments: ') self._cmd.poutput(', '.join(['{}'] * len(args)).format(*args)) @@ -44,5 +44,5 @@ def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> lis @cmd2.with_category('Alone') @cmd2.with_argparser(elderberry_parser) - def do_elderberry(self, ns: argparse.Namespace): + def do_elderberry(self, ns: argparse.Namespace) -> None: self._cmd.poutput(f'Elderberry {ns.arg1}!!') diff --git a/examples/modular_commands/commandset_custominit.py b/examples/modular_commands/commandset_custominit.py index 5ef2beca4..0b5101b27 100644 --- a/examples/modular_commands/commandset_custominit.py +++ b/examples/modular_commands/commandset_custominit.py @@ -12,14 +12,14 @@ @with_default_category('Custom Init') class CustomInitCommandSet(CommandSet): - def __init__(self, arg1, arg2): + def __init__(self, arg1, arg2) -> None: super().__init__() self._arg1 = arg1 self._arg2 = arg2 - def do_show_arg1(self, cmd: Cmd, _: Statement): + def do_show_arg1(self, cmd: Cmd, _: Statement) -> None: self._cmd.poutput('Arg1: ' + self._arg1) - def do_show_arg2(self, cmd: Cmd, _: Statement): + def do_show_arg2(self, cmd: Cmd, _: Statement) -> None: self._cmd.poutput('Arg2: ' + self._arg2) diff --git a/examples/modular_commands_basic.py b/examples/modular_commands_basic.py index 12eca4073..23065a3bd 100755 --- a/examples/modular_commands_basic.py +++ b/examples/modular_commands_basic.py @@ -12,13 +12,13 @@ @with_default_category('My Category') class AutoLoadCommandSet(CommandSet): - def __init__(self): + def __init__(self) -> None: super().__init__() - def do_hello(self, _: cmd2.Statement): + def do_hello(self, _: cmd2.Statement) -> None: self._cmd.poutput('Hello') - def do_world(self, _: cmd2.Statement): + def do_world(self, _: cmd2.Statement) -> None: self._cmd.poutput('World') @@ -27,10 +27,10 @@ class ExampleApp(cmd2.Cmd): CommandSets are automatically loaded. Nothing needs to be done. """ - def __init__(self): + def __init__(self) -> None: super().__init__() - def do_something(self, arg): + def do_something(self, arg) -> None: self.poutput('this is the something command') diff --git a/examples/modular_commands_dynamic.py b/examples/modular_commands_dynamic.py index b68e6f577..6a399a5c8 100755 --- a/examples/modular_commands_dynamic.py +++ b/examples/modular_commands_dynamic.py @@ -21,25 +21,25 @@ @with_default_category('Fruits') class LoadableFruits(CommandSet): - def __init__(self): + def __init__(self) -> None: super().__init__() - def do_apple(self, _: cmd2.Statement): + def do_apple(self, _: cmd2.Statement) -> None: self._cmd.poutput('Apple') - def do_banana(self, _: cmd2.Statement): + def do_banana(self, _: cmd2.Statement) -> None: self._cmd.poutput('Banana') @with_default_category('Vegetables') class LoadableVegetables(CommandSet): - def __init__(self): + def __init__(self) -> None: super().__init__() - def do_arugula(self, _: cmd2.Statement): + def do_arugula(self, _: cmd2.Statement) -> None: self._cmd.poutput('Arugula') - def do_bokchoy(self, _: cmd2.Statement): + def do_bokchoy(self, _: cmd2.Statement) -> None: self._cmd.poutput('Bok Choy') @@ -48,7 +48,7 @@ class ExampleApp(cmd2.Cmd): CommandSets are loaded via the `load` and `unload` commands """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # gotta have this or neither the plugin or cmd2 will initialize super().__init__(*args, auto_load_commands=False, **kwargs) @@ -60,7 +60,7 @@ def __init__(self, *args, **kwargs): @with_argparser(load_parser) @with_category('Command Loading') - def do_load(self, ns: argparse.Namespace): + def do_load(self, ns: argparse.Namespace) -> None: if ns.cmds == 'fruits': try: self.register_command_set(self._fruits) @@ -76,7 +76,7 @@ def do_load(self, ns: argparse.Namespace): self.poutput('Vegetables already loaded') @with_argparser(load_parser) - def do_unload(self, ns: argparse.Namespace): + def do_unload(self, ns: argparse.Namespace) -> None: if ns.cmds == 'fruits': self.unregister_command_set(self._fruits) self.poutput('Fruits unloaded') diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 202eedc57..7477d5310 100755 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -27,7 +27,7 @@ class WithCommandSets(Cmd): - def __init__(self, command_sets: Optional[Iterable[CommandSet]] = None): + def __init__(self, command_sets: Optional[Iterable[CommandSet]] = None) -> None: super().__init__(command_sets=command_sets) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index ef340f526..e3a0fec43 100755 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -23,10 +23,10 @@ @with_default_category('Fruits') class LoadableFruits(CommandSet): - def __init__(self): + def __init__(self) -> None: super().__init__() - def do_apple(self, _: cmd2.Statement): + def do_apple(self, _: cmd2.Statement) -> None: self._cmd.poutput('Apple') banana_description = "Cut a banana" @@ -34,17 +34,17 @@ def do_apple(self, _: cmd2.Statement): banana_parser.add_argument('direction', choices=['discs', 'lengthwise']) @cmd2.as_subcommand_to('cut', 'banana', banana_parser, help=banana_description.lower()) - def cut_banana(self, ns: argparse.Namespace): + def cut_banana(self, ns: argparse.Namespace) -> None: """Cut banana""" self._cmd.poutput('cutting banana: ' + ns.direction) @with_default_category('Vegetables') class LoadableVegetables(CommandSet): - def __init__(self): + def __init__(self) -> None: super().__init__() - def do_arugula(self, _: cmd2.Statement): + def do_arugula(self, _: cmd2.Statement) -> None: self._cmd.poutput('Arugula') bokchoy_description = "Cut some bokchoy" @@ -52,7 +52,7 @@ def do_arugula(self, _: cmd2.Statement): bokchoy_parser.add_argument('style', choices=['quartered', 'diced']) @cmd2.as_subcommand_to('cut', 'bokchoy', bokchoy_parser, help=bokchoy_description.lower()) - def cut_bokchoy(self, _: argparse.Namespace): + def cut_bokchoy(self, _: argparse.Namespace) -> None: self._cmd.poutput('Bok Choy') @@ -61,7 +61,7 @@ class ExampleApp(cmd2.Cmd): CommandSets are automatically loaded. Nothing needs to be done. """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # gotta have this or neither the plugin or cmd2 will initialize super().__init__(*args, auto_load_commands=False, **kwargs) @@ -73,7 +73,7 @@ def __init__(self, *args, **kwargs): @with_argparser(load_parser) @with_category('Command Loading') - def do_load(self, ns: argparse.Namespace): + def do_load(self, ns: argparse.Namespace) -> None: if ns.cmds == 'fruits': try: self.register_command_set(self._fruits) @@ -89,7 +89,7 @@ def do_load(self, ns: argparse.Namespace): self.poutput('Vegetables already loaded') @with_argparser(load_parser) - def do_unload(self, ns: argparse.Namespace): + def do_unload(self, ns: argparse.Namespace) -> None: if ns.cmds == 'fruits': self.unregister_command_set(self._fruits) self.poutput('Fruits unloaded') @@ -102,7 +102,7 @@ def do_unload(self, ns: argparse.Namespace): cut_subparsers = cut_parser.add_subparsers(title='item', help='item to cut') @with_argparser(cut_parser) - def do_cut(self, ns: argparse.Namespace): + def do_cut(self, ns: argparse.Namespace) -> None: # Call handler for whatever subcommand was selected handler = ns.cmd2_handler.get() if handler is not None: diff --git a/examples/paged_output.py b/examples/paged_output.py index 9aed7e193..935bdd2e9 100755 --- a/examples/paged_output.py +++ b/examples/paged_output.py @@ -9,10 +9,10 @@ class PagedOutput(cmd2.Cmd): """Example cmd2 application which shows how to display output using a pager.""" - def __init__(self): + def __init__(self) -> None: super().__init__() - def page_file(self, file_path: str, chop: bool = False): + def page_file(self, file_path: str, chop: bool = False) -> None: """Helper method to prevent having too much duplicated code.""" filename = os.path.expanduser(file_path) try: @@ -23,7 +23,7 @@ def page_file(self, file_path: str, chop: bool = False): self.pexcept(f'Error reading {filename!r}: {ex}') @cmd2.with_argument_list - def do_page_wrap(self, args: list[str]): + def do_page_wrap(self, args: list[str]) -> None: """Read in a text file and display its output in a pager, wrapping long lines if they don't fit. Usage: page_wrap @@ -36,7 +36,7 @@ def do_page_wrap(self, args: list[str]): complete_page_wrap = cmd2.Cmd.path_complete @cmd2.with_argument_list - def do_page_truncate(self, args: list[str]): + def do_page_truncate(self, args: list[str]) -> None: """Read in a text file and display its output in a pager, truncating long lines if they don't fit. Truncated lines can still be accessed by scrolling to the right using the arrow keys. diff --git a/examples/persistent_history.py b/examples/persistent_history.py index c185370cf..d2ae8ceff 100755 --- a/examples/persistent_history.py +++ b/examples/persistent_history.py @@ -11,7 +11,7 @@ class Cmd2PersistentHistory(cmd2.Cmd): """Basic example of how to enable persistent readline history within your cmd2 app.""" - def __init__(self, hist_file): + def __init__(self, hist_file) -> None: """Configure the app to load persistent history from a file (both readline and cmd2 history command affected). :param hist_file: file to load history from at start and write it to at end diff --git a/examples/pirate.py b/examples/pirate.py index 1e59e5fe6..d6095b6dd 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -20,7 +20,7 @@ class Pirate(cmd2.Cmd): """A piratical example cmd2 application involving looting and drinking.""" - def __init__(self): + def __init__(self) -> None: """Initialize the base class as well as this one""" shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) shortcuts.update({'~': 'sing'}) @@ -52,11 +52,11 @@ def postcmd(self, stop, line): stop = True return stop - def do_loot(self, arg): + def do_loot(self, arg) -> None: """Seize booty from a passing ship.""" self.gold += 1 - def do_drink(self, arg): + def do_drink(self, arg) -> None: """Drown your sorrrows in rrrum. drink [n] - drink [n] barrel[s] o' rum.""" @@ -67,12 +67,12 @@ def do_drink(self, arg): self.poutput(f'''What's "{arg}"? I'll take rrrum.''') self.gold -= 1 - def do_quit(self, arg): + def do_quit(self, arg) -> bool: """Quit the application gracefully.""" self.poutput("Quiterrr!") return True - def do_sing(self, arg): + def do_sing(self, arg) -> None: """Sing a colorful song.""" self.poutput(cmd2.ansi.style(arg, fg=Fg[self.songcolor.upper()])) @@ -82,7 +82,7 @@ def do_sing(self, arg): yo_parser.add_argument('beverage', help='beverage to drink with the chant') @cmd2.with_argparser(yo_parser) - def do_yo(self, args): + def do_yo(self, args) -> None: """Compose a yo-ho-ho type chant with flexible options.""" chant = ['yo'] + ['ho'] * args.ho separator = ', ' if args.commas else ' ' diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 1943e214e..d1def89c1 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -29,13 +29,13 @@ class CmdLineApp(cmd2.Cmd): """Example cmd2 application to showcase conditional control flow in Python scripting within cmd2 apps.""" - def __init__(self): + def __init__(self) -> None: # Set include_ipy to True to enable the "ipy" command which runs an interactive IPython shell super().__init__(include_ipy=True) self._set_prompt() self.intro = 'Happy 𝛑 Day. Note the full Unicode support: 😇 💩' - def _set_prompt(self): + def _set_prompt(self) -> None: """Set prompt so it displays the current working directory.""" self.cwd = os.getcwd() self.prompt = ansi.style(f'{self.cwd} $ ', fg=ansi.Fg.CYAN) @@ -52,7 +52,7 @@ def postcmd(self, stop: bool, line: str) -> bool: return stop @cmd2.with_argument_list - def do_cd(self, arglist): + def do_cd(self, arglist) -> None: """Change directory. Usage: cd @@ -96,7 +96,7 @@ def complete_cd(self, text, line, begidx, endidx): dir_parser.add_argument('-l', '--long', action='store_true', help="display in long format with one item per line") @cmd2.with_argparser(dir_parser, with_unknown_args=True) - def do_dir(self, args, unknown): + def do_dir(self, args, unknown) -> None: """List contents of current directory.""" # No arguments for this command if unknown: diff --git a/examples/remove_builtin_commands.py b/examples/remove_builtin_commands.py index a511a4944..64acd17d8 100755 --- a/examples/remove_builtin_commands.py +++ b/examples/remove_builtin_commands.py @@ -14,7 +14,7 @@ class RemoveBuiltinCommands(cmd2.Cmd): """Example cmd2 application where we remove some unused built-in commands.""" - def __init__(self): + def __init__(self) -> None: super().__init__() # To hide commands from displaying in the help menu, add them to the hidden_commands list diff --git a/examples/remove_settable.py b/examples/remove_settable.py index 191f033d7..266f7b7d5 100755 --- a/examples/remove_settable.py +++ b/examples/remove_settable.py @@ -7,7 +7,7 @@ class MyApp(cmd2.Cmd): - def __init__(self): + def __init__(self) -> None: super().__init__() self.remove_settable('debug') diff --git a/examples/subcommands.py b/examples/subcommands.py index bff9f2c86..4f6a936ed 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -67,19 +67,19 @@ class SubcommandsExample(cmd2.Cmd): and the "sport" subcommand has tab completion enabled. """ - def __init__(self): + def __init__(self) -> None: super().__init__() # subcommand functions for the base command - def base_foo(self, args): + def base_foo(self, args) -> None: """foo subcommand of base command""" self.poutput(args.x * args.y) - def base_bar(self, args): + def base_bar(self, args) -> None: """bar subcommand of base command""" self.poutput(f'(({args.z}))') - def base_sport(self, args): + def base_sport(self, args) -> None: """sport subcommand of base command""" self.poutput(f'Sport is {args.sport}') @@ -89,7 +89,7 @@ def base_sport(self, args): parser_sport.set_defaults(func=base_sport) @cmd2.with_argparser(base_parser) - def do_base(self, args): + def do_base(self, args) -> None: """Base command help""" func = getattr(args, 'func', None) if func is not None: @@ -100,7 +100,7 @@ def do_base(self, args): self.do_help('base') @cmd2.with_argparser(base2_parser) - def do_alternate(self, args): + def do_alternate(self, args) -> None: """Alternate command help""" func = getattr(args, 'func', None) if func is not None: diff --git a/examples/table_creation.py b/examples/table_creation.py index 6181e390c..6ebe830d2 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -63,12 +63,12 @@ def __init__(self, name: str, birthday: str, place_of_birth: str) -> None: self.relatives: list[Relative] = [] -def ansi_print(text): +def ansi_print(text) -> None: """Wraps style_aware_write so style can be stripped if needed""" ansi.style_aware_write(sys.stdout, text + '\n\n') -def basic_tables(): +def basic_tables() -> None: """Demonstrates basic examples of the table classes""" # Table data which demonstrates handling of wrapping and text styles @@ -112,7 +112,7 @@ def basic_tables(): ansi_print(table) -def nested_tables(): +def nested_tables() -> None: """ Demonstrates how to nest tables with styles which conflict with the parent table by setting style_data_text to False. It also demonstrates coloring various aspects of tables. diff --git a/examples/unicode_commands.py b/examples/unicode_commands.py index ac2cafd39..6981352a7 100755 --- a/examples/unicode_commands.py +++ b/examples/unicode_commands.py @@ -9,15 +9,15 @@ class UnicodeApp(cmd2.Cmd): """Example cmd2 application with unicode command names.""" - def __init__(self): + def __init__(self) -> None: super().__init__() self.intro = 'Welcome the Unicode example app. Note the full Unicode support: 😇 💩' - def do_𝛑print(self, _): + def do_𝛑print(self, _) -> None: """This command prints 𝛑 to 5 decimal places.""" self.poutput(f"𝛑 = {math.pi:.6}") - def do_你好(self, arg): + def do_你好(self, arg) -> None: """This command says hello in Chinese (Mandarin).""" self.poutput("你好 " + arg) diff --git a/plugins/tasks.py b/plugins/tasks.py index 14345dcbc..c55a744b7 100644 --- a/plugins/tasks.py +++ b/plugins/tasks.py @@ -37,7 +37,7 @@ @invoke.task(pre=[ext_test_tasks.pytest]) @invoke.task() -def pytest(_): +def pytest(_) -> None: """Run tests and code coverage using pytest""" @@ -45,7 +45,7 @@ def pytest(_): @invoke.task(pre=[ext_test_tasks.pytest_clean]) -def pytest_clean(_): +def pytest_clean(_) -> None: """Remove pytest cache and code coverage files and directories""" @@ -53,7 +53,7 @@ def pytest_clean(_): @invoke.task(pre=[ext_test_tasks.mypy]) -def mypy(_): +def mypy(_) -> None: """Run mypy optional static type checker""" @@ -61,7 +61,7 @@ def mypy(_): @invoke.task(pre=[ext_test_tasks.mypy_clean]) -def mypy_clean(_): +def mypy_clean(_) -> None: """Remove mypy cache directory""" # pylint: disable=unused-argument @@ -79,7 +79,7 @@ def mypy_clean(_): @invoke.task(pre=[ext_test_tasks.build_clean]) -def build_clean(_): +def build_clean(_) -> None: """Remove the build directory""" @@ -87,7 +87,7 @@ def build_clean(_): @invoke.task(pre=[ext_test_tasks.dist_clean]) -def dist_clean(_): +def dist_clean(_) -> None: """Remove the dist directory""" @@ -99,7 +99,7 @@ def dist_clean(_): @invoke.task(pre=list(namespace_clean.tasks.values()), default=True) -def clean_all(_): +def clean_all(_) -> None: """Run all clean tasks""" # pylint: disable=unused-argument @@ -108,7 +108,7 @@ def clean_all(_): @invoke.task(pre=[clean_all], post=[ext_test_tasks.sdist]) -def sdist(_): +def sdist(_) -> None: """Create a source distribution""" @@ -116,7 +116,7 @@ def sdist(_): @invoke.task(pre=[clean_all], post=[ext_test_tasks.wheel]) -def wheel(_): +def wheel(_) -> None: """Build a wheel distribution""" @@ -125,7 +125,7 @@ def wheel(_): # ruff linter @invoke.task(pre=[ext_test_tasks.lint]) -def lint(context): +def lint(context) -> None: with context.cd(TASK_ROOT_STR): context.run("ruff check") @@ -135,7 +135,7 @@ def lint(context): # ruff formatter @invoke.task(pre=[ext_test_tasks.format]) -def format(context): # noqa: A001 +def format(context) -> None: # noqa: A001 """Run formatter""" with context.cd(TASK_ROOT_STR): context.run("ruff format --check") diff --git a/plugins/template/cmd2_myplugin/myplugin.py b/plugins/template/cmd2_myplugin/myplugin.py index b5a91ca24..818730bba 100644 --- a/plugins/template/cmd2_myplugin/myplugin.py +++ b/plugins/template/cmd2_myplugin/myplugin.py @@ -17,7 +17,7 @@ def empty_decorator(func: Callable) -> Callable: """An empty decorator for myplugin""" @functools.wraps(func) - def _empty_decorator(self, *args, **kwargs): + def _empty_decorator(self, *args, **kwargs) -> None: self.poutput("in the empty decorator") func(self, *args, **kwargs) @@ -37,7 +37,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # code placed here runs before cmd2 initializes super().__init__(*args, **kwargs) # code placed here runs after cmd2 initializes @@ -46,7 +46,7 @@ def __init__(self, *args, **kwargs): self.register_postloop_hook(self.cmd2_myplugin_postloop_hook) self.register_postparsing_hook(self.cmd2_myplugin_postparsing_hook) - def do_say(self, statement): + def do_say(self, statement) -> None: """Simple say command""" self.poutput(statement) diff --git a/plugins/template/examples/example.py b/plugins/template/examples/example.py index 997334906..47ba3a15b 100644 --- a/plugins/template/examples/example.py +++ b/plugins/template/examples/example.py @@ -8,12 +8,12 @@ class Example(cmd2_myplugin.MyPlugin, cmd2.Cmd): """An class to show how to use a plugin""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # gotta have this or neither the plugin or cmd2 will initialize super().__init__(*args, **kwargs) @cmd2_myplugin.empty_decorator - def do_something(self, arg): + def do_something(self, arg) -> None: self.poutput('this is the something command') diff --git a/plugins/template/noxfile.py b/plugins/template/noxfile.py index d8aa344bf..cac9f9177 100644 --- a/plugins/template/noxfile.py +++ b/plugins/template/noxfile.py @@ -2,6 +2,6 @@ @nox.session(python=['3.9', '3.10', '3.11', '3.12', '3.13']) -def tests(session): +def tests(session) -> None: session.install('invoke', './[test]') session.run('invoke', 'pytest', '--junit', '--no-pty') diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index 4fa76c683..8ae41c8d3 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -12,7 +12,7 @@ # shared function -def rmrf(items, verbose=True): +def rmrf(items, verbose=True) -> None: """Silently remove a list of directories or files""" if isinstance(items, str): items = [items] @@ -41,7 +41,7 @@ def rmrf(items, verbose=True): @invoke.task -def pytest(context, junit=False, pty=True, append_cov=False): +def pytest(context, junit=False, pty=True, append_cov=False) -> None: """Run tests and code coverage using pytest""" ROOT_PATH = TASK_ROOT.parent.parent @@ -59,7 +59,7 @@ def pytest(context, junit=False, pty=True, append_cov=False): @invoke.task -def pytest_clean(context): +def pytest_clean(context) -> None: """Remove pytest cache and code coverage files and directories""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): @@ -71,7 +71,7 @@ def pytest_clean(context): @invoke.task -def pylint(context): +def pylint(context) -> None: """Check code quality using pylint""" context.run('pylint --rcfile=cmd2_myplugin/pylintrc cmd2_myplugin') @@ -80,7 +80,7 @@ def pylint(context): @invoke.task -def pylint_tests(context): +def pylint_tests(context) -> None: """Check code quality of test suite using pylint""" context.run('pylint --rcfile=tests/pylintrc tests') @@ -98,7 +98,7 @@ def pylint_tests(context): @invoke.task -def build_clean(context): +def build_clean(context) -> None: """Remove the build directory""" # pylint: disable=unused-argument rmrf(BUILDDIR) @@ -108,7 +108,7 @@ def build_clean(context): @invoke.task -def dist_clean(context): +def dist_clean(context) -> None: """Remove the dist directory""" # pylint: disable=unused-argument rmrf(DISTDIR) @@ -118,7 +118,7 @@ def dist_clean(context): @invoke.task -def eggs_clean(context): +def eggs_clean(context) -> None: """Remove egg directories""" # pylint: disable=unused-argument dirs = set() @@ -135,7 +135,7 @@ def eggs_clean(context): @invoke.task -def bytecode_clean(context): +def bytecode_clean(context) -> None: """Remove __pycache__ directories and *.pyc files""" # pylint: disable=unused-argument dirs = set() @@ -157,7 +157,7 @@ def bytecode_clean(context): @invoke.task(pre=list(namespace_clean.tasks.values()), default=True) -def clean_all(context): +def clean_all(context) -> None: """Run all clean tasks""" # pylint: disable=unused-argument @@ -166,7 +166,7 @@ def clean_all(context): @invoke.task(pre=[clean_all]) -def sdist(context): +def sdist(context) -> None: """Create a source distribution""" context.run('python -m build --sdist') @@ -175,7 +175,7 @@ def sdist(context): @invoke.task(pre=[clean_all]) -def wheel(context): +def wheel(context) -> None: """Build a wheel distribution""" context.run('python -m build --wheel') diff --git a/plugins/template/tests/test_myplugin.py b/plugins/template/tests/test_myplugin.py index 30c42f9f1..2c42ef0a1 100644 --- a/plugins/template/tests/test_myplugin.py +++ b/plugins/template/tests/test_myplugin.py @@ -16,11 +16,11 @@ class MyApp(cmd2_myplugin.MyPluginMixin, cmd2.Cmd): """Simple subclass of cmd2.Cmd with our SayMixin plugin included.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @cmd2_myplugin.empty_decorator - def do_empty(self, args): + def do_empty(self, args) -> None: self.poutput("running the empty command") @@ -48,7 +48,7 @@ def init_app(): ##### -def test_say(capsys): +def test_say(capsys) -> None: # call our initialization function instead of using a fixture app = init_app() # run our mixed in command @@ -60,7 +60,7 @@ def test_say(capsys): assert not err -def test_decorator(capsys): +def test_decorator(capsys) -> None: # call our initialization function instead of using a fixture app = init_app() # run one command in the app diff --git a/pyproject.toml b/pyproject.toml index f0892e834..5c47da41d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -158,7 +158,7 @@ select = [ # https://docs.astral.sh/ruff/rules "A", # flake8-builtins (variables or arguments shadowing built-ins) # "AIR", # Airflow specific warnings - # "ANN", # flake8-annotations (missing type annotations for arguments or return types) + # "ANN", # flake8-annotations (missing type annotations for arguments or return types) # "ARG", # flake8-unused-arguments (functions or methods with arguments that are never used) "ASYNC", # flake8-async (async await bugs) # "B", # flake8-bugbear (various likely bugs and design issues) diff --git a/tasks.py b/tasks.py index 683f67334..e0a2b207b 100644 --- a/tasks.py +++ b/tasks.py @@ -23,7 +23,7 @@ # shared function -def rmrf(items, verbose=True): +def rmrf(items, verbose=True) -> None: """Silently remove a list of directories or files""" if isinstance(items, str): items = [items] @@ -52,7 +52,7 @@ def rmrf(items, verbose=True): @invoke.task() -def pytest(context, junit=False, pty=True, base=False, isolated=False): +def pytest(context, junit=False, pty=True, base=False, isolated=False) -> None: """Run tests and code coverage using pytest""" with context.cd(TASK_ROOT_STR): command_str = 'pytest ' @@ -80,7 +80,7 @@ def pytest(context, junit=False, pty=True, base=False, isolated=False): @invoke.task(post=[plugin_tasks.pytest_clean]) -def pytest_clean(context): +def pytest_clean(context) -> None: """Remove pytest cache and code coverage files and directories""" # pylint: disable=unused-argument with context.cd(str(TASK_ROOT / 'tests')): @@ -93,7 +93,7 @@ def pytest_clean(context): @invoke.task() -def mypy(context): +def mypy(context) -> None: """Run mypy optional static type checker""" with context.cd(TASK_ROOT_STR): context.run("mypy .") @@ -103,7 +103,7 @@ def mypy(context): @invoke.task() -def mypy_clean(context): +def mypy_clean(context) -> None: """Remove mypy cache directory""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): @@ -124,7 +124,7 @@ def mypy_clean(context): @invoke.task() -def docs(context, builder='html'): +def docs(context, builder='html') -> None: """Build documentation using MkDocs""" with context.cd(TASK_ROOT_STR): context.run('mkdocs build', pty=True) @@ -134,7 +134,7 @@ def docs(context, builder='html'): @invoke.task -def docs_clean(context): +def docs_clean(context) -> None: """Remove rendered documentation""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): @@ -145,7 +145,7 @@ def docs_clean(context): @invoke.task -def livehtml(context): +def livehtml(context) -> None: """Launch webserver on http://localhost:8000 with rendered documentation""" with context.cd(TASK_ROOT_STR): context.run('mkdocs serve', pty=True) @@ -164,7 +164,7 @@ def livehtml(context): @invoke.task(post=[plugin_tasks.build_clean]) -def build_clean(context): +def build_clean(context) -> None: """Remove the build directory""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): @@ -175,7 +175,7 @@ def build_clean(context): @invoke.task(post=[plugin_tasks.dist_clean]) -def dist_clean(context): +def dist_clean(context) -> None: """Remove the dist directory""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): @@ -186,7 +186,7 @@ def dist_clean(context): @invoke.task() -def eggs_clean(context): +def eggs_clean(context) -> None: """Remove egg directories""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): @@ -204,7 +204,7 @@ def eggs_clean(context): @invoke.task() -def pycache_clean(context): +def pycache_clean(context) -> None: """Remove __pycache__ directories""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): @@ -221,7 +221,7 @@ def pycache_clean(context): # ruff fast linter @invoke.task() -def lint(context): +def lint(context) -> None: """Run ruff fast linter""" with context.cd(TASK_ROOT_STR): context.run("ruff check") @@ -232,7 +232,7 @@ def lint(context): # ruff fast formatter @invoke.task() -def format(context): # noqa: A001 +def format(context) -> None: # noqa: A001 """Run ruff format --check""" with context.cd(TASK_ROOT_STR): context.run("ruff format --check") @@ -242,7 +242,7 @@ def format(context): # noqa: A001 @invoke.task() -def ruff_clean(context): +def ruff_clean(context) -> None: """Remove .ruff_cache directory""" with context.cd(TASK_ROOT_STR): context.run("ruff clean") @@ -257,7 +257,7 @@ def ruff_clean(context): @invoke.task(pre=clean_tasks, default=True) -def clean_all(_): +def clean_all(_) -> None: """Run all clean tasks""" # pylint: disable=unused-argument @@ -266,7 +266,7 @@ def clean_all(_): @invoke.task -def tag(context, name, message=''): +def tag(context, name, message='') -> None: """Add a Git tag and push it to origin""" # If a tag was provided on the command-line, then add a Git tag and push it to origin if name: @@ -278,7 +278,7 @@ def tag(context, name, message=''): @invoke.task() -def validatetag(context): +def validatetag(context) -> None: """Check to make sure that a tag exists for the current HEAD and it looks like a valid version number""" # Validate that a Git tag exists for the current commit HEAD result = context.run("git describe --exact-match --tags $(git log -n1 --pretty='%h')") @@ -298,7 +298,7 @@ def validatetag(context): @invoke.task(pre=[clean_all], post=[plugin_tasks.sdist]) -def sdist(context): +def sdist(context) -> None: """Create a source distribution""" with context.cd(TASK_ROOT_STR): context.run('python -m build --sdist') @@ -308,7 +308,7 @@ def sdist(context): @invoke.task(pre=[clean_all], post=[plugin_tasks.wheel]) -def wheel(context): +def wheel(context) -> None: """Build a wheel distribution""" with context.cd(TASK_ROOT_STR): context.run('python -m build --wheel') @@ -318,7 +318,7 @@ def wheel(context): @invoke.task(pre=[validatetag, sdist, wheel]) -def pypi(context): +def pypi(context) -> None: """Build and upload a distribution to pypi""" with context.cd(TASK_ROOT_STR): context.run('twine upload dist/*') @@ -328,7 +328,7 @@ def pypi(context): @invoke.task(pre=[validatetag, sdist, wheel]) -def pypi_test(context): +def pypi_test(context) -> None: """Build and upload a distribution to https://test.pypi.org""" with context.cd(TASK_ROOT_STR): context.run('twine upload --repository testpypi dist/*') diff --git a/tests/test_ansi.py b/tests/test_ansi.py index e5bf5fe8b..73fb51933 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -11,14 +11,14 @@ HELLO_WORLD = 'Hello, world!' -def test_strip_style(): +def test_strip_style() -> None: base_str = HELLO_WORLD ansi_str = ansi.style(base_str, fg=ansi.Fg.GREEN) assert base_str != ansi_str assert base_str == ansi.strip_style(ansi_str) -def test_style_aware_wcswidth(): +def test_style_aware_wcswidth() -> None: base_str = HELLO_WORLD ansi_str = ansi.style(base_str, fg=ansi.Fg.GREEN) assert ansi.style_aware_wcswidth(HELLO_WORLD) == ansi.style_aware_wcswidth(ansi_str) @@ -27,7 +27,7 @@ def test_style_aware_wcswidth(): assert ansi.style_aware_wcswidth('i have a newline\n') == -1 -def test_widest_line(): +def test_widest_line() -> None: text = ansi.style('i have\n3 lines\nThis is the longest one', fg=ansi.Fg.GREEN) assert ansi.widest_line(text) == ansi.style_aware_wcswidth("This is the longest one") @@ -37,27 +37,27 @@ def test_widest_line(): assert ansi.widest_line('i have a tab\t') == -1 -def test_style_none(): +def test_style_none() -> None: base_str = HELLO_WORLD ansi_str = base_str assert ansi.style(base_str) == ansi_str @pytest.mark.parametrize('fg_color', [ansi.Fg.BLUE, ansi.EightBitFg.AQUAMARINE_1A, ansi.RgbFg(0, 2, 4)]) -def test_style_fg(fg_color): +def test_style_fg(fg_color) -> None: base_str = HELLO_WORLD ansi_str = fg_color + base_str + ansi.Fg.RESET assert ansi.style(base_str, fg=fg_color) == ansi_str @pytest.mark.parametrize('bg_color', [ansi.Bg.BLUE, ansi.EightBitBg.AQUAMARINE_1A, ansi.RgbBg(0, 2, 4)]) -def test_style_bg(bg_color): +def test_style_bg(bg_color) -> None: base_str = HELLO_WORLD ansi_str = bg_color + base_str + ansi.Bg.RESET assert ansi.style(base_str, bg=bg_color) == ansi_str -def test_style_invalid_types(): +def test_style_invalid_types() -> None: # Use a BgColor with fg with pytest.raises(TypeError): ansi.style('test', fg=ansi.Bg.BLUE) @@ -67,43 +67,43 @@ def test_style_invalid_types(): ansi.style('test', bg=ansi.Fg.BLUE) -def test_style_bold(): +def test_style_bold() -> None: base_str = HELLO_WORLD ansi_str = ansi.TextStyle.INTENSITY_BOLD + base_str + ansi.TextStyle.INTENSITY_NORMAL assert ansi.style(base_str, bold=True) == ansi_str -def test_style_dim(): +def test_style_dim() -> None: base_str = HELLO_WORLD ansi_str = ansi.TextStyle.INTENSITY_DIM + base_str + ansi.TextStyle.INTENSITY_NORMAL assert ansi.style(base_str, dim=True) == ansi_str -def test_style_italic(): +def test_style_italic() -> None: base_str = HELLO_WORLD ansi_str = ansi.TextStyle.ITALIC_ENABLE + base_str + ansi.TextStyle.ITALIC_DISABLE assert ansi.style(base_str, italic=True) == ansi_str -def test_style_overline(): +def test_style_overline() -> None: base_str = HELLO_WORLD ansi_str = ansi.TextStyle.OVERLINE_ENABLE + base_str + ansi.TextStyle.OVERLINE_DISABLE assert ansi.style(base_str, overline=True) == ansi_str -def test_style_strikethrough(): +def test_style_strikethrough() -> None: base_str = HELLO_WORLD ansi_str = ansi.TextStyle.STRIKETHROUGH_ENABLE + base_str + ansi.TextStyle.STRIKETHROUGH_DISABLE assert ansi.style(base_str, strikethrough=True) == ansi_str -def test_style_underline(): +def test_style_underline() -> None: base_str = HELLO_WORLD ansi_str = ansi.TextStyle.UNDERLINE_ENABLE + base_str + ansi.TextStyle.UNDERLINE_DISABLE assert ansi.style(base_str, underline=True) == ansi_str -def test_style_multi(): +def test_style_multi() -> None: base_str = HELLO_WORLD fg_color = ansi.Fg.LIGHT_BLUE bg_color = ansi.Bg.LIGHT_GRAY @@ -142,7 +142,7 @@ def test_style_multi(): ) -def test_set_title(): +def test_set_title() -> None: title = HELLO_WORLD assert ansi.set_title(title) == ansi.OSC + '2;' + title + ansi.BEL @@ -169,12 +169,12 @@ def test_set_title(): ), ], ) -def test_async_alert_str(cols, prompt, line, cursor, msg, expected): +def test_async_alert_str(cols, prompt, line, cursor, msg, expected) -> None: alert_str = ansi.async_alert_str(terminal_columns=cols, prompt=prompt, line=line, cursor_offset=cursor, alert_msg=msg) assert alert_str == expected -def test_clear_screen(): +def test_clear_screen() -> None: clear_type = 2 assert ansi.clear_screen(clear_type) == f"{ansi.CSI}{clear_type}J" @@ -188,7 +188,7 @@ def test_clear_screen(): ansi.clear_screen(clear_type) -def test_clear_line(): +def test_clear_line() -> None: clear_type = 2 assert ansi.clear_line(clear_type) == f"{ansi.CSI}{clear_type}K" @@ -202,7 +202,7 @@ def test_clear_line(): ansi.clear_line(clear_type) -def test_cursor(): +def test_cursor() -> None: count = 1 assert ansi.Cursor.UP(count) == f"{ansi.CSI}{count}A" assert ansi.Cursor.DOWN(count) == f"{ansi.CSI}{count}B" @@ -226,7 +226,7 @@ def test_cursor(): ansi.TextStyle.OVERLINE_ENABLE, ], ) -def test_sequence_str_building(ansi_sequence): +def test_sequence_str_building(ansi_sequence) -> None: """This tests __add__(), __radd__(), and __str__() methods for AnsiSequences""" assert ansi_sequence + ansi_sequence == str(ansi_sequence) + str(ansi_sequence) @@ -244,7 +244,7 @@ def test_sequence_str_building(ansi_sequence): (255, 255, 256, False), ], ) -def test_rgb_bounds(r, g, b, valid): +def test_rgb_bounds(r, g, b, valid) -> None: if valid: ansi.RgbFg(r, g, b) ansi.RgbBg(r, g, b) @@ -256,7 +256,7 @@ def test_rgb_bounds(r, g, b, valid): ansi.RgbBg(r, g, b) -def test_std_color_re(): +def test_std_color_re() -> None: """Test regular expressions for matching standard foreground and background colors""" for color in ansi.Fg: assert ansi.STD_FG_RE.match(str(color)) @@ -270,7 +270,7 @@ def test_std_color_re(): assert not ansi.STD_BG_RE.match(f'{ansi.CSI}48m') -def test_eight_bit_color_re(): +def test_eight_bit_color_re() -> None: """Test regular expressions for matching eight-bit foreground and background colors""" for color in ansi.EightBitFg: assert ansi.EIGHT_BIT_FG_RE.match(str(color)) @@ -284,7 +284,7 @@ def test_eight_bit_color_re(): assert not ansi.EIGHT_BIT_BG_RE.match(f'{ansi.CSI}48;5;256m') -def test_rgb_color_re(): +def test_rgb_color_re() -> None: """Test regular expressions for matching RGB foreground and background colors""" for i in range(256): fg_color = ansi.RgbFg(i, i, i) diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 150f61694..47f836f56 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -15,7 +15,7 @@ class ArgparseApp(cmd2.Cmd): - def __init__(self): + def __init__(self) -> None: self.maxrepeats = 3 cmd2.Cmd.__init__(self) @@ -34,7 +34,7 @@ def _say_parser_builder() -> cmd2.Cmd2ArgumentParser: return say_parser @cmd2.with_argparser(_say_parser_builder) - def do_say(self, args, *, keyword_arg: Optional[str] = None): + def do_say(self, args, *, keyword_arg: Optional[str] = None) -> None: """ Repeat what you tell me to. @@ -64,16 +64,16 @@ def do_say(self, args, *, keyword_arg: Optional[str] = None): tag_parser.add_argument('content', nargs='+', help='content to surround with tag') @cmd2.with_argparser(tag_parser, preserve_quotes=True) - def do_tag(self, args): + def do_tag(self, args) -> None: self.stdout.write('<{0}>{1}'.format(args.tag, ' '.join(args.content))) self.stdout.write('\n') @cmd2.with_argparser(cmd2.Cmd2ArgumentParser(), ns_provider=namespace_provider) - def do_test_argparse_ns(self, args): + def do_test_argparse_ns(self, args) -> None: self.stdout.write(f'{args.custom_stuff}') @cmd2.with_argument_list - def do_arglist(self, arglist, *, keyword_arg: Optional[str] = None): + def do_arglist(self, arglist, *, keyword_arg: Optional[str] = None) -> None: if isinstance(arglist, list): self.stdout.write('True') else: @@ -83,7 +83,7 @@ def do_arglist(self, arglist, *, keyword_arg: Optional[str] = None): print(keyword_arg) @cmd2.with_argument_list(preserve_quotes=True) - def do_preservelist(self, arglist): + def do_preservelist(self, arglist) -> None: self.stdout.write(f'{arglist}') @classmethod @@ -95,7 +95,7 @@ def _speak_parser_builder(cls) -> cmd2.Cmd2ArgumentParser: return known_parser @cmd2.with_argparser(_speak_parser_builder, with_unknown_args=True) - def do_speak(self, args, extra, *, keyword_arg: Optional[str] = None): + def do_speak(self, args, extra, *, keyword_arg: Optional[str] = None) -> None: """Repeat what you tell me to.""" words = [] for word in extra: @@ -115,11 +115,11 @@ def do_speak(self, args, extra, *, keyword_arg: Optional[str] = None): print(keyword_arg) @cmd2.with_argparser(cmd2.Cmd2ArgumentParser(), preserve_quotes=True, with_unknown_args=True) - def do_test_argparse_with_list_quotes(self, args, extra): + def do_test_argparse_with_list_quotes(self, args, extra) -> None: self.stdout.write('{}'.format(' '.join(extra))) @cmd2.with_argparser(cmd2.Cmd2ArgumentParser(), ns_provider=namespace_provider, with_unknown_args=True) - def do_test_argparse_with_list_ns(self, args, extra): + def do_test_argparse_with_list_ns(self, args, extra) -> None: self.stdout.write(f'{args.custom_stuff}') @@ -129,88 +129,88 @@ def argparse_app(): return app -def test_invalid_syntax(argparse_app): +def test_invalid_syntax(argparse_app) -> None: out, err = run_cmd(argparse_app, 'speak "') assert err[0] == "Invalid syntax: No closing quotation" -def test_argparse_basic_command(argparse_app): +def test_argparse_basic_command(argparse_app) -> None: out, err = run_cmd(argparse_app, 'say hello') assert out == ['hello'] -def test_argparse_remove_quotes(argparse_app): +def test_argparse_remove_quotes(argparse_app) -> None: out, err = run_cmd(argparse_app, 'say "hello there"') assert out == ['hello there'] -def test_argparse_with_no_args(argparse_app): +def test_argparse_with_no_args(argparse_app) -> None: """Make sure we receive TypeError when calling argparse-based function with no args""" with pytest.raises(TypeError) as excinfo: argparse_app.do_say() assert 'Expected arguments' in str(excinfo.value) -def test_argparser_kwargs(argparse_app, capsys): +def test_argparser_kwargs(argparse_app, capsys) -> None: """Test with_argparser wrapper passes through kwargs to command function""" argparse_app.do_say('word', keyword_arg="foo") out, err = capsys.readouterr() assert out == "foo\n" -def test_argparse_preserve_quotes(argparse_app): +def test_argparse_preserve_quotes(argparse_app) -> None: out, err = run_cmd(argparse_app, 'tag mytag "hello"') assert out[0] == '"hello"' -def test_argparse_custom_namespace(argparse_app): +def test_argparse_custom_namespace(argparse_app) -> None: out, err = run_cmd(argparse_app, 'test_argparse_ns') assert out[0] == 'custom' -def test_argparse_with_list(argparse_app): +def test_argparse_with_list(argparse_app) -> None: out, err = run_cmd(argparse_app, 'speak -s hello world!') assert out == ['HELLO WORLD!'] -def test_argparse_with_list_remove_quotes(argparse_app): +def test_argparse_with_list_remove_quotes(argparse_app) -> None: out, err = run_cmd(argparse_app, 'speak -s hello "world!"') assert out == ['HELLO WORLD!'] -def test_argparse_with_list_preserve_quotes(argparse_app): +def test_argparse_with_list_preserve_quotes(argparse_app) -> None: out, err = run_cmd(argparse_app, 'test_argparse_with_list_quotes "hello" person') assert out[0] == '"hello" person' -def test_argparse_with_list_custom_namespace(argparse_app): +def test_argparse_with_list_custom_namespace(argparse_app) -> None: out, err = run_cmd(argparse_app, 'test_argparse_with_list_ns') assert out[0] == 'custom' -def test_argparse_with_list_and_empty_doc(argparse_app): +def test_argparse_with_list_and_empty_doc(argparse_app) -> None: out, err = run_cmd(argparse_app, 'speak -s hello world!') assert out == ['HELLO WORLD!'] -def test_argparser_correct_args_with_quotes_and_midline_options(argparse_app): +def test_argparser_correct_args_with_quotes_and_midline_options(argparse_app) -> None: out, err = run_cmd(argparse_app, "speak 'This is a' -s test of the emergency broadcast system!") assert out == ['THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM!'] -def test_argparser_and_unknown_args_kwargs(argparse_app, capsys): +def test_argparser_and_unknown_args_kwargs(argparse_app, capsys) -> None: """Test with_argparser wrapper passing through kwargs to command function""" argparse_app.do_speak('', keyword_arg="foo") out, err = capsys.readouterr() assert out == "foo\n" -def test_argparse_quoted_arguments_multiple(argparse_app): +def test_argparse_quoted_arguments_multiple(argparse_app) -> None: out, err = run_cmd(argparse_app, 'say "hello there" "rick & morty"') assert out == ['hello there rick & morty'] -def test_argparse_help_docstring(argparse_app): +def test_argparse_help_docstring(argparse_app) -> None: out, err = run_cmd(argparse_app, 'help say') assert out[0].startswith('Usage: say') assert out[1] == '' @@ -220,32 +220,32 @@ def test_argparse_help_docstring(argparse_app): assert not line.startswith(':') -def test_argparse_help_description(argparse_app): +def test_argparse_help_description(argparse_app) -> None: out, err = run_cmd(argparse_app, 'help tag') assert out[0].startswith('Usage: tag') assert out[1] == '' assert out[2] == 'create a html tag' -def test_argparse_prog(argparse_app): +def test_argparse_prog(argparse_app) -> None: out, err = run_cmd(argparse_app, 'help tag') progname = out[0].split(' ')[1] assert progname == 'tag' -def test_arglist(argparse_app): +def test_arglist(argparse_app) -> None: out, err = run_cmd(argparse_app, 'arglist "we should" get these') assert out[0] == 'True' -def test_arglist_kwargs(argparse_app, capsys): +def test_arglist_kwargs(argparse_app, capsys) -> None: """Test with_argument_list wrapper passes through kwargs to command function""" argparse_app.do_arglist('arg', keyword_arg="foo") out, err = capsys.readouterr() assert out == "foo\n" -def test_preservelist(argparse_app): +def test_preservelist(argparse_app) -> None: out, err = run_cmd(argparse_app, 'preservelist foo "bar baz"') assert out[0] == "['foo', '\"bar baz\"']" @@ -260,15 +260,15 @@ class SubcommandApp(cmd2.Cmd): """Example cmd2 application where we a base command which has a couple subcommands.""" # subcommand functions for the base command - def base_foo(self, args): + def base_foo(self, args) -> None: """foo subcommand of base command""" self.poutput(args.x * args.y) - def base_bar(self, args): + def base_bar(self, args) -> None: """bar subcommand of base command""" self.poutput(f'(({args.z}))') - def base_helpless(self, args): + def base_helpless(self, args) -> None: """helpless subcommand of base command""" self.poutput(f'(({args.z}))') @@ -296,7 +296,7 @@ def base_helpless(self, args): parser_helpless.set_defaults(func=base_bar) @cmd2.with_argparser(base_parser) - def do_base(self, args): + def do_base(self, args) -> None: """Base command help""" # Call whatever subcommand function was selected func = getattr(args, 'func') @@ -304,14 +304,14 @@ def do_base(self, args): # Add subcommands using as_subcommand_to decorator @cmd2.with_argparser(_build_has_subcmd_parser) - def do_test_subcmd_decorator(self, args: argparse.Namespace): + def do_test_subcmd_decorator(self, args: argparse.Namespace) -> None: handler = args.cmd2_handler.get() handler(args) subcmd_parser = cmd2.Cmd2ArgumentParser(description="A subcommand") @cmd2.as_subcommand_to('test_subcmd_decorator', 'subcmd', subcmd_parser, help=subcmd_parser.description.lower()) - def subcmd_func(self, args: argparse.Namespace): + def subcmd_func(self, args: argparse.Namespace) -> None: # Make sure printing the Namespace works. The way we originally added cmd2_handler to it resulted in a RecursionError. self.poutput(args) @@ -320,7 +320,7 @@ def subcmd_func(self, args: argparse.Namespace): @cmd2.as_subcommand_to( 'test_subcmd_decorator', 'helpless_subcmd', helpless_subcmd_parser, help=helpless_subcmd_parser.description.lower() ) - def helpless_subcmd_func(self, args: argparse.Namespace): + def helpless_subcmd_func(self, args: argparse.Namespace) -> None: # Make sure vars(Namespace) works. The way we originally added cmd2_handler to it resulted in a RecursionError. self.poutput(vars(args)) @@ -331,30 +331,30 @@ def subcommand_app(): return app -def test_subcommand_foo(subcommand_app): +def test_subcommand_foo(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'base foo -x2 5.0') assert out == ['10.0'] -def test_subcommand_bar(subcommand_app): +def test_subcommand_bar(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'base bar baz') assert out == ['((baz))'] -def test_subcommand_invalid(subcommand_app): +def test_subcommand_invalid(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'base baz') assert err[0].startswith('Usage: base') assert err[1].startswith("Error: argument SUBCOMMAND: invalid choice: 'baz'") -def test_subcommand_base_help(subcommand_app): +def test_subcommand_base_help(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'help base') assert out[0].startswith('Usage: base') assert out[1] == '' assert out[2] == 'Base command help' -def test_subcommand_help(subcommand_app): +def test_subcommand_help(subcommand_app) -> None: # foo has no aliases out, err = run_cmd(subcommand_app, 'help base foo') assert out[0].startswith('Usage: base foo') @@ -394,12 +394,12 @@ def test_subcommand_help(subcommand_app): assert out[2] == 'positional arguments:' -def test_subcommand_invalid_help(subcommand_app): +def test_subcommand_invalid_help(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'help base baz') assert out[0].startswith('Usage: base') -def test_add_another_subcommand(subcommand_app): +def test_add_another_subcommand(subcommand_app) -> None: """ This tests makes sure _set_parser_prog() sets _prog_prefix on every _SubParsersAction so that all future calls to add_parser() write the correct prog value to the parser being added. @@ -413,7 +413,7 @@ def test_add_another_subcommand(subcommand_app): assert new_parser.prog == "base new_sub" -def test_subcmd_decorator(subcommand_app): +def test_subcmd_decorator(subcommand_app) -> None: # Test subcommand that has help option out, err = run_cmd(subcommand_app, 'test_subcmd_decorator subcmd') assert out[0].startswith('Namespace(') @@ -438,7 +438,7 @@ def test_subcmd_decorator(subcommand_app): assert err[1] == 'Error: unrecognized arguments: -h' -def test_unittest_mock(): +def test_unittest_mock() -> None: from unittest import ( mock, ) @@ -461,7 +461,7 @@ def test_unittest_mock(): ArgparseApp() -def test_pytest_mock_invalid(mocker): +def test_pytest_mock_invalid(mocker) -> None: from cmd2 import ( CommandSetRegistrationError, ) @@ -479,6 +479,6 @@ def test_pytest_mock_invalid(mocker): {'autospec': True}, ], ) -def test_pytest_mock_valid(mocker, spec_param): +def test_pytest_mock_valid(mocker, spec_param) -> None: mocker.patch.object(ArgparseApp, 'namespace_provider', **spec_param) ArgparseApp() diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index db891acb3..b7666193f 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -44,7 +44,7 @@ def standalone_completer(cli: cmd2.Cmd, text: str, line: str, begidx: int, endid class ArgparseCompleterTester(cmd2.Cmd): """Cmd2 app that exercises ArgparseCompleter class""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) ############################################################################################################ @@ -342,13 +342,13 @@ def ac_app(): @pytest.mark.parametrize('command', ['music', 'music create', 'music create rock', 'music create jazz']) -def test_help(ac_app, command): +def test_help(ac_app, command) -> None: out1, err1 = run_cmd(ac_app, f'{command} -h') out2, err2 = run_cmd(ac_app, f'help {command}') assert out1 == out2 -def test_bad_subcommand_help(ac_app): +def test_bad_subcommand_help(ac_app) -> None: # These should give the same output because the second one isn't using a # real subcommand, so help will be called on the music command instead. out1, err1 = run_cmd(ac_app, 'help music') @@ -369,7 +369,7 @@ def test_bad_subcommand_help(ac_app): ('music fake', '', []), ], ) -def test_complete_help(ac_app, command, text, completions): +def test_complete_help(ac_app, command, text, completions) -> None: line = f'help {command} {text}' endidx = len(line) begidx = endidx - len(text) @@ -387,7 +387,7 @@ def test_complete_help(ac_app, command, text, completions): ('subcommand', 'text', 'completions'), [('create', '', ['jazz', 'rock']), ('create', 'ja', ['jazz ']), ('create', 'foo', []), ('creab', 'ja', [])], ) -def test_subcommand_completions(ac_app, subcommand, text, completions): +def test_subcommand_completions(ac_app, subcommand, text, completions) -> None: line = f'music {subcommand} {text}' endidx = len(line) begidx = endidx - len(text) @@ -532,7 +532,7 @@ def test_subcommand_completions(ac_app, subcommand, text, completions): ('pos_and_flag choice -f -h ', '', [], []), ], ) -def test_autcomp_flag_completion(ac_app, command_and_args, text, completion_matches, display_matches): +def test_autcomp_flag_completion(ac_app, command_and_args, text, completion_matches, display_matches) -> None: line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -561,7 +561,7 @@ def test_autcomp_flag_completion(ac_app, command_and_args, text, completion_matc ('--num_completion_items', '', ArgparseCompleterTester.num_completion_items), ], ) -def test_autocomp_flag_choices_completion(ac_app, flag, text, completions): +def test_autocomp_flag_choices_completion(ac_app, flag, text, completions) -> None: line = f'choices {flag} {text}' endidx = len(line) begidx = endidx - len(text) @@ -594,7 +594,7 @@ def test_autocomp_flag_choices_completion(ac_app, flag, text, completions): (4, '', []), ], ) -def test_autocomp_positional_choices_completion(ac_app, pos, text, completions): +def test_autocomp_positional_choices_completion(ac_app, pos, text, completions) -> None: # Generate line were preceding positionals are already filled line = 'choices {} {}'.format('foo ' * (pos - 1), text) endidx = len(line) @@ -616,7 +616,7 @@ def test_autocomp_positional_choices_completion(ac_app, pos, text, completions): assert ac_app.completion_matches == completions -def test_flag_sorting(ac_app): +def test_flag_sorting(ac_app) -> None: # This test exercises the case where a positional arg has non-negative integers for its choices. # ArgparseCompleter will sort these numerically before converting them to strings. As a result, # cmd2.matches_sorted gets set to True. If no completion matches are returned and the entered @@ -642,7 +642,7 @@ def test_flag_sorting(ac_app): ('flag', 'text', 'completions'), [('-c', '', ArgparseCompleterTester.completions_for_flag), ('--completer', 'f', ['flag', 'fairly'])], ) -def test_autocomp_flag_completers(ac_app, flag, text, completions): +def test_autocomp_flag_completers(ac_app, flag, text, completions) -> None: line = f'completer {flag} {text}' endidx = len(line) begidx = endidx - len(text) @@ -665,7 +665,7 @@ def test_autocomp_flag_completers(ac_app, flag, text, completions): (2, 'm', ['missed', 'me']), ], ) -def test_autocomp_positional_completers(ac_app, pos, text, completions): +def test_autocomp_positional_completers(ac_app, pos, text, completions) -> None: # Generate line were preceding positionals are already filled line = 'completer {} {}'.format('foo ' * (pos - 1), text) endidx = len(line) @@ -680,7 +680,7 @@ def test_autocomp_positional_completers(ac_app, pos, text, completions): assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key) -def test_autocomp_blank_token(ac_app): +def test_autocomp_blank_token(ac_app) -> None: """Force a blank token to make sure ArgparseCompleter consumes them like argparse does""" from cmd2.argparse_completer import ( ArgparseCompleter, @@ -711,7 +711,7 @@ def test_autocomp_blank_token(ac_app): assert sorted(completions) == sorted(ArgparseCompleterTester.completions_for_pos_2) -def test_completion_items(ac_app): +def test_completion_items(ac_app) -> None: # First test CompletionItems created from strings text = '' line = f'choices --completion_items {text}' @@ -769,7 +769,7 @@ def test_completion_items(ac_app): (100, False), ], ) -def test_max_completion_items(ac_app, num_aliases, show_description): +def test_max_completion_items(ac_app, num_aliases, show_description) -> None: # Create aliases for i in range(num_aliases): run_cmd(ac_app, f'alias create fake_alias{i} help') @@ -849,7 +849,7 @@ def test_max_completion_items(ac_app, num_aliases, show_description): ('the positional remainder --set_value', ['choices ']), ], ) -def test_autcomp_nargs(ac_app, args, completions): +def test_autcomp_nargs(ac_app, args, completions) -> None: text = '' line = f'nargs {args} {text}' endidx = len(line) @@ -897,7 +897,7 @@ def test_autcomp_nargs(ac_app, args, completions): ('nargs --range', '--', True), ], ) -def test_unfinished_flag_error(ac_app, command_and_args, text, is_error, capsys): +def test_unfinished_flag_error(ac_app, command_and_args, text, is_error, capsys) -> None: line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -908,7 +908,7 @@ def test_unfinished_flag_error(ac_app, command_and_args, text, is_error, capsys) assert is_error == all(x in out for x in ["Error: argument", "expected"]) -def test_completion_items_arg_header(ac_app): +def test_completion_items_arg_header(ac_app) -> None: # Test when metavar is None text = '' line = f'choices --desc_header {text}' @@ -957,7 +957,7 @@ def test_completion_items_arg_header(ac_app): assert ac_app.TUPLE_METAVAR[1].upper() in normalize(ac_app.formatted_completions)[0] -def test_completion_items_descriptive_header(ac_app): +def test_completion_items_descriptive_header(ac_app) -> None: from cmd2.argparse_completer import ( DEFAULT_DESCRIPTIVE_HEADER, ) @@ -1007,7 +1007,7 @@ def test_completion_items_descriptive_header(ac_app): ('nargs the choices remainder', '-', True), ], ) -def test_autocomp_hint(ac_app, command_and_args, text, has_hint, capsys): +def test_autocomp_hint(ac_app, command_and_args, text, has_hint, capsys) -> None: line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -1020,7 +1020,7 @@ def test_autocomp_hint(ac_app, command_and_args, text, has_hint, capsys): assert not out -def test_autocomp_hint_no_help_text(ac_app, capsys): +def test_autocomp_hint_no_help_text(ac_app, capsys) -> None: text = '' line = f'hint foo {text}' endidx = len(line) @@ -1049,7 +1049,7 @@ def test_autocomp_hint_no_help_text(ac_app, capsys): ('', 'completer'), ], ) -def test_completion_error(ac_app, capsys, args, text): +def test_completion_error(ac_app, capsys, args, text) -> None: line = f'raise_completion_error {args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -1072,7 +1072,7 @@ def test_completion_error(ac_app, capsys, args, text): ('arg_tokens completer subcmd --parent_arg override fake', ['override', 'subcmd']), ], ) -def test_arg_tokens(ac_app, command_and_args, completions): +def test_arg_tokens(ac_app, command_and_args, completions) -> None: text = '' line = f'{command_and_args} {text}' endidx = len(line) @@ -1110,7 +1110,7 @@ def test_arg_tokens(ac_app, command_and_args, completions): ('mutex --flag flag_val --flag', '', 'the flag arg', None), ], ) -def test_complete_mutex_group(ac_app, command_and_args, text, output_contains, first_match, capsys): +def test_complete_mutex_group(ac_app, command_and_args, text, output_contains, first_match, capsys) -> None: line = f'{command_and_args} {text}' endidx = len(line) begidx = endidx - len(text) @@ -1121,7 +1121,7 @@ def test_complete_mutex_group(ac_app, command_and_args, text, output_contains, f assert output_contains in out -def test_single_prefix_char(): +def test_single_prefix_char() -> None: from cmd2.argparse_completer import ( _single_prefix_char, ) @@ -1140,7 +1140,7 @@ def test_single_prefix_char(): assert _single_prefix_char('+', parser) -def test_looks_like_flag(): +def test_looks_like_flag() -> None: from cmd2.argparse_completer import ( _looks_like_flag, ) @@ -1160,7 +1160,7 @@ def test_looks_like_flag(): assert _looks_like_flag('--flag', parser) -def test_complete_command_no_tokens(ac_app): +def test_complete_command_no_tokens(ac_app) -> None: from cmd2.argparse_completer import ( ArgparseCompleter, ) @@ -1172,7 +1172,7 @@ def test_complete_command_no_tokens(ac_app): assert not completions -def test_complete_command_help_no_tokens(ac_app): +def test_complete_command_help_no_tokens(ac_app) -> None: from cmd2.argparse_completer import ( ArgparseCompleter, ) @@ -1187,7 +1187,7 @@ def test_complete_command_help_no_tokens(ac_app): @pytest.mark.parametrize( ('flag', 'completions'), [('--provider', standalone_choices), ('--completer', standalone_completions)] ) -def test_complete_standalone(ac_app, flag, completions): +def test_complete_standalone(ac_app, flag, completions) -> None: text = '' line = f'standalone {flag} {text}' endidx = len(line) @@ -1219,7 +1219,7 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matche # App used to test custom ArgparseCompleter types and custom argparse attributes class CustomCompleterApp(cmd2.Cmd): - def __init__(self): + def __init__(self) -> None: super().__init__() self.is_ready = True @@ -1275,7 +1275,7 @@ def custom_completer_app(): return app -def test_default_custom_completer_type(custom_completer_app: CustomCompleterApp): +def test_default_custom_completer_type(custom_completer_app: CustomCompleterApp) -> None: """Test altering the app-wide default ArgparseCompleter type""" try: argparse_completer.set_default_ap_completer_type(CustomCompleter) @@ -1300,7 +1300,7 @@ def test_default_custom_completer_type(custom_completer_app: CustomCompleterApp) argparse_completer.set_default_ap_completer_type(argparse_completer.ArgparseCompleter) -def test_custom_completer_type(custom_completer_app: CustomCompleterApp): +def test_custom_completer_type(custom_completer_app: CustomCompleterApp) -> None: """Test parser with a specific custom ArgparseCompleter type""" text = '--m' line = f'custom_completer {text}' @@ -1318,7 +1318,7 @@ def test_custom_completer_type(custom_completer_app: CustomCompleterApp): assert not custom_completer_app.completion_matches -def test_decorated_subcmd_custom_completer(custom_completer_app: CustomCompleterApp): +def test_decorated_subcmd_custom_completer(custom_completer_app: CustomCompleterApp) -> None: """Tests custom completer type on a subcommand created with @cmd2.as_subcommand_to""" # First test the subcommand without the custom completer @@ -1353,7 +1353,7 @@ def test_decorated_subcmd_custom_completer(custom_completer_app: CustomCompleter assert not custom_completer_app.completion_matches -def test_add_parser_custom_completer(): +def test_add_parser_custom_completer() -> None: """Tests setting a custom completer type on a subcommand using add_parser()""" parser = Cmd2ArgumentParser() subparsers = parser.add_subparsers() diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index bcb7e5a80..6e9ee86e1 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -23,7 +23,7 @@ class ApCustomTestApp(cmd2.Cmd): """Test app for cmd2's argparse customization""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) range_parser = Cmd2ArgumentParser() @@ -35,7 +35,7 @@ def __init__(self, *args, **kwargs): range_parser.add_argument('--arg5', nargs=argparse.ONE_OR_MORE) @cmd2.with_argparser(range_parser) - def do_range(self, _): + def do_range(self, _) -> None: pass @@ -44,7 +44,7 @@ def cust_app(): return ApCustomTestApp() -def fake_func(): +def fake_func() -> None: pass @@ -56,7 +56,7 @@ def fake_func(): ({'choices_provider': fake_func, 'completer': fake_func}, False), ], ) -def test_apcustom_choices_callable_count(kwargs, is_valid): +def test_apcustom_choices_callable_count(kwargs, is_valid) -> None: parser = Cmd2ArgumentParser() if is_valid: parser.add_argument('name', **kwargs) @@ -67,7 +67,7 @@ def test_apcustom_choices_callable_count(kwargs, is_valid): @pytest.mark.parametrize('kwargs', [({'choices_provider': fake_func}), ({'completer': fake_func})]) -def test_apcustom_no_choices_callables_alongside_choices(kwargs): +def test_apcustom_no_choices_callables_alongside_choices(kwargs) -> None: parser = Cmd2ArgumentParser() with pytest.raises(TypeError) as excinfo: parser.add_argument('name', choices=['my', 'choices', 'list'], **kwargs) @@ -75,26 +75,26 @@ def test_apcustom_no_choices_callables_alongside_choices(kwargs): @pytest.mark.parametrize('kwargs', [({'choices_provider': fake_func}), ({'completer': fake_func})]) -def test_apcustom_no_choices_callables_when_nargs_is_0(kwargs): +def test_apcustom_no_choices_callables_when_nargs_is_0(kwargs) -> None: parser = Cmd2ArgumentParser() with pytest.raises(TypeError) as excinfo: parser.add_argument('--name', action='store_true', **kwargs) assert 'None of the following parameters can be used on an action that takes no arguments' in str(excinfo.value) -def test_apcustom_usage(): +def test_apcustom_usage() -> None: usage = "A custom usage statement" parser = Cmd2ArgumentParser(usage=usage) assert usage in parser.format_help() -def test_apcustom_nargs_help_format(cust_app): +def test_apcustom_nargs_help_format(cust_app) -> None: out, err = run_cmd(cust_app, 'help range') assert 'Usage: range [-h] [--arg0 ARG0] [--arg1 ARG1{2}] [--arg2 ARG2{3+}]' in out[0] assert ' [--arg3 ARG3{2..3}] [--arg4 [ARG4 [...]]] [--arg5 ARG5 [...]]' in out[1] -def test_apcustom_nargs_range_validation(cust_app): +def test_apcustom_nargs_range_validation(cust_app) -> None: # nargs = (3,) out, err = run_cmd(cust_app, 'range --arg2 one two') assert 'Error: argument --arg2: expected at least 3 arguments' in err[2] @@ -125,28 +125,28 @@ def test_apcustom_nargs_range_validation(cust_app): (1, 2, 3), ], ) -def test_apcustom_narg_invalid_tuples(nargs_tuple): +def test_apcustom_narg_invalid_tuples(nargs_tuple) -> None: parser = Cmd2ArgumentParser() expected_err = 'Ranged values for nargs must be a tuple of 1 or 2 integers' with pytest.raises(ValueError, match=expected_err): parser.add_argument('invalid_tuple', nargs=nargs_tuple) -def test_apcustom_narg_tuple_order(): +def test_apcustom_narg_tuple_order() -> None: parser = Cmd2ArgumentParser() expected_err = 'Invalid nargs range. The first value must be less than the second' with pytest.raises(ValueError, match=expected_err): parser.add_argument('invalid_tuple', nargs=(2, 1)) -def test_apcustom_narg_tuple_negative(): +def test_apcustom_narg_tuple_negative() -> None: parser = Cmd2ArgumentParser() expected_err = 'Negative numbers are invalid for nargs range' with pytest.raises(ValueError, match=expected_err): parser.add_argument('invalid_tuple', nargs=(-1, 1)) -def test_apcustom_narg_tuple_zero_base(): +def test_apcustom_narg_tuple_zero_base() -> None: parser = Cmd2ArgumentParser() arg = parser.add_argument('arg', nargs=(0,)) assert arg.nargs == argparse.ZERO_OR_MORE @@ -166,7 +166,7 @@ def test_apcustom_narg_tuple_zero_base(): assert "arg{0..3}" in parser.format_help() -def test_apcustom_narg_tuple_one_base(): +def test_apcustom_narg_tuple_one_base() -> None: parser = Cmd2ArgumentParser() arg = parser.add_argument('arg', nargs=(1,)) assert arg.nargs == argparse.ONE_OR_MORE @@ -180,7 +180,7 @@ def test_apcustom_narg_tuple_one_base(): assert "arg{1..5}" in parser.format_help() -def test_apcustom_narg_tuple_other_ranges(): +def test_apcustom_narg_tuple_other_ranges() -> None: # Test range with no upper bound on max parser = Cmd2ArgumentParser() arg = parser.add_argument('arg', nargs=(2,)) @@ -194,7 +194,7 @@ def test_apcustom_narg_tuple_other_ranges(): assert arg.nargs_range == (2, 5) -def test_apcustom_print_message(capsys): +def test_apcustom_print_message(capsys) -> None: import sys test_message = 'The test message' @@ -212,7 +212,7 @@ def test_apcustom_print_message(capsys): assert test_message in err -def test_generate_range_error(): +def test_generate_range_error() -> None: # max is INFINITY err_str = generate_range_error(1, constants.INFINITY) assert err_str == "expected at least 1 argument" @@ -235,14 +235,14 @@ def test_generate_range_error(): assert err_str == "expected 0 to 2 arguments" -def test_apcustom_required_options(): +def test_apcustom_required_options() -> None: # Make sure a 'required arguments' section shows when a flag is marked required parser = Cmd2ArgumentParser() parser.add_argument('--required_flag', required=True) assert 'required arguments' in parser.format_help() -def test_override_parser(): +def test_override_parser() -> None: """Test overriding argparse_custom.DEFAULT_ARGUMENT_PARSER""" import importlib @@ -265,14 +265,14 @@ def test_override_parser(): assert argparse_custom.DEFAULT_ARGUMENT_PARSER == CustomParser -def test_apcustom_metavar_tuple(): +def test_apcustom_metavar_tuple() -> None: # Test the case when a tuple metavar is used with nargs an integer > 1 parser = Cmd2ArgumentParser() parser.add_argument('--aflag', nargs=2, metavar=('foo', 'bar'), help='This is a test') assert '[--aflag foo bar]' in parser.format_help() -def test_cmd2_attribute_wrapper(): +def test_cmd2_attribute_wrapper() -> None: initial_val = 5 wrapper = cmd2.Cmd2AttributeWrapper(initial_val) assert wrapper.get() == initial_val @@ -282,7 +282,7 @@ def test_cmd2_attribute_wrapper(): assert wrapper.get() == new_val -def test_completion_items_as_choices(capsys): +def test_completion_items_as_choices(capsys) -> None: """ Test cmd2's patch to Argparse._check_value() which supports CompletionItems as choices. Choices are compared to CompletionItems.orig_value instead of the CompletionItem instance. diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index c4589a586..878e9d9c1 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -11,6 +11,7 @@ from code import ( InteractiveConsole, ) +from typing import Never from unittest import ( mock, ) @@ -73,11 +74,11 @@ def outsim_app(): return CreateOutsimApp() -def test_version(base_app): +def test_version(base_app) -> None: assert cmd2.__version__ -def test_not_in_main_thread(base_app, capsys): +def test_not_in_main_thread(base_app, capsys) -> None: import threading # Mock threading.main_thread() to return our fake thread @@ -93,19 +94,19 @@ def test_not_in_main_thread(base_app, capsys): assert "cmdloop must be run in the main thread" in str(excinfo.value) -def test_empty_statement(base_app): +def test_empty_statement(base_app) -> None: out, err = run_cmd(base_app, '') expected = normalize('') assert out == expected -def test_base_help(base_app): +def test_base_help(base_app) -> None: out, err = run_cmd(base_app, 'help') assert base_app.last_result is True verify_help_text(base_app, out) -def test_base_help_verbose(base_app): +def test_base_help_verbose(base_app) -> None: out, err = run_cmd(base_app, 'help -v') assert base_app.last_result is True verify_help_text(base_app, out) @@ -121,7 +122,7 @@ def test_base_help_verbose(base_app): assert ':param' not in ''.join(out) -def test_base_argparse_help(base_app): +def test_base_argparse_help(base_app) -> None: # Verify that "set -h" gives the same output as "help set" and that it starts in a way that makes sense out1, err1 = run_cmd(base_app, 'set -h') out2, err2 = run_cmd(base_app, 'help set') @@ -132,26 +133,26 @@ def test_base_argparse_help(base_app): assert out1[2].startswith('Set a settable parameter') -def test_base_invalid_option(base_app): +def test_base_invalid_option(base_app) -> None: out, err = run_cmd(base_app, 'set -z') assert err[0] == 'Usage: set [-h] [param] [value]' assert 'Error: unrecognized arguments: -z' in err[1] -def test_base_shortcuts(base_app): +def test_base_shortcuts(base_app) -> None: out, err = run_cmd(base_app, 'shortcuts') expected = normalize(SHORTCUTS_TXT) assert out == expected assert base_app.last_result is True -def test_command_starts_with_shortcut(): +def test_command_starts_with_shortcut() -> None: expected_err = "Invalid command name 'help'" with pytest.raises(ValueError, match=expected_err): cmd2.Cmd(shortcuts={'help': 'fake'}) -def test_base_set(base_app): +def test_base_set(base_app) -> None: # force editor to be 'vim' so test is repeatable across platforms base_app.editor = 'vim' out, err = run_cmd(base_app, 'set') @@ -163,7 +164,7 @@ def test_base_set(base_app): assert base_app.last_result[param] == base_app.settables[param].get_value() -def test_set(base_app): +def test_set(base_app) -> None: out, err = run_cmd(base_app, 'set quiet True') expected = normalize( """ @@ -187,21 +188,21 @@ def test_set(base_app): assert base_app.last_result['quiet'] is True -def test_set_val_empty(base_app): +def test_set_val_empty(base_app) -> None: base_app.editor = "fake" out, err = run_cmd(base_app, 'set editor ""') assert base_app.editor == '' assert base_app.last_result is True -def test_set_val_is_flag(base_app): +def test_set_val_is_flag(base_app) -> None: base_app.editor = "fake" out, err = run_cmd(base_app, 'set editor "-h"') assert base_app.editor == '-h' assert base_app.last_result is True -def test_set_not_supported(base_app): +def test_set_not_supported(base_app) -> None: out, err = run_cmd(base_app, 'set qqq True') expected = normalize( """ @@ -212,7 +213,7 @@ def test_set_not_supported(base_app): assert base_app.last_result is False -def test_set_no_settables(base_app): +def test_set_no_settables(base_app) -> None: base_app._settables.clear() out, err = run_cmd(base_app, 'set quiet True') expected = normalize("There are no settable parameters") @@ -232,7 +233,7 @@ def test_set_no_settables(base_app): ('invalid', False, ansi.AllowStyle.TERMINAL), ], ) -def test_set_allow_style(base_app, new_val, is_valid, expected): +def test_set_allow_style(base_app, new_val, is_valid, expected) -> None: # Initialize allow_style for this test ansi.allow_style = ansi.AllowStyle.TERMINAL @@ -250,7 +251,7 @@ def test_set_allow_style(base_app, new_val, is_valid, expected): ansi.allow_style = ansi.AllowStyle.TERMINAL -def test_set_with_choices(base_app): +def test_set_with_choices(base_app) -> None: """Test choices validation of Settables""" fake_choices = ['valid', 'choices'] base_app.fake = fake_choices[0] @@ -270,7 +271,7 @@ def test_set_with_choices(base_app): class OnChangeHookApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.add_settable(utils.Settable('quiet', bool, "my description", self, onchange_cb=self._onchange_quiet)) @@ -285,7 +286,7 @@ def onchange_app(): return app -def test_set_onchange_hook(onchange_app): +def test_set_onchange_hook(onchange_app) -> None: out, err = run_cmd(onchange_app, 'set quiet True') expected = normalize( """ @@ -298,7 +299,7 @@ def test_set_onchange_hook(onchange_app): assert onchange_app.last_result is True -def test_base_shell(base_app, monkeypatch): +def test_base_shell(base_app, monkeypatch) -> None: m = mock.Mock() monkeypatch.setattr("{}.Popen".format('subprocess'), m) out, err = run_cmd(base_app, 'shell echo a') @@ -306,13 +307,13 @@ def test_base_shell(base_app, monkeypatch): assert m.called -def test_shell_last_result(base_app): +def test_shell_last_result(base_app) -> None: base_app.last_result = None run_cmd(base_app, 'shell fake') assert base_app.last_result is not None -def test_shell_manual_call(base_app): +def test_shell_manual_call(base_app) -> None: # Verifies crash from Issue #986 doesn't happen cmds = ['echo "hi"', 'echo "there"', 'echo "cmd2!"'] cmd = ';'.join(cmds) @@ -324,12 +325,12 @@ def test_shell_manual_call(base_app): base_app.do_shell(cmd) -def test_base_error(base_app): +def test_base_error(base_app) -> None: out, err = run_cmd(base_app, 'meow') assert "is not a recognized command" in err[0] -def test_base_error_suggest_command(base_app): +def test_base_error_suggest_command(base_app) -> None: try: old_suggest_similar_command = base_app.suggest_similar_command base_app.suggest_similar_command = True @@ -339,7 +340,7 @@ def test_base_error_suggest_command(base_app): base_app.suggest_similar_command = old_suggest_similar_command -def test_run_script(base_app, request): +def test_run_script(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') @@ -368,13 +369,13 @@ def test_run_script(base_app, request): assert script_err == manual_err -def test_run_script_with_empty_args(base_app): +def test_run_script_with_empty_args(base_app) -> None: out, err = run_cmd(base_app, 'run_script') assert "the following arguments are required" in err[1] assert base_app.last_result is None -def test_run_script_with_invalid_file(base_app, request): +def test_run_script_with_invalid_file(base_app, request) -> None: # Path does not exist out, err = run_cmd(base_app, 'run_script does_not_exist.txt') assert "Problem accessing script from " in err[0] @@ -387,7 +388,7 @@ def test_run_script_with_invalid_file(base_app, request): assert base_app.last_result is False -def test_run_script_with_empty_file(base_app, request): +def test_run_script_with_empty_file(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'empty.txt') out, err = run_cmd(base_app, f'run_script {filename}') @@ -396,7 +397,7 @@ def test_run_script_with_empty_file(base_app, request): assert base_app.last_result is True -def test_run_script_with_binary_file(base_app, request): +def test_run_script_with_binary_file(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'binary.bin') out, err = run_cmd(base_app, f'run_script {filename}') @@ -404,7 +405,7 @@ def test_run_script_with_binary_file(base_app, request): assert base_app.last_result is False -def test_run_script_with_python_file(base_app, request): +def test_run_script_with_python_file(base_app, request) -> None: m = mock.MagicMock(name='input', return_value='2') builtins.input = m @@ -415,7 +416,7 @@ def test_run_script_with_python_file(base_app, request): assert base_app.last_result is False -def test_run_script_with_utf8_file(base_app, request): +def test_run_script_with_utf8_file(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'utf8.txt') @@ -444,7 +445,7 @@ def test_run_script_with_utf8_file(base_app, request): assert script_err == manual_err -def test_scripts_add_to_history(base_app, request): +def test_scripts_add_to_history(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'help.txt') command = f'run_script {filename}' @@ -465,7 +466,7 @@ def test_scripts_add_to_history(base_app, request): assert base_app.history.get(1).raw == command -def test_run_script_nested_run_scripts(base_app, request): +def test_run_script_nested_run_scripts(base_app, request) -> None: # Verify that running a script with nested run_script commands works correctly, # and runs the nested script commands in the correct order. test_dir = os.path.dirname(request.module.__file__) @@ -489,7 +490,7 @@ def test_run_script_nested_run_scripts(base_app, request): assert out == normalize(expected) -def test_runcmds_plus_hooks(base_app, request): +def test_runcmds_plus_hooks(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) prefilepath = os.path.join(test_dir, 'scripts', 'precmds.txt') postfilepath = os.path.join(test_dir, 'scripts', 'postcmds.txt') @@ -507,11 +508,11 @@ def test_runcmds_plus_hooks(base_app, request): assert out == normalize(expected) -def test_runcmds_plus_hooks_ctrl_c(base_app, capsys): +def test_runcmds_plus_hooks_ctrl_c(base_app, capsys) -> None: """Test Ctrl-C while in runcmds_plus_hooks""" import types - def do_keyboard_interrupt(self, _): + def do_keyboard_interrupt(self, _) -> Never: raise KeyboardInterrupt('Interrupting this command') setattr(base_app, 'do_keyboard_interrupt', types.MethodType(do_keyboard_interrupt, base_app)) @@ -531,7 +532,7 @@ def do_keyboard_interrupt(self, _): assert len(base_app.history) == 2 -def test_relative_run_script(base_app, request): +def test_relative_run_script(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') @@ -561,7 +562,7 @@ def test_relative_run_script(base_app, request): @pytest.mark.parametrize('file_name', odd_file_names) -def test_relative_run_script_with_odd_file_names(base_app, file_name, monkeypatch): +def test_relative_run_script_with_odd_file_names(base_app, file_name, monkeypatch) -> None: """Test file names with various patterns""" # Mock out the do_run_script call to see what args are passed to it run_script_mock = mock.MagicMock(name='do_run_script') @@ -571,15 +572,15 @@ def test_relative_run_script_with_odd_file_names(base_app, file_name, monkeypatc run_script_mock.assert_called_once_with(utils.quote_string(file_name)) -def test_relative_run_script_requires_an_argument(base_app): +def test_relative_run_script_requires_an_argument(base_app) -> None: out, err = run_cmd(base_app, '_relative_run_script') assert 'Error: the following arguments' in err[1] assert base_app.last_result is None -def test_in_script(request): +def test_in_script(request) -> None: class HookApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.register_cmdfinalization_hook(self.hook) @@ -596,13 +597,13 @@ def hook(self: cmd2.Cmd, data: plugin.CommandFinalizationData) -> plugin.Command assert "WE ARE IN SCRIPT" in out[-1] -def test_system_exit_in_command(base_app, capsys): +def test_system_exit_in_command(base_app, capsys) -> None: """Test raising SystemExit in a command""" import types exit_code = 5 - def do_system_exit(self, _): + def do_system_exit(self, _) -> Never: raise SystemExit(exit_code) setattr(base_app, 'do_system_exit', types.MethodType(do_system_exit, base_app)) @@ -612,13 +613,13 @@ def do_system_exit(self, _): assert base_app.exit_code == exit_code -def test_passthrough_exception_in_command(base_app): +def test_passthrough_exception_in_command(base_app) -> None: """Test raising a PassThroughException in a command""" import types expected_err = "Pass me up" - def do_passthrough(self, _): + def do_passthrough(self, _) -> Never: wrapped_ex = OSError(expected_err) raise exceptions.PassThroughException(wrapped_ex=wrapped_ex) @@ -628,7 +629,7 @@ def do_passthrough(self, _): base_app.onecmd_plus_hooks('passthrough') -def test_output_redirection(base_app): +def test_output_redirection(base_app) -> None: fd, filename = tempfile.mkstemp(prefix='cmd2_test', suffix='.txt') os.close(fd) @@ -651,7 +652,7 @@ def test_output_redirection(base_app): os.remove(filename) -def test_output_redirection_to_nonexistent_directory(base_app): +def test_output_redirection_to_nonexistent_directory(base_app) -> None: filename = '~/fakedir/this_does_not_exist.txt' out, err = run_cmd(base_app, f'help > {filename}') @@ -661,7 +662,7 @@ def test_output_redirection_to_nonexistent_directory(base_app): assert 'Failed to redirect' in err[0] -def test_output_redirection_to_too_long_filename(base_app): +def test_output_redirection_to_too_long_filename(base_app) -> None: filename = ( '~/sdkfhksdjfhkjdshfkjsdhfkjsdhfkjdshfkjdshfkjshdfkhdsfkjhewfuihewiufhweiufhiweufhiuewhiuewhfiuwehfia' 'ewhfiuewhfiuewhfiuewhiuewhfiuewhfiuewfhiuwehewiufhewiuhfiweuhfiuwehfiuewfhiuwehiuewfhiuewhiewuhfiueh' @@ -677,7 +678,7 @@ def test_output_redirection_to_too_long_filename(base_app): assert 'Failed to redirect' in err[0] -def test_feedback_to_output_true(base_app): +def test_feedback_to_output_true(base_app) -> None: base_app.feedback_to_output = True base_app.timing = True f, filename = tempfile.mkstemp(prefix='cmd2_test', suffix='.txt') @@ -694,7 +695,7 @@ def test_feedback_to_output_true(base_app): os.remove(filename) -def test_feedback_to_output_false(base_app): +def test_feedback_to_output_false(base_app) -> None: base_app.feedback_to_output = False base_app.timing = True f, filename = tempfile.mkstemp(prefix='feedback_to_output', suffix='.txt') @@ -713,7 +714,7 @@ def test_feedback_to_output_false(base_app): os.remove(filename) -def test_disallow_redirection(base_app): +def test_disallow_redirection(base_app) -> None: # Set allow_redirection to False base_app.allow_redirection = False @@ -727,7 +728,7 @@ def test_disallow_redirection(base_app): assert not os.path.exists(filename) -def test_pipe_to_shell(base_app): +def test_pipe_to_shell(base_app) -> None: if sys.platform == "win32": # Windows command = 'help | sort' @@ -741,7 +742,7 @@ def test_pipe_to_shell(base_app): assert not err -def test_pipe_to_shell_and_redirect(base_app): +def test_pipe_to_shell_and_redirect(base_app) -> None: filename = 'out.txt' if sys.platform == "win32": # Windows @@ -758,7 +759,7 @@ def test_pipe_to_shell_and_redirect(base_app): os.remove(filename) -def test_pipe_to_shell_error(base_app): +def test_pipe_to_shell_error(base_app) -> None: # Try to pipe command output to a shell command that doesn't exist in order to produce an error out, err = run_cmd(base_app, 'help | foobarbaz.this_does_not_exist') assert not out @@ -780,7 +781,7 @@ def test_pipe_to_shell_error(base_app): @pytest.mark.skipif(not can_paste, reason="Pyperclip could not find a copy/paste mechanism for your system") -def test_send_to_paste_buffer(base_app): +def test_send_to_paste_buffer(base_app) -> None: # Test writing to the PasteBuffer/Clipboard run_cmd(base_app, 'help >') paste_contents = cmd2.cmd2.get_paste_buffer() @@ -793,7 +794,7 @@ def test_send_to_paste_buffer(base_app): assert len(appended_contents) > len(paste_contents) -def test_get_paste_buffer_exception(base_app, mocker, capsys): +def test_get_paste_buffer_exception(base_app, mocker, capsys) -> None: # Force get_paste_buffer to throw an exception pastemock = mocker.patch('pyperclip.paste') pastemock.side_effect = ValueError('foo') @@ -809,7 +810,7 @@ def test_get_paste_buffer_exception(base_app, mocker, capsys): assert 'foo' in err -def test_allow_clipboard_initializer(base_app): +def test_allow_clipboard_initializer(base_app) -> None: assert base_app.allow_clipboard is True noclipcmd = cmd2.Cmd(allow_clipboard=False) assert noclipcmd.allow_clipboard is False @@ -819,14 +820,14 @@ def test_allow_clipboard_initializer(base_app): # before it tries to do anything with pyperclip, that's why we can # safely run this test without skipping it if pyperclip doesn't # work in the test environment, like we do for test_send_to_paste_buffer() -def test_allow_clipboard(base_app): +def test_allow_clipboard(base_app) -> None: base_app.allow_clipboard = False out, err = run_cmd(base_app, 'help >') assert not out assert "Clipboard access not allowed" in err -def test_base_timing(base_app): +def test_base_timing(base_app) -> None: base_app.feedback_to_output = False out, err = run_cmd(base_app, 'set timing True') expected = normalize( @@ -858,7 +859,7 @@ def _expected_no_editor_error(): return expected_text -def test_base_debug(base_app): +def test_base_debug(base_app) -> None: # Purposely set the editor to None base_app.editor = None @@ -883,7 +884,7 @@ def test_base_debug(base_app): assert err[0].startswith('Traceback (most recent call last):') -def test_debug_not_settable(base_app): +def test_debug_not_settable(base_app) -> None: # Set debug to False and make it unsettable base_app.debug = False base_app.remove_settable('debug') @@ -895,12 +896,12 @@ def test_debug_not_settable(base_app): assert err == ['Invalid syntax: No closing quotation'] -def test_remove_settable_keyerror(base_app): +def test_remove_settable_keyerror(base_app) -> None: with pytest.raises(KeyError): base_app.remove_settable('fake') -def test_edit_file(base_app, request, monkeypatch): +def test_edit_file(base_app, request, monkeypatch) -> None: # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock base_app.editor = 'fooedit' @@ -918,7 +919,7 @@ def test_edit_file(base_app, request, monkeypatch): @pytest.mark.parametrize('file_name', odd_file_names) -def test_edit_file_with_odd_file_names(base_app, file_name, monkeypatch): +def test_edit_file_with_odd_file_names(base_app, file_name, monkeypatch) -> None: """Test editor and file names with various patterns""" # Mock out the do_shell call to see what args are passed to it shell_mock = mock.MagicMock(name='do_shell') @@ -930,7 +931,7 @@ def test_edit_file_with_odd_file_names(base_app, file_name, monkeypatch): shell_mock.assert_called_once_with(f'"fooedit" {utils.quote_string(file_name)}') -def test_edit_file_with_spaces(base_app, request, monkeypatch): +def test_edit_file_with_spaces(base_app, request, monkeypatch) -> None: # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock base_app.editor = 'fooedit' @@ -947,7 +948,7 @@ def test_edit_file_with_spaces(base_app, request, monkeypatch): m.assert_called_once() -def test_edit_blank(base_app, monkeypatch): +def test_edit_blank(base_app, monkeypatch) -> None: # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock base_app.editor = 'fooedit' @@ -961,7 +962,7 @@ def test_edit_blank(base_app, monkeypatch): m.assert_called_once() -def test_base_py_interactive(base_app): +def test_base_py_interactive(base_app) -> None: # Mock out the InteractiveConsole.interact() call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='interact') InteractiveConsole.interact = m @@ -972,7 +973,7 @@ def test_base_py_interactive(base_app): m.assert_called_once() -def test_base_cmdloop_with_startup_commands(): +def test_base_cmdloop_with_startup_commands() -> None: intro = 'Hello World, this is an intro ...' # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args @@ -991,7 +992,7 @@ def test_base_cmdloop_with_startup_commands(): assert out == expected -def test_base_cmdloop_without_startup_commands(): +def test_base_cmdloop_without_startup_commands() -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] with mock.patch.object(sys, 'argv', testargs): @@ -1012,7 +1013,7 @@ def test_base_cmdloop_without_startup_commands(): assert out == expected -def test_cmdloop_without_rawinput(): +def test_cmdloop_without_rawinput() -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] with mock.patch.object(sys, 'argv', testargs): @@ -1035,7 +1036,7 @@ def test_cmdloop_without_rawinput(): @pytest.mark.skipif(sys.platform.startswith('win'), reason="stty sane only run on Linux/Mac") -def test_stty_sane(base_app, monkeypatch): +def test_stty_sane(base_app, monkeypatch) -> None: """Make sure stty sane is run on Linux/Mac after each command if stdin is a terminal""" with mock.patch('sys.stdin.isatty', mock.MagicMock(name='isatty', return_value=True)): # Mock out the subprocess.Popen call so we don't actually run stty sane @@ -1046,7 +1047,7 @@ def test_stty_sane(base_app, monkeypatch): m.assert_called_once_with(['stty', 'sane']) -def test_sigint_handler(base_app): +def test_sigint_handler(base_app) -> None: # No KeyboardInterrupt should be raised when using sigint_protection with base_app.sigint_protection: base_app.sigint_handler(signal.SIGINT, 1) @@ -1056,14 +1057,14 @@ def test_sigint_handler(base_app): base_app.sigint_handler(signal.SIGINT, 1) -def test_raise_keyboard_interrupt(base_app): +def test_raise_keyboard_interrupt(base_app) -> None: with pytest.raises(KeyboardInterrupt) as excinfo: base_app._raise_keyboard_interrupt() assert 'Got a keyboard interrupt' in str(excinfo.value) @pytest.mark.skipif(sys.platform.startswith('win'), reason="SIGTERM only handled on Linux/Mac") -def test_termination_signal_handler(base_app): +def test_termination_signal_handler(base_app) -> None: with pytest.raises(SystemExit) as excinfo: base_app.termination_signal_handler(signal.SIGHUP, 1) assert excinfo.value.code == signal.SIGHUP + 128 @@ -1074,7 +1075,7 @@ def test_termination_signal_handler(base_app): class HookFailureApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) # register a postparsing hook method self.register_postparsing_hook(self.postparsing_precmd) @@ -1091,21 +1092,21 @@ def hook_failure(): return app -def test_precmd_hook_success(base_app): +def test_precmd_hook_success(base_app) -> None: out = base_app.onecmd_plus_hooks('help') assert out is False -def test_precmd_hook_failure(hook_failure): +def test_precmd_hook_failure(hook_failure) -> None: out = hook_failure.onecmd_plus_hooks('help') assert out is True class SayApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def do_say(self, arg): + def do_say(self, arg) -> None: self.poutput(arg) @@ -1116,7 +1117,7 @@ def say_app(): return app -def test_ctrl_c_at_prompt(say_app): +def test_ctrl_c_at_prompt(say_app) -> None: # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input') m.side_effect = ['say hello', KeyboardInterrupt(), 'say goodbye', 'eof'] @@ -1130,12 +1131,12 @@ def test_ctrl_c_at_prompt(say_app): class ShellApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.default_to_shell = True -def test_default_to_shell(base_app, monkeypatch): +def test_default_to_shell(base_app, monkeypatch) -> None: if sys.platform.startswith('win'): line = 'dir' else: @@ -1149,7 +1150,7 @@ def test_default_to_shell(base_app, monkeypatch): assert m.called -def test_escaping_prompt(): +def test_escaping_prompt() -> None: from cmd2.rl_utils import ( rl_escape_prompt, rl_unescape_prompt, @@ -1180,23 +1181,23 @@ def test_escaping_prompt(): class HelpApp(cmd2.Cmd): """Class for testing custom help_* methods which override docstring help.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def do_squat(self, arg): + def do_squat(self, arg) -> None: """This docstring help will never be shown because the help_squat method overrides it.""" - def help_squat(self): + def help_squat(self) -> None: self.stdout.write('This command does diddly squat...\n') - def do_edit(self, arg): + def do_edit(self, arg) -> None: """This overrides the edit command and does nothing.""" # This command will be in the "undocumented" section of the help menu - def do_undoc(self, arg): + def do_undoc(self, arg) -> None: pass - def do_multiline_docstr(self, arg): + def do_multiline_docstr(self, arg) -> None: """ This documentation is multiple lines @@ -1207,7 +1208,7 @@ def do_multiline_docstr(self, arg): parser_cmd_parser = cmd2.Cmd2ArgumentParser(description="This is the description.") @cmd2.with_argparser(parser_cmd_parser) - def do_parser_cmd(self, args): + def do_parser_cmd(self, args) -> None: """This is the docstring.""" @@ -1217,39 +1218,39 @@ def help_app(): return app -def test_custom_command_help(help_app): +def test_custom_command_help(help_app) -> None: out, err = run_cmd(help_app, 'help squat') expected = normalize('This command does diddly squat...') assert out == expected assert help_app.last_result is True -def test_custom_help_menu(help_app): +def test_custom_help_menu(help_app) -> None: out, err = run_cmd(help_app, 'help') verify_help_text(help_app, out) -def test_help_undocumented(help_app): +def test_help_undocumented(help_app) -> None: out, err = run_cmd(help_app, 'help undoc') assert err[0].startswith("No help on undoc") assert help_app.last_result is False -def test_help_overridden_method(help_app): +def test_help_overridden_method(help_app) -> None: out, err = run_cmd(help_app, 'help edit') expected = normalize('This overrides the edit command and does nothing.') assert out == expected assert help_app.last_result is True -def test_help_multiline_docstring(help_app): +def test_help_multiline_docstring(help_app) -> None: out, err = run_cmd(help_app, 'help multiline_docstr') expected = normalize('This documentation\nis multiple lines\nand there are no\ntabs') assert out == expected assert help_app.last_result is True -def test_help_verbose_uses_parser_description(help_app: HelpApp): +def test_help_verbose_uses_parser_description(help_app: HelpApp) -> None: out, err = run_cmd(help_app, 'help --verbose') verify_help_text(help_app, out, verbose_strings=[help_app.parser_cmd_parser.description]) @@ -1257,31 +1258,31 @@ def test_help_verbose_uses_parser_description(help_app: HelpApp): class HelpCategoriesApp(cmd2.Cmd): """Class for testing custom help_* methods which override docstring help.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @cmd2.with_category('Some Category') - def do_diddly(self, arg): + def do_diddly(self, arg) -> None: """This command does diddly""" # This command will be in the "Some Category" section of the help menu even though it has no docstring @cmd2.with_category("Some Category") - def do_cat_nodoc(self, arg): + def do_cat_nodoc(self, arg) -> None: pass - def do_squat(self, arg): + def do_squat(self, arg) -> None: """This docstring help will never be shown because the help_squat method overrides it.""" - def help_squat(self): + def help_squat(self) -> None: self.stdout.write('This command does diddly squat...\n') - def do_edit(self, arg): + def do_edit(self, arg) -> None: """This overrides the edit command and does nothing.""" cmd2.categorize((do_squat, do_edit), 'Custom Category') # This command will be in the "undocumented" section of the help menu - def do_undoc(self, arg): + def do_undoc(self, arg) -> None: pass @@ -1291,20 +1292,20 @@ def helpcat_app(): return app -def test_help_cat_base(helpcat_app): +def test_help_cat_base(helpcat_app) -> None: out, err = run_cmd(helpcat_app, 'help') assert helpcat_app.last_result is True verify_help_text(helpcat_app, out) -def test_help_cat_verbose(helpcat_app): +def test_help_cat_verbose(helpcat_app) -> None: out, err = run_cmd(helpcat_app, 'help --verbose') assert helpcat_app.last_result is True verify_help_text(helpcat_app, out) class SelectApp(cmd2.Cmd): - def do_eat(self, arg): + def do_eat(self, arg) -> None: """Eat something, with a selection of sauces to choose from.""" # Pass in a single string of space-separated selections sauce = self.select('sweet salty', 'Sauce? ') @@ -1312,14 +1313,14 @@ def do_eat(self, arg): result = result.format(food=arg, sauce=sauce) self.stdout.write(result + '\n') - def do_study(self, arg): + def do_study(self, arg) -> None: """Learn something, with a selection of subjects to choose from.""" # Pass in a list of strings for selections subject = self.select(['math', 'science'], 'Subject? ') result = f'Good luck learning {subject}!\n' self.stdout.write(result) - def do_procrastinate(self, arg): + def do_procrastinate(self, arg) -> None: """Waste time in your manner of choice.""" # Pass in a list of tuples for selections leisure_activity = self.select( @@ -1328,14 +1329,14 @@ def do_procrastinate(self, arg): result = f'Have fun procrasinating with {leisure_activity}!\n' self.stdout.write(result) - def do_play(self, arg): + def do_play(self, arg) -> None: """Play your favorite musical instrument.""" # Pass in an uneven list of tuples for selections instrument = self.select([('Guitar', 'Electric Guitar'), ('Drums',)], 'Instrument? ') result = f'Charm us with the {instrument}...\n' self.stdout.write(result) - def do_return_type(self, arg): + def do_return_type(self, arg) -> None: """Test that return values can be non-strings""" choice = self.select([(1, 'Integer'), ("test_str", 'String'), (self.do_play, 'Method')], 'Choice? ') result = f'The return type is {type(choice)}\n' @@ -1348,7 +1349,7 @@ def select_app(): return app -def test_select_options(select_app, monkeypatch): +def test_select_options(select_app, monkeypatch) -> None: # Mock out the read_input call so we don't actually wait for a user's response on stdin read_input_mock = mock.MagicMock(name='read_input', return_value='2') monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) @@ -1370,7 +1371,7 @@ def test_select_options(select_app, monkeypatch): assert out == expected -def test_select_invalid_option_too_big(select_app, monkeypatch): +def test_select_invalid_option_too_big(select_app, monkeypatch) -> None: # Mock out the input call so we don't actually wait for a user's response on stdin read_input_mock = mock.MagicMock(name='read_input') @@ -1399,7 +1400,7 @@ def test_select_invalid_option_too_big(select_app, monkeypatch): assert out == expected -def test_select_invalid_option_too_small(select_app, monkeypatch): +def test_select_invalid_option_too_small(select_app, monkeypatch) -> None: # Mock out the input call so we don't actually wait for a user's response on stdin read_input_mock = mock.MagicMock(name='read_input') @@ -1428,7 +1429,7 @@ def test_select_invalid_option_too_small(select_app, monkeypatch): assert out == expected -def test_select_list_of_strings(select_app, monkeypatch): +def test_select_list_of_strings(select_app, monkeypatch) -> None: # Mock out the input call so we don't actually wait for a user's response on stdin read_input_mock = mock.MagicMock(name='read_input', return_value='2') monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) @@ -1449,7 +1450,7 @@ def test_select_list_of_strings(select_app, monkeypatch): assert out == expected -def test_select_list_of_tuples(select_app, monkeypatch): +def test_select_list_of_tuples(select_app, monkeypatch) -> None: # Mock out the input call so we don't actually wait for a user's response on stdin read_input_mock = mock.MagicMock(name='read_input', return_value='2') monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) @@ -1470,7 +1471,7 @@ def test_select_list_of_tuples(select_app, monkeypatch): assert out == expected -def test_select_uneven_list_of_tuples(select_app, monkeypatch): +def test_select_uneven_list_of_tuples(select_app, monkeypatch) -> None: # Mock out the input call so we don't actually wait for a user's response on stdin read_input_mock = mock.MagicMock(name='read_input', return_value='2') monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) @@ -1499,7 +1500,7 @@ def test_select_uneven_list_of_tuples(select_app, monkeypatch): ('3', ""), ], ) -def test_select_return_type(select_app, monkeypatch, selection, type_str): +def test_select_return_type(select_app, monkeypatch, selection, type_str) -> None: # Mock out the input call so we don't actually wait for a user's response on stdin read_input_mock = mock.MagicMock(name='read_input', return_value=selection) monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) @@ -1521,7 +1522,7 @@ def test_select_return_type(select_app, monkeypatch, selection, type_str): assert out == expected -def test_select_eof(select_app, monkeypatch): +def test_select_eof(select_app, monkeypatch) -> None: # Ctrl-D during select causes an EOFError that just reprompts the user read_input_mock = mock.MagicMock(name='read_input', side_effect=[EOFError, 2]) monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) @@ -1536,7 +1537,7 @@ def test_select_eof(select_app, monkeypatch): assert read_input_mock.call_count == 2 -def test_select_ctrl_c(outsim_app, monkeypatch): +def test_select_ctrl_c(outsim_app, monkeypatch) -> None: # Ctrl-C during select prints ^C and raises a KeyboardInterrupt read_input_mock = mock.MagicMock(name='read_input', side_effect=KeyboardInterrupt) monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) @@ -1553,14 +1554,14 @@ class HelpNoDocstringApp(cmd2.Cmd): greet_parser.add_argument('-s', '--shout', action="store_true", help="N00B EMULATION MODE") @cmd2.with_argparser(greet_parser, with_unknown_args=True) - def do_greet(self, opts, arg): + def do_greet(self, opts, arg) -> None: arg = ''.join(arg) if opts.shout: arg = arg.upper() self.stdout.write(arg + '\n') -def test_help_with_no_docstring(capsys): +def test_help_with_no_docstring(capsys) -> None: app = HelpNoDocstringApp() app.onecmd_plus_hooks('greet -h') out, err = capsys.readouterr() @@ -1578,14 +1579,14 @@ def test_help_with_no_docstring(capsys): class MultilineApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, multiline_commands=['orate'], **kwargs) orate_parser = cmd2.Cmd2ArgumentParser() orate_parser.add_argument('-s', '--shout', action="store_true", help="N00B EMULATION MODE") @cmd2.with_argparser(orate_parser, with_unknown_args=True) - def do_orate(self, opts, arg): + def do_orate(self, opts, arg) -> None: arg = ''.join(arg) if opts.shout: arg = arg.upper() @@ -1598,12 +1599,12 @@ def multiline_app(): return app -def test_multiline_complete_empty_statement_raises_exception(multiline_app): +def test_multiline_complete_empty_statement_raises_exception(multiline_app) -> None: with pytest.raises(exceptions.EmptyStatement): multiline_app._complete_statement('') -def test_multiline_complete_statement_without_terminator(multiline_app): +def test_multiline_complete_statement_without_terminator(multiline_app) -> None: # Mock out the input call so we don't actually wait for a user's response # on stdin when it looks for more input m = mock.MagicMock(name='input', return_value='\n') @@ -1618,7 +1619,7 @@ def test_multiline_complete_statement_without_terminator(multiline_app): assert statement.multiline_command == command -def test_multiline_complete_statement_with_unclosed_quotes(multiline_app): +def test_multiline_complete_statement_with_unclosed_quotes(multiline_app) -> None: # Mock out the input call so we don't actually wait for a user's response # on stdin when it looks for more input m = mock.MagicMock(name='input', side_effect=['quotes', '" now closed;']) @@ -1632,7 +1633,7 @@ def test_multiline_complete_statement_with_unclosed_quotes(multiline_app): assert statement.terminator == ';' -def test_multiline_input_line_to_statement(multiline_app): +def test_multiline_input_line_to_statement(multiline_app) -> None: # Verify _input_line_to_statement saves the fully entered input line for multiline commands # Mock out the input call so we don't actually wait for a user's response @@ -1648,7 +1649,7 @@ def test_multiline_input_line_to_statement(multiline_app): assert statement.multiline_command == 'orate' -def test_multiline_history_no_prior_history(multiline_app): +def test_multiline_history_no_prior_history(multiline_app) -> None: # Test no existing history prior to typing the command m = mock.MagicMock(name='input', side_effect=['person', '\n']) builtins.input = m @@ -1665,7 +1666,7 @@ def test_multiline_history_no_prior_history(multiline_app): assert readline.get_history_item(1) == "orate hi person" -def test_multiline_history_first_line_matches_prev_entry(multiline_app): +def test_multiline_history_first_line_matches_prev_entry(multiline_app) -> None: # Test when first line of multiline command matches previous history entry m = mock.MagicMock(name='input', side_effect=['person', '\n']) builtins.input = m @@ -1684,7 +1685,7 @@ def test_multiline_history_first_line_matches_prev_entry(multiline_app): assert readline.get_history_item(2) == "orate hi person" -def test_multiline_history_matches_prev_entry(multiline_app): +def test_multiline_history_matches_prev_entry(multiline_app) -> None: # Test combined multiline command that matches previous history entry m = mock.MagicMock(name='input', side_effect=['person', '\n']) builtins.input = m @@ -1702,7 +1703,7 @@ def test_multiline_history_matches_prev_entry(multiline_app): assert readline.get_history_item(1) == "orate hi person" -def test_multiline_history_does_not_match_prev_entry(multiline_app): +def test_multiline_history_does_not_match_prev_entry(multiline_app) -> None: # Test combined multiline command that does not match previous history entry m = mock.MagicMock(name='input', side_effect=['person', '\n']) builtins.input = m @@ -1721,7 +1722,7 @@ def test_multiline_history_does_not_match_prev_entry(multiline_app): assert readline.get_history_item(2) == "orate hi person" -def test_multiline_history_with_quotes(multiline_app): +def test_multiline_history_with_quotes(multiline_app) -> None: # Test combined multiline command with quotes m = mock.MagicMock(name='input', side_effect=[' and spaces ', ' "', ' in', 'quotes.', ';']) builtins.input = m @@ -1743,19 +1744,19 @@ def test_multiline_history_with_quotes(multiline_app): class CommandResultApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def do_affirmative(self, arg): + def do_affirmative(self, arg) -> None: self.last_result = cmd2.CommandResult(arg, data=True) - def do_negative(self, arg): + def do_negative(self, arg) -> None: self.last_result = cmd2.CommandResult(arg, data=False) - def do_affirmative_no_data(self, arg): + def do_affirmative_no_data(self, arg) -> None: self.last_result = cmd2.CommandResult(arg) - def do_negative_no_data(self, arg): + def do_negative_no_data(self, arg) -> None: self.last_result = cmd2.CommandResult('', arg) @@ -1765,7 +1766,7 @@ def commandresult_app(): return app -def test_commandresult_truthy(commandresult_app): +def test_commandresult_truthy(commandresult_app) -> None: arg = 'foo' run_cmd(commandresult_app, f'affirmative {arg}') assert commandresult_app.last_result @@ -1776,7 +1777,7 @@ def test_commandresult_truthy(commandresult_app): assert commandresult_app.last_result == cmd2.CommandResult(arg) -def test_commandresult_falsy(commandresult_app): +def test_commandresult_falsy(commandresult_app) -> None: arg = 'bar' run_cmd(commandresult_app, f'negative {arg}') assert not commandresult_app.last_result @@ -1788,7 +1789,7 @@ def test_commandresult_falsy(commandresult_app): @pytest.mark.skipif(sys.platform.startswith('win'), reason="Test is problematic on GitHub Actions Windows runners") -def test_is_text_file_bad_input(base_app): +def test_is_text_file_bad_input(base_app) -> None: # Test with a non-existent file with pytest.raises(FileNotFoundError): utils.is_text_file('does_not_exist.txt') @@ -1798,19 +1799,19 @@ def test_is_text_file_bad_input(base_app): utils.is_text_file('.') -def test_eof(base_app): +def test_eof(base_app) -> None: # Only thing to verify is that it returns True assert base_app.do_eof('') assert base_app.last_result is True -def test_quit(base_app): +def test_quit(base_app) -> None: # Only thing to verify is that it returns True assert base_app.do_quit('') assert base_app.last_result is True -def test_echo(capsys): +def test_echo(capsys) -> None: app = cmd2.Cmd() app.echo = True commands = ['help history'] @@ -1821,7 +1822,7 @@ def test_echo(capsys): assert out.startswith(f'{app.prompt}{commands[0]}\n' + HELP_HISTORY.split()[0]) -def test_read_input_rawinput_true(capsys, monkeypatch): +def test_read_input_rawinput_true(capsys, monkeypatch) -> None: prompt_str = 'the_prompt' input_str = 'some input' @@ -1886,7 +1887,7 @@ def test_read_input_rawinput_true(capsys, monkeypatch): assert not out -def test_read_input_rawinput_false(capsys, monkeypatch): +def test_read_input_rawinput_false(capsys, monkeypatch) -> None: prompt_str = 'the_prompt' input_str = 'some input' @@ -1939,7 +1940,7 @@ def make_app(isatty: bool, empty_input: bool = False): assert not out -def test_read_command_line_eof(base_app, monkeypatch): +def test_read_command_line_eof(base_app, monkeypatch) -> None: read_input_mock = mock.MagicMock(name='read_input', side_effect=EOFError) monkeypatch.setattr("cmd2.Cmd.read_input", read_input_mock) @@ -1947,7 +1948,7 @@ def test_read_command_line_eof(base_app, monkeypatch): assert line == 'eof' -def test_poutput_string(outsim_app): +def test_poutput_string(outsim_app) -> None: msg = 'This is a test' outsim_app.poutput(msg) out = outsim_app.stdout.getvalue() @@ -1955,7 +1956,7 @@ def test_poutput_string(outsim_app): assert out == expected -def test_poutput_zero(outsim_app): +def test_poutput_zero(outsim_app) -> None: msg = 0 outsim_app.poutput(msg) out = outsim_app.stdout.getvalue() @@ -1963,7 +1964,7 @@ def test_poutput_zero(outsim_app): assert out == expected -def test_poutput_empty_string(outsim_app): +def test_poutput_empty_string(outsim_app) -> None: msg = '' outsim_app.poutput(msg) out = outsim_app.stdout.getvalue() @@ -1971,7 +1972,7 @@ def test_poutput_empty_string(outsim_app): assert out == expected -def test_poutput_none(outsim_app): +def test_poutput_none(outsim_app) -> None: msg = None outsim_app.poutput(msg) out = outsim_app.stdout.getvalue() @@ -1980,7 +1981,7 @@ def test_poutput_none(outsim_app): @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_poutput_ansi_always(outsim_app): +def test_poutput_ansi_always(outsim_app) -> None: msg = 'Hello World' colored_msg = ansi.style(msg, fg=ansi.Fg.CYAN) outsim_app.poutput(colored_msg) @@ -1991,7 +1992,7 @@ def test_poutput_ansi_always(outsim_app): @with_ansi_style(ansi.AllowStyle.NEVER) -def test_poutput_ansi_never(outsim_app): +def test_poutput_ansi_never(outsim_app) -> None: msg = 'Hello World' colored_msg = ansi.style(msg, fg=ansi.Fg.CYAN) outsim_app.poutput(colored_msg) @@ -2015,7 +2016,7 @@ def test_poutput_ansi_never(outsim_app): ] -def test_get_alias_completion_items(base_app): +def test_get_alias_completion_items(base_app) -> None: run_cmd(base_app, 'alias create fake run_pyscript') run_cmd(base_app, 'alias create ls !ls -hal') @@ -2028,7 +2029,7 @@ def test_get_alias_completion_items(base_app): assert cur_res.description.rstrip() == base_app.aliases[cur_res] -def test_get_macro_completion_items(base_app): +def test_get_macro_completion_items(base_app) -> None: run_cmd(base_app, 'macro create foo !echo foo') run_cmd(base_app, 'macro create bar !echo bar') @@ -2041,7 +2042,7 @@ def test_get_macro_completion_items(base_app): assert cur_res.description.rstrip() == base_app.macros[cur_res].value -def test_get_settable_completion_items(base_app): +def test_get_settable_completion_items(base_app) -> None: results = base_app._get_settable_completion_items() assert len(results) == len(base_app.settables) @@ -2059,13 +2060,13 @@ def test_get_settable_completion_items(base_app): assert cur_settable.description[0:10] in cur_res.description -def test_alias_no_subcommand(base_app): +def test_alias_no_subcommand(base_app) -> None: out, err = run_cmd(base_app, 'alias') assert "Usage: alias [-h]" in err[0] assert "Error: the following arguments are required: SUBCOMMAND" in err[1] -def test_alias_create(base_app): +def test_alias_create(base_app) -> None: # Create the alias out, err = run_cmd(base_app, 'alias create fake run_pyscript') assert out == normalize("Alias 'fake' created") @@ -2099,7 +2100,7 @@ def test_alias_create(base_app): assert base_app.last_result['fake'] == "help" -def test_alias_create_with_quoted_tokens(base_app): +def test_alias_create_with_quoted_tokens(base_app) -> None: """Demonstrate that quotes in alias value will be preserved""" alias_name = "fake" alias_command = 'help ">" "out file.txt" ";"' @@ -2117,19 +2118,19 @@ def test_alias_create_with_quoted_tokens(base_app): @pytest.mark.parametrize('alias_name', invalid_command_name) -def test_alias_create_invalid_name(base_app, alias_name, capsys): +def test_alias_create_invalid_name(base_app, alias_name, capsys) -> None: out, err = run_cmd(base_app, f'alias create {alias_name} help') assert "Invalid alias name" in err[0] assert base_app.last_result is False -def test_alias_create_with_command_name(base_app): +def test_alias_create_with_command_name(base_app) -> None: out, err = run_cmd(base_app, 'alias create help stuff') assert "Alias cannot have the same name as a command" in err[0] assert base_app.last_result is False -def test_alias_create_with_macro_name(base_app): +def test_alias_create_with_macro_name(base_app) -> None: macro = "my_macro" run_cmd(base_app, f'macro create {macro} help') out, err = run_cmd(base_app, f'alias create {macro} help') @@ -2137,7 +2138,7 @@ def test_alias_create_with_macro_name(base_app): assert base_app.last_result is False -def test_alias_that_resolves_into_comment(base_app): +def test_alias_that_resolves_into_comment(base_app) -> None: # Create the alias out, err = run_cmd(base_app, 'alias create fake ' + constants.COMMENT_CHAR + ' blah blah') assert out == normalize("Alias 'fake' created") @@ -2148,14 +2149,14 @@ def test_alias_that_resolves_into_comment(base_app): assert not err -def test_alias_list_invalid_alias(base_app): +def test_alias_list_invalid_alias(base_app) -> None: # Look up invalid alias out, err = run_cmd(base_app, 'alias list invalid') assert "Alias 'invalid' not found" in err[0] assert base_app.last_result == {} -def test_alias_delete(base_app): +def test_alias_delete(base_app) -> None: # Create an alias run_cmd(base_app, 'alias create fake run_pyscript') @@ -2165,25 +2166,25 @@ def test_alias_delete(base_app): assert base_app.last_result is True -def test_alias_delete_all(base_app): +def test_alias_delete_all(base_app) -> None: out, err = run_cmd(base_app, 'alias delete --all') assert out == normalize("All aliases deleted") assert base_app.last_result is True -def test_alias_delete_non_existing(base_app): +def test_alias_delete_non_existing(base_app) -> None: out, err = run_cmd(base_app, 'alias delete fake') assert "Alias 'fake' does not exist" in err[0] assert base_app.last_result is True -def test_alias_delete_no_name(base_app): +def test_alias_delete_no_name(base_app) -> None: out, err = run_cmd(base_app, 'alias delete') assert "Either --all or alias name(s)" in err[0] assert base_app.last_result is False -def test_multiple_aliases(base_app): +def test_multiple_aliases(base_app) -> None: alias1 = 'h1' alias2 = 'h2' run_cmd(base_app, f'alias create {alias1} help') @@ -2195,13 +2196,13 @@ def test_multiple_aliases(base_app): verify_help_text(base_app, out) -def test_macro_no_subcommand(base_app): +def test_macro_no_subcommand(base_app) -> None: out, err = run_cmd(base_app, 'macro') assert "Usage: macro [-h]" in err[0] assert "Error: the following arguments are required: SUBCOMMAND" in err[1] -def test_macro_create(base_app): +def test_macro_create(base_app) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake run_pyscript') assert out == normalize("Macro 'fake' created") @@ -2235,7 +2236,7 @@ def test_macro_create(base_app): assert base_app.last_result['fake'] == "help" -def test_macro_create_with_quoted_tokens(base_app): +def test_macro_create_with_quoted_tokens(base_app) -> None: """Demonstrate that quotes in macro value will be preserved""" macro_name = "fake" macro_command = 'help ">" "out file.txt" ";"' @@ -2253,19 +2254,19 @@ def test_macro_create_with_quoted_tokens(base_app): @pytest.mark.parametrize('macro_name', invalid_command_name) -def test_macro_create_invalid_name(base_app, macro_name): +def test_macro_create_invalid_name(base_app, macro_name) -> None: out, err = run_cmd(base_app, f'macro create {macro_name} help') assert "Invalid macro name" in err[0] assert base_app.last_result is False -def test_macro_create_with_command_name(base_app): +def test_macro_create_with_command_name(base_app) -> None: out, err = run_cmd(base_app, 'macro create help stuff') assert "Macro cannot have the same name as a command" in err[0] assert base_app.last_result is False -def test_macro_create_with_alias_name(base_app): +def test_macro_create_with_alias_name(base_app) -> None: macro = "my_macro" run_cmd(base_app, f'alias create {macro} help') out, err = run_cmd(base_app, f'macro create {macro} help') @@ -2273,7 +2274,7 @@ def test_macro_create_with_alias_name(base_app): assert base_app.last_result is False -def test_macro_create_with_args(base_app): +def test_macro_create_with_args(base_app) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake {1} {2}') assert out == normalize("Macro 'fake' created") @@ -2283,7 +2284,7 @@ def test_macro_create_with_args(base_app): verify_help_text(base_app, out) -def test_macro_create_with_escaped_args(base_app): +def test_macro_create_with_escaped_args(base_app) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake help {{1}}') assert out == normalize("Macro 'fake' created") @@ -2293,7 +2294,7 @@ def test_macro_create_with_escaped_args(base_app): assert err[0].startswith('No help on {1}') -def test_macro_usage_with_missing_args(base_app): +def test_macro_usage_with_missing_args(base_app) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake help {1} {2}') assert out == normalize("Macro 'fake' created") @@ -2303,7 +2304,7 @@ def test_macro_usage_with_missing_args(base_app): assert "expects at least 2 arguments" in err[0] -def test_macro_usage_with_exta_args(base_app): +def test_macro_usage_with_exta_args(base_app) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake help {1}') assert out == normalize("Macro 'fake' created") @@ -2313,21 +2314,21 @@ def test_macro_usage_with_exta_args(base_app): assert "Usage: alias create" in out[0] -def test_macro_create_with_missing_arg_nums(base_app): +def test_macro_create_with_missing_arg_nums(base_app) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake help {1} {3}') assert "Not all numbers between 1 and 3" in err[0] assert base_app.last_result is False -def test_macro_create_with_invalid_arg_num(base_app): +def test_macro_create_with_invalid_arg_num(base_app) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake help {1} {-1} {0}') assert "Argument numbers must be greater than 0" in err[0] assert base_app.last_result is False -def test_macro_create_with_unicode_numbered_arg(base_app): +def test_macro_create_with_unicode_numbered_arg(base_app) -> None: # Create the macro expecting 1 argument out, err = run_cmd(base_app, 'macro create fake help {\N{ARABIC-INDIC DIGIT ONE}}') assert out == normalize("Macro 'fake' created") @@ -2337,13 +2338,13 @@ def test_macro_create_with_unicode_numbered_arg(base_app): assert "expects at least 1 argument" in err[0] -def test_macro_create_with_missing_unicode_arg_nums(base_app): +def test_macro_create_with_missing_unicode_arg_nums(base_app) -> None: out, err = run_cmd(base_app, 'macro create fake help {1} {\N{ARABIC-INDIC DIGIT THREE}}') assert "Not all numbers between 1 and 3" in err[0] assert base_app.last_result is False -def test_macro_that_resolves_into_comment(base_app): +def test_macro_that_resolves_into_comment(base_app) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake {1} blah blah') assert out == normalize("Macro 'fake' created") @@ -2354,14 +2355,14 @@ def test_macro_that_resolves_into_comment(base_app): assert not err -def test_macro_list_invalid_macro(base_app): +def test_macro_list_invalid_macro(base_app) -> None: # Look up invalid macro out, err = run_cmd(base_app, 'macro list invalid') assert "Macro 'invalid' not found" in err[0] assert base_app.last_result == {} -def test_macro_delete(base_app): +def test_macro_delete(base_app) -> None: # Create an macro run_cmd(base_app, 'macro create fake run_pyscript') @@ -2371,25 +2372,25 @@ def test_macro_delete(base_app): assert base_app.last_result is True -def test_macro_delete_all(base_app): +def test_macro_delete_all(base_app) -> None: out, err = run_cmd(base_app, 'macro delete --all') assert out == normalize("All macros deleted") assert base_app.last_result is True -def test_macro_delete_non_existing(base_app): +def test_macro_delete_non_existing(base_app) -> None: out, err = run_cmd(base_app, 'macro delete fake') assert "Macro 'fake' does not exist" in err[0] assert base_app.last_result is True -def test_macro_delete_no_name(base_app): +def test_macro_delete_no_name(base_app) -> None: out, err = run_cmd(base_app, 'macro delete') assert "Either --all or macro name(s)" in err[0] assert base_app.last_result is False -def test_multiple_macros(base_app): +def test_multiple_macros(base_app) -> None: macro1 = 'h1' macro2 = 'h2' run_cmd(base_app, f'macro create {macro1} help') @@ -2402,7 +2403,7 @@ def test_multiple_macros(base_app): assert len(out2) > len(out) -def test_nonexistent_macro(base_app): +def test_nonexistent_macro(base_app) -> None: from cmd2.parsing import ( StatementParser, ) @@ -2418,7 +2419,7 @@ def test_nonexistent_macro(base_app): @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_perror_style(base_app, capsys): +def test_perror_style(base_app, capsys) -> None: msg = 'testing...' end = '\n' base_app.perror(msg) @@ -2427,7 +2428,7 @@ def test_perror_style(base_app, capsys): @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_perror_no_style(base_app, capsys): +def test_perror_no_style(base_app, capsys) -> None: msg = 'testing...' end = '\n' base_app.perror(msg, apply_style=False) @@ -2436,7 +2437,7 @@ def test_perror_no_style(base_app, capsys): @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_pexcept_style(base_app, capsys): +def test_pexcept_style(base_app, capsys) -> None: msg = Exception('testing...') base_app.pexcept(msg) @@ -2445,7 +2446,7 @@ def test_pexcept_style(base_app, capsys): @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_pexcept_no_style(base_app, capsys): +def test_pexcept_no_style(base_app, capsys) -> None: msg = Exception('testing...') base_app.pexcept(msg, apply_style=False) @@ -2454,7 +2455,7 @@ def test_pexcept_no_style(base_app, capsys): @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_pexcept_not_exception(base_app, capsys): +def test_pexcept_not_exception(base_app, capsys) -> None: # Pass in a msg that is not an Exception object msg = False @@ -2463,7 +2464,7 @@ def test_pexcept_not_exception(base_app, capsys): assert err.startswith(ansi.style_error(msg)) -def test_ppaged(outsim_app): +def test_ppaged(outsim_app) -> None: msg = 'testing...' end = '\n' outsim_app.ppaged(msg) @@ -2472,7 +2473,7 @@ def test_ppaged(outsim_app): @with_ansi_style(ansi.AllowStyle.TERMINAL) -def test_ppaged_strips_ansi_when_redirecting(outsim_app): +def test_ppaged_strips_ansi_when_redirecting(outsim_app) -> None: msg = 'testing...' end = '\n' outsim_app._redirecting = True @@ -2482,7 +2483,7 @@ def test_ppaged_strips_ansi_when_redirecting(outsim_app): @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_ppaged_strips_ansi_when_redirecting_if_always(outsim_app): +def test_ppaged_strips_ansi_when_redirecting_if_always(outsim_app) -> None: msg = 'testing...' end = '\n' outsim_app._redirecting = True @@ -2496,7 +2497,7 @@ def test_ppaged_strips_ansi_when_redirecting_if_always(outsim_app): # command parsing by parent methods we don't override # don't need to test all the parsing logic here, because # parseline just calls StatementParser.parse_command_only() -def test_parseline_empty(base_app): +def test_parseline_empty(base_app) -> None: statement = '' command, args, line = base_app.parseline(statement) assert not command @@ -2504,7 +2505,7 @@ def test_parseline_empty(base_app): assert not line -def test_parseline_quoted(base_app): +def test_parseline_quoted(base_app) -> None: statement = " command with 'partially completed quotes " command, args, line = base_app.parseline(statement) assert command == 'command' @@ -2512,7 +2513,7 @@ def test_parseline_quoted(base_app): assert line == statement.lstrip() -def test_onecmd_raw_str_continue(outsim_app): +def test_onecmd_raw_str_continue(outsim_app) -> None: line = "help" stop = outsim_app.onecmd(line) out = outsim_app.stdout.getvalue() @@ -2520,7 +2521,7 @@ def test_onecmd_raw_str_continue(outsim_app): verify_help_text(outsim_app, out) -def test_onecmd_raw_str_quit(outsim_app): +def test_onecmd_raw_str_quit(outsim_app) -> None: line = "quit" stop = outsim_app.onecmd(line) out = outsim_app.stdout.getvalue() @@ -2528,7 +2529,7 @@ def test_onecmd_raw_str_quit(outsim_app): assert out == '' -def test_onecmd_add_to_history(outsim_app): +def test_onecmd_add_to_history(outsim_app) -> None: line = "help" saved_hist_len = len(outsim_app.history) @@ -2545,7 +2546,7 @@ def test_onecmd_add_to_history(outsim_app): assert new_hist_len == saved_hist_len -def test_get_all_commands(base_app): +def test_get_all_commands(base_app) -> None: # Verify that the base app has the expected commands commands = base_app.get_all_commands() expected_commands = [ @@ -2568,22 +2569,22 @@ def test_get_all_commands(base_app): assert commands == expected_commands -def test_get_help_topics(base_app): +def test_get_help_topics(base_app) -> None: # Verify that the base app has no additional help_foo methods custom_help = base_app.get_help_topics() assert len(custom_help) == 0 -def test_get_help_topics_hidden(): +def test_get_help_topics_hidden() -> None: # Verify get_help_topics() filters out hidden commands class TestApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def do_my_cmd(self, args): + def do_my_cmd(self, args) -> None: pass - def help_my_cmd(self, args): + def help_my_cmd(self, args) -> None: pass app = TestApp() @@ -2596,7 +2597,7 @@ def help_my_cmd(self, args): class ReplWithExitCode(cmd2.Cmd): """Example cmd2 application where we can specify an exit code when existing.""" - def __init__(self): + def __init__(self) -> None: super().__init__(allow_cli_args=False) @cmd2.with_argument_list @@ -2629,7 +2630,7 @@ def exit_code_repl(): return app -def test_exit_code_default(exit_code_repl): +def test_exit_code_default(exit_code_repl) -> None: app = exit_code_repl app.use_rawinput = True @@ -2645,7 +2646,7 @@ def test_exit_code_default(exit_code_repl): assert out == expected -def test_exit_code_nonzero(exit_code_repl): +def test_exit_code_nonzero(exit_code_repl) -> None: app = exit_code_repl app.use_rawinput = True @@ -2662,21 +2663,21 @@ def test_exit_code_nonzero(exit_code_repl): class AnsiApp(cmd2.Cmd): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def do_echo(self, args): + def do_echo(self, args) -> None: self.poutput(args) self.perror(args) - def do_echo_error(self, args): + def do_echo_error(self, args) -> None: self.poutput(ansi.style(args, fg=ansi.Fg.RED)) # perror uses colors by default self.perror(args) @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_ansi_pouterr_always_tty(mocker, capsys): +def test_ansi_pouterr_always_tty(mocker, capsys) -> None: app = AnsiApp() mocker.patch.object(app.stdout, 'isatty', return_value=True) mocker.patch.object(sys.stderr, 'isatty', return_value=True) @@ -2699,7 +2700,7 @@ def test_ansi_pouterr_always_tty(mocker, capsys): @with_ansi_style(ansi.AllowStyle.ALWAYS) -def test_ansi_pouterr_always_notty(mocker, capsys): +def test_ansi_pouterr_always_notty(mocker, capsys) -> None: app = AnsiApp() mocker.patch.object(app.stdout, 'isatty', return_value=False) mocker.patch.object(sys.stderr, 'isatty', return_value=False) @@ -2722,7 +2723,7 @@ def test_ansi_pouterr_always_notty(mocker, capsys): @with_ansi_style(ansi.AllowStyle.TERMINAL) -def test_ansi_terminal_tty(mocker, capsys): +def test_ansi_terminal_tty(mocker, capsys) -> None: app = AnsiApp() mocker.patch.object(app.stdout, 'isatty', return_value=True) mocker.patch.object(sys.stderr, 'isatty', return_value=True) @@ -2744,7 +2745,7 @@ def test_ansi_terminal_tty(mocker, capsys): @with_ansi_style(ansi.AllowStyle.TERMINAL) -def test_ansi_terminal_notty(mocker, capsys): +def test_ansi_terminal_notty(mocker, capsys) -> None: app = AnsiApp() mocker.patch.object(app.stdout, 'isatty', return_value=False) mocker.patch.object(sys.stderr, 'isatty', return_value=False) @@ -2759,7 +2760,7 @@ def test_ansi_terminal_notty(mocker, capsys): @with_ansi_style(ansi.AllowStyle.NEVER) -def test_ansi_never_tty(mocker, capsys): +def test_ansi_never_tty(mocker, capsys) -> None: app = AnsiApp() mocker.patch.object(app.stdout, 'isatty', return_value=True) mocker.patch.object(sys.stderr, 'isatty', return_value=True) @@ -2774,7 +2775,7 @@ def test_ansi_never_tty(mocker, capsys): @with_ansi_style(ansi.AllowStyle.NEVER) -def test_ansi_never_notty(mocker, capsys): +def test_ansi_never_notty(mocker, capsys) -> None: app = AnsiApp() mocker.patch.object(app.stdout, 'isatty', return_value=False) mocker.patch.object(sys.stderr, 'isatty', return_value=False) @@ -2793,21 +2794,21 @@ class DisableCommandsApp(cmd2.Cmd): category_name = "Test Category" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @cmd2.with_category(category_name) - def do_has_helper_funcs(self, arg): + def do_has_helper_funcs(self, arg) -> None: self.poutput("The real has_helper_funcs") - def help_has_helper_funcs(self): + def help_has_helper_funcs(self) -> None: self.poutput('Help for has_helper_funcs') def complete_has_helper_funcs(self, *args): return ['result'] @cmd2.with_category(category_name) - def do_has_no_helper_funcs(self, arg): + def do_has_no_helper_funcs(self, arg) -> None: """Help for has_no_helper_funcs""" self.poutput("The real has_no_helper_funcs") @@ -2818,7 +2819,7 @@ def disable_commands_app(): return app -def test_disable_and_enable_category(disable_commands_app): +def test_disable_and_enable_category(disable_commands_app) -> None: ########################################################################## # Disable the category ########################################################################## @@ -2911,7 +2912,7 @@ def test_disable_and_enable_category(disable_commands_app): assert 'has_helper_funcs' in help_topics -def test_enable_enabled_command(disable_commands_app): +def test_enable_enabled_command(disable_commands_app) -> None: # Test enabling a command that is not disabled saved_len = len(disable_commands_app.disabled_commands) disable_commands_app.enable_command('has_helper_funcs') @@ -2920,12 +2921,12 @@ def test_enable_enabled_command(disable_commands_app): assert saved_len == len(disable_commands_app.disabled_commands) -def test_disable_fake_command(disable_commands_app): +def test_disable_fake_command(disable_commands_app) -> None: with pytest.raises(AttributeError): disable_commands_app.disable_command('fake', 'fake message') -def test_disable_command_twice(disable_commands_app): +def test_disable_command_twice(disable_commands_app) -> None: saved_len = len(disable_commands_app.disabled_commands) message_to_print = 'These commands are currently disabled' disable_commands_app.disable_command('has_helper_funcs', message_to_print) @@ -2941,7 +2942,7 @@ def test_disable_command_twice(disable_commands_app): assert saved_len == new_len -def test_disabled_command_not_in_history(disable_commands_app): +def test_disabled_command_not_in_history(disable_commands_app) -> None: message_to_print = 'These commands are currently disabled' disable_commands_app.disable_command('has_helper_funcs', message_to_print) @@ -2950,7 +2951,7 @@ def test_disabled_command_not_in_history(disable_commands_app): assert saved_len == len(disable_commands_app.history) -def test_disabled_message_command_name(disable_commands_app): +def test_disabled_message_command_name(disable_commands_app) -> None: message_to_print = f'{COMMAND_NAME} is currently disabled' disable_commands_app.disable_command('has_helper_funcs', message_to_print) @@ -2959,7 +2960,7 @@ def test_disabled_message_command_name(disable_commands_app): @pytest.mark.parametrize('silence_startup_script', [True, False]) -def test_startup_script(request, capsys, silence_startup_script): +def test_startup_script(request, capsys, silence_startup_script) -> None: test_dir = os.path.dirname(request.module.__file__) startup_script = os.path.join(test_dir, '.cmd2rc') app = cmd2.Cmd(allow_cli_args=False, startup_script=startup_script, silence_startup_script=silence_startup_script) @@ -2979,7 +2980,7 @@ def test_startup_script(request, capsys, silence_startup_script): @pytest.mark.parametrize('startup_script', odd_file_names) -def test_startup_script_with_odd_file_names(startup_script): +def test_startup_script_with_odd_file_names(startup_script) -> None: """Test file names with various patterns""" # Mock os.path.exists to trick cmd2 into adding this script to its startup commands saved_exists = os.path.exists @@ -2993,13 +2994,13 @@ def test_startup_script_with_odd_file_names(startup_script): os.path.exists = saved_exists -def test_transcripts_at_init(): +def test_transcripts_at_init() -> None: transcript_files = ['foo', 'bar'] app = cmd2.Cmd(allow_cli_args=False, transcript_files=transcript_files) assert app._transcript_files == transcript_files -def test_columnize_too_wide(outsim_app): +def test_columnize_too_wide(outsim_app) -> None: """Test calling columnize with output that wider than display_width""" str_list = ["way too wide", "much wider than the first"] outsim_app.columnize(str_list, display_width=5) @@ -3008,7 +3009,7 @@ def test_columnize_too_wide(outsim_app): assert outsim_app.stdout.getvalue() == expected -def test_command_parser_retrieval(outsim_app: cmd2.Cmd): +def test_command_parser_retrieval(outsim_app: cmd2.Cmd) -> None: # Pass something that isn't a method not_a_method = "just a string" assert outsim_app._command_parsers.get(not_a_method) is None @@ -3017,7 +3018,7 @@ def test_command_parser_retrieval(outsim_app: cmd2.Cmd): assert outsim_app._command_parsers.get(outsim_app.__init__) is None -def test_command_synonym_parser(): +def test_command_synonym_parser() -> None: # Make sure a command synonym returns the same parser as what it aliases class SynonymApp(cmd2.cmd2.Cmd): do_synonym = cmd2.cmd2.Cmd.do_help diff --git a/tests/test_completion.py b/tests/test_completion.py index 8667a4acc..ab6284f84 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -8,6 +8,7 @@ import enum import os import sys +from typing import Never from unittest import ( mock, ) @@ -61,7 +62,7 @@ class CompletionsExample(cmd2.Cmd): Example cmd2 application used to exercise tab completion tests """ - def __init__(self): + def __init__(self) -> None: cmd2.Cmd.__init__(self, multiline_commands=['test_multiline']) self.foo = 'bar' self.add_settable( @@ -74,38 +75,38 @@ def __init__(self): ) ) - def do_test_basic(self, args): + def do_test_basic(self, args) -> None: pass def complete_test_basic(self, text, line, begidx, endidx): return self.basic_complete(text, line, begidx, endidx, food_item_strs) - def do_test_delimited(self, args): + def do_test_delimited(self, args) -> None: pass def complete_test_delimited(self, text, line, begidx, endidx): return self.delimiter_complete(text, line, begidx, endidx, delimited_strs, '/') - def do_test_sort_key(self, args): + def do_test_sort_key(self, args) -> None: pass def complete_test_sort_key(self, text, line, begidx, endidx): num_strs = ['2', '11', '1'] return self.basic_complete(text, line, begidx, endidx, num_strs) - def do_test_raise_exception(self, args): + def do_test_raise_exception(self, args) -> None: pass - def complete_test_raise_exception(self, text, line, begidx, endidx): + def complete_test_raise_exception(self, text, line, begidx, endidx) -> Never: raise IndexError("You are out of bounds!!") - def do_test_multiline(self, args): + def do_test_multiline(self, args) -> None: pass def complete_test_multiline(self, text, line, begidx, endidx): return self.basic_complete(text, line, begidx, endidx, sport_item_strs) - def do_test_no_completer(self, args): + def do_test_no_completer(self, args) -> None: """Completing this should result in completedefault() being called""" def complete_foo_val(self, text, line, begidx, endidx, arg_tokens): @@ -129,7 +130,7 @@ def cmd2_app(): return c -def test_cmd2_command_completion_single(cmd2_app): +def test_cmd2_command_completion_single(cmd2_app) -> None: text = 'he' line = text endidx = len(line) @@ -137,7 +138,7 @@ def test_cmd2_command_completion_single(cmd2_app): assert cmd2_app.completenames(text, line, begidx, endidx) == ['help'] -def test_complete_command_single(cmd2_app): +def test_complete_command_single(cmd2_app) -> None: text = 'he' line = text endidx = len(line) @@ -148,7 +149,7 @@ def test_complete_command_single(cmd2_app): assert cmd2_app.completion_matches == ['help '] -def test_complete_empty_arg(cmd2_app): +def test_complete_empty_arg(cmd2_app) -> None: text = '' line = f'help {text}' endidx = len(line) @@ -161,7 +162,7 @@ def test_complete_empty_arg(cmd2_app): assert cmd2_app.completion_matches == expected -def test_complete_bogus_command(cmd2_app): +def test_complete_bogus_command(cmd2_app) -> None: text = '' line = f'fizbuzz {text}' endidx = len(line) @@ -173,7 +174,7 @@ def test_complete_bogus_command(cmd2_app): assert cmd2_app.completion_matches == expected -def test_complete_exception(cmd2_app, capsys): +def test_complete_exception(cmd2_app, capsys) -> None: text = '' line = f'test_raise_exception {text}' endidx = len(line) @@ -186,7 +187,7 @@ def test_complete_exception(cmd2_app, capsys): assert "IndexError" in err -def test_complete_macro(base_app, request): +def test_complete_macro(base_app, request) -> None: # Create the macro out, err = run_cmd(base_app, 'macro create fake run_pyscript {1}') assert out == normalize("Macro 'fake' created") @@ -206,7 +207,7 @@ def test_complete_macro(base_app, request): assert base_app.completion_matches == expected -def test_default_sort_key(cmd2_app): +def test_default_sort_key(cmd2_app) -> None: text = '' line = f'test_sort_key {text}' endidx = len(line) @@ -227,7 +228,7 @@ def test_default_sort_key(cmd2_app): assert cmd2_app.completion_matches == expected -def test_cmd2_command_completion_multiple(cmd2_app): +def test_cmd2_command_completion_multiple(cmd2_app) -> None: text = 'h' line = text endidx = len(line) @@ -235,7 +236,7 @@ def test_cmd2_command_completion_multiple(cmd2_app): assert cmd2_app.completenames(text, line, begidx, endidx) == ['help', 'history'] -def test_cmd2_command_completion_nomatch(cmd2_app): +def test_cmd2_command_completion_nomatch(cmd2_app) -> None: text = 'fakecommand' line = text endidx = len(line) @@ -243,7 +244,7 @@ def test_cmd2_command_completion_nomatch(cmd2_app): assert cmd2_app.completenames(text, line, begidx, endidx) == [] -def test_cmd2_help_completion_single(cmd2_app): +def test_cmd2_help_completion_single(cmd2_app) -> None: text = 'he' line = f'help {text}' endidx = len(line) @@ -256,7 +257,7 @@ def test_cmd2_help_completion_single(cmd2_app): assert cmd2_app.completion_matches == ['help '] -def test_cmd2_help_completion_multiple(cmd2_app): +def test_cmd2_help_completion_multiple(cmd2_app) -> None: text = 'h' line = f'help {text}' endidx = len(line) @@ -267,7 +268,7 @@ def test_cmd2_help_completion_multiple(cmd2_app): assert cmd2_app.completion_matches == ['help', 'history'] -def test_cmd2_help_completion_nomatch(cmd2_app): +def test_cmd2_help_completion_nomatch(cmd2_app) -> None: text = 'fakecommand' line = f'help {text}' endidx = len(line) @@ -277,7 +278,7 @@ def test_cmd2_help_completion_nomatch(cmd2_app): assert first_match is None -def test_set_allow_style_completion(cmd2_app): +def test_set_allow_style_completion(cmd2_app) -> None: """Confirm that completing allow_style presents AllowStyle strings""" text = '' line = 'set allow_style' @@ -291,7 +292,7 @@ def test_set_allow_style_completion(cmd2_app): assert cmd2_app.completion_matches == sorted(expected, key=cmd2_app.default_sort_key) -def test_set_bool_completion(cmd2_app): +def test_set_bool_completion(cmd2_app) -> None: """Confirm that completing a boolean Settable presents true and false strings""" text = '' line = 'set debug' @@ -305,7 +306,7 @@ def test_set_bool_completion(cmd2_app): assert cmd2_app.completion_matches == sorted(expected, key=cmd2_app.default_sort_key) -def test_shell_command_completion_shortcut(cmd2_app): +def test_shell_command_completion_shortcut(cmd2_app) -> None: # Made sure ! runs a shell command and all matches start with ! since there # isn't a space between ! and the shell command. Display matches won't # begin with the !. @@ -328,7 +329,7 @@ def test_shell_command_completion_shortcut(cmd2_app): assert cmd2_app.display_matches == expected_display -def test_shell_command_completion_doesnt_match_wildcards(cmd2_app): +def test_shell_command_completion_doesnt_match_wildcards(cmd2_app) -> None: if sys.platform == "win32": text = 'c*' else: @@ -342,7 +343,7 @@ def test_shell_command_completion_doesnt_match_wildcards(cmd2_app): assert first_match is None -def test_shell_command_completion_multiple(cmd2_app): +def test_shell_command_completion_multiple(cmd2_app) -> None: if sys.platform == "win32": text = 'c' expected = 'calc.exe' @@ -359,7 +360,7 @@ def test_shell_command_completion_multiple(cmd2_app): assert expected in cmd2_app.completion_matches -def test_shell_command_completion_nomatch(cmd2_app): +def test_shell_command_completion_nomatch(cmd2_app) -> None: text = 'zzzz' line = f'shell {text}' endidx = len(line) @@ -369,7 +370,7 @@ def test_shell_command_completion_nomatch(cmd2_app): assert first_match is None -def test_shell_command_completion_doesnt_complete_when_just_shell(cmd2_app): +def test_shell_command_completion_doesnt_complete_when_just_shell(cmd2_app) -> None: text = '' line = f'shell {text}' endidx = len(line) @@ -379,7 +380,7 @@ def test_shell_command_completion_doesnt_complete_when_just_shell(cmd2_app): assert first_match is None -def test_shell_command_completion_does_path_completion_when_after_command(cmd2_app, request): +def test_shell_command_completion_does_path_completion_when_after_command(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'conftest') @@ -393,7 +394,7 @@ def test_shell_command_completion_does_path_completion_when_after_command(cmd2_a assert cmd2_app.completion_matches == [text + '.py '] -def test_shell_command_complete_in_path(cmd2_app, request): +def test_shell_command_complete_in_path(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 's') @@ -410,7 +411,7 @@ def test_shell_command_complete_in_path(cmd2_app, request): assert expected in cmd2_app.completion_matches -def test_path_completion_single_end(cmd2_app, request): +def test_path_completion_single_end(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'conftest') @@ -422,7 +423,7 @@ def test_path_completion_single_end(cmd2_app, request): assert cmd2_app.path_complete(text, line, begidx, endidx) == [text + '.py'] -def test_path_completion_multiple(cmd2_app, request): +def test_path_completion_multiple(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 's') @@ -436,7 +437,7 @@ def test_path_completion_multiple(cmd2_app, request): assert matches == expected -def test_path_completion_nomatch(cmd2_app, request): +def test_path_completion_nomatch(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'fakepath') @@ -448,7 +449,7 @@ def test_path_completion_nomatch(cmd2_app, request): assert cmd2_app.path_complete(text, line, begidx, endidx) == [] -def test_default_to_shell_completion(cmd2_app, request): +def test_default_to_shell_completion(cmd2_app, request) -> None: cmd2_app.default_to_shell = True test_dir = os.path.dirname(request.module.__file__) @@ -471,7 +472,7 @@ def test_default_to_shell_completion(cmd2_app, request): assert cmd2_app.completion_matches == [text + '.py '] -def test_path_completion_no_text(cmd2_app): +def test_path_completion_no_text(cmd2_app) -> None: # Run path complete with no search text which should show what's in cwd text = '' line = f'shell ls {text}' @@ -493,7 +494,7 @@ def test_path_completion_no_text(cmd2_app): assert completions_cwd -def test_path_completion_no_path(cmd2_app): +def test_path_completion_no_path(cmd2_app) -> None: # Run path complete with search text that isn't preceded by a path. This should use CWD as the path. text = 'p' line = f'shell ls {text}' @@ -516,7 +517,7 @@ def test_path_completion_no_path(cmd2_app): @pytest.mark.skipif(sys.platform == 'win32', reason="this only applies on systems where the root directory is a slash") -def test_path_completion_cwd_is_root_dir(cmd2_app): +def test_path_completion_cwd_is_root_dir(cmd2_app) -> None: # Change our CWD to root dir cwd = os.getcwd() os.chdir(os.path.sep) @@ -534,7 +535,7 @@ def test_path_completion_cwd_is_root_dir(cmd2_app): os.chdir(cwd) -def test_path_completion_doesnt_match_wildcards(cmd2_app, request): +def test_path_completion_doesnt_match_wildcards(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c*') @@ -547,7 +548,7 @@ def test_path_completion_doesnt_match_wildcards(cmd2_app, request): assert cmd2_app.path_complete(text, line, begidx, endidx) == [] -def test_path_completion_complete_user(cmd2_app): +def test_path_completion_complete_user(cmd2_app) -> None: import getpass user = getpass.getuser() @@ -562,7 +563,7 @@ def test_path_completion_complete_user(cmd2_app): assert expected in completions -def test_path_completion_user_path_expansion(cmd2_app): +def test_path_completion_user_path_expansion(cmd2_app) -> None: # Run path with a tilde and a slash if sys.platform.startswith('win'): cmd = 'dir' @@ -586,7 +587,7 @@ def test_path_completion_user_path_expansion(cmd2_app): assert completions_tilde_slash == completions_home -def test_path_completion_directories_only(cmd2_app, request): +def test_path_completion_directories_only(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 's') @@ -600,7 +601,7 @@ def test_path_completion_directories_only(cmd2_app, request): assert cmd2_app.path_complete(text, line, begidx, endidx, path_filter=os.path.isdir) == expected -def test_basic_completion_single(cmd2_app): +def test_basic_completion_single(cmd2_app) -> None: text = 'Pi' line = f'list_food -f {text}' endidx = len(line) @@ -609,7 +610,7 @@ def test_basic_completion_single(cmd2_app): assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == ['Pizza'] -def test_basic_completion_multiple(cmd2_app): +def test_basic_completion_multiple(cmd2_app) -> None: text = '' line = f'list_food -f {text}' endidx = len(line) @@ -619,7 +620,7 @@ def test_basic_completion_multiple(cmd2_app): assert matches == sorted(food_item_strs) -def test_basic_completion_nomatch(cmd2_app): +def test_basic_completion_nomatch(cmd2_app) -> None: text = 'q' line = f'list_food -f {text}' endidx = len(line) @@ -628,7 +629,7 @@ def test_basic_completion_nomatch(cmd2_app): assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == [] -def test_delimiter_completion(cmd2_app): +def test_delimiter_completion(cmd2_app) -> None: text = '/home/' line = f'run_script {text}' endidx = len(line) @@ -643,7 +644,7 @@ def test_delimiter_completion(cmd2_app): assert display_list == ['other user', 'user'] -def test_flag_based_completion_single(cmd2_app): +def test_flag_based_completion_single(cmd2_app) -> None: text = 'Pi' line = f'list_food -f {text}' endidx = len(line) @@ -652,7 +653,7 @@ def test_flag_based_completion_single(cmd2_app): assert cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) == ['Pizza'] -def test_flag_based_completion_multiple(cmd2_app): +def test_flag_based_completion_multiple(cmd2_app) -> None: text = '' line = f'list_food -f {text}' endidx = len(line) @@ -662,7 +663,7 @@ def test_flag_based_completion_multiple(cmd2_app): assert matches == sorted(food_item_strs) -def test_flag_based_completion_nomatch(cmd2_app): +def test_flag_based_completion_nomatch(cmd2_app) -> None: text = 'q' line = f'list_food -f {text}' endidx = len(line) @@ -671,7 +672,7 @@ def test_flag_based_completion_nomatch(cmd2_app): assert cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) == [] -def test_flag_based_default_completer(cmd2_app, request): +def test_flag_based_default_completer(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c') @@ -685,7 +686,7 @@ def test_flag_based_default_completer(cmd2_app, request): ] -def test_flag_based_callable_completer(cmd2_app, request): +def test_flag_based_callable_completer(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c') @@ -698,7 +699,7 @@ def test_flag_based_callable_completer(cmd2_app, request): assert cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) == [text + 'onftest.py'] -def test_index_based_completion_single(cmd2_app): +def test_index_based_completion_single(cmd2_app) -> None: text = 'Foo' line = f'command Pizza {text}' endidx = len(line) @@ -707,7 +708,7 @@ def test_index_based_completion_single(cmd2_app): assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) == ['Football'] -def test_index_based_completion_multiple(cmd2_app): +def test_index_based_completion_multiple(cmd2_app) -> None: text = '' line = f'command Pizza {text}' endidx = len(line) @@ -717,7 +718,7 @@ def test_index_based_completion_multiple(cmd2_app): assert matches == sorted(sport_item_strs) -def test_index_based_completion_nomatch(cmd2_app): +def test_index_based_completion_nomatch(cmd2_app) -> None: text = 'q' line = f'command {text}' endidx = len(line) @@ -725,7 +726,7 @@ def test_index_based_completion_nomatch(cmd2_app): assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) == [] -def test_index_based_default_completer(cmd2_app, request): +def test_index_based_default_completer(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c') @@ -739,7 +740,7 @@ def test_index_based_default_completer(cmd2_app, request): ] -def test_index_based_callable_completer(cmd2_app, request): +def test_index_based_callable_completer(cmd2_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) text = os.path.join(test_dir, 'c') @@ -752,7 +753,7 @@ def test_index_based_callable_completer(cmd2_app, request): assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) == [text + 'onftest.py'] -def test_tokens_for_completion_quoted(cmd2_app): +def test_tokens_for_completion_quoted(cmd2_app) -> None: text = 'Pi' line = f'list_food "{text}"' endidx = len(line) @@ -766,7 +767,7 @@ def test_tokens_for_completion_quoted(cmd2_app): assert expected_raw_tokens == raw_tokens -def test_tokens_for_completion_unclosed_quote(cmd2_app): +def test_tokens_for_completion_unclosed_quote(cmd2_app) -> None: text = 'Pi' line = f'list_food "{text}' endidx = len(line) @@ -780,7 +781,7 @@ def test_tokens_for_completion_unclosed_quote(cmd2_app): assert expected_raw_tokens == raw_tokens -def test_tokens_for_completion_punctuation(cmd2_app): +def test_tokens_for_completion_punctuation(cmd2_app) -> None: """Test that redirectors and terminators are word delimiters""" text = 'file' line = f'command | < ;>>{text}' @@ -795,7 +796,7 @@ def test_tokens_for_completion_punctuation(cmd2_app): assert expected_raw_tokens == raw_tokens -def test_tokens_for_completion_quoted_punctuation(cmd2_app): +def test_tokens_for_completion_quoted_punctuation(cmd2_app) -> None: """Test that quoted punctuation characters are not word delimiters""" text = '>file' line = f'command "{text}' @@ -810,7 +811,7 @@ def test_tokens_for_completion_quoted_punctuation(cmd2_app): assert expected_raw_tokens == raw_tokens -def test_add_opening_quote_basic_no_text(cmd2_app): +def test_add_opening_quote_basic_no_text(cmd2_app) -> None: text = '' line = f'test_basic {text}' endidx = len(line) @@ -822,7 +823,7 @@ def test_add_opening_quote_basic_no_text(cmd2_app): assert cmd2_app.completion_matches == sorted(food_item_strs, key=cmd2_app.default_sort_key) -def test_add_opening_quote_basic_nothing_added(cmd2_app): +def test_add_opening_quote_basic_nothing_added(cmd2_app) -> None: text = 'P' line = f'test_basic {text}' endidx = len(line) @@ -833,7 +834,7 @@ def test_add_opening_quote_basic_nothing_added(cmd2_app): assert cmd2_app.completion_matches == ['Pizza', 'Potato'] -def test_add_opening_quote_basic_quote_added(cmd2_app): +def test_add_opening_quote_basic_quote_added(cmd2_app) -> None: text = 'Ha' line = f'test_basic {text}' endidx = len(line) @@ -845,7 +846,7 @@ def test_add_opening_quote_basic_quote_added(cmd2_app): assert cmd2_app.completion_matches == expected -def test_add_opening_quote_basic_single_quote_added(cmd2_app): +def test_add_opening_quote_basic_single_quote_added(cmd2_app) -> None: text = 'Ch' line = f'test_basic {text}' endidx = len(line) @@ -857,7 +858,7 @@ def test_add_opening_quote_basic_single_quote_added(cmd2_app): assert cmd2_app.completion_matches == expected -def test_add_opening_quote_basic_text_is_common_prefix(cmd2_app): +def test_add_opening_quote_basic_text_is_common_prefix(cmd2_app) -> None: # This tests when the text entered is the same as the common prefix of the matches text = 'Ham' line = f'test_basic {text}' @@ -870,7 +871,7 @@ def test_add_opening_quote_basic_text_is_common_prefix(cmd2_app): assert cmd2_app.completion_matches == expected -def test_add_opening_quote_delimited_no_text(cmd2_app): +def test_add_opening_quote_delimited_no_text(cmd2_app) -> None: text = '' line = f'test_delimited {text}' endidx = len(line) @@ -882,7 +883,7 @@ def test_add_opening_quote_delimited_no_text(cmd2_app): assert cmd2_app.completion_matches == sorted(delimited_strs, key=cmd2_app.default_sort_key) -def test_add_opening_quote_delimited_nothing_added(cmd2_app): +def test_add_opening_quote_delimited_nothing_added(cmd2_app) -> None: text = '/ho' line = f'test_delimited {text}' endidx = len(line) @@ -897,7 +898,7 @@ def test_add_opening_quote_delimited_nothing_added(cmd2_app): assert cmd2_app.display_matches == expected_display -def test_add_opening_quote_delimited_quote_added(cmd2_app): +def test_add_opening_quote_delimited_quote_added(cmd2_app) -> None: text = '/home/user/fi' line = f'test_delimited {text}' endidx = len(line) @@ -912,7 +913,7 @@ def test_add_opening_quote_delimited_quote_added(cmd2_app): assert cmd2_app.display_matches == expected_display -def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app): +def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app) -> None: # This tests when the text entered is the same as the common prefix of the matches text = '/home/user/file' line = f'test_delimited {text}' @@ -928,7 +929,7 @@ def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app): assert cmd2_app.display_matches == expected_display -def test_add_opening_quote_delimited_space_in_prefix(cmd2_app): +def test_add_opening_quote_delimited_space_in_prefix(cmd2_app) -> None: # This test when a space appears before the part of the string that is the display match text = '/home/oth' line = f'test_delimited {text}' @@ -944,7 +945,7 @@ def test_add_opening_quote_delimited_space_in_prefix(cmd2_app): assert cmd2_app.display_matches == expected_display -def test_no_completer(cmd2_app): +def test_no_completer(cmd2_app) -> None: text = '' line = f'test_no_completer {text}' endidx = len(line) @@ -956,7 +957,7 @@ def test_no_completer(cmd2_app): assert cmd2_app.completion_matches == expected -def test_wordbreak_in_command(cmd2_app): +def test_wordbreak_in_command(cmd2_app) -> None: text = '' line = f'"{text}' endidx = len(line) @@ -967,7 +968,7 @@ def test_wordbreak_in_command(cmd2_app): assert not cmd2_app.completion_matches -def test_complete_multiline_on_single_line(cmd2_app): +def test_complete_multiline_on_single_line(cmd2_app) -> None: text = '' line = f'test_multiline {text}' endidx = len(line) @@ -980,7 +981,7 @@ def test_complete_multiline_on_single_line(cmd2_app): assert cmd2_app.completion_matches == expected -def test_complete_multiline_on_multiple_lines(cmd2_app): +def test_complete_multiline_on_multiple_lines(cmd2_app) -> None: # Set the same variables _complete_statement() sets when a user is entering data at a continuation prompt cmd2_app._at_continuation_prompt = True cmd2_app._multiline_in_progress = "test_multiline\n" @@ -1032,7 +1033,7 @@ class RedirCompType(enum.Enum): ('fake > file >>', RedirCompType.NONE), ], ) -def test_redirect_complete(cmd2_app, monkeypatch, line, comp_type): +def test_redirect_complete(cmd2_app, monkeypatch, line, comp_type) -> None: # Test both cases of allow_redirection cmd2_app.allow_redirection = True for count in range(2): @@ -1068,7 +1069,7 @@ def test_redirect_complete(cmd2_app, monkeypatch, line, comp_type): comp_type = RedirCompType.NONE -def test_complete_set_value(cmd2_app): +def test_complete_set_value(cmd2_app) -> None: text = '' line = f'set foo {text}' endidx = len(line) @@ -1079,7 +1080,7 @@ def test_complete_set_value(cmd2_app): assert cmd2_app.completion_hint == "Hint:\n value a settable param\n" -def test_complete_set_value_invalid_settable(cmd2_app, capsys): +def test_complete_set_value_invalid_settable(cmd2_app, capsys) -> None: text = '' line = f'set fake {text}' endidx = len(line) @@ -1099,7 +1100,7 @@ def sc_app(): return c -def test_cmd2_subcommand_completion_single_end(sc_app): +def test_cmd2_subcommand_completion_single_end(sc_app) -> None: text = 'f' line = f'base {text}' endidx = len(line) @@ -1112,7 +1113,7 @@ def test_cmd2_subcommand_completion_single_end(sc_app): assert sc_app.completion_matches == ['foo '] -def test_cmd2_subcommand_completion_multiple(sc_app): +def test_cmd2_subcommand_completion_multiple(sc_app) -> None: text = '' line = f'base {text}' endidx = len(line) @@ -1123,7 +1124,7 @@ def test_cmd2_subcommand_completion_multiple(sc_app): assert sc_app.completion_matches == ['bar', 'foo', 'sport'] -def test_cmd2_subcommand_completion_nomatch(sc_app): +def test_cmd2_subcommand_completion_nomatch(sc_app) -> None: text = 'z' line = f'base {text}' endidx = len(line) @@ -1133,7 +1134,7 @@ def test_cmd2_subcommand_completion_nomatch(sc_app): assert first_match is None -def test_help_subcommand_completion_single(sc_app): +def test_help_subcommand_completion_single(sc_app) -> None: text = 'base' line = f'help {text}' endidx = len(line) @@ -1146,7 +1147,7 @@ def test_help_subcommand_completion_single(sc_app): assert sc_app.completion_matches == ['base '] -def test_help_subcommand_completion_multiple(sc_app): +def test_help_subcommand_completion_multiple(sc_app) -> None: text = '' line = f'help base {text}' endidx = len(line) @@ -1157,7 +1158,7 @@ def test_help_subcommand_completion_multiple(sc_app): assert sc_app.completion_matches == ['bar', 'foo', 'sport'] -def test_help_subcommand_completion_nomatch(sc_app): +def test_help_subcommand_completion_nomatch(sc_app) -> None: text = 'z' line = f'help base {text}' endidx = len(line) @@ -1167,7 +1168,7 @@ def test_help_subcommand_completion_nomatch(sc_app): assert first_match is None -def test_subcommand_tab_completion(sc_app): +def test_subcommand_tab_completion(sc_app) -> None: # This makes sure the correct completer for the sport subcommand is called text = 'Foot' line = f'base sport {text}' @@ -1181,7 +1182,7 @@ def test_subcommand_tab_completion(sc_app): assert sc_app.completion_matches == ['Football '] -def test_subcommand_tab_completion_with_no_completer(sc_app): +def test_subcommand_tab_completion_with_no_completer(sc_app) -> None: # This tests what happens when a subcommand has no completer # In this case, the foo subcommand has no completer defined text = 'Foot' @@ -1193,7 +1194,7 @@ def test_subcommand_tab_completion_with_no_completer(sc_app): assert first_match is None -def test_subcommand_tab_completion_space_in_text(sc_app): +def test_subcommand_tab_completion_space_in_text(sc_app) -> None: text = 'B' line = f'base sport "Space {text}' endidx = len(line) @@ -1215,19 +1216,19 @@ class SubcommandsWithUnknownExample(cmd2.Cmd): and the "sport" subcommand has tab completion enabled. """ - def __init__(self): + def __init__(self) -> None: cmd2.Cmd.__init__(self) # subcommand functions for the base command - def base_foo(self, args): + def base_foo(self, args) -> None: """foo subcommand of base command""" self.poutput(args.x * args.y) - def base_bar(self, args): + def base_bar(self, args) -> None: """bar subcommand of base command""" self.poutput(f'(({args.z}))') - def base_sport(self, args): + def base_sport(self, args) -> None: """sport subcommand of base command""" self.poutput(f'Sport is {args.sport}') @@ -1251,7 +1252,7 @@ def base_sport(self, args): sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport', choices=sport_item_strs) @cmd2.with_argparser(base_parser, with_unknown_args=True) - def do_base(self, args): + def do_base(self, args) -> None: """Base command help""" func = getattr(args, 'func', None) if func is not None: @@ -1269,7 +1270,7 @@ def scu_app(): return app -def test_subcmd_with_unknown_completion_single_end(scu_app): +def test_subcmd_with_unknown_completion_single_end(scu_app) -> None: text = 'f' line = f'base {text}' endidx = len(line) @@ -1284,7 +1285,7 @@ def test_subcmd_with_unknown_completion_single_end(scu_app): assert scu_app.completion_matches == ['foo '] -def test_subcmd_with_unknown_completion_multiple(scu_app): +def test_subcmd_with_unknown_completion_multiple(scu_app) -> None: text = '' line = f'base {text}' endidx = len(line) @@ -1295,7 +1296,7 @@ def test_subcmd_with_unknown_completion_multiple(scu_app): assert scu_app.completion_matches == ['bar', 'foo', 'sport'] -def test_subcmd_with_unknown_completion_nomatch(scu_app): +def test_subcmd_with_unknown_completion_nomatch(scu_app) -> None: text = 'z' line = f'base {text}' endidx = len(line) @@ -1305,7 +1306,7 @@ def test_subcmd_with_unknown_completion_nomatch(scu_app): assert first_match is None -def test_help_subcommand_completion_single_scu(scu_app): +def test_help_subcommand_completion_single_scu(scu_app) -> None: text = 'base' line = f'help {text}' endidx = len(line) @@ -1318,7 +1319,7 @@ def test_help_subcommand_completion_single_scu(scu_app): assert scu_app.completion_matches == ['base '] -def test_help_subcommand_completion_multiple_scu(scu_app): +def test_help_subcommand_completion_multiple_scu(scu_app) -> None: text = '' line = f'help base {text}' endidx = len(line) @@ -1329,7 +1330,7 @@ def test_help_subcommand_completion_multiple_scu(scu_app): assert scu_app.completion_matches == ['bar', 'foo', 'sport'] -def test_help_subcommand_completion_with_flags_before_command(scu_app): +def test_help_subcommand_completion_with_flags_before_command(scu_app) -> None: text = '' line = f'help -h -v base {text}' endidx = len(line) @@ -1340,7 +1341,7 @@ def test_help_subcommand_completion_with_flags_before_command(scu_app): assert scu_app.completion_matches == ['bar', 'foo', 'sport'] -def test_complete_help_subcommands_with_blank_command(scu_app): +def test_complete_help_subcommands_with_blank_command(scu_app) -> None: text = '' line = f'help "" {text}' endidx = len(line) @@ -1351,7 +1352,7 @@ def test_complete_help_subcommands_with_blank_command(scu_app): assert not scu_app.completion_matches -def test_help_subcommand_completion_nomatch_scu(scu_app): +def test_help_subcommand_completion_nomatch_scu(scu_app) -> None: text = 'z' line = f'help base {text}' endidx = len(line) @@ -1361,7 +1362,7 @@ def test_help_subcommand_completion_nomatch_scu(scu_app): assert first_match is None -def test_subcommand_tab_completion_scu(scu_app): +def test_subcommand_tab_completion_scu(scu_app) -> None: # This makes sure the correct completer for the sport subcommand is called text = 'Foot' line = f'base sport {text}' @@ -1375,7 +1376,7 @@ def test_subcommand_tab_completion_scu(scu_app): assert scu_app.completion_matches == ['Football '] -def test_subcommand_tab_completion_with_no_completer_scu(scu_app): +def test_subcommand_tab_completion_with_no_completer_scu(scu_app) -> None: # This tests what happens when a subcommand has no completer # In this case, the foo subcommand has no completer defined text = 'Foot' @@ -1387,7 +1388,7 @@ def test_subcommand_tab_completion_with_no_completer_scu(scu_app): assert first_match is None -def test_subcommand_tab_completion_space_in_text_scu(scu_app): +def test_subcommand_tab_completion_space_in_text_scu(scu_app) -> None: text = 'B' line = f'base sport "Space {text}' endidx = len(line) diff --git a/tests/test_history.py b/tests/test_history.py index 012c23c7d..3db295af5 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -32,7 +32,7 @@ def verify_hi_last_result(app: cmd2.Cmd, expected_length: int) -> None: # # readline tests # -def test_readline_remove_history_item(): +def test_readline_remove_history_item() -> None: from cmd2.rl_utils import ( readline, ) @@ -159,7 +159,7 @@ def persisted_hist(): return h -def test_history_class_span(hist): +def test_history_class_span(hist) -> None: span = hist.span('2..') assert len(span) == 3 assert span[2].statement.raw == 'second' @@ -231,7 +231,7 @@ def test_history_class_span(hist): hist.span(tryit) -def test_persisted_history_span(persisted_hist): +def test_persisted_history_span(persisted_hist) -> None: span = persisted_hist.span('2..') assert len(span) == 5 assert span[2].statement.raw == 'second' @@ -282,7 +282,7 @@ def test_persisted_history_span(persisted_hist): persisted_hist.span(tryit) -def test_history_class_get(hist): +def test_history_class_get(hist) -> None: assert hist.get(1).statement.raw == 'first' assert hist.get(3).statement.raw == 'third' assert hist.get(-2) == hist[-2] @@ -295,7 +295,7 @@ def test_history_class_get(hist): hist.get(5) -def test_history_str_search(hist): +def test_history_str_search(hist) -> None: items = hist.str_search('ir') assert len(items) == 2 assert items[1].statement.raw == 'first' @@ -306,7 +306,7 @@ def test_history_str_search(hist): assert items[4].statement.raw == 'fourth' -def test_history_regex_search(hist): +def test_history_regex_search(hist) -> None: items = hist.regex_search('/i.*d/') assert len(items) == 1 assert items[3].statement.raw == 'third' @@ -316,28 +316,28 @@ def test_history_regex_search(hist): assert items[2].statement.raw == 'second' -def test_history_max_length_zero(hist): +def test_history_max_length_zero(hist) -> None: hist.truncate(0) assert len(hist) == 0 -def test_history_max_length_negative(hist): +def test_history_max_length_negative(hist) -> None: hist.truncate(-1) assert len(hist) == 0 -def test_history_max_length(hist): +def test_history_max_length(hist) -> None: hist.truncate(2) assert len(hist) == 2 assert hist.get(1).statement.raw == 'third' assert hist.get(2).statement.raw == 'fourth' -def test_history_to_json(hist): +def test_history_to_json(hist) -> None: assert hist_json == hist.to_json() -def test_history_from_json(hist): +def test_history_from_json(hist) -> None: import json from cmd2.history import ( @@ -408,7 +408,7 @@ def parser(): return parser -def test_multiline_histitem(parser): +def test_multiline_histitem(parser) -> None: from cmd2.history import ( History, ) @@ -424,7 +424,7 @@ def test_multiline_histitem(parser): assert pr_lines[0].endswith('multiline foo bar') -def test_multiline_with_quotes_histitem(parser): +def test_multiline_with_quotes_histitem(parser) -> None: # Test that spaces and newlines in quotes are preserved from cmd2.history import ( History, @@ -445,7 +445,7 @@ def test_multiline_with_quotes_histitem(parser): assert pr_lines[2] == ' " in quotes.;' -def test_multiline_histitem_verbose(parser): +def test_multiline_histitem_verbose(parser) -> None: from cmd2.history import ( History, ) @@ -462,7 +462,7 @@ def test_multiline_histitem_verbose(parser): assert pr_lines[1] == 'bar' -def test_single_line_format_blank(parser): +def test_single_line_format_blank(parser) -> None: from cmd2.history import ( single_line_format, ) @@ -472,7 +472,7 @@ def test_single_line_format_blank(parser): assert single_line_format(statement) == line -def test_history_item_instantiate(): +def test_history_item_instantiate() -> None: from cmd2.history import ( HistoryItem, ) @@ -490,7 +490,7 @@ def test_history_item_instantiate(): _ = HistoryItem() -def test_history_item_properties(histitem): +def test_history_item_properties(histitem) -> None: assert histitem.raw == 'help history' assert histitem.expanded == 'help history' assert str(histitem) == 'help history' @@ -499,7 +499,7 @@ def test_history_item_properties(histitem): # # test history command # -def test_base_history(base_app): +def test_base_history(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') out, err = run_cmd(base_app, 'history') @@ -530,7 +530,7 @@ def test_base_history(base_app): verify_hi_last_result(base_app, 1) -def test_history_script_format(base_app): +def test_history_script_format(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') out, err = run_cmd(base_app, 'history -s') @@ -544,7 +544,7 @@ def test_history_script_format(base_app): verify_hi_last_result(base_app, 2) -def test_history_with_string_argument(base_app): +def test_history_with_string_argument(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') @@ -559,7 +559,7 @@ def test_history_with_string_argument(base_app): verify_hi_last_result(base_app, 2) -def test_history_expanded_with_string_argument(base_app): +def test_history_expanded_with_string_argument(base_app) -> None: run_cmd(base_app, 'alias create sc shortcuts') run_cmd(base_app, 'help') run_cmd(base_app, 'help history') @@ -576,7 +576,7 @@ def test_history_expanded_with_string_argument(base_app): verify_hi_last_result(base_app, 2) -def test_history_expanded_with_regex_argument(base_app): +def test_history_expanded_with_regex_argument(base_app) -> None: run_cmd(base_app, 'alias create sc shortcuts') run_cmd(base_app, 'help') run_cmd(base_app, 'help history') @@ -593,7 +593,7 @@ def test_history_expanded_with_regex_argument(base_app): verify_hi_last_result(base_app, 2) -def test_history_with_integer_argument(base_app): +def test_history_with_integer_argument(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') out, err = run_cmd(base_app, 'history 1') @@ -606,7 +606,7 @@ def test_history_with_integer_argument(base_app): verify_hi_last_result(base_app, 1) -def test_history_with_integer_span(base_app): +def test_history_with_integer_span(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') @@ -621,7 +621,7 @@ def test_history_with_integer_span(base_app): verify_hi_last_result(base_app, 2) -def test_history_with_span_start(base_app): +def test_history_with_span_start(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') @@ -636,7 +636,7 @@ def test_history_with_span_start(base_app): verify_hi_last_result(base_app, 2) -def test_history_with_span_end(base_app): +def test_history_with_span_end(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') @@ -651,7 +651,7 @@ def test_history_with_span_end(base_app): verify_hi_last_result(base_app, 2) -def test_history_with_span_index_error(base_app): +def test_history_with_span_index_error(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'help history') run_cmd(base_app, '!ls -hal :') @@ -660,7 +660,7 @@ def test_history_with_span_index_error(base_app): base_app.onecmd('history "hal :"') -def test_history_output_file(): +def test_history_output_file() -> None: app = cmd2.Cmd(multiline_commands=['alias']) run_cmd(app, 'help') run_cmd(app, 'shortcuts') @@ -678,7 +678,7 @@ def test_history_output_file(): assert content == expected -def test_history_bad_output_file(base_app): +def test_history_bad_output_file(base_app) -> None: run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') @@ -691,7 +691,7 @@ def test_history_bad_output_file(base_app): assert base_app.last_result is False -def test_history_edit(monkeypatch): +def test_history_edit(monkeypatch) -> None: app = cmd2.Cmd(multiline_commands=['alias']) # Set a fake editor just to make sure we have one. We aren't really @@ -717,7 +717,7 @@ def test_history_edit(monkeypatch): run_script_mock.assert_called_once() -def test_history_run_all_commands(base_app): +def test_history_run_all_commands(base_app) -> None: # make sure we refuse to run all commands as a default run_cmd(base_app, 'shortcuts') out, err = run_cmd(base_app, 'history -r') @@ -727,14 +727,14 @@ def test_history_run_all_commands(base_app): assert base_app.last_result is False -def test_history_run_one_command(base_app): +def test_history_run_one_command(base_app) -> None: out1, err1 = run_cmd(base_app, 'help') out2, err2 = run_cmd(base_app, 'history -r 1') assert out1 == out2 assert base_app.last_result is True -def test_history_clear(mocker, hist_file): +def test_history_clear(mocker, hist_file) -> None: # Add commands to history app = cmd2.Cmd(persistent_history_file=hist_file) run_cmd(app, 'help') @@ -769,7 +769,7 @@ def test_history_clear(mocker, hist_file): assert app.last_result is False -def test_history_verbose_with_other_options(base_app): +def test_history_verbose_with_other_options(base_app) -> None: # make sure -v shows a usage error if any other options are present options_to_test = ['-r', '-e', '-o file', '-t file', '-c', '-x'] for opt in options_to_test: @@ -780,7 +780,7 @@ def test_history_verbose_with_other_options(base_app): assert base_app.last_result is False -def test_history_verbose(base_app): +def test_history_verbose(base_app) -> None: # validate function of -v option run_cmd(base_app, 'alias create s shortcuts') run_cmd(base_app, 's') @@ -797,7 +797,7 @@ def test_history_verbose(base_app): verify_hi_last_result(base_app, 2) -def test_history_script_with_invalid_options(base_app): +def test_history_script_with_invalid_options(base_app) -> None: # make sure -s shows a usage error if -c, -r, -e, -o, or -t are present options_to_test = ['-r', '-e', '-o file', '-t file', '-c'] for opt in options_to_test: @@ -808,7 +808,7 @@ def test_history_script_with_invalid_options(base_app): assert base_app.last_result is False -def test_history_script(base_app): +def test_history_script(base_app) -> None: cmds = ['alias create s shortcuts', 's'] for cmd in cmds: run_cmd(base_app, cmd) @@ -817,7 +817,7 @@ def test_history_script(base_app): verify_hi_last_result(base_app, 2) -def test_history_expanded_with_invalid_options(base_app): +def test_history_expanded_with_invalid_options(base_app) -> None: # make sure -x shows a usage error if -c, -r, -e, -o, or -t are present options_to_test = ['-r', '-e', '-o file', '-t file', '-c'] for opt in options_to_test: @@ -828,7 +828,7 @@ def test_history_expanded_with_invalid_options(base_app): assert base_app.last_result is False -def test_history_expanded(base_app): +def test_history_expanded(base_app) -> None: # validate function of -x option cmds = ['alias create s shortcuts', 's'] for cmd in cmds: @@ -839,7 +839,7 @@ def test_history_expanded(base_app): verify_hi_last_result(base_app, 2) -def test_history_script_expanded(base_app): +def test_history_script_expanded(base_app) -> None: # validate function of -s -x options together cmds = ['alias create s shortcuts', 's'] for cmd in cmds: @@ -850,12 +850,12 @@ def test_history_script_expanded(base_app): verify_hi_last_result(base_app, 2) -def test_base_help_history(base_app): +def test_base_help_history(base_app) -> None: out, err = run_cmd(base_app, 'help history') assert out == normalize(HELP_HISTORY) -def test_exclude_from_history(base_app): +def test_exclude_from_history(base_app) -> None: # Run history command run_cmd(base_app, 'history') verify_hi_last_result(base_app, 0) @@ -890,7 +890,7 @@ def hist_file(): pass -def test_history_file_is_directory(capsys): +def test_history_file_is_directory(capsys) -> None: with tempfile.TemporaryDirectory() as test_dir: # Create a new cmd2 app cmd2.Cmd(persistent_history_file=test_dir) @@ -898,7 +898,7 @@ def test_history_file_is_directory(capsys): assert 'is a directory' in err -def test_history_can_create_directory(mocker): +def test_history_can_create_directory(mocker) -> None: # Mock out atexit.register so the persistent file doesn't written when this function # exists because we will be deleting the directory it needs to go to. mocker.patch('atexit.register') @@ -920,7 +920,7 @@ def test_history_can_create_directory(mocker): os.rmdir(hist_file_dir) -def test_history_cannot_create_directory(mocker, capsys): +def test_history_cannot_create_directory(mocker, capsys) -> None: mock_open = mocker.patch('os.makedirs') mock_open.side_effect = OSError @@ -930,7 +930,7 @@ def test_history_cannot_create_directory(mocker, capsys): assert 'Error creating persistent history file directory' in err -def test_history_file_permission_error(mocker, capsys): +def test_history_file_permission_error(mocker, capsys) -> None: mock_open = mocker.patch('builtins.open') mock_open.side_effect = PermissionError @@ -940,7 +940,7 @@ def test_history_file_permission_error(mocker, capsys): assert 'Cannot read persistent history file' in err -def test_history_file_bad_compression(mocker, capsys): +def test_history_file_bad_compression(mocker, capsys) -> None: history_file = '/tmp/doesntmatter' with open(history_file, "wb") as f: f.write(b"THIS IS NOT COMPRESSED DATA") @@ -951,7 +951,7 @@ def test_history_file_bad_compression(mocker, capsys): assert 'Error decompressing persistent history data' in err -def test_history_file_bad_json(mocker, capsys): +def test_history_file_bad_json(mocker, capsys) -> None: import lzma data = b"THIS IS NOT JSON" @@ -967,7 +967,7 @@ def test_history_file_bad_json(mocker, capsys): assert 'Error processing persistent history data' in err -def test_history_populates_readline(hist_file): +def test_history_populates_readline(hist_file) -> None: # - create a cmd2 with persistent history app = cmd2.Cmd(persistent_history_file=hist_file) run_cmd(app, 'help') @@ -1004,7 +1004,7 @@ def test_history_populates_readline(hist_file): # we assume that the atexit module will call this method # properly # -def test_persist_history_ensure_no_error_if_no_histfile(base_app, capsys): +def test_persist_history_ensure_no_error_if_no_histfile(base_app, capsys) -> None: # make sure if there is no persistent history file and someone # calls the private method call that we don't get an error base_app._persist_history() @@ -1013,7 +1013,7 @@ def test_persist_history_ensure_no_error_if_no_histfile(base_app, capsys): assert not err -def test_persist_history_permission_error(hist_file, mocker, capsys): +def test_persist_history_permission_error(hist_file, mocker, capsys) -> None: app = cmd2.Cmd(persistent_history_file=hist_file) run_cmd(app, 'help') mock_open = mocker.patch('builtins.open') diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 812abb872..250811cbb 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -42,7 +42,7 @@ def default_parser(): return parser -def test_parse_empty_string(parser): +def test_parse_empty_string(parser) -> None: line = '' statement = parser.parse(line) assert statement == '' @@ -60,7 +60,7 @@ def test_parse_empty_string(parser): assert statement.argv == statement.arg_list -def test_parse_empty_string_default(default_parser): +def test_parse_empty_string_default(default_parser) -> None: line = '' statement = default_parser.parse(line) assert statement == '' @@ -91,7 +91,7 @@ def test_parse_empty_string_default(default_parser): ('help|less', ['help', '|', 'less']), ], ) -def test_tokenize_default(default_parser, line, tokens): +def test_tokenize_default(default_parser, line, tokens) -> None: tokens_to_test = default_parser.tokenize(line) assert tokens_to_test == tokens @@ -112,12 +112,12 @@ def test_tokenize_default(default_parser, line, tokens): ('l|less', ['shell', 'ls', '-al', '|', 'less']), ], ) -def test_tokenize(parser, line, tokens): +def test_tokenize(parser, line, tokens) -> None: tokens_to_test = parser.tokenize(line) assert tokens_to_test == tokens -def test_tokenize_unclosed_quotes(parser): +def test_tokenize_unclosed_quotes(parser) -> None: with pytest.raises(exceptions.Cmd2ShlexError): _ = parser.tokenize('command with "unclosed quotes') @@ -126,7 +126,7 @@ def test_tokenize_unclosed_quotes(parser): ('tokens', 'command', 'args'), [([], '', ''), (['command'], 'command', ''), (['command', 'arg1', 'arg2'], 'command', 'arg1 arg2')], ) -def test_command_and_args(parser, tokens, command, args): +def test_command_and_args(parser, tokens, command, args) -> None: (parsed_command, parsed_args) = parser._command_and_args(tokens) assert command == parsed_command assert args == parsed_args @@ -140,7 +140,7 @@ def test_command_and_args(parser, tokens, command, args): "'one word'", ], ) -def test_parse_single_word(parser, line): +def test_parse_single_word(parser, line) -> None: statement = parser.parse(line) assert statement.command == line assert statement == '' @@ -166,7 +166,7 @@ def test_parse_single_word(parser, line): ('termbare &', '&'), ], ) -def test_parse_word_plus_terminator(parser, line, terminator): +def test_parse_word_plus_terminator(parser, line, terminator) -> None: statement = parser.parse(line) assert statement.command == 'termbare' assert statement == '' @@ -185,7 +185,7 @@ def test_parse_word_plus_terminator(parser, line, terminator): ('termbare &suffx', '&'), ], ) -def test_parse_suffix_after_terminator(parser, line, terminator): +def test_parse_suffix_after_terminator(parser, line, terminator) -> None: statement = parser.parse(line) assert statement.command == 'termbare' assert statement == '' @@ -197,7 +197,7 @@ def test_parse_suffix_after_terminator(parser, line, terminator): assert statement.expanded_command_line == statement.command + statement.terminator + ' ' + statement.suffix -def test_parse_command_with_args(parser): +def test_parse_command_with_args(parser) -> None: line = 'command with args' statement = parser.parse(line) assert statement.command == 'command' @@ -207,7 +207,7 @@ def test_parse_command_with_args(parser): assert statement.arg_list == statement.argv[1:] -def test_parse_command_with_quoted_args(parser): +def test_parse_command_with_quoted_args(parser) -> None: line = 'command with "quoted args" and "some not"' statement = parser.parse(line) assert statement.command == 'command' @@ -217,7 +217,7 @@ def test_parse_command_with_quoted_args(parser): assert statement.arg_list == ['with', '"quoted args"', 'and', '"some not"'] -def test_parse_command_with_args_terminator_and_suffix(parser): +def test_parse_command_with_args_terminator_and_suffix(parser) -> None: line = 'command with args and terminator; and suffix' statement = parser.parse(line) assert statement.command == 'command' @@ -229,7 +229,7 @@ def test_parse_command_with_args_terminator_and_suffix(parser): assert statement.suffix == 'and suffix' -def test_parse_comment(parser): +def test_parse_comment(parser) -> None: statement = parser.parse(constants.COMMENT_CHAR + ' this is all a comment') assert statement.command == '' assert statement == '' @@ -238,7 +238,7 @@ def test_parse_comment(parser): assert not statement.arg_list -def test_parse_embedded_comment_char(parser): +def test_parse_embedded_comment_char(parser) -> None: command_str = 'hi ' + constants.COMMENT_CHAR + ' not a comment' statement = parser.parse(command_str) assert statement.command == 'hi' @@ -255,7 +255,7 @@ def test_parse_embedded_comment_char(parser): 'simple|piped', ], ) -def test_parse_simple_pipe(parser, line): +def test_parse_simple_pipe(parser, line) -> None: statement = parser.parse(line) assert statement.command == 'simple' assert statement == '' @@ -266,7 +266,7 @@ def test_parse_simple_pipe(parser, line): assert statement.expanded_command_line == statement.command + ' | ' + statement.pipe_to -def test_parse_double_pipe_is_not_a_pipe(parser): +def test_parse_double_pipe_is_not_a_pipe(parser) -> None: line = 'double-pipe || is not a pipe' statement = parser.parse(line) assert statement.command == 'double-pipe' @@ -277,7 +277,7 @@ def test_parse_double_pipe_is_not_a_pipe(parser): assert not statement.pipe_to -def test_parse_complex_pipe(parser): +def test_parse_complex_pipe(parser) -> None: line = 'command with args, terminator&sufx | piped' statement = parser.parse(line) assert statement.command == 'command' @@ -299,7 +299,7 @@ def test_parse_complex_pipe(parser): ('help>>out.txt', '>>'), ], ) -def test_parse_redirect(parser, line, output): +def test_parse_redirect(parser, line, output) -> None: statement = parser.parse(line) assert statement.command == 'help' assert statement == '' @@ -316,7 +316,7 @@ def test_parse_redirect(parser, line, output): 'python-cmd2/afile.txt', ], ) # without dashes # with dashes in path -def test_parse_redirect_with_args(parser, dest): +def test_parse_redirect_with_args(parser, dest) -> None: line = f'output into > {dest}' statement = parser.parse(line) assert statement.command == 'output' @@ -328,7 +328,7 @@ def test_parse_redirect_with_args(parser, dest): assert statement.output_to == dest -def test_parse_redirect_append(parser): +def test_parse_redirect_append(parser) -> None: line = 'output appended to >> /tmp/afile.txt' statement = parser.parse(line) assert statement.command == 'output' @@ -340,7 +340,7 @@ def test_parse_redirect_append(parser): assert statement.output_to == '/tmp/afile.txt' -def test_parse_pipe_then_redirect(parser): +def test_parse_pipe_then_redirect(parser) -> None: line = 'output into;sufx | pipethrume plz > afile.txt' statement = parser.parse(line) assert statement.command == 'output' @@ -355,7 +355,7 @@ def test_parse_pipe_then_redirect(parser): assert statement.output_to == '' -def test_parse_multiple_pipes(parser): +def test_parse_multiple_pipes(parser) -> None: line = 'output into;sufx | pipethrume plz | grep blah' statement = parser.parse(line) assert statement.command == 'output' @@ -370,7 +370,7 @@ def test_parse_multiple_pipes(parser): assert statement.output_to == '' -def test_redirect_then_pipe(parser): +def test_redirect_then_pipe(parser) -> None: line = 'help alias > file.txt | grep blah' statement = parser.parse(line) assert statement.command == 'help' @@ -385,7 +385,7 @@ def test_redirect_then_pipe(parser): assert statement.output_to == 'file.txt' -def test_append_then_pipe(parser): +def test_append_then_pipe(parser) -> None: line = 'help alias >> file.txt | grep blah' statement = parser.parse(line) assert statement.command == 'help' @@ -400,7 +400,7 @@ def test_append_then_pipe(parser): assert statement.output_to == 'file.txt' -def test_append_then_redirect(parser): +def test_append_then_redirect(parser) -> None: line = 'help alias >> file.txt > file2.txt' statement = parser.parse(line) assert statement.command == 'help' @@ -415,7 +415,7 @@ def test_append_then_redirect(parser): assert statement.output_to == 'file.txt' -def test_redirect_then_append(parser): +def test_redirect_then_append(parser) -> None: line = 'help alias > file.txt >> file2.txt' statement = parser.parse(line) assert statement.command == 'help' @@ -430,7 +430,7 @@ def test_redirect_then_append(parser): assert statement.output_to == 'file.txt' -def test_redirect_to_quoted_string(parser): +def test_redirect_to_quoted_string(parser) -> None: line = 'help alias > "file.txt"' statement = parser.parse(line) assert statement.command == 'help' @@ -445,7 +445,7 @@ def test_redirect_to_quoted_string(parser): assert statement.output_to == '"file.txt"' -def test_redirect_to_single_quoted_string(parser): +def test_redirect_to_single_quoted_string(parser) -> None: line = "help alias > 'file.txt'" statement = parser.parse(line) assert statement.command == 'help' @@ -460,7 +460,7 @@ def test_redirect_to_single_quoted_string(parser): assert statement.output_to == "'file.txt'" -def test_redirect_to_empty_quoted_string(parser): +def test_redirect_to_empty_quoted_string(parser) -> None: line = 'help alias > ""' statement = parser.parse(line) assert statement.command == 'help' @@ -475,7 +475,7 @@ def test_redirect_to_empty_quoted_string(parser): assert statement.output_to == '' -def test_redirect_to_empty_single_quoted_string(parser): +def test_redirect_to_empty_single_quoted_string(parser) -> None: line = "help alias > ''" statement = parser.parse(line) assert statement.command == 'help' @@ -490,7 +490,7 @@ def test_redirect_to_empty_single_quoted_string(parser): assert statement.output_to == '' -def test_parse_output_to_paste_buffer(parser): +def test_parse_output_to_paste_buffer(parser) -> None: line = 'output to paste buffer >> ' statement = parser.parse(line) assert statement.command == 'output' @@ -501,7 +501,7 @@ def test_parse_output_to_paste_buffer(parser): assert statement.output == '>>' -def test_parse_redirect_inside_terminator(parser): +def test_parse_redirect_inside_terminator(parser) -> None: """The terminator designates the end of the command/arguments portion. If a redirector occurs before a terminator, then it will be treated as part of the arguments and not as a redirector.""" @@ -528,7 +528,7 @@ def test_parse_redirect_inside_terminator(parser): ('multiline with | inside &; &;', '&'), ], ) -def test_parse_multiple_terminators(parser, line, terminator): +def test_parse_multiple_terminators(parser, line, terminator) -> None: statement = parser.parse(line) assert statement.multiline_command == 'multiline' assert statement == 'with | inside' @@ -538,7 +538,7 @@ def test_parse_multiple_terminators(parser, line, terminator): assert statement.terminator == terminator -def test_parse_unfinished_multiliine_command(parser): +def test_parse_unfinished_multiliine_command(parser) -> None: line = 'multiline has > inside an unfinished command' statement = parser.parse(line) assert statement.multiline_command == 'multiline' @@ -550,7 +550,7 @@ def test_parse_unfinished_multiliine_command(parser): assert statement.terminator == '' -def test_parse_basic_multiline_command(parser): +def test_parse_basic_multiline_command(parser) -> None: line = 'multiline foo\nbar\n\n' statement = parser.parse(line) assert statement.multiline_command == 'multiline' @@ -573,7 +573,7 @@ def test_parse_basic_multiline_command(parser): ('multiline has > inside & &', '&'), ], ) -def test_parse_multiline_command_ignores_redirectors_within_it(parser, line, terminator): +def test_parse_multiline_command_ignores_redirectors_within_it(parser, line, terminator) -> None: statement = parser.parse(line) assert statement.multiline_command == 'multiline' assert statement == 'has > inside' @@ -583,7 +583,7 @@ def test_parse_multiline_command_ignores_redirectors_within_it(parser, line, ter assert statement.terminator == terminator -def test_parse_multiline_terminated_by_empty_line(parser): +def test_parse_multiline_terminated_by_empty_line(parser) -> None: line = 'multiline command ends\n\n' statement = parser.parse(line) assert statement.multiline_command == 'multiline' @@ -606,7 +606,7 @@ def test_parse_multiline_terminated_by_empty_line(parser): ('multiline command "with\nembedded newline"\n\n', '\n'), ], ) -def test_parse_multiline_with_embedded_newline(parser, line, terminator): +def test_parse_multiline_with_embedded_newline(parser, line, terminator) -> None: statement = parser.parse(line) assert statement.multiline_command == 'multiline' assert statement.command == 'multiline' @@ -617,7 +617,7 @@ def test_parse_multiline_with_embedded_newline(parser, line, terminator): assert statement.terminator == terminator -def test_parse_multiline_ignores_terminators_in_quotes(parser): +def test_parse_multiline_ignores_terminators_in_quotes(parser) -> None: line = 'multiline command "with term; ends" now\n\n' statement = parser.parse(line) assert statement.multiline_command == 'multiline' @@ -629,7 +629,7 @@ def test_parse_multiline_ignores_terminators_in_quotes(parser): assert statement.terminator == '\n' -def test_parse_command_with_unicode_args(parser): +def test_parse_command_with_unicode_args(parser) -> None: line = 'drink café' statement = parser.parse(line) assert statement.command == 'drink' @@ -639,7 +639,7 @@ def test_parse_command_with_unicode_args(parser): assert statement.arg_list == statement.argv[1:] -def test_parse_unicode_command(parser): +def test_parse_unicode_command(parser) -> None: line = 'café au lait' statement = parser.parse(line) assert statement.command == 'café' @@ -649,7 +649,7 @@ def test_parse_unicode_command(parser): assert statement.arg_list == statement.argv[1:] -def test_parse_redirect_to_unicode_filename(parser): +def test_parse_redirect_to_unicode_filename(parser) -> None: line = 'dir home > café' statement = parser.parse(line) assert statement.command == 'dir' @@ -661,12 +661,12 @@ def test_parse_redirect_to_unicode_filename(parser): assert statement.output_to == 'café' -def test_parse_unclosed_quotes(parser): +def test_parse_unclosed_quotes(parser) -> None: with pytest.raises(exceptions.Cmd2ShlexError): _ = parser.tokenize("command with 'unclosed quotes") -def test_empty_statement_raises_exception(): +def test_empty_statement_raises_exception() -> None: app = cmd2.Cmd() with pytest.raises(exceptions.EmptyStatement): app._complete_statement('') @@ -687,14 +687,14 @@ def test_empty_statement_raises_exception(): ('l', 'shell', 'ls -al'), ], ) -def test_parse_alias_and_shortcut_expansion(parser, line, command, args): +def test_parse_alias_and_shortcut_expansion(parser, line, command, args) -> None: statement = parser.parse(line) assert statement.command == command assert statement == args assert statement.args == statement -def test_parse_alias_on_multiline_command(parser): +def test_parse_alias_on_multiline_command(parser) -> None: line = 'anothermultiline has > inside an unfinished command' statement = parser.parse(line) assert statement.multiline_command == 'multiline' @@ -713,7 +713,7 @@ def test_parse_alias_on_multiline_command(parser): ('helpalias>>out.txt', '>>'), ], ) -def test_parse_alias_redirection(parser, line, output): +def test_parse_alias_redirection(parser, line, output) -> None: statement = parser.parse(line) assert statement.command == 'help' assert statement == '' @@ -729,7 +729,7 @@ def test_parse_alias_redirection(parser, line, output): 'helpalias|less', ], ) -def test_parse_alias_pipe(parser, line): +def test_parse_alias_pipe(parser, line) -> None: statement = parser.parse(line) assert statement.command == 'help' assert statement == '' @@ -748,7 +748,7 @@ def test_parse_alias_pipe(parser, line): 'helpalias ;; ;', ], ) -def test_parse_alias_terminator_no_whitespace(parser, line): +def test_parse_alias_terminator_no_whitespace(parser, line) -> None: statement = parser.parse(line) assert statement.command == 'help' assert statement == '' @@ -756,7 +756,7 @@ def test_parse_alias_terminator_no_whitespace(parser, line): assert statement.terminator == ';' -def test_parse_command_only_command_and_args(parser): +def test_parse_command_only_command_and_args(parser) -> None: line = 'help history' statement = parser.parse_command_only(line) assert statement == 'history' @@ -773,7 +773,7 @@ def test_parse_command_only_command_and_args(parser): assert statement.output_to == '' -def test_parse_command_only_strips_line(parser): +def test_parse_command_only_strips_line(parser) -> None: line = ' help history ' statement = parser.parse_command_only(line) assert statement == 'history' @@ -790,7 +790,7 @@ def test_parse_command_only_strips_line(parser): assert statement.output_to == '' -def test_parse_command_only_expands_alias(parser): +def test_parse_command_only_expands_alias(parser) -> None: line = 'fake foobar.py "somebody.py' statement = parser.parse_command_only(line) assert statement == 'foobar.py "somebody.py' @@ -807,7 +807,7 @@ def test_parse_command_only_expands_alias(parser): assert statement.output_to == '' -def test_parse_command_only_expands_shortcuts(parser): +def test_parse_command_only_expands_shortcuts(parser) -> None: line = '!cat foobar.txt' statement = parser.parse_command_only(line) assert statement == 'cat foobar.txt' @@ -825,7 +825,7 @@ def test_parse_command_only_expands_shortcuts(parser): assert statement.output_to == '' -def test_parse_command_only_quoted_args(parser): +def test_parse_command_only_quoted_args(parser) -> None: line = 'l "/tmp/directory with spaces/doit.sh"' statement = parser.parse_command_only(line) assert statement == 'ls -al "/tmp/directory with spaces/doit.sh"' @@ -843,7 +843,7 @@ def test_parse_command_only_quoted_args(parser): assert statement.output_to == '' -def test_parse_command_only_unclosed_quote(parser): +def test_parse_command_only_unclosed_quote(parser) -> None: # Quoted trailing spaces will be preserved line = 'command with unclosed "quote ' statement = parser.parse_command_only(line) @@ -875,7 +875,7 @@ def test_parse_command_only_unclosed_quote(parser): ('help; ;;', '; ;;'), ], ) -def test_parse_command_only_specialchars(parser, line, args): +def test_parse_command_only_specialchars(parser, line, args) -> None: statement = parser.parse_command_only(line) assert statement == args assert statement.args == args @@ -906,7 +906,7 @@ def test_parse_command_only_specialchars(parser, line, args): '|', ], ) -def test_parse_command_only_empty(parser, line): +def test_parse_command_only_empty(parser, line) -> None: statement = parser.parse_command_only(line) assert statement == '' assert statement.args == statement @@ -923,7 +923,7 @@ def test_parse_command_only_empty(parser, line): assert statement.output_to == '' -def test_parse_command_only_multiline(parser): +def test_parse_command_only_multiline(parser) -> None: line = 'multiline with partially "open quotes and no terminator' statement = parser.parse_command_only(line) assert statement.command == 'multiline' @@ -933,7 +933,7 @@ def test_parse_command_only_multiline(parser): assert statement.args == statement -def test_statement_initialization(): +def test_statement_initialization() -> None: string = 'alias' statement = cmd2.Statement(string) assert string == statement @@ -953,7 +953,7 @@ def test_statement_initialization(): assert statement.output_to == '' -def test_statement_is_immutable(): +def test_statement_is_immutable() -> None: string = 'foo' statement = cmd2.Statement(string) assert string == statement @@ -965,7 +965,7 @@ def test_statement_is_immutable(): statement.raw = 'baz' -def test_statement_as_dict(parser): +def test_statement_as_dict(parser) -> None: # Make sure to_dict() results can be restored to identical Statement statement = parser.parse("!ls > out.txt") assert statement == Statement.from_dict(statement.to_dict()) @@ -985,7 +985,7 @@ def test_statement_as_dict(parser): Statement.from_dict(statement_dict) -def test_is_valid_command_invalid(mocker, parser): +def test_is_valid_command_invalid(mocker, parser) -> None: # Non-string command valid, errmsg = parser.is_valid_command(5) assert not valid @@ -1032,7 +1032,7 @@ def test_is_valid_command_invalid(mocker, parser): assert 'cannot contain: whitespace, quotes,' in errmsg -def test_is_valid_command_valid(parser): +def test_is_valid_command_valid(parser) -> None: # Valid command valid, errmsg = parser.is_valid_command('shell') assert valid @@ -1044,7 +1044,7 @@ def test_is_valid_command_valid(parser): assert not errmsg -def test_macro_normal_arg_pattern(): +def test_macro_normal_arg_pattern() -> None: # This pattern matches digits surrounded by exactly 1 brace on a side and 1 or more braces on the opposite side from cmd2.parsing import ( MacroArg, @@ -1098,7 +1098,7 @@ def test_macro_normal_arg_pattern(): assert not matches -def test_macro_escaped_arg_pattern(): +def test_macro_escaped_arg_pattern() -> None: # This pattern matches digits surrounded by 2 or more braces on both sides from cmd2.parsing import ( MacroArg, diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 8991ad902..54b5ffdf5 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -4,6 +4,7 @@ import argparse import sys +from typing import Never from unittest import ( mock, ) @@ -22,11 +23,11 @@ class Plugin: """A mixin class for testing hook registration and calling""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.reset_counters() - def reset_counters(self): + def reset_counters(self) -> None: self.called_preparse = 0 self.called_postparsing = 0 self.called_precmd = 0 @@ -98,7 +99,7 @@ def postparse_hook_undeclared_parameter_annotation(self, data) -> cmd2.plugin.Po def postparse_hook_wrong_parameter_annotation(self, data: str) -> cmd2.plugin.PostparsingData: """A postparsing hook with the wrong parameter type""" - def postparse_hook_undeclared_return_annotation(self, data: cmd2.plugin.PostparsingData): + def postparse_hook_undeclared_return_annotation(self, data: cmd2.plugin.PostparsingData) -> None: """A postparsing hook with an undeclared return type""" def postparse_hook_wrong_return_annotation(self, data: cmd2.plugin.PostparsingData) -> str: @@ -265,14 +266,14 @@ def cmdfinalization_hook_wrong_return_annotation(self, data: plugin.CommandFinal class PluggedApp(Plugin, cmd2.Cmd): """A sample app with a plugin mixed in""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def do_say(self, statement): + def do_say(self, statement) -> None: """Repeat back the arguments""" self.poutput(statement) - def do_skip_postcmd_hooks(self, _): + def do_skip_postcmd_hooks(self, _) -> Never: self.poutput("In do_skip_postcmd_hooks") raise exceptions.SkipPostcommandHooks @@ -280,7 +281,7 @@ def do_skip_postcmd_hooks(self, _): parser.add_argument("my_arg", help="some help text") @with_argparser(parser) - def do_argparse_cmd(self, namespace: argparse.Namespace): + def do_argparse_cmd(self, namespace: argparse.Namespace) -> None: """Repeat back the arguments""" self.poutput(namespace.cmd2_statement.get()) @@ -290,19 +291,19 @@ def do_argparse_cmd(self, namespace: argparse.Namespace): # test pre and postloop hooks # ### -def test_register_preloop_hook_too_many_parameters(): +def test_register_preloop_hook_too_many_parameters() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_preloop_hook(app.prepost_hook_too_many_parameters) -def test_register_preloop_hook_with_return_annotation(): +def test_register_preloop_hook_with_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_preloop_hook(app.prepost_hook_with_wrong_return_annotation) -def test_preloop_hook(capsys): +def test_preloop_hook(capsys) -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog", "say hello", 'quit'] @@ -316,7 +317,7 @@ def test_preloop_hook(capsys): assert not err -def test_preloop_hooks(capsys): +def test_preloop_hooks(capsys) -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog", "say hello", 'quit'] @@ -331,19 +332,19 @@ def test_preloop_hooks(capsys): assert not err -def test_register_postloop_hook_too_many_parameters(): +def test_register_postloop_hook_too_many_parameters() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postloop_hook(app.prepost_hook_too_many_parameters) -def test_register_postloop_hook_with_wrong_return_annotation(): +def test_register_postloop_hook_with_wrong_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postloop_hook(app.prepost_hook_with_wrong_return_annotation) -def test_postloop_hook(capsys): +def test_postloop_hook(capsys) -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog", "say hello", 'quit'] @@ -357,7 +358,7 @@ def test_postloop_hook(capsys): assert not err -def test_postloop_hooks(capsys): +def test_postloop_hooks(capsys) -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog", "say hello", 'quit'] @@ -377,7 +378,7 @@ def test_postloop_hooks(capsys): # test preparse hook # ### -def test_preparse(capsys): +def test_preparse(capsys) -> None: app = PluggedApp() app.register_postparsing_hook(app.preparse) app.onecmd_plus_hooks('say hello') @@ -392,37 +393,37 @@ def test_preparse(capsys): # test postparsing hooks # ### -def test_postparsing_hook_too_many_parameters(): +def test_postparsing_hook_too_many_parameters() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postparsing_hook(app.postparse_hook_too_many_parameters) -def test_postparsing_hook_undeclared_parameter_annotation(): +def test_postparsing_hook_undeclared_parameter_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postparsing_hook(app.postparse_hook_undeclared_parameter_annotation) -def test_postparsing_hook_wrong_parameter_annotation(): +def test_postparsing_hook_wrong_parameter_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postparsing_hook(app.postparse_hook_wrong_parameter_annotation) -def test_postparsing_hook_undeclared_return_annotation(): +def test_postparsing_hook_undeclared_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postparsing_hook(app.postparse_hook_undeclared_return_annotation) -def test_postparsing_hook_wrong_return_annotation(): +def test_postparsing_hook_wrong_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postparsing_hook(app.postparse_hook_wrong_return_annotation) -def test_postparsing_hook(capsys): +def test_postparsing_hook(capsys) -> None: app = PluggedApp() app.onecmd_plus_hooks('say hello') out, err = capsys.readouterr() @@ -448,7 +449,7 @@ def test_postparsing_hook(capsys): assert app.called_postparsing == 2 -def test_postparsing_hook_stop_first(capsys): +def test_postparsing_hook_stop_first(capsys) -> None: app = PluggedApp() app.register_postparsing_hook(app.postparse_hook_stop) stop = app.onecmd_plus_hooks('say hello') @@ -463,7 +464,7 @@ def test_postparsing_hook_stop_first(capsys): assert stop -def test_postparsing_hook_stop_second(capsys): +def test_postparsing_hook_stop_second(capsys) -> None: app = PluggedApp() app.register_postparsing_hook(app.postparse_hook) stop = app.onecmd_plus_hooks('say hello') @@ -485,7 +486,7 @@ def test_postparsing_hook_stop_second(capsys): assert stop -def test_postparsing_hook_emptystatement_first(capsys): +def test_postparsing_hook_emptystatement_first(capsys) -> None: app = PluggedApp() app.register_postparsing_hook(app.postparse_hook_emptystatement) stop = app.onecmd_plus_hooks('say hello') @@ -506,7 +507,7 @@ def test_postparsing_hook_emptystatement_first(capsys): assert app.called_postparsing == 1 -def test_postparsing_hook_emptystatement_second(capsys): +def test_postparsing_hook_emptystatement_second(capsys) -> None: app = PluggedApp() app.register_postparsing_hook(app.postparse_hook) stop = app.onecmd_plus_hooks('say hello') @@ -537,7 +538,7 @@ def test_postparsing_hook_emptystatement_second(capsys): assert app.called_postparsing == 2 -def test_postparsing_hook_exception(capsys): +def test_postparsing_hook_exception(capsys) -> None: app = PluggedApp() app.register_postparsing_hook(app.postparse_hook_exception) stop = app.onecmd_plus_hooks('say hello') @@ -563,7 +564,7 @@ def test_postparsing_hook_exception(capsys): # test precmd hooks # ##### -def test_register_precmd_hook_parameter_count(): +def test_register_precmd_hook_parameter_count() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_precmd_hook(app.precmd_hook_not_enough_parameters) @@ -571,31 +572,31 @@ def test_register_precmd_hook_parameter_count(): app.register_precmd_hook(app.precmd_hook_too_many_parameters) -def test_register_precmd_hook_no_parameter_annotation(): +def test_register_precmd_hook_no_parameter_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_precmd_hook(app.precmd_hook_no_parameter_annotation) -def test_register_precmd_hook_wrong_parameter_annotation(): +def test_register_precmd_hook_wrong_parameter_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_precmd_hook(app.precmd_hook_wrong_parameter_annotation) -def test_register_precmd_hook_no_return_annotation(): +def test_register_precmd_hook_no_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_precmd_hook(app.precmd_hook_no_return_annotation) -def test_register_precmd_hook_wrong_return_annotation(): +def test_register_precmd_hook_wrong_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_precmd_hook(app.precmd_hook_wrong_return_annotation) -def test_precmd_hook(capsys): +def test_precmd_hook(capsys) -> None: app = PluggedApp() app.onecmd_plus_hooks('say hello') out, err = capsys.readouterr() @@ -624,7 +625,7 @@ def test_precmd_hook(capsys): assert app.called_precmd == 3 -def test_precmd_hook_emptystatement_first(capsys): +def test_precmd_hook_emptystatement_first(capsys) -> None: app = PluggedApp() app.register_precmd_hook(app.precmd_hook_emptystatement) stop = app.onecmd_plus_hooks('say hello') @@ -650,7 +651,7 @@ def test_precmd_hook_emptystatement_first(capsys): assert app.called_precmd == 1 -def test_precmd_hook_emptystatement_second(capsys): +def test_precmd_hook_emptystatement_second(capsys) -> None: app = PluggedApp() app.register_precmd_hook(app.precmd_hook) stop = app.onecmd_plus_hooks('say hello') @@ -692,7 +693,7 @@ def test_precmd_hook_emptystatement_second(capsys): # test postcmd hooks # #### -def test_register_postcmd_hook_parameter_count(): +def test_register_postcmd_hook_parameter_count() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postcmd_hook(app.postcmd_hook_not_enough_parameters) @@ -700,31 +701,31 @@ def test_register_postcmd_hook_parameter_count(): app.register_postcmd_hook(app.postcmd_hook_too_many_parameters) -def test_register_postcmd_hook_no_parameter_annotation(): +def test_register_postcmd_hook_no_parameter_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postcmd_hook(app.postcmd_hook_no_parameter_annotation) -def test_register_postcmd_hook_wrong_parameter_annotation(): +def test_register_postcmd_hook_wrong_parameter_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postcmd_hook(app.postcmd_hook_wrong_parameter_annotation) -def test_register_postcmd_hook_no_return_annotation(): +def test_register_postcmd_hook_no_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postcmd_hook(app.postcmd_hook_no_return_annotation) -def test_register_postcmd_hook_wrong_return_annotation(): +def test_register_postcmd_hook_wrong_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_postcmd_hook(app.postcmd_hook_wrong_return_annotation) -def test_postcmd(capsys): +def test_postcmd(capsys) -> None: app = PluggedApp() app.onecmd_plus_hooks('say hello') out, err = capsys.readouterr() @@ -753,7 +754,7 @@ def test_postcmd(capsys): assert app.called_postcmd == 3 -def test_postcmd_exception_first(capsys): +def test_postcmd_exception_first(capsys) -> None: app = PluggedApp() app.register_postcmd_hook(app.postcmd_hook_exception) stop = app.onecmd_plus_hooks('say hello') @@ -780,7 +781,7 @@ def test_postcmd_exception_first(capsys): assert app.called_postcmd == 1 -def test_postcmd_exception_second(capsys): +def test_postcmd_exception_second(capsys) -> None: app = PluggedApp() app.register_postcmd_hook(app.postcmd_hook) stop = app.onecmd_plus_hooks('say hello') @@ -811,7 +812,7 @@ def test_postcmd_exception_second(capsys): # command finalization # ### -def test_register_cmdfinalization_hook_parameter_count(): +def test_register_cmdfinalization_hook_parameter_count() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_cmdfinalization_hook(app.cmdfinalization_hook_not_enough_parameters) @@ -819,31 +820,31 @@ def test_register_cmdfinalization_hook_parameter_count(): app.register_cmdfinalization_hook(app.cmdfinalization_hook_too_many_parameters) -def test_register_cmdfinalization_hook_no_parameter_annotation(): +def test_register_cmdfinalization_hook_no_parameter_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_cmdfinalization_hook(app.cmdfinalization_hook_no_parameter_annotation) -def test_register_cmdfinalization_hook_wrong_parameter_annotation(): +def test_register_cmdfinalization_hook_wrong_parameter_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_cmdfinalization_hook(app.cmdfinalization_hook_wrong_parameter_annotation) -def test_register_cmdfinalization_hook_no_return_annotation(): +def test_register_cmdfinalization_hook_no_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_cmdfinalization_hook(app.cmdfinalization_hook_no_return_annotation) -def test_register_cmdfinalization_hook_wrong_return_annotation(): +def test_register_cmdfinalization_hook_wrong_return_annotation() -> None: app = PluggedApp() with pytest.raises(TypeError): app.register_cmdfinalization_hook(app.cmdfinalization_hook_wrong_return_annotation) -def test_cmdfinalization(capsys): +def test_cmdfinalization(capsys) -> None: app = PluggedApp() app.onecmd_plus_hooks('say hello') out, err = capsys.readouterr() @@ -868,7 +869,7 @@ def test_cmdfinalization(capsys): assert app.called_cmdfinalization == 2 -def test_cmdfinalization_stop_first(capsys): +def test_cmdfinalization_stop_first(capsys) -> None: app = PluggedApp() app.register_cmdfinalization_hook(app.cmdfinalization_hook_stop) app.register_cmdfinalization_hook(app.cmdfinalization_hook) @@ -880,7 +881,7 @@ def test_cmdfinalization_stop_first(capsys): assert stop -def test_cmdfinalization_stop_second(capsys): +def test_cmdfinalization_stop_second(capsys) -> None: app = PluggedApp() app.register_cmdfinalization_hook(app.cmdfinalization_hook) app.register_cmdfinalization_hook(app.cmdfinalization_hook_stop) @@ -892,7 +893,7 @@ def test_cmdfinalization_stop_second(capsys): assert stop -def test_cmdfinalization_hook_exception(capsys): +def test_cmdfinalization_hook_exception(capsys) -> None: app = PluggedApp() app.register_cmdfinalization_hook(app.cmdfinalization_hook_exception) stop = app.onecmd_plus_hooks('say hello') @@ -913,7 +914,7 @@ def test_cmdfinalization_hook_exception(capsys): assert app.called_cmdfinalization == 1 -def test_cmdfinalization_hook_system_exit(): +def test_cmdfinalization_hook_system_exit() -> None: app = PluggedApp() app.register_cmdfinalization_hook(app.cmdfinalization_hook_system_exit) stop = app.onecmd_plus_hooks('say hello') @@ -922,7 +923,7 @@ def test_cmdfinalization_hook_system_exit(): assert app.exit_code == 5 -def test_cmdfinalization_hook_keyboard_interrupt(): +def test_cmdfinalization_hook_keyboard_interrupt() -> None: app = PluggedApp() app.register_cmdfinalization_hook(app.cmdfinalization_hook_keyboard_interrupt) @@ -945,7 +946,7 @@ def test_cmdfinalization_hook_keyboard_interrupt(): assert app.called_cmdfinalization == 1 -def test_cmdfinalization_hook_passthrough_exception(): +def test_cmdfinalization_hook_passthrough_exception() -> None: app = PluggedApp() app.register_cmdfinalization_hook(app.cmdfinalization_hook_passthrough_exception) @@ -955,7 +956,7 @@ def test_cmdfinalization_hook_passthrough_exception(): assert app.called_cmdfinalization == 1 -def test_skip_postcmd_hooks(capsys): +def test_skip_postcmd_hooks(capsys) -> None: app = PluggedApp() app.register_postcmd_hook(app.postcmd_hook) app.register_cmdfinalization_hook(app.cmdfinalization_hook) @@ -968,7 +969,7 @@ def test_skip_postcmd_hooks(capsys): assert app.called_cmdfinalization == 1 -def test_cmd2_argparse_exception(capsys): +def test_cmd2_argparse_exception(capsys) -> None: """ Verify Cmd2ArgparseErrors raised after calling a command prevent postcmd events from running but do not affect cmdfinalization events diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index 741762a3c..c619ee2bd 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -29,7 +29,7 @@ def cmdfinalization_hook(data: plugin.CommandFinalizationData) -> plugin.Command return data -def test_run_pyscript(base_app, request): +def test_run_pyscript(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'script.py') expected = 'This is a python script running ...' @@ -39,7 +39,7 @@ def test_run_pyscript(base_app, request): assert base_app.last_result is True -def test_run_pyscript_recursive_not_allowed(base_app, request): +def test_run_pyscript_recursive_not_allowed(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'recursive.py') expected = 'Recursively entering interactive Python shells is not allowed' @@ -49,14 +49,14 @@ def test_run_pyscript_recursive_not_allowed(base_app, request): assert base_app.last_result is False -def test_run_pyscript_with_nonexist_file(base_app): +def test_run_pyscript_with_nonexist_file(base_app) -> None: python_script = 'does_not_exist.py' out, err = run_cmd(base_app, f"run_pyscript {python_script}") assert "Error reading script file" in err[0] assert base_app.last_result is False -def test_run_pyscript_with_non_python_file(base_app, request): +def test_run_pyscript_with_non_python_file(base_app, request) -> None: m = mock.MagicMock(name='input', return_value='2') builtins.input = m @@ -68,7 +68,7 @@ def test_run_pyscript_with_non_python_file(base_app, request): @pytest.mark.parametrize('python_script', odd_file_names) -def test_run_pyscript_with_odd_file_names(base_app, python_script): +def test_run_pyscript_with_odd_file_names(base_app, python_script) -> None: """ Pass in file names with various patterns. Since these files don't exist, we will rely on the error text to make sure the file names were processed correctly. @@ -83,7 +83,7 @@ def test_run_pyscript_with_odd_file_names(base_app, python_script): assert base_app.last_result is False -def test_run_pyscript_with_exception(base_app, request): +def test_run_pyscript_with_exception(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'raises_exception.py') out, err = run_cmd(base_app, f"run_pyscript {python_script}") @@ -92,13 +92,13 @@ def test_run_pyscript_with_exception(base_app, request): assert base_app.last_result is True -def test_run_pyscript_requires_an_argument(base_app): +def test_run_pyscript_requires_an_argument(base_app) -> None: out, err = run_cmd(base_app, "run_pyscript") assert "the following arguments are required: script_path" in err[1] assert base_app.last_result is None -def test_run_pyscript_help(base_app, request): +def test_run_pyscript_help(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'help.py') out1, err1 = run_cmd(base_app, 'help') @@ -107,7 +107,7 @@ def test_run_pyscript_help(base_app, request): assert out1 == out2 -def test_scripts_add_to_history(base_app, request): +def test_scripts_add_to_history(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'help.py') command = f'run_pyscript {python_script}' @@ -128,7 +128,7 @@ def test_scripts_add_to_history(base_app, request): assert base_app.history.get(1).raw == command -def test_run_pyscript_dir(base_app, request): +def test_run_pyscript_dir(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'pyscript_dir.py') @@ -136,7 +136,7 @@ def test_run_pyscript_dir(base_app, request): assert out[0] == "['cmd_echo']" -def test_run_pyscript_stdout_capture(base_app, request): +def test_run_pyscript_stdout_capture(base_app, request) -> None: base_app.register_cmdfinalization_hook(cmdfinalization_hook) test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'stdout_capture.py') @@ -146,7 +146,7 @@ def test_run_pyscript_stdout_capture(base_app, request): assert out[1] == "PASSED" -def test_run_pyscript_stop(base_app, request): +def test_run_pyscript_stop(base_app, request) -> None: # Verify onecmd_plus_hooks() returns True if any commands in a pyscript return True for stop test_dir = os.path.dirname(request.module.__file__) @@ -161,7 +161,7 @@ def test_run_pyscript_stop(base_app, request): assert stop -def test_run_pyscript_environment(base_app, request): +def test_run_pyscript_environment(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'environment.py') out, err = run_cmd(base_app, f'run_pyscript {python_script}') @@ -169,7 +169,7 @@ def test_run_pyscript_environment(base_app, request): assert out[0] == "PASSED" -def test_run_pyscript_self_in_py(base_app, request): +def test_run_pyscript_self_in_py(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'self_in_py.py') @@ -184,7 +184,7 @@ def test_run_pyscript_self_in_py(base_app, request): assert 'I do not see self' in out[0] -def test_run_pyscript_py_locals(base_app, request): +def test_run_pyscript_py_locals(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'py_locals.py') @@ -205,7 +205,7 @@ def test_run_pyscript_py_locals(base_app, request): assert base_app.py_locals['my_list'][0] == 2 -def test_run_pyscript_app_echo(base_app, request): +def test_run_pyscript_app_echo(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'pyscript', 'echo.py') out, err = run_cmd(base_app, f'run_pyscript {python_script}') diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py index 1e9cf5e17..2c54625cf 100644 --- a/tests/test_table_creator.py +++ b/tests/test_table_creator.py @@ -25,7 +25,7 @@ # fmt: off -def test_column_creation(): +def test_column_creation() -> None: # Width less than 1 with pytest.raises(ValueError, match="Column width cannot be less than 1"): Column("Column 1", width=0) @@ -82,7 +82,7 @@ def test_column_creation(): assert c.style_data_text is False -def test_column_alignment(): +def test_column_alignment() -> None: column_1 = Column( "Col 1", width=10, @@ -137,7 +137,7 @@ def test_column_alignment(): ) -def test_blank_last_line(): +def test_blank_last_line() -> None: """This tests that an empty line is inserted when the last data line is blank""" column_1 = Column("Col 1", width=10) tc = TableCreator([column_1]) @@ -156,7 +156,7 @@ def test_blank_last_line(): assert row == ' ' -def test_wrap_text(): +def test_wrap_text() -> None: column_1 = Column("Col 1", width=10) tc = TableCreator([column_1]) @@ -178,7 +178,7 @@ def test_wrap_text(): ' last one ') -def test_wrap_text_max_lines(): +def test_wrap_text_max_lines() -> None: column_1 = Column("Col 1", width=10, max_data_lines=2) tc = TableCreator([column_1]) @@ -213,7 +213,7 @@ def test_wrap_text_max_lines(): 'last line…') -def test_wrap_long_word(): +def test_wrap_long_word() -> None: # Make sure words wider than column start on own line and wrap column_1 = Column("LongColumnName", width=10) column_2 = Column("Col 2", width=10) @@ -256,7 +256,7 @@ def test_wrap_long_word(): assert row == expected -def test_wrap_long_word_max_data_lines(): +def test_wrap_long_word_max_data_lines() -> None: column_1 = Column("Col 1", width=10, max_data_lines=2) column_2 = Column("Col 2", width=10, max_data_lines=2) column_3 = Column("Col 3", width=10, max_data_lines=2) @@ -285,7 +285,7 @@ def test_wrap_long_word_max_data_lines(): '10FitsLast 10FitsLas… 10RunsOve… ') -def test_wrap_long_char_wider_than_max_width(): +def test_wrap_long_char_wider_than_max_width() -> None: """ This tests case where a character is wider than max_width. This can happen if max_width is 1 and the text contains wide characters (e.g. East Asian). Replace it with an ellipsis. @@ -296,7 +296,7 @@ def test_wrap_long_char_wider_than_max_width(): assert row == '…' -def test_generate_row_exceptions(): +def test_generate_row_exceptions() -> None: column_1 = Column("Col 1") tc = TableCreator([column_1]) row_data = ['fake'] @@ -318,7 +318,7 @@ def test_generate_row_exceptions(): tc.generate_row(row_data=row_data, is_header=False) -def test_tabs(): +def test_tabs() -> None: column_1 = Column("Col\t1", width=20) column_2 = Column("Col 2") columns = [column_1, column_2] @@ -332,7 +332,7 @@ def test_tabs(): TableCreator([column_1, column_2], tab_width=0) -def test_simple_table_creation(): +def test_simple_table_creation() -> None: column_1 = Column("Col 1", width=16) column_2 = Column("Col 2", width=16) @@ -472,7 +472,7 @@ def test_simple_table_creation(): ) -def test_simple_table_width(): +def test_simple_table_width() -> None: # Base width for num_cols in range(1, 10): assert SimpleTable.base_width(num_cols) == (num_cols - 1) * 2 @@ -493,7 +493,7 @@ def test_simple_table_width(): assert st.total_width() == 34 -def test_simple_generate_data_row_exceptions(): +def test_simple_generate_data_row_exceptions() -> None: column_1 = Column("Col 1") tc = SimpleTable([column_1]) @@ -503,7 +503,7 @@ def test_simple_generate_data_row_exceptions(): tc.generate_data_row(row_data=row_data) -def test_bordered_table_creation(): +def test_bordered_table_creation() -> None: column_1 = Column("Col 1", width=15) column_2 = Column("Col 2", width=15) @@ -595,7 +595,7 @@ def test_bordered_table_creation(): ) -def test_bordered_table_width(): +def test_bordered_table_width() -> None: # Default behavior (column_borders=True, padding=1) assert BorderedTable.base_width(1) == 4 assert BorderedTable.base_width(2) == 7 @@ -632,7 +632,7 @@ def test_bordered_table_width(): assert bt.total_width() == 37 -def test_bordered_generate_data_row_exceptions(): +def test_bordered_generate_data_row_exceptions() -> None: column_1 = Column("Col 1") tc = BorderedTable([column_1]) @@ -642,7 +642,7 @@ def test_bordered_generate_data_row_exceptions(): tc.generate_data_row(row_data=row_data) -def test_alternating_table_creation(): +def test_alternating_table_creation() -> None: column_1 = Column("Col 1", width=15) column_2 = Column("Col 2", width=15) diff --git a/tests/test_transcript.py b/tests/test_transcript.py index 4372bd6a8..f7df29cfa 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -7,6 +7,7 @@ import re import sys import tempfile +from typing import Never from unittest import ( mock, ) @@ -33,7 +34,7 @@ class CmdLineApp(cmd2.Cmd): MUMBLE_FIRST = ['so', 'like', 'well'] MUMBLE_LAST = ['right?'] - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: self.maxrepeats = 3 super().__init__(*args, multiline_commands=['orate'], **kwargs) @@ -49,7 +50,7 @@ def __init__(self, *args, **kwargs): speak_parser.add_argument('-r', '--repeat', type=int, help="output [n] times") @cmd2.with_argparser(speak_parser, with_unknown_args=True) - def do_speak(self, opts, arg): + def do_speak(self, opts, arg) -> None: """Repeats what you tell me to.""" arg = ' '.join(arg) if opts.piglatin: @@ -70,7 +71,7 @@ def do_speak(self, opts, arg): mumble_parser.add_argument('-r', '--repeat', type=int, help="output [n] times") @cmd2.with_argparser(mumble_parser, with_unknown_args=True) - def do_mumble(self, opts, arg): + def do_mumble(self, opts, arg) -> None: """Mumbles what you tell me to.""" repetitions = opts.repeat or 1 # arg = arg.split() @@ -86,14 +87,14 @@ def do_mumble(self, opts, arg): output.append(random.choice(self.MUMBLE_LAST)) self.poutput(' '.join(output)) - def do_nothing(self, statement): + def do_nothing(self, statement) -> None: """Do nothing and output nothing""" - def do_keyboard_interrupt(self, _): + def do_keyboard_interrupt(self, _) -> Never: raise KeyboardInterrupt('Interrupting this command') -def test_commands_at_invocation(): +def test_commands_at_invocation() -> None: testargs = ["prog", "say hello", "say Gracie", "quit"] expected = "This is an intro banner ...\nhello\nGracie\n" with mock.patch.object(sys, 'argv', testargs): @@ -125,7 +126,7 @@ def test_commands_at_invocation(): ('word_boundaries.txt', False), ], ) -def test_transcript(request, capsys, filename, feedback_to_output): +def test_transcript(request, capsys, filename, feedback_to_output) -> None: # Get location of the transcript test_dir = os.path.dirname(request.module.__file__) transcript_file = os.path.join(test_dir, 'transcripts', filename) @@ -152,7 +153,7 @@ def test_transcript(request, capsys, filename, feedback_to_output): assert err.endswith(expected_end) -def test_history_transcript(): +def test_history_transcript() -> None: app = CmdLineApp() app.stdout = StdSim(app.stdout) run_cmd(app, 'orate this is\na /multiline/\ncommand;\n') @@ -180,7 +181,7 @@ def test_history_transcript(): assert xscript == expected -def test_history_transcript_bad_path(mocker): +def test_history_transcript_bad_path(mocker) -> None: app = CmdLineApp() app.stdout = StdSim(app.stdout) run_cmd(app, 'orate this is\na /multiline/\ncommand;\n') @@ -200,7 +201,7 @@ def test_history_transcript_bad_path(mocker): assert "Error saving transcript file" in err[0] -def test_run_script_record_transcript(base_app, request): +def test_run_script_record_transcript(base_app, request) -> None: test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'help.txt') @@ -225,7 +226,7 @@ def test_run_script_record_transcript(base_app, request): verify_help_text(base_app, xscript) -def test_generate_transcript_stop(capsys): +def test_generate_transcript_stop(capsys) -> None: # Verify transcript generation stops when a command returns True for stop app = CmdLineApp() @@ -276,7 +277,7 @@ def test_generate_transcript_stop(capsys): (r'lots /\/?/ more /.*/ stuff', re.escape('lots ') + '/?' + re.escape(' more ') + '.*' + re.escape(' stuff')), ], ) -def test_parse_transcript_expected(expected, transformed): +def test_parse_transcript_expected(expected, transformed) -> None: app = CmdLineApp() class TestMyAppCase(transcript.Cmd2TestCase): @@ -286,7 +287,7 @@ class TestMyAppCase(transcript.Cmd2TestCase): assert testcase._transform_transcript_expected(expected) == transformed -def test_transcript_failure(request, capsys): +def test_transcript_failure(request, capsys) -> None: # Get location of the transcript test_dir = os.path.dirname(request.module.__file__) transcript_file = os.path.join(test_dir, 'transcripts', 'failure.txt') @@ -312,7 +313,7 @@ def test_transcript_failure(request, capsys): assert err.endswith(expected_end) -def test_transcript_no_file(request, capsys): +def test_transcript_no_file(request, capsys) -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with # arguments equal to the py.test args testargs = ['prog', '-t'] diff --git a/tests/test_utils.py b/tests/test_utils.py index 5511cd00d..402deb226 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -24,50 +24,50 @@ HELLO_WORLD = 'Hello, world!' -def test_strip_quotes_no_quotes(): +def test_strip_quotes_no_quotes() -> None: base_str = HELLO_WORLD stripped = cu.strip_quotes(base_str) assert base_str == stripped -def test_strip_quotes_with_quotes(): +def test_strip_quotes_with_quotes() -> None: base_str = '"' + HELLO_WORLD + '"' stripped = cu.strip_quotes(base_str) assert stripped == HELLO_WORLD -def test_remove_duplicates_no_duplicates(): +def test_remove_duplicates_no_duplicates() -> None: no_dups = [5, 4, 3, 2, 1] assert cu.remove_duplicates(no_dups) == no_dups -def test_remove_duplicates_with_duplicates(): +def test_remove_duplicates_with_duplicates() -> None: duplicates = [1, 1, 2, 3, 9, 9, 7, 8] assert cu.remove_duplicates(duplicates) == [1, 2, 3, 9, 7, 8] -def test_unicode_normalization(): +def test_unicode_normalization() -> None: s1 = 'café' s2 = 'cafe\u0301' assert s1 != s2 assert cu.norm_fold(s1) == cu.norm_fold(s2) -def test_unicode_casefold(): +def test_unicode_casefold() -> None: micro = 'µ' micro_cf = micro.casefold() assert micro != micro_cf assert cu.norm_fold(micro) == cu.norm_fold(micro_cf) -def test_alphabetical_sort(): +def test_alphabetical_sort() -> None: my_list = ['café', 'µ', 'A', 'micro', 'unity', 'cafeteria'] assert cu.alphabetical_sort(my_list) == ['A', 'cafeteria', 'café', 'micro', 'unity', 'µ'] my_list = ['a3', 'a22', 'A2', 'A11', 'a1'] assert cu.alphabetical_sort(my_list) == ['a1', 'A11', 'A2', 'a22', 'a3'] -def test_try_int_or_force_to_lower_case(): +def test_try_int_or_force_to_lower_case() -> None: str1 = '17' assert cu.try_int_or_force_to_lower_case(str1) == 17 str1 = 'ABC' @@ -78,7 +78,7 @@ def test_try_int_or_force_to_lower_case(): assert cu.try_int_or_force_to_lower_case(str1) == '' -def test_natural_keys(): +def test_natural_keys() -> None: my_list = ['café', 'µ', 'A', 'micro', 'unity', 'x1', 'X2', 'X11', 'X0', 'x22'] my_list.sort(key=cu.natural_keys) assert my_list == ['A', 'café', 'micro', 'unity', 'X0', 'x1', 'X2', 'X11', 'x22', 'µ'] @@ -87,28 +87,28 @@ def test_natural_keys(): assert my_list == ['a1', 'A2', 'a3', 'A11', 'a22'] -def test_natural_sort(): +def test_natural_sort() -> None: my_list = ['café', 'µ', 'A', 'micro', 'unity', 'x1', 'X2', 'X11', 'X0', 'x22'] assert cu.natural_sort(my_list) == ['A', 'café', 'micro', 'unity', 'X0', 'x1', 'X2', 'X11', 'x22', 'µ'] my_list = ['a3', 'a22', 'A2', 'A11', 'a1'] assert cu.natural_sort(my_list) == ['a1', 'A2', 'a3', 'A11', 'a22'] -def test_is_quoted_short(): +def test_is_quoted_short() -> None: my_str = '' assert not cu.is_quoted(my_str) your_str = '"' assert not cu.is_quoted(your_str) -def test_is_quoted_yes(): +def test_is_quoted_yes() -> None: my_str = '"This is a test"' assert cu.is_quoted(my_str) your_str = "'of the emergengy broadcast system'" assert cu.is_quoted(your_str) -def test_is_quoted_no(): +def test_is_quoted_no() -> None: my_str = '"This is a test' assert not cu.is_quoted(my_str) your_str = "of the emergengy broadcast system'" @@ -117,7 +117,7 @@ def test_is_quoted_no(): assert not cu.is_quoted(simple_str) -def test_quote_string(): +def test_quote_string() -> None: my_str = "Hello World" assert cu.quote_string(my_str) == '"' + my_str + '"' @@ -128,14 +128,14 @@ def test_quote_string(): assert cu.quote_string(my_str) == "'" + my_str + "'" -def test_quote_string_if_needed_yes(): +def test_quote_string_if_needed_yes() -> None: my_str = "Hello World" assert cu.quote_string_if_needed(my_str) == '"' + my_str + '"' your_str = '"foo" bar' assert cu.quote_string_if_needed(your_str) == "'" + your_str + "'" -def test_quote_string_if_needed_no(): +def test_quote_string_if_needed_no() -> None: my_str = "HelloWorld" assert cu.quote_string_if_needed(my_str) == my_str your_str = "'Hello World'" @@ -148,32 +148,32 @@ def stdout_sim(): return stdsim -def test_stdsim_write_str(stdout_sim): +def test_stdsim_write_str(stdout_sim) -> None: my_str = 'Hello World' stdout_sim.write(my_str) assert stdout_sim.getvalue() == my_str -def test_stdsim_write_bytes(stdout_sim): +def test_stdsim_write_bytes(stdout_sim) -> None: b_str = b'Hello World' with pytest.raises(TypeError): stdout_sim.write(b_str) -def test_stdsim_buffer_write_bytes(stdout_sim): +def test_stdsim_buffer_write_bytes(stdout_sim) -> None: b_str = b'Hello World' stdout_sim.buffer.write(b_str) assert stdout_sim.getvalue() == b_str.decode() assert stdout_sim.getbytes() == b_str -def test_stdsim_buffer_write_str(stdout_sim): +def test_stdsim_buffer_write_str(stdout_sim) -> None: my_str = 'Hello World' with pytest.raises(TypeError): stdout_sim.buffer.write(my_str) -def test_stdsim_read(stdout_sim): +def test_stdsim_read(stdout_sim) -> None: my_str = 'Hello World' stdout_sim.write(my_str) # getvalue() returns the value and leaves it unaffected internally @@ -189,7 +189,7 @@ def test_stdsim_read(stdout_sim): assert stdout_sim.getvalue() == my_str[2:] -def test_stdsim_read_bytes(stdout_sim): +def test_stdsim_read_bytes(stdout_sim) -> None: b_str = b'Hello World' stdout_sim.buffer.write(b_str) # getbytes() returns the value and leaves it unaffected internally @@ -199,7 +199,7 @@ def test_stdsim_read_bytes(stdout_sim): assert stdout_sim.getbytes() == b'' -def test_stdsim_clear(stdout_sim): +def test_stdsim_clear(stdout_sim) -> None: my_str = 'Hello World' stdout_sim.write(my_str) assert stdout_sim.getvalue() == my_str @@ -207,7 +207,7 @@ def test_stdsim_clear(stdout_sim): assert stdout_sim.getvalue() == '' -def test_stdsim_getattr_exist(stdout_sim): +def test_stdsim_getattr_exist(stdout_sim) -> None: # Here the StdSim getattr is allowing us to access methods within StdSim my_str = 'Hello World' stdout_sim.write(my_str) @@ -215,12 +215,12 @@ def test_stdsim_getattr_exist(stdout_sim): assert val_func() == my_str -def test_stdsim_getattr_noexist(stdout_sim): +def test_stdsim_getattr_noexist(stdout_sim) -> None: # Here the StdSim getattr is allowing us to access methods defined by the inner stream assert not stdout_sim.isatty() -def test_stdsim_pause_storage(stdout_sim): +def test_stdsim_pause_storage(stdout_sim) -> None: # Test pausing storage for string data my_str = 'Hello World' @@ -244,7 +244,7 @@ def test_stdsim_pause_storage(stdout_sim): assert stdout_sim.getbytes() == b'' -def test_stdsim_line_buffering(base_app): +def test_stdsim_line_buffering(base_app) -> None: # This exercises the case of writing binary data that contains new lines/carriage returns to a StdSim # when line buffering is on. The output should immediately be flushed to the underlying stream. import os @@ -285,7 +285,7 @@ def pr_none(): return pr -def test_proc_reader_send_sigint(pr_none): +def test_proc_reader_send_sigint(pr_none) -> None: assert pr_none._proc.poll() is None pr_none.send_sigint() pr_none.wait() @@ -298,7 +298,7 @@ def test_proc_reader_send_sigint(pr_none): assert ret_code == -signal.SIGINT -def test_proc_reader_terminate(pr_none): +def test_proc_reader_terminate(pr_none) -> None: assert pr_none._proc.poll() is None pr_none.terminate() @@ -322,18 +322,18 @@ def context_flag(): return cu.ContextFlag() -def test_context_flag_bool(context_flag): +def test_context_flag_bool(context_flag) -> None: assert not context_flag with context_flag: assert context_flag -def test_context_flag_exit_err(context_flag): +def test_context_flag_exit_err(context_flag) -> None: with pytest.raises(ValueError, match="count has gone below 0"): context_flag.__exit__() -def test_remove_overridden_styles(): +def test_remove_overridden_styles() -> None: from cmd2 import ( Bg, EightBitBg, @@ -402,42 +402,42 @@ def make_strs(styles_list: list[ansi.AnsiSequence]) -> list[str]: assert cu._remove_overridden_styles(styles_to_parse) == expected -def test_truncate_line(): +def test_truncate_line() -> None: line = 'long' max_width = 3 truncated = cu.truncate_line(line, max_width) assert truncated == 'lo' + HORIZONTAL_ELLIPSIS -def test_truncate_line_already_fits(): +def test_truncate_line_already_fits() -> None: line = 'long' max_width = 4 truncated = cu.truncate_line(line, max_width) assert truncated == line -def test_truncate_line_with_newline(): +def test_truncate_line_with_newline() -> None: line = 'fo\no' max_width = 2 with pytest.raises(ValueError, match="text contains an unprintable character"): cu.truncate_line(line, max_width) -def test_truncate_line_width_is_too_small(): +def test_truncate_line_width_is_too_small() -> None: line = 'foo' max_width = 0 with pytest.raises(ValueError, match="max_width must be at least 1"): cu.truncate_line(line, max_width) -def test_truncate_line_wide_text(): +def test_truncate_line_wide_text() -> None: line = '苹苹other' max_width = 6 truncated = cu.truncate_line(line, max_width) assert truncated == '苹苹o' + HORIZONTAL_ELLIPSIS -def test_truncate_line_split_wide_text(): +def test_truncate_line_split_wide_text() -> None: """Test when truncation results in a string which is shorter than max_width""" line = '1苹2苹' max_width = 3 @@ -445,14 +445,14 @@ def test_truncate_line_split_wide_text(): assert truncated == '1' + HORIZONTAL_ELLIPSIS -def test_truncate_line_tabs(): +def test_truncate_line_tabs() -> None: line = 'has\ttab' max_width = 9 truncated = cu.truncate_line(line, max_width) assert truncated == 'has t' + HORIZONTAL_ELLIPSIS -def test_truncate_with_style(): +def test_truncate_with_style() -> None: from cmd2 import ( Fg, TextStyle, @@ -484,7 +484,7 @@ def test_truncate_with_style(): assert truncated == 'lo' + HORIZONTAL_ELLIPSIS + filtered_after_text -def test_align_text_fill_char_is_tab(): +def test_align_text_fill_char_is_tab() -> None: text = 'foo' fill_char = '\t' width = 5 @@ -492,7 +492,7 @@ def test_align_text_fill_char_is_tab(): assert aligned == text + ' ' -def test_align_text_with_style(): +def test_align_text_with_style() -> None: from cmd2 import ( Fg, TextStyle, @@ -540,7 +540,7 @@ def test_align_text_with_style(): assert aligned == (left_fill + line_1_text + right_fill + '\n' + left_fill + line_2_text + right_fill) -def test_align_text_width_is_too_small(): +def test_align_text_width_is_too_small() -> None: text = 'foo' fill_char = '-' width = 0 @@ -548,7 +548,7 @@ def test_align_text_width_is_too_small(): cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width) -def test_align_text_fill_char_is_too_long(): +def test_align_text_fill_char_is_too_long() -> None: text = 'foo' fill_char = 'fill' width = 5 @@ -556,7 +556,7 @@ def test_align_text_fill_char_is_too_long(): cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width) -def test_align_text_fill_char_is_newline(): +def test_align_text_fill_char_is_newline() -> None: text = 'foo' fill_char = '\n' width = 5 @@ -564,7 +564,7 @@ def test_align_text_fill_char_is_newline(): cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width) -def test_align_text_has_tabs(): +def test_align_text_has_tabs() -> None: text = '\t\tfoo' fill_char = '-' width = 10 @@ -572,7 +572,7 @@ def test_align_text_has_tabs(): assert aligned == ' ' + 'foo' + '---' -def test_align_text_blank(): +def test_align_text_blank() -> None: text = '' fill_char = '-' width = 5 @@ -580,7 +580,7 @@ def test_align_text_blank(): assert aligned == fill_char * width -def test_align_text_wider_than_width(): +def test_align_text_wider_than_width() -> None: text = 'long text field' fill_char = '-' width = 8 @@ -588,7 +588,7 @@ def test_align_text_wider_than_width(): assert aligned == text -def test_align_text_wider_than_width_truncate(): +def test_align_text_wider_than_width_truncate() -> None: text = 'long text field' fill_char = '-' width = 8 @@ -596,7 +596,7 @@ def test_align_text_wider_than_width_truncate(): assert aligned == 'long te' + HORIZONTAL_ELLIPSIS -def test_align_text_wider_than_width_truncate_add_fill(): +def test_align_text_wider_than_width_truncate_add_fill() -> None: """Test when truncation results in a string which is shorter than width and align_text adds filler""" text = '1苹2苹' fill_char = '-' @@ -605,7 +605,7 @@ def test_align_text_wider_than_width_truncate_add_fill(): assert aligned == '1' + HORIZONTAL_ELLIPSIS + fill_char -def test_align_text_has_unprintable(): +def test_align_text_has_unprintable() -> None: text = 'foo\x02' fill_char = '-' width = 5 @@ -613,7 +613,7 @@ def test_align_text_has_unprintable(): cu.align_text(text, cu.TextAlignment.LEFT, fill_char=fill_char, width=width) -def test_align_text_term_width(): +def test_align_text_term_width() -> None: import shutil text = 'foo' @@ -628,7 +628,7 @@ def test_align_text_term_width(): assert aligned == text + expected_fill -def test_align_left(): +def test_align_left() -> None: text = 'foo' fill_char = '-' width = 5 @@ -636,7 +636,7 @@ def test_align_left(): assert aligned == text + fill_char + fill_char -def test_align_left_multiline(): +def test_align_left_multiline() -> None: # Without style text = "foo\nshoes" fill_char = '-' @@ -661,7 +661,7 @@ def test_align_left_multiline(): assert aligned == expected -def test_align_left_wide_text(): +def test_align_left_wide_text() -> None: text = '苹' fill_char = '-' width = 4 @@ -669,7 +669,7 @@ def test_align_left_wide_text(): assert aligned == text + fill_char + fill_char -def test_align_left_wide_fill(): +def test_align_left_wide_fill() -> None: text = 'foo' fill_char = '苹' width = 5 @@ -677,7 +677,7 @@ def test_align_left_wide_fill(): assert aligned == text + fill_char -def test_align_left_wide_fill_needs_padding(): +def test_align_left_wide_fill_needs_padding() -> None: """Test when fill_char's display width does not divide evenly into gap""" text = 'foo' fill_char = '苹' @@ -686,7 +686,7 @@ def test_align_left_wide_fill_needs_padding(): assert aligned == text + fill_char + ' ' -def test_align_center(): +def test_align_center() -> None: text = 'foo' fill_char = '-' width = 5 @@ -694,7 +694,7 @@ def test_align_center(): assert aligned == fill_char + text + fill_char -def test_align_center_multiline(): +def test_align_center_multiline() -> None: # Without style text = "foo\nshoes" fill_char = '-' @@ -719,7 +719,7 @@ def test_align_center_multiline(): assert aligned == expected -def test_align_center_wide_text(): +def test_align_center_wide_text() -> None: text = '苹' fill_char = '-' width = 4 @@ -727,7 +727,7 @@ def test_align_center_wide_text(): assert aligned == fill_char + text + fill_char -def test_align_center_wide_fill(): +def test_align_center_wide_fill() -> None: text = 'foo' fill_char = '苹' width = 7 @@ -735,7 +735,7 @@ def test_align_center_wide_fill(): assert aligned == fill_char + text + fill_char -def test_align_center_wide_fill_needs_right_padding(): +def test_align_center_wide_fill_needs_right_padding() -> None: """Test when fill_char's display width does not divide evenly into right gap""" text = 'foo' fill_char = '苹' @@ -744,7 +744,7 @@ def test_align_center_wide_fill_needs_right_padding(): assert aligned == fill_char + text + fill_char + ' ' -def test_align_center_wide_fill_needs_left_and_right_padding(): +def test_align_center_wide_fill_needs_left_and_right_padding() -> None: """Test when fill_char's display width does not divide evenly into either gap""" text = 'foo' fill_char = '苹' @@ -753,7 +753,7 @@ def test_align_center_wide_fill_needs_left_and_right_padding(): assert aligned == fill_char + ' ' + text + fill_char + ' ' -def test_align_right(): +def test_align_right() -> None: text = 'foo' fill_char = '-' width = 5 @@ -761,7 +761,7 @@ def test_align_right(): assert aligned == fill_char + fill_char + text -def test_align_right_multiline(): +def test_align_right_multiline() -> None: # Without style text = "foo\nshoes" fill_char = '-' @@ -786,7 +786,7 @@ def test_align_right_multiline(): assert aligned == expected -def test_align_right_wide_text(): +def test_align_right_wide_text() -> None: text = '苹' fill_char = '-' width = 4 @@ -794,7 +794,7 @@ def test_align_right_wide_text(): assert aligned == fill_char + fill_char + text -def test_align_right_wide_fill(): +def test_align_right_wide_fill() -> None: text = 'foo' fill_char = '苹' width = 5 @@ -802,7 +802,7 @@ def test_align_right_wide_fill(): assert aligned == fill_char + text -def test_align_right_wide_fill_needs_padding(): +def test_align_right_wide_fill_needs_padding() -> None: """Test when fill_char's display width does not divide evenly into gap""" text = 'foo' fill_char = '苹' @@ -811,51 +811,51 @@ def test_align_right_wide_fill_needs_padding(): assert aligned == fill_char + ' ' + text -def test_to_bool_str_true(): +def test_to_bool_str_true() -> None: assert cu.to_bool('true') assert cu.to_bool('True') assert cu.to_bool('TRUE') assert cu.to_bool('tRuE') -def test_to_bool_str_false(): +def test_to_bool_str_false() -> None: assert not cu.to_bool('false') assert not cu.to_bool('False') assert not cu.to_bool('FALSE') assert not cu.to_bool('fAlSe') -def test_to_bool_str_invalid(): +def test_to_bool_str_invalid() -> None: with pytest.raises(ValueError): # noqa: PT011 cu.to_bool('other') -def test_to_bool_bool(): +def test_to_bool_bool() -> None: assert cu.to_bool(True) assert not cu.to_bool(False) -def test_to_bool_int(): +def test_to_bool_int() -> None: assert cu.to_bool(1) assert cu.to_bool(-1) assert not cu.to_bool(0) -def test_to_bool_float(): +def test_to_bool_float() -> None: assert cu.to_bool(2.35) assert cu.to_bool(0.25) assert cu.to_bool(-3.1415) assert not cu.to_bool(0) -def test_find_editor_specified(): +def test_find_editor_specified() -> None: expected_editor = os.path.join('fake_dir', 'editor') with mock.patch.dict(os.environ, {'EDITOR': expected_editor}): editor = cu.find_editor() assert editor == expected_editor -def test_find_editor_not_specified(): +def test_find_editor_not_specified() -> None: # Use existing path env setting. Something in the editor list should be found. editor = cu.find_editor() assert editor @@ -866,26 +866,26 @@ def test_find_editor_not_specified(): assert editor is None -def test_similarity(): +def test_similarity() -> None: suggested_command = cu.suggest_similar("comand", ["command", "UNRELATED", "NOT_SIMILAR"]) assert suggested_command == "command" suggested_command = cu.suggest_similar("command", ["COMMAND", "acommands"]) assert suggested_command == "COMMAND" -def test_similarity_without_good_canididates(): +def test_similarity_without_good_canididates() -> None: suggested_command = cu.suggest_similar("comand", ["UNRELATED", "NOT_SIMILAR"]) assert suggested_command is None suggested_command = cu.suggest_similar("comand", []) assert suggested_command is None -def test_similarity_overwrite_function(): +def test_similarity_overwrite_function() -> None: options = ["history", "test"] suggested_command = cu.suggest_similar("test", options) assert suggested_command == 'test' - def custom_similarity_function(s1, s2): + def custom_similarity_function(s1, s2) -> float: return 1.0 if 'history' in (s1, s2) else 0.0 suggested_command = cu.suggest_similar("test", options, similarity_function_to_use=custom_similarity_function) diff --git a/tests/test_utils_defining_class.py b/tests/test_utils_defining_class.py index bf92385bb..fa24d202f 100644 --- a/tests/test_utils_defining_class.py +++ b/tests/test_utils_defining_class.py @@ -8,40 +8,40 @@ class ParentClass: - def func_with_overrides(self): + def func_with_overrides(self) -> None: pass - def parent_only_func(self, param1, param2): + def parent_only_func(self, param1, param2) -> None: pass class ChildClass(ParentClass): - def func_with_overrides(self): + def func_with_overrides(self) -> None: super().func_with_overrides() - def child_function(self): + def child_function(self) -> None: pass - def lambda1(): + def lambda1() -> int: return 1 - def lambda2(): + def lambda2() -> int: return 2 @classmethod - def class_method(cls): + def class_method(cls) -> None: pass @staticmethod - def static_meth(): + def static_meth() -> None: pass -def func_not_in_class(): +def func_not_in_class() -> None: pass -def test_get_defining_class(): +def test_get_defining_class() -> None: parent_instance = ParentClass() child_instance = ChildClass() diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 03e17e731..32db784b2 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -177,7 +177,7 @@ def get_endidx(): class WithCommandSets(ExternalTestMixin, cmd2.Cmd): """Class for testing custom help_* methods which override docstring help.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/tests_isolated/test_commandset/test_argparse_subcommands.py b/tests_isolated/test_commandset/test_argparse_subcommands.py index f7b218576..cce5d93ad 100644 --- a/tests_isolated/test_commandset/test_argparse_subcommands.py +++ b/tests_isolated/test_commandset/test_argparse_subcommands.py @@ -15,19 +15,19 @@ class SubcommandSet(cmd2.CommandSet): """Example cmd2 application where we a base command which has a couple subcommands.""" - def __init__(self, dummy): + def __init__(self, dummy) -> None: super().__init__() # subcommand functions for the base command - def base_foo(self, args): + def base_foo(self, args) -> None: """foo subcommand of base command""" self._cmd.poutput(args.x * args.y) - def base_bar(self, args): + def base_bar(self, args) -> None: """bar subcommand of base command""" self._cmd.poutput(f'(({args.z}))') - def base_helpless(self, args): + def base_helpless(self, args) -> None: """helpless subcommand of base command""" self._cmd.poutput(f'(({args.z}))') @@ -55,7 +55,7 @@ def base_helpless(self, args): parser_helpless.set_defaults(func=base_bar) @cmd2.with_argparser(base_parser) - def do_base(self, args): + def do_base(self, args) -> None: """Base command help""" # Call whatever subcommand function was selected func = getattr(args, 'func') @@ -68,30 +68,30 @@ def subcommand_app(): return app -def test_subcommand_foo(subcommand_app): +def test_subcommand_foo(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'base foo -x2 5.0') assert out == ['10.0'] -def test_subcommand_bar(subcommand_app): +def test_subcommand_bar(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'base bar baz') assert out == ['((baz))'] -def test_subcommand_invalid(subcommand_app): +def test_subcommand_invalid(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'base baz') assert err[0].startswith('Usage: base') assert err[1].startswith("Error: argument SUBCOMMAND: invalid choice: 'baz'") -def test_subcommand_base_help(subcommand_app): +def test_subcommand_base_help(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'help base') assert out[0].startswith('Usage: base') assert out[1] == '' assert out[2] == 'Base command help' -def test_subcommand_help(subcommand_app): +def test_subcommand_help(subcommand_app) -> None: # foo has no aliases out, err = run_cmd(subcommand_app, 'help base foo') assert out[0].startswith('Usage: base foo') @@ -131,6 +131,6 @@ def test_subcommand_help(subcommand_app): assert out[2] == 'positional arguments:' -def test_subcommand_invalid_help(subcommand_app): +def test_subcommand_invalid_help(subcommand_app) -> None: out, err = run_cmd(subcommand_app, 'help base baz') assert out[0].startswith('Usage: base') diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index 30b525580..0e16f109f 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -15,7 +15,7 @@ class MyBaseCommandSet(CommandSet): """Defines a default category for all sub-class CommandSets""" - def __init__(self, _: Any): + def __init__(self, _: Any) -> None: super().__init__() @@ -24,10 +24,10 @@ class ChildInheritsParentCategories(MyBaseCommandSet): This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category' """ - def do_hello(self, _: cmd2.Statement): + def do_hello(self, _: cmd2.Statement) -> None: self._cmd.poutput('Hello') - def do_world(self, _: cmd2.Statement): + def do_world(self, _: cmd2.Statement) -> None: self._cmd.poutput('World') @@ -38,7 +38,7 @@ class ChildOverridesParentCategoriesNonHeritable(MyBaseCommandSet): CommandSet will not inherit this category and will, instead, inherit 'Default Category' """ - def do_goodbye(self, _: cmd2.Statement): + def do_goodbye(self, _: cmd2.Statement) -> None: self._cmd.poutput('Goodbye') @@ -48,7 +48,7 @@ class GrandchildInheritsGrandparentCategory(ChildOverridesParentCategoriesNonHer by the grandparent class. """ - def do_aloha(self, _: cmd2.Statement): + def do_aloha(self, _: cmd2.Statement) -> None: self._cmd.poutput('Aloha') @@ -59,7 +59,7 @@ class ChildOverridesParentCategories(MyBaseCommandSet): category declaration. """ - def do_bonjour(self, _: cmd2.Statement): + def do_bonjour(self, _: cmd2.Statement) -> None: self._cmd.poutput('Bonjour') @@ -69,7 +69,7 @@ class GrandchildInheritsHeritable(ChildOverridesParentCategories): CommandSet will be categorized under 'Heritable Category' """ - def do_monde(self, _: cmd2.Statement): + def do_monde(self, _: cmd2.Statement) -> None: self._cmd.poutput('Monde') @@ -78,14 +78,14 @@ class ExampleApp(cmd2.Cmd): Example to demonstrate heritable default categories """ - def __init__(self): + def __init__(self) -> None: super().__init__(auto_load_commands=False) - def do_something(self, arg): + def do_something(self, arg) -> None: self.poutput('this is the something command') -def test_heritable_categories(): +def test_heritable_categories() -> None: app = ExampleApp() base_cs = MyBaseCommandSet(0) diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 2205ec56b..d869e370d 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -45,10 +45,10 @@ def on_unregistered(self) -> None: super().on_unregistered() print("in on_unregistered now") - def do_apple(self, statement: cmd2.Statement): + def do_apple(self, statement: cmd2.Statement) -> None: self._cmd.poutput('Apple!') - def do_banana(self, statement: cmd2.Statement): + def do_banana(self, statement: cmd2.Statement) -> None: """Banana Command""" self._cmd.poutput('Banana!!') @@ -56,18 +56,18 @@ def do_banana(self, statement: cmd2.Statement): cranberry_parser.add_argument('arg1', choices=['lemonade', 'juice', 'sauce']) @cmd2.with_argparser(cranberry_parser, with_unknown_args=True) - def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]): + def do_cranberry(self, ns: argparse.Namespace, unknown: list[str]) -> None: self._cmd.poutput(f'Cranberry {ns.arg1}!!') if unknown and len(unknown): self._cmd.poutput('Unknown: ' + ', '.join(['{}'] * len(unknown)).format(*unknown)) self._cmd.last_result = {'arg1': ns.arg1, 'unknown': unknown} - def help_cranberry(self): + def help_cranberry(self) -> None: self._cmd.stdout.write('This command does diddly squat...\n') @cmd2.with_argument_list @cmd2.with_category('Also Alone') - def do_durian(self, args: list[str]): + def do_durian(self, args: list[str]) -> None: """Durian Command""" self._cmd.poutput(f'{len(args)} Arguments: ') self._cmd.poutput(', '.join(['{}'] * len(args)).format(*args)) @@ -81,7 +81,7 @@ def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> lis @cmd2.with_category('Alone') @cmd2.with_argparser(elderberry_parser) - def do_elderberry(self, ns: argparse.Namespace): + def do_elderberry(self, ns: argparse.Namespace) -> None: self._cmd.poutput(f'Elderberry {ns.arg1}!!') self._cmd.last_result = {'arg1': ns.arg1} @@ -107,22 +107,22 @@ def subcmd_func(self, args: argparse.Namespace) -> None: @cmd2.with_default_category('Command Set B') class CommandSetB(CommandSetBase): - def __init__(self, arg1): + def __init__(self, arg1) -> None: super().__init__() self._arg1 = arg1 - def do_aardvark(self, statement: cmd2.Statement): + def do_aardvark(self, statement: cmd2.Statement) -> None: self._cmd.poutput('Aardvark!') - def do_bat(self, statement: cmd2.Statement): + def do_bat(self, statement: cmd2.Statement) -> None: """Banana Command""" self._cmd.poutput('Bat!!') - def do_crocodile(self, statement: cmd2.Statement): + def do_crocodile(self, statement: cmd2.Statement) -> None: self._cmd.poutput('Crocodile!!') -def test_autoload_commands(command_sets_app): +def test_autoload_commands(command_sets_app) -> None: # verifies that, when autoload is enabled, CommandSets and registered functions all show up cmds_cats, cmds_doc, cmds_undoc, help_topics = command_sets_app._build_command_info() @@ -144,16 +144,16 @@ def test_autoload_commands(command_sets_app): assert 'Command Set B' not in cmds_cats -def test_command_synonyms(): +def test_command_synonyms() -> None: """Test the use of command synonyms in CommandSets""" class SynonymCommandSet(cmd2.CommandSet): - def __init__(self, arg1): + def __init__(self, arg1) -> None: super().__init__() self._arg1 = arg1 @cmd2.with_argparser(cmd2.Cmd2ArgumentParser(description="Native Command")) - def do_builtin(self, _): + def do_builtin(self, _) -> None: pass # Create a synonym to a command inside of this CommandSet @@ -190,7 +190,7 @@ def do_builtin(self, _): assert normalize(alias_parser.format_help())[0] in out -def test_custom_construct_commandsets(): +def test_custom_construct_commandsets() -> None: command_set_b = CommandSetB('foo') # Verify that _cmd cannot be accessed until CommandSet is registered. @@ -232,7 +232,7 @@ def test_custom_construct_commandsets(): assert command_set_2 not in matches -def test_load_commands(command_sets_manual, capsys): +def test_load_commands(command_sets_manual, capsys) -> None: # now install a command set and verify the commands are now present cmd_set = CommandSetA() @@ -298,7 +298,7 @@ def test_load_commands(command_sets_manual, capsys): assert 'cranberry' in cmds_cats['Fruits'] -def test_commandset_decorators(command_sets_app): +def test_commandset_decorators(command_sets_app) -> None: result = command_sets_app.app_cmd('cranberry juice extra1 extra2') assert result is not None assert result.data is not None @@ -328,7 +328,7 @@ def test_commandset_decorators(command_sets_app): assert result.data is None -def test_load_commandset_errors(command_sets_manual, capsys): +def test_load_commandset_errors(command_sets_manual, capsys) -> None: cmd_set = CommandSetA() # create a conflicting command before installing CommandSet to verify rollback behavior @@ -383,7 +383,7 @@ def test_load_commandset_errors(command_sets_manual, capsys): class LoadableBase(cmd2.CommandSet): - def __init__(self, dummy): + def __init__(self, dummy) -> None: super().__init__() self._dummy = dummy # prevents autoload self._cut_called = False @@ -397,7 +397,7 @@ def namespace_provider(self) -> argparse.Namespace: return ns @cmd2.with_argparser(cut_parser) - def do_cut(self, ns: argparse.Namespace): + def do_cut(self, ns: argparse.Namespace) -> None: """Cut something""" handler = ns.cmd2_handler.get() if handler is not None: @@ -413,7 +413,7 @@ def do_cut(self, ns: argparse.Namespace): stir_subparsers = stir_parser.add_subparsers(title='item', help='what to stir') @cmd2.with_argparser(stir_parser, ns_provider=namespace_provider) - def do_stir(self, ns: argparse.Namespace): + def do_stir(self, ns: argparse.Namespace) -> None: """Stir something""" if not ns.cut_called: self._cmd.poutput('Need to cut before stirring') @@ -433,7 +433,7 @@ def do_stir(self, ns: argparse.Namespace): stir_pasta_parser.add_subparsers(title='style', help='Stir style') @cmd2.as_subcommand_to('stir', 'pasta', stir_pasta_parser) - def stir_pasta(self, ns: argparse.Namespace): + def stir_pasta(self, ns: argparse.Namespace) -> None: handler = ns.cmd2_handler.get() if handler is not None: # Call whatever subcommand function was selected @@ -443,11 +443,11 @@ def stir_pasta(self, ns: argparse.Namespace): class LoadableBadBase(cmd2.CommandSet): - def __init__(self, dummy): + def __init__(self, dummy) -> None: super().__init__() self._dummy = dummy # prevents autoload - def do_cut(self, ns: argparse.Namespace): + def do_cut(self, ns: argparse.Namespace) -> None: """Cut something""" handler = ns.cmd2_handler.get() if handler is not None: @@ -461,24 +461,24 @@ def do_cut(self, ns: argparse.Namespace): @cmd2.with_default_category('Fruits') class LoadableFruits(cmd2.CommandSet): - def __init__(self, dummy): + def __init__(self, dummy) -> None: super().__init__() self._dummy = dummy # prevents autoload - def do_apple(self, _: cmd2.Statement): + def do_apple(self, _: cmd2.Statement) -> None: self._cmd.poutput('Apple') banana_parser = cmd2.Cmd2ArgumentParser() banana_parser.add_argument('direction', choices=['discs', 'lengthwise']) @cmd2.as_subcommand_to('cut', 'banana', banana_parser, help='Cut banana', aliases=['bananer']) - def cut_banana(self, ns: argparse.Namespace): + def cut_banana(self, ns: argparse.Namespace) -> None: """Cut banana""" self._cmd.poutput('cutting banana: ' + ns.direction) class LoadablePastaStir(cmd2.CommandSet): - def __init__(self, dummy): + def __init__(self, dummy) -> None: super().__init__() self._dummy = dummy # prevents autoload @@ -486,17 +486,17 @@ def __init__(self, dummy): stir_pasta_vigor_parser.add_argument('frequency') @cmd2.as_subcommand_to('stir pasta', 'vigorously', stir_pasta_vigor_parser) - def stir_pasta_vigorously(self, ns: argparse.Namespace): + def stir_pasta_vigorously(self, ns: argparse.Namespace) -> None: self._cmd.poutput('stir the pasta vigorously') @cmd2.with_default_category('Vegetables') class LoadableVegetables(cmd2.CommandSet): - def __init__(self, dummy): + def __init__(self, dummy) -> None: super().__init__() self._dummy = dummy # prevents autoload - def do_arugula(self, _: cmd2.Statement): + def do_arugula(self, _: cmd2.Statement) -> None: self._cmd.poutput('Arugula') def complete_style_arg(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: @@ -506,11 +506,11 @@ def complete_style_arg(self, text: str, line: str, begidx: int, endidx: int) -> bokchoy_parser.add_argument('style', completer=complete_style_arg) @cmd2.as_subcommand_to('cut', 'bokchoy', bokchoy_parser) - def cut_bokchoy(self, ns: argparse.Namespace): + def cut_bokchoy(self, ns: argparse.Namespace) -> None: self._cmd.poutput('Bok Choy: ' + ns.style) -def test_subcommands(command_sets_manual): +def test_subcommands(command_sets_manual) -> None: base_cmds = LoadableBase(1) badbase_cmds = LoadableBadBase(1) fruit_cmds = LoadableFruits(1) @@ -631,11 +631,11 @@ def test_subcommands(command_sets_manual): command_sets_manual.unregister_command_set(base_cmds) -def test_commandset_sigint(command_sets_manual): +def test_commandset_sigint(command_sets_manual) -> None: # shows that the command is able to continue execution if the sigint_handler # returns True that we've handled interrupting the command. class SigintHandledCommandSet(cmd2.CommandSet): - def do_foo(self, _): + def do_foo(self, _) -> None: self._cmd.poutput('in foo') self._cmd.sigint_handler(signal.SIGINT, None) self._cmd.poutput('end of foo') @@ -651,7 +651,7 @@ def sigint_handler(self) -> bool: # shows that the command is interrupted if we don't report we've handled the sigint class SigintUnhandledCommandSet(cmd2.CommandSet): - def do_bar(self, _): + def do_bar(self, _) -> None: self._cmd.poutput('in do bar') self._cmd.sigint_handler(signal.SIGINT, None) self._cmd.poutput('end of do bar') @@ -663,7 +663,7 @@ def do_bar(self, _): assert 'end of do bar' not in out.stdout -def test_nested_subcommands(command_sets_manual): +def test_nested_subcommands(command_sets_manual) -> None: base_cmds = LoadableBase(1) pasta_cmds = LoadablePastaStir(1) @@ -678,7 +678,7 @@ def test_nested_subcommands(command_sets_manual): command_sets_manual.unregister_command_set(base_cmds) class BadNestedSubcommands(cmd2.CommandSet): - def __init__(self, dummy): + def __init__(self, dummy) -> None: super().__init__() self._dummy = dummy # prevents autoload @@ -687,7 +687,7 @@ def __init__(self, dummy): # stir sauce doesn't exist anywhere, this should fail @cmd2.as_subcommand_to('stir sauce', 'vigorously', stir_pasta_vigor_parser) - def stir_pasta_vigorously(self, ns: argparse.Namespace): + def stir_pasta_vigorously(self, ns: argparse.Namespace) -> None: self._cmd.poutput('stir the pasta vigorously') with pytest.raises(CommandSetRegistrationError): @@ -711,14 +711,14 @@ def stir_pasta_vigorously(self, ns: argparse.Namespace): class AppWithSubCommands(cmd2.Cmd): """Class for testing usage of `as_subcommand_to` decorator directly in a Cmd2 subclass.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) cut_parser = cmd2.Cmd2ArgumentParser() cut_subparsers = cut_parser.add_subparsers(title='item', help='item to cut') @cmd2.with_argparser(cut_parser) - def do_cut(self, ns: argparse.Namespace): + def do_cut(self, ns: argparse.Namespace) -> None: """Cut something""" handler = ns.cmd2_handler.get() if handler is not None: @@ -733,7 +733,7 @@ def do_cut(self, ns: argparse.Namespace): banana_parser.add_argument('direction', choices=['discs', 'lengthwise']) @cmd2.as_subcommand_to('cut', 'banana', banana_parser, help='Cut banana', aliases=['bananer']) - def cut_banana(self, ns: argparse.Namespace): + def cut_banana(self, ns: argparse.Namespace) -> None: """Cut banana""" self.poutput('cutting banana: ' + ns.direction) @@ -744,7 +744,7 @@ def complete_style_arg(self, text: str, line: str, begidx: int, endidx: int) -> bokchoy_parser.add_argument('style', completer=complete_style_arg) @cmd2.as_subcommand_to('cut', 'bokchoy', bokchoy_parser) - def cut_bokchoy(self, _: argparse.Namespace): + def cut_bokchoy(self, _: argparse.Namespace) -> None: self.poutput('Bok Choy') @@ -754,7 +754,7 @@ def static_subcommands_app(): return app -def test_static_subcommands(static_subcommands_app): +def test_static_subcommands(static_subcommands_app) -> None: cmds_cats, cmds_doc, cmds_undoc, help_topics = static_subcommands_app._build_command_info() assert 'Fruits' in cmds_cats @@ -788,7 +788,7 @@ class SupportFuncProvider(cmd2.CommandSet): states = ['alabama', 'alaska', 'arizona', 'arkansas', 'california', 'colorado', 'connecticut', 'delaware'] - def __init__(self, dummy): + def __init__(self, dummy) -> None: """dummy variable prevents this from being autoloaded in other tests""" super().__init__() @@ -804,7 +804,7 @@ class SupportFuncUserSubclass1(SupportFuncProvider): parser.add_argument('state', type=str, completer=SupportFuncProvider.complete_states) @cmd2.with_argparser(parser) - def do_user_sub1(self, ns: argparse.Namespace): + def do_user_sub1(self, ns: argparse.Namespace) -> None: self._cmd.poutput(f'something {ns.state}') @@ -815,14 +815,14 @@ class SupportFuncUserSubclass2(SupportFuncProvider): parser.add_argument('state', type=str, completer=SupportFuncProvider.complete_states) @cmd2.with_argparser(parser) - def do_user_sub2(self, ns: argparse.Namespace): + def do_user_sub2(self, ns: argparse.Namespace) -> None: self._cmd.poutput(f'something {ns.state}') class SupportFuncUserUnrelated(cmd2.CommandSet): """A CommandSet that isn't related to SupportFuncProvider which uses its support function""" - def __init__(self, dummy): + def __init__(self, dummy) -> None: """dummy variable prevents this from being autoloaded in other tests""" super().__init__() @@ -830,11 +830,11 @@ def __init__(self, dummy): parser.add_argument('state', type=str, completer=SupportFuncProvider.complete_states) @cmd2.with_argparser(parser) - def do_user_unrelated(self, ns: argparse.Namespace): + def do_user_unrelated(self, ns: argparse.Namespace) -> None: self._cmd.poutput(f'something {ns.state}') -def test_cross_commandset_completer(command_sets_manual, capsys): +def test_cross_commandset_completer(command_sets_manual, capsys) -> None: global complete_states_expected_self # This tests the different ways to locate the matching CommandSet when completing an argparse argument. # Exercises the 3 cases in cmd2.Cmd._resolve_func_self() which is called during argparse tab completion. @@ -967,7 +967,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys): class CommandSetWithPathComplete(cmd2.CommandSet): - def __init__(self, dummy): + def __init__(self, dummy) -> None: """dummy variable prevents this from being autoloaded in other tests""" super().__init__() @@ -975,11 +975,11 @@ def __init__(self, dummy): parser.add_argument('path', nargs='+', help='paths', completer=cmd2.Cmd.path_complete) @cmd2.with_argparser(parser) - def do_path(self, app: cmd2.Cmd, args): + def do_path(self, app: cmd2.Cmd, args) -> None: app.poutput(args.path) -def test_path_complete(command_sets_manual): +def test_path_complete(command_sets_manual) -> None: test_set = CommandSetWithPathComplete(1) command_sets_manual.register_command_set(test_set) @@ -993,25 +993,25 @@ def test_path_complete(command_sets_manual): assert first_match is not None -def test_bad_subcommand(): +def test_bad_subcommand() -> None: class BadSubcommandApp(cmd2.Cmd): """Class for testing usage of `as_subcommand_to` decorator directly in a Cmd2 subclass.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) cut_parser = cmd2.Cmd2ArgumentParser() cut_subparsers = cut_parser.add_subparsers(title='item', help='item to cut') @cmd2.with_argparser(cut_parser) - def do_cut(self, ns: argparse.Namespace): + def do_cut(self, ns: argparse.Namespace) -> None: """Cut something""" banana_parser = cmd2.Cmd2ArgumentParser() banana_parser.add_argument('direction', choices=['discs', 'lengthwise']) @cmd2.as_subcommand_to('cut', 'bad name', banana_parser, help='This should fail') - def cut_banana(self, ns: argparse.Namespace): + def cut_banana(self, ns: argparse.Namespace) -> None: """Cut banana""" self.poutput('cutting banana: ' + ns.direction) @@ -1019,15 +1019,15 @@ def cut_banana(self, ns: argparse.Namespace): BadSubcommandApp() -def test_commandset_settables(): +def test_commandset_settables() -> None: # Define an arbitrary class with some attribute class Arbitrary: - def __init__(self): + def __init__(self) -> None: self.some_value = 5 # Declare a CommandSet with a settable of some arbitrary property class WithSettablesA(CommandSetBase): - def __init__(self): + def __init__(self) -> None: super().__init__() self._arbitrary = Arbitrary() @@ -1046,7 +1046,7 @@ def __init__(self): # Declare a CommandSet with an empty settable prefix class WithSettablesNoPrefix(CommandSetBase): - def __init__(self): + def __init__(self) -> None: super().__init__() self._arbitrary = Arbitrary() @@ -1065,7 +1065,7 @@ def __init__(self): # Declare a commandset with duplicate settable name class WithSettablesB(CommandSetBase): - def __init__(self): + def __init__(self) -> None: super().__init__() self._arbitrary = Arbitrary() @@ -1198,7 +1198,7 @@ def __init__(self): class NsProviderSet(cmd2.CommandSet): # CommandSet which implements a namespace provider - def __init__(self, dummy): + def __init__(self, dummy) -> None: # Use dummy argument so this won't be autoloaded by other tests super().__init__() @@ -1221,7 +1221,7 @@ def do_test_ns(self, args: argparse.Namespace) -> None: self.last_result = args.self -def test_ns_provider(): +def test_ns_provider() -> None: """This exercises code in with_argparser() decorator that calls namespace providers""" ns_provider_set = NsProviderSet(1) app = NsProviderApp(auto_load_commands=False) From fd2603eee6fe277e79c990165edce0fd92f64bc4 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 12:27:39 -0400 Subject: [PATCH 59/79] Replace typing.Never with typing.NoReturn since Never wasn't introduced until Python 3.11 --- tests/test_cmd2.py | 8 ++++---- tests/test_completion.py | 4 ++-- tests/test_plugin.py | 4 ++-- tests/test_transcript.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 878e9d9c1..5e35cd8d3 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -11,7 +11,7 @@ from code import ( InteractiveConsole, ) -from typing import Never +from typing import NoReturn from unittest import ( mock, ) @@ -512,7 +512,7 @@ def test_runcmds_plus_hooks_ctrl_c(base_app, capsys) -> None: """Test Ctrl-C while in runcmds_plus_hooks""" import types - def do_keyboard_interrupt(self, _) -> Never: + def do_keyboard_interrupt(self, _) -> NoReturn: raise KeyboardInterrupt('Interrupting this command') setattr(base_app, 'do_keyboard_interrupt', types.MethodType(do_keyboard_interrupt, base_app)) @@ -603,7 +603,7 @@ def test_system_exit_in_command(base_app, capsys) -> None: exit_code = 5 - def do_system_exit(self, _) -> Never: + def do_system_exit(self, _) -> NoReturn: raise SystemExit(exit_code) setattr(base_app, 'do_system_exit', types.MethodType(do_system_exit, base_app)) @@ -619,7 +619,7 @@ def test_passthrough_exception_in_command(base_app) -> None: expected_err = "Pass me up" - def do_passthrough(self, _) -> Never: + def do_passthrough(self, _) -> NoReturn: wrapped_ex = OSError(expected_err) raise exceptions.PassThroughException(wrapped_ex=wrapped_ex) diff --git a/tests/test_completion.py b/tests/test_completion.py index ab6284f84..e59e91520 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -8,7 +8,7 @@ import enum import os import sys -from typing import Never +from typing import NoReturn from unittest import ( mock, ) @@ -97,7 +97,7 @@ def complete_test_sort_key(self, text, line, begidx, endidx): def do_test_raise_exception(self, args) -> None: pass - def complete_test_raise_exception(self, text, line, begidx, endidx) -> Never: + def complete_test_raise_exception(self, text, line, begidx, endidx) -> NoReturn: raise IndexError("You are out of bounds!!") def do_test_multiline(self, args) -> None: diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 54b5ffdf5..b2043b9a0 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -4,7 +4,7 @@ import argparse import sys -from typing import Never +from typing import NoReturn from unittest import ( mock, ) @@ -273,7 +273,7 @@ def do_say(self, statement) -> None: """Repeat back the arguments""" self.poutput(statement) - def do_skip_postcmd_hooks(self, _) -> Never: + def do_skip_postcmd_hooks(self, _) -> NoReturn: self.poutput("In do_skip_postcmd_hooks") raise exceptions.SkipPostcommandHooks diff --git a/tests/test_transcript.py b/tests/test_transcript.py index f7df29cfa..f14433b2f 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -7,7 +7,7 @@ import re import sys import tempfile -from typing import Never +from typing import NoReturn from unittest import ( mock, ) @@ -90,7 +90,7 @@ def do_mumble(self, opts, arg) -> None: def do_nothing(self, statement) -> None: """Do nothing and output nothing""" - def do_keyboard_interrupt(self, _) -> Never: + def do_keyboard_interrupt(self, _) -> NoReturn: raise KeyboardInterrupt('Interrupting this command') From 0d48428ead7de4bd2f4b5c3b2cce5602d2d6bc9d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 12:40:40 -0400 Subject: [PATCH 60/79] Apply automated docstring refactoring --- cmd2/ansi.py | 79 +++------ cmd2/argparse_completer.py | 35 ++-- cmd2/argparse_custom.py | 90 ++++------ cmd2/clipboard.py | 4 +- cmd2/cmd2.py | 162 ++++++------------ cmd2/command_definition.py | 34 ++-- cmd2/decorators.py | 32 ++-- cmd2/exceptions.py | 21 +-- cmd2/history.py | 20 +-- cmd2/parsing.py | 23 +-- cmd2/py_bridge.py | 10 +- cmd2/rl_utils.py | 26 +-- cmd2/table_creator.py | 88 ++++------ cmd2/utils.py | 103 ++++------- examples/arg_decorators.py | 3 +- examples/argparse_completion.py | 10 +- examples/async_printing.py | 14 +- examples/basic_completion.py | 6 +- examples/cmd_as_argument.py | 4 +- examples/colors.py | 3 +- examples/custom_parser.py | 4 +- examples/decorator_example.py | 4 +- examples/default_categories.py | 24 +-- examples/environment.py | 4 +- examples/example.py | 3 +- examples/exit_code.py | 3 +- examples/first_app.py | 21 ++- examples/hello_cmd2.py | 4 +- examples/help_categories.py | 6 +- examples/hooks.py | 3 +- examples/migrating.py | 4 +- examples/modular_commands/commandset_basic.py | 7 +- .../modular_commands/commandset_complex.py | 4 +- .../modular_commands/commandset_custominit.py | 4 +- examples/modular_commands_basic.py | 8 +- examples/modular_commands_dynamic.py | 7 +- examples/modular_commands_main.py | 3 +- examples/modular_subcommands.py | 4 +- examples/override_parser.py | 3 +- examples/pirate.py | 6 +- examples/read_input.py | 4 +- examples/remove_settable.py | 4 +- examples/scripts/conditional.py | 3 +- examples/scripts/save_help_text.py | 7 +- examples/scripts/script.py | 4 +- examples/subcommands.py | 10 +- examples/table_creation.py | 5 +- pyproject.toml | 2 +- tests/conftest.py | 7 +- tests/pyscript/raises_exception.py | 4 +- tests/pyscript/recursive.py | 4 +- tests/script.py | 4 +- tests/test_ansi.py | 4 +- tests/test_argparse.py | 16 +- tests/test_argparse_completer.py | 6 +- tests/test_argparse_custom.py | 7 +- tests/test_cmd2.py | 10 +- tests/test_completion.py | 16 +- tests/test_history.py | 4 +- tests/test_parsing.py | 7 +- tests/test_plugin.py | 7 +- tests/test_run_pyscript.py | 7 +- tests/test_table_creator.py | 7 +- tests/test_transcript.py | 4 +- tests/test_utils.py | 4 +- tests/test_utils_defining_class.py | 4 +- tests_isolated/test_commandset/conftest.py | 7 +- .../test_argparse_subcommands.py | 10 +- .../test_commandset/test_categories.py | 24 +-- .../test_commandset/test_commandset.py | 10 +- 70 files changed, 361 insertions(+), 744 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index f5765bfde..4abcd87be 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -1,5 +1,4 @@ -""" -Support for ANSI escape sequences which are used for things like applying style to text, +"""Support for ANSI escape sequences which are used for things like applying style to text, setting the window title, and asynchronous alerts. """ @@ -83,8 +82,7 @@ def __repr__(self) -> str: def strip_style(text: str) -> str: - """ - Strip ANSI style sequences from a string. + """Strip ANSI style sequences from a string. :param text: string which may contain ANSI style sequences :return: the same string with any ANSI style sequences removed @@ -93,8 +91,7 @@ def strip_style(text: str) -> str: def style_aware_wcswidth(text: str) -> int: - """ - Wrap wcswidth to make it compatible with strings that contain ANSI style sequences. + """Wrap wcswidth to make it compatible with strings that contain ANSI style sequences. This is intended for single line strings. If text contains a newline, this function will return -1. For multiline strings, call widest_line() instead. @@ -108,8 +105,7 @@ def style_aware_wcswidth(text: str) -> int: def widest_line(text: str) -> int: - """ - Return the width of the widest line in a multiline string. This wraps style_aware_wcswidth() + """Return the width of the widest line in a multiline string. This wraps style_aware_wcswidth() so it handles ANSI style sequences and has the same restrictions on non-printable characters. :param text: the string being measured @@ -128,8 +124,7 @@ def widest_line(text: str) -> int: def style_aware_write(fileobj: IO[str], msg: str) -> None: - """ - Write a string to a fileobject and strip its ANSI style sequences if required by allow_style setting + """Write a string to a fileobject and strip its ANSI style sequences if required by allow_style setting :param fileobj: the file object being written to :param msg: the string being written @@ -143,8 +138,7 @@ def style_aware_write(fileobj: IO[str], msg: str) -> None: # Utility functions which create various ANSI sequences #################################################################################### def set_title(title: str) -> str: - """ - Generate a string that, when printed, sets a terminal's window title. + """Generate a string that, when printed, sets a terminal's window title. :param title: new title for the window :return: the set title string @@ -153,8 +147,7 @@ def set_title(title: str) -> str: def clear_screen(clear_type: int = 2) -> str: - """ - Generate a string that, when printed, clears a terminal screen based on value of clear_type. + """Generate a string that, when printed, clears a terminal screen based on value of clear_type. :param clear_type: integer which specifies how to clear the screen (Defaults to 2) Possible values: @@ -171,8 +164,7 @@ def clear_screen(clear_type: int = 2) -> str: def clear_line(clear_type: int = 2) -> str: - """ - Generate a string that, when printed, clears a line based on value of clear_type. + """Generate a string that, when printed, clears a line based on value of clear_type. :param clear_type: integer which specifies how to clear the line (Defaults to 2) Possible values: @@ -194,15 +186,13 @@ class AnsiSequence: """Base class to create ANSI sequence strings""" def __add__(self, other: Any) -> str: - """ - Support building an ANSI sequence string when self is the left operand + """Support building an ANSI sequence string when self is the left operand e.g. Fg.LIGHT_MAGENTA + "hello" """ return str(self) + str(other) def __radd__(self, other: Any) -> str: - """ - Support building an ANSI sequence string when self is the right operand + """Support building an ANSI sequence string when self is the right operand e.g. "hello" + Fg.RESET """ return str(other) + str(self) @@ -272,8 +262,7 @@ class TextStyle(AnsiSequence, Enum): UNDERLINE_DISABLE = 24 def __str__(self) -> str: - """ - Return ANSI text style sequence instead of enum name + """Return ANSI text style sequence instead of enum name This is helpful when using a TextStyle in an f-string or format() call e.g. my_str = f"{TextStyle.UNDERLINE_ENABLE}hello{TextStyle.UNDERLINE_DISABLE}" """ @@ -281,8 +270,7 @@ def __str__(self) -> str: class Fg(FgColor, Enum): - """ - Create ANSI sequences for the 16 standard terminal foreground text colors. + """Create ANSI sequences for the 16 standard terminal foreground text colors. A terminal's color settings affect how these colors appear. To reset any foreground color, use Fg.RESET. """ @@ -307,8 +295,7 @@ class Fg(FgColor, Enum): RESET = 39 def __str__(self) -> str: - """ - Return ANSI color sequence instead of enum name + """Return ANSI color sequence instead of enum name This is helpful when using an Fg in an f-string or format() call e.g. my_str = f"{Fg.BLUE}hello{Fg.RESET}" """ @@ -316,8 +303,7 @@ def __str__(self) -> str: class Bg(BgColor, Enum): - """ - Create ANSI sequences for the 16 standard terminal background text colors. + """Create ANSI sequences for the 16 standard terminal background text colors. A terminal's color settings affect how these colors appear. To reset any background color, use Bg.RESET. """ @@ -342,8 +328,7 @@ class Bg(BgColor, Enum): RESET = 49 def __str__(self) -> str: - """ - Return ANSI color sequence instead of enum name + """Return ANSI color sequence instead of enum name This is helpful when using a Bg in an f-string or format() call e.g. my_str = f"{Bg.BLACK}hello{Bg.RESET}" """ @@ -351,8 +336,7 @@ def __str__(self) -> str: class EightBitFg(FgColor, Enum): - """ - Create ANSI sequences for 8-bit terminal foreground text colors. Most terminals support 8-bit/256-color mode. + """Create ANSI sequences for 8-bit terminal foreground text colors. Most terminals support 8-bit/256-color mode. The first 16 colors correspond to the 16 colors from Fg and behave the same way. To reset any foreground color, including 8-bit, use Fg.RESET. """ @@ -615,8 +599,7 @@ class EightBitFg(FgColor, Enum): GRAY_93 = 255 def __str__(self) -> str: - """ - Return ANSI color sequence instead of enum name + """Return ANSI color sequence instead of enum name This is helpful when using an EightBitFg in an f-string or format() call e.g. my_str = f"{EightBitFg.SLATE_BLUE_1}hello{Fg.RESET}" """ @@ -624,8 +607,7 @@ def __str__(self) -> str: class EightBitBg(BgColor, Enum): - """ - Create ANSI sequences for 8-bit terminal background text colors. Most terminals support 8-bit/256-color mode. + """Create ANSI sequences for 8-bit terminal background text colors. Most terminals support 8-bit/256-color mode. The first 16 colors correspond to the 16 colors from Bg and behave the same way. To reset any background color, including 8-bit, use Bg.RESET. """ @@ -888,8 +870,7 @@ class EightBitBg(BgColor, Enum): GRAY_93 = 255 def __str__(self) -> str: - """ - Return ANSI color sequence instead of enum name + """Return ANSI color sequence instead of enum name This is helpful when using an EightBitBg in an f-string or format() call e.g. my_str = f"{EightBitBg.KHAKI_3}hello{Bg.RESET}" """ @@ -897,14 +878,12 @@ def __str__(self) -> str: class RgbFg(FgColor): - """ - Create ANSI sequences for 24-bit (RGB) terminal foreground text colors. The terminal must support 24-bit/true-color mode. + """Create ANSI sequences for 24-bit (RGB) terminal foreground text colors. The terminal must support 24-bit/true-color. To reset any foreground color, including 24-bit, use Fg.RESET. """ def __init__(self, r: int, g: int, b: int) -> None: - """ - RgbFg initializer + """RgbFg initializer :param r: integer from 0-255 for the red component of the color :param g: integer from 0-255 for the green component of the color @@ -917,8 +896,7 @@ def __init__(self, r: int, g: int, b: int) -> None: self._sequence = f"{CSI}38;2;{r};{g};{b}m" def __str__(self) -> str: - """ - Return ANSI color sequence instead of enum name + """Return ANSI color sequence instead of enum name This is helpful when using an RgbFg in an f-string or format() call e.g. my_str = f"{RgbFg(0, 55, 100)}hello{Fg.RESET}" """ @@ -926,14 +904,12 @@ def __str__(self) -> str: class RgbBg(BgColor): - """ - Create ANSI sequences for 24-bit (RGB) terminal background text colors. The terminal must support 24-bit/true-color mode. + """Create ANSI sequences for 24-bit (RGB) terminal background text colors. The terminal must support 24-bit/true-color. To reset any background color, including 24-bit, use Bg.RESET. """ def __init__(self, r: int, g: int, b: int) -> None: - """ - RgbBg initializer + """RgbBg initializer :param r: integer from 0-255 for the red component of the color :param g: integer from 0-255 for the green component of the color @@ -946,8 +922,7 @@ def __init__(self, r: int, g: int, b: int) -> None: self._sequence = f"{CSI}48;2;{r};{g};{b}m" def __str__(self) -> str: - """ - Return ANSI color sequence instead of enum name + """Return ANSI color sequence instead of enum name This is helpful when using an RgbBg in an f-string or format() call e.g. my_str = f"{RgbBg(100, 255, 27)}hello{Bg.RESET}" """ @@ -966,8 +941,7 @@ def style( strikethrough: Optional[bool] = None, underline: Optional[bool] = None, ) -> str: - """ - Apply ANSI colors and/or styles to a string and return it. + """Apply ANSI colors and/or styles to a string and return it. The styling is self contained which means that at the end of the string reset code(s) are issued to undo whatever styling was done at the beginning. @@ -1056,7 +1030,6 @@ def async_alert_str(*, terminal_columns: int, prompt: str, line: str, cursor_off :param alert_msg: the message to display to the user :return: the correct string so that the alert message appears to the user to be printed above the current line. """ - # Split the prompt lines since it can contain newline characters. prompt_lines = prompt.splitlines() or [''] diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 48d1fd05a..961d609cb 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -1,5 +1,4 @@ -""" -This module defines the ArgparseCompleter class which provides argparse-based tab completion to cmd2 apps. +"""This module defines the ArgparseCompleter class which provides argparse-based tab completion to cmd2 apps. See the header of argparse_custom.py for instructions on how to use these features. """ @@ -76,8 +75,7 @@ def _single_prefix_char(token: str, parser: argparse.ArgumentParser) -> bool: def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool: - """ - Determine if a token looks like a flag. Unless an argument has nargs set to argparse.REMAINDER, + """Determine if a token looks like a flag. Unless an argument has nargs set to argparse.REMAINDER, then anything that looks like a flag can't be consumed as a value for it. Based on argparse._parse_optional(). """ @@ -138,8 +136,7 @@ def __init__(self, arg_action: argparse.Action) -> None: class _UnfinishedFlagError(CompletionError): def __init__(self, flag_arg_state: _ArgumentState) -> None: - """ - CompletionError which occurs when the user has not finished the current flag + """CompletionError which occurs when the user has not finished the current flag :param flag_arg_state: information about the unfinished flag action """ arg = f'{argparse._get_action_name(flag_arg_state.action)}' @@ -150,8 +147,7 @@ def __init__(self, flag_arg_state: _ArgumentState) -> None: class _NoResultsError(CompletionError): def __init__(self, parser: argparse.ArgumentParser, arg_action: argparse.Action) -> None: - """ - CompletionError which occurs when there are no results. If hinting is allowed, then its message will + """CompletionError which occurs when there are no results. If hinting is allowed, then its message will be a hint about the argument being tab completed. :param parser: ArgumentParser instance which owns the action being tab completed :param arg_action: action being tab completed @@ -166,8 +162,7 @@ class ArgparseCompleter: def __init__( self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: Optional[dict[str, list[str]]] = None ) -> None: - """ - Create an ArgparseCompleter + """Create an ArgparseCompleter :param parser: ArgumentParser instance :param cmd2_app: reference to the Cmd2 application that owns this ArgparseCompleter @@ -207,8 +202,7 @@ def __init__( def complete( self, text: str, line: str, begidx: int, endidx: int, tokens: list[str], *, cmd_set: Optional[CommandSet] = None ) -> list[str]: - """ - Complete text using argparse metadata + """Complete text using argparse metadata :param text: the string prefix we are attempting to match (all matches must begin with it) :param line: the current input line with leading whitespace removed @@ -252,8 +246,7 @@ def consume_argument(arg_state: _ArgumentState) -> None: consumed_arg_values[arg_state.action.dest].append(token) def update_mutex_groups(arg_action: argparse.Action) -> None: - """ - Check if an argument belongs to a mutually exclusive group and either mark that group + """Check if an argument belongs to a mutually exclusive group and either mark that group as complete or print an error if the group has already been completed :param arg_action: the action of the argument :raises CompletionError: if the group is already completed @@ -501,7 +494,6 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matched_flags: list[str]) -> list[str]: """Tab completion routine for a parsers unused flags""" - # Build a list of flags that can be tab completed match_against = [] @@ -535,7 +527,6 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matche def _format_completions(self, arg_state: _ArgumentState, completions: Union[list[str], list[CompletionItem]]) -> list[str]: """Format CompletionItems into hint table""" - # Nothing to do if we don't have at least 2 completions which are all CompletionItems if len(completions) < 2 or not all(isinstance(c, CompletionItem) for c in completions): return cast(list[str], completions) @@ -611,8 +602,7 @@ def _format_completions(self, arg_state: _ArgumentState, completions: Union[list return cast(list[str], completions) def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: list[str]) -> list[str]: - """ - Supports cmd2's help command in the completion of subcommand names + """Supports cmd2's help command in the completion of subcommand names :param text: the string prefix we are attempting to match (all matches must begin with it) :param line: the current input line with leading whitespace removed :param begidx: the beginning index of the prefix text @@ -638,8 +628,7 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in return [] def format_help(self, tokens: list[str]) -> str: - """ - Supports cmd2's help command in the retrieval of help text + """Supports cmd2's help command in the retrieval of help text :param tokens: arguments passed to help command :return: help text of the command being queried """ @@ -668,8 +657,7 @@ def _complete_arg( *, cmd_set: Optional[CommandSet] = None, ) -> list[str]: - """ - Tab completion routine for an argparse argument + """Tab completion routine for an argparse argument :return: list of completions :raises CompletionError: if the completer or choices function this calls raises one """ @@ -767,8 +755,7 @@ def _complete_arg( def set_default_ap_completer_type(completer_type: type[ArgparseCompleter]) -> None: - """ - Set the default ArgparseCompleter class for a cmd2 app. + """Set the default ArgparseCompleter class for a cmd2 app. :param completer_type: Type that is a subclass of ArgparseCompleter. """ diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index d11b36f0d..cbe6cda96 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1,5 +1,4 @@ -""" -This module adds capabilities to argparse by patching a few of its functions. +"""This module adds capabilities to argparse by patching a few of its functions. It also defines a parser class called Cmd2ArgumentParser which improves error and help output over normal argparse. All cmd2 code uses this parser and it is recommended that developers of cmd2-based apps either use it or write their own @@ -276,8 +275,7 @@ def generate_range_error(range_min: int, range_max: float) -> str: class CompletionItem(str): # noqa: SLOT000 - """ - Completion item with descriptive text attached + """Completion item with descriptive text attached See header of this file for more information """ @@ -286,8 +284,7 @@ def __new__(cls, value: object, *args: Any, **kwargs: Any) -> 'CompletionItem': return super().__new__(cls, value) def __init__(self, value: object, description: str = '', *args: Any) -> None: - """ - CompletionItem Initializer + """CompletionItem Initializer :param value: the value being tab completed :param description: description text to display @@ -313,18 +310,14 @@ def orig_value(self) -> Any: @runtime_checkable class ChoicesProviderFuncBase(Protocol): - """ - Function that returns a list of choices in support of tab completion - """ + """Function that returns a list of choices in support of tab completion""" def __call__(self) -> list[str]: ... # pragma: no cover @runtime_checkable class ChoicesProviderFuncWithTokens(Protocol): - """ - Function that returns a list of choices in support of tab completion and accepts a dictionary of prior arguments. - """ + """Function that returns a list of choices in support of tab completion and accepts a dictionary of prior arguments.""" def __call__(self, *, arg_tokens: dict[str, list[str]] = {}) -> list[str]: ... # pragma: no cover @@ -334,9 +327,7 @@ def __call__(self, *, arg_tokens: dict[str, list[str]] = {}) -> list[str]: ... @runtime_checkable class CompleterFuncBase(Protocol): - """ - Function to support tab completion with the provided state of the user prompt - """ + """Function to support tab completion with the provided state of the user prompt""" def __call__( self, @@ -349,8 +340,7 @@ def __call__( @runtime_checkable class CompleterFuncWithTokens(Protocol): - """ - Function to support tab completion with the provided state of the user prompt and accepts a dictionary of prior + """Function to support tab completion with the provided state of the user prompt and accepts a dictionary of prior arguments. """ @@ -369,8 +359,7 @@ def __call__( class ChoicesCallable: - """ - Enables using a callable as the choices provider for an argparse argument. + """Enables using a callable as the choices provider for an argparse argument. While argparse has the built-in choices attribute, it is limited to an iterable. """ @@ -379,8 +368,7 @@ def __init__( is_completer: bool, to_call: Union[CompleterFunc, ChoicesProviderFunc], ) -> None: - """ - Initializer + """Initializer :param is_completer: True if to_call is a tab completion routine which expects the args: text, line, begidx, endidx :param to_call: the callable object that will be called to provide choices for the argument @@ -437,8 +425,7 @@ def choices_provider(self) -> ChoicesProviderFunc: # Patch argparse.Action with accessors for choice_callable attribute ############################################################################################################ def _action_get_choices_callable(self: argparse.Action) -> Optional[ChoicesCallable]: - """ - Get the choices_callable attribute of an argparse Action. + """Get the choices_callable attribute of an argparse Action. This function is added by cmd2 as a method called ``get_choices_callable()`` to ``argparse.Action`` class. @@ -454,8 +441,7 @@ def _action_get_choices_callable(self: argparse.Action) -> Optional[ChoicesCalla def _action_set_choices_callable(self: argparse.Action, choices_callable: ChoicesCallable) -> None: - """ - Set the choices_callable attribute of an argparse Action. + """Set the choices_callable attribute of an argparse Action. This function is added by cmd2 as a method called ``_set_choices_callable()`` to ``argparse.Action`` class. @@ -485,8 +471,7 @@ def _action_set_choices_provider( self: argparse.Action, choices_provider: ChoicesProviderFunc, ) -> None: - """ - Set choices_provider of an argparse Action. + """Set choices_provider of an argparse Action. This function is added by cmd2 as a method called ``set_choices_callable()`` to ``argparse.Action`` class. @@ -506,8 +491,7 @@ def _action_set_completer( self: argparse.Action, completer: CompleterFunc, ) -> None: - """ - Set completer of an argparse Action. + """Set completer of an argparse Action. This function is added by cmd2 as a method called ``set_completer()`` to ``argparse.Action`` class. @@ -527,8 +511,7 @@ def _action_set_completer( # Patch argparse.Action with accessors for descriptive_header attribute ############################################################################################################ def _action_get_descriptive_header(self: argparse.Action) -> Optional[str]: - """ - Get the descriptive_header attribute of an argparse Action. + """Get the descriptive_header attribute of an argparse Action. This function is added by cmd2 as a method called ``get_descriptive_header()`` to ``argparse.Action`` class. @@ -544,8 +527,7 @@ def _action_get_descriptive_header(self: argparse.Action) -> Optional[str]: def _action_set_descriptive_header(self: argparse.Action, descriptive_header: Optional[str]) -> None: - """ - Set the descriptive_header attribute of an argparse Action. + """Set the descriptive_header attribute of an argparse Action. This function is added by cmd2 as a method called ``set_descriptive_header()`` to ``argparse.Action`` class. @@ -564,8 +546,7 @@ def _action_set_descriptive_header(self: argparse.Action, descriptive_header: Op # Patch argparse.Action with accessors for nargs_range attribute ############################################################################################################ def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[int, float]]]: - """ - Get the nargs_range attribute of an argparse Action. + """Get the nargs_range attribute of an argparse Action. This function is added by cmd2 as a method called ``get_nargs_range()`` to ``argparse.Action`` class. @@ -581,8 +562,7 @@ def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[ def _action_set_nargs_range(self: argparse.Action, nargs_range: Optional[tuple[int, Union[int, float]]]) -> None: - """ - Set the nargs_range attribute of an argparse Action. + """Set the nargs_range attribute of an argparse Action. This function is added by cmd2 as a method called ``set_nargs_range()`` to ``argparse.Action`` class. @@ -601,8 +581,7 @@ def _action_set_nargs_range(self: argparse.Action, nargs_range: Optional[tuple[i # Patch argparse.Action with accessors for suppress_tab_hint attribute ############################################################################################################ def _action_get_suppress_tab_hint(self: argparse.Action) -> bool: - """ - Get the suppress_tab_hint attribute of an argparse Action. + """Get the suppress_tab_hint attribute of an argparse Action. This function is added by cmd2 as a method called ``get_suppress_tab_hint()`` to ``argparse.Action`` class. @@ -618,8 +597,7 @@ def _action_get_suppress_tab_hint(self: argparse.Action) -> bool: def _action_set_suppress_tab_hint(self: argparse.Action, suppress_tab_hint: bool) -> None: - """ - Set the suppress_tab_hint attribute of an argparse Action. + """Set the suppress_tab_hint attribute of an argparse Action. This function is added by cmd2 as a method called ``set_suppress_tab_hint()`` to ``argparse.Action`` class. @@ -643,8 +621,7 @@ def _action_set_suppress_tab_hint(self: argparse.Action, suppress_tab_hint: bool def register_argparse_argument_parameter(param_name: str, param_type: Optional[type[Any]]) -> None: - """ - Registers a custom argparse argument parameter. + """Registers a custom argparse argument parameter. The registered name will then be a recognized keyword parameter to the parser's `add_argument()` function. @@ -718,8 +695,7 @@ def _add_argument_wrapper( descriptive_header: Optional[str] = None, **kwargs: Any, ) -> argparse.Action: - """ - Wrapper around _ActionsContainer.add_argument() which supports more settings used by cmd2 + """Wrapper around _ActionsContainer.add_argument() which supports more settings used by cmd2 # Args from original function :param self: instance of the _ActionsContainer being added to @@ -908,8 +884,7 @@ def _match_argument_wrapper(self: argparse.ArgumentParser, action: argparse.Acti def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[type['ArgparseCompleter']]: - """ - Get the ap_completer_type attribute of an argparse ArgumentParser. + """Get the ap_completer_type attribute of an argparse ArgumentParser. This function is added by cmd2 as a method called ``get_ap_completer_type()`` to ``argparse.ArgumentParser`` class. @@ -925,8 +900,7 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: type['ArgparseCompleter']) -> None: - """ - Set the ap_completer_type attribute of an argparse ArgumentParser. + """Set the ap_completer_type attribute of an argparse ArgumentParser. This function is added by cmd2 as a method called ``set_ap_completer_type()`` to ``argparse.ArgumentParser`` class. @@ -945,8 +919,7 @@ def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_comp # Patch ArgumentParser._check_value to support CompletionItems as choices ############################################################################################################ def _ArgumentParser_check_value(self: argparse.ArgumentParser, action: argparse.Action, value: Any) -> None: - """ - Custom override of ArgumentParser._check_value that supports CompletionItems as choices. + """Custom override of ArgumentParser._check_value that supports CompletionItems as choices. When evaluating choices, input is compared to CompletionItem.orig_value instead of the CompletionItem instance. @@ -978,8 +951,7 @@ def _ArgumentParser_check_value(self: argparse.ArgumentParser, action: argparse. def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str) -> None: # type: ignore[type-arg] - """ - Removes a sub-parser from a sub-parsers group. Used to remove subcommands from a parser. + """Removes a sub-parser from a sub-parsers group. Used to remove subcommands from a parser. This function is added by cmd2 as a method called ``remove_parser()`` to ``argparse._SubParsersAction`` class. @@ -1243,8 +1215,7 @@ def __init__( *, ap_completer_type: Optional[type['ArgparseCompleter']] = None, ) -> None: - """ - # Custom parameter added by cmd2 + """# Custom parameter added by cmd2 :param ap_completer_type: optional parameter which specifies a subclass of ArgparseCompleter for custom tab completion behavior on this parser. If this is None or not present, then cmd2 will use @@ -1290,8 +1261,7 @@ def __init__( self.set_ap_completer_type(ap_completer_type) # type: ignore[attr-defined] def add_subparsers(self, **kwargs: Any) -> argparse._SubParsersAction: # type: ignore[type-arg] - """ - Custom override. Sets a default title if one was not given. + """Custom override. Sets a default title if one was not given. :param kwargs: additional keyword arguments :return: argparse Subparser Action @@ -1380,8 +1350,7 @@ def _print_message(self, message: str, file: Optional[IO[str]] = None) -> None: class Cmd2AttributeWrapper: - """ - Wraps a cmd2-specific attribute added to an argparse Namespace. + """Wraps a cmd2-specific attribute added to an argparse Namespace. This makes it easy to know which attributes in a Namespace are arguments from a parser and which were added by cmd2. """ @@ -1403,8 +1372,7 @@ def set(self, new_val: Any) -> None: def set_default_argument_parser_type(parser_type: type[argparse.ArgumentParser]) -> None: - """ - Set the default ArgumentParser class for a cmd2 app. This must be called prior to loading cmd2.py if + """Set the default ArgumentParser class for a cmd2 app. This must be called prior to loading cmd2.py if you want to override the parser for cmd2's built-in commands. See examples/override_parser.py. """ global DEFAULT_ARGUMENT_PARSER # noqa: PLW0603 diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index 45a44cbb1..7d77d492f 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -1,6 +1,4 @@ -""" -This module provides basic ability to copy from and paste to the clipboard/pastebuffer. -""" +"""This module provides basic ability to copy from and paste to the clipboard/pastebuffer.""" import typing diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index eb4cf6091..057ecd3b0 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -211,8 +211,7 @@ def __init__(self) -> None: class _CommandParsers: - """ - Create and store all command method argument parsers for a given Cmd instance. + """Create and store all command method argument parsers for a given Cmd instance. Parser creation and retrieval are accomplished through the get() method. """ @@ -233,8 +232,7 @@ def _fully_qualified_name(command_method: CommandFunc) -> str: return "" def __contains__(self, command_method: CommandFunc) -> bool: - """ - Return whether a given method's parser is in self. + """Return whether a given method's parser is in self. If the parser does not yet exist, it will be created if applicable. This is basically for checking if a method is argarse-based. @@ -243,8 +241,7 @@ def __contains__(self, command_method: CommandFunc) -> bool: return bool(parser) def get(self, command_method: CommandFunc) -> Optional[argparse.ArgumentParser]: - """ - Return a given method's parser or None if the method is not argparse-based. + """Return a given method's parser or None if the method is not argparse-based. If the parser does not yet exist, it will be created. """ @@ -635,8 +632,7 @@ def __init__( self.current_command: Optional[Statement] = None def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: bool = False) -> list[CommandSet]: - """ - Find all CommandSets that match the provided CommandSet type. + """Find all CommandSets that match the provided CommandSet type. By default, locates a CommandSet that is an exact type match but may optionally return all CommandSets that are sub-classes of the provided type :param commandset_type: CommandSet sub-class type to search for @@ -650,8 +646,7 @@ def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: ] def find_commandset_for_command(self, command_name: str) -> Optional[CommandSet]: - """ - Finds the CommandSet that registered the command name + """Finds the CommandSet that registered the command name :param command_name: command name to search :return: CommandSet that provided the command """ @@ -682,8 +677,7 @@ def load_commandset_by_type(commandset_types: list[type[CommandSet]]) -> None: load_commandset_by_type(all_commandset_defs) def register_command_set(self, cmdset: CommandSet) -> None: - """ - Installs a CommandSet, loading all commands defined in the CommandSet + """Installs a CommandSet, loading all commands defined in the CommandSet :param cmdset: CommandSet to load """ @@ -782,8 +776,7 @@ def _build_parser( return parser def _install_command_function(self, command_func_name: str, command_method: CommandFunc, context: str = '') -> None: - """ - Install a new command function into the CLI. + """Install a new command function into the CLI. :param command_func_name: name of command function to add This points to the command method and may differ from the method's @@ -792,7 +785,6 @@ def _install_command_function(self, command_func_name: str, command_method: Comm :param context: optional info to provide in error message. (e.g. class this function belongs to) :raises CommandSetRegistrationError: if the command function fails to install """ - # command_func_name must begin with COMMAND_FUNC_PREFIX to be identified as a command by cmd2. if not command_func_name.startswith(COMMAND_FUNC_PREFIX): raise CommandSetRegistrationError(f"{command_func_name} does not begin with '{COMMAND_FUNC_PREFIX}'") @@ -839,8 +831,7 @@ def _install_help_function(self, cmd_name: str, cmd_help: Callable[..., None]) - setattr(self, help_func_name, cmd_help) def unregister_command_set(self, cmdset: CommandSet) -> None: - """ - Uninstalls a CommandSet and unloads all associated commands + """Uninstalls a CommandSet and unloads all associated commands :param cmdset: CommandSet to uninstall """ @@ -911,8 +902,7 @@ def check_parser_uninstallable(parser: argparse.ArgumentParser) -> None: check_parser_uninstallable(command_parser) def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: - """ - Register subcommands with their base command + """Register subcommands with their base command :param cmdset: CommandSet or cmd2.Cmd subclass containing subcommands """ @@ -1018,8 +1008,7 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> break def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: - """ - Unregister subcommands from their base command + """Unregister subcommands from their base command :param cmdset: CommandSet containing subcommands """ @@ -1065,8 +1054,7 @@ def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: @property def always_prefix_settables(self) -> bool: - """ - Flags whether CommandSet settable values should always be prefixed + """Flags whether CommandSet settable values should always be prefixed :return: True if CommandSet settable values will always be prefixed. False if not. """ @@ -1074,8 +1062,7 @@ def always_prefix_settables(self) -> bool: @always_prefix_settables.setter def always_prefix_settables(self, new_value: bool) -> None: - """ - Set whether CommandSet settable values should always be prefixed. + """Set whether CommandSet settable values should always be prefixed. :param new_value: True if CommandSet settable values should always be prefixed. False if not. :raises ValueError: If a registered CommandSet does not have a defined prefix @@ -1091,8 +1078,7 @@ def always_prefix_settables(self, new_value: bool) -> None: @property def settables(self) -> Mapping[str, Settable]: - """ - Get all available user-settable attributes. This includes settables defined in installed CommandSets + """Get all available user-settable attributes. This includes settables defined in installed CommandSets :return: Mapping from attribute-name to Settable of all user-settable attributes from """ @@ -1107,8 +1093,7 @@ def settables(self) -> Mapping[str, Settable]: return all_settables def add_settable(self, settable: Settable) -> None: - """ - Add a settable parameter to ``self.settables`` + """Add a settable parameter to ``self.settables`` :param settable: Settable object being added """ @@ -1118,8 +1103,7 @@ def add_settable(self, settable: Settable) -> None: self._settables[settable.name] = settable def remove_settable(self, name: str) -> None: - """ - Convenience method for removing a settable parameter from ``self.settables`` + """Convenience method for removing a settable parameter from ``self.settables`` :param name: name of the settable being removed :raises KeyError: if the Settable matches this name @@ -1206,8 +1190,7 @@ def print_to( end: str = '\n', style: Optional[Callable[[str], str]] = None, ) -> None: - """ - Print message to a given file object. + """Print message to a given file object. :param dest: the file object being written to :param msg: object to print @@ -1357,8 +1340,7 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False) -> None: # ----- Methods related to tab completion ----- def _reset_completion_defaults(self) -> None: - """ - Resets tab completion settings + """Resets tab completion settings Needs to be called each time readline runs tab completion """ self.allow_appended_space = True @@ -1447,8 +1429,7 @@ def basic_complete( endidx: int, match_against: Iterable[str], ) -> list[str]: - """ - Basic tab completion function that matches against a list of strings without considering line contents + """Basic tab completion function that matches against a list of strings without considering line contents or cursor position. The args required by this function are defined in the header of Python's cmd.py. :param text: the string prefix we are attempting to match (all matches must begin with it) @@ -1469,8 +1450,7 @@ def delimiter_complete( match_against: Iterable[str], delimiter: str, ) -> list[str]: - """ - Performs tab completion against a list but each match is split on a delimiter and only + """Performs tab completion against a list but each match is split on a delimiter and only the portion of the match being tab completed is shown as the completion suggestions. This is useful if you match against strings that are hierarchical in nature and have a common delimiter. @@ -1995,8 +1975,7 @@ def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no @staticmethod def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argparse_completer.ArgparseCompleter]: - """ - Determine what type of ArgparseCompleter to use on a given parser. If the parser does not have one + """Determine what type of ArgparseCompleter to use on a given parser. If the parser does not have one set, then use argparse_completer.DEFAULT_AP_COMPLETER. :param parser: the parser to examine @@ -2012,8 +1991,7 @@ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argpar def _perform_completion( self, text: str, line: str, begidx: int, endidx: int, custom_settings: Optional[utils.CustomCompletionSettings] = None ) -> None: - """ - Helper function for complete() that performs the actual completion + """Helper function for complete() that performs the actual completion :param text: the string prefix we are attempting to match (all matches must begin with it) :param line: the current input line with leading whitespace removed @@ -2408,8 +2386,7 @@ def sigint_handler(self, signum: int, _: Optional[FrameType]) -> None: self._raise_keyboard_interrupt() def termination_signal_handler(self, signum: int, _: Optional[FrameType]) -> None: - """ - Signal handler for SIGHUP and SIGTERM. Only runs on Linux and Mac. + """Signal handler for SIGHUP and SIGTERM. Only runs on Linux and Mac. SIGHUP - received when terminal window is closed SIGTERM - received when this app has been requested to terminate @@ -2640,8 +2617,7 @@ def runcmds_plus_hooks( add_to_history: bool = True, stop_on_keyboard_interrupt: bool = False, ) -> bool: - """ - Used when commands are being run in an automated fashion like text scripts or history replays. + """Used when commands are being run in an automated fashion like text scripts or history replays. The prompt and command line for each command will be printed if echo is True. :param cmds: commands to run @@ -2761,8 +2737,7 @@ def combine_rl_history(statement: Statement) -> None: return statement def _input_line_to_statement(self, line: str, *, orig_rl_history_length: Optional[int] = None) -> Statement: - """ - Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved + """Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved :param line: the line being parsed :param orig_rl_history_length: Optional length of the readline history before the current command was typed. @@ -2815,8 +2790,7 @@ def _input_line_to_statement(self, line: str, *, orig_rl_history_length: Optiona return statement def _resolve_macro(self, statement: Statement) -> Optional[str]: - """ - Resolve a macro and return the resulting string + """Resolve a macro and return the resulting string :param statement: the parsed statement from the command line :return: the resolved macro or None on error @@ -2999,18 +2973,17 @@ def _restore_output(self, statement: Statement, saved_redir_state: utils.Redirec self._redirecting = saved_redir_state.saved_redirecting def cmd_func(self, command: str) -> Optional[CommandFunc]: - """ - Get the function for a command + """Get the function for a command :param command: the name of the command Example: - ```py helpfunc = self.cmd_func('help') ``` helpfunc now contains a reference to the ``do_help`` method + """ func_name = constants.COMMAND_FUNC_PREFIX + command func = getattr(self, func_name, None) @@ -3084,8 +3057,7 @@ def read_input( completer: Optional[CompleterFunc] = None, parser: Optional[argparse.ArgumentParser] = None, ) -> str: - """ - Read input from appropriate stdin value. Also supports tab completion and up-arrow history while + """Read input from appropriate stdin value. Also supports tab completion and up-arrow history while input is being entered. :param prompt: prompt to display to user @@ -3232,8 +3204,7 @@ def restore_readline() -> None: return line.rstrip('\r\n') def _read_command_line(self, prompt: str) -> str: - """ - Read command line from appropriate stdin + """Read command line from appropriate stdin :param prompt: prompt to display to user :return: command line text of 'eof' if an EOFError was caught @@ -3254,8 +3225,7 @@ def _read_command_line(self, prompt: str) -> str: self.terminal_lock.acquire() def _set_up_cmd2_readline(self) -> _SavedReadlineSettings: - """ - Called at beginning of command loop to set up readline with cmd2-specific settings + """Called at beginning of command loop to set up readline with cmd2-specific settings :return: Class containing saved readline settings """ @@ -3295,8 +3265,7 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings: return readline_settings def _restore_readline(self, readline_settings: _SavedReadlineSettings) -> None: - """ - Called at end of command loop to restore saved readline settings + """Called at end of command loop to restore saved readline settings :param readline_settings: the readline settings to restore """ @@ -3757,7 +3726,6 @@ def _macro_list(self, args: argparse.Namespace) -> None: def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: """Completes the command argument of help""" - # Complete token against topics and visible commands topics = set(self.get_help_topics()) visible_commands = set(self.get_visible_commands()) @@ -3768,7 +3736,6 @@ def complete_help_subcommands( self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] ) -> list[str]: """Completes the subcommands argument of help""" - # Make sure we have a command whose subcommands we will complete command = arg_tokens['command'][0] if not command: @@ -3836,8 +3803,7 @@ def do_help(self, args: argparse.Namespace) -> None: self.last_result = False def print_topics(self, header: str, cmds: Optional[list[str]], cmdlen: int, maxcol: int) -> None: - """ - Print groups of commands and topics in columns and an optional header + """Print groups of commands and topics in columns and an optional header Override of cmd's print_topics() to handle headers with newlines, ANSI style sequences, and wide characters :param header: string to print above commands being printed @@ -4045,8 +4011,7 @@ def do_shortcuts(self, _: argparse.Namespace) -> None: @with_argparser(eof_parser) def do_eof(self, _: argparse.Namespace) -> Optional[bool]: - """ - Called when Ctrl-D is pressed and calls quit with no arguments. + """Called when Ctrl-D is pressed and calls quit with no arguments. This can be overridden if quit should be called differently. """ self.poutput() @@ -4073,7 +4038,8 @@ def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], p | a list of strings -> will be offered as options | a list of tuples -> interpreted as (value, text), so that the return value can differ from - the text advertised to the user""" + the text advertised to the user + """ local_opts: Union[list[str], list[tuple[Any, Optional[str]]]] if isinstance(opts, str): local_opts = cast(list[tuple[Any, Optional[str]]], list(zip(opts.split(), opts.split()))) @@ -4289,8 +4255,7 @@ def do_shell(self, args: argparse.Namespace) -> None: @staticmethod def _reset_py_display() -> None: - """ - Resets the dynamic objects in the sys module that the py and ipy consoles fight over. + """Resets the dynamic objects in the sys module that the py and ipy consoles fight over. When a Python console starts it adopts certain display settings if they've already been set. If an ipy console has previously been run, then py uses its settings and ends up looking like an ipy console in terms of prompt and exception text. This method forces the Python @@ -4312,8 +4277,7 @@ def _reset_py_display() -> None: sys.excepthook = sys.__excepthook__ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env: - """ - Set up interactive Python shell environment + """Set up interactive Python shell environment :return: Class containing saved up cmd2 environment """ cmd2_env = _SavedCmd2Env() @@ -4376,8 +4340,7 @@ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env: return cmd2_env def _restore_cmd2_env(self, cmd2_env: _SavedCmd2Env) -> None: - """ - Restore cmd2 environment after exiting an interactive Python shell + """Restore cmd2 environment after exiting an interactive Python shell :param cmd2_env: the environment settings to restore """ @@ -4413,8 +4376,7 @@ def _restore_cmd2_env(self, cmd2_env: _SavedCmd2Env) -> None: sys.modules['readline'] = cmd2_env.readline_module def _run_python(self, *, pyscript: Optional[str] = None) -> Optional[bool]: - """ - Called by do_py() and do_run_pyscript(). + """Called by do_py() and do_run_pyscript(). If pyscript is None, then this function runs an interactive Python shell. Otherwise, it runs the pyscript file. @@ -4532,8 +4494,7 @@ def py_quit() -> None: @with_argparser(py_parser) def do_py(self, _: argparse.Namespace) -> Optional[bool]: - """ - Run an interactive Python shell + """Run an interactive Python shell :return: True if running of commands should stop """ # self.last_resort will be set by _run_python() @@ -4547,8 +4508,7 @@ def do_py(self, _: argparse.Namespace) -> Optional[bool]: @with_argparser(run_pyscript_parser) def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]: - """ - Run a Python script file inside the console + """Run a Python script file inside the console :return: True if running of commands should stop """ @@ -4584,8 +4544,7 @@ def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]: @with_argparser(ipython_parser) def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover - """ - Enter an interactive IPython shell + """Enter an interactive IPython shell :return: True if running of commands should stop """ @@ -4704,8 +4663,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover @with_argparser(history_parser) def do_history(self, args: argparse.Namespace) -> Optional[bool]: - """ - View, run, edit, save, or clear previously entered commands + """View, run, edit, save, or clear previously entered commands :return: True if running of commands should stop """ @@ -5047,13 +5005,11 @@ def _generate_transcript( @with_argparser(edit_parser) def do_edit(self, args: argparse.Namespace) -> None: """Run a text editor and optionally open a file with it""" - # self.last_result will be set by do_shell() which is called by run_editor() self.run_editor(args.file_path) def run_editor(self, file_path: Optional[str] = None) -> None: - """ - Run a text editor and optionally open a file with it + """Run a text editor and optionally open a file with it :param file_path: optional path of the file to edit. Defaults to None. :raises EnvironmentError: if self.editor is not set @@ -5173,8 +5129,7 @@ def do_run_script(self, args: argparse.Namespace) -> Optional[bool]: @with_argparser(relative_run_script_parser) def do__relative_run_script(self, args: argparse.Namespace) -> Optional[bool]: - """ - Run commands in script file that is encoded as either ASCII or UTF-8 text + """Run commands in script file that is encoded as either ASCII or UTF-8 text :return: True if running of commands should stop """ @@ -5248,8 +5203,7 @@ class TestMyAppCase(Cmd2TestCase): self.exit_code = 1 def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None: # pragma: no cover - """ - Display an important message to the user while they are at a command line prompt. + """Display an important message to the user while they are at a command line prompt. To the user it appears as if an alert message is printed above the prompt and their current input text and cursor location is left alone. @@ -5320,8 +5274,7 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None: raise RuntimeError("another thread holds terminal_lock") def async_update_prompt(self, new_prompt: str) -> None: # pragma: no cover - """ - Update the command line prompt while the user is still typing at it. + """Update the command line prompt while the user is still typing at it. This is good for alerting the user to system changes dynamically in between commands. For instance you could alter the color of the prompt to indicate a system status or increase a @@ -5340,8 +5293,7 @@ def async_update_prompt(self, new_prompt: str) -> None: # pragma: no cover self.async_alert('', new_prompt) def async_refresh_prompt(self) -> None: # pragma: no cover - """ - Refresh the oncreen prompt to match self.prompt. + """Refresh the oncreen prompt to match self.prompt. One case where the onscreen prompt and self.prompt can get out of sync is when async_alert() is called while a user is in search mode (e.g. Ctrl-r). @@ -5367,8 +5319,7 @@ def need_prompt_refresh(self) -> bool: # pragma: no cover @staticmethod def set_window_title(title: str) -> None: # pragma: no cover - """ - Set the terminal window title. + """Set the terminal window title. NOTE: This function writes to stderr. Therefore, if you call this during a command run by a pyscript, the string which updates the title will appear in that command's CommandResult.stderr data. @@ -5386,8 +5337,7 @@ def set_window_title(title: str) -> None: # pragma: no cover pass def enable_command(self, command: str) -> None: - """ - Enable a command by restoring its functions + """Enable a command by restoring its functions :param command: the command being enabled """ @@ -5419,8 +5369,7 @@ def enable_command(self, command: str) -> None: del self.disabled_commands[command] def enable_category(self, category: str) -> None: - """ - Enable an entire category of commands + """Enable an entire category of commands :param category: the category to enable """ @@ -5430,8 +5379,7 @@ def enable_category(self, category: str) -> None: self.enable_command(cmd_name) def disable_command(self, command: str, message_to_print: str) -> None: - """ - Disable a command and overwrite its functions + """Disable a command and overwrite its functions :param command: the command being disabled :param message_to_print: what to print when this command is run or help is called on it while disabled @@ -5487,8 +5435,7 @@ def disable_category(self, category: str, message_to_print: str) -> None: self.disable_command(cmd_name, message_to_print) def _report_disabled_command_usage(self, *_args: Any, message_to_print: str, **_kwargs: Any) -> None: - """ - Report when a disabled command has been run or had help called on it + """Report when a disabled command has been run or had help called on it :param _args: not used :param message_to_print: the message reporting that the command is disabled @@ -5684,8 +5631,7 @@ def _resolve_func_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 + """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 instance of each type, using type is a reasonably safe way to resolve the correct object instance diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index fc891490f..16412bd49 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -1,6 +1,4 @@ -""" -Supports the definition of commands in separate classes to be composed into cmd2.Cmd -""" +"""Supports the definition of commands in separate classes to be composed into cmd2.Cmd""" from collections.abc import Callable, Mapping from typing import ( @@ -31,8 +29,7 @@ def with_default_category(category: str, *, heritable: bool = True) -> Callable[[CommandSetType], CommandSetType]: - """ - Decorator that applies a category to all ``do_*`` command methods in a class that do not already + """Decorator that applies a category to all ``do_*`` command methods in a class that do not already have a category specified. CommandSets that are decorated by this with `heritable` set to True (default) will set a class attribute that is @@ -82,8 +79,7 @@ def decorate_class(cls: CommandSetType) -> CommandSetType: class CommandSet: - """ - Base class for defining sets of commands to load in cmd2. + """Base class for defining sets of commands to load in cmd2. ``with_default_category`` can be used to apply a default category to all commands in the CommandSet. @@ -101,8 +97,7 @@ def __init__(self) -> None: @property def _cmd(self) -> 'cmd2.Cmd': - """ - Property for child classes to access self.__cmd_internal. + """Property for child classes to access self.__cmd_internal. Using this property ensures that self.__cmd_internal has been set and it tells type checkers that it's no longer a None type. @@ -117,8 +112,7 @@ def _cmd(self) -> 'cmd2.Cmd': return self.__cmd_internal def on_register(self, cmd: 'cmd2.Cmd') -> None: - """ - Called by cmd2.Cmd as the first step to registering a CommandSet. The commands defined in this class have + """Called by cmd2.Cmd as the first step to registering a CommandSet. The commands defined in this class have not been added to the CLI object at this point. Subclasses can override this to perform any initialization requiring access to the Cmd object (e.g. configure commands and their parsers based on CLI state data). @@ -131,21 +125,18 @@ def on_register(self, cmd: 'cmd2.Cmd') -> None: raise CommandSetRegistrationError('This CommandSet has already been registered') def on_registered(self) -> None: - """ - Called by cmd2.Cmd after a CommandSet is registered and all its commands have been added to the CLI. + """Called by cmd2.Cmd after a CommandSet is registered and all its commands have been added to the CLI. Subclasses can override this to perform custom steps related to the newly added commands (e.g. setting them to a disabled state). """ def on_unregister(self) -> None: - """ - Called by ``cmd2.Cmd`` as the first step to unregistering a CommandSet. Subclasses can override this to + """Called by ``cmd2.Cmd`` as the first step to unregistering a CommandSet. Subclasses can override this to perform any cleanup steps which require their commands being registered in the CLI. """ def on_unregistered(self) -> None: - """ - Called by ``cmd2.Cmd`` after a CommandSet has been unregistered and all its commands removed from the CLI. + """Called by ``cmd2.Cmd`` after a CommandSet has been unregistered and all its commands removed from the CLI. Subclasses can override this to perform remaining cleanup steps. """ self.__cmd_internal = None @@ -159,8 +150,7 @@ def settables(self) -> Mapping[str, Settable]: return self._settables def add_settable(self, settable: Settable) -> None: - """ - Convenience method to add a settable parameter to the CommandSet + """Convenience method to add a settable parameter to the CommandSet :param settable: Settable object being added """ @@ -175,8 +165,7 @@ def add_settable(self, settable: Settable) -> None: self._settables[settable.name] = settable def remove_settable(self, name: str) -> None: - """ - Convenience method for removing a settable parameter from the CommandSet + """Convenience method for removing a settable parameter from the CommandSet :param name: name of the settable being removed :raises KeyError: if the Settable matches this name @@ -187,8 +176,7 @@ def remove_settable(self, name: str) -> None: raise KeyError(name + " is not a settable parameter") def sigint_handler(self) -> bool: - """ - Handle a SIGINT that occurred for a command in this CommandSet. + """Handle a SIGINT that occurred for a command in this CommandSet. :return: True if this completes the interrupt handling and no KeyboardInterrupt will be raised. False to raise a KeyboardInterrupt. diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 5f1b4f7a6..815062ebe 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -38,7 +38,6 @@ def with_category(category: str) -> Callable[[CommandFunc], CommandFunc]: be grouped when displaying the list of commands. Example: - ```py class MyApp(cmd2.Cmd): @cmd2.with_category('Text Functions') @@ -48,6 +47,7 @@ def do_echo(self, args) For an alternative approach to categorizing commands using a function, see [cmd2.utils.categorize][] + """ def cat_decorator(func: CommandFunc) -> CommandFunc: @@ -74,8 +74,7 @@ def cat_decorator(func: CommandFunc) -> CommandFunc: # found we can swap out the statement with each decorator's specific parameters ########################## def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Statement, str]]: - """ - Helper function for cmd2 decorators to inspect the positional arguments until the cmd2.Cmd argument is found + """Helper function for cmd2 decorators to inspect the positional arguments until the cmd2.Cmd argument is found Assumes that we will find cmd2.Cmd followed by the command statement object or string. :arg args: The positional arguments to inspect :return: The cmd2.Cmd reference and the command line statement @@ -98,8 +97,7 @@ def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Stateme def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> list[Any]: - """ - Helper function for cmd2 decorators to swap the Statement parameter with one or more decorator-specific parameters + """Helper function for cmd2 decorators to swap the Statement parameter with one or more decorator-specific parameters :param args: The original positional arguments :param search_arg: The argument to search for (usually the Statement) @@ -138,8 +136,7 @@ def with_argument_list( RawCommandFuncOptionalBoolReturn[CommandParent], Callable[[ArgListCommandFunc[CommandParent]], RawCommandFuncOptionalBoolReturn[CommandParent]], ]: - """ - A decorator to alter the arguments passed to a ``do_*`` method. Default + """A decorator to alter the arguments passed to a ``do_*`` method. Default passes a string of whatever the user typed. With this decorator, the decorated method will receive a list of arguments parsed from user input. @@ -155,12 +152,12 @@ class MyApp(cmd2.Cmd): def do_echo(self, arglist): self.poutput(' '.join(arglist) ``` + """ import functools def arg_decorator(func: ArgListCommandFunc[CommandParent]) -> RawCommandFuncOptionalBoolReturn[CommandParent]: - """ - Decorator function that ingests an Argument List function and returns a raw command function. + """Decorator function that ingests an Argument List function and returns a raw command function. The returned function will process the raw input into an argument list to be passed to the wrapped function. :param func: The defined argument list command function @@ -169,8 +166,7 @@ def arg_decorator(func: ArgListCommandFunc[CommandParent]) -> RawCommandFuncOpti @functools.wraps(func) def cmd_wrapper(*args: Any, **kwargs: Any) -> Optional[bool]: - """ - Command function wrapper which translates command line into an argument list and calls actual command function + """Command function wrapper which translates command line into an argument list and calls actual command function :param args: All positional arguments to this function. We're expecting there to be: cmd2_app, statement: Union[Statement, str] @@ -193,8 +189,7 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> Optional[bool]: def _set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None: - """ - Recursively set prog attribute of a parser and all of its subparsers so that the root command + """Recursively set prog attribute of a parser and all of its subparsers so that the root command is a command name and not sys.argv[0]. :param parser: the parser being edited @@ -294,7 +289,6 @@ def with_argparser( parsing the command line. This can be useful if the command function needs to know the command line. Example: - ```py parser = cmd2.Cmd2ArgumentParser() parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') @@ -324,12 +318,12 @@ def do_argprint(self, args, unknown): self.poutput(f'args: {args!r}') self.poutput(f'unknowns: {unknown}') ``` + """ import functools def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> RawCommandFuncOptionalBoolReturn[CommandParent]: - """ - Decorator function that ingests an Argparse Command Function and returns a raw command function. + """Decorator function that ingests an Argparse Command Function and returns a raw command function. The returned function will process the raw input into an argparse Namespace to be passed to the wrapped function. :param func: The defined argparse command function @@ -338,8 +332,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> RawCommandFuncOpt @functools.wraps(func) def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]: - """ - Command function wrapper which translates command line into argparse Namespace and calls actual + """Command function wrapper which translates command line into argparse Namespace and calls actual command function :param args: All positional arguments to this function. We're expecting there to be: @@ -417,8 +410,7 @@ def as_subcommand_to( help: Optional[str] = None, # noqa: A002 aliases: Optional[list[str]] = None, ) -> Callable[[ArgparseCommandFunc[CommandParent]], ArgparseCommandFunc[CommandParent]]: - """ - Tag this method as a subcommand to an existing argparse decorated command. + """Tag this method as a subcommand to an existing argparse decorated command. :param command: Command Name. Space-delimited subcommands may optionally be specified :param subcommand: Subcommand name diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index 98adff191..d5aa6c7a5 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -8,15 +8,13 @@ class SkipPostcommandHooks(Exception): - """ - Custom exception class for when a command has a failure bad enough to skip post command + """Custom exception class for when a command has a failure bad enough to skip post command hooks, but not bad enough to print the exception to the user. """ class Cmd2ArgparseError(SkipPostcommandHooks): - """ - A ``SkipPostcommandHooks`` exception for when a command fails to parse its arguments. + """A ``SkipPostcommandHooks`` exception for when a command fails to parse its arguments. Normally argparse raises a SystemExit exception in these cases. To avoid stopping the command loop, catch the SystemExit and raise this instead. If you still need to run post command hooks after parsing fails, just return instead of raising an exception. @@ -24,15 +22,13 @@ class Cmd2ArgparseError(SkipPostcommandHooks): class CommandSetRegistrationError(Exception): - """ - Exception that can be thrown when an error occurs while a CommandSet is being added or removed + """Exception that can be thrown when an error occurs while a CommandSet is being added or removed from a cmd2 application. """ class CompletionError(Exception): - """ - Raised during tab completion operations to report any sort of error you want printed. This can also be used + """Raised during tab completion operations to report any sort of error you want printed. This can also be used just to display a message, even if it's not an error. For instance, ArgparseCompleter raises CompletionErrors to display tab completion hints and sets apply_style to False so hints aren't colored like error text. @@ -44,8 +40,7 @@ class CompletionError(Exception): """ def __init__(self, *args: Any, apply_style: bool = True) -> None: - """ - Initializer for CompletionError + """Initializer for CompletionError :param apply_style: If True, then ansi.style_error will be applied to the message text when printed. Set to False in cases where the message text already has the desired style. Defaults to True. @@ -56,14 +51,12 @@ def __init__(self, *args: Any, apply_style: bool = True) -> None: class PassThroughException(Exception): - """ - Normally all unhandled exceptions raised during commands get printed to the user. + """Normally all unhandled exceptions raised during commands get printed to the user. This class is used to wrap an exception that should be raised instead of printed. """ def __init__(self, *args: Any, wrapped_ex: BaseException) -> None: - """ - Initializer for PassThroughException + """Initializer for PassThroughException :param wrapped_ex: the exception that will be raised """ self.wrapped_ex = wrapped_ex diff --git a/cmd2/history.py b/cmd2/history.py index 1a7c5a628..dd40c1e8c 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -1,6 +1,4 @@ -""" -History management classes -""" +"""History management classes""" import json import re @@ -28,8 +26,7 @@ def single_line_format(statement: Statement) -> str: - """ - Format a command line to display on a single line. + """Format a command line to display on a single line. Spaces and newlines in quotes are preserved so those strings will span multiple lines. @@ -134,8 +131,7 @@ def to_dict(self) -> dict[str, Any]: @staticmethod def from_dict(source_dict: dict[str, Any]) -> 'HistoryItem': - """ - Utility method to restore a HistoryItem from a dictionary + """Utility method to restore a HistoryItem from a dictionary :param source_dict: source data dictionary (generated using to_dict()) :return: HistoryItem object @@ -296,7 +292,7 @@ def str_search(self, search: str, include_persisted: bool = False) -> 'OrderedDi """ def isin(history_item: HistoryItem) -> bool: - """filter function for string search of history""" + """Filter function for string search of history""" sloppy = utils.norm_fold(search) inraw = sloppy in utils.norm_fold(history_item.raw) inexpanded = sloppy in utils.norm_fold(history_item.expanded) @@ -319,7 +315,7 @@ def regex_search(self, regex: str, include_persisted: bool = False) -> 'OrderedD finder = re.compile(regex, re.DOTALL | re.MULTILINE) def isin(hi: HistoryItem) -> bool: - """filter function for doing a regular expression search of history""" + """Filter function for doing a regular expression search of history""" return bool(finder.search(hi.raw) or finder.search(hi.expanded)) start = 0 if include_persisted else self.session_start_index @@ -342,8 +338,7 @@ def truncate(self, max_length: int) -> None: def _build_result_dictionary( self, start: int, end: int, filter_func: Optional[Callable[[HistoryItem], bool]] = None ) -> 'OrderedDict[int, HistoryItem]': - """ - Build history search results + """Build history search results :param start: start index to search from :param end: end index to stop searching (exclusive) """ @@ -363,8 +358,7 @@ def to_json(self) -> str: @staticmethod def from_json(history_json: str) -> 'History': - """ - Utility method to restore History from a JSON string + """Utility method to restore History from a JSON string :param history_json: history data as JSON string (generated using to_json()) :return: History object diff --git a/cmd2/parsing.py b/cmd2/parsing.py index b189e8f40..6cd619271 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -23,8 +23,7 @@ def shlex_split(str_to_split: str) -> list[str]: - """ - A wrapper around shlex.split() that uses cmd2's preferred arguments. + """A wrapper around shlex.split() that uses cmd2's preferred arguments. This allows other classes to easily call split() the same way StatementParser does. :param str_to_split: the string being split @@ -35,8 +34,7 @@ def shlex_split(str_to_split: str) -> list[str]: @dataclass(frozen=True) class MacroArg: - """ - Information used to replace or unescape arguments in a macro value when the macro is resolved + """Information used to replace or unescape arguments in a macro value when the macro is resolved Normal argument syntax: {5} Escaped argument syntax: {{5}} """ @@ -202,7 +200,7 @@ def expanded_command_line(self) -> str: @property def argv(self) -> list[str]: - """a list of arguments a-la ``sys.argv``. + """A list of arguments a-la ``sys.argv``. The first element of the list is the command after shortcut and macro expansion. Subsequent elements of the list contain any additional @@ -225,8 +223,7 @@ def to_dict(self) -> dict[str, Any]: @staticmethod def from_dict(source_dict: dict[str, Any]) -> 'Statement': - """ - Utility method to restore a Statement from a dictionary + """Utility method to restore a Statement from a dictionary :param source_dict: source data dictionary (generated using to_dict()) :return: Statement object @@ -364,15 +361,13 @@ def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> tuple[b return valid, errmsg def tokenize(self, line: str) -> list[str]: - """ - Lex a string into a list of tokens. Shortcuts and aliases are expanded and + """Lex a string into a list of tokens. Shortcuts and aliases are expanded and comments are removed. :param line: the command line being lexed :return: A list of tokens :raises Cmd2ShlexError: if a shlex error occurs (e.g. No closing quotation) """ - # expand shortcuts and aliases line = self._expand(line) @@ -391,8 +386,7 @@ def tokenize(self, line: str) -> list[str]: return tokens def parse(self, line: str) -> Statement: - """ - Tokenize the input and parse it into a [cmd2.parsing.Statement][] object, + """Tokenize the input and parse it into a [cmd2.parsing.Statement][] object, stripping comments, expanding aliases and shortcuts, and extracting output redirection directives. @@ -400,7 +394,6 @@ def parse(self, line: str) -> Statement: :return: a new [cmd2.parsing.Statement][] object :raises Cmd2ShlexError: if a shlex error occurs (e.g. No closing quotation) """ - # handle the special case/hardcoded terminator of a blank line # we have to do this before we tokenize because tokenizing # destroys all unquoted whitespace in the input @@ -602,8 +595,7 @@ def parse_command_only(self, rawinput: str) -> Statement: def get_command_arg_list( self, command_name: str, to_parse: Union[Statement, str], preserve_quotes: bool ) -> tuple[Statement, list[str]]: - """ - Convenience method used by the argument parsing decorators. + """Convenience method used by the argument parsing decorators. Retrieves just the arguments being passed to their ``do_*`` methods as a list. @@ -634,7 +626,6 @@ def get_command_arg_list( def _expand(self, line: str) -> str: """Expand aliases and shortcuts""" - # Make a copy of aliases so we can keep track of what aliases have been resolved to avoid an infinite loop remaining_aliases = list(self.aliases.keys()) keep_expanding = bool(remaining_aliases) diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index a0476ca1c..0a28460a1 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -1,5 +1,4 @@ -""" -Bridges calls made inside of a Python environment to the Cmd2 host app +"""Bridges calls made inside of a Python environment to the Cmd2 host app while maintaining a reasonable degree of isolation between the two. """ @@ -70,7 +69,6 @@ class CommandResult(NamedTuple): def __bool__(self) -> bool: """Returns True if the command succeeded, otherwise False""" - # If data was set, then use it to determine success if self.data is not None: return bool(self.data) @@ -80,8 +78,7 @@ def __bool__(self) -> bool: class PyBridge: - """ - Provides a Python API wrapper for application commands. + """Provides a Python API wrapper for application commands. :param cmd2_app: app being controlled by this PyBridge. :param add_to_history: If True, then add all commands run by this PyBridge to history. @@ -103,8 +100,7 @@ def __dir__(self) -> list[str]: return attributes def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResult: - """ - Provide functionality to call application commands by calling PyBridge + """Provide functionality to call application commands by calling PyBridge ex: app('help') :param command: command line being run :param echo: If provided, this temporarily overrides the value of self.cmd_echo while the diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 7f3e452a8..0ca2188f0 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -1,6 +1,4 @@ -""" -Imports the proper Readline for the platform and provides utility functions for it -""" +"""Imports the proper Readline for the platform and provides utility functions for it""" import sys from enum import ( @@ -69,8 +67,7 @@ class RlType(Enum): if sys.stdout is not None and sys.stdout.isatty(): # pragma: no cover def enable_win_vt100(handle: HANDLE) -> bool: - """ - Enables VT100 character sequences in a Windows console + """Enables VT100 character sequences in a Windows console This only works on Windows 10 and up :param handle: the handle on which to enable vt100 :return: True if vt100 characters are enabled for the handle @@ -110,8 +107,7 @@ def enable_win_vt100(handle: HANDLE) -> bool: except AttributeError: def pyreadline_remove_history_item(pos: int) -> None: - """ - An implementation of remove_history_item() for pyreadline3 + """An implementation of remove_history_item() for pyreadline3 :param pos: The 0-based position in history to remove """ # Save of the current location of the history cursor @@ -156,8 +152,7 @@ def pyreadline_remove_history_item(pos: int) -> None: def rl_force_redisplay() -> None: # pragma: no cover - """ - Causes readline to display the prompt and input text wherever the cursor is and start + """Causes readline to display the prompt and input text wherever the cursor is and start reading input from this location. This is the proper way to restore the input line after printing to the screen """ @@ -178,9 +173,7 @@ def rl_force_redisplay() -> None: # pragma: no cover def rl_get_point() -> int: # pragma: no cover - """ - Returns the offset of the current cursor position in rl_line_buffer - """ + """Returns the offset of the current cursor position in rl_line_buffer""" if rl_type == RlType.GNU: return ctypes.c_int.in_dll(readline_lib, "rl_point").value @@ -213,8 +206,7 @@ def rl_get_prompt() -> str: # pragma: no cover def rl_get_display_prompt() -> str: # pragma: no cover - """ - Get Readline's currently displayed prompt. + """Get Readline's currently displayed prompt. In GNU Readline, the displayed prompt sometimes differs from the prompt. This occurs in functions that use the prompt string as a message area, such as incremental search. @@ -230,8 +222,7 @@ def rl_get_display_prompt() -> str: # pragma: no cover def rl_set_prompt(prompt: str) -> None: # pragma: no cover - """ - Sets Readline's prompt + """Sets Readline's prompt :param prompt: the new prompt value """ escaped_prompt = rl_escape_prompt(prompt) @@ -245,8 +236,7 @@ def rl_set_prompt(prompt: str) -> None: # pragma: no cover def rl_escape_prompt(prompt: str) -> str: - """ - Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes + """Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes :param prompt: original prompt :return: prompt safe to pass to GNU Readline diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index 340be3df0..f3bf18cbc 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -1,5 +1,4 @@ -""" -cmd2 table creation API +"""cmd2 table creation API This API is built upon two core classes: Column and TableCreator The general use case is to inherit from TableCreator to create a table class with custom formatting options. There are already implemented and ready-to-use examples of this below TableCreator's code. @@ -66,8 +65,7 @@ def __init__( style_data_text: bool = True, max_data_lines: float = constants.INFINITY, ) -> None: - """ - Column initializer + """Column initializer :param header: label for column header :param width: display width of column. This does not account for any borders or padding which @@ -111,8 +109,7 @@ def __init__( class TableCreator: - """ - Base table creation class. This class handles ANSI style sequences and characters with display widths greater than 1 + """Base table creation class. This class handles ANSI style sequences and characters with display widths greater than 1 when performing width calculations. It was designed with the ability to build tables one row at a time. This helps when you have large data sets that you don't want to hold in memory or when you receive portions of the data set incrementally. @@ -126,8 +123,7 @@ class TableCreator: """ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None: - """ - TableCreator initializer + """TableCreator initializer :param cols: column definitions for this table :param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab, @@ -151,8 +147,7 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None: @staticmethod def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> tuple[str, int, int]: - """ - Used by _wrap_text() to wrap a long word over multiple lines + """Used by _wrap_text() to wrap a long word over multiple lines :param word: word being wrapped :param max_width: maximum display width of a line @@ -215,8 +210,7 @@ def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: b @staticmethod def _wrap_text(text: str, max_width: int, max_lines: float) -> str: - """ - Wrap text into lines with a display width no longer than max_width. This function breaks words on whitespace + """Wrap text into lines with a display width no longer than max_width. This function breaks words on whitespace boundaries. If a word is longer than the space remaining on a line, then it will start on a new line. ANSI escape sequences do not count toward the width of a line. @@ -225,14 +219,12 @@ def _wrap_text(text: str, max_width: int, max_lines: float) -> str: :param max_lines: maximum lines to wrap before ending the last line displayed with an ellipsis :return: wrapped text """ - # 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 + """Called from loop to add a word to the wrapped text :param word_to_add: the word being added :param is_last_word: True if this is the last word of the total text being wrapped @@ -377,8 +369,7 @@ def add_word(word_to_add: str, is_last_word: bool) -> None: return wrapped_buf.getvalue() def _generate_cell_lines(self, cell_data: Any, is_header: bool, col: Column, fill_char: str) -> tuple[deque[str], int]: - """ - Generate the lines of a table cell + """Generate the lines of a table cell :param cell_data: data to be included in cell :param is_header: True if writing a header cell, otherwise writing a data cell. This determines whether to @@ -422,8 +413,7 @@ def generate_row( inter_cell: str = (2 * SPACE), post_line: str = EMPTY, ) -> str: - """ - Generate a header or data table row + """Generate a header or data table row :param row_data: data with an entry for each column in the row :param is_header: True if writing a header cell, otherwise writing a data cell. This determines whether to @@ -538,8 +528,7 @@ def __init__(self) -> None: # of tables. They can be used as-is or serve as inspiration for other custom table classes. ############################################################################################################ class SimpleTable(TableCreator): - """ - Implementation of TableCreator which generates a borderless table with an optional divider row after the header. + """Implementation of TableCreator which generates a borderless table with an optional divider row after the header. This class can be used to create the whole table at once or one row at a time. """ @@ -553,8 +542,7 @@ def __init__( header_bg: Optional[ansi.BgColor] = None, data_bg: Optional[ansi.BgColor] = None, ) -> None: - """ - SimpleTable initializer + """SimpleTable initializer :param cols: column definitions for this table :param column_spacing: how many spaces to place between columns. Defaults to 2. @@ -592,8 +580,7 @@ def __init__( self.data_bg = data_bg def apply_header_bg(self, value: Any) -> str: - """ - If defined, apply the header background color to header text + """If defined, apply the header background color to header text :param value: object whose text is to be colored :return: formatted text """ @@ -602,8 +589,7 @@ def apply_header_bg(self, value: Any) -> str: return ansi.style(value, bg=self.header_bg) def apply_data_bg(self, value: Any) -> str: - """ - If defined, apply the data background color to data text + """If defined, apply the data background color to data text :param value: object whose text is to be colored :return: formatted data string """ @@ -613,8 +599,7 @@ def apply_data_bg(self, value: Any) -> str: @classmethod def base_width(cls, num_cols: int, *, column_spacing: int = 2) -> int: - """ - Utility method to calculate the display width required for a table before data is added to it. + """Utility method to calculate the display width required for a table before data is added to it. This is useful when determining how wide to make your columns to have a table be a specific width. :param num_cols: how many columns the table will have @@ -674,8 +659,7 @@ def generate_divider(self) -> str: return utils.align_left('', fill_char=self.divider_char, width=self.total_width()) def generate_data_row(self, row_data: Sequence[Any]) -> str: - """ - Generate a data row + """Generate a data row :param row_data: data with an entry for each column in the row :return: data row string @@ -698,8 +682,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: return self.generate_row(to_display, is_header=False, fill_char=fill_char, inter_cell=inter_cell) def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True, row_spacing: int = 1) -> str: - """ - Generate a table from a data set + """Generate a table from a data set :param table_data: Data with an entry for each data row of the table. Each entry should have data for each column in the row. @@ -734,8 +717,7 @@ def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: class BorderedTable(TableCreator): - """ - Implementation of TableCreator which generates a table with borders around the table and between rows. Borders + """Implementation of TableCreator which generates a table with borders around the table and between rows. Borders between columns can also be toggled. This class can be used to create the whole table at once or one row at a time. """ @@ -751,8 +733,7 @@ def __init__( header_bg: Optional[ansi.BgColor] = None, data_bg: Optional[ansi.BgColor] = None, ) -> None: - """ - BorderedTable initializer + """BorderedTable initializer :param cols: column definitions for this table :param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab, @@ -782,8 +763,7 @@ def __init__( self.data_bg = data_bg def apply_border_color(self, value: Any) -> str: - """ - If defined, apply the border foreground and background colors + """If defined, apply the border foreground and background colors :param value: object whose text is to be colored :return: formatted text """ @@ -792,8 +772,7 @@ def apply_border_color(self, value: Any) -> str: return ansi.style(value, fg=self.border_fg, bg=self.border_bg) def apply_header_bg(self, value: Any) -> str: - """ - If defined, apply the header background color to header text + """If defined, apply the header background color to header text :param value: object whose text is to be colored :return: formatted text """ @@ -802,8 +781,7 @@ def apply_header_bg(self, value: Any) -> str: return ansi.style(value, bg=self.header_bg) def apply_data_bg(self, value: Any) -> str: - """ - If defined, apply the data background color to data text + """If defined, apply the data background color to data text :param value: object whose text is to be colored :return: formatted data string """ @@ -813,8 +791,7 @@ def apply_data_bg(self, value: Any) -> str: @classmethod def base_width(cls, num_cols: int, *, column_borders: bool = True, padding: int = 1) -> int: - """ - Utility method to calculate the display width required for a table before data is added to it. + """Utility method to calculate the display width required for a table before data is added to it. This is useful when determining how wide to make your columns to have a table be a specific width. :param num_cols: how many columns the table will have @@ -964,8 +941,7 @@ def generate_header(self) -> str: return header_buf.getvalue() def generate_data_row(self, row_data: Sequence[Any]) -> str: - """ - Generate a data row + """Generate a data row :param row_data: data with an entry for each column in the row :return: data row string @@ -998,8 +974,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: ) def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True) -> str: - """ - Generate a table from a data set + """Generate a table from a data set :param table_data: Data with an entry for each data row of the table. Each entry should have data for each column in the row. @@ -1031,8 +1006,7 @@ def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: class AlternatingTable(BorderedTable): - """ - Implementation of BorderedTable which uses background colors to distinguish between rows instead of row border + """Implementation of BorderedTable which uses background colors to distinguish between rows instead of row border lines. This class can be used to create the whole table at once or one row at a time. To nest an AlternatingTable within another AlternatingTable, set style_data_text to False on the Column @@ -1053,8 +1027,7 @@ def __init__( odd_bg: Optional[ansi.BgColor] = None, even_bg: Optional[ansi.BgColor] = ansi.Bg.DARK_GRAY, ) -> None: - """ - AlternatingTable initializer + """AlternatingTable initializer Note: Specify background colors using subclasses of BgColor (e.g. Bg, EightBitBg, RgbBg) @@ -1087,8 +1060,7 @@ def __init__( self.even_bg = even_bg def apply_data_bg(self, value: Any) -> str: - """ - Apply background color to data text based on what row is being generated and whether a color has been defined + """Apply background color to data text based on what row is being generated and whether a color has been defined :param value: object whose text is to be colored :return: formatted data string """ @@ -1099,8 +1071,7 @@ def apply_data_bg(self, value: Any) -> str: return str(value) def generate_data_row(self, row_data: Sequence[Any]) -> str: - """ - Generate a data row + """Generate a data row :param row_data: data with an entry for each column in the row :return: data row string @@ -1110,8 +1081,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: return row def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True) -> str: - """ - Generate a table from a data set + """Generate a table from a data set :param table_data: Data with an entry for each data row of the table. Each entry should have data for each column in the row. diff --git a/cmd2/utils.py b/cmd2/utils.py index d4e686655..0a12b53be 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -48,8 +48,7 @@ def is_quoted(arg: str) -> bool: - """ - Checks if a string is quoted + """Checks if a string is quoted :param arg: the string being checked for quotes :return: True if a string is quoted @@ -125,8 +124,7 @@ def __init__( choices_provider: Optional[ChoicesProviderFunc] = None, completer: Optional[CompleterFunc] = None, ) -> None: - """ - Settable Initializer + """Settable Initializer :param name: name of the instance attribute being made settable :param val_type: callable used to cast the string value from the command line into its proper type and @@ -176,8 +174,7 @@ def get_value(self) -> Any: return getattr(self.settable_obj, self.settable_attrib_name) def set_value(self, value: Any) -> None: - """ - Set the settable attribute on the specified destination object. + """Set the settable attribute on the specified destination object. :param value: new value to set """ @@ -263,8 +260,7 @@ def alphabetical_sort(list_to_sort: Iterable[str]) -> list[str]: def try_int_or_force_to_lower_case(input_str: str) -> Union[int, str]: - """ - Tries to convert the passed-in string to an integer. If that fails, it converts it to lower case using norm_fold. + """Tries to convert the passed-in string to an integer. If that fails, it converts it to lower case using norm_fold. :param input_str: string to convert :return: the string as an integer or a lower case version of the string """ @@ -275,8 +271,7 @@ def try_int_or_force_to_lower_case(input_str: str) -> Union[int, str]: def natural_keys(input_str: str) -> list[Union[int, str]]: - """ - Converts a string into a list of integers and strings to support natural sorting (see natural_sort). + """Converts a string into a list of integers and strings to support natural sorting (see natural_sort). For example: natural_keys('abc123def') -> ['abc', '123', 'def'] :param input_str: string to convert @@ -286,8 +281,7 @@ def natural_keys(input_str: str) -> list[Union[int, str]]: def natural_sort(list_to_sort: Iterable[str]) -> list[str]: - """ - Sorts a list of strings case insensitively as well as numerically. + """Sorts a list of strings case insensitively as well as numerically. For example: ['a1', 'A2', 'a3', 'A11', 'a22'] @@ -302,8 +296,7 @@ def natural_sort(list_to_sort: Iterable[str]) -> list[str]: def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None: - """ - Quote specific tokens in a list + """Quote specific tokens in a list :param tokens: token list being edited :param tokens_to_quote: the tokens, which if present in tokens, to quote @@ -314,8 +307,7 @@ def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> None: - """ - Unquote specific tokens in a list + """Unquote specific tokens in a list :param tokens: token list being edited :param tokens_to_unquote: the tokens, which if present in tokens, to unquote @@ -327,8 +319,7 @@ def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> def expand_user(token: str) -> str: - """ - Wrap os.expanduser() to support expanding ~ in quoted strings + """Wrap os.expanduser() to support expanding ~ in quoted strings :param token: the string to expand """ if token: @@ -348,8 +339,7 @@ def expand_user(token: str) -> str: def expand_user_in_tokens(tokens: list[str]) -> None: - """ - Call expand_user() on all tokens in a list of strings + """Call expand_user() on all tokens in a list of strings :param tokens: tokens to expand """ for index, _ in enumerate(tokens): @@ -357,8 +347,7 @@ def expand_user_in_tokens(tokens: list[str]) -> None: def find_editor() -> Optional[str]: - """ - Used to set cmd2.Cmd.DEFAULT_EDITOR. If EDITOR env variable is set, that will be used. + """Used to set cmd2.Cmd.DEFAULT_EDITOR. If EDITOR env variable is set, that will be used. Otherwise the function will look for a known editor in directories specified by PATH env variable. :return: Default editor or None """ @@ -453,8 +442,7 @@ def get_exes_in_path(starts_with: str) -> list[str]: class StdSim: - """ - Class to simulate behavior of sys.stdout or sys.stderr. + """Class to simulate behavior of sys.stdout or sys.stderr. Stores contents in internal buffer and optionally echos to the inner stream it is simulating. """ @@ -466,8 +454,7 @@ def __init__( encoding: str = 'utf-8', errors: str = 'replace', ) -> None: - """ - StdSim Initializer + """StdSim Initializer :param inner_stream: the wrapped stream. Should be a TextIO or StdSim instance. :param echo: if True, then all input will be echoed to inner_stream @@ -482,8 +469,7 @@ def __init__( self.buffer = ByteBuf(self) def write(self, s: str) -> None: - """ - Add str to internal bytes buffer and if echo is True, echo contents to inner stream + """Add str to internal bytes buffer and if echo is True, echo contents to inner stream :param s: String to write to the stream """ @@ -504,8 +490,7 @@ def getbytes(self) -> bytes: return bytes(self.buffer.byte_buf) def read(self, size: Optional[int] = -1) -> str: - """ - Read from the internal contents as a str and then clear them out + """Read from the internal contents as a str and then clear them out :param size: Number of bytes to read from the stream """ @@ -536,8 +521,7 @@ def isatty(self) -> bool: @property def line_buffering(self) -> bool: - """ - Handle when the inner stream doesn't have a line_buffering attribute which is the case + """Handle when the inner stream doesn't have a line_buffering attribute which is the case when running unit tests because pytest sets stdout to a pytest EncodedFile object. """ try: @@ -552,9 +536,7 @@ def __getattr__(self, item: str) -> Any: class ByteBuf: - """ - Used by StdSim to write binary data and stores the actual bytes written - """ + """Used by StdSim to write binary data and stores the actual bytes written""" # Used to know when to flush the StdSim NEWLINES = [b'\n', b'\r'] @@ -582,14 +564,12 @@ def write(self, b: bytes) -> None: class ProcReader: - """ - Used to capture stdout and stderr from a Popen process if any of those were set to subprocess.PIPE. + """Used to capture stdout and stderr from a Popen process if any of those were set to subprocess.PIPE. If neither are pipes, then the process will run normally and no output will be captured. """ def __init__(self, proc: PopenTextIO, stdout: Union[StdSim, TextIO], stderr: Union[StdSim, TextIO]) -> None: - """ - ProcReader initializer + """ProcReader initializer :param proc: the Popen process being read from :param stdout: the stream to write captured stdout :param stderr: the stream to write captured stderr @@ -646,8 +626,7 @@ def wait(self) -> None: self._write_bytes(self._stderr, err) def _reader_thread_func(self, read_stdout: bool) -> None: - """ - Thread function that reads a stream from the process + """Thread function that reads a stream from the process :param read_stdout: if True, then this thread deals with stdout. Otherwise it deals with stderr. """ if read_stdout: @@ -669,8 +648,7 @@ def _reader_thread_func(self, read_stdout: bool) -> None: @staticmethod def _write_bytes(stream: Union[StdSim, TextIO], to_write: Union[bytes, str]) -> None: - """ - Write bytes to a stream + """Write bytes to a stream :param stream: the stream being written to :param to_write: the bytes being written """ @@ -719,8 +697,7 @@ def __init__( pipe_proc_reader: Optional[ProcReader], saved_redirecting: bool, ) -> None: - """ - RedirectionSavedState initializer + """RedirectionSavedState initializer :param self_stdout: saved value of Cmd.stdout :param sys_stdout: saved value of sys.stdout :param pipe_proc_reader: saved value of Cmd._cur_pipe_proc_reader @@ -739,8 +716,7 @@ def __init__( def _remove_overridden_styles(styles_to_parse: list[str]) -> list[str]: - """ - Utility function for align_text() / truncate_line() which filters a style list down + """Utility function for align_text() / truncate_line() which filters a style list down to only those which would still be in effect if all were processed in order. This is mainly used to reduce how many style strings are stored in memory when @@ -836,8 +812,7 @@ def align_text( tab_width: int = 4, truncate: bool = False, ) -> str: - """ - Align text for display within a given width. Supports characters with display widths greater than 1. + """Align text for display within a given width. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. If text has line breaks, then each line is aligned independently. @@ -963,8 +938,7 @@ def align_text( def align_left( text: str, *, fill_char: str = ' ', width: Optional[int] = None, tab_width: int = 4, truncate: bool = False ) -> str: - """ - Left align text for display within a given width. Supports characters with display widths greater than 1. + """Left align text for display within a given width. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. If text has line breaks, then each line is aligned independently. @@ -986,8 +960,7 @@ def align_left( def align_center( text: str, *, fill_char: str = ' ', width: Optional[int] = None, tab_width: int = 4, truncate: bool = False ) -> str: - """ - Center text for display within a given width. Supports characters with display widths greater than 1. + """Center text for display within a given width. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. If text has line breaks, then each line is aligned independently. @@ -1009,8 +982,7 @@ def align_center( def align_right( text: str, *, fill_char: str = ' ', width: Optional[int] = None, tab_width: int = 4, truncate: bool = False ) -> str: - """ - Right align text for display within a given width. Supports characters with display widths greater than 1. + """Right align text for display within a given width. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. If text has line breaks, then each line is aligned independently. @@ -1030,8 +1002,7 @@ def align_right( def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: - """ - Truncate a single line to fit within a given display width. Any portion of the string that is truncated + """Truncate a single line to fit within a given display width. Any portion of the string that is truncated is replaced by a '…' character. Supports characters with display widths greater than 1. ANSI style sequences do not count toward the display width. @@ -1109,8 +1080,7 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: def get_styles_dict(text: str) -> dict[int, str]: - """ - Return an OrderedDict containing all ANSI style sequences found in a string + """Return an OrderedDict containing all ANSI style sequences found in a string The structure of the dictionary is: key: index where sequences begins @@ -1147,7 +1117,6 @@ def categorize(func: Union[Callable[..., Any], Iterable[Callable[..., Any]]], ca :param category: category to put it in Example: - ```py import cmd2 class MyApp(cmd2.Cmd): @@ -1158,6 +1127,7 @@ def do_echo(self, arglist): ``` For an alternative approach to categorizing commands using a decorator, see [cmd2.decorators.with_category][] + """ if isinstance(func, Iterable): for item in func: @@ -1169,8 +1139,7 @@ def do_echo(self, arglist): def get_defining_class(meth: Callable[..., Any]) -> Optional[type[Any]]: - """ - Attempts to resolve the class that defined a method. + """Attempts to resolve the class that defined a method. Inspired by implementation published here: https://stackoverflow.com/a/25959545/1956611 @@ -1216,8 +1185,7 @@ class CustomCompletionSettings: """Used by cmd2.Cmd.complete() to tab complete strings other than command arguments""" def __init__(self, parser: argparse.ArgumentParser, *, preserve_quotes: bool = False) -> None: - """ - Initializer + """Initializer :param parser: arg parser defining format of string being tab completed :param preserve_quotes: if True, then quoted tokens will keep their quotes when processed by @@ -1231,8 +1199,7 @@ def __init__(self, parser: argparse.ArgumentParser, *, preserve_quotes: bool = F def strip_doc_annotations(doc: str) -> str: - """ - Strip annotations from a docstring leaving only the text description + """Strip annotations from a docstring leaving only the text description :param doc: documentation string """ @@ -1268,15 +1235,13 @@ def similarity_function(s1: str, s2: str) -> float: def suggest_similar( requested_command: str, options: Iterable[str], similarity_function_to_use: Optional[Callable[[str, str], float]] = None ) -> Optional[str]: - """ - Given a requested command and an iterable of possible options returns the most similar (if any is similar) + """Given a requested command and an iterable of possible options returns the most similar (if any is similar) :param requested_command: The command entered by the user :param options: The list of available commands to search for the most similar :param similarity_function_to_use: An optional callable to use to compare commands :return: The most similar command or None if no one is similar """ - proposed_command = None best_simil = MIN_SIMIL_TO_CONSIDER requested_command_to_compare = requested_command.lower() diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index ec0cddd79..7ce86b141 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -48,8 +48,7 @@ def do_fsize(self, args: argparse.Namespace) -> None: @cmd2.with_argparser(pow_parser) def do_pow(self, args: argparse.Namespace) -> None: - """ - Raise an integer to a small integer exponent, either positive or negative + """Raise an integer to a small integer exponent, either positive or negative :param args: argparse arguments """ diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index df3c31551..7db68184e 100755 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -""" -A simple example demonstrating how to integrate tab completion with argparse-based commands. -""" +"""A simple example demonstrating how to integrate tab completion with argparse-based commands.""" import argparse @@ -28,8 +26,7 @@ def choices_provider(self) -> list[str]: return self.sport_item_strs def choices_completion_error(self) -> list[str]: - """ - CompletionErrors can be raised if an error occurs while tab completing. + """CompletionErrors can be raised if an error occurs while tab completing. Example use cases - Reading a database to retrieve a tab completion data set failed @@ -47,8 +44,7 @@ def choices_completion_item(self) -> list[CompletionItem]: return [CompletionItem(item_id, description) for item_id, description in items.items()] def choices_arg_tokens(self, arg_tokens: dict[str, list[str]]) -> list[str]: - """ - If a choices or completer function/method takes a value called arg_tokens, then it will be + """If a choices or completer function/method takes a value called arg_tokens, then it will be passed a dictionary that maps the command line tokens up through the one being completed to their argparse argument name. All values of the arg_tokens dictionary are lists, even if a particular argument expects only 1 token. diff --git a/examples/async_printing.py b/examples/async_printing.py index d1137c3fe..2b9af76f7 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -A simple example demonstrating an application that asynchronously prints alerts, updates the prompt +"""A simple example demonstrating an application that asynchronously prints alerts, updates the prompt and changes the window title """ @@ -60,7 +59,6 @@ def _preloop_hook(self) -> None: def _postloop_hook(self) -> None: """Stops the alerter thread""" - # After this function returns, cmdloop() releases self.terminal_lock which could make the alerter # thread think the prompt is on screen. Therefore this is the best place to stop the alerter thread. # You can also stop it via a command. See do_stop_alerts(). @@ -86,8 +84,7 @@ def do_stop_alerts(self, _) -> None: print("The alert thread is already stopped") def _get_alerts(self) -> list[str]: - """ - Reports alerts + """Reports alerts :return: the list of alerts """ cur_time = time.monotonic() @@ -115,8 +112,7 @@ def _get_alerts(self) -> list[str]: return alerts def _generate_alert_str(self) -> str: - """ - Combines alerts into one string that can be printed to the terminal + """Combines alerts into one string that can be printed to the terminal :return: the alert string """ alert_str = '' @@ -138,8 +134,7 @@ def _generate_alert_str(self) -> str: return alert_str def _generate_colored_prompt(self) -> str: - """ - Randomly generates a colored prompt + """Randomly generates a colored prompt :return: the new prompt """ rand_num = random.randint(1, 20) @@ -161,7 +156,6 @@ def _generate_colored_prompt(self) -> str: def _alerter_thread_func(self) -> None: """Prints alerts and updates the prompt any time the prompt is showing""" - self._alert_count = 0 self._next_alert_time = 0 diff --git a/examples/basic_completion.py b/examples/basic_completion.py index 0c451077c..2862440ea 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -A simple example demonstrating how to enable tab completion by assigning a completer function to do_* commands. +"""A simple example demonstrating how to enable tab completion by assigning a completer function to do_* commands. This also demonstrates capabilities of the following completer features included with cmd2: - CompletionError exceptions - delimiter_complete() @@ -84,8 +83,7 @@ def do_raise_error(self, statement: cmd2.Statement) -> None: self.poutput(f"Args: {statement.args}") def complete_raise_error(self, text, line, begidx, endidx) -> list[str]: - """ - CompletionErrors can be raised if an error occurs while tab completing. + """CompletionErrors can be raised if an error occurs while tab completing. Example use cases - Reading a database to retrieve a tab completion data set failed diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index 2cb7691c0..b05c02a7c 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -A sample application for cmd2. +"""A sample application for cmd2. This example is very similar to example.py, but had additional code in main() that shows how to accept a command from @@ -84,7 +83,6 @@ def do_mumble(self, args) -> None: def main(argv=None): """Run when invoked from the operating system shell""" - parser = cmd2.Cmd2ArgumentParser(description='Commands as arguments') command_help = 'optional command to run, if no command given, enter an interactive shell' parser.add_argument('command', nargs='?', help=command_help) diff --git a/examples/colors.py b/examples/colors.py index 792173b86..0d20be8c9 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -A sample application for cmd2. Demonstrating colorized output. +"""A sample application for cmd2. Demonstrating colorized output. Experiment with the command line options on the `speak` command to see how different output colors ca diff --git a/examples/custom_parser.py b/examples/custom_parser.py index fd74f5b56..ba1237531 100644 --- a/examples/custom_parser.py +++ b/examples/custom_parser.py @@ -1,6 +1,4 @@ -""" -Defines the CustomParser used with override_parser.py example -""" +"""Defines the CustomParser used with override_parser.py example""" import sys diff --git a/examples/decorator_example.py b/examples/decorator_example.py index ce48b4ba6..bb9d9306f 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -63,7 +63,7 @@ def do_speak(self, args: argparse.Namespace) -> None: @cmd2.with_argparser(tag_parser) def do_tag(self, args: argparse.Namespace) -> None: - """create an html tag""" + """Create an html tag""" # The Namespace always includes the Statement object created when parsing the command line statement = args.cmd2_statement.get() @@ -73,7 +73,7 @@ def do_tag(self, args: argparse.Namespace) -> None: @cmd2.with_argument_list def do_tagg(self, arglist: list[str]) -> None: - """version of creating an html tag using arglist instead of argparser""" + """Version of creating an html tag using arglist instead of argparser""" if len(arglist) >= 2: tag = arglist[0] content = arglist[1:] diff --git a/examples/default_categories.py b/examples/default_categories.py index 3a26a45ff..246e4bbd3 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -""" -Simple example demonstrating basic CommandSet usage. -""" +"""Simple example demonstrating basic CommandSet usage.""" import cmd2 from cmd2 import ( @@ -16,9 +14,7 @@ class MyBaseCommandSet(CommandSet): class ChildInheritsParentCategories(MyBaseCommandSet): - """ - This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category' - """ + """This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category'""" def do_hello(self, _: cmd2.Statement) -> None: self._cmd.poutput('Hello') @@ -29,8 +25,7 @@ def do_world(self, _: cmd2.Statement) -> None: @with_default_category('Non-Heritable Category', heritable=False) class ChildOverridesParentCategoriesNonHeritable(MyBaseCommandSet): - """ - This subclass overrides the 'Default Category' from the parent, but in a non-heritable fashion. Sub-classes of this + """This subclass overrides the 'Default Category' from the parent, but in a non-heritable fashion. Sub-classes of this CommandSet will not inherit this category and will, instead, inherit 'Default Category' """ @@ -39,8 +34,7 @@ def do_goodbye(self, _: cmd2.Statement) -> None: class GrandchildInheritsGrandparentCategory(ChildOverridesParentCategoriesNonHeritable): - """ - This subclass's parent class declared its default category non-heritable. Instead, it inherits the category defined + """This subclass's parent class declared its default category non-heritable. Instead, it inherits the category defined by the grandparent class. """ @@ -50,8 +44,7 @@ def do_aloha(self, _: cmd2.Statement) -> None: @with_default_category('Heritable Category') class ChildOverridesParentCategories(MyBaseCommandSet): - """ - This subclass is decorated with a default category that is heritable. This overrides the parent class's default + """This subclass is decorated with a default category that is heritable. This overrides the parent class's default category declaration. """ @@ -60,8 +53,7 @@ def do_bonjour(self, _: cmd2.Statement) -> None: class GrandchildInheritsHeritable(ChildOverridesParentCategories): - """ - This subclass's parent declares a default category that overrides its parent. As a result, commands in this + """This subclass's parent declares a default category that overrides its parent. As a result, commands in this CommandSet will be categorized under 'Heritable Category' """ @@ -70,9 +62,7 @@ def do_monde(self, _: cmd2.Statement) -> None: class ExampleApp(cmd2.Cmd): - """ - Example to demonstrate heritable default categories - """ + """Example to demonstrate heritable default categories""" def __init__(self) -> None: super().__init__() diff --git a/examples/environment.py b/examples/environment.py index 352ca9d0e..cf2775eb7 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -""" -A sample application for cmd2 demonstrating customized environment parameters -""" +"""A sample application for cmd2 demonstrating customized environment parameters""" import cmd2 diff --git a/examples/example.py b/examples/example.py index 51e35289d..a24be2b0a 100755 --- a/examples/example.py +++ b/examples/example.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -A sample application for cmd2. +"""A sample application for cmd2. Thanks to cmd2's built-in transcript testing capability, it also serves as a test suite for example.py when used with the transcript_regex.txt transcript. diff --git a/examples/exit_code.py b/examples/exit_code.py index 58877d2ad..bfce8c909 100755 --- a/examples/exit_code.py +++ b/examples/exit_code.py @@ -16,7 +16,8 @@ def do_exit(self, arg_list: list[str]) -> bool: Usage: exit [exit_code] Where: - * exit_code - integer exit code to return to the shell""" + * exit_code - integer exit code to return to the shell + """ # If an argument was provided if arg_list: try: diff --git a/examples/first_app.py b/examples/first_app.py index f52cb6116..c82768a37 100755 --- a/examples/first_app.py +++ b/examples/first_app.py @@ -1,15 +1,14 @@ #!/usr/bin/env python -""" -A simple application using cmd2 which demonstrates 8 key features: - - * Settings - * Commands - * Argument Parsing - * Generating Output - * Help - * Shortcuts - * Multiline Commands - * History +"""A simple application using cmd2 which demonstrates 8 key features: + +* Settings +* Commands +* Argument Parsing +* Generating Output +* Help +* Shortcuts +* Multiline Commands +* History """ import cmd2 diff --git a/examples/hello_cmd2.py b/examples/hello_cmd2.py index 76ff55c75..a480aa5e4 100755 --- a/examples/hello_cmd2.py +++ b/examples/hello_cmd2.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -""" -This is intended to be a completely bare-bones cmd2 application suitable for rapid testing and debugging. -""" +"""This is intended to be a completely bare-bones cmd2 application suitable for rapid testing and debugging.""" from cmd2 import ( cmd2, diff --git a/examples/help_categories.py b/examples/help_categories.py index 35f333942..6c44f82f7 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -A sample application for tagging categories on commands. +"""A sample application for tagging categories on commands. It also demonstrates the effects of decorator order when it comes to argparse errors occurring. """ @@ -126,8 +125,7 @@ def do_thread_dump(self, _) -> None: self.poutput('Thread Dump') def do_sslconnectorciphers(self, _) -> None: - """ - SSL Connector Ciphers command is an example of a command that contains + """SSL Connector Ciphers command is an example of a command that contains multiple lines of help information for the user. Each line of help in a contiguous set of lines will be printed and aligned in the verbose output provided with 'help --verbose' diff --git a/examples/hooks.py b/examples/hooks.py index e019b3e0b..c2c3e2b0d 100755 --- a/examples/hooks.py +++ b/examples/hooks.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -A sample application for cmd2 demonstrating how to use hooks. +"""A sample application for cmd2 demonstrating how to use hooks. This application shows how to use postparsing hooks to allow case insensitive command names, abbreviated commands, as well as allowing numeric arguments to diff --git a/examples/migrating.py b/examples/migrating.py index 5d57f1ba2..42276bf9a 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -""" -A sample cmd application that shows how to trivially migrate a cmd application to use cmd2. -""" +"""A sample cmd application that shows how to trivially migrate a cmd application to use cmd2.""" # import cmd2 as cmd import cmd # Comment this line and uncomment the one above to migrate to cmd2 diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index 7e17ac024..d4515e3c1 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -1,6 +1,4 @@ -""" -A simple example demonstrating a loadable command set -""" +"""A simple example demonstrating a loadable command set""" from cmd2 import ( CommandSet, @@ -76,8 +74,7 @@ def do_raise_error(self, statement: Statement) -> None: self._cmd.poutput(f"Args: {statement.args}") def complete_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: - """ - CompletionErrors can be raised if an error occurs while tab completing. + """CompletionErrors can be raised if an error occurs while tab completing. Example use cases - Reading a database to retrieve a tab completion data set failed diff --git a/examples/modular_commands/commandset_complex.py b/examples/modular_commands/commandset_complex.py index 1cc76e3a8..a33800440 100644 --- a/examples/modular_commands/commandset_complex.py +++ b/examples/modular_commands/commandset_complex.py @@ -1,6 +1,4 @@ -""" -Test CommandSet -""" +"""Test CommandSet""" import argparse diff --git a/examples/modular_commands/commandset_custominit.py b/examples/modular_commands/commandset_custominit.py index 0b5101b27..83c1fbea8 100644 --- a/examples/modular_commands/commandset_custominit.py +++ b/examples/modular_commands/commandset_custominit.py @@ -1,6 +1,4 @@ -""" -A simple example demonstrating a loadable command set -""" +"""A simple example demonstrating a loadable command set""" from cmd2 import ( Cmd, diff --git a/examples/modular_commands_basic.py b/examples/modular_commands_basic.py index 23065a3bd..7b8f26dba 100755 --- a/examples/modular_commands_basic.py +++ b/examples/modular_commands_basic.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -""" -Simple example demonstrating basic CommandSet usage. -""" +"""Simple example demonstrating basic CommandSet usage.""" import cmd2 from cmd2 import ( @@ -23,9 +21,7 @@ def do_world(self, _: cmd2.Statement) -> None: class ExampleApp(cmd2.Cmd): - """ - CommandSets are automatically loaded. Nothing needs to be done. - """ + """CommandSets are automatically loaded. Nothing needs to be done.""" def __init__(self) -> None: super().__init__() diff --git a/examples/modular_commands_dynamic.py b/examples/modular_commands_dynamic.py index 6a399a5c8..46170f696 100755 --- a/examples/modular_commands_dynamic.py +++ b/examples/modular_commands_dynamic.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -""" -Simple example demonstrating dynamic CommandSet loading and unloading. +"""Simple example demonstrating dynamic CommandSet loading and unloading. There are 2 CommandSets defined. ExampleApp sets the `auto_load_commands` flag to false. @@ -44,9 +43,7 @@ def do_bokchoy(self, _: cmd2.Statement) -> None: class ExampleApp(cmd2.Cmd): - """ - CommandSets are loaded via the `load` and `unload` commands - """ + """CommandSets are loaded via the `load` and `unload` commands""" def __init__(self, *args, **kwargs) -> None: # gotta have this or neither the plugin or cmd2 will initialize diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 7477d5310..16fad3146 100755 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators +"""A complex example demonstrating a variety of methods to load CommandSets using a mix of command decorators with examples of how to integrate tab completion with argparse-based commands. """ diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index e3a0fec43..98c3015ef 100755 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -57,9 +57,7 @@ def cut_bokchoy(self, _: argparse.Namespace) -> None: class ExampleApp(cmd2.Cmd): - """ - CommandSets are automatically loaded. Nothing needs to be done. - """ + """CommandSets are automatically loaded. Nothing needs to be done.""" def __init__(self, *args, **kwargs) -> None: # gotta have this or neither the plugin or cmd2 will initialize diff --git a/examples/override_parser.py b/examples/override_parser.py index f08e0dc30..0c74fb2df 100755 --- a/examples/override_parser.py +++ b/examples/override_parser.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -The standard parser used by cmd2 built-in commands is Cmd2ArgumentParser. +"""The standard parser used by cmd2 built-in commands is Cmd2ArgumentParser. The following code shows how to override it with your own parser class. """ diff --git a/examples/pirate.py b/examples/pirate.py index d6095b6dd..a6e657fc1 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -""" -This example is adapted from the pirate8.py example created by Catherine Devlin and +"""This example is adapted from the pirate8.py example created by Catherine Devlin and presented as part of her PyCon 2010 talk. It demonstrates many features of cmd2. @@ -59,7 +58,8 @@ def do_loot(self, arg) -> None: def do_drink(self, arg) -> None: """Drown your sorrrows in rrrum. - drink [n] - drink [n] barrel[s] o' rum.""" + drink [n] - drink [n] barrel[s] o' rum. + """ try: self.gold -= int(arg) except ValueError: diff --git a/examples/read_input.py b/examples/read_input.py index a8404bac1..61ba9c8fc 100755 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -""" -A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion -""" +"""A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion""" import cmd2 diff --git a/examples/remove_settable.py b/examples/remove_settable.py index 266f7b7d5..c2c338890 100755 --- a/examples/remove_settable.py +++ b/examples/remove_settable.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -""" -A sample application for cmd2 demonstrating how to remove one of the built-in runtime settable parameters. -""" +"""A sample application for cmd2 demonstrating how to remove one of the built-in runtime settable parameters.""" import cmd2 diff --git a/examples/scripts/conditional.py b/examples/scripts/conditional.py index ca993ca66..99c442de7 100644 --- a/examples/scripts/conditional.py +++ b/examples/scripts/conditional.py @@ -1,5 +1,4 @@ -""" -This is a Python script intended to be used with the "python_scripting.py" cmd2 example application. +"""This is a Python script intended to be used with the "python_scripting.py" cmd2 example application. To run it you should do the following: ./python_scripting.py diff --git a/examples/scripts/save_help_text.py b/examples/scripts/save_help_text.py index e037ced00..a647cd286 100644 --- a/examples/scripts/save_help_text.py +++ b/examples/scripts/save_help_text.py @@ -1,5 +1,4 @@ -""" -A cmd2 script that saves the help text for every command, subcommand, and topic to a file. +"""A cmd2 script that saves the help text for every command, subcommand, and topic to a file. This is meant to be run within a cmd2 session using run_pyscript. """ @@ -32,8 +31,7 @@ def get_sub_commands(parser: argparse.ArgumentParser) -> list[str]: def add_help_to_file(item: str, outfile: TextIO, is_command: bool) -> None: - """ - Write help text for commands and topics to the output file + """Write help text for commands and topics to the output file :param item: what is having its help text saved :param outfile: file being written to :param is_command: tells if the item is a command and not just a help topic @@ -52,7 +50,6 @@ def add_help_to_file(item: str, outfile: TextIO, is_command: bool) -> None: def main() -> None: """Main function of this script""" - # Make sure we have access to self if 'self' not in globals(): print("Re-run this script from a cmd2 application where self_in_py is True") diff --git a/examples/scripts/script.py b/examples/scripts/script.py index a14f7660d..cca0130c1 100644 --- a/examples/scripts/script.py +++ b/examples/scripts/script.py @@ -1,5 +1,3 @@ -""" -Trivial example of a Python script which can be run inside a cmd2 application. -""" +"""Trivial example of a Python script which can be run inside a cmd2 application.""" print("This is a python script running ...") diff --git a/examples/subcommands.py b/examples/subcommands.py index 4f6a936ed..6d6c2fefe 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """A simple example demonstrating how to use Argparse to support subcommands. - This example shows an easy way for a single command to have many subcommands, each of which takes different arguments and provides separate contextual help. """ @@ -62,8 +61,7 @@ class SubcommandsExample(cmd2.Cmd): - """ - Example cmd2 application where we a base command which has a couple subcommands + """Example cmd2 application where we a base command which has a couple subcommands and the "sport" subcommand has tab completion enabled. """ @@ -72,15 +70,15 @@ def __init__(self) -> None: # subcommand functions for the base command def base_foo(self, args) -> None: - """foo subcommand of base command""" + """Foo subcommand of base command""" self.poutput(args.x * args.y) def base_bar(self, args) -> None: - """bar subcommand of base command""" + """Bar subcommand of base command""" self.poutput(f'(({args.z}))') def base_sport(self, args) -> None: - """sport subcommand of base command""" + """Sport subcommand of base command""" self.poutput(f'Sport is {args.sport}') # Set handler functions for the subcommands diff --git a/examples/table_creation.py b/examples/table_creation.py index 6ebe830d2..02a65fb49 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -70,7 +70,6 @@ def ansi_print(text) -> None: def basic_tables() -> None: """Demonstrates basic examples of the table classes""" - # Table data which demonstrates handling of wrapping and text styles data_list: list[list[Any]] = [] data_list.append(["Billy Smith", "123 Sesame St.\nFake Town, USA 33445", DollarFormatter(100333.03)]) @@ -113,11 +112,9 @@ def basic_tables() -> None: def nested_tables() -> None: - """ - Demonstrates how to nest tables with styles which conflict with the parent table by setting style_data_text to False. + """Demonstrates how to nest tables with styles which conflict with the parent table by setting style_data_text to False. It also demonstrates coloring various aspects of tables. """ - # Create data for this example author_data: list[Author] = [] author_1 = Author("Frank Herbert", "10/08/1920", "Tacoma, Washington") diff --git a/pyproject.toml b/pyproject.toml index 5c47da41d..c344cbbe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -167,7 +167,7 @@ select = [ "C90", # McCabe cyclomatic complexity (warn about functions that are too complex) "COM", # flake8-commas (forces commas at the end of every type of iterable/container # "CPY", # flake8-copyright (warn about missing copyright notice at top of file - currently in preview) - # "D", # pydocstyle (warn about things like missing docstrings) + # "D", # pydocstyle (warn about things like missing docstrings) # "DOC", # pydoclint (docstring warnings - currently in preview) # "DJ", # flake8-django (Django-specific warnings) "DTZ", # flake8-datetimez (warn about datetime calls where no timezone is specified) diff --git a/tests/conftest.py b/tests/conftest.py index 87d9f1fa6..530f8655e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,4 @@ -""" -Cmd2 unit/functional testing -""" +"""Cmd2 unit/functional testing""" import argparse import sys @@ -159,8 +157,7 @@ def base_app(): def complete_tester(text: str, line: str, begidx: int, endidx: int, app) -> Optional[str]: - """ - This is a convenience function to test cmd2.complete() since + """This is a convenience function to test cmd2.complete() since in a unit test environment there is no actual console readline is monitoring. Therefore we use mock to provide readline data to complete(). diff --git a/tests/pyscript/raises_exception.py b/tests/pyscript/raises_exception.py index 0df499d9b..5595472bf 100644 --- a/tests/pyscript/raises_exception.py +++ b/tests/pyscript/raises_exception.py @@ -1,5 +1,3 @@ -""" -Example demonstrating what happens when a Python script raises an exception -""" +"""Example demonstrating what happens when a Python script raises an exception""" 1 + 'blue' diff --git a/tests/pyscript/recursive.py b/tests/pyscript/recursive.py index 3805d027b..f71234b8e 100644 --- a/tests/pyscript/recursive.py +++ b/tests/pyscript/recursive.py @@ -1,6 +1,4 @@ -""" -Example demonstrating that calling run_pyscript recursively inside another Python script isn't allowed -""" +"""Example demonstrating that calling run_pyscript recursively inside another Python script isn't allowed""" import os import sys diff --git a/tests/script.py b/tests/script.py index a14f7660d..cca0130c1 100644 --- a/tests/script.py +++ b/tests/script.py @@ -1,5 +1,3 @@ -""" -Trivial example of a Python script which can be run inside a cmd2 application. -""" +"""Trivial example of a Python script which can be run inside a cmd2 application.""" print("This is a python script running ...") diff --git a/tests/test_ansi.py b/tests/test_ansi.py index 73fb51933..841190724 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -1,6 +1,4 @@ -""" -Unit testing for cmd2/ansi.py module -""" +"""Unit testing for cmd2/ansi.py module""" import pytest diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 47f836f56..3b7416a26 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -1,6 +1,4 @@ -""" -Cmd2 testing for argument parsing -""" +"""Cmd2 testing for argument parsing""" import argparse from typing import Optional @@ -35,8 +33,7 @@ def _say_parser_builder() -> cmd2.Cmd2ArgumentParser: @cmd2.with_argparser(_say_parser_builder) def do_say(self, args, *, keyword_arg: Optional[str] = None) -> None: - """ - Repeat what you + """Repeat what you tell me to. :param args: argparse namespace @@ -261,15 +258,15 @@ class SubcommandApp(cmd2.Cmd): # subcommand functions for the base command def base_foo(self, args) -> None: - """foo subcommand of base command""" + """Foo subcommand of base command""" self.poutput(args.x * args.y) def base_bar(self, args) -> None: - """bar subcommand of base command""" + """Bar subcommand of base command""" self.poutput(f'(({args.z}))') def base_helpless(self, args) -> None: - """helpless subcommand of base command""" + """Helpless subcommand of base command""" self.poutput(f'(({args.z}))') # create the top-level parser for the base command @@ -400,8 +397,7 @@ def test_subcommand_invalid_help(subcommand_app) -> None: def test_add_another_subcommand(subcommand_app) -> None: - """ - This tests makes sure _set_parser_prog() sets _prog_prefix on every _SubParsersAction so that all future calls + """This tests makes sure _set_parser_prog() sets _prog_prefix on every _SubParsersAction so that all future calls to add_parser() write the correct prog value to the parser being added. """ base_parser = subcommand_app._command_parsers.get(subcommand_app.do_base) diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index b7666193f..5da930e23 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -1,6 +1,4 @@ -""" -Unit/functional testing for argparse completer in cmd2 -""" +"""Unit/functional testing for argparse completer in cmd2""" import argparse import numbers @@ -1202,7 +1200,6 @@ def test_complete_standalone(ac_app, flag, completions) -> None: class CustomCompleter(argparse_completer.ArgparseCompleter): def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matched_flags: list[str]) -> list[str]: """Override so flags with 'complete_when_ready' set to True will complete only when app is ready""" - # Find flags which should not be completed and place them in matched_flags for flag in self._flags: action = self._flag_to_action[flag] @@ -1320,7 +1317,6 @@ def test_custom_completer_type(custom_completer_app: CustomCompleterApp) -> None def test_decorated_subcmd_custom_completer(custom_completer_app: CustomCompleterApp) -> None: """Tests custom completer type on a subcommand created with @cmd2.as_subcommand_to""" - # First test the subcommand without the custom completer text = '--m' line = f'top no_custom {text}' diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index 6e9ee86e1..df050b105 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -1,6 +1,4 @@ -""" -Unit/functional testing for argparse customizations in cmd2 -""" +"""Unit/functional testing for argparse customizations in cmd2""" import argparse @@ -283,8 +281,7 @@ def test_cmd2_attribute_wrapper() -> None: def test_completion_items_as_choices(capsys) -> None: - """ - Test cmd2's patch to Argparse._check_value() which supports CompletionItems as choices. + """Test cmd2's patch to Argparse._check_value() which supports CompletionItems as choices. Choices are compared to CompletionItems.orig_value instead of the CompletionItem instance. """ from cmd2.argparse_custom import ( diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 5e35cd8d3..e8f0aec2b 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1,6 +1,4 @@ -""" -Cmd2 unit/functional testing -""" +"""Cmd2 unit/functional testing""" import builtins import io @@ -1198,8 +1196,7 @@ def do_undoc(self, arg) -> None: pass def do_multiline_docstr(self, arg) -> None: - """ - This documentation + """This documentation is multiple lines and there are no tabs @@ -2606,7 +2603,8 @@ def do_exit(self, arg_list) -> bool: Usage: exit [exit_code] Where: - * exit_code - integer exit code to return to the shell""" + * exit_code - integer exit code to return to the shell + """ # If an argument was provided if arg_list: try: diff --git a/tests/test_completion.py b/tests/test_completion.py index e59e91520..055c4418c 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -1,5 +1,4 @@ -""" -Unit/functional testing for readline tab completion functions in the cmd2.py module. +"""Unit/functional testing for readline tab completion functions in the cmd2.py module. These are primarily tests related to readline completer functions which handle tab completion of cmd2/cmd commands, file system paths, and shell commands. @@ -58,9 +57,7 @@ class CompletionsExample(cmd2.Cmd): - """ - Example cmd2 application used to exercise tab completion tests - """ + """Example cmd2 application used to exercise tab completion tests""" def __init__(self) -> None: cmd2.Cmd.__init__(self, multiline_commands=['test_multiline']) @@ -1211,8 +1208,7 @@ def test_subcommand_tab_completion_space_in_text(sc_app) -> None: class SubcommandsWithUnknownExample(cmd2.Cmd): - """ - Example cmd2 application where we a base command which has a couple subcommands + """Example cmd2 application where we a base command which has a couple subcommands and the "sport" subcommand has tab completion enabled. """ @@ -1221,15 +1217,15 @@ def __init__(self) -> None: # subcommand functions for the base command def base_foo(self, args) -> None: - """foo subcommand of base command""" + """Foo subcommand of base command""" self.poutput(args.x * args.y) def base_bar(self, args) -> None: - """bar subcommand of base command""" + """Bar subcommand of base command""" self.poutput(f'(({args.z}))') def base_sport(self, args) -> None: - """sport subcommand of base command""" + """Sport subcommand of base command""" self.poutput(f'Sport is {args.sport}') # create the top-level parser for the base command diff --git a/tests/test_history.py b/tests/test_history.py index 3db295af5..1ce70c928 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -1,6 +1,4 @@ -""" -Test history functions of cmd2 -""" +"""Test history functions of cmd2""" import os import tempfile diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 250811cbb..0bdf1a4e9 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1,6 +1,4 @@ -""" -Test the parsing logic in parsing.py -""" +"""Test the parsing logic in parsing.py""" import dataclasses @@ -504,7 +502,8 @@ def test_parse_output_to_paste_buffer(parser) -> None: def test_parse_redirect_inside_terminator(parser) -> None: """The terminator designates the end of the command/arguments portion. If a redirector occurs before a terminator, then it will be treated as - part of the arguments and not as a redirector.""" + part of the arguments and not as a redirector. + """ line = 'has > inside;' statement = parser.parse(line) assert statement.command == 'has' diff --git a/tests/test_plugin.py b/tests/test_plugin.py index b2043b9a0..56c2f2d56 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,6 +1,4 @@ -""" -Test plugin infrastructure and hooks. -""" +"""Test plugin infrastructure and hooks.""" import argparse import sys @@ -970,8 +968,7 @@ def test_skip_postcmd_hooks(capsys) -> None: def test_cmd2_argparse_exception(capsys) -> None: - """ - Verify Cmd2ArgparseErrors raised after calling a command prevent postcmd events from + """Verify Cmd2ArgparseErrors raised after calling a command prevent postcmd events from running but do not affect cmdfinalization events """ app = PluggedApp() diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index c619ee2bd..a64f77ba9 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -1,6 +1,4 @@ -""" -Unit/functional testing for run_pytest in cmd2 -""" +"""Unit/functional testing for run_pytest in cmd2""" import builtins import os @@ -69,8 +67,7 @@ def test_run_pyscript_with_non_python_file(base_app, request) -> None: @pytest.mark.parametrize('python_script', odd_file_names) def test_run_pyscript_with_odd_file_names(base_app, python_script) -> None: - """ - Pass in file names with various patterns. Since these files don't exist, we will rely + """Pass in file names with various patterns. Since these files don't exist, we will rely on the error text to make sure the file names were processed correctly. """ # Mock input to get us passed the warning about not ending in .py diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py index 2c54625cf..caf19b7eb 100644 --- a/tests/test_table_creator.py +++ b/tests/test_table_creator.py @@ -1,6 +1,4 @@ -""" -Unit testing for cmd2/table_creator.py module -""" +"""Unit testing for cmd2/table_creator.py module""" import pytest @@ -286,8 +284,7 @@ def test_wrap_long_word_max_data_lines() -> None: def test_wrap_long_char_wider_than_max_width() -> None: - """ - This tests case where a character is wider than max_width. This can happen if max_width + """This tests case where a character is wider than max_width. This can happen if max_width is 1 and the text contains wide characters (e.g. East Asian). Replace it with an ellipsis. """ column_1 = Column("Col 1", width=1) diff --git a/tests/test_transcript.py b/tests/test_transcript.py index f14433b2f..d49d85909 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -1,6 +1,4 @@ -""" -Cmd2 functional testing based on transcript -""" +"""Cmd2 functional testing based on transcript""" import os import random diff --git a/tests/test_utils.py b/tests/test_utils.py index 402deb226..ca8ca4edd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,4 @@ -""" -Unit testing for cmd2/utils.py module. -""" +"""Unit testing for cmd2/utils.py module.""" import os import signal diff --git a/tests/test_utils_defining_class.py b/tests/test_utils_defining_class.py index fa24d202f..f0c278957 100644 --- a/tests/test_utils_defining_class.py +++ b/tests/test_utils_defining_class.py @@ -1,6 +1,4 @@ -""" -Unit testing for get_defining_class in cmd2/utils.py module. -""" +"""Unit testing for get_defining_class in cmd2/utils.py module.""" import functools diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 32db784b2..e0b86d6a6 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -1,6 +1,4 @@ -""" -Cmd2 unit/functional testing -""" +"""Cmd2 unit/functional testing""" import sys from contextlib import ( @@ -142,8 +140,7 @@ def base_app(): def complete_tester(text: str, line: str, begidx: int, endidx: int, app) -> Optional[str]: - """ - This is a convenience function to test cmd2.complete() since + """This is a convenience function to test cmd2.complete() since in a unit test environment there is no actual console readline is monitoring. Therefore we use mock to provide readline data to complete(). diff --git a/tests_isolated/test_commandset/test_argparse_subcommands.py b/tests_isolated/test_commandset/test_argparse_subcommands.py index cce5d93ad..6a10c2d1f 100644 --- a/tests_isolated/test_commandset/test_argparse_subcommands.py +++ b/tests_isolated/test_commandset/test_argparse_subcommands.py @@ -1,6 +1,4 @@ -""" -reproduces test_argparse.py except with SubCommands -""" +"""reproduces test_argparse.py except with SubCommands""" import pytest @@ -20,15 +18,15 @@ def __init__(self, dummy) -> None: # subcommand functions for the base command def base_foo(self, args) -> None: - """foo subcommand of base command""" + """Foo subcommand of base command""" self._cmd.poutput(args.x * args.y) def base_bar(self, args) -> None: - """bar subcommand of base command""" + """Bar subcommand of base command""" self._cmd.poutput(f'(({args.z}))') def base_helpless(self, args) -> None: - """helpless subcommand of base command""" + """Helpless subcommand of base command""" self._cmd.poutput(f'(({args.z}))') # create the top-level parser for the base command diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index 0e16f109f..8150c5e7d 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -1,6 +1,4 @@ -""" -Simple example demonstrating basic CommandSet usage. -""" +"""Simple example demonstrating basic CommandSet usage.""" from typing import Any @@ -20,9 +18,7 @@ def __init__(self, _: Any) -> None: class ChildInheritsParentCategories(MyBaseCommandSet): - """ - This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category' - """ + """This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category'""" def do_hello(self, _: cmd2.Statement) -> None: self._cmd.poutput('Hello') @@ -33,8 +29,7 @@ def do_world(self, _: cmd2.Statement) -> None: @with_default_category('Non-Heritable Category', heritable=False) class ChildOverridesParentCategoriesNonHeritable(MyBaseCommandSet): - """ - This subclass overrides the 'Default Category' from the parent, but in a non-heritable fashion. Sub-classes of this + """This subclass overrides the 'Default Category' from the parent, but in a non-heritable fashion. Sub-classes of this CommandSet will not inherit this category and will, instead, inherit 'Default Category' """ @@ -43,8 +38,7 @@ def do_goodbye(self, _: cmd2.Statement) -> None: class GrandchildInheritsGrandparentCategory(ChildOverridesParentCategoriesNonHeritable): - """ - This subclass's parent class declared its default category non-heritable. Instead, it inherits the category defined + """This subclass's parent class declared its default category non-heritable. Instead, it inherits the category defined by the grandparent class. """ @@ -54,8 +48,7 @@ def do_aloha(self, _: cmd2.Statement) -> None: @with_default_category('Heritable Category') class ChildOverridesParentCategories(MyBaseCommandSet): - """ - This subclass is decorated with a default category that is heritable. This overrides the parent class's default + """This subclass is decorated with a default category that is heritable. This overrides the parent class's default category declaration. """ @@ -64,8 +57,7 @@ def do_bonjour(self, _: cmd2.Statement) -> None: class GrandchildInheritsHeritable(ChildOverridesParentCategories): - """ - This subclass's parent declares a default category that overrides its parent. As a result, commands in this + """This subclass's parent declares a default category that overrides its parent. As a result, commands in this CommandSet will be categorized under 'Heritable Category' """ @@ -74,9 +66,7 @@ def do_monde(self, _: cmd2.Statement) -> None: class ExampleApp(cmd2.Cmd): - """ - Example to demonstrate heritable default categories - """ + """Example to demonstrate heritable default categories""" def __init__(self) -> None: super().__init__(auto_load_commands=False) diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index d869e370d..3243dc4b2 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -1,6 +1,4 @@ -""" -Test CommandSet -""" +"""Test CommandSet""" import argparse import signal @@ -789,7 +787,7 @@ class SupportFuncProvider(cmd2.CommandSet): states = ['alabama', 'alaska', 'arizona', 'arkansas', 'california', 'colorado', 'connecticut', 'delaware'] def __init__(self, dummy) -> None: - """dummy variable prevents this from being autoloaded in other tests""" + """Dummy variable prevents this from being autoloaded in other tests""" super().__init__() def complete_states(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: @@ -823,7 +821,7 @@ class SupportFuncUserUnrelated(cmd2.CommandSet): """A CommandSet that isn't related to SupportFuncProvider which uses its support function""" def __init__(self, dummy) -> None: - """dummy variable prevents this from being autoloaded in other tests""" + """Dummy variable prevents this from being autoloaded in other tests""" super().__init__() parser = cmd2.Cmd2ArgumentParser() @@ -968,7 +966,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys) -> None: class CommandSetWithPathComplete(cmd2.CommandSet): def __init__(self, dummy) -> None: - """dummy variable prevents this from being autoloaded in other tests""" + """Dummy variable prevents this from being autoloaded in other tests""" super().__init__() parser = cmd2.Cmd2ArgumentParser() From a86a0474d3454b25f61ae509d9f1cb37c8501044 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 13:02:54 -0400 Subject: [PATCH 61/79] Automatically remove whitespace at top of files --- plugins/template/cmd2_myplugin/__init__.py | 1 - plugins/template/cmd2_myplugin/myplugin.py | 1 - plugins/template/examples/example.py | 2 -- plugins/template/setup.py | 2 -- plugins/template/tasks.py | 1 - plugins/template/tests/test_myplugin.py | 2 -- tests/__init__.py | 2 -- tests_isolated/test_commandset/__init__.py | 2 -- 8 files changed, 13 deletions(-) diff --git a/plugins/template/cmd2_myplugin/__init__.py b/plugins/template/cmd2_myplugin/__init__.py index e157ee7a4..d61792467 100644 --- a/plugins/template/cmd2_myplugin/__init__.py +++ b/plugins/template/cmd2_myplugin/__init__.py @@ -1,4 +1,3 @@ -# """Description of myplugin An overview of what myplugin does. diff --git a/plugins/template/cmd2_myplugin/myplugin.py b/plugins/template/cmd2_myplugin/myplugin.py index 818730bba..1d34a848b 100644 --- a/plugins/template/cmd2_myplugin/myplugin.py +++ b/plugins/template/cmd2_myplugin/myplugin.py @@ -1,4 +1,3 @@ -# """An example cmd2 plugin""" import functools diff --git a/plugins/template/examples/example.py b/plugins/template/examples/example.py index 47ba3a15b..50500872b 100644 --- a/plugins/template/examples/example.py +++ b/plugins/template/examples/example.py @@ -1,5 +1,3 @@ -# - import cmd2_myplugin import cmd2 diff --git a/plugins/template/setup.py b/plugins/template/setup.py index 59b064d5a..34ec6f57a 100644 --- a/plugins/template/setup.py +++ b/plugins/template/setup.py @@ -1,5 +1,3 @@ -# - import os import setuptools diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index 8ae41c8d3..eb012eaec 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -1,4 +1,3 @@ -# """Development related tasks to be run with 'invoke'""" import os diff --git a/plugins/template/tests/test_myplugin.py b/plugins/template/tests/test_myplugin.py index 2c42ef0a1..21bccc9fc 100644 --- a/plugins/template/tests/test_myplugin.py +++ b/plugins/template/tests/test_myplugin.py @@ -1,5 +1,3 @@ -# - import cmd2_myplugin from cmd2 import ( diff --git a/tests/__init__.py b/tests/__init__.py index 6e9bbe36f..e69de29bb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,2 +0,0 @@ -# -# diff --git a/tests_isolated/test_commandset/__init__.py b/tests_isolated/test_commandset/__init__.py index 6e9bbe36f..e69de29bb 100644 --- a/tests_isolated/test_commandset/__init__.py +++ b/tests_isolated/test_commandset/__init__.py @@ -1,2 +0,0 @@ -# -# From c82aecde351d0143c825bab1c52d291e3c8f0abd Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 13:09:47 -0400 Subject: [PATCH 62/79] Refactor a conditional for readability --- cmd2/argparse_completer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 961d609cb..68b779f70 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -123,7 +123,7 @@ def __init__(self, arg_action: argparse.Action) -> None: elif self.action.nargs == argparse.OPTIONAL: self.min = 0 self.max = 1 - elif self.action.nargs == argparse.ZERO_OR_MORE or self.action.nargs == argparse.REMAINDER: + elif self.action.nargs in (argparse.ZERO_OR_MORE, argparse.REMAINDER): self.min = 0 self.max = INFINITY elif self.action.nargs == argparse.ONE_OR_MORE: From 7b6247f5d6c284b5b4aa12f65bc948413fff065b Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 13:13:40 -0400 Subject: [PATCH 63/79] Move a comment back to where it belongs --- cmd2/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd2/__init__.py b/cmd2/__init__.py index d3a0c7211..e9c9a5c6d 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -1,5 +1,6 @@ """This simply imports certain things for backwards compatibility.""" +import argparse import importlib.metadata as importlib_metadata import sys @@ -9,10 +10,6 @@ # package is not installed pass -# Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER. -# Do this before loading cmd2.Cmd class so its commands use the custom parser. -import argparse - from .ansi import ( Bg, Cursor, @@ -32,6 +29,8 @@ set_default_argument_parser_type, ) +# Check if user has defined a module that sets a custom value for argparse_custom.DEFAULT_ARGUMENT_PARSER. +# Do this before loading cmd2.Cmd class so its commands use the custom parser. cmd2_parser_module = getattr(argparse, 'cmd2_parser_module', None) if cmd2_parser_module is not None: import importlib From 2dd2909c230db7297f9b3d13809a081a8d92fcb9 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 13:21:39 -0400 Subject: [PATCH 64/79] Removed some else cases that were not needed due to a return in the if case --- cmd2/argparse_completer.py | 47 +++++++++++++++++--------------------- tests/test_completion.py | 3 +-- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 68b779f70..6de7a6d85 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -60,13 +60,13 @@ def _build_hint(parser: argparse.ArgumentParser, arg_action: argparse.Action) -> suppress_hint = arg_action.get_suppress_tab_hint() # type: ignore[attr-defined] if suppress_hint or arg_action.help == argparse.SUPPRESS: return '' - else: - # Use the parser's help formatter to display just this action's help text - formatter = parser._get_formatter() - formatter.start_section("Hint") - formatter.add_argument(arg_action) - formatter.end_section() - return formatter.format_help() + + # Use the parser's help formatter to display just this action's help text + formatter = parser._get_formatter() + formatter.start_section("Hint") + formatter.add_argument(arg_action) + formatter.end_section() + return formatter.format_help() def _single_prefix_char(token: str, parser: argparse.ArgumentParser) -> bool: @@ -274,7 +274,7 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: for group_action in group._group_actions: if group_action == arg_action: continue - elif group_action in self._flag_to_action.values(): + if group_action in self._flag_to_action.values(): matched_flags.extend(group_action.option_strings) elif group_action in remaining_positionals: remaining_positionals.remove(group_action) @@ -292,7 +292,7 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: continue # If we're in a flag REMAINDER arg, force all future tokens to go to that until a double dash is hit - elif flag_arg_state is not None and flag_arg_state.is_remainder: + if flag_arg_state is not None and flag_arg_state.is_remainder: if token == '--': flag_arg_state = None else: @@ -300,7 +300,7 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: continue # Handle '--' which tells argparse all remaining arguments are non-flags - elif token == '--' and not skip_remaining_flags: + if token == '--' and not skip_remaining_flags: # Check if there is an unfinished flag if ( flag_arg_state is not None @@ -310,10 +310,9 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: raise _UnfinishedFlagError(flag_arg_state) # Otherwise end the current flag - else: - flag_arg_state = None - skip_remaining_flags = True - continue + flag_arg_state = None + skip_remaining_flags = True + continue # Check the format of the current token to see if it can be an argument's value if _looks_like_flag(token, self._parser) and not skip_remaining_flags: @@ -393,13 +392,11 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: return completer.complete( text, line, begidx, endidx, tokens[token_index + 1 :], cmd_set=cmd_set ) - else: - # Invalid subcommand entered, so no way to complete remaining tokens - return [] + # Invalid subcommand entered, so no way to complete remaining tokens + return [] # Otherwise keep track of the argument - else: - pos_arg_state = _ArgumentState(action) + pos_arg_state = _ArgumentState(action) # Check if we have a positional to consume this token if pos_arg_state is not None: @@ -452,7 +449,7 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: return completion_results # Otherwise, print a hint if the flag isn't finished or text isn't possibly the start of a flag - elif ( + if ( (isinstance(flag_arg_state.min, int) and flag_arg_state.count < flag_arg_state.min) or not _single_prefix_char(text, self._parser) or skip_remaining_flags @@ -478,7 +475,7 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: return completion_results # Otherwise, print a hint if text isn't possibly the start of a flag - elif not _single_prefix_char(text, self._parser) or skip_remaining_flags: + if not _single_prefix_char(text, self._parser) or skip_remaining_flags: raise _NoResultsError(self._parser, pos_arg_state.action) # If we aren't skipping remaining flags, then complete flag names if either is True: @@ -620,11 +617,10 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in completer = completer_type(parser, self._cmd2_app) return completer.complete_subcommand_help(text, line, begidx, endidx, tokens[token_index + 1 :]) - elif token_index == len(tokens) - 1: + if token_index == len(tokens) - 1: # Since this is the last token, we will attempt to complete it return self._cmd2_app.basic_complete(text, line, begidx, endidx, self._subcommand_action.choices) - else: - break + break return [] def format_help(self, tokens: list[str]) -> str: @@ -642,8 +638,7 @@ def format_help(self, tokens: list[str]) -> str: completer = completer_type(parser, self._cmd2_app) return completer.format_help(tokens[token_index + 1 :]) - else: - break + break return self._parser.format_help() def _complete_arg( diff --git a/tests/test_completion.py b/tests/test_completion.py index 055c4418c..bb77f51bb 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -110,8 +110,7 @@ def complete_foo_val(self, text, line, begidx, endidx, arg_tokens): """Supports unit testing cmd2.Cmd2.complete_set_val to confirm it passes all tokens in the set command""" if 'param' in arg_tokens: return ["SUCCESS"] - else: - return ["FAIL"] + return ["FAIL"] def completedefault(self, *ignored): """Method called to complete an input line when no command-specific From 29e64d649019b52faf4d93a93304dc26db8c0984 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 13:34:43 -0400 Subject: [PATCH 65/79] Enable ruff RET ruleset --- cmd2/clipboard.py | 3 +- cmd2/parsing.py | 12 +++----- cmd2/py_bridge.py | 3 +- plugins/template/tests/test_myplugin.py | 3 +- pyproject.toml | 2 +- tests/test_argparse.py | 6 ++-- tests/test_argparse_completer.py | 3 +- tests/test_cmd2.py | 28 ++++++------------- tests/test_completion.py | 6 ++-- tests/test_history.py | 9 ++---- tests/test_parsing.py | 6 ++-- tests/test_utils.py | 6 ++-- tests_isolated/test_commandset/conftest.py | 6 ++-- .../test_argparse_subcommands.py | 3 +- .../test_commandset/test_commandset.py | 3 +- 15 files changed, 33 insertions(+), 66 deletions(-) diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index 7d77d492f..9b6cfc775 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -10,8 +10,7 @@ def get_paste_buffer() -> str: :return: contents of the clipboard """ - pb_str = typing.cast(str, pyperclip.paste()) - return pb_str + return typing.cast(str, pyperclip.paste()) def write_to_paste_buffer(txt: str) -> None: diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 6cd619271..1eaa5a082 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -154,8 +154,7 @@ def __new__(cls, value: object, *pos_args: Any, **kw_args: Any) -> 'Statement': NOTE: @dataclass takes care of initializing other members in the __init__ it generates. """ - stmt = super().__new__(cls, value) - return stmt + return super().__new__(cls, value) @property def command_and_args(self) -> str: @@ -382,8 +381,7 @@ def tokenize(self, line: str) -> list[str]: raise Cmd2ShlexError(ex) # custom lexing - tokens = self.split_on_punctuation(tokens) - return tokens + return self.split_on_punctuation(tokens) def parse(self, line: str) -> Statement: """Tokenize the input and parse it into a [cmd2.parsing.Statement][] object, @@ -515,7 +513,7 @@ def parse(self, line: str) -> Statement: multiline_command = '' # build the statement - statement = Statement( + return Statement( args, raw=line, command=command, @@ -527,7 +525,6 @@ def parse(self, line: str) -> Statement: output=output, output_to=output_to, ) - return statement def parse_command_only(self, rawinput: str) -> Statement: """Partially parse input into a [cmd2.Statement][] object. @@ -589,8 +586,7 @@ def parse_command_only(self, rawinput: str) -> Statement: multiline_command = '' # build the statement - statement = Statement(args, raw=rawinput, command=command, multiline_command=multiline_command) - return statement + return Statement(args, raw=rawinput, command=command, multiline_command=multiline_command) def get_command_arg_list( self, command_name: str, to_parse: Union[Statement, str], preserve_quotes: bool diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 0a28460a1..e4ca0cf49 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -137,10 +137,9 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul self.stop = stop or self.stop # Save the result - result = CommandResult( + return CommandResult( stdout=copy_cmd_stdout.getvalue(), stderr=copy_stderr.getvalue(), stop=stop, data=self._cmd2_app.last_result, ) - return result diff --git a/plugins/template/tests/test_myplugin.py b/plugins/template/tests/test_myplugin.py index 21bccc9fc..7386a8b17 100644 --- a/plugins/template/tests/test_myplugin.py +++ b/plugins/template/tests/test_myplugin.py @@ -35,8 +35,7 @@ def do_empty(self, args) -> None: def init_app(): - app = MyApp() - return app + return MyApp() ##### diff --git a/pyproject.toml b/pyproject.toml index c344cbbe9..c7d2b3b35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,7 +203,7 @@ select = [ # "PTH", # flake8-use-pathlib (force use of pathlib instead of os.path) "PYI", # flake8-pyi (warnings related to type hint best practices) "Q", # flake8-quotes (force double quotes) - # "RET", # flake8-return (various warnings related to implicit vs explicit return statements) + "RET", # flake8-return (various warnings related to implicit vs explicit return statements) "RSE", # flake8-raise (warn about unnecessary parentheses on raised exceptions) # "RUF", # Ruff-specific rules (miscellaneous grab bag of lint checks specific to Ruff) # "S", # flake8-bandit (security oriented checks, but extremely pedantic - do not attempt to apply to unit test files) diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 3b7416a26..209777c61 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -122,8 +122,7 @@ def do_test_argparse_with_list_ns(self, args, extra) -> None: @pytest.fixture def argparse_app(): - app = ArgparseApp() - return app + return ArgparseApp() def test_invalid_syntax(argparse_app) -> None: @@ -324,8 +323,7 @@ def helpless_subcmd_func(self, args: argparse.Namespace) -> None: @pytest.fixture def subcommand_app(): - app = SubcommandApp() - return app + return SubcommandApp() def test_subcommand_foo(subcommand_app) -> None: diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 5da930e23..f4c173abe 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -1268,8 +1268,7 @@ def _subcmd_custom(self, args: argparse.Namespace) -> None: @pytest.fixture def custom_completer_app(): - app = CustomCompleterApp() - return app + return CustomCompleterApp() def test_default_custom_completer_type(custom_completer_app: CustomCompleterApp) -> None: diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index e8f0aec2b..b8e27295a 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -280,8 +280,7 @@ def _onchange_quiet(self, name, old, new) -> None: @pytest.fixture def onchange_app(): - app = OnChangeHookApp() - return app + return OnChangeHookApp() def test_set_onchange_hook(onchange_app) -> None: @@ -847,15 +846,13 @@ def _expected_no_editor_error(): if hasattr(sys, "pypy_translation_info"): expected_exception = 'EnvironmentError' - expected_text = normalize( + return normalize( f""" EXCEPTION of type '{expected_exception}' occurred with message: Please use 'set editor' to specify your text editing program of choice. To enable full traceback, run the following command: 'set debug true' """ ) - return expected_text - def test_base_debug(base_app) -> None: # Purposely set the editor to None @@ -1086,8 +1083,7 @@ def postparsing_precmd(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.P @pytest.fixture def hook_failure(): - app = HookFailureApp() - return app + return HookFailureApp() def test_precmd_hook_success(base_app) -> None: @@ -1211,8 +1207,7 @@ def do_parser_cmd(self, args) -> None: @pytest.fixture def help_app(): - app = HelpApp() - return app + return HelpApp() def test_custom_command_help(help_app) -> None: @@ -1285,8 +1280,7 @@ def do_undoc(self, arg) -> None: @pytest.fixture def helpcat_app(): - app = HelpCategoriesApp() - return app + return HelpCategoriesApp() def test_help_cat_base(helpcat_app) -> None: @@ -1342,8 +1336,7 @@ def do_return_type(self, arg) -> None: @pytest.fixture def select_app(): - app = SelectApp() - return app + return SelectApp() def test_select_options(select_app, monkeypatch) -> None: @@ -1592,8 +1585,7 @@ def do_orate(self, opts, arg) -> None: @pytest.fixture def multiline_app(): - app = MultilineApp() - return app + return MultilineApp() def test_multiline_complete_empty_statement_raises_exception(multiline_app) -> None: @@ -1759,8 +1751,7 @@ def do_negative_no_data(self, arg) -> None: @pytest.fixture def commandresult_app(): - app = CommandResultApp() - return app + return CommandResultApp() def test_commandresult_truthy(commandresult_app) -> None: @@ -2813,8 +2804,7 @@ def do_has_no_helper_funcs(self, arg) -> None: @pytest.fixture def disable_commands_app(): - app = DisableCommandsApp() - return app + return DisableCommandsApp() def test_disable_and_enable_category(disable_commands_app) -> None: diff --git a/tests/test_completion.py b/tests/test_completion.py index bb77f51bb..2deb6a80a 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -122,8 +122,7 @@ def completedefault(self, *ignored): @pytest.fixture def cmd2_app(): - c = CompletionsExample() - return c + return CompletionsExample() def test_cmd2_command_completion_single(cmd2_app) -> None: @@ -1261,8 +1260,7 @@ def do_base(self, args) -> None: @pytest.fixture def scu_app(): """Declare test fixture for with_argparser decorator""" - app = SubcommandsWithUnknownExample() - return app + return SubcommandsWithUnknownExample() def test_subcmd_with_unknown_completion_single_end(scu_app) -> None: diff --git a/tests/test_history.py b/tests/test_history.py index 1ce70c928..99db7a02f 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -56,7 +56,7 @@ def hist(): Statement, ) - h = History( + return History( [ HistoryItem(Statement('', raw='first')), HistoryItem(Statement('', raw='second')), @@ -64,7 +64,6 @@ def hist(): HistoryItem(Statement('', raw='fourth')), ] ) - return h # Represents the hist fixture's JSON @@ -381,8 +380,7 @@ def histitem(): command='help', arg_list=['history'], ) - histitem = HistoryItem(statement) - return histitem + return HistoryItem(statement) @pytest.fixture @@ -391,7 +389,7 @@ def parser(): StatementParser, ) - parser = StatementParser( + return StatementParser( terminators=[';', '&'], multiline_commands=['multiline'], aliases={ @@ -403,7 +401,6 @@ def parser(): }, shortcuts={'?': 'help', '!': 'shell'}, ) - return parser def test_multiline_histitem(parser) -> None: diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 0bdf1a4e9..711868cad 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -19,7 +19,7 @@ @pytest.fixture def parser(): - parser = StatementParser( + return StatementParser( terminators=[';', '&'], multiline_commands=['multiline'], aliases={ @@ -31,13 +31,11 @@ def parser(): }, shortcuts={'?': 'help', '!': 'shell'}, ) - return parser @pytest.fixture def default_parser(): - parser = StatementParser() - return parser + return StatementParser() def test_parse_empty_string(parser) -> None: diff --git a/tests/test_utils.py b/tests/test_utils.py index ca8ca4edd..bb05093ea 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -142,8 +142,7 @@ def test_quote_string_if_needed_no() -> None: @pytest.fixture def stdout_sim(): - stdsim = cu.StdSim(sys.stdout, echo=True) - return stdsim + return cu.StdSim(sys.stdout, echo=True) def test_stdsim_write_str(stdout_sim) -> None: @@ -279,8 +278,7 @@ def pr_none(): kwargs['start_new_session'] = True proc = subprocess.Popen(command, shell=True, **kwargs) - pr = cu.ProcReader(proc, None, None) - return pr + return cu.ProcReader(proc, None, None) def test_proc_reader_send_sigint(pr_none) -> None: diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index e0b86d6a6..065065ea8 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -180,11 +180,9 @@ def __init__(self, *args, **kwargs) -> None: @pytest.fixture def command_sets_app(): - app = WithCommandSets() - return app + return WithCommandSets() @pytest.fixture def command_sets_manual(): - app = WithCommandSets(auto_load_commands=False) - return app + return WithCommandSets(auto_load_commands=False) diff --git a/tests_isolated/test_commandset/test_argparse_subcommands.py b/tests_isolated/test_commandset/test_argparse_subcommands.py index 6a10c2d1f..74655de4a 100644 --- a/tests_isolated/test_commandset/test_argparse_subcommands.py +++ b/tests_isolated/test_commandset/test_argparse_subcommands.py @@ -62,8 +62,7 @@ def do_base(self, args) -> None: @pytest.fixture def subcommand_app(): - app = WithCommandSets(auto_load_commands=False, command_sets=[SubcommandSet(1)]) - return app + return WithCommandSets(auto_load_commands=False, command_sets=[SubcommandSet(1)]) def test_subcommand_foo(subcommand_app) -> None: diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 3243dc4b2..c50b78fe5 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -748,8 +748,7 @@ def cut_bokchoy(self, _: argparse.Namespace) -> None: @pytest.fixture def static_subcommands_app(): - app = AppWithSubCommands() - return app + return AppWithSubCommands() def test_static_subcommands(static_subcommands_app) -> None: From abb8579f4f6d7c27554fc5fd4f0c7a511bf56845 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 13:40:32 -0400 Subject: [PATCH 66/79] Applied some list performance optimizations from ruff RUF ruleset --- cmd2/argparse_custom.py | 8 ++++---- cmd2/cmd2.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index cbe6cda96..5ad52a855 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1078,14 +1078,14 @@ def get_lines(parts: list[str], indent: str, prefix: Optional[str] = None) -> li indent = ' ' * (len(prefix) + len(prog) + 1) # Begin cmd2 customization if req_parts: - lines = get_lines([prog] + req_parts, indent, prefix) + lines = get_lines([prog, *req_parts], indent, prefix) lines.extend(get_lines(opt_parts, indent)) lines.extend(get_lines(pos_parts, indent)) elif opt_parts: - lines = get_lines([prog] + opt_parts, indent, prefix) + lines = get_lines([prog, *opt_parts], indent, prefix) lines.extend(get_lines(pos_parts, indent)) elif pos_parts: - lines = get_lines([prog] + pos_parts, indent, prefix) + lines = get_lines([prog, *pos_parts], indent, prefix) else: lines = [prog] # End cmd2 customization @@ -1102,7 +1102,7 @@ def get_lines(parts: list[str], indent: str, prefix: Optional[str] = None) -> li lines.extend(get_lines(opt_parts, indent)) lines.extend(get_lines(pos_parts, indent)) # End cmd2 customization - lines = [prog] + lines + lines = [prog, *lines] # join lines into usage usage = '\n'.join(lines) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 057ecd3b0..51433236e 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -4224,7 +4224,7 @@ def do_shell(self, args: argparse.Namespace) -> None: kwargs['executable'] = shell # Create a list of arguments to shell - tokens = [args.command] + args.command_args + tokens = [args.command, *args.command_args] # Expand ~ where needed utils.expand_user_in_tokens(tokens) @@ -4530,7 +4530,7 @@ def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]: try: # Overwrite sys.argv to allow the script to take command line arguments - sys.argv = [args.script_path] + args.script_arguments + sys.argv = [args.script_path, *args.script_arguments] # self.last_resort will be set by _run_python() py_return = self._run_python(pyscript=args.script_path) @@ -5562,7 +5562,7 @@ def _validate_postparsing_callable(cls, func: Callable[[plugin.PostparsingData], """Check parameter and return types for postparsing hooks""" cls._validate_callable_param_count(cast(Callable[..., Any], func), 1) signature = inspect.signature(func) - _, param = list(signature.parameters.items())[0] + _, param = next(iter(signature.parameters.items())) if param.annotation != plugin.PostparsingData: raise TypeError(f"{func.__name__} must have one parameter declared with type 'cmd2.plugin.PostparsingData'") if signature.return_annotation != plugin.PostparsingData: @@ -5584,7 +5584,7 @@ def _validate_prepostcmd_hook( # validate that the callable has the right number of parameters cls._validate_callable_param_count(cast(Callable[..., Any], func), 1) # validate the parameter has the right annotation - paramname = list(signature.parameters.keys())[0] + paramname = next(iter(signature.parameters.keys())) param = signature.parameters[paramname] if param.annotation != data_type: raise TypeError(f'argument 1 of {func.__name__} has incompatible type {param.annotation}, expected {data_type}') @@ -5613,7 +5613,7 @@ def _validate_cmdfinalization_callable( """Check parameter and return types for command finalization hooks.""" cls._validate_callable_param_count(func, 1) signature = inspect.signature(func) - _, param = list(signature.parameters.items())[0] + _, param = next(iter(signature.parameters.items())) if param.annotation != plugin.CommandFinalizationData: raise TypeError(f"{func.__name__} must have one parameter declared with type {plugin.CommandFinalizationData}") if signature.return_annotation != plugin.CommandFinalizationData: From b5a8eeba0926c344c9dd72ec9b155dec49556a3c Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 13:54:41 -0400 Subject: [PATCH 67/79] Applied automated pydocstyle refactoring outside of test directories --- cmd2/ansi.py | 50 ++--- cmd2/argparse_completer.py | 30 +-- cmd2/argparse_custom.py | 34 ++-- cmd2/cmd2.py | 186 +++++++++--------- cmd2/command_definition.py | 6 +- cmd2/decorators.py | 10 +- cmd2/exceptions.py | 8 +- cmd2/history.py | 30 +-- cmd2/parsing.py | 16 +- cmd2/plugin.py | 10 +- cmd2/py_bridge.py | 8 +- cmd2/rl_utils.py | 22 +-- cmd2/table_creator.py | 68 +++---- cmd2/utils.py | 74 +++---- examples/alias_startup.py | 2 +- examples/arg_decorators.py | 6 +- examples/arg_print.py | 2 +- examples/argparse_completion.py | 4 +- examples/async_printing.py | 22 +-- examples/basic.py | 6 +- examples/basic_completion.py | 14 +- examples/cmd_as_argument.py | 2 +- examples/colors.py | 2 +- examples/custom_parser.py | 6 +- examples/decorator_example.py | 4 +- examples/default_categories.py | 10 +- examples/environment.py | 2 +- examples/help_categories.py | 44 ++--- examples/hooks.py | 4 +- examples/initialization.py | 6 +- examples/migrating.py | 2 +- examples/modular_commands/commandset_basic.py | 14 +- .../modular_commands/commandset_complex.py | 6 +- .../modular_commands/commandset_custominit.py | 2 +- examples/modular_commands_dynamic.py | 2 +- examples/modular_commands_main.py | 4 +- examples/modular_subcommands.py | 4 +- examples/pirate.py | 2 +- examples/python_scripting.py | 2 +- examples/read_input.py | 18 +- examples/scripts/save_help_text.py | 6 +- examples/subcommands.py | 10 +- examples/table_creation.py | 16 +- plugins/tasks.py | 20 +- plugins/template/cmd2_myplugin/__init__.py | 2 +- plugins/template/cmd2_myplugin/myplugin.py | 14 +- plugins/template/examples/example.py | 2 +- plugins/template/tasks.py | 26 +-- pyproject.toml | 9 + tasks.py | 44 ++--- 50 files changed, 451 insertions(+), 442 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 4abcd87be..c29176e01 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -28,18 +28,18 @@ class AllowStyle(Enum): - """Values for ``cmd2.ansi.allow_style``""" + """Values for ``cmd2.ansi.allow_style``.""" ALWAYS = 'Always' # Always output ANSI style sequences NEVER = 'Never' # Remove ANSI style sequences from all output TERMINAL = 'Terminal' # Remove ANSI style sequences if the output is not going to the terminal def __str__(self) -> str: - """Return value instead of enum name for printing in cmd2's set command""" + """Return value instead of enum name for printing in cmd2's set command.""" return str(self.value) def __repr__(self) -> str: - """Return quoted value instead of enum description for printing in cmd2's set command""" + """Return quoted value instead of enum description for printing in cmd2's set command.""" return repr(self.value) @@ -124,7 +124,7 @@ def widest_line(text: str) -> int: def style_aware_write(fileobj: IO[str], msg: str) -> None: - """Write a string to a fileobject and strip its ANSI style sequences if required by allow_style setting + """Write a string to a fileobject and strip its ANSI style sequences if required by allow_style setting. :param fileobj: the file object being written to :param msg: the string being written @@ -183,63 +183,63 @@ def clear_line(clear_type: int = 2) -> str: # Base classes which are not intended to be used directly #################################################################################### class AnsiSequence: - """Base class to create ANSI sequence strings""" + """Base class to create ANSI sequence strings.""" def __add__(self, other: Any) -> str: """Support building an ANSI sequence string when self is the left operand - e.g. Fg.LIGHT_MAGENTA + "hello" + e.g. Fg.LIGHT_MAGENTA + "hello". """ return str(self) + str(other) def __radd__(self, other: Any) -> str: """Support building an ANSI sequence string when self is the right operand - e.g. "hello" + Fg.RESET + e.g. "hello" + Fg.RESET. """ return str(other) + str(self) class FgColor(AnsiSequence): - """Base class for ANSI Sequences which set foreground text color""" + """Base class for ANSI Sequences which set foreground text color.""" class BgColor(AnsiSequence): - """Base class for ANSI Sequences which set background text color""" + """Base class for ANSI Sequences which set background text color.""" #################################################################################### # Implementations intended for direct use #################################################################################### class Cursor: - """Create ANSI sequences to alter the cursor position""" + """Create ANSI sequences to alter the cursor position.""" @staticmethod def UP(count: int = 1) -> str: - """Move the cursor up a specified amount of lines (Defaults to 1)""" + """Move the cursor up a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}A" @staticmethod def DOWN(count: int = 1) -> str: - """Move the cursor down a specified amount of lines (Defaults to 1)""" + """Move the cursor down a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}B" @staticmethod def FORWARD(count: int = 1) -> str: - """Move the cursor forward a specified amount of lines (Defaults to 1)""" + """Move the cursor forward a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}C" @staticmethod def BACK(count: int = 1) -> str: - """Move the cursor back a specified amount of lines (Defaults to 1)""" + """Move the cursor back a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}D" @staticmethod def SET_POS(x: int, y: int) -> str: - """Set the cursor position to coordinates which are 1-based""" + """Set the cursor position to coordinates which are 1-based.""" return f"{CSI}{y};{x}H" class TextStyle(AnsiSequence, Enum): - """Create text style ANSI sequences""" + """Create text style ANSI sequences.""" # Resets all styles and colors of text RESET_ALL = 0 @@ -264,7 +264,7 @@ class TextStyle(AnsiSequence, Enum): def __str__(self) -> str: """Return ANSI text style sequence instead of enum name This is helpful when using a TextStyle in an f-string or format() call - e.g. my_str = f"{TextStyle.UNDERLINE_ENABLE}hello{TextStyle.UNDERLINE_DISABLE}" + e.g. my_str = f"{TextStyle.UNDERLINE_ENABLE}hello{TextStyle.UNDERLINE_DISABLE}". """ return f"{CSI}{self.value}m" @@ -297,7 +297,7 @@ class Fg(FgColor, Enum): def __str__(self) -> str: """Return ANSI color sequence instead of enum name This is helpful when using an Fg in an f-string or format() call - e.g. my_str = f"{Fg.BLUE}hello{Fg.RESET}" + e.g. my_str = f"{Fg.BLUE}hello{Fg.RESET}". """ return f"{CSI}{self.value}m" @@ -330,7 +330,7 @@ class Bg(BgColor, Enum): def __str__(self) -> str: """Return ANSI color sequence instead of enum name This is helpful when using a Bg in an f-string or format() call - e.g. my_str = f"{Bg.BLACK}hello{Bg.RESET}" + e.g. my_str = f"{Bg.BLACK}hello{Bg.RESET}". """ return f"{CSI}{self.value}m" @@ -601,7 +601,7 @@ class EightBitFg(FgColor, Enum): def __str__(self) -> str: """Return ANSI color sequence instead of enum name This is helpful when using an EightBitFg in an f-string or format() call - e.g. my_str = f"{EightBitFg.SLATE_BLUE_1}hello{Fg.RESET}" + e.g. my_str = f"{EightBitFg.SLATE_BLUE_1}hello{Fg.RESET}". """ return f"{CSI}38;5;{self.value}m" @@ -872,7 +872,7 @@ class EightBitBg(BgColor, Enum): def __str__(self) -> str: """Return ANSI color sequence instead of enum name This is helpful when using an EightBitBg in an f-string or format() call - e.g. my_str = f"{EightBitBg.KHAKI_3}hello{Bg.RESET}" + e.g. my_str = f"{EightBitBg.KHAKI_3}hello{Bg.RESET}". """ return f"{CSI}48;5;{self.value}m" @@ -883,7 +883,7 @@ class RgbFg(FgColor): """ def __init__(self, r: int, g: int, b: int) -> None: - """RgbFg initializer + """RgbFg initializer. :param r: integer from 0-255 for the red component of the color :param g: integer from 0-255 for the green component of the color @@ -898,7 +898,7 @@ def __init__(self, r: int, g: int, b: int) -> None: def __str__(self) -> str: """Return ANSI color sequence instead of enum name This is helpful when using an RgbFg in an f-string or format() call - e.g. my_str = f"{RgbFg(0, 55, 100)}hello{Fg.RESET}" + e.g. my_str = f"{RgbFg(0, 55, 100)}hello{Fg.RESET}". """ return self._sequence @@ -909,7 +909,7 @@ class RgbBg(BgColor): """ def __init__(self, r: int, g: int, b: int) -> None: - """RgbBg initializer + """RgbBg initializer. :param r: integer from 0-255 for the red component of the color :param g: integer from 0-255 for the green component of the color @@ -924,7 +924,7 @@ def __init__(self, r: int, g: int, b: int) -> None: def __str__(self) -> str: """Return ANSI color sequence instead of enum name This is helpful when using an RgbBg in an f-string or format() call - e.g. my_str = f"{RgbBg(100, 255, 27)}hello{Bg.RESET}" + e.g. my_str = f"{RgbBg(100, 255, 27)}hello{Bg.RESET}". """ return self._sequence diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 6de7a6d85..88e898196 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -55,7 +55,7 @@ def _build_hint(parser: argparse.ArgumentParser, arg_action: argparse.Action) -> str: - """Build tab completion hint for a given argument""" + """Build tab completion hint for a given argument.""" # Check if hinting is disabled for this argument suppress_hint = arg_action.get_suppress_tab_hint() # type: ignore[attr-defined] if suppress_hint or arg_action.help == argparse.SUPPRESS: @@ -70,7 +70,7 @@ def _build_hint(parser: argparse.ArgumentParser, arg_action: argparse.Action) -> def _single_prefix_char(token: str, parser: argparse.ArgumentParser) -> bool: - """Returns if a token is just a single flag prefix character""" + """Returns if a token is just a single flag prefix character.""" return len(token) == 1 and token[0] in parser.prefix_chars @@ -101,7 +101,7 @@ def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool: class _ArgumentState: - """Keeps state of an argument being parsed""" + """Keeps state of an argument being parsed.""" def __init__(self, arg_action: argparse.Action) -> None: self.action = arg_action @@ -137,7 +137,7 @@ def __init__(self, arg_action: argparse.Action) -> None: class _UnfinishedFlagError(CompletionError): def __init__(self, flag_arg_state: _ArgumentState) -> None: """CompletionError which occurs when the user has not finished the current flag - :param flag_arg_state: information about the unfinished flag action + :param flag_arg_state: information about the unfinished flag action. """ arg = f'{argparse._get_action_name(flag_arg_state.action)}' err = f'{generate_range_error(cast(int, flag_arg_state.min), cast(Union[int, float], flag_arg_state.max))}' @@ -150,19 +150,19 @@ def __init__(self, parser: argparse.ArgumentParser, arg_action: argparse.Action) """CompletionError which occurs when there are no results. If hinting is allowed, then its message will be a hint about the argument being tab completed. :param parser: ArgumentParser instance which owns the action being tab completed - :param arg_action: action being tab completed + :param arg_action: action being tab completed. """ # Set apply_style to False because we don't want hints to look like errors super().__init__(_build_hint(parser, arg_action), apply_style=False) class ArgparseCompleter: - """Automatic command line tab completion based on argparse parameters""" + """Automatic command line tab completion based on argparse parameters.""" def __init__( self, parser: argparse.ArgumentParser, cmd2_app: 'Cmd', *, parent_tokens: Optional[dict[str, list[str]]] = None ) -> None: - """Create an ArgparseCompleter + """Create an ArgparseCompleter. :param parser: ArgumentParser instance :param cmd2_app: reference to the Cmd2 application that owns this ArgparseCompleter @@ -202,7 +202,7 @@ def __init__( def complete( self, text: str, line: str, begidx: int, endidx: int, tokens: list[str], *, cmd_set: Optional[CommandSet] = None ) -> list[str]: - """Complete text using argparse metadata + """Complete text using argparse metadata. :param text: the string prefix we are attempting to match (all matches must begin with it) :param line: the current input line with leading whitespace removed @@ -240,7 +240,7 @@ def complete( completed_mutex_groups: dict[argparse._MutuallyExclusiveGroup, argparse.Action] = {} def consume_argument(arg_state: _ArgumentState) -> None: - """Consuming token as an argument""" + """Consuming token as an argument.""" arg_state.count += 1 consumed_arg_values.setdefault(arg_state.action.dest, []) consumed_arg_values[arg_state.action.dest].append(token) @@ -249,7 +249,7 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: """Check if an argument belongs to a mutually exclusive group and either mark that group as complete or print an error if the group has already been completed :param arg_action: the action of the argument - :raises CompletionError: if the group is already completed + :raises CompletionError: if the group is already completed. """ # Check if this action is in a mutually exclusive group for group in self._parser._mutually_exclusive_groups: @@ -490,7 +490,7 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: return completion_results def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matched_flags: list[str]) -> list[str]: - """Tab completion routine for a parsers unused flags""" + """Tab completion routine for a parsers unused flags.""" # Build a list of flags that can be tab completed match_against = [] @@ -523,7 +523,7 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matche return matches def _format_completions(self, arg_state: _ArgumentState, completions: Union[list[str], list[CompletionItem]]) -> list[str]: - """Format CompletionItems into hint table""" + """Format CompletionItems into hint table.""" # Nothing to do if we don't have at least 2 completions which are all CompletionItems if len(completions) < 2 or not all(isinstance(c, CompletionItem) for c in completions): return cast(list[str], completions) @@ -605,7 +605,7 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in :param begidx: the beginning index of the prefix text :param endidx: the ending index of the prefix text :param tokens: arguments passed to command/subcommand - :return: list of subcommand completions + :return: list of subcommand completions. """ # If our parser has subcommands, we must examine the tokens and check if they are subcommands # If so, we will let the subcommand's parser handle the rest of the tokens via another ArgparseCompleter. @@ -626,7 +626,7 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in def format_help(self, tokens: list[str]) -> str: """Supports cmd2's help command in the retrieval of help text :param tokens: arguments passed to help command - :return: help text of the command being queried + :return: help text of the command being queried. """ # If our parser has subcommands, we must examine the tokens and check if they are subcommands # If so, we will let the subcommand's parser handle the rest of the tokens via another ArgparseCompleter. @@ -654,7 +654,7 @@ def _complete_arg( ) -> list[str]: """Tab completion routine for an argparse argument :return: list of completions - :raises CompletionError: if the completer or choices function this calls raises one + :raises CompletionError: if the completer or choices function this calls raises one. """ # Check if the arg provides choices to the user arg_choices: Union[list[str], ChoicesCallable] diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 5ad52a855..f4dc70842 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -256,7 +256,7 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens) def generate_range_error(range_min: int, range_max: float) -> str: - """Generate an error message when the the number of arguments provided is not within the expected range""" + """Generate an error message when the the number of arguments provided is not within the expected range.""" err_str = "expected " if range_max == constants.INFINITY: @@ -275,7 +275,7 @@ def generate_range_error(range_min: int, range_max: float) -> str: class CompletionItem(str): # noqa: SLOT000 - """Completion item with descriptive text attached + """Completion item with descriptive text attached. See header of this file for more information """ @@ -284,7 +284,7 @@ def __new__(cls, value: object, *args: Any, **kwargs: Any) -> 'CompletionItem': return super().__new__(cls, value) def __init__(self, value: object, description: str = '', *args: Any) -> None: - """CompletionItem Initializer + """CompletionItem Initializer. :param value: the value being tab completed :param description: description text to display @@ -299,7 +299,7 @@ def __init__(self, value: object, description: str = '', *args: Any) -> None: @property def orig_value(self) -> Any: - """Read-only property for _orig_value""" + """Read-only property for _orig_value.""" return self._orig_value @@ -310,7 +310,7 @@ def orig_value(self) -> Any: @runtime_checkable class ChoicesProviderFuncBase(Protocol): - """Function that returns a list of choices in support of tab completion""" + """Function that returns a list of choices in support of tab completion.""" def __call__(self) -> list[str]: ... # pragma: no cover @@ -327,7 +327,7 @@ def __call__(self, *, arg_tokens: dict[str, list[str]] = {}) -> list[str]: ... @runtime_checkable class CompleterFuncBase(Protocol): - """Function to support tab completion with the provided state of the user prompt""" + """Function to support tab completion with the provided state of the user prompt.""" def __call__( self, @@ -371,7 +371,7 @@ def __init__( """Initializer :param is_completer: True if to_call is a tab completion routine which expects the args: text, line, begidx, endidx - :param to_call: the callable object that will be called to provide choices for the argument + :param to_call: the callable object that will be called to provide choices for the argument. """ self.is_completer = is_completer if is_completer: @@ -695,7 +695,7 @@ def _add_argument_wrapper( descriptive_header: Optional[str] = None, **kwargs: Any, ) -> argparse.Action: - """Wrapper around _ActionsContainer.add_argument() which supports more settings used by cmd2 + """Wrapper around _ActionsContainer.add_argument() which supports more settings used by cmd2. # Args from original function :param self: instance of the _ActionsContainer being added to @@ -989,7 +989,7 @@ def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str) class Cmd2HelpFormatter(argparse.RawTextHelpFormatter): - """Custom help formatter to configure ordering of help text""" + """Custom help formatter to configure ordering of help text.""" def _format_usage( self, @@ -1138,7 +1138,7 @@ def _determine_metavar( action: argparse.Action, default_metavar: Union[str, tuple[str, ...]], ) -> Union[str, tuple[str, ...]]: - """Custom method to determine what to use as the metavar value of an action""" + """Custom method to determine what to use as the metavar value of an action.""" if action.metavar is not None: result = action.metavar elif action.choices is not None: @@ -1165,7 +1165,7 @@ def format_tuple(tuple_size: int) -> tuple[str, ...]: return format_tuple def _format_args(self, action: argparse.Action, default_metavar: Union[str, tuple[str, ...]]) -> str: - """Customized to handle ranged nargs and make other output less verbose""" + """Customized to handle ranged nargs and make other output less verbose.""" metavar = self._determine_metavar(action, default_metavar) metavar_formatter = self._metavar_formatter(action, default_metavar) @@ -1193,7 +1193,7 @@ def _format_args(self, action: argparse.Action, default_metavar: Union[str, tupl class Cmd2ArgumentParser(argparse.ArgumentParser): - """Custom ArgumentParser class that improves error and help output""" + """Custom ArgumentParser class that improves error and help output.""" def __init__( self, @@ -1215,7 +1215,7 @@ def __init__( *, ap_completer_type: Optional[type['ArgparseCompleter']] = None, ) -> None: - """# Custom parameter added by cmd2 + """# Custom parameter added by cmd2. :param ap_completer_type: optional parameter which specifies a subclass of ArgparseCompleter for custom tab completion behavior on this parser. If this is None or not present, then cmd2 will use @@ -1272,7 +1272,7 @@ def add_subparsers(self, **kwargs: Any) -> argparse._SubParsersAction: # type: return super().add_subparsers(**kwargs) def error(self, message: str) -> NoReturn: - """Custom override that applies custom formatting to the error message""" + """Custom override that applies custom formatting to the error message.""" lines = message.split('\n') linum = 0 formatted_message = '' @@ -1288,7 +1288,7 @@ def error(self, message: str) -> NoReturn: self.exit(2, f'{formatted_message}\n\n') def format_help(self) -> str: - """Copy of format_help() from argparse.ArgumentParser with tweaks to separately display required parameters""" + """Copy of format_help() from argparse.ArgumentParser with tweaks to separately display required parameters.""" formatter = self._get_formatter() # usage @@ -1359,11 +1359,11 @@ def __init__(self, attribute: Any) -> None: self.__attribute = attribute def get(self) -> Any: - """Get the value of the attribute""" + """Get the value of the attribute.""" return self.__attribute def set(self, new_val: Any) -> None: - """Set the value of the attribute""" + """Set the value of the attribute.""" self.__attribute = new_val diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 51433236e..d64cf0cad 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -179,7 +179,7 @@ class _SavedReadlineSettings: - """readline settings that are backed up when switching between readline environments""" + """readline settings that are backed up when switching between readline environments.""" def __init__(self) -> None: self.completer = None @@ -188,7 +188,7 @@ def __init__(self) -> None: class _SavedCmd2Env: - """cmd2 environment settings that are backed up when entering an interactive Python shell""" + """cmd2 environment settings that are backed up when entering an interactive Python shell.""" def __init__(self) -> None: self.readline_settings = _SavedReadlineSettings() @@ -637,7 +637,7 @@ def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: are sub-classes of the provided type :param commandset_type: CommandSet sub-class type to search for :param subclass_match: If True, return all sub-classes of provided type, otherwise only search for exact match - :return: Matching CommandSets + :return: Matching CommandSets. """ return [ cmdset @@ -648,7 +648,7 @@ def find_commandsets(self, commandset_type: type[CommandSet], *, subclass_match: def find_commandset_for_command(self, command_name: str) -> Optional[CommandSet]: """Finds the CommandSet that registered the command name :param command_name: command name to search - :return: CommandSet that provided the command + :return: CommandSet that provided the command. """ return self._cmd_to_command_sets.get(command_name) @@ -677,7 +677,7 @@ def load_commandset_by_type(commandset_types: list[type[CommandSet]]) -> None: load_commandset_by_type(all_commandset_defs) def register_command_set(self, cmdset: CommandSet) -> None: - """Installs a CommandSet, loading all commands defined in the CommandSet + """Installs a CommandSet, loading all commands defined in the CommandSet. :param cmdset: CommandSet to load """ @@ -831,7 +831,7 @@ def _install_help_function(self, cmd_name: str, cmd_help: Callable[..., None]) - setattr(self, help_func_name, cmd_help) def unregister_command_set(self, cmdset: CommandSet) -> None: - """Uninstalls a CommandSet and unloads all associated commands + """Uninstalls a CommandSet and unloads all associated commands. :param cmdset: CommandSet to uninstall """ @@ -902,7 +902,7 @@ def check_parser_uninstallable(parser: argparse.ArgumentParser) -> None: check_parser_uninstallable(command_parser) def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: - """Register subcommands with their base command + """Register subcommands with their base command. :param cmdset: CommandSet or cmd2.Cmd subclass containing subcommands """ @@ -1008,7 +1008,7 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> break def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: - """Unregister subcommands from their base command + """Unregister subcommands from their base command. :param cmdset: CommandSet containing subcommands """ @@ -1054,7 +1054,7 @@ def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None: @property def always_prefix_settables(self) -> bool: - """Flags whether CommandSet settable values should always be prefixed + """Flags whether CommandSet settable values should always be prefixed. :return: True if CommandSet settable values will always be prefixed. False if not. """ @@ -1078,7 +1078,7 @@ def always_prefix_settables(self, new_value: bool) -> None: @property def settables(self) -> Mapping[str, Settable]: - """Get all available user-settable attributes. This includes settables defined in installed CommandSets + """Get all available user-settable attributes. This includes settables defined in installed CommandSets. :return: Mapping from attribute-name to Settable of all user-settable attributes from """ @@ -1093,7 +1093,7 @@ def settables(self) -> Mapping[str, Settable]: return all_settables def add_settable(self, settable: Settable) -> None: - """Add a settable parameter to ``self.settables`` + """Add a settable parameter to ``self.settables``. :param settable: Settable object being added """ @@ -1103,7 +1103,7 @@ def add_settable(self, settable: Settable) -> None: self._settables[settable.name] = settable def remove_settable(self, name: str) -> None: - """Convenience method for removing a settable parameter from ``self.settables`` + """Convenience method for removing a settable parameter from ``self.settables``. :param name: name of the settable being removed :raises KeyError: if the Settable matches this name @@ -1114,14 +1114,14 @@ def remove_settable(self, name: str) -> None: raise KeyError(name + " is not a settable parameter") def build_settables(self) -> None: - """Create the dictionary of user-settable parameters""" + """Create the dictionary of user-settable parameters.""" def get_allow_style_choices(cli_self: Cmd) -> list[str]: - """Used to tab complete allow_style values""" + """Used to tab complete allow_style values.""" return [val.name.lower() for val in ansi.AllowStyle] def allow_style_type(value: str) -> ansi.AllowStyle: - """Converts a string value into an ansi.AllowStyle""" + """Converts a string value into an ansi.AllowStyle.""" try: return ansi.AllowStyle[value.upper()] except KeyError: @@ -1159,16 +1159,16 @@ def allow_style_type(value: str) -> ansi.AllowStyle: @property def allow_style(self) -> ansi.AllowStyle: - """Read-only property needed to support do_set when it reads allow_style""" + """Read-only property needed to support do_set when it reads allow_style.""" return ansi.allow_style @allow_style.setter def allow_style(self, new_val: ansi.AllowStyle) -> None: - """Setter property needed to support do_set when it updates allow_style""" + """Setter property needed to support do_set when it updates allow_style.""" ansi.allow_style = new_val def _completion_supported(self) -> bool: - """Return whether tab completion is supported""" + """Return whether tab completion is supported.""" return self.use_rawinput and bool(self.completekey) and rl_type != RlType.NONE @property @@ -1210,7 +1210,7 @@ def print_to( sys.stderr.write(self.broken_pipe_warning) def poutput(self, msg: Any = '', *, end: str = '\n') -> None: - """Print message to self.stdout and appends a newline by default + """Print message to self.stdout and appends a newline by default. :param msg: object to print :param end: string appended after the end of the message, default a newline @@ -1218,7 +1218,7 @@ def poutput(self, msg: Any = '', *, end: str = '\n') -> None: self.print_to(self.stdout, msg, end=end) def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> None: - """Print message to sys.stderr + """Print message to sys.stderr. :param msg: object to print :param end: string appended after the end of the message, default a newline @@ -1228,7 +1228,7 @@ def perror(self, msg: Any = '', *, end: str = '\n', apply_style: bool = True) -> self.print_to(sys.stderr, msg, end=end, style=ansi.style_error if apply_style else None) def psuccess(self, msg: Any = '', *, end: str = '\n') -> None: - """Wraps poutput, but applies ansi.style_success by default + """Wraps poutput, but applies ansi.style_success by default. :param msg: object to print :param end: string appended after the end of the message, default a newline @@ -1237,7 +1237,7 @@ def psuccess(self, msg: Any = '', *, end: str = '\n') -> None: self.poutput(msg, end=end) def pwarning(self, msg: Any = '', *, end: str = '\n') -> None: - """Wraps perror, but applies ansi.style_warning by default + """Wraps perror, but applies ansi.style_warning by default. :param msg: object to print :param end: string appended after the end of the message, default a newline @@ -1341,7 +1341,7 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False) -> None: def _reset_completion_defaults(self) -> None: """Resets tab completion settings - Needs to be called each time readline runs tab completion + Needs to be called each time readline runs tab completion. """ self.allow_appended_space = True self.allow_closing_quote = True @@ -1612,7 +1612,7 @@ def index_based_complete( def path_complete( self, text: str, line: str, begidx: int, endidx: int, *, path_filter: Optional[Callable[[str], bool]] = None ) -> list[str]: - """Performs completion of local file system paths + """Performs completion of local file system paths. :param text: the string prefix we are attempting to match (all matches must begin with it) :param line: the current input line with leading whitespace removed @@ -1752,7 +1752,7 @@ def complete_users() -> list[str]: return matches def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, complete_blank: bool = False) -> list[str]: - """Performs completion of executables either in a user's path or a given path + """Performs completion of executables either in a user's path or a given path. :param text: the string prefix we are attempting to match (all matches must begin with it) :param line: the current input line with leading whitespace removed @@ -1778,7 +1778,7 @@ def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int, *, def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: CompleterFunc) -> list[str]: """Called by complete() as the first tab completion function for all commands It determines if it should tab complete for redirection (|, >, >>) or use the - completer function for the current command + completer function for the current command. :param text: the string prefix we are attempting to match (all matches must begin with it) :param line: the current input line with leading whitespace removed @@ -1880,7 +1880,7 @@ def _pad_matches_to_display(matches_to_display: list[str]) -> tuple[list[str], i def _display_matches_gnu_readline( self, substitution: str, matches: list[str], longest_match_length: int ) -> None: # pragma: no cover - """Prints a match list using GNU readline's rl_display_match_list() + """Prints a match list using GNU readline's rl_display_match_list(). :param substitution: the substitution written to the command line :param matches: the tab completion matches to display @@ -1939,7 +1939,7 @@ def _display_matches_gnu_readline( rl_force_redisplay() def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no cover - """Prints a match list using pyreadline3's _display_completions() + """Prints a match list using pyreadline3's _display_completions(). :param matches: the tab completion matches to display """ @@ -1991,7 +1991,7 @@ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argpar def _perform_completion( self, text: str, line: str, begidx: int, endidx: int, custom_settings: Optional[utils.CustomCompletionSettings] = None ) -> None: - """Helper function for complete() that performs the actual completion + """Helper function for complete() that performs the actual completion. :param text: the string prefix we are attempting to match (all matches must begin with it) :param line: the current input line with leading whitespace removed @@ -2172,7 +2172,7 @@ def _perform_completion( def complete( # type: ignore[override] self, text: str, state: int, custom_settings: Optional[utils.CustomCompletionSettings] = None ) -> Optional[str]: - """Override of cmd's complete method which returns the next possible completion for 'text' + """Override of cmd's complete method which returns the next possible completion for 'text'. This completer function is called by readline as complete(text, state), for state in 0, 1, 2, …, until it returns a non-string value. It should return the next possible completion starting with text. @@ -2274,16 +2274,16 @@ def complete( # type: ignore[override] return None def in_script(self) -> bool: - """Return whether a text script is running""" + """Return whether a text script is running.""" return self._current_script_dir is not None def in_pyscript(self) -> bool: - """Return whether running inside a Python shell or pyscript""" + """Return whether running inside a Python shell or pyscript.""" return self._in_py @property def aliases(self) -> dict[str, str]: - """Read-only property to access the aliases stored in the StatementParser""" + """Read-only property to access the aliases stored in the StatementParser.""" return self.statement_parser.aliases def get_names(self) -> list[str]: @@ -2291,7 +2291,7 @@ def get_names(self) -> list[str]: return dir(self) def get_all_commands(self) -> list[str]: - """Return a list of all commands""" + """Return a list of all commands.""" return [ name[len(constants.COMMAND_FUNC_PREFIX) :] for name in self.get_names() @@ -2299,7 +2299,7 @@ def get_all_commands(self) -> list[str]: ] def get_visible_commands(self) -> list[str]: - """Return a list of commands that have not been hidden or disabled""" + """Return a list of commands that have not been hidden or disabled.""" return [ command for command in self.get_all_commands() @@ -2310,7 +2310,7 @@ def get_visible_commands(self) -> list[str]: _alias_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) def _get_alias_completion_items(self) -> list[CompletionItem]: - """Return list of alias names and values as CompletionItems""" + """Return list of alias names and values as CompletionItems.""" results: list[CompletionItem] = [] for cur_key in self.aliases: @@ -2323,7 +2323,7 @@ def _get_alias_completion_items(self) -> list[CompletionItem]: _macro_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) def _get_macro_completion_items(self) -> list[CompletionItem]: - """Return list of macro names and values as CompletionItems""" + """Return list of macro names and values as CompletionItems.""" results: list[CompletionItem] = [] for cur_key in self.macros: @@ -2336,7 +2336,7 @@ def _get_macro_completion_items(self) -> list[CompletionItem]: _settable_completion_table = SimpleTable([Column('Value', width=30), Column('Description', width=60)], divider_char=None) def _get_settable_completion_items(self) -> list[CompletionItem]: - """Return list of Settable names, values, and descriptions as CompletionItems""" + """Return list of Settable names, values, and descriptions as CompletionItems.""" results: list[CompletionItem] = [] for cur_key in self.settables: @@ -2346,14 +2346,14 @@ def _get_settable_completion_items(self) -> list[CompletionItem]: return results def _get_commands_aliases_and_macros_for_completion(self) -> list[str]: - """Return a list of visible commands, aliases, and macros for tab completion""" + """Return a list of visible commands, aliases, and macros for tab completion.""" visible_commands = set(self.get_visible_commands()) alias_names = set(self.aliases) macro_names = set(self.macros) return list(visible_commands | alias_names | macro_names) def get_help_topics(self) -> list[str]: - """Return a list of help topics""" + """Return a list of help topics.""" all_topics = [ name[len(constants.HELP_FUNC_PREFIX) :] for name in self.get_names() @@ -2402,7 +2402,7 @@ def termination_signal_handler(self, signum: int, _: Optional[FrameType]) -> Non sys.exit(128 + signum) def _raise_keyboard_interrupt(self) -> None: - """Helper function to raise a KeyboardInterrupt""" + """Helper function to raise a KeyboardInterrupt.""" raise KeyboardInterrupt("Got a keyboard interrupt") def precmd(self, statement: Union[Statement, str]) -> Statement: @@ -2593,7 +2593,7 @@ def onecmd_plus_hooks( return stop def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) -> bool: - """Run the command finalization hooks""" + """Run the command finalization hooks.""" with self.sigint_protection: if not sys.platform.startswith('win') and self.stdin.isatty(): # Before the next command runs, fix any terminal problems like those @@ -2664,7 +2664,7 @@ def _complete_statement(self, line: str, *, orig_rl_history_length: Optional[int """ def combine_rl_history(statement: Statement) -> None: - """Combine all lines of a multiline command into a single readline history entry""" + """Combine all lines of a multiline command into a single readline history entry.""" if orig_rl_history_length is None or not statement.multiline_command: return @@ -2737,7 +2737,7 @@ def combine_rl_history(statement: Statement) -> None: return statement def _input_line_to_statement(self, line: str, *, orig_rl_history_length: Optional[int] = None) -> Statement: - """Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved + """Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved. :param line: the line being parsed :param orig_rl_history_length: Optional length of the readline history before the current command was typed. @@ -2790,7 +2790,7 @@ def _input_line_to_statement(self, line: str, *, orig_rl_history_length: Optiona return statement def _resolve_macro(self, statement: Statement) -> Optional[str]: - """Resolve a macro and return the resulting string + """Resolve a macro and return the resulting string. :param statement: the parsed statement from the command line :return: the resolved macro or None on error @@ -2943,7 +2943,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: return redir_saved_state def _restore_output(self, statement: Statement, saved_redir_state: utils.RedirectionSavedState) -> None: - """Handles restoring state after output redirection + """Handles restoring state after output redirection. :param statement: Statement object which contains the parsed input from the user :param saved_redir_state: contains information needed to restore state data @@ -2973,7 +2973,7 @@ def _restore_output(self, statement: Statement, saved_redir_state: utils.Redirec self._redirecting = saved_redir_state.saved_redirecting def cmd_func(self, command: str) -> Optional[CommandFunc]: - """Get the function for a command + """Get the function for a command. :param command: the name of the command @@ -3092,7 +3092,7 @@ def read_input( saved_history: Optional[list[str]] = None def configure_readline() -> None: - """Configure readline tab completion and history""" + """Configure readline tab completion and history.""" nonlocal readline_configured nonlocal saved_completer nonlocal saved_history @@ -3148,7 +3148,7 @@ def complete_none(text: str, state: int) -> Optional[str]: # pragma: no cover readline_configured = True def restore_readline() -> None: - """Restore readline tab completion and history""" + """Restore readline tab completion and history.""" nonlocal readline_configured if not readline_configured or rl_type == RlType.NONE: # pragma: no cover return @@ -3204,7 +3204,7 @@ def restore_readline() -> None: return line.rstrip('\r\n') def _read_command_line(self, prompt: str) -> str: - """Read command line from appropriate stdin + """Read command line from appropriate stdin. :param prompt: prompt to display to user :return: command line text of 'eof' if an EOFError was caught @@ -3225,7 +3225,7 @@ def _read_command_line(self, prompt: str) -> str: self.terminal_lock.acquire() def _set_up_cmd2_readline(self) -> _SavedReadlineSettings: - """Called at beginning of command loop to set up readline with cmd2-specific settings + """Called at beginning of command loop to set up readline with cmd2-specific settings. :return: Class containing saved readline settings """ @@ -3265,7 +3265,7 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings: return readline_settings def _restore_readline(self, readline_settings: _SavedReadlineSettings) -> None: - """Called at end of command loop to restore saved readline settings + """Called at end of command loop to restore saved readline settings. :param readline_settings: the readline settings to restore """ @@ -3334,7 +3334,7 @@ def _cmdloop(self) -> None: # Preserve quotes since we are passing strings to other commands @with_argparser(alias_parser, preserve_quotes=True) def do_alias(self, args: argparse.Namespace) -> None: - """Manage aliases""" + """Manage aliases.""" # Call handler for whatever subcommand was selected handler = args.cmd2_handler.get() handler(args) @@ -3369,7 +3369,7 @@ def do_alias(self, args: argparse.Namespace) -> None: @as_subcommand_to('alias', 'create', alias_create_parser, help=alias_create_description.lower()) def _alias_create(self, args: argparse.Namespace) -> None: - """Create or overwrite an alias""" + """Create or overwrite an alias.""" self.last_result = False # Validate the alias name @@ -3419,7 +3419,7 @@ def _alias_create(self, args: argparse.Namespace) -> None: @as_subcommand_to('alias', 'delete', alias_delete_parser, help=alias_delete_help) def _alias_delete(self, args: argparse.Namespace) -> None: - """Delete aliases""" + """Delete aliases.""" self.last_result = True if args.all: @@ -3456,7 +3456,7 @@ def _alias_delete(self, args: argparse.Namespace) -> None: @as_subcommand_to('alias', 'list', alias_list_parser, help=alias_list_help) def _alias_list(self, args: argparse.Namespace) -> None: - """List some or all aliases as 'alias create' commands""" + """List some or all aliases as 'alias create' commands.""" self.last_result = {} # dict[alias_name, alias_value] tokens_to_quote = constants.REDIRECTION_TOKENS @@ -3502,7 +3502,7 @@ def _alias_list(self, args: argparse.Namespace) -> None: # Preserve quotes since we are passing strings to other commands @with_argparser(macro_parser, preserve_quotes=True) def do_macro(self, args: argparse.Namespace) -> None: - """Manage macros""" + """Manage macros.""" # Call handler for whatever subcommand was selected handler = args.cmd2_handler.get() handler(args) @@ -3561,7 +3561,7 @@ def do_macro(self, args: argparse.Namespace) -> None: @as_subcommand_to('macro', 'create', macro_create_parser, help=macro_create_help) def _macro_create(self, args: argparse.Namespace) -> None: - """Create or overwrite a macro""" + """Create or overwrite a macro.""" self.last_result = False # Validate the macro name @@ -3654,7 +3654,7 @@ def _macro_create(self, args: argparse.Namespace) -> None: @as_subcommand_to('macro', 'delete', macro_delete_parser, help=macro_delete_help) def _macro_delete(self, args: argparse.Namespace) -> None: - """Delete macros""" + """Delete macros.""" self.last_result = True if args.all: @@ -3691,7 +3691,7 @@ def _macro_delete(self, args: argparse.Namespace) -> None: @as_subcommand_to('macro', 'list', macro_list_parser, help=macro_list_help) def _macro_list(self, args: argparse.Namespace) -> None: - """List some or all macros as 'macro create' commands""" + """List some or all macros as 'macro create' commands.""" self.last_result = {} # dict[macro_name, macro_value] tokens_to_quote = constants.REDIRECTION_TOKENS @@ -3725,7 +3725,7 @@ def _macro_list(self, args: argparse.Namespace) -> None: self.perror(f"Macro '{name}' not found") def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: - """Completes the command argument of help""" + """Completes the command argument of help.""" # Complete token against topics and visible commands topics = set(self.get_help_topics()) visible_commands = set(self.get_visible_commands()) @@ -3735,7 +3735,7 @@ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) def complete_help_subcommands( self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] ) -> list[str]: - """Completes the subcommands argument of help""" + """Completes the subcommands argument of help.""" # Make sure we have a command whose subcommands we will complete command = arg_tokens['command'][0] if not command: @@ -3767,7 +3767,7 @@ def complete_help_subcommands( @with_argparser(help_parser) def do_help(self, args: argparse.Namespace) -> None: - """List available commands or provide detailed help for a specific command""" + """List available commands or provide detailed help for a specific command.""" self.last_result = True if not args.command or args.verbose: @@ -3804,7 +3804,7 @@ def do_help(self, args: argparse.Namespace) -> None: def print_topics(self, header: str, cmds: Optional[list[str]], cmdlen: int, maxcol: int) -> None: """Print groups of commands and topics in columns and an optional header - Override of cmd's print_topics() to handle headers with newlines, ANSI style sequences, and wide characters + Override of cmd's print_topics() to handle headers with newlines, ANSI style sequences, and wide characters. :param header: string to print above commands being printed :param cmds: list of topics to print @@ -3821,7 +3821,7 @@ def print_topics(self, header: str, cmds: Optional[list[str]], cmdlen: int, maxc def columnize(self, str_list: Optional[list[str]], display_width: int = 80) -> None: """Display a list of single-line strings as a compact set of columns. - Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters + Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters. Each column is only as wide as necessary. Columns are separated by two spaces (one was not legible enough). @@ -3877,7 +3877,7 @@ def columnize(self, str_list: Optional[list[str]], display_width: int = 80) -> N self.poutput(" ".join(texts)) def _help_menu(self, verbose: bool = False) -> None: - """Show a list of commands which help can be displayed for""" + """Show a list of commands which help can be displayed for.""" cmds_cats, cmds_doc, cmds_undoc, help_topics = self._build_command_info() if not cmds_cats: @@ -3927,7 +3927,7 @@ def _build_command_info(self) -> tuple[dict[str, list[str]], list[str], list[str return cmds_cats, cmds_doc, cmds_undoc, help_topics def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None: - """Customized version of print_topics that can switch between verbose or traditional output""" + """Customized version of print_topics that can switch between verbose or traditional output.""" import io if cmds: @@ -3998,7 +3998,7 @@ def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None: @with_argparser(shortcuts_parser) def do_shortcuts(self, _: argparse.Namespace) -> None: - """List available shortcuts""" + """List available shortcuts.""" # Sort the shortcut tuples by name sorted_shortcuts = sorted(self.statement_parser.shortcuts, key=lambda x: self.default_sort_key(x[0])) result = "\n".join(f'{sc[0]}: {sc[1]}' for sc in sorted_shortcuts) @@ -4023,7 +4023,7 @@ def do_eof(self, _: argparse.Namespace) -> Optional[bool]: @with_argparser(quit_parser) def do_quit(self, _: argparse.Namespace) -> Optional[bool]: - """Exit this application""" + """Exit this application.""" # Return True to stop the command loop self.last_result = True return True @@ -4081,7 +4081,7 @@ def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], p def complete_set_value( self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]] ) -> list[str]: - """Completes the value argument of set""" + """Completes the value argument of set.""" param = arg_tokens['param'][0] try: settable = self.settables[param] @@ -4134,7 +4134,7 @@ def complete_set_value( # Preserve quotes so users can pass in quoted empty strings and flags (e.g. -h) as the value @with_argparser(set_parser, preserve_quotes=True) def do_set(self, args: argparse.Namespace) -> None: - """Set a settable parameter or show current settings of parameters""" + """Set a settable parameter or show current settings of parameters.""" self.last_result = False if not self.settables: @@ -4198,7 +4198,7 @@ def do_set(self, args: argparse.Namespace) -> None: # Preserve quotes since we are passing these strings to the shell @with_argparser(shell_parser, preserve_quotes=True) def do_shell(self, args: argparse.Namespace) -> None: - """Execute a command as if at the OS prompt""" + """Execute a command as if at the OS prompt.""" import signal import subprocess @@ -4278,7 +4278,7 @@ def _reset_py_display() -> None: def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env: """Set up interactive Python shell environment - :return: Class containing saved up cmd2 environment + :return: Class containing saved up cmd2 environment. """ cmd2_env = _SavedCmd2Env() @@ -4340,7 +4340,7 @@ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env: return cmd2_env def _restore_cmd2_env(self, cmd2_env: _SavedCmd2Env) -> None: - """Restore cmd2 environment after exiting an interactive Python shell + """Restore cmd2 environment after exiting an interactive Python shell. :param cmd2_env: the environment settings to restore """ @@ -4387,7 +4387,7 @@ def _run_python(self, *, pyscript: Optional[str] = None) -> Optional[bool]: self.last_result = False def py_quit() -> None: - """Function callable from the interactive Python console to exit that environment""" + """Function callable from the interactive Python console to exit that environment.""" raise EmbeddedConsoleExit from .py_bridge import ( @@ -4495,7 +4495,7 @@ def py_quit() -> None: @with_argparser(py_parser) def do_py(self, _: argparse.Namespace) -> Optional[bool]: """Run an interactive Python shell - :return: True if running of commands should stop + :return: True if running of commands should stop. """ # self.last_resort will be set by _run_python() return self._run_python() @@ -4508,7 +4508,7 @@ def do_py(self, _: argparse.Namespace) -> Optional[bool]: @with_argparser(run_pyscript_parser) def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]: - """Run a Python script file inside the console + """Run a Python script file inside the console. :return: True if running of commands should stop """ @@ -4544,7 +4544,7 @@ def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]: @with_argparser(ipython_parser) def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover - """Enter an interactive IPython shell + """Enter an interactive IPython shell. :return: True if running of commands should stop """ @@ -4663,7 +4663,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover @with_argparser(history_parser) def do_history(self, args: argparse.Namespace) -> Optional[bool]: - """View, run, edit, save, or clear previously entered commands + """View, run, edit, save, or clear previously entered commands. :return: True if running of commands should stop """ @@ -4779,7 +4779,7 @@ def _get_history(self, args: argparse.Namespace) -> 'OrderedDict[int, HistoryIte return history def _initialize_history(self, hist_file: str) -> None: - """Initialize history using history related attributes + """Initialize history using history related attributes. :param hist_file: optional path to persistent history file. If specified, then history from previous sessions will be included. Additionally, all history will be written @@ -4872,7 +4872,7 @@ def _initialize_history(self, hist_file: str) -> None: readline.add_history(formatted_command) def _persist_history(self) -> None: - """Write history out to the persistent history file as compressed JSON""" + """Write history out to the persistent history file as compressed JSON.""" if not self.persistent_history_file: return @@ -4898,7 +4898,7 @@ def _generate_transcript( *, add_to_history: bool = True, ) -> None: - """Generate a transcript file from a given history of commands""" + """Generate a transcript file from a given history of commands.""" self.last_result = False # Validate the transcript file path to make sure directory exists and write access is available @@ -5004,12 +5004,12 @@ def _generate_transcript( @with_argparser(edit_parser) def do_edit(self, args: argparse.Namespace) -> None: - """Run a text editor and optionally open a file with it""" + """Run a text editor and optionally open a file with it.""" # self.last_result will be set by do_shell() which is called by run_editor() self.run_editor(args.file_path) def run_editor(self, file_path: Optional[str] = None) -> None: - """Run a text editor and optionally open a file with it + """Run a text editor and optionally open a file with it. :param file_path: optional path of the file to edit. Defaults to None. :raises EnvironmentError: if self.editor is not set @@ -5129,7 +5129,7 @@ def do_run_script(self, args: argparse.Namespace) -> Optional[bool]: @with_argparser(relative_run_script_parser) def do__relative_run_script(self, args: argparse.Namespace) -> Optional[bool]: - """Run commands in script file that is encoded as either ASCII or UTF-8 text + """Run commands in script file that is encoded as either ASCII or UTF-8 text. :return: True if running of commands should stop """ @@ -5337,7 +5337,7 @@ def set_window_title(title: str) -> None: # pragma: no cover pass def enable_command(self, command: str) -> None: - """Enable a command by restoring its functions + """Enable a command by restoring its functions. :param command: the command being enabled """ @@ -5369,7 +5369,7 @@ def enable_command(self, command: str) -> None: del self.disabled_commands[command] def enable_category(self, category: str) -> None: - """Enable an entire category of commands + """Enable an entire category of commands. :param category: the category to enable """ @@ -5379,7 +5379,7 @@ def enable_category(self, category: str) -> None: self.enable_command(cmd_name) def disable_command(self, command: str, message_to_print: str) -> None: - """Disable a command and overwrite its functions + """Disable a command and overwrite its functions. :param command: the command being disabled :param message_to_print: what to print when this command is run or help is called on it while disabled @@ -5435,7 +5435,7 @@ def disable_category(self, category: str, message_to_print: str) -> None: self.disable_command(cmd_name, message_to_print) def _report_disabled_command_usage(self, *_args: Any, message_to_print: str, **_kwargs: Any) -> None: - """Report when a disabled command has been run or had help called on it + """Report when a disabled command has been run or had help called on it. :param _args: not used :param message_to_print: the message reporting that the command is disabled @@ -5520,7 +5520,7 @@ def cmdloop(self, intro: Optional[str] = None) -> int: # type: ignore[override] # ### def _initialize_plugin_system(self) -> None: - """Initialize the plugin system""" + """Initialize the plugin system.""" self._preloop_hooks: list[Callable[[], None]] = [] self._postloop_hooks: list[Callable[[], None]] = [] self._postparsing_hooks: list[Callable[[plugin.PostparsingData], plugin.PostparsingData]] = [] @@ -5559,7 +5559,7 @@ def register_postloop_hook(self, func: Callable[[], None]) -> None: @classmethod def _validate_postparsing_callable(cls, func: Callable[[plugin.PostparsingData], plugin.PostparsingData]) -> None: - """Check parameter and return types for postparsing hooks""" + """Check parameter and return types for postparsing hooks.""" cls._validate_callable_param_count(cast(Callable[..., Any], func), 1) signature = inspect.signature(func) _, param = next(iter(signature.parameters.items())) @@ -5569,7 +5569,7 @@ def _validate_postparsing_callable(cls, func: Callable[[plugin.PostparsingData], raise TypeError(f"{func.__name__} must declare return a return type of 'cmd2.plugin.PostparsingData'") def register_postparsing_hook(self, func: Callable[[plugin.PostparsingData], plugin.PostparsingData]) -> None: - """Register a function to be called after parsing user input but before running the command""" + """Register a function to be called after parsing user input but before running the command.""" self._validate_postparsing_callable(func) self._postparsing_hooks.append(func) @@ -5633,7 +5633,7 @@ def _resolve_func_self( ) -> 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 - instance of each type, using type is a reasonably safe way to resolve the correct object instance + instance of each type, using type is a reasonably safe way to resolve the correct object instance. :param cmd_support_func: command support function. This could be a completer or namespace provider :param cmd_self: The `self` associated with the command or subcommand diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index 16412bd49..066f63157 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -1,4 +1,4 @@ -"""Supports the definition of commands in separate classes to be composed into cmd2.Cmd""" +"""Supports the definition of commands in separate classes to be composed into cmd2.Cmd.""" from collections.abc import Callable, Mapping from typing import ( @@ -150,7 +150,7 @@ def settables(self) -> Mapping[str, Settable]: return self._settables def add_settable(self, settable: Settable) -> None: - """Convenience method to add a settable parameter to the CommandSet + """Convenience method to add a settable parameter to the CommandSet. :param settable: Settable object being added """ @@ -165,7 +165,7 @@ def add_settable(self, settable: Settable) -> None: self._settables[settable.name] = settable def remove_settable(self, name: str) -> None: - """Convenience method for removing a settable parameter from the CommandSet + """Convenience method for removing a settable parameter from the CommandSet. :param name: name of the settable being removed :raises KeyError: if the Settable matches this name diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 815062ebe..ca9fdba28 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -1,4 +1,4 @@ -"""Decorators for ``cmd2`` commands""" +"""Decorators for ``cmd2`` commands.""" import argparse from collections.abc import Callable, Sequence @@ -77,7 +77,7 @@ def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Stateme """Helper function for cmd2 decorators to inspect the positional arguments until the cmd2.Cmd argument is found Assumes that we will find cmd2.Cmd followed by the command statement object or string. :arg args: The positional arguments to inspect - :return: The cmd2.Cmd reference and the command line statement + :return: The cmd2.Cmd reference and the command line statement. """ for pos, arg in enumerate(args): from cmd2 import ( @@ -97,7 +97,7 @@ def _parse_positionals(args: tuple[Any, ...]) -> tuple['cmd2.Cmd', Union[Stateme def _arg_swap(args: Union[Sequence[Any]], search_arg: Any, *replace_arg: Any) -> list[Any]: - """Helper function for cmd2 decorators to swap the Statement parameter with one or more decorator-specific parameters + """Helper function for cmd2 decorators to swap the Statement parameter with one or more decorator-specific parameters. :param args: The original positional arguments :param search_arg: The argument to search for (usually the Statement) @@ -166,7 +166,7 @@ def arg_decorator(func: ArgListCommandFunc[CommandParent]) -> RawCommandFuncOpti @functools.wraps(func) def cmd_wrapper(*args: Any, **kwargs: Any) -> Optional[bool]: - """Command function wrapper which translates command line into an argument list and calls actual command function + """Command function wrapper which translates command line into an argument list and calls actual command function. :param args: All positional arguments to this function. We're expecting there to be: cmd2_app, statement: Union[Statement, str] @@ -333,7 +333,7 @@ def arg_decorator(func: ArgparseCommandFunc[CommandParent]) -> RawCommandFuncOpt @functools.wraps(func) def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]: """Command function wrapper which translates command line into argparse Namespace and calls actual - command function + command function. :param args: All positional arguments to this function. We're expecting there to be: cmd2_app, statement: Union[Statement, str] diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index d5aa6c7a5..3fa9d64a7 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -1,4 +1,4 @@ -"""Custom exceptions for cmd2""" +"""Custom exceptions for cmd2.""" from typing import Any @@ -57,7 +57,7 @@ class PassThroughException(Exception): def __init__(self, *args: Any, wrapped_ex: BaseException) -> None: """Initializer for PassThroughException - :param wrapped_ex: the exception that will be raised + :param wrapped_ex: the exception that will be raised. """ self.wrapped_ex = wrapped_ex super().__init__(*args) @@ -69,7 +69,7 @@ def __init__(self, *args: Any, wrapped_ex: BaseException) -> None: class Cmd2ShlexError(Exception): - """Raised when shlex fails to parse a command line string in StatementParser""" + """Raised when shlex fails to parse a command line string in StatementParser.""" class EmbeddedConsoleExit(SystemExit): @@ -81,4 +81,4 @@ class EmptyStatement(Exception): class RedirectionError(Exception): - """Custom exception class for when redirecting or piping output fails""" + """Custom exception class for when redirecting or piping output fails.""" diff --git a/cmd2/history.py b/cmd2/history.py index dd40c1e8c..4bd47aa9b 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -1,4 +1,4 @@ -"""History management classes""" +"""History management classes.""" import json import re @@ -64,7 +64,7 @@ def single_line_format(statement: Statement) -> str: @dataclass(frozen=True) class HistoryItem: - """Class used to represent one command in the history list""" + """Class used to represent one command in the history list.""" _listformat = ' {:>4} {}' _ex_listformat = ' {:>4}x {}' @@ -75,7 +75,7 @@ class HistoryItem: statement: Statement def __str__(self) -> str: - """A convenient human-readable representation of the history item""" + """A convenient human-readable representation of the history item.""" return self.statement.raw @property @@ -89,7 +89,7 @@ def raw(self) -> str: @property def expanded(self) -> str: """Return the command as run which includes shortcuts and aliases resolved - plus any changes made in hooks + plus any changes made in hooks. Proxy property for ``self.statement.expanded_command_line`` """ @@ -126,12 +126,12 @@ def pr(self, idx: int, script: bool = False, expanded: bool = False, verbose: bo return ret_str def to_dict(self) -> dict[str, Any]: - """Utility method to convert this HistoryItem into a dictionary for use in persistent JSON history files""" + """Utility method to convert this HistoryItem into a dictionary for use in persistent JSON history files.""" return {HistoryItem._statement_field: self.statement.to_dict()} @staticmethod def from_dict(source_dict: dict[str, Any]) -> 'HistoryItem': - """Utility method to restore a HistoryItem from a dictionary + """Utility method to restore a HistoryItem from a dictionary. :param source_dict: source data dictionary (generated using to_dict()) :return: HistoryItem object @@ -234,7 +234,7 @@ def get(self, index: int) -> HistoryItem: spanpattern = re.compile(r'^\s*(?P-?[1-9]\d*)?(?P:|(\.{2,}))(?P-?[1-9]\d*)?\s*$') def span(self, span: str, include_persisted: bool = False) -> 'OrderedDict[int, HistoryItem]': - """Return a slice of the History list + """Return a slice of the History list. :param span: string containing an index or a slice :param include_persisted: if True, then retrieve full results including from persisted history @@ -283,7 +283,7 @@ def span(self, span: str, include_persisted: bool = False) -> 'OrderedDict[int, return self._build_result_dictionary(start, end) def str_search(self, search: str, include_persisted: bool = False) -> 'OrderedDict[int, HistoryItem]': - """Find history items which contain a given string + """Find history items which contain a given string. :param search: the string to search for :param include_persisted: if True, then search full history including persisted history @@ -292,7 +292,7 @@ def str_search(self, search: str, include_persisted: bool = False) -> 'OrderedDi """ def isin(history_item: HistoryItem) -> bool: - """Filter function for string search of history""" + """Filter function for string search of history.""" sloppy = utils.norm_fold(search) inraw = sloppy in utils.norm_fold(history_item.raw) inexpanded = sloppy in utils.norm_fold(history_item.expanded) @@ -302,7 +302,7 @@ def isin(history_item: HistoryItem) -> bool: return self._build_result_dictionary(start, len(self), isin) def regex_search(self, regex: str, include_persisted: bool = False) -> 'OrderedDict[int, HistoryItem]': - """Find history items which match a given regular expression + """Find history items which match a given regular expression. :param regex: the regular expression to search for. :param include_persisted: if True, then search full history including persisted history @@ -315,14 +315,14 @@ def regex_search(self, regex: str, include_persisted: bool = False) -> 'OrderedD finder = re.compile(regex, re.DOTALL | re.MULTILINE) def isin(hi: HistoryItem) -> bool: - """Filter function for doing a regular expression search of history""" + """Filter function for doing a regular expression search of history.""" return bool(finder.search(hi.raw) or finder.search(hi.expanded)) start = 0 if include_persisted else self.session_start_index return self._build_result_dictionary(start, len(self), isin) def truncate(self, max_length: int) -> None: - """Truncate the length of the history, dropping the oldest items if necessary + """Truncate the length of the history, dropping the oldest items if necessary. :param max_length: the maximum length of the history, if negative, all history items will be deleted @@ -340,7 +340,7 @@ def _build_result_dictionary( ) -> 'OrderedDict[int, HistoryItem]': """Build history search results :param start: start index to search from - :param end: end index to stop searching (exclusive) + :param end: end index to stop searching (exclusive). """ results: OrderedDict[int, HistoryItem] = OrderedDict() for index in range(start, end): @@ -349,7 +349,7 @@ def _build_result_dictionary( return results def to_json(self) -> str: - """Utility method to convert this History into a JSON string for use in persistent history files""" + """Utility method to convert this History into a JSON string for use in persistent history files.""" json_dict = { History._history_version_field: History._history_version, History._history_items_field: [hi.to_dict() for hi in self], @@ -358,7 +358,7 @@ def to_json(self) -> str: @staticmethod def from_json(history_json: str) -> 'History': - """Utility method to restore History from a JSON string + """Utility method to restore History from a JSON string. :param history_json: history data as JSON string (generated using to_json()) :return: History object diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 1eaa5a082..ec60977fd 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -1,4 +1,4 @@ -"""Statement parsing classes for cmd2""" +"""Statement parsing classes for cmd2.""" import re import shlex @@ -36,7 +36,7 @@ def shlex_split(str_to_split: str) -> list[str]: class MacroArg: """Information used to replace or unescape arguments in a macro value when the macro is resolved Normal argument syntax: {5} - Escaped argument syntax: {{5}} + Escaped argument syntax: {{5}}. """ # The starting index of this argument in the macro value @@ -66,7 +66,7 @@ class MacroArg: @dataclass(frozen=True) class Macro: - """Defines a cmd2 macro""" + """Defines a cmd2 macro.""" # Name of the macro name: str @@ -174,7 +174,7 @@ def command_and_args(self) -> str: @property def post_command(self) -> str: - """A string containing any ending terminator, suffix, and redirection chars""" + """A string containing any ending terminator, suffix, and redirection chars.""" rtn = '' if self.terminator: rtn += self.terminator @@ -194,7 +194,7 @@ def post_command(self) -> str: @property def expanded_command_line(self) -> str: - """Concatenate [command_and_args][cmd2.Statement.command_and_args] and [post_command][cmd2.Statement.post_command]""" + """Concatenate [command_and_args][cmd2.Statement.command_and_args] and [post_command][cmd2.Statement.post_command].""" return self.command_and_args + self.post_command @property @@ -217,12 +217,12 @@ def argv(self) -> list[str]: return rtn def to_dict(self) -> dict[str, Any]: - """Utility method to convert this Statement into a dictionary for use in persistent JSON history files""" + """Utility method to convert this Statement into a dictionary for use in persistent JSON history files.""" return self.__dict__.copy() @staticmethod def from_dict(source_dict: dict[str, Any]) -> 'Statement': - """Utility method to restore a Statement from a dictionary + """Utility method to restore a Statement from a dictionary. :param source_dict: source data dictionary (generated using to_dict()) :return: Statement object @@ -621,7 +621,7 @@ def get_command_arg_list( return to_parse, to_parse.argv[1:] def _expand(self, line: str) -> str: - """Expand aliases and shortcuts""" + """Expand aliases and shortcuts.""" # Make a copy of aliases so we can keep track of what aliases have been resolved to avoid an infinite loop remaining_aliases = list(self.aliases.keys()) keep_expanding = bool(remaining_aliases) diff --git a/cmd2/plugin.py b/cmd2/plugin.py index e6f5ac7d1..92cb80bd1 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -1,4 +1,4 @@ -"""Classes for the cmd2 plugin system""" +"""Classes for the cmd2 plugin system.""" from dataclasses import ( dataclass, @@ -12,7 +12,7 @@ @dataclass class PostparsingData: - """Data class containing information passed to postparsing hook methods""" + """Data class containing information passed to postparsing hook methods.""" stop: bool statement: Statement @@ -20,14 +20,14 @@ class PostparsingData: @dataclass class PrecommandData: - """Data class containing information passed to precommand hook methods""" + """Data class containing information passed to precommand hook methods.""" statement: Statement @dataclass class PostcommandData: - """Data class containing information passed to postcommand hook methods""" + """Data class containing information passed to postcommand hook methods.""" stop: bool statement: Statement @@ -35,7 +35,7 @@ class PostcommandData: @dataclass class CommandFinalizationData: - """Data class containing information passed to command finalization hook methods""" + """Data class containing information passed to command finalization hook methods.""" stop: bool statement: Optional[Statement] diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index e4ca0cf49..10c28e42a 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -27,7 +27,7 @@ class CommandResult(NamedTuple): - """Encapsulates the results from a cmd2 app command + """Encapsulates the results from a cmd2 app command. :stdout: str - output captured from stdout while this command is executing :stderr: str - output captured from stderr while this command is executing @@ -68,7 +68,7 @@ class CommandResult(NamedTuple): data: Any = None def __bool__(self) -> bool: - """Returns True if the command succeeded, otherwise False""" + """Returns True if the command succeeded, otherwise False.""" # If data was set, then use it to determine success if self.data is not None: return bool(self.data) @@ -94,7 +94,7 @@ def __init__(self, cmd2_app: 'cmd2.Cmd', *, add_to_history: bool = True) -> None self.stop = False def __dir__(self) -> list[str]: - """Return a custom set of attribute names""" + """Return a custom set of attribute names.""" attributes: list[str] = [] attributes.insert(0, 'cmd_echo') return attributes @@ -104,7 +104,7 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul ex: app('help') :param command: command line being run :param echo: If provided, this temporarily overrides the value of self.cmd_echo while the - command runs. If True, output will be echoed to stdout/stderr. (Defaults to None) + command runs. If True, output will be echoed to stdout/stderr. (Defaults to None). """ if echo is None: diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 0ca2188f0..a00c74b14 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -1,4 +1,4 @@ -"""Imports the proper Readline for the platform and provides utility functions for it""" +"""Imports the proper Readline for the platform and provides utility functions for it.""" import sys from enum import ( @@ -34,7 +34,7 @@ class RlType(Enum): - """Readline library types we support""" + """Readline library types we support.""" GNU = 1 PYREADLINE = 2 @@ -70,7 +70,7 @@ def enable_win_vt100(handle: HANDLE) -> bool: """Enables VT100 character sequences in a Windows console This only works on Windows 10 and up :param handle: the handle on which to enable vt100 - :return: True if vt100 characters are enabled for the handle + :return: True if vt100 characters are enabled for the handle. """ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 @@ -108,7 +108,7 @@ def enable_win_vt100(handle: HANDLE) -> bool: def pyreadline_remove_history_item(pos: int) -> None: """An implementation of remove_history_item() for pyreadline3 - :param pos: The 0-based position in history to remove + :param pos: The 0-based position in history to remove. """ # Save of the current location of the history cursor saved_cursor = readline.rl.mode._history.history_cursor @@ -154,7 +154,7 @@ def pyreadline_remove_history_item(pos: int) -> None: def rl_force_redisplay() -> None: # pragma: no cover """Causes readline to display the prompt and input text wherever the cursor is and start reading input from this location. This is the proper way to restore the input line after - printing to the screen + printing to the screen. """ if not sys.stdout.isatty(): return @@ -173,7 +173,7 @@ def rl_force_redisplay() -> None: # pragma: no cover def rl_get_point() -> int: # pragma: no cover - """Returns the offset of the current cursor position in rl_line_buffer""" + """Returns the offset of the current cursor position in rl_line_buffer.""" if rl_type == RlType.GNU: return ctypes.c_int.in_dll(readline_lib, "rl_point").value @@ -184,7 +184,7 @@ def rl_get_point() -> int: # pragma: no cover def rl_get_prompt() -> str: # pragma: no cover - """Get Readline's prompt""" + """Get Readline's prompt.""" if rl_type == RlType.GNU: encoded_prompt = ctypes.c_char_p.in_dll(readline_lib, "rl_prompt").value if encoded_prompt is None: @@ -223,7 +223,7 @@ def rl_get_display_prompt() -> str: # pragma: no cover def rl_set_prompt(prompt: str) -> None: # pragma: no cover """Sets Readline's prompt - :param prompt: the new prompt value + :param prompt: the new prompt value. """ escaped_prompt = rl_escape_prompt(prompt) @@ -236,7 +236,7 @@ def rl_set_prompt(prompt: str) -> None: # pragma: no cover def rl_escape_prompt(prompt: str) -> str: - """Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes + """Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes. :param prompt: original prompt :return: prompt safe to pass to GNU Readline @@ -267,7 +267,7 @@ def rl_escape_prompt(prompt: str) -> str: def rl_unescape_prompt(prompt: str) -> str: - """Remove escape characters from a Readline prompt""" + """Remove escape characters from a Readline prompt.""" if rl_type == RlType.GNU: escape_start = "\x01" escape_end = "\x02" @@ -277,7 +277,7 @@ def rl_unescape_prompt(prompt: str) -> str: def rl_in_search_mode() -> bool: # pragma: no cover - """Check if readline is doing either an incremental (e.g. Ctrl-r) or non-incremental (e.g. Esc-p) search""" + """Check if readline is doing either an incremental (e.g. Ctrl-r) or non-incremental (e.g. Esc-p) search.""" if rl_type == RlType.GNU: # GNU Readline defines constants that we can use to determine if in search mode. # RL_STATE_ISEARCH 0x0000080 diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index f3bf18cbc..6f8d0d806 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -34,7 +34,7 @@ class HorizontalAlignment(Enum): - """Horizontal alignment of text in a cell""" + """Horizontal alignment of text in a cell.""" LEFT = 1 CENTER = 2 @@ -42,7 +42,7 @@ class HorizontalAlignment(Enum): class VerticalAlignment(Enum): - """Vertical alignment of text in a cell""" + """Vertical alignment of text in a cell.""" TOP = 1 MIDDLE = 2 @@ -50,7 +50,7 @@ class VerticalAlignment(Enum): class Column: - """Table column configuration""" + """Table column configuration.""" def __init__( self, @@ -65,7 +65,7 @@ def __init__( style_data_text: bool = True, max_data_lines: float = constants.INFINITY, ) -> None: - """Column initializer + """Column initializer. :param header: label for column header :param width: display width of column. This does not account for any borders or padding which @@ -123,7 +123,7 @@ class TableCreator: """ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None: - """TableCreator initializer + """TableCreator initializer. :param cols: column definitions for this table :param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab, @@ -147,7 +147,7 @@ def __init__(self, cols: Sequence[Column], *, tab_width: int = 4) -> None: @staticmethod def _wrap_long_word(word: str, max_width: int, max_lines: float, is_last_word: bool) -> tuple[str, int, int]: - """Used by _wrap_text() to wrap a long word over multiple lines + """Used by _wrap_text() to wrap a long word over multiple lines. :param word: word being wrapped :param max_width: maximum display width of a line @@ -224,7 +224,7 @@ def _wrap_text(text: str, max_width: int, max_lines: float) -> str: 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 + """Called from loop to add a word to the wrapped text. :param word_to_add: the word being added :param is_last_word: True if this is the last word of the total text being wrapped @@ -369,7 +369,7 @@ def add_word(word_to_add: str, is_last_word: bool) -> None: return wrapped_buf.getvalue() def _generate_cell_lines(self, cell_data: Any, is_header: bool, col: Column, fill_char: str) -> tuple[deque[str], int]: - """Generate the lines of a table cell + """Generate the lines of a table cell. :param cell_data: data to be included in cell :param is_header: True if writing a header cell, otherwise writing a data cell. This determines whether to @@ -413,7 +413,7 @@ def generate_row( inter_cell: str = (2 * SPACE), post_line: str = EMPTY, ) -> str: - """Generate a header or data table row + """Generate a header or data table row. :param row_data: data with an entry for each column in the row :param is_header: True if writing a header cell, otherwise writing a data cell. This determines whether to @@ -434,7 +434,7 @@ def generate_row( """ class Cell: - """Inner class which represents a table cell""" + """Inner class which represents a table cell.""" def __init__(self) -> None: # Data in this cell split into individual lines @@ -542,7 +542,7 @@ def __init__( header_bg: Optional[ansi.BgColor] = None, data_bg: Optional[ansi.BgColor] = None, ) -> None: - """SimpleTable initializer + """SimpleTable initializer. :param cols: column definitions for this table :param column_spacing: how many spaces to place between columns. Defaults to 2. @@ -582,7 +582,7 @@ def __init__( def apply_header_bg(self, value: Any) -> str: """If defined, apply the header background color to header text :param value: object whose text is to be colored - :return: formatted text + :return: formatted text. """ if self.header_bg is None: return str(value) @@ -591,7 +591,7 @@ def apply_header_bg(self, value: Any) -> str: def apply_data_bg(self, value: Any) -> str: """If defined, apply the data background color to data text :param value: object whose text is to be colored - :return: formatted data string + :return: formatted data string. """ if self.data_bg is None: return str(value) @@ -620,13 +620,13 @@ def base_width(cls, num_cols: int, *, column_spacing: int = 2) -> int: return ansi.style_aware_wcswidth(data_row) - data_width def total_width(self) -> int: - """Calculate the total display width of this table""" + """Calculate the total display width of this table.""" base_width = self.base_width(len(self.cols), column_spacing=self.column_spacing) data_width = sum(col.width for col in self.cols) return base_width + data_width def generate_header(self) -> str: - """Generate table header with an optional divider row""" + """Generate table header with an optional divider row.""" header_buf = io.StringIO() fill_char = self.apply_header_bg(SPACE) @@ -652,14 +652,14 @@ def generate_header(self) -> str: return header_buf.getvalue() def generate_divider(self) -> str: - """Generate divider row""" + """Generate divider row.""" if self.divider_char is None: return '' return utils.align_left('', fill_char=self.divider_char, width=self.total_width()) def generate_data_row(self, row_data: Sequence[Any]) -> str: - """Generate a data row + """Generate a data row. :param row_data: data with an entry for each column in the row :return: data row string @@ -682,7 +682,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: return self.generate_row(to_display, is_header=False, fill_char=fill_char, inter_cell=inter_cell) def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True, row_spacing: int = 1) -> str: - """Generate a table from a data set + """Generate a table from a data set. :param table_data: Data with an entry for each data row of the table. Each entry should have data for each column in the row. @@ -733,7 +733,7 @@ def __init__( header_bg: Optional[ansi.BgColor] = None, data_bg: Optional[ansi.BgColor] = None, ) -> None: - """BorderedTable initializer + """BorderedTable initializer. :param cols: column definitions for this table :param tab_width: all tabs will be replaced with this many spaces. If a row's fill_char is a tab, @@ -765,7 +765,7 @@ def __init__( def apply_border_color(self, value: Any) -> str: """If defined, apply the border foreground and background colors :param value: object whose text is to be colored - :return: formatted text + :return: formatted text. """ if self.border_fg is None and self.border_bg is None: return str(value) @@ -774,7 +774,7 @@ def apply_border_color(self, value: Any) -> str: def apply_header_bg(self, value: Any) -> str: """If defined, apply the header background color to header text :param value: object whose text is to be colored - :return: formatted text + :return: formatted text. """ if self.header_bg is None: return str(value) @@ -783,7 +783,7 @@ def apply_header_bg(self, value: Any) -> str: def apply_data_bg(self, value: Any) -> str: """If defined, apply the data background color to data text :param value: object whose text is to be colored - :return: formatted data string + :return: formatted data string. """ if self.data_bg is None: return str(value) @@ -812,13 +812,13 @@ def base_width(cls, num_cols: int, *, column_borders: bool = True, padding: int return ansi.style_aware_wcswidth(data_row) - data_width def total_width(self) -> int: - """Calculate the total display width of this table""" + """Calculate the total display width of this table.""" base_width = self.base_width(len(self.cols), column_borders=self.column_borders, padding=self.padding) data_width = sum(col.width for col in self.cols) return base_width + data_width def generate_table_top_border(self) -> str: - """Generate a border which appears at the top of the header and data section""" + """Generate a border which appears at the top of the header and data section.""" fill_char = '═' pre_line = '╔' + self.padding * '═' @@ -840,7 +840,7 @@ def generate_table_top_border(self) -> str: ) def generate_header_bottom_border(self) -> str: - """Generate a border which appears at the bottom of the header""" + """Generate a border which appears at the bottom of the header.""" fill_char = '═' pre_line = '╠' + self.padding * '═' @@ -862,7 +862,7 @@ def generate_header_bottom_border(self) -> str: ) def generate_row_bottom_border(self) -> str: - """Generate a border which appears at the bottom of rows""" + """Generate a border which appears at the bottom of rows.""" fill_char = '─' pre_line = '╟' + self.padding * '─' @@ -884,7 +884,7 @@ def generate_row_bottom_border(self) -> str: ) def generate_table_bottom_border(self) -> str: - """Generate a border which appears at the bottom of the table""" + """Generate a border which appears at the bottom of the table.""" fill_char = '═' pre_line = '╚' + self.padding * '═' @@ -906,7 +906,7 @@ def generate_table_bottom_border(self) -> str: ) def generate_header(self) -> str: - """Generate table header""" + """Generate table header.""" fill_char = self.apply_header_bg(SPACE) pre_line = self.apply_border_color('║') + self.apply_header_bg(self.padding * SPACE) @@ -941,7 +941,7 @@ def generate_header(self) -> str: return header_buf.getvalue() def generate_data_row(self, row_data: Sequence[Any]) -> str: - """Generate a data row + """Generate a data row. :param row_data: data with an entry for each column in the row :return: data row string @@ -974,7 +974,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: ) def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True) -> str: - """Generate a table from a data set + """Generate a table from a data set. :param table_data: Data with an entry for each data row of the table. Each entry should have data for each column in the row. @@ -1027,7 +1027,7 @@ def __init__( odd_bg: Optional[ansi.BgColor] = None, even_bg: Optional[ansi.BgColor] = ansi.Bg.DARK_GRAY, ) -> None: - """AlternatingTable initializer + """AlternatingTable initializer. Note: Specify background colors using subclasses of BgColor (e.g. Bg, EightBitBg, RgbBg) @@ -1062,7 +1062,7 @@ def __init__( def apply_data_bg(self, value: Any) -> str: """Apply background color to data text based on what row is being generated and whether a color has been defined :param value: object whose text is to be colored - :return: formatted data string + :return: formatted data string. """ if self.row_num % 2 == 0 and self.even_bg is not None: return ansi.style(value, bg=self.even_bg) @@ -1071,7 +1071,7 @@ def apply_data_bg(self, value: Any) -> str: return str(value) def generate_data_row(self, row_data: Sequence[Any]) -> str: - """Generate a data row + """Generate a data row. :param row_data: data with an entry for each column in the row :return: data row string @@ -1081,7 +1081,7 @@ def generate_data_row(self, row_data: Sequence[Any]) -> str: return row def generate_table(self, table_data: Sequence[Sequence[Any]], *, include_header: bool = True) -> str: - """Generate a table from a data set + """Generate a table from a data set. :param table_data: Data with an entry for each data row of the table. Each entry should have data for each column in the row. diff --git a/cmd2/utils.py b/cmd2/utils.py index 0a12b53be..0d61c8fc9 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -1,4 +1,4 @@ -"""Shared utility functions""" +"""Shared utility functions.""" import argparse import collections @@ -48,7 +48,7 @@ def is_quoted(arg: str) -> bool: - """Checks if a string is quoted + """Checks if a string is quoted. :param arg: the string being checked for quotes :return: True if a string is quoted @@ -57,7 +57,7 @@ def is_quoted(arg: str) -> bool: def quote_string(arg: str) -> str: - """Quote a string""" + """Quote a string.""" if '"' in arg: quote = "'" else: @@ -67,7 +67,7 @@ def quote_string(arg: str) -> str: def quote_string_if_needed(arg: str) -> str: - """Quote a string if it contains spaces and isn't already quoted""" + """Quote a string if it contains spaces and isn't already quoted.""" if is_quoted(arg) or ' ' not in arg: return arg @@ -109,7 +109,7 @@ def to_bool(val: Any) -> bool: class Settable: - """Used to configure an attribute to be settable via the set command in the CLI""" + """Used to configure an attribute to be settable via the set command in the CLI.""" def __init__( self, @@ -124,7 +124,7 @@ def __init__( choices_provider: Optional[ChoicesProviderFunc] = None, completer: Optional[CompleterFunc] = None, ) -> None: - """Settable Initializer + """Settable Initializer. :param name: name of the instance attribute being made settable :param val_type: callable used to cast the string value from the command line into its proper type and @@ -153,7 +153,7 @@ def __init__( if val_type is bool: def get_bool_choices(_) -> list[str]: # type: ignore[no-untyped-def] - """Used to tab complete lowercase boolean values""" + """Used to tab complete lowercase boolean values.""" return ['true', 'false'] val_type = to_bool @@ -262,7 +262,7 @@ def alphabetical_sort(list_to_sort: Iterable[str]) -> list[str]: def try_int_or_force_to_lower_case(input_str: str) -> Union[int, str]: """Tries to convert the passed-in string to an integer. If that fails, it converts it to lower case using norm_fold. :param input_str: string to convert - :return: the string as an integer or a lower case version of the string + :return: the string as an integer or a lower case version of the string. """ try: return int(input_str) @@ -296,7 +296,7 @@ def natural_sort(list_to_sort: Iterable[str]) -> list[str]: def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None: - """Quote specific tokens in a list + """Quote specific tokens in a list. :param tokens: token list being edited :param tokens_to_quote: the tokens, which if present in tokens, to quote @@ -307,7 +307,7 @@ def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> None: - """Unquote specific tokens in a list + """Unquote specific tokens in a list. :param tokens: token list being edited :param tokens_to_unquote: the tokens, which if present in tokens, to unquote @@ -320,7 +320,7 @@ def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> def expand_user(token: str) -> str: """Wrap os.expanduser() to support expanding ~ in quoted strings - :param token: the string to expand + :param token: the string to expand. """ if token: if is_quoted(token): @@ -340,7 +340,7 @@ def expand_user(token: str) -> str: def expand_user_in_tokens(tokens: list[str]) -> None: """Call expand_user() on all tokens in a list of strings - :param tokens: tokens to expand + :param tokens: tokens to expand. """ for index, _ in enumerate(tokens): tokens[index] = expand_user(tokens[index]) @@ -349,7 +349,7 @@ def expand_user_in_tokens(tokens: list[str]) -> None: def find_editor() -> Optional[str]: """Used to set cmd2.Cmd.DEFAULT_EDITOR. If EDITOR env variable is set, that will be used. Otherwise the function will look for a known editor in directories specified by PATH env variable. - :return: Default editor or None + :return: Default editor or None. """ editor = os.environ.get('EDITOR') if not editor: @@ -409,7 +409,7 @@ def files_from_glob_patterns(patterns: list[str], access: int = os.F_OK) -> list def get_exes_in_path(starts_with: str) -> list[str]: - """Returns names of executables in a user's path + """Returns names of executables in a user's path. :param starts_with: what the exes should start with. leave blank for all exes in path. :return: a list of matching exe names @@ -454,7 +454,7 @@ def __init__( encoding: str = 'utf-8', errors: str = 'replace', ) -> None: - """StdSim Initializer + """StdSim Initializer. :param inner_stream: the wrapped stream. Should be a TextIO or StdSim instance. :param echo: if True, then all input will be echoed to inner_stream @@ -469,7 +469,7 @@ def __init__( self.buffer = ByteBuf(self) def write(self, s: str) -> None: - """Add str to internal bytes buffer and if echo is True, echo contents to inner stream + """Add str to internal bytes buffer and if echo is True, echo contents to inner stream. :param s: String to write to the stream """ @@ -482,15 +482,15 @@ def write(self, s: str) -> None: self.inner_stream.write(s) def getvalue(self) -> str: - """Get the internal contents as a str""" + """Get the internal contents as a str.""" return self.buffer.byte_buf.decode(encoding=self.encoding, errors=self.errors) def getbytes(self) -> bytes: - """Get the internal contents as bytes""" + """Get the internal contents as bytes.""" return bytes(self.buffer.byte_buf) def read(self, size: Optional[int] = -1) -> str: - """Read from the internal contents as a str and then clear them out + """Read from the internal contents as a str and then clear them out. :param size: Number of bytes to read from the stream """ @@ -504,13 +504,13 @@ def read(self, size: Optional[int] = -1) -> str: return result def readbytes(self) -> bytes: - """Read from the internal contents as bytes and then clear them out""" + """Read from the internal contents as bytes and then clear them out.""" result = self.getbytes() self.clear() return result def clear(self) -> None: - """Clear the internal contents""" + """Clear the internal contents.""" self.buffer.byte_buf.clear() def isatty(self) -> bool: @@ -536,7 +536,7 @@ def __getattr__(self, item: str) -> Any: class ByteBuf: - """Used by StdSim to write binary data and stores the actual bytes written""" + """Used by StdSim to write binary data and stores the actual bytes written.""" # Used to know when to flush the StdSim NEWLINES = [b'\n', b'\r'] @@ -572,7 +572,7 @@ def __init__(self, proc: PopenTextIO, stdout: Union[StdSim, TextIO], stderr: Uni """ProcReader initializer :param proc: the Popen process being read from :param stdout: the stream to write captured stdout - :param stderr: the stream to write captured stderr + :param stderr: the stream to write captured stderr. """ self._proc = proc self._stdout = stdout @@ -589,7 +589,7 @@ def __init__(self, proc: PopenTextIO, stdout: Union[StdSim, TextIO], stderr: Uni self._err_thread.start() def send_sigint(self) -> None: - """Send a SIGINT to the process similar to if +C were pressed""" + """Send a SIGINT to the process similar to if +C were pressed.""" import signal if sys.platform.startswith('win'): @@ -606,11 +606,11 @@ def send_sigint(self) -> None: return def terminate(self) -> None: - """Terminate the process""" + """Terminate the process.""" self._proc.terminate() def wait(self) -> None: - """Wait for the process to finish""" + """Wait for the process to finish.""" if self._out_thread.is_alive(): self._out_thread.join() if self._err_thread.is_alive(): @@ -650,7 +650,7 @@ def _reader_thread_func(self, read_stdout: bool) -> None: def _write_bytes(stream: Union[StdSim, TextIO], to_write: Union[bytes, str]) -> None: """Write bytes to a stream :param stream: the stream being written to - :param to_write: the bytes being written + :param to_write: the bytes being written. """ if isinstance(to_write, str): to_write = to_write.encode() @@ -688,7 +688,7 @@ def __exit__(self, *args: object) -> None: class RedirectionSavedState: - """Created by each command to store information required to restore state after redirection""" + """Created by each command to store information required to restore state after redirection.""" def __init__( self, @@ -701,7 +701,7 @@ def __init__( :param self_stdout: saved value of Cmd.stdout :param sys_stdout: saved value of sys.stdout :param pipe_proc_reader: saved value of Cmd._cur_pipe_proc_reader - :param saved_redirecting: saved value of Cmd._redirecting + :param saved_redirecting: saved value of Cmd._redirecting. """ # Tells if command is redirecting self.redirecting = False @@ -731,7 +731,7 @@ def _remove_overridden_styles(styles_to_parse: list[str]) -> list[str]: ) class StyleState: - """Keeps track of what text styles are enabled""" + """Keeps track of what text styles are enabled.""" def __init__(self) -> None: # Contains styles still in effect, keyed by their index in styles_to_parse @@ -796,7 +796,7 @@ def __init__(self) -> None: class TextAlignment(Enum): - """Horizontal text alignment""" + """Horizontal text alignment.""" LEFT = 1 CENTER = 2 @@ -1080,7 +1080,7 @@ def truncate_line(line: str, max_width: int, *, tab_width: int = 4) -> str: def get_styles_dict(text: str) -> dict[int, str]: - """Return an OrderedDict containing all ANSI style sequences found in a string + """Return an OrderedDict containing all ANSI style sequences found in a string. The structure of the dictionary is: key: index where sequences begins @@ -1164,7 +1164,7 @@ def get_defining_class(meth: Callable[..., Any]) -> Optional[type[Any]]: class CompletionMode(Enum): - """Enum for what type of tab completion to perform in cmd2.Cmd.read_input()""" + """Enum for what type of tab completion to perform in cmd2.Cmd.read_input().""" # Tab completion will be disabled during read_input() call # Use of custom up-arrow history supported @@ -1182,10 +1182,10 @@ class CompletionMode(Enum): class CustomCompletionSettings: - """Used by cmd2.Cmd.complete() to tab complete strings other than command arguments""" + """Used by cmd2.Cmd.complete() to tab complete strings other than command arguments.""" def __init__(self, parser: argparse.ArgumentParser, *, preserve_quotes: bool = False) -> None: - """Initializer + """Initializer. :param parser: arg parser defining format of string being tab completed :param preserve_quotes: if True, then quoted tokens will keep their quotes when processed by @@ -1199,7 +1199,7 @@ def __init__(self, parser: argparse.ArgumentParser, *, preserve_quotes: bool = F def strip_doc_annotations(doc: str) -> str: - """Strip annotations from a docstring leaving only the text description + """Strip annotations from a docstring leaving only the text description. :param doc: documentation string """ @@ -1235,7 +1235,7 @@ def similarity_function(s1: str, s2: str) -> float: def suggest_similar( requested_command: str, options: Iterable[str], similarity_function_to_use: Optional[Callable[[str, str], float]] = None ) -> Optional[str]: - """Given a requested command and an iterable of possible options returns the most similar (if any is similar) + """Given a requested command and an iterable of possible options returns the most similar (if any is similar). :param requested_command: The command entered by the user :param options: The list of available commands to search for the most similar diff --git a/examples/alias_startup.py b/examples/alias_startup.py index cac5a265f..f6e401a0c 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """A simple example demonstrating the following: 1) How to add custom command aliases using the alias command -2) How to run an initialization script at startup +2) How to run an initialization script at startup. """ import os diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index 7ce86b141..5fe262d4c 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""An example demonstrating how use one of cmd2's argument parsing decorators""" +"""An example demonstrating how use one of cmd2's argument parsing decorators.""" import argparse import os @@ -20,7 +20,7 @@ def __init__(self) -> None: @cmd2.with_argparser(fsize_parser) def do_fsize(self, args: argparse.Namespace) -> None: - """Obtain the size of a file""" + """Obtain the size of a file.""" expanded_path = os.path.expanduser(args.file_path) try: @@ -48,7 +48,7 @@ def do_fsize(self, args: argparse.Namespace) -> None: @cmd2.with_argparser(pow_parser) def do_pow(self, args: argparse.Namespace) -> None: - """Raise an integer to a small integer exponent, either positive or negative + """Raise an integer to a small integer exponent, either positive or negative. :param args: argparse arguments """ diff --git a/examples/arg_print.py b/examples/arg_print.py index 1ca73b753..506e92250 100755 --- a/examples/arg_print.py +++ b/examples/arg_print.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """A simple example demonstrating the following: 1) How arguments and options get parsed and passed to commands - 2) How to change what syntax gets parsed as a comment and stripped from the arguments + 2) How to change what syntax gets parsed as a comment and stripped from the arguments. This is intended to serve as a live demonstration so that developers can experiment with and understand how command and argument parsing work. diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index 7db68184e..43cad367b 100755 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -22,7 +22,7 @@ def __init__(self, *args, **kwargs) -> None: self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] def choices_provider(self) -> list[str]: - """A choices provider is useful when the choice list is based on instance data of your application""" + """A choices provider is useful when the choice list is based on instance data of your application.""" return self.sport_item_strs def choices_completion_error(self) -> list[str]: @@ -97,7 +97,7 @@ def choices_arg_tokens(self, arg_tokens: dict[str, list[str]]) -> list[str]: @with_argparser(example_parser) def do_example(self, _: argparse.Namespace) -> None: - """The example command""" + """The example command.""" self.poutput("I do nothing") diff --git a/examples/async_printing.py b/examples/async_printing.py index 2b9af76f7..5399c2f70 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -1,6 +1,6 @@ #!/usr/bin/env python """A simple example demonstrating an application that asynchronously prints alerts, updates the prompt -and changes the window title +and changes the window title. """ import random @@ -28,10 +28,10 @@ class AlerterApp(cmd2.Cmd): - """An app that shows off async_alert() and async_update_prompt()""" + """An app that shows off async_alert() and async_update_prompt().""" def __init__(self, *args, **kwargs) -> None: - """Initializer""" + """Initializer.""" super().__init__(*args, **kwargs) self.prompt = "(APR)> " @@ -47,7 +47,7 @@ def __init__(self, *args, **kwargs) -> None: self.register_postloop_hook(self._postloop_hook) def _preloop_hook(self) -> None: - """Start the alerter thread""" + """Start the alerter thread.""" # This runs after cmdloop() acquires self.terminal_lock, which will be locked until the prompt appears. # Therefore this is the best place to start the alerter thread since there is no risk of it alerting # before the prompt is displayed. You can also start it via a command if its not something that should @@ -58,7 +58,7 @@ def _preloop_hook(self) -> None: self._alerter_thread.start() def _postloop_hook(self) -> None: - """Stops the alerter thread""" + """Stops the alerter thread.""" # After this function returns, cmdloop() releases self.terminal_lock which could make the alerter # thread think the prompt is on screen. Therefore this is the best place to stop the alerter thread. # You can also stop it via a command. See do_stop_alerts(). @@ -67,7 +67,7 @@ def _postloop_hook(self) -> None: self._alerter_thread.join() def do_start_alerts(self, _) -> None: - """Starts the alerter thread""" + """Starts the alerter thread.""" if self._alerter_thread.is_alive(): print("The alert thread is already started") else: @@ -76,7 +76,7 @@ def do_start_alerts(self, _) -> None: self._alerter_thread.start() def do_stop_alerts(self, _) -> None: - """Stops the alerter thread""" + """Stops the alerter thread.""" self._stop_event.set() if self._alerter_thread.is_alive(): self._alerter_thread.join() @@ -85,7 +85,7 @@ def do_stop_alerts(self, _) -> None: def _get_alerts(self) -> list[str]: """Reports alerts - :return: the list of alerts + :return: the list of alerts. """ cur_time = time.monotonic() if cur_time < self._next_alert_time: @@ -113,7 +113,7 @@ def _get_alerts(self) -> list[str]: def _generate_alert_str(self) -> str: """Combines alerts into one string that can be printed to the terminal - :return: the alert string + :return: the alert string. """ alert_str = '' alerts = self._get_alerts() @@ -135,7 +135,7 @@ def _generate_alert_str(self) -> str: def _generate_colored_prompt(self) -> str: """Randomly generates a colored prompt - :return: the new prompt + :return: the new prompt. """ rand_num = random.randint(1, 20) @@ -155,7 +155,7 @@ def _generate_colored_prompt(self) -> str: return style(self.visible_prompt, fg=status_color) def _alerter_thread_func(self) -> None: - """Prints alerts and updates the prompt any time the prompt is showing""" + """Prints alerts and updates the prompt any time the prompt is showing.""" self._alert_count = 0 self._next_alert_time = 0 diff --git a/examples/basic.py b/examples/basic.py index e088f3820..20ebe20a5 100755 --- a/examples/basic.py +++ b/examples/basic.py @@ -5,7 +5,7 @@ 3) Persistent history 4) How to run an initialization script at startup 5) How to add custom command aliases using the alias command -6) Shell-like capabilities +6) Shell-like capabilities. """ import cmd2 @@ -37,12 +37,12 @@ def __init__(self) -> None: @cmd2.with_category(CUSTOM_CATEGORY) def do_intro(self, _) -> None: - """Display the intro banner""" + """Display the intro banner.""" self.poutput(self.intro) @cmd2.with_category(CUSTOM_CATEGORY) def do_echo(self, arg) -> None: - """Example of a multiline command""" + """Example of a multiline command.""" self.poutput(arg) diff --git a/examples/basic_completion.py b/examples/basic_completion.py index 2862440ea..e1391540f 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -4,7 +4,7 @@ - CompletionError exceptions - delimiter_complete() - flag_based_complete() (see note below) -- index_based_complete() (see note below) +- index_based_complete() (see note below). flag_based_complete() and index_based_complete() are basic methods and should only be used if you are not familiar with argparse. The recommended approach for tab completing positional tokens and flags is to use @@ -37,12 +37,12 @@ def do_flag_based(self, statement: cmd2.Statement) -> None: """Tab completes arguments based on a preceding flag using flag_based_complete -f, --food [completes food items] -s, --sport [completes sports] - -p, --path [completes local file system paths] + -p, --path [completes local file system paths]. """ self.poutput(f"Args: {statement.args}") def complete_flag_based(self, text, line, begidx, endidx) -> list[str]: - """Completion function for do_flag_based""" + """Completion function for do_flag_based.""" flag_dict = { # Tab complete food items after -f and --food flags in command line '-f': food_item_strs, @@ -58,11 +58,11 @@ def complete_flag_based(self, text, line, begidx, endidx) -> list[str]: return self.flag_based_complete(text, line, begidx, endidx, flag_dict=flag_dict) def do_index_based(self, statement: cmd2.Statement) -> None: - """Tab completes first 3 arguments using index_based_complete""" + """Tab completes first 3 arguments using index_based_complete.""" self.poutput(f"Args: {statement.args}") def complete_index_based(self, text, line, begidx, endidx) -> list[str]: - """Completion function for do_index_based""" + """Completion function for do_index_based.""" index_dict = { 1: food_item_strs, # Tab complete food items at index 1 in command line 2: sport_item_strs, # Tab complete sport items at index 2 in command line @@ -72,14 +72,14 @@ def complete_index_based(self, text, line, begidx, endidx) -> list[str]: return self.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) def do_delimiter_complete(self, statement: cmd2.Statement) -> None: - """Tab completes files from a list using delimiter_complete""" + """Tab completes files from a list using delimiter_complete.""" self.poutput(f"Args: {statement.args}") # Use a partialmethod to set arguments to delimiter_complete complete_delimiter_complete = functools.partialmethod(cmd2.Cmd.delimiter_complete, match_against=file_strs, delimiter='/') def do_raise_error(self, statement: cmd2.Statement) -> None: - """Demonstrates effect of raising CompletionError""" + """Demonstrates effect of raising CompletionError.""" self.poutput(f"Args: {statement.args}") def complete_raise_error(self, text, line, begidx, endidx) -> list[str]: diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index b05c02a7c..2a9c7033d 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -82,7 +82,7 @@ def do_mumble(self, args) -> None: def main(argv=None): - """Run when invoked from the operating system shell""" + """Run when invoked from the operating system shell.""" parser = cmd2.Cmd2ArgumentParser(description='Commands as arguments') command_help = 'optional command to run, if no command given, enter an interactive shell' parser.add_argument('command', nargs='?', help=command_help) diff --git a/examples/colors.py b/examples/colors.py index 0d20be8c9..fad3c9586 100755 --- a/examples/colors.py +++ b/examples/colors.py @@ -78,7 +78,7 @@ def do_speak(self, args) -> None: self.poutput(output_str) def do_timetravel(self, _) -> None: - """A command which always generates an error message, to demonstrate custom error colors""" + """A command which always generates an error message, to demonstrate custom error colors.""" self.perror('Mr. Fusion failed to start. Could not energize flux capacitor.') diff --git a/examples/custom_parser.py b/examples/custom_parser.py index ba1237531..c814a2996 100644 --- a/examples/custom_parser.py +++ b/examples/custom_parser.py @@ -1,4 +1,4 @@ -"""Defines the CustomParser used with override_parser.py example""" +"""Defines the CustomParser used with override_parser.py example.""" import sys @@ -11,13 +11,13 @@ # First define the parser class CustomParser(Cmd2ArgumentParser): - """Overrides error class""" + """Overrides error class.""" def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) def error(self, message: str) -> None: - """Custom override that applies custom formatting to the error message""" + """Custom override that applies custom formatting to the error message.""" lines = message.split('\n') linum = 0 formatted_message = '' diff --git a/examples/decorator_example.py b/examples/decorator_example.py index bb9d9306f..0629d422f 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -63,7 +63,7 @@ def do_speak(self, args: argparse.Namespace) -> None: @cmd2.with_argparser(tag_parser) def do_tag(self, args: argparse.Namespace) -> None: - """Create an html tag""" + """Create an html tag.""" # The Namespace always includes the Statement object created when parsing the command line statement = args.cmd2_statement.get() @@ -73,7 +73,7 @@ def do_tag(self, args: argparse.Namespace) -> None: @cmd2.with_argument_list def do_tagg(self, arglist: list[str]) -> None: - """Version of creating an html tag using arglist instead of argparser""" + """Version of creating an html tag using arglist instead of argparser.""" if len(arglist) >= 2: tag = arglist[0] content = arglist[1:] diff --git a/examples/default_categories.py b/examples/default_categories.py index 246e4bbd3..fd681a3c3 100755 --- a/examples/default_categories.py +++ b/examples/default_categories.py @@ -10,11 +10,11 @@ @with_default_category('Default Category') class MyBaseCommandSet(CommandSet): - """Defines a default category for all sub-class CommandSets""" + """Defines a default category for all sub-class CommandSets.""" class ChildInheritsParentCategories(MyBaseCommandSet): - """This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category'""" + """This subclass doesn't declare any categories so all commands here are also categorized under 'Default Category'.""" def do_hello(self, _: cmd2.Statement) -> None: self._cmd.poutput('Hello') @@ -26,7 +26,7 @@ def do_world(self, _: cmd2.Statement) -> None: @with_default_category('Non-Heritable Category', heritable=False) class ChildOverridesParentCategoriesNonHeritable(MyBaseCommandSet): """This subclass overrides the 'Default Category' from the parent, but in a non-heritable fashion. Sub-classes of this - CommandSet will not inherit this category and will, instead, inherit 'Default Category' + CommandSet will not inherit this category and will, instead, inherit 'Default Category'. """ def do_goodbye(self, _: cmd2.Statement) -> None: @@ -54,7 +54,7 @@ def do_bonjour(self, _: cmd2.Statement) -> None: class GrandchildInheritsHeritable(ChildOverridesParentCategories): """This subclass's parent declares a default category that overrides its parent. As a result, commands in this - CommandSet will be categorized under 'Heritable Category' + CommandSet will be categorized under 'Heritable Category'. """ def do_monde(self, _: cmd2.Statement) -> None: @@ -62,7 +62,7 @@ def do_monde(self, _: cmd2.Statement) -> None: class ExampleApp(cmd2.Cmd): - """Example to demonstrate heritable default categories""" + """Example to demonstrate heritable default categories.""" def __init__(self) -> None: super().__init__() diff --git a/examples/environment.py b/examples/environment.py index cf2775eb7..1983b3d21 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -"""A sample application for cmd2 demonstrating customized environment parameters""" +"""A sample application for cmd2 demonstrating customized environment parameters.""" import cmd2 diff --git a/examples/help_categories.py b/examples/help_categories.py index 6c44f82f7..923c16468 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -36,7 +36,7 @@ def __init__(self) -> None: super().__init__() def do_connect(self, _) -> None: - """Connect command""" + """Connect command.""" self.poutput('Connect') # Tag the above command functions under the category Connecting @@ -44,15 +44,15 @@ def do_connect(self, _) -> None: @cmd2.with_category(CMD_CAT_CONNECTING) def do_which(self, _) -> None: - """Which command""" + """Which command.""" self.poutput('Which') def do_list(self, _) -> None: - """List command""" + """List command.""" self.poutput('List') def do_deploy(self, _) -> None: - """Deploy command""" + """Deploy command.""" self.poutput('Deploy') start_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER( @@ -63,15 +63,15 @@ def do_deploy(self, _) -> None: @my_decorator @cmd2.with_argparser(start_parser) def do_start(self, _) -> None: - """Start command""" + """Start command.""" self.poutput('Start') def do_sessions(self, _) -> None: - """Sessions command""" + """Sessions command.""" self.poutput('Sessions') def do_redeploy(self, _) -> None: - """Redeploy command""" + """Redeploy command.""" self.poutput('Redeploy') restart_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER( @@ -83,23 +83,23 @@ def do_redeploy(self, _) -> None: @cmd2.with_category(CMD_CAT_APP_MGMT) @my_decorator def do_restart(self, _) -> None: - """Restart command""" + """Restart command.""" self.poutput('Restart') def do_expire(self, _) -> None: - """Expire command""" + """Expire command.""" self.poutput('Expire') def do_undeploy(self, _) -> None: - """Undeploy command""" + """Undeploy command.""" self.poutput('Undeploy') def do_stop(self, _) -> None: - """Stop command""" + """Stop command.""" self.poutput('Stop') def do_findleakers(self, _) -> None: - """Find Leakers command""" + """Find Leakers command.""" self.poutput('Find Leakers') # Tag the above command functions under the category Application Management @@ -109,33 +109,33 @@ def do_findleakers(self, _) -> None: ) def do_resources(self, _) -> None: - """Resources command""" + """Resources command.""" self.poutput('Resources') def do_status(self, _) -> None: - """Status command""" + """Status command.""" self.poutput('Status') def do_serverinfo(self, _) -> None: - """Server Info command""" + """Server Info command.""" self.poutput('Server Info') def do_thread_dump(self, _) -> None: - """Thread Dump command""" + """Thread Dump command.""" self.poutput('Thread Dump') def do_sslconnectorciphers(self, _) -> None: """SSL Connector Ciphers command is an example of a command that contains multiple lines of help information for the user. Each line of help in a contiguous set of lines will be printed and aligned in the verbose output - provided with 'help --verbose' + provided with 'help --verbose'. This is after a blank line and won't de displayed in the verbose help """ self.poutput('SSL Connector Ciphers') def do_vminfo(self, _) -> None: - """VM Info command""" + """VM Info command.""" self.poutput('VM Info') # Tag the above command functions under the category Server Information @@ -149,23 +149,23 @@ def do_vminfo(self, _) -> None: # The following command functions don't have the HELP_CATEGORY attribute set # and show up in the 'Other' group def do_config(self, _) -> None: - """Config command""" + """Config command.""" self.poutput('Config') def do_version(self, _) -> None: - """Version command""" + """Version command.""" self.poutput(cmd2.__version__) @cmd2.with_category("Command Management") def do_disable_commands(self, _) -> None: - """Disable the Application Management commands""" + """Disable the Application Management commands.""" message_to_print = f"{COMMAND_NAME} is not available while {self.CMD_CAT_APP_MGMT} commands are disabled" self.disable_category(self.CMD_CAT_APP_MGMT, message_to_print) self.poutput("The Application Management commands have been disabled") @cmd2.with_category("Command Management") def do_enable_commands(self, _) -> None: - """Enable the Application Management commands""" + """Enable the Application Management commands.""" self.enable_category(self.CMD_CAT_APP_MGMT) self.poutput("The Application Management commands have been enabled") diff --git a/examples/hooks.py b/examples/hooks.py index c2c3e2b0d..d14eb66e3 100755 --- a/examples/hooks.py +++ b/examples/hooks.py @@ -80,7 +80,7 @@ def downcase_hook(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.Postpa return data def abbrev_hook(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: - """Accept unique abbreviated commands""" + """Accept unique abbreviated commands.""" func = self.cmd_func(data.statement.command) if func is None: # check if the entered command might be an abbreviation @@ -91,7 +91,7 @@ def abbrev_hook(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.Postpars return data def proof_hook(self, data: cmd2.plugin.PostcommandData) -> cmd2.plugin.PostcommandData: - """Update the shell prompt with the new raw statement after postparsing hooks are finished""" + """Update the shell prompt with the new raw statement after postparsing hooks are finished.""" if self.debug: self.prompt = f'({data.statement.raw})' return data diff --git a/examples/initialization.py b/examples/initialization.py index 3267a3258..22de3ff20 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -9,7 +9,7 @@ 7) Allowing access to your application in py and ipy 8) Displaying an intro banner upon starting your application 9) Using a custom prompt -10) How to make custom attributes settable at runtime +10) How to make custom attributes settable at runtime. """ import cmd2 @@ -57,12 +57,12 @@ def __init__(self) -> None: @cmd2.with_category(CUSTOM_CATEGORY) def do_intro(self, _) -> None: - """Display the intro banner""" + """Display the intro banner.""" self.poutput(self.intro) @cmd2.with_category(CUSTOM_CATEGORY) def do_echo(self, arg) -> None: - """Example of a multiline command""" + """Example of a multiline command.""" fg_color = Fg[self.foreground_color.upper()] self.poutput(style(arg, fg=fg_color)) diff --git a/examples/migrating.py b/examples/migrating.py index 42276bf9a..e1af0f76f 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -14,7 +14,7 @@ class CmdLineApp(cmd.Cmd): MUMBLE_LAST = ['right?'] def do_exit(self, line) -> bool: - """Exit the application""" + """Exit the application.""" return True do_EOF = do_exit diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index d4515e3c1..d90e56ae0 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -1,4 +1,4 @@ -"""A simple example demonstrating a loadable command set""" +"""A simple example demonstrating a loadable command set.""" from cmd2 import ( CommandSet, @@ -28,12 +28,12 @@ def do_flag_based(self, statement: Statement) -> None: """Tab completes arguments based on a preceding flag using flag_based_complete -f, --food [completes food items] -s, --sport [completes sports] - -p, --path [completes local file system paths] + -p, --path [completes local file system paths]. """ self._cmd.poutput(f"Args: {statement.args}") def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: - """Completion function for do_flag_based""" + """Completion function for do_flag_based.""" flag_dict = { # Tab complete food items after -f and --food flags in command line '-f': self.food_item_strs, @@ -49,11 +49,11 @@ def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> return self._cmd.flag_based_complete(text, line, begidx, endidx, flag_dict=flag_dict) def do_index_based(self, statement: Statement) -> None: - """Tab completes first 3 arguments using index_based_complete""" + """Tab completes first 3 arguments using index_based_complete.""" self._cmd.poutput(f"Args: {statement.args}") def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: - """Completion function for do_index_based""" + """Completion function for do_index_based.""" index_dict = { 1: self.food_item_strs, # Tab complete food items at index 1 in command line 2: self.sport_item_strs, # Tab complete sport items at index 2 in command line @@ -63,14 +63,14 @@ def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) - return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) def do_delimiter_complete(self, statement: Statement) -> None: - """Tab completes files from a list using delimiter_complete""" + """Tab completes files from a list using delimiter_complete.""" self._cmd.poutput(f"Args: {statement.args}") def complete_delimiter_complete(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: return self._cmd.delimiter_complete(text, line, begidx, endidx, match_against=self.file_strs, delimiter='/') def do_raise_error(self, statement: Statement) -> None: - """Demonstrates effect of raising CompletionError""" + """Demonstrates effect of raising CompletionError.""" self._cmd.poutput(f"Args: {statement.args}") def complete_raise_error(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: diff --git a/examples/modular_commands/commandset_complex.py b/examples/modular_commands/commandset_complex.py index a33800440..8a5b86c6e 100644 --- a/examples/modular_commands/commandset_complex.py +++ b/examples/modular_commands/commandset_complex.py @@ -1,4 +1,4 @@ -"""Test CommandSet""" +"""Test CommandSet.""" import argparse @@ -11,7 +11,7 @@ def do_apple(self, statement: cmd2.Statement) -> None: self._cmd.poutput('Apple!') def do_banana(self, statement: cmd2.Statement) -> None: - """Banana Command""" + """Banana Command.""" self._cmd.poutput('Banana!!') cranberry_parser = cmd2.Cmd2ArgumentParser() @@ -30,7 +30,7 @@ def help_cranberry(self) -> None: @cmd2.with_argument_list @cmd2.with_category('Also Alone') def do_durian(self, args: list[str]) -> None: - """Durian Command""" + """Durian Command.""" self._cmd.poutput(f'{len(args)} Arguments: ') self._cmd.poutput(', '.join(['{}'] * len(args)).format(*args)) diff --git a/examples/modular_commands/commandset_custominit.py b/examples/modular_commands/commandset_custominit.py index 83c1fbea8..90228016e 100644 --- a/examples/modular_commands/commandset_custominit.py +++ b/examples/modular_commands/commandset_custominit.py @@ -1,4 +1,4 @@ -"""A simple example demonstrating a loadable command set""" +"""A simple example demonstrating a loadable command set.""" from cmd2 import ( Cmd, diff --git a/examples/modular_commands_dynamic.py b/examples/modular_commands_dynamic.py index 46170f696..163c9dc8a 100755 --- a/examples/modular_commands_dynamic.py +++ b/examples/modular_commands_dynamic.py @@ -43,7 +43,7 @@ def do_bokchoy(self, _: cmd2.Statement) -> None: class ExampleApp(cmd2.Cmd): - """CommandSets are loaded via the `load` and `unload` commands""" + """CommandSets are loaded via the `load` and `unload` commands.""" def __init__(self, *args, **kwargs) -> None: # gotta have this or neither the plugin or cmd2 will initialize diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 16fad3146..f03ea38d6 100755 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -31,7 +31,7 @@ def __init__(self, command_sets: Optional[Iterable[CommandSet]] = None) -> None: self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] def choices_provider(self) -> list[str]: - """A choices provider is useful when the choice list is based on instance data of your application""" + """A choices provider is useful when the choice list is based on instance data of your application.""" return self.sport_item_strs # Parser for example command @@ -55,7 +55,7 @@ def choices_provider(self) -> list[str]: @with_argparser(example_parser) def do_example(self, _: argparse.Namespace) -> None: - """The example command""" + """The example command.""" self.poutput("I do nothing") diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index 98c3015ef..f1dbd024c 100755 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""A simple example demonstrating modular subcommand loading through CommandSets +"""A simple example demonstrating modular subcommand loading through CommandSets. In this example, there are loadable CommandSets defined. Each CommandSet has 1 subcommand defined that will be attached to the 'cut' command. @@ -35,7 +35,7 @@ def do_apple(self, _: cmd2.Statement) -> None: @cmd2.as_subcommand_to('cut', 'banana', banana_parser, help=banana_description.lower()) def cut_banana(self, ns: argparse.Namespace) -> None: - """Cut banana""" + """Cut banana.""" self._cmd.poutput('cutting banana: ' + ns.direction) diff --git a/examples/pirate.py b/examples/pirate.py index a6e657fc1..75c004da6 100755 --- a/examples/pirate.py +++ b/examples/pirate.py @@ -20,7 +20,7 @@ class Pirate(cmd2.Cmd): """A piratical example cmd2 application involving looting and drinking.""" def __init__(self) -> None: - """Initialize the base class as well as this one""" + """Initialize the base class as well as this one.""" shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) shortcuts.update({'~': 'sing'}) super().__init__(multiline_commands=['sing'], terminators=[MULTILINE_TERMINATOR, '...'], shortcuts=shortcuts) diff --git a/examples/python_scripting.py b/examples/python_scripting.py index d1def89c1..2211e6301 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -55,7 +55,7 @@ def postcmd(self, stop: bool, line: str) -> bool: def do_cd(self, arglist) -> None: """Change directory. Usage: - cd + cd . """ # Expect 1 argument, the directory to change to if not arglist or len(arglist) != 1: diff --git a/examples/read_input.py b/examples/read_input.py index 61ba9c8fc..65b81a414 100755 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -"""A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion""" +"""A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion.""" import cmd2 @@ -14,7 +14,7 @@ def __init__(self, *args, **kwargs) -> None: @cmd2.with_category(EXAMPLE_COMMANDS) def do_basic(self, _) -> None: - """Call read_input with no history or tab completion""" + """Call read_input with no history or tab completion.""" self.poutput("Tab completion and up-arrow history is off") try: self.read_input("> ") @@ -23,7 +23,7 @@ def do_basic(self, _) -> None: @cmd2.with_category(EXAMPLE_COMMANDS) def do_basic_with_history(self, _) -> None: - """Call read_input with custom history and no tab completion""" + """Call read_input with custom history and no tab completion.""" self.poutput("Tab completion is off but using custom history") try: input_str = self.read_input("> ", history=self.custom_history) @@ -34,7 +34,7 @@ def do_basic_with_history(self, _) -> None: @cmd2.with_category(EXAMPLE_COMMANDS) def do_commands(self, _) -> None: - """Call read_input the same way cmd2 prompt does to read commands""" + """Call read_input the same way cmd2 prompt does to read commands.""" self.poutput("Tab completing and up-arrow history configured for commands") try: self.read_input("> ", completion_mode=cmd2.CompletionMode.COMMANDS) @@ -43,7 +43,7 @@ def do_commands(self, _) -> None: @cmd2.with_category(EXAMPLE_COMMANDS) def do_custom_choices(self, _) -> None: - """Call read_input to use custom history and choices""" + """Call read_input to use custom history and choices.""" self.poutput("Tab completing with static choices list and using custom history") try: input_str = self.read_input( @@ -58,12 +58,12 @@ def do_custom_choices(self, _) -> None: self.custom_history.append(input_str) def choices_provider(self) -> list[str]: - """Example choices provider function""" + """Example choices provider function.""" return ["from_provider_1", "from_provider_2", "from_provider_3"] @cmd2.with_category(EXAMPLE_COMMANDS) def do_custom_choices_provider(self, _) -> None: - """Call read_input to use custom history and choices provider function""" + """Call read_input to use custom history and choices provider function.""" self.poutput("Tab completing with choices from provider function and using custom history") try: input_str = self.read_input( @@ -79,7 +79,7 @@ def do_custom_choices_provider(self, _) -> None: @cmd2.with_category(EXAMPLE_COMMANDS) def do_custom_completer(self, _) -> None: - """Call read_input to use custom history and completer function""" + """Call read_input to use custom history and completer function.""" self.poutput("Tab completing paths and using custom history") try: input_str = self.read_input( @@ -91,7 +91,7 @@ def do_custom_completer(self, _) -> None: @cmd2.with_category(EXAMPLE_COMMANDS) def do_custom_parser(self, _) -> None: - """Call read_input to use a custom history and an argument parser""" + """Call read_input to use a custom history and an argument parser.""" parser = cmd2.Cmd2ArgumentParser(prog='', description="An example parser") parser.add_argument('-o', '--option', help="an optional arg") parser.add_argument('arg_1', help="a choice for this arg", metavar='arg_1', choices=['my_choice', 'your_choice']) diff --git a/examples/scripts/save_help_text.py b/examples/scripts/save_help_text.py index a647cd286..92b62255a 100644 --- a/examples/scripts/save_help_text.py +++ b/examples/scripts/save_help_text.py @@ -11,7 +11,7 @@ def get_sub_commands(parser: argparse.ArgumentParser) -> list[str]: - """Get a list of subcommands for an ArgumentParser""" + """Get a list of subcommands for an ArgumentParser.""" sub_cmds = [] # Check if this is parser has subcommands @@ -34,7 +34,7 @@ def add_help_to_file(item: str, outfile: TextIO, is_command: bool) -> None: """Write help text for commands and topics to the output file :param item: what is having its help text saved :param outfile: file being written to - :param is_command: tells if the item is a command and not just a help topic + :param is_command: tells if the item is a command and not just a help topic. """ if is_command: label = "COMMAND" @@ -49,7 +49,7 @@ def add_help_to_file(item: str, outfile: TextIO, is_command: bool) -> None: def main() -> None: - """Main function of this script""" + """Main function of this script.""" # Make sure we have access to self if 'self' not in globals(): print("Re-run this script from a cmd2 application where self_in_py is True") diff --git a/examples/subcommands.py b/examples/subcommands.py index 6d6c2fefe..b2768cffe 100755 --- a/examples/subcommands.py +++ b/examples/subcommands.py @@ -70,15 +70,15 @@ def __init__(self) -> None: # subcommand functions for the base command def base_foo(self, args) -> None: - """Foo subcommand of base command""" + """Foo subcommand of base command.""" self.poutput(args.x * args.y) def base_bar(self, args) -> None: - """Bar subcommand of base command""" + """Bar subcommand of base command.""" self.poutput(f'(({args.z}))') def base_sport(self, args) -> None: - """Sport subcommand of base command""" + """Sport subcommand of base command.""" self.poutput(f'Sport is {args.sport}') # Set handler functions for the subcommands @@ -88,7 +88,7 @@ def base_sport(self, args) -> None: @cmd2.with_argparser(base_parser) def do_base(self, args) -> None: - """Base command help""" + """Base command help.""" func = getattr(args, 'func', None) if func is not None: # Call whatever subcommand function was selected @@ -99,7 +99,7 @@ def do_base(self, args) -> None: @cmd2.with_argparser(base2_parser) def do_alternate(self, args) -> None: - """Alternate command help""" + """Alternate command help.""" func = getattr(args, 'func', None) if func is not None: # Call whatever subcommand function was selected diff --git a/examples/table_creation.py b/examples/table_creation.py index 02a65fb49..00a45d292 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -"""Examples of using the cmd2 table creation API""" +"""Examples of using the cmd2 table creation API.""" import functools import sys @@ -26,18 +26,18 @@ class DollarFormatter: - """Example class to show that any object type can be passed as data to TableCreator and converted to a string""" + """Example class to show that any object type can be passed as data to TableCreator and converted to a string.""" def __init__(self, val: float) -> None: self.val = val def __str__(self) -> str: - """Returns the value in dollar currency form (e.g. $100.22)""" + """Returns the value in dollar currency form (e.g. $100.22).""" return f"${self.val:,.2f}" class Relative: - """Class used for example data""" + """Class used for example data.""" def __init__(self, name: str, relationship: str) -> None: self.name = name @@ -45,7 +45,7 @@ def __init__(self, name: str, relationship: str) -> None: class Book: - """Class used for example data""" + """Class used for example data.""" def __init__(self, title: str, year_published: str) -> None: self.title = title @@ -53,7 +53,7 @@ def __init__(self, title: str, year_published: str) -> None: class Author: - """Class used for example data""" + """Class used for example data.""" def __init__(self, name: str, birthday: str, place_of_birth: str) -> None: self.name = name @@ -64,12 +64,12 @@ def __init__(self, name: str, birthday: str, place_of_birth: str) -> None: def ansi_print(text) -> None: - """Wraps style_aware_write so style can be stripped if needed""" + """Wraps style_aware_write so style can be stripped if needed.""" ansi.style_aware_write(sys.stdout, text + '\n\n') def basic_tables() -> None: - """Demonstrates basic examples of the table classes""" + """Demonstrates basic examples of the table classes.""" # Table data which demonstrates handling of wrapping and text styles data_list: list[list[Any]] = [] data_list.append(["Billy Smith", "123 Sesame St.\nFake Town, USA 33445", DollarFormatter(100333.03)]) diff --git a/plugins/tasks.py b/plugins/tasks.py index c55a744b7..b2e2024ee 100644 --- a/plugins/tasks.py +++ b/plugins/tasks.py @@ -38,7 +38,7 @@ @invoke.task(pre=[ext_test_tasks.pytest]) @invoke.task() def pytest(_) -> None: - """Run tests and code coverage using pytest""" + """Run tests and code coverage using pytest.""" namespace.add_task(pytest) @@ -46,7 +46,7 @@ def pytest(_) -> None: @invoke.task(pre=[ext_test_tasks.pytest_clean]) def pytest_clean(_) -> None: - """Remove pytest cache and code coverage files and directories""" + """Remove pytest cache and code coverage files and directories.""" namespace_clean.add_task(pytest_clean, 'pytest') @@ -54,7 +54,7 @@ def pytest_clean(_) -> None: @invoke.task(pre=[ext_test_tasks.mypy]) def mypy(_) -> None: - """Run mypy optional static type checker""" + """Run mypy optional static type checker.""" namespace.add_task(mypy) @@ -62,7 +62,7 @@ def mypy(_) -> None: @invoke.task(pre=[ext_test_tasks.mypy_clean]) def mypy_clean(_) -> None: - """Remove mypy cache directory""" + """Remove mypy cache directory.""" # pylint: disable=unused-argument @@ -80,7 +80,7 @@ def mypy_clean(_) -> None: @invoke.task(pre=[ext_test_tasks.build_clean]) def build_clean(_) -> None: - """Remove the build directory""" + """Remove the build directory.""" namespace_clean.add_task(build_clean, 'build') @@ -88,7 +88,7 @@ def build_clean(_) -> None: @invoke.task(pre=[ext_test_tasks.dist_clean]) def dist_clean(_) -> None: - """Remove the dist directory""" + """Remove the dist directory.""" namespace_clean.add_task(dist_clean, 'dist') @@ -100,7 +100,7 @@ def dist_clean(_) -> None: @invoke.task(pre=list(namespace_clean.tasks.values()), default=True) def clean_all(_) -> None: - """Run all clean tasks""" + """Run all clean tasks.""" # pylint: disable=unused-argument @@ -109,7 +109,7 @@ def clean_all(_) -> None: @invoke.task(pre=[clean_all], post=[ext_test_tasks.sdist]) def sdist(_) -> None: - """Create a source distribution""" + """Create a source distribution.""" namespace.add_task(sdist) @@ -117,7 +117,7 @@ def sdist(_) -> None: @invoke.task(pre=[clean_all], post=[ext_test_tasks.wheel]) def wheel(_) -> None: - """Build a wheel distribution""" + """Build a wheel distribution.""" namespace.add_task(wheel) @@ -136,7 +136,7 @@ def lint(context) -> None: # ruff formatter @invoke.task(pre=[ext_test_tasks.format]) def format(context) -> None: # noqa: A001 - """Run formatter""" + """Run formatter.""" with context.cd(TASK_ROOT_STR): context.run("ruff format --check") diff --git a/plugins/template/cmd2_myplugin/__init__.py b/plugins/template/cmd2_myplugin/__init__.py index d61792467..3d4703d54 100644 --- a/plugins/template/cmd2_myplugin/__init__.py +++ b/plugins/template/cmd2_myplugin/__init__.py @@ -1,4 +1,4 @@ -"""Description of myplugin +"""Description of myplugin. An overview of what myplugin does. """ diff --git a/plugins/template/cmd2_myplugin/myplugin.py b/plugins/template/cmd2_myplugin/myplugin.py index 1d34a848b..37639a5c2 100644 --- a/plugins/template/cmd2_myplugin/myplugin.py +++ b/plugins/template/cmd2_myplugin/myplugin.py @@ -1,4 +1,4 @@ -"""An example cmd2 plugin""" +"""An example cmd2 plugin.""" import functools from collections.abc import Callable @@ -13,7 +13,7 @@ def empty_decorator(func: Callable) -> Callable: - """An empty decorator for myplugin""" + """An empty decorator for myplugin.""" @functools.wraps(func) def _empty_decorator(self, *args, **kwargs) -> None: @@ -25,7 +25,7 @@ def _empty_decorator(self, *args, **kwargs) -> None: class MyPluginMixin(_Base): - """A mixin class which adds a 'say' command to a cmd2 subclass + """A mixin class which adds a 'say' command to a cmd2 subclass. The order in which you add the mixin matters. Say you want to use this mixin in a class called MyApp. @@ -46,20 +46,20 @@ def __init__(self, *args, **kwargs) -> None: self.register_postparsing_hook(self.cmd2_myplugin_postparsing_hook) def do_say(self, statement) -> None: - """Simple say command""" + """Simple say command.""" self.poutput(statement) # # define hooks as functions, not methods def cmd2_myplugin_preloop_hook(self) -> None: - """Method to be called before the command loop begins""" + """Method to be called before the command loop begins.""" self.poutput("preloop hook") def cmd2_myplugin_postloop_hook(self) -> None: - """Method to be called after the command loop finishes""" + """Method to be called after the command loop finishes.""" self.poutput("postloop hook") def cmd2_myplugin_postparsing_hook(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: - """Method to be called after parsing user input, but before running the command""" + """Method to be called after parsing user input, but before running the command.""" self.poutput('in postparsing hook') return data diff --git a/plugins/template/examples/example.py b/plugins/template/examples/example.py index 50500872b..8a887e20e 100644 --- a/plugins/template/examples/example.py +++ b/plugins/template/examples/example.py @@ -4,7 +4,7 @@ class Example(cmd2_myplugin.MyPlugin, cmd2.Cmd): - """An class to show how to use a plugin""" + """An class to show how to use a plugin.""" def __init__(self, *args, **kwargs) -> None: # gotta have this or neither the plugin or cmd2 will initialize diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index eb012eaec..88db3f6ed 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -1,4 +1,4 @@ -"""Development related tasks to be run with 'invoke'""" +"""Development related tasks to be run with 'invoke'.""" import os import pathlib @@ -12,7 +12,7 @@ # shared function def rmrf(items, verbose=True) -> None: - """Silently remove a list of directories or files""" + """Silently remove a list of directories or files.""" if isinstance(items, str): items = [items] @@ -41,7 +41,7 @@ def rmrf(items, verbose=True) -> None: @invoke.task def pytest(context, junit=False, pty=True, append_cov=False) -> None: - """Run tests and code coverage using pytest""" + """Run tests and code coverage using pytest.""" ROOT_PATH = TASK_ROOT.parent.parent with context.cd(str(ROOT_PATH)): @@ -59,7 +59,7 @@ def pytest(context, junit=False, pty=True, append_cov=False) -> None: @invoke.task def pytest_clean(context) -> None: - """Remove pytest cache and code coverage files and directories""" + """Remove pytest cache and code coverage files and directories.""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): dirs = ['.pytest_cache', '.cache', '.coverage'] @@ -71,7 +71,7 @@ def pytest_clean(context) -> None: @invoke.task def pylint(context) -> None: - """Check code quality using pylint""" + """Check code quality using pylint.""" context.run('pylint --rcfile=cmd2_myplugin/pylintrc cmd2_myplugin') @@ -80,7 +80,7 @@ def pylint(context) -> None: @invoke.task def pylint_tests(context) -> None: - """Check code quality of test suite using pylint""" + """Check code quality of test suite using pylint.""" context.run('pylint --rcfile=tests/pylintrc tests') @@ -98,7 +98,7 @@ def pylint_tests(context) -> None: @invoke.task def build_clean(context) -> None: - """Remove the build directory""" + """Remove the build directory.""" # pylint: disable=unused-argument rmrf(BUILDDIR) @@ -108,7 +108,7 @@ def build_clean(context) -> None: @invoke.task def dist_clean(context) -> None: - """Remove the dist directory""" + """Remove the dist directory.""" # pylint: disable=unused-argument rmrf(DISTDIR) @@ -118,7 +118,7 @@ def dist_clean(context) -> None: @invoke.task def eggs_clean(context) -> None: - """Remove egg directories""" + """Remove egg directories.""" # pylint: disable=unused-argument dirs = set() dirs.add('.eggs') @@ -135,7 +135,7 @@ def eggs_clean(context) -> None: @invoke.task def bytecode_clean(context) -> None: - """Remove __pycache__ directories and *.pyc files""" + """Remove __pycache__ directories and *.pyc files.""" # pylint: disable=unused-argument dirs = set() for root, dirnames, files in os.walk(os.curdir): @@ -157,7 +157,7 @@ def bytecode_clean(context) -> None: @invoke.task(pre=list(namespace_clean.tasks.values()), default=True) def clean_all(context) -> None: - """Run all clean tasks""" + """Run all clean tasks.""" # pylint: disable=unused-argument @@ -166,7 +166,7 @@ def clean_all(context) -> None: @invoke.task(pre=[clean_all]) def sdist(context) -> None: - """Create a source distribution""" + """Create a source distribution.""" context.run('python -m build --sdist') @@ -175,7 +175,7 @@ def sdist(context) -> None: @invoke.task(pre=[clean_all]) def wheel(context) -> None: - """Build a wheel distribution""" + """Build a wheel distribution.""" context.run('python -m build --wheel') diff --git a/pyproject.toml b/pyproject.toml index c7d2b3b35..08d98d06f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -263,6 +263,7 @@ per-file-ignores."cmd2/argparse_custom.py" = [ per-file-ignores."examples/*.py" = [ "INP001", # Module is part of an implicit namespace "PLW2901", # loop variable overwritten inside loop + "S", # Ignore all Security rules in examples folder ] per-file-ignores."examples/override_parser.py" = [ @@ -279,10 +280,13 @@ per-file-ignores."examples/unicode_commands.py" = [ per-file-ignores."plugins/*.py" = [ "INP001", # Module is part of an implicit namespace + "S", # Ignore all Security rules in test folders ] per-file-ignores."tests/*.py" = [ + "D", # Ignore all pydocstyle rules in test folders "E501", # Line too long + "S", # Ignore all Security rules in test folders ] per-file-ignores."tests/test_argparse.py" = [ @@ -294,6 +298,11 @@ per-file-ignores."tests/pyscript/*.py" = [ "INP001", # Module is part of an implicit namespace ] +per-file-ignores."tests_isolated/*.py" = [ + "D", # Ignore all pydocstyle rules in test folders + "S", # Ignore all Security rules in test folders +] + per-file-ignores."tests_isolated/test_commandset/test_commandset.py" = [ "PLW0603", # Using the global statement to update {name} is discouraged ] diff --git a/tasks.py b/tasks.py index e0a2b207b..7eae28c77 100644 --- a/tasks.py +++ b/tasks.py @@ -24,7 +24,7 @@ # shared function def rmrf(items, verbose=True) -> None: - """Silently remove a list of directories or files""" + """Silently remove a list of directories or files.""" if isinstance(items, str): items = [items] @@ -53,7 +53,7 @@ def rmrf(items, verbose=True) -> None: @invoke.task() def pytest(context, junit=False, pty=True, base=False, isolated=False) -> None: - """Run tests and code coverage using pytest""" + """Run tests and code coverage using pytest.""" with context.cd(TASK_ROOT_STR): command_str = 'pytest ' command_str += ' --cov=cmd2 ' @@ -81,7 +81,7 @@ def pytest(context, junit=False, pty=True, base=False, isolated=False) -> None: @invoke.task(post=[plugin_tasks.pytest_clean]) def pytest_clean(context) -> None: - """Remove pytest cache and code coverage files and directories""" + """Remove pytest cache and code coverage files and directories.""" # pylint: disable=unused-argument with context.cd(str(TASK_ROOT / 'tests')): dirs = ['.pytest_cache', '.cache', 'htmlcov', '.coverage'] @@ -94,7 +94,7 @@ def pytest_clean(context) -> None: @invoke.task() def mypy(context) -> None: - """Run mypy optional static type checker""" + """Run mypy optional static type checker.""" with context.cd(TASK_ROOT_STR): context.run("mypy .") @@ -104,7 +104,7 @@ def mypy(context) -> None: @invoke.task() def mypy_clean(context) -> None: - """Remove mypy cache directory""" + """Remove mypy cache directory.""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): dirs = ['.mypy_cache', 'dmypy.json', 'dmypy.sock'] @@ -125,7 +125,7 @@ def mypy_clean(context) -> None: @invoke.task() def docs(context, builder='html') -> None: - """Build documentation using MkDocs""" + """Build documentation using MkDocs.""" with context.cd(TASK_ROOT_STR): context.run('mkdocs build', pty=True) @@ -135,7 +135,7 @@ def docs(context, builder='html') -> None: @invoke.task def docs_clean(context) -> None: - """Remove rendered documentation""" + """Remove rendered documentation.""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): rmrf(DOCS_BUILDDIR) @@ -146,7 +146,7 @@ def docs_clean(context) -> None: @invoke.task def livehtml(context) -> None: - """Launch webserver on http://localhost:8000 with rendered documentation""" + """Launch webserver on http://localhost:8000 with rendered documentation.""" with context.cd(TASK_ROOT_STR): context.run('mkdocs serve', pty=True) @@ -165,7 +165,7 @@ def livehtml(context) -> None: @invoke.task(post=[plugin_tasks.build_clean]) def build_clean(context) -> None: - """Remove the build directory""" + """Remove the build directory.""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): rmrf(BUILDDIR) @@ -176,7 +176,7 @@ def build_clean(context) -> None: @invoke.task(post=[plugin_tasks.dist_clean]) def dist_clean(context) -> None: - """Remove the dist directory""" + """Remove the dist directory.""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): rmrf(DISTDIR) @@ -187,7 +187,7 @@ def dist_clean(context) -> None: @invoke.task() def eggs_clean(context) -> None: - """Remove egg directories""" + """Remove egg directories.""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): dirs = set() @@ -205,7 +205,7 @@ def eggs_clean(context) -> None: @invoke.task() def pycache_clean(context) -> None: - """Remove __pycache__ directories""" + """Remove __pycache__ directories.""" # pylint: disable=unused-argument with context.cd(TASK_ROOT_STR): dirs = set() @@ -222,7 +222,7 @@ def pycache_clean(context) -> None: # ruff fast linter @invoke.task() def lint(context) -> None: - """Run ruff fast linter""" + """Run ruff fast linter.""" with context.cd(TASK_ROOT_STR): context.run("ruff check") @@ -233,7 +233,7 @@ def lint(context) -> None: # ruff fast formatter @invoke.task() def format(context) -> None: # noqa: A001 - """Run ruff format --check""" + """Run ruff format --check.""" with context.cd(TASK_ROOT_STR): context.run("ruff format --check") @@ -243,7 +243,7 @@ def format(context) -> None: # noqa: A001 @invoke.task() def ruff_clean(context) -> None: - """Remove .ruff_cache directory""" + """Remove .ruff_cache directory.""" with context.cd(TASK_ROOT_STR): context.run("ruff clean") @@ -258,7 +258,7 @@ def ruff_clean(context) -> None: @invoke.task(pre=clean_tasks, default=True) def clean_all(_) -> None: - """Run all clean tasks""" + """Run all clean tasks.""" # pylint: disable=unused-argument @@ -267,7 +267,7 @@ def clean_all(_) -> None: @invoke.task def tag(context, name, message='') -> None: - """Add a Git tag and push it to origin""" + """Add a Git tag and push it to origin.""" # If a tag was provided on the command-line, then add a Git tag and push it to origin if name: context.run(f'git tag -a {name} -m {message!r}') @@ -279,7 +279,7 @@ def tag(context, name, message='') -> None: @invoke.task() def validatetag(context) -> None: - """Check to make sure that a tag exists for the current HEAD and it looks like a valid version number""" + """Check to make sure that a tag exists for the current HEAD and it looks like a valid version number.""" # Validate that a Git tag exists for the current commit HEAD result = context.run("git describe --exact-match --tags $(git log -n1 --pretty='%h')") git_tag = result.stdout.rstrip() @@ -299,7 +299,7 @@ def validatetag(context) -> None: @invoke.task(pre=[clean_all], post=[plugin_tasks.sdist]) def sdist(context) -> None: - """Create a source distribution""" + """Create a source distribution.""" with context.cd(TASK_ROOT_STR): context.run('python -m build --sdist') @@ -309,7 +309,7 @@ def sdist(context) -> None: @invoke.task(pre=[clean_all], post=[plugin_tasks.wheel]) def wheel(context) -> None: - """Build a wheel distribution""" + """Build a wheel distribution.""" with context.cd(TASK_ROOT_STR): context.run('python -m build --wheel') @@ -319,7 +319,7 @@ def wheel(context) -> None: @invoke.task(pre=[validatetag, sdist, wheel]) def pypi(context) -> None: - """Build and upload a distribution to pypi""" + """Build and upload a distribution to pypi.""" with context.cd(TASK_ROOT_STR): context.run('twine upload dist/*') @@ -329,7 +329,7 @@ def pypi(context) -> None: @invoke.task(pre=[validatetag, sdist, wheel]) def pypi_test(context) -> None: - """Build and upload a distribution to https://test.pypi.org""" + """Build and upload a distribution to https://test.pypi.org.""" with context.cd(TASK_ROOT_STR): context.run('twine upload --repository testpypi dist/*') From c171aaa64e77308765bef3813201d761126430ca Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 13:57:37 -0400 Subject: [PATCH 68/79] Also ignore pydocstyle in examples and plugins directories --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 08d98d06f..7ce947494 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -261,6 +261,7 @@ per-file-ignores."cmd2/argparse_custom.py" = [ ] per-file-ignores."examples/*.py" = [ + "D", # Ignore all pydocstyle rules in examples folder "INP001", # Module is part of an implicit namespace "PLW2901", # loop variable overwritten inside loop "S", # Ignore all Security rules in examples folder @@ -279,6 +280,7 @@ per-file-ignores."examples/unicode_commands.py" = [ ] per-file-ignores."plugins/*.py" = [ + "D", # Ignore all pydocstyle rules in test folders "INP001", # Module is part of an implicit namespace "S", # Ignore all Security rules in test folders ] From d67c73d17bb8ba7aa24c3181395e0932e9d3e327 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 14:02:33 -0400 Subject: [PATCH 69/79] Disable missing type annotation warnings in test directories --- pyproject.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7ce947494..43d0cca1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -261,6 +261,7 @@ per-file-ignores."cmd2/argparse_custom.py" = [ ] per-file-ignores."examples/*.py" = [ + "ANN", # Ignore all type annotation rules in examples folder "D", # Ignore all pydocstyle rules in examples folder "INP001", # Module is part of an implicit namespace "PLW2901", # loop variable overwritten inside loop @@ -280,12 +281,14 @@ per-file-ignores."examples/unicode_commands.py" = [ ] per-file-ignores."plugins/*.py" = [ + "ANN", # Ignore all type annotation rules in test folders "D", # Ignore all pydocstyle rules in test folders "INP001", # Module is part of an implicit namespace "S", # Ignore all Security rules in test folders ] per-file-ignores."tests/*.py" = [ + "ANN", # Ignore all type annotation rules in test folders "D", # Ignore all pydocstyle rules in test folders "E501", # Line too long "S", # Ignore all Security rules in test folders @@ -301,8 +304,9 @@ per-file-ignores."tests/pyscript/*.py" = [ ] per-file-ignores."tests_isolated/*.py" = [ - "D", # Ignore all pydocstyle rules in test folders - "S", # Ignore all Security rules in test folders + "ANN", # Ignore all type annotation rules in test folders + "D", # Ignore all pydocstyle rules in test folders + "S", # Ignore all Security rules in test folders ] per-file-ignores."tests_isolated/test_commandset/test_commandset.py" = [ From a232620a39044f338b0acf03e6561e803d9f7526 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 14:07:15 -0400 Subject: [PATCH 70/79] Apply a couple automated ruff SIM refactorings --- tests/test_argparse_custom.py | 4 ++-- tests_isolated/test_commandset/test_commandset.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index df050b105..0fa07dc69 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -249,7 +249,7 @@ def test_override_parser() -> None: ) # The standard parser is Cmd2ArgumentParser - assert argparse_custom.DEFAULT_ARGUMENT_PARSER == Cmd2ArgumentParser + assert Cmd2ArgumentParser == argparse_custom.DEFAULT_ARGUMENT_PARSER # Set our parser module and force a reload of cmd2 so it loads the module argparse.cmd2_parser_module = 'examples.custom_parser' @@ -260,7 +260,7 @@ def test_override_parser() -> None: CustomParser, ) - assert argparse_custom.DEFAULT_ARGUMENT_PARSER == CustomParser + assert CustomParser == argparse_custom.DEFAULT_ARGUMENT_PARSER def test_apcustom_metavar_tuple() -> None: diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index c50b78fe5..35294f0c2 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -553,7 +553,7 @@ def test_subcommands(command_sets_manual) -> None: assert first_match is not None # check that the alias shows up correctly - assert ['banana', 'bananer', 'bokchoy'] == command_sets_manual.completion_matches + assert command_sets_manual.completion_matches == ['banana', 'bananer', 'bokchoy'] cmd_result = command_sets_manual.app_cmd('cut banana discs') assert 'cutting banana: discs' in cmd_result.stdout @@ -566,7 +566,7 @@ def test_subcommands(command_sets_manual) -> None: assert first_match is not None # verify that argparse completer in commandset functions correctly - assert ['diced', 'quartered'] == command_sets_manual.completion_matches + assert command_sets_manual.completion_matches == ['diced', 'quartered'] # verify that command set uninstalls without problems command_sets_manual.unregister_command_set(fruit_cmds) @@ -598,7 +598,7 @@ def test_subcommands(command_sets_manual) -> None: assert first_match is not None # check that the alias shows up correctly - assert ['banana', 'bananer', 'bokchoy'] == command_sets_manual.completion_matches + assert command_sets_manual.completion_matches == ['banana', 'bananer', 'bokchoy'] text = '' line = f'cut bokchoy {text}' @@ -608,7 +608,7 @@ def test_subcommands(command_sets_manual) -> None: assert first_match is not None # verify that argparse completer in commandset functions correctly - assert ['diced', 'quartered'] == command_sets_manual.completion_matches + assert command_sets_manual.completion_matches == ['diced', 'quartered'] # disable again and verify can still uninstnall command_sets_manual.disable_command('cut', 'disabled for test') @@ -763,7 +763,7 @@ def test_static_subcommands(static_subcommands_app) -> None: assert first_match is not None # check that the alias shows up correctly - assert ['banana', 'bananer', 'bokchoy'] == static_subcommands_app.completion_matches + assert static_subcommands_app.completion_matches == ['banana', 'bananer', 'bokchoy'] text = '' line = f'cut bokchoy {text}' @@ -773,7 +773,7 @@ def test_static_subcommands(static_subcommands_app) -> None: assert first_match is not None # verify that argparse completer in commandset functions correctly - assert ['diced', 'quartered'] == static_subcommands_app.completion_matches + assert static_subcommands_app.completion_matches == ['diced', 'quartered'] complete_states_expected_self = None From 9db905efff15b0d3a389e3ad0752eae9fee9ae62 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 14:11:15 -0400 Subject: [PATCH 71/79] Apply some more ruff SIM automated simplifications --- cmd2/argparse_completer.py | 5 +- cmd2/argparse_custom.py | 10 +--- cmd2/cmd2.py | 59 +++++-------------- cmd2/command_definition.py | 4 +- cmd2/history.py | 5 +- cmd2/parsing.py | 17 ++---- cmd2/py_bridge.py | 13 ++-- cmd2/rl_utils.py | 15 +---- cmd2/utils.py | 25 ++------ examples/read_input.py | 10 ++-- examples/scripts/save_help_text.py | 5 +- plugins/template/tasks.py | 5 +- tasks.py | 5 +- tests/conftest.py | 17 ++---- tests/test_argparse.py | 5 +- tests/test_argparse_completer.py | 9 +-- tests/test_history.py | 5 +- tests_isolated/test_commandset/conftest.py | 17 ++---- .../test_commandset/test_commandset.py | 12 ++-- 19 files changed, 75 insertions(+), 168 deletions(-) diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 88e898196..7a0760cb2 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -88,9 +88,8 @@ def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool: return False # If it looks like a negative number, it is not a flag unless there are negative-number-like flags - if parser._negative_number_matcher.match(token): - if not parser._has_negative_number_optionals: - return False + if parser._negative_number_matcher.match(token) and not parser._has_negative_number_optionals: + return False # Flags can't have a space if ' ' in token: diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index f4dc70842..8fc7b4d95 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1056,10 +1056,7 @@ def _format_usage( def get_lines(parts: list[str], indent: str, prefix: Optional[str] = None) -> list[str]: lines: list[str] = [] line: list[str] = [] - if prefix is not None: - line_len = len(prefix) - 1 - else: - line_len = len(indent) - 1 + line_len = len(prefix) - 1 if prefix is not None else len(indent) - 1 for part in parts: if line_len + 1 + len(part) > text_width and line: lines.append(indent + ' '.join(line)) @@ -1172,10 +1169,7 @@ def _format_args(self, action: argparse.Action, default_metavar: Union[str, tupl # Handle nargs specified as a range nargs_range = action.get_nargs_range() # type: ignore[attr-defined] if nargs_range is not None: - if nargs_range[1] == constants.INFINITY: - range_str = f'{nargs_range[0]}+' - else: - range_str = f'{nargs_range[0]}..{nargs_range[1]}' + range_str = f'{nargs_range[0]}+' if nargs_range[1] == constants.INFINITY else f'{nargs_range[0]}..{nargs_range[1]}' return '{}{{{}}}'.format('%s' % metavar_formatter(1), range_str) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index d64cf0cad..cab6fdbc4 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -50,6 +50,7 @@ from collections.abc import Callable, Iterable, Mapping from contextlib import ( redirect_stdout, + suppress, ) from types import ( FrameType, @@ -689,13 +690,13 @@ def register_command_set(self, cmdset: CommandSet) -> None: if self.always_prefix_settables: if not cmdset.settable_prefix.strip(): raise CommandSetRegistrationError('CommandSet settable prefix must not be empty') - for key in cmdset.settables.keys(): + for key in cmdset.settables: prefixed_name = f'{cmdset.settable_prefix}.{key}' if prefixed_name in all_settables: raise CommandSetRegistrationError(f'Duplicate settable: {key}') else: - for key in cmdset.settables.keys(): + for key in cmdset.settables: if key in all_settables: raise CommandSetRegistrationError(f'Duplicate settable {key} is already registered') @@ -1098,7 +1099,7 @@ def add_settable(self, settable: Settable) -> None: :param settable: Settable object being added """ if not self.always_prefix_settables: - if settable.name in self.settables.keys() and settable.name not in self._settables.keys(): + if settable.name in self.settables and settable.name not in self._settables: raise KeyError(f'Duplicate settable: {settable.name}') self._settables[settable.name] = settable @@ -1594,10 +1595,7 @@ def index_based_complete( # Check if token is at an index in the dictionary match_against: Optional[Union[Iterable[str], CompleterFunc]] - if index in index_dict: - match_against = index_dict[index] - else: - match_against = all_else + match_against = index_dict.get(index, all_else) # Perform tab completion using a Iterable if isinstance(match_against, Iterable): @@ -1739,10 +1737,7 @@ def complete_users() -> list[str]: # Remove cwd if it was added to match the text readline expects if cwd_added: - if cwd == os.path.sep: - to_replace = cwd - else: - to_replace = cwd + os.path.sep + to_replace = cwd if cwd == os.path.sep else cwd + os.path.sep matches = [cur_path.replace(to_replace, '', 1) for cur_path in matches] # Restore the tilde string if we expanded one to match the text readline expects @@ -1962,10 +1957,7 @@ def _display_matches_pyreadline(self, matches: list[str]) -> None: # pragma: no # Otherwise use pyreadline3's formatter else: # Check if we should show display_matches - if self.display_matches: - matches_to_display = self.display_matches - else: - matches_to_display = matches + matches_to_display = self.display_matches if self.display_matches else matches # Add padding for visual appeal matches_to_display, _ = self._pad_matches_to_display(matches_to_display) @@ -2154,10 +2146,7 @@ def _perform_completion( if add_quote: # Figure out what kind of quote to add and save it as the unclosed_quote - if any('"' in match for match in self.completion_matches): - completion_token_quote = "'" - else: - completion_token_quote = '"' + completion_token_quote = "'" if any('"' in match for match in self.completion_matches) else '"' self.completion_matches = [completion_token_quote + match for match in self.completion_matches] @@ -2762,7 +2751,7 @@ def _input_line_to_statement(self, line: str, *, orig_rl_history_length: Optiona orig_rl_history_length = None # Check if this command matches a macro and wasn't already processed to avoid an infinite loop - if statement.command in self.macros.keys() and statement.command not in used_macros: + if statement.command in self.macros and statement.command not in used_macros: used_macros.append(statement.command) resolve_result = self._resolve_macro(statement) if resolve_result is None: @@ -2795,7 +2784,7 @@ def _resolve_macro(self, statement: Statement) -> Optional[str]: :param statement: the parsed statement from the command line :return: the resolved macro or None on error """ - if statement.command not in self.macros.keys(): + if statement.command not in self.macros: raise KeyError(f"{statement.command} is not a macro") macro = self.macros[statement.command] @@ -2886,10 +2875,8 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: # like: !ls -l | grep user | wc -l > out.txt. But this makes it difficult to know if the pipe process # started OK, since the shell itself always starts. Therefore, we will wait a short time and check # if the pipe process is still running. - try: + with suppress(subprocess.TimeoutExpired): proc.wait(0.2) - except subprocess.TimeoutExpired: - pass # Check if the pipe process already exited if proc.returncode is not None: @@ -3462,10 +3449,7 @@ def _alias_list(self, args: argparse.Namespace) -> None: tokens_to_quote = constants.REDIRECTION_TOKENS tokens_to_quote.extend(self.statement_parser.terminators) - if args.names: - to_list = utils.remove_duplicates(args.names) - else: - to_list = sorted(self.aliases, key=self.default_sort_key) + to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.aliases, key=self.default_sort_key) not_found: list[str] = [] for name in to_list: @@ -3697,10 +3681,7 @@ def _macro_list(self, args: argparse.Namespace) -> None: tokens_to_quote = constants.REDIRECTION_TOKENS tokens_to_quote.extend(self.statement_parser.terminators) - if args.names: - to_list = utils.remove_duplicates(args.names) - else: - to_list = sorted(self.macros, key=self.default_sort_key) + to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.macros, key=self.default_sort_key) not_found: list[str] = [] for name in to_list: @@ -3865,10 +3846,7 @@ def columnize(self, str_list: Optional[list[str]], display_width: int = 80) -> N texts = [] for col in range(ncols): i = row + nrows * col - if i >= size: - x = "" - else: - x = str_list[i] + x = "" if i >= size else str_list[i] texts.append(x) while texts and not texts[-1]: del texts[-1] @@ -4267,10 +4245,8 @@ def _reset_py_display() -> None: # Delete any prompts that have been set attributes = ['ps1', 'ps2', 'ps3'] for cur_attr in attributes: - try: + with suppress(KeyError): del sys.__dict__[cur_attr] - except KeyError: - pass # Reset functions sys.displayhook = sys.__displayhook__ @@ -4982,10 +4958,7 @@ def _generate_transcript( self.perror(f"Error saving transcript file '{transcript_path}': {ex}") else: # and let the user know what we did - if commands_run == 1: - plural = 'command and its output' - else: - plural = 'commands and their outputs' + plural = 'command and its output' if commands_run == 1 else 'commands and their outputs' self.pfeedback(f"{commands_run} {plural} saved to transcript file '{transcript_path}'") self.last_result = True diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index 066f63157..941fe0337 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -156,11 +156,11 @@ def add_settable(self, settable: Settable) -> None: """ if self.__cmd_internal is not None: if not self._cmd.always_prefix_settables: - if settable.name in self._cmd.settables.keys() and settable.name not in self._settables.keys(): + if settable.name in self._cmd.settables and settable.name not in self._settables: raise KeyError(f'Duplicate settable: {settable.name}') else: prefixed_name = f'{self._settable_prefix}.{settable.name}' - if prefixed_name in self._cmd.settables.keys() and settable.name not in self._settables.keys(): + if prefixed_name in self._cmd.settables and settable.name not in self._settables: raise KeyError(f'Duplicate settable: {settable.name}') self._settables[settable.name] = settable diff --git a/cmd2/history.py b/cmd2/history.py index 4bd47aa9b..05d79a40a 100644 --- a/cmd2/history.py +++ b/cmd2/history.py @@ -114,10 +114,7 @@ def pr(self, idx: int, script: bool = False, expanded: bool = False, verbose: bo if raw != expanded_command: ret_str += '\n' + self._ex_listformat.format(idx, expanded_command) else: - if expanded: - ret_str = self.expanded - else: - ret_str = single_line_format(self.statement).rstrip() + ret_str = self.expanded if expanded else single_line_format(self.statement).rstrip() # Display a numbered list if not writing to a script if not script: diff --git a/cmd2/parsing.py b/cmd2/parsing.py index ec60977fd..e77c81bb9 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -353,10 +353,9 @@ def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> tuple[b errmsg += ', '.join([shlex.quote(x) for x in errchars]) match = self._command_pattern.search(word) - if match: - if word == match.group(1): - valid = True - errmsg = '' + if match and word == match.group(1): + valid = True + errmsg = '' return valid, errmsg def tokenize(self, line: str) -> list[str]: @@ -507,10 +506,7 @@ def parse(self, line: str) -> Statement: arg_list = tokens[1:] # set multiline - if command in self.multiline_commands: - multiline_command = command - else: - multiline_command = '' + multiline_command = command if command in self.multiline_commands else '' # build the statement return Statement( @@ -580,10 +576,7 @@ def parse_command_only(self, rawinput: str) -> Statement: args = '' # set multiline - if command in self.multiline_commands: - multiline_command = command - else: - multiline_command = '' + multiline_command = command if command in self.multiline_commands else '' # build the statement return Statement(args, raw=rawinput, command=command, multiline_command=multiline_command) diff --git a/cmd2/py_bridge.py b/cmd2/py_bridge.py index 10c28e42a..1cccbaed4 100644 --- a/cmd2/py_bridge.py +++ b/cmd2/py_bridge.py @@ -124,13 +124,12 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul stop = False try: self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout) - with redirect_stdout(cast(IO[str], copy_cmd_stdout)): - with redirect_stderr(cast(IO[str], copy_stderr)): - stop = self._cmd2_app.onecmd_plus_hooks( - command, - add_to_history=self._add_to_history, - py_bridge_call=True, - ) + with redirect_stdout(cast(IO[str], copy_cmd_stdout)), redirect_stderr(cast(IO[str], copy_stderr)): + stop = self._cmd2_app.onecmd_plus_hooks( + command, + add_to_history=self._add_to_history, + py_bridge_call=True, + ) finally: with self._cmd2_app.sigint_protection: self._cmd2_app.stdout = cast(IO[str], copy_cmd_stdout.inner_stream) diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index a00c74b14..7a51d71d4 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -187,17 +187,11 @@ def rl_get_prompt() -> str: # pragma: no cover """Get Readline's prompt.""" if rl_type == RlType.GNU: encoded_prompt = ctypes.c_char_p.in_dll(readline_lib, "rl_prompt").value - if encoded_prompt is None: - prompt = '' - else: - prompt = encoded_prompt.decode(encoding='utf-8') + prompt = '' if encoded_prompt is None else encoded_prompt.decode(encoding='utf-8') elif rl_type == RlType.PYREADLINE: prompt_data: Union[str, bytes] = readline.rl.prompt - if isinstance(prompt_data, bytes): - prompt = prompt_data.decode(encoding='utf-8') - else: - prompt = prompt_data + prompt = prompt_data.decode(encoding='utf-8') if isinstance(prompt_data, bytes) else prompt_data else: prompt = '' @@ -213,10 +207,7 @@ def rl_get_display_prompt() -> str: # pragma: no cover """ if rl_type == RlType.GNU: encoded_prompt = ctypes.c_char_p.in_dll(readline_lib, "rl_display_prompt").value - if encoded_prompt is None: - prompt = '' - else: - prompt = encoded_prompt.decode(encoding='utf-8') + prompt = '' if encoded_prompt is None else encoded_prompt.decode(encoding='utf-8') return rl_unescape_prompt(prompt) return rl_get_prompt() diff --git a/cmd2/utils.py b/cmd2/utils.py index 0d61c8fc9..4cf1d3106 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -58,10 +58,7 @@ def is_quoted(arg: str) -> bool: def quote_string(arg: str) -> str: """Quote a string.""" - if '"' in arg: - quote = "'" - else: - quote = '"' + quote = "'" if '"' in arg else '"' return quote + arg + quote @@ -360,10 +357,7 @@ def find_editor() -> Optional[str]: # Get a list of every directory in the PATH environment variable and ignore symbolic links env_path = os.getenv('PATH') - if env_path is None: - paths = [] - else: - paths = [p for p in env_path.split(os.path.pathsep) if not os.path.islink(p)] + paths = [] if env_path is None else [p for p in env_path.split(os.path.pathsep) if not os.path.islink(p)] for possible_editor, path in itertools.product(editors, paths): editor_path = os.path.join(path, possible_editor) @@ -422,10 +416,7 @@ def get_exes_in_path(starts_with: str) -> list[str]: # Get a list of every directory in the PATH environment variable and ignore symbolic links env_path = os.getenv('PATH') - if env_path is None: - paths = [] - else: - paths = [p for p in env_path.split(os.path.pathsep) if not os.path.islink(p)] + paths = [] if env_path is None else [p for p in env_path.split(os.path.pathsep) if not os.path.islink(p)] # Use a set to store exe names since there can be duplicates exes_set = set() @@ -558,9 +549,8 @@ def write(self, b: bytes) -> None: # and the bytes being written contain a new line character. This is helpful when StdSim # is being used to capture output of a shell command because it causes the output to print # to the screen more often than if we waited for the stream to flush its buffer. - if self.std_sim_instance.line_buffering: - if any(newline in b for newline in ByteBuf.NEWLINES): - self.std_sim_instance.flush() + if self.std_sim_instance.line_buffering and any(newline in b for newline in ByteBuf.NEWLINES): + self.std_sim_instance.flush() class ProcReader: @@ -862,10 +852,7 @@ def align_text( # fill characters. Instead of repeating the style characters for each fill character, we'll wrap each sequence. fill_char_style_begin, fill_char_style_end = fill_char.split(stripped_fill_char) - if text: - lines = text.splitlines() - else: - lines = [''] + lines = text.splitlines() if text else [''] text_buf = io.StringIO() diff --git a/examples/read_input.py b/examples/read_input.py index 65b81a414..408617705 100755 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -1,6 +1,8 @@ #!/usr/bin/env python """A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion.""" +import contextlib + import cmd2 EXAMPLE_COMMANDS = "Example Commands" @@ -16,10 +18,8 @@ def __init__(self, *args, **kwargs) -> None: def do_basic(self, _) -> None: """Call read_input with no history or tab completion.""" self.poutput("Tab completion and up-arrow history is off") - try: + with contextlib.suppress(EOFError): self.read_input("> ") - except EOFError: - pass @cmd2.with_category(EXAMPLE_COMMANDS) def do_basic_with_history(self, _) -> None: @@ -36,10 +36,8 @@ def do_basic_with_history(self, _) -> None: def do_commands(self, _) -> None: """Call read_input the same way cmd2 prompt does to read commands.""" self.poutput("Tab completing and up-arrow history configured for commands") - try: + with contextlib.suppress(EOFError): self.read_input("> ", completion_mode=cmd2.CompletionMode.COMMANDS) - except EOFError: - pass @cmd2.with_category(EXAMPLE_COMMANDS) def do_custom_choices(self, _) -> None: diff --git a/examples/scripts/save_help_text.py b/examples/scripts/save_help_text.py index 92b62255a..a1e2cdd26 100644 --- a/examples/scripts/save_help_text.py +++ b/examples/scripts/save_help_text.py @@ -36,10 +36,7 @@ def add_help_to_file(item: str, outfile: TextIO, is_command: bool) -> None: :param outfile: file being written to :param is_command: tells if the item is a command and not just a help topic. """ - if is_command: - label = "COMMAND" - else: - label = "TOPIC" + label = "COMMAND" if is_command else "TOPIC" header = f'{ASTERISKS}\n{label}: {item}\n{ASTERISKS}\n' outfile.write(header) diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index 88db3f6ed..f83ac863e 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -1,5 +1,6 @@ """Development related tasks to be run with 'invoke'.""" +import contextlib import os import pathlib import shutil @@ -21,10 +22,8 @@ def rmrf(items, verbose=True) -> None: print(f"Removing {item}") shutil.rmtree(item, ignore_errors=True) # rmtree doesn't remove bare files - try: + with contextlib.suppress(FileNotFoundError): os.remove(item) - except FileNotFoundError: - pass # create namespaces diff --git a/tasks.py b/tasks.py index 7eae28c77..e0163c032 100644 --- a/tasks.py +++ b/tasks.py @@ -6,6 +6,7 @@ - setuptools >= 39.1.0 """ +import contextlib import os import pathlib import re @@ -33,10 +34,8 @@ def rmrf(items, verbose=True) -> None: print(f"Removing {item}") shutil.rmtree(item, ignore_errors=True) # rmtree doesn't remove bare files - try: + with contextlib.suppress(FileNotFoundError): os.remove(item) - except FileNotFoundError: - pass # create namespaces diff --git a/tests/conftest.py b/tests/conftest.py index 530f8655e..026b62efc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,10 +34,7 @@ def verify_help_text( :param help_output: output of help, either as a string or list of strings :param verbose_strings: optional list of verbose strings to search for """ - if isinstance(help_output, str): - help_text = help_output - else: - help_text = ''.join(help_output) + help_text = help_output if isinstance(help_output, str) else ''.join(help_output) commands = cmd2_app.get_visible_commands() for command in commands: assert command in help_text @@ -135,9 +132,8 @@ def run_cmd(app, cmd): try: app.stdout = copy_cmd_stdout - with redirect_stdout(copy_cmd_stdout): - with redirect_stderr(copy_stderr): - app.onecmd_plus_hooks(cmd) + with redirect_stdout(copy_cmd_stdout), redirect_stderr(copy_stderr): + app.onecmd_plus_hooks(cmd) finally: app.stdout = copy_cmd_stdout.inner_stream sys.stdout = saved_sysout @@ -182,10 +178,9 @@ def get_endidx(): return endidx # Run the readline tab completion function with readline mocks in place - with mock.patch.object(readline, 'get_line_buffer', get_line): - with mock.patch.object(readline, 'get_begidx', get_begidx): - with mock.patch.object(readline, 'get_endidx', get_endidx): - return app.complete(text, 0) + with mock.patch.object(readline, 'get_line_buffer', get_line), mock.patch.object(readline, 'get_begidx', get_begidx): + with mock.patch.object(readline, 'get_endidx', get_endidx): + return app.complete(text, 0) def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser: diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 209777c61..37b0544e8 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -441,9 +441,8 @@ def test_unittest_mock() -> None: CommandSetRegistrationError, ) - with mock.patch.object(ArgparseApp, 'namespace_provider'): - with pytest.raises(CommandSetRegistrationError): - ArgparseApp() + with mock.patch.object(ArgparseApp, 'namespace_provider'), pytest.raises(CommandSetRegistrationError): + ArgparseApp() with mock.patch.object(ArgparseApp, 'namespace_provider', spec=True): ArgparseApp() diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index f4c173abe..4605a34b8 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -1028,14 +1028,7 @@ def test_autocomp_hint_no_help_text(ac_app, capsys) -> None: out, err = capsys.readouterr() assert first_match is None - assert ( - not out - == ''' -Hint: - NO_HELP_POS - -''' - ) + assert out != '''\nHint:\n NO_HELP_POS\n\n''' @pytest.mark.parametrize( diff --git a/tests/test_history.py b/tests/test_history.py index 99db7a02f..7b2a3a7c6 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -1,5 +1,6 @@ """Test history functions of cmd2""" +import contextlib import os import tempfile from unittest import ( @@ -879,10 +880,8 @@ def hist_file(): os.close(fd) yield filename # teardown code - try: + with contextlib.suppress(FileNotFoundError): os.remove(filename) - except FileNotFoundError: - pass def test_history_file_is_directory(capsys) -> None: diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 065065ea8..d5853de4a 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -36,10 +36,7 @@ def verify_help_text( :param help_output: output of help, either as a string or list of strings :param verbose_strings: optional list of verbose strings to search for """ - if isinstance(help_output, str): - help_text = help_output - else: - help_text = ''.join(help_output) + help_text = help_output if isinstance(help_output, str) else ''.join(help_output) commands = cmd2_app.get_visible_commands() for command in commands: assert command in help_text @@ -118,9 +115,8 @@ def run_cmd(app, cmd): try: app.stdout = copy_cmd_stdout - with redirect_stdout(copy_cmd_stdout): - with redirect_stderr(copy_stderr): - app.onecmd_plus_hooks(cmd) + with redirect_stdout(copy_cmd_stdout), redirect_stderr(copy_stderr): + app.onecmd_plus_hooks(cmd) finally: app.stdout = copy_cmd_stdout.inner_stream sys.stdout = saved_sysout @@ -165,10 +161,9 @@ def get_endidx(): return endidx # Run the readline tab completion function with readline mocks in place - with mock.patch.object(readline, 'get_line_buffer', get_line): - with mock.patch.object(readline, 'get_begidx', get_begidx): - with mock.patch.object(readline, 'get_endidx', get_endidx): - return app.complete(text, 0) + with mock.patch.object(readline, 'get_line_buffer', get_line), mock.patch.object(readline, 'get_begidx', get_begidx): + with mock.patch.object(readline, 'get_endidx', get_endidx): + return app.complete(text, 0) class WithCommandSets(ExternalTestMixin, cmd2.Cmd): diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index 35294f0c2..eace75d0b 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -1087,9 +1087,9 @@ def __init__(self) -> None: app.add_settable(Settable('always_prefix_settables', bool, 'Prefix settables', app)) app._settables['str_value'] = Settable('str_value', str, 'String value', app) - assert 'arbitrary_value' in app.settables.keys() - assert 'always_prefix_settables' in app.settables.keys() - assert 'str_value' in app.settables.keys() + assert 'arbitrary_value' in app.settables + assert 'always_prefix_settables' in app.settables + assert 'str_value' in app.settables # verify the settable shows up out, err = run_cmd(app, 'set') @@ -1160,12 +1160,12 @@ def __init__(self) -> None: app.register_command_set(cmdset) # Verify the settable is back with the defined prefix. - assert 'addon.arbitrary_value' in app.settables.keys() + assert 'addon.arbitrary_value' in app.settables # rename the prefix and verify that the prefix changes everywhere cmdset._settable_prefix = 'some' - assert 'addon.arbitrary_value' not in app.settables.keys() - assert 'some.arbitrary_value' in app.settables.keys() + assert 'addon.arbitrary_value' not in app.settables + assert 'some.arbitrary_value' in app.settables out, err = run_cmd(app, 'set') any('some.arbitrary_value' in line and '5' in line for line in out) From 980d893645ab54d83fec223e15a1aea5dc99532e Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 14:15:20 -0400 Subject: [PATCH 72/79] Ignore warning about private/protected member access in test directories --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 43d0cca1f..7d9d9a710 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -208,7 +208,7 @@ select = [ # "RUF", # Ruff-specific rules (miscellaneous grab bag of lint checks specific to Ruff) # "S", # flake8-bandit (security oriented checks, but extremely pedantic - do not attempt to apply to unit test files) # "SIM", # flake8-simplify (rules to attempt to simplify code) - # "SLF", # flake8-self (warn when protected members are accessed outside of a class or file) + # "SLF", # flake8-self (warn when protected members are accessed outside of a class or file) "SLOT", # flake8-slots (warn about subclasses that should define __slots__) "T10", # flake8-debugger (check for pdb traces left in Python code) # "T20", # flake8-print (warn about use of `print` or `pprint` - force use of loggers) @@ -285,6 +285,7 @@ per-file-ignores."plugins/*.py" = [ "D", # Ignore all pydocstyle rules in test folders "INP001", # Module is part of an implicit namespace "S", # Ignore all Security rules in test folders + "SLF", # Ignore all warnings about private or protected member access in test folders ] per-file-ignores."tests/*.py" = [ @@ -292,6 +293,7 @@ per-file-ignores."tests/*.py" = [ "D", # Ignore all pydocstyle rules in test folders "E501", # Line too long "S", # Ignore all Security rules in test folders + "SLF", # Ignore all warnings about private or protected member access in test folders ] per-file-ignores."tests/test_argparse.py" = [ @@ -307,6 +309,7 @@ per-file-ignores."tests_isolated/*.py" = [ "ANN", # Ignore all type annotation rules in test folders "D", # Ignore all pydocstyle rules in test folders "S", # Ignore all Security rules in test folders + "SLF", # Ignore all warnings about private or protected member access in test folders ] per-file-ignores."tests_isolated/test_commandset/test_commandset.py" = [ From 3f7f3188fc4c759a909f44f7be776cbe99e74cfd Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 14:19:09 -0400 Subject: [PATCH 73/79] Apply a few tryceratops automated refactorings --- cmd2/cmd2.py | 12 ++++++------ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index cab6fdbc4..5aa3b35eb 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -2553,9 +2553,9 @@ def onecmd_plus_hooks( self.perror(f"Invalid syntax: {ex}") except RedirectionError as ex: self.perror(ex) - except KeyboardInterrupt as ex: + except KeyboardInterrupt: if raise_keyboard_interrupt and not stop: - raise ex + raise except SystemExit as ex: if isinstance(ex.code, int): self.exit_code = ex.code @@ -2567,9 +2567,9 @@ def onecmd_plus_hooks( finally: try: stop = self._run_cmdfinalization_hooks(stop, statement) - except KeyboardInterrupt as ex: + except KeyboardInterrupt: if raise_keyboard_interrupt and not stop: - raise ex + raise except SystemExit as ex: if isinstance(ex.code, int): self.exit_code = ex.code @@ -4041,9 +4041,9 @@ def select(self, opts: Union[str, list[str], list[tuple[Any, Optional[str]]]], p except EOFError: response = '' self.poutput() - except KeyboardInterrupt as ex: + except KeyboardInterrupt: self.poutput('^C') - raise ex + raise if not response: continue diff --git a/pyproject.toml b/pyproject.toml index 7d9d9a710..b45397102 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -215,7 +215,7 @@ select = [ "TC", # flake8-type-checking (type checking warnings) "TD", # flake8-todos (force all TODOs to include an author and issue link) "TID", # flake8-tidy-imports (extra import rules to check) - # "TRY", # tryceratops (warnings related to exceptions and try/except) + # "TRY", # tryceratops (warnings related to exceptions and try/except) "UP", # pyupgrade (A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language) "W", # pycodestyle warnings (warn about minor stylistic issues) "YTT", # flake8-2020 (checks for misuse of sys.version or sys.version_info) From f589404010ccfb7108e123c8deae8020efc20438 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 14:50:21 -0400 Subject: [PATCH 74/79] Enable ruff BLE ruleset --- cmd2/cmd2.py | 12 ++++++------ examples/python_scripting.py | 2 +- pyproject.toml | 2 +- tests/test_cmd2.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 5aa3b35eb..91149871a 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -2255,7 +2255,7 @@ def complete( # type: ignore[override] ansi.style_aware_write(sys.stdout, '\n' + err_str + '\n') rl_force_redisplay() return None - except Exception as ex: + except Exception as ex: # noqa: BLE001 # Insert a newline so the exception doesn't print in the middle of the command line being tab completed self.perror() self.pexcept(ex) @@ -2562,7 +2562,7 @@ def onecmd_plus_hooks( stop = True except PassThroughException as ex: raise ex.wrapped_ex - except Exception as ex: + except Exception as ex: # noqa: BLE001 self.pexcept(ex) finally: try: @@ -2576,7 +2576,7 @@ def onecmd_plus_hooks( stop = True except PassThroughException as ex: raise ex.wrapped_ex - except Exception as ex: + except Exception as ex: # noqa: BLE001 self.pexcept(ex) return stop @@ -4131,7 +4131,7 @@ def do_set(self, args: argparse.Namespace) -> None: try: orig_value = settable.get_value() settable.set_value(utils.strip_quotes(args.value)) - except Exception as ex: + except ValueError as ex: self.perror(f"Error setting {args.param}: {ex}") else: self.poutput(f"{args.param} - was: {orig_value!r}\nnow: {settable.get_value()!r}") @@ -4425,7 +4425,7 @@ def py_quit() -> None: if py_code_to_run: try: interp.runcode(py_code_to_run) # type: ignore[arg-type] - except BaseException: + except BaseException: # noqa: BLE001 # We don't care about any exception that happened in the Python code pass @@ -4448,7 +4448,7 @@ def py_quit() -> None: # Since quit() or exit() raise an EmbeddedConsoleExit, interact() exits before printing # the exitmsg. Therefore, we will not provide it one and print it manually later. interp.interact(banner=banner, exitmsg='') - except BaseException: + except BaseException: # noqa: BLE001 # We don't care about any exception that happened in the interactive console pass finally: diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 2211e6301..037e39e6c 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -77,7 +77,7 @@ def do_cd(self, arglist) -> None: else: try: os.chdir(path) - except Exception as ex: + except Exception as ex: # noqa: BLE001 err = f'{ex}' else: self.poutput(f'Successfully changed directory to {path}') diff --git a/pyproject.toml b/pyproject.toml index b45397102..0f933518f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -162,7 +162,7 @@ select = [ # "ARG", # flake8-unused-arguments (functions or methods with arguments that are never used) "ASYNC", # flake8-async (async await bugs) # "B", # flake8-bugbear (various likely bugs and design issues) - # "BLE", # flake8-blind-except (force more specific exception types than just Exception) + "BLE", # flake8-blind-except (force more specific exception types than just Exception) "C4", # flake8-comprehensions (warn about things that could be written as a comprehensions but aren't) "C90", # McCabe cyclomatic complexity (warn about functions that are too complex) "COM", # flake8-commas (forces commas at the end of every type of iterable/container diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index b8e27295a..424912b4f 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -771,7 +771,7 @@ def test_pipe_to_shell_error(base_app) -> None: # ValueError for headless Linux systems without Gtk installed # AssertionError can be raised by paste_klipper(). # PyperclipException for pyperclip-specific exceptions -except Exception: +except Exception: # noqa: BLE001 can_paste = False else: can_paste = True From 26cf7b9c980da2af6651d6ad89f0601abb225f02 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 15:04:32 -0400 Subject: [PATCH 75/79] Enabled ruff ERA ruleset for eradicating commented-out code --- cmd2/rl_utils.py | 2 +- examples/cmd_as_argument.py | 2 +- examples/decorator_example.py | 2 +- examples/example.py | 2 +- examples/hooks.py | 2 +- examples/migrating.py | 2 +- plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py | 1 - plugins/ext_test/setup.py | 6 ------ plugins/template/setup.py | 3 +-- plugins/template/tasks.py | 12 ++++++------ pyproject.toml | 2 +- tests/test_argparse_custom.py | 4 ++-- tests/test_transcript.py | 1 - 13 files changed, 16 insertions(+), 25 deletions(-) diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 7a51d71d4..c53c989e8 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -101,7 +101,7 @@ def enable_win_vt100(handle: HANDLE) -> bool: ############################################################################################################ # pyreadline3 is incomplete in terms of the Python readline API. Add the missing functions we need. ############################################################################################################ - # readline.remove_history_item() + # Add missing `readline.remove_history_item()` try: getattr(readline, 'remove_history_item') except AttributeError: diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index 2a9c7033d..e12be178d 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -20,7 +20,7 @@ class CmdLineApp(cmd2.Cmd): """Example cmd2 application.""" # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist - # default_to_shell = True + # default_to_shell = True # noqa: ERA001 MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh'] MUMBLE_FIRST = ['so', 'like', 'well'] MUMBLE_LAST = ['right?'] diff --git a/examples/decorator_example.py b/examples/decorator_example.py index 0629d422f..10b044713 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -32,7 +32,7 @@ def __init__(self, ip_addr=None, port=None, transcript_files=None) -> None: self._port = port # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist - # self.default_to_shell = True + # self.default_to_shell = True # noqa: ERA001 speak_parser = cmd2.Cmd2ArgumentParser() speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') diff --git a/examples/example.py b/examples/example.py index a24be2b0a..fc083c513 100755 --- a/examples/example.py +++ b/examples/example.py @@ -18,7 +18,7 @@ class CmdLineApp(cmd2.Cmd): """Example cmd2 application.""" # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist - # default_to_shell = True + # default_to_shell = True # noqa: ERA001 MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh'] MUMBLE_FIRST = ['so', 'like', 'well'] MUMBLE_LAST = ['right?'] diff --git a/examples/hooks.py b/examples/hooks.py index d14eb66e3..ccb9a8386 100755 --- a/examples/hooks.py +++ b/examples/hooks.py @@ -38,7 +38,7 @@ class CmdLineApp(cmd2.Cmd): """ # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist - # default_to_shell = True + # default_to_shell = True # noqa: ERA001 def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/examples/migrating.py b/examples/migrating.py index e1af0f76f..1e9c55297 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """A sample cmd application that shows how to trivially migrate a cmd application to use cmd2.""" -# import cmd2 as cmd +# import cmd2 as cmd # noqa: ERA001 import cmd # Comment this line and uncomment the one above to migrate to cmd2 import random diff --git a/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py b/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py index fc2c43fe5..1cb45f603 100644 --- a/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py +++ b/plugins/ext_test/cmd2_ext_test/cmd2_ext_test.py @@ -63,7 +63,6 @@ def fixture_teardown(self): :type self: cmd2.Cmd """ - # assert isinstance(self, cmd2.Cmd) and isinstance(self, ExternalTestMixin) for func in self._postloop_hooks: func() self.postloop() diff --git a/plugins/ext_test/setup.py b/plugins/ext_test/setup.py index a0e97ece9..b274959c6 100644 --- a/plugins/ext_test/setup.py +++ b/plugins/ext_test/setup.py @@ -2,17 +2,11 @@ import setuptools -# # get the long description from the README file here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: long_description = f.read() -# scm_version = { -# 'root': '../..', -# 'git_describe_command': "git describe --dirty --tags --long --match plugin-ext-test*", -# } - PACKAGE_DATA = { 'cmd2_ext_test': ['py.typed'], } diff --git a/plugins/template/setup.py b/plugins/template/setup.py index 34ec6f57a..3eed7f283 100644 --- a/plugins/template/setup.py +++ b/plugins/template/setup.py @@ -2,7 +2,6 @@ import setuptools -# # get the long description from the README file here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: @@ -10,7 +9,7 @@ setuptools.setup( name='cmd2-myplugin', - # use_scm_version=True, # use_scm_version doesn't work if setup.py isn't in the repository root + # use_scm_version=True, # use_scm_version doesn't work if setup.py isn't in the repository root # noqa: ERA001 version='2.0.0', description='A template used to build plugins for cmd2', long_description=long_description, diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index f83ac863e..2b2ce406b 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -180,7 +180,7 @@ def wheel(context) -> None: namespace.add_task(wheel) -# + # these two tasks are commented out so you don't # accidentally run them and upload this template to pypi # @@ -188,11 +188,11 @@ def wheel(context) -> None: # @invoke.task(pre=[sdist, wheel]) # def pypi(context): # """Build and upload a distribution to pypi""" -# context.run('twine upload dist/*') -# namespace.add_task(pypi) +# context.run('twine upload dist/*') # noqa: ERA001 +# namespace.add_task(pypi) # noqa: ERA001 # @invoke.task(pre=[sdist, wheel]) # def pypi_test(context): -# """Build and upload a distribution to https://test.pypi.org""" -# context.run('twine upload --repository-url https://test.pypi.org/legacy/ dist/*') -# namespace.add_task(pypi_test) +# """Build and upload a distribution to https://test.pypi.org""" # noqa: ERA001 +# context.run('twine upload --repository-url https://test.pypi.org/legacy/ dist/*') # noqa: ERA001 +# namespace.add_task(pypi_test) # noqa: ERA001 diff --git a/pyproject.toml b/pyproject.toml index 0f933518f..841c12cdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -173,7 +173,7 @@ select = [ "DTZ", # flake8-datetimez (warn about datetime calls where no timezone is specified) "E", # pycodestyle errors (warn about major stylistic issues like mixing spaces and tabs) # "EM", # flake8-errmsg (warn about exceptions that use string literals that aren't assigned to a variable first) - # "ERA", # eradicate (warn about commented-out code) + "ERA", # eradicate (warn about commented-out code) "EXE", # flake8-executable (warn about files with a shebang present that aren't executable or vice versa) "F", # Pyflakes (a bunch of common warnings for things like unused imports, imports shadowed by variables, etc) "FA", # flake8-future-annotations (warn if certain from __future__ imports are used but missing) diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index 0fa07dc69..bd79910e3 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -93,7 +93,7 @@ def test_apcustom_nargs_help_format(cust_app) -> None: def test_apcustom_nargs_range_validation(cust_app) -> None: - # nargs = (3,) + # nargs = (3,) # noqa: ERA001 out, err = run_cmd(cust_app, 'range --arg2 one two') assert 'Error: argument --arg2: expected at least 3 arguments' in err[2] @@ -103,7 +103,7 @@ def test_apcustom_nargs_range_validation(cust_app) -> None: out, err = run_cmd(cust_app, 'range --arg2 one two three four') assert not err - # nargs = (2,3) + # nargs = (2,3) # noqa: ERA001 out, err = run_cmd(cust_app, 'range --arg3 one') assert 'Error: argument --arg3: expected 2 to 3 arguments' in err[2] diff --git a/tests/test_transcript.py b/tests/test_transcript.py index d49d85909..c94f2b8c1 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -72,7 +72,6 @@ def do_speak(self, opts, arg) -> None: def do_mumble(self, opts, arg) -> None: """Mumbles what you tell me to.""" repetitions = opts.repeat or 1 - # arg = arg.split() for _ in range(min(repetitions, self.maxrepeats)): output = [] if random.random() < 0.33: From 88e31eb99a8c3afb3175a4e5be966ea2ef5e052f Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 15:17:06 -0400 Subject: [PATCH 76/79] Enabled ruff S ruleset for security checks Strategically disabled on various lines in cmd2 code where we are using subprocess.Popen() in a way it doesn't like as well as when we are using asserts intentionally like in transcript testing. --- cmd2/argparse_completer.py | 4 ++-- cmd2/argparse_custom.py | 3 --- cmd2/cmd2.py | 12 ++++++------ cmd2/transcript.py | 8 ++++---- cmd2/utils.py | 3 ++- pyproject.toml | 2 +- 6 files changed, 15 insertions(+), 17 deletions(-) diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 7a0760cb2..420fd6447 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -292,14 +292,14 @@ def update_mutex_groups(arg_action: argparse.Action) -> None: # If we're in a flag REMAINDER arg, force all future tokens to go to that until a double dash is hit if flag_arg_state is not None and flag_arg_state.is_remainder: - if token == '--': + if token == '--': # noqa: S105 flag_arg_state = None else: consume_argument(flag_arg_state) continue # Handle '--' which tells argparse all remaining arguments are non-flags - if token == '--' and not skip_remaining_flags: + if token == '--' and not skip_remaining_flags: # noqa: S105 # Check if there is an unfinished flag if ( flag_arg_state is not None diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 8fc7b4d95..879129307 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -1046,9 +1046,6 @@ def _format_usage( req_parts = re.findall(part_regexp, req_usage) opt_parts = re.findall(part_regexp, opt_usage) pos_parts = re.findall(part_regexp, pos_usage) - assert ' '.join(req_parts) == req_usage - assert ' '.join(opt_parts) == opt_usage - assert ' '.join(pos_parts) == pos_usage # End cmd2 customization diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 91149871a..9f5f0477e 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1327,7 +1327,7 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False) -> None: with self.sigint_protection: import subprocess - pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=self.stdout) + pipe_proc = subprocess.Popen(pager, shell=True, stdin=subprocess.PIPE, stdout=self.stdout) # noqa: S602 pipe_proc.communicate(final_msg.encode('utf-8', 'replace')) except BrokenPipeError: # This occurs if a command's output is being piped to another process and that process closes before the @@ -2589,7 +2589,7 @@ def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) # caused by certain binary characters having been printed to it. import subprocess - proc = subprocess.Popen(['stty', 'sane']) + proc = subprocess.Popen(['stty', 'sane']) # noqa: S603, S607 proc.communicate() data = plugin.CommandFinalizationData(stop, statement) @@ -2862,7 +2862,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: kwargs['executable'] = shell # For any stream that is a StdSim, we will use a pipe so we can capture its output - proc = subprocess.Popen( # type: ignore[call-overload] + proc = subprocess.Popen( # type: ignore[call-overload] # noqa: S602 statement.pipe_to, stdin=subproc_stdin, stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable] @@ -4212,7 +4212,7 @@ def do_shell(self, args: argparse.Namespace) -> None: # still receive the SIGINT since it is in the same process group as us. with self.sigint_protection: # For any stream that is a StdSim, we will use a pipe so we can capture its output - proc = subprocess.Popen( # type: ignore[call-overload] + proc = subprocess.Popen( # type: ignore[call-overload] # noqa: S602 expanded_command, stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable] stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr, # type: ignore[unreachable] @@ -4425,7 +4425,7 @@ def py_quit() -> None: if py_code_to_run: try: interp.runcode(py_code_to_run) # type: ignore[arg-type] - except BaseException: # noqa: BLE001 + except BaseException: # noqa: BLE001, S110 # We don't care about any exception that happened in the Python code pass @@ -4448,7 +4448,7 @@ def py_quit() -> None: # Since quit() or exit() raise an EmbeddedConsoleExit, interact() exits before printing # the exitmsg. Therefore, we will not provide it one and print it manually later. interp.interact(banner=banner, exitmsg='') - except BaseException: # noqa: BLE001 + except BaseException: # noqa: BLE001, S110 # We don't care about any exception that happened in the interactive console pass finally: diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 6d5c4de8b..f26f1d838 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -108,9 +108,9 @@ def _test_transcript(self, fname: str, transcript: Iterator[str]) -> None: # Read the expected result from transcript if ansi.strip_style(line).startswith(self.cmdapp.visible_prompt): message = f'\nFile {fname}, line {line_num}\nCommand was:\n{command}\nExpected: (nothing)\nGot:\n{result}\n' - assert not result.strip(), message + assert not result.strip(), message # noqa: S101 # If the command signaled the application to quit there should be no more commands - assert not stop, stop_msg + assert not stop, stop_msg # noqa: S101 continue expected_parts = [] while not ansi.strip_style(line).startswith(self.cmdapp.visible_prompt): @@ -124,13 +124,13 @@ def _test_transcript(self, fname: str, transcript: Iterator[str]) -> None: if stop: # This should only be hit if the command that set stop to True had output text - assert finished, stop_msg + assert finished, stop_msg # noqa: S101 # transform the expected text into a valid regular expression expected = ''.join(expected_parts) expected = self._transform_transcript_expected(expected) message = f'\nFile {fname}, line {line_num}\nCommand was:\n{command}\nExpected:\n{expected}\nGot:\n{result}\n' - assert re.match(expected, result, re.MULTILINE | re.DOTALL), message + assert re.match(expected, result, re.MULTILINE | re.DOTALL), message # noqa: S101 def _transform_transcript_expected(self, s: str) -> str: r"""Parse the string with slashed regexes into a valid regex. diff --git a/cmd2/utils.py b/cmd2/utils.py index 4cf1d3106..d58a2b530 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -627,7 +627,8 @@ def _reader_thread_func(self, read_stdout: bool) -> None: write_stream = self._stderr # The thread should have been started only if this stream was a pipe - assert read_stream is not None + if read_stream is None: + raise ValueError("read_stream is None") # Run until process completes while self._proc.poll() is None: diff --git a/pyproject.toml b/pyproject.toml index 841c12cdd..86ad58c07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -206,7 +206,7 @@ select = [ "RET", # flake8-return (various warnings related to implicit vs explicit return statements) "RSE", # flake8-raise (warn about unnecessary parentheses on raised exceptions) # "RUF", # Ruff-specific rules (miscellaneous grab bag of lint checks specific to Ruff) - # "S", # flake8-bandit (security oriented checks, but extremely pedantic - do not attempt to apply to unit test files) + "S", # flake8-bandit (security oriented checks, but extremely pedantic - do not attempt to apply to unit test files) # "SIM", # flake8-simplify (rules to attempt to simplify code) # "SLF", # flake8-self (warn when protected members are accessed outside of a class or file) "SLOT", # flake8-slots (warn about subclasses that should define __slots__) From 62802c8d9e2c760ab3d3918987c3b9d5b1b9c11e Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 15:51:33 -0400 Subject: [PATCH 77/79] Enable ruff N ruleset for pep8 naming conventions --- cmd2/argparse_custom.py | 8 +++---- cmd2/cmd2.py | 6 ++--- cmd2/exceptions.py | 6 ++--- cmd2/rl_utils.py | 18 +++++++-------- cmd2/transcript.py | 6 ++--- examples/migrating.py | 2 +- examples/override_parser.py | 4 +--- examples/unicode_commands.py | 4 ++-- plugins/ext_test/tasks.py | 6 ++--- plugins/template/tasks.py | 6 ++--- pyproject.toml | 22 +++++-------------- tests/test_argparse.py | 18 ++++++++------- tests/test_cmd2.py | 10 ++++----- .../test_commandset/test_commandset.py | 2 +- 14 files changed, 53 insertions(+), 65 deletions(-) diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 879129307..675995377 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -883,7 +883,7 @@ def _match_argument_wrapper(self: argparse.ArgumentParser, action: argparse.Acti ATTR_AP_COMPLETER_TYPE = 'ap_completer_type' -def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[type['ArgparseCompleter']]: +def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Optional[type['ArgparseCompleter']]: # noqa: N802 """Get the ap_completer_type attribute of an argparse ArgumentParser. This function is added by cmd2 as a method called ``get_ap_completer_type()`` to ``argparse.ArgumentParser`` class. @@ -899,7 +899,7 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti setattr(argparse.ArgumentParser, 'get_ap_completer_type', _ArgumentParser_get_ap_completer_type) -def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: type['ArgparseCompleter']) -> None: +def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_completer_type: type['ArgparseCompleter']) -> None: # noqa: N802 """Set the ap_completer_type attribute of an argparse ArgumentParser. This function is added by cmd2 as a method called ``set_ap_completer_type()`` to ``argparse.ArgumentParser`` class. @@ -918,7 +918,7 @@ def _ArgumentParser_set_ap_completer_type(self: argparse.ArgumentParser, ap_comp ############################################################################################################ # Patch ArgumentParser._check_value to support CompletionItems as choices ############################################################################################################ -def _ArgumentParser_check_value(self: argparse.ArgumentParser, action: argparse.Action, value: Any) -> None: +def _ArgumentParser_check_value(self: argparse.ArgumentParser, action: argparse.Action, value: Any) -> None: # noqa: N802 """Custom override of ArgumentParser._check_value that supports CompletionItems as choices. When evaluating choices, input is compared to CompletionItem.orig_value instead of the CompletionItem instance. @@ -950,7 +950,7 @@ def _ArgumentParser_check_value(self: argparse.ArgumentParser, action: argparse. ############################################################################################################ -def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str) -> None: # type: ignore[type-arg] +def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str) -> None: # type: ignore[type-arg] # noqa: N802 """Removes a sub-parser from a sub-parsers group. Used to remove subcommands from a parser. This function is added by cmd2 as a method called ``remove_parser()`` to ``argparse._SubParsersAction`` class. diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 9f5f0477e..3d760436d 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1973,7 +1973,7 @@ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> type[argpar :param parser: the parser to examine :return: type of ArgparseCompleter """ - Completer = Optional[type[argparse_completer.ArgparseCompleter]] + Completer = Optional[type[argparse_completer.ArgparseCompleter]] # noqa: N806 completer_type: Completer = parser.get_ap_completer_type() # type: ignore[attr-defined] if completer_type is None: @@ -4528,7 +4528,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover # Detect whether IPython is installed try: - import traitlets.config.loader as TraitletsLoader # type: ignore[import] + import traitlets.config.loader as traitlets_loader # type: ignore[import] # Allow users to install ipython from a cmd2 prompt when needed and still have ipy command work try: @@ -4569,7 +4569,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover local_vars['self'] = self # Configure IPython - config = TraitletsLoader.Config() + config = traitlets_loader.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' diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index 3fa9d64a7..0e9e9ce4c 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -7,7 +7,7 @@ ############################################################################################################ -class SkipPostcommandHooks(Exception): +class SkipPostcommandHooks(Exception): # noqa: N818 """Custom exception class for when a command has a failure bad enough to skip post command hooks, but not bad enough to print the exception to the user. """ @@ -50,7 +50,7 @@ def __init__(self, *args: Any, apply_style: bool = True) -> None: super().__init__(*args) -class PassThroughException(Exception): +class PassThroughException(Exception): # noqa: N818 """Normally all unhandled exceptions raised during commands get printed to the user. This class is used to wrap an exception that should be raised instead of printed. """ @@ -76,7 +76,7 @@ class EmbeddedConsoleExit(SystemExit): """Custom exception class for use with the py command.""" -class EmptyStatement(Exception): +class EmptyStatement(Exception): # noqa: N818 """Custom exception class for handling behavior when the user just presses .""" diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index c53c989e8..e765f28ac 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -72,24 +72,24 @@ def enable_win_vt100(handle: HANDLE) -> bool: :param handle: the handle on which to enable vt100 :return: True if vt100 characters are enabled for the handle. """ - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + enable_virtual_terminal_processing = 0x0004 # Get the current mode for this handle in the console cur_mode = DWORD(0) readline.rl.console.GetConsoleMode(handle, byref(cur_mode)) - retVal = False + ret_val = False # Check if ENABLE_VIRTUAL_TERMINAL_PROCESSING is already enabled - if (cur_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0: - retVal = True + if (cur_mode.value & enable_virtual_terminal_processing) != 0: + ret_val = True - elif readline.rl.console.SetConsoleMode(handle, cur_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING): + elif readline.rl.console.SetConsoleMode(handle, cur_mode.value | enable_virtual_terminal_processing): # Restore the original mode when we exit atexit.register(readline.rl.console.SetConsoleMode, handle, cur_mode) - retVal = True + ret_val = True - return retVal + return ret_val # Enable VT100 sequences for stdout and stderr STD_OUT_HANDLE = -11 @@ -273,10 +273,10 @@ def rl_in_search_mode() -> bool: # pragma: no cover # GNU Readline defines constants that we can use to determine if in search mode. # RL_STATE_ISEARCH 0x0000080 # RL_STATE_NSEARCH 0x0000100 - IN_SEARCH_MODE = 0x0000180 + in_search_mode = 0x0000180 readline_state = ctypes.c_int.in_dll(readline_lib, "rl_readline_state").value - return bool(IN_SEARCH_MODE & readline_state) + return bool(in_search_mode & readline_state) if rl_type == RlType.PYREADLINE: from pyreadline3.modes.emacs import ( # type: ignore[import] EmacsMode, diff --git a/cmd2/transcript.py b/cmd2/transcript.py index f26f1d838..73e65f988 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -43,7 +43,7 @@ class Cmd2TestCase(unittest.TestCase): def setUp(self) -> None: if self.cmdapp: - self._fetchTranscripts() + self._fetch_transcripts() # Trap stdout self._orig_stdout = self.cmdapp.stdout @@ -54,13 +54,13 @@ def tearDown(self) -> None: # Restore stdout self.cmdapp.stdout = self._orig_stdout - def runTest(self) -> None: # was testall + def runTest(self) -> None: # was testall # noqa: N802 if self.cmdapp: its = sorted(self.transcripts.items()) for fname, transcript in its: self._test_transcript(fname, transcript) - def _fetchTranscripts(self) -> None: + def _fetch_transcripts(self) -> None: self.transcripts = {} testfiles = cast(list[str], getattr(self.cmdapp, 'testfiles', [])) for fname in testfiles: diff --git a/examples/migrating.py b/examples/migrating.py index 1e9c55297..55740fa34 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -17,7 +17,7 @@ def do_exit(self, line) -> bool: """Exit the application.""" return True - do_EOF = do_exit + do_EOF = do_exit # noqa: N815 do_quit = do_exit def do_speak(self, line) -> None: diff --git a/examples/override_parser.py b/examples/override_parser.py index 0c74fb2df..2d4a0f9ca 100755 --- a/examples/override_parser.py +++ b/examples/override_parser.py @@ -12,9 +12,7 @@ # Next import from cmd2. It will import your module just before the cmd2.Cmd class file is imported # and therefore override the parser class it uses on its commands. -from cmd2 import ( - cmd2, -) +from cmd2 import cmd2 # noqa: E402 if __name__ == '__main__': import sys diff --git a/examples/unicode_commands.py b/examples/unicode_commands.py index 6981352a7..3321e636f 100755 --- a/examples/unicode_commands.py +++ b/examples/unicode_commands.py @@ -13,11 +13,11 @@ def __init__(self) -> None: super().__init__() self.intro = 'Welcome the Unicode example app. Note the full Unicode support: 😇 💩' - def do_𝛑print(self, _) -> None: + def do_𝛑print(self, _) -> None: # noqa: PLC2401 """This command prints 𝛑 to 5 decimal places.""" self.poutput(f"𝛑 = {math.pi:.6}") - def do_你好(self, arg) -> None: + def do_你好(self, arg) -> None: # noqa: N802, PLC2401 """This command says hello in Chinese (Mandarin).""" self.poutput("你好 " + arg) diff --git a/plugins/ext_test/tasks.py b/plugins/ext_test/tasks.py index 73c35433e..31fc3f4f0 100644 --- a/plugins/ext_test/tasks.py +++ b/plugins/ext_test/tasks.py @@ -48,15 +48,15 @@ def rmrf(items, verbose=True): @invoke.task def pytest(context, junit=False, pty=True, append_cov=False): """Run tests and code coverage using pytest""" - ROOT_PATH = TASK_ROOT.parent.parent + root_path = TASK_ROOT.parent.parent - with context.cd(str(ROOT_PATH)): + with context.cd(str(root_path)): command_str = 'pytest --cov=cmd2_ext_test --cov-report=term --cov-report=html' if append_cov: command_str += ' --cov-append' if junit: command_str += ' --junitxml=junit/test-results.xml' - command_str += ' ' + str((TASK_ROOT / 'tests').relative_to(ROOT_PATH)) + command_str += ' ' + str((TASK_ROOT / 'tests').relative_to(root_path)) context.run(command_str, pty=pty) diff --git a/plugins/template/tasks.py b/plugins/template/tasks.py index 2b2ce406b..e3e262189 100644 --- a/plugins/template/tasks.py +++ b/plugins/template/tasks.py @@ -41,15 +41,15 @@ def rmrf(items, verbose=True) -> None: @invoke.task def pytest(context, junit=False, pty=True, append_cov=False) -> None: """Run tests and code coverage using pytest.""" - ROOT_PATH = TASK_ROOT.parent.parent + root_path = TASK_ROOT.parent.parent - with context.cd(str(ROOT_PATH)): + with context.cd(str(root_path)): command_str = 'pytest --cov=cmd2_myplugin --cov-report=term --cov-report=html' if append_cov: command_str += ' --cov-append' if junit: command_str += ' --junitxml=junit/test-results.xml' - command_str += ' ' + str((TASK_ROOT / 'tests').relative_to(ROOT_PATH)) + command_str += ' ' + str((TASK_ROOT / 'tests').relative_to(root_path)) context.run(command_str, pty=pty) diff --git a/pyproject.toml b/pyproject.toml index 86ad58c07..c59d173a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -189,7 +189,7 @@ select = [ "INT", # flake8-gettext (warnings that only apply when you are internationalizing your strings) "ISC", # flake8-implicit-str-concat (warnings related to implicit vs explicit string concatenation) "LOG", # flake8-logging (warn about potential logger issues, but very pedantic) - # "N", # pep8-naming (force idiomatic naming for classes, functions/methods, and variables/arguments) + "N", # pep8-naming (force idiomatic naming for classes, functions/methods, and variables/arguments) # "NPY", # NumPy specific rules # "PD", # pandas-vet (Pandas specific rules) "PERF", # Perflint (warn about performance issues) @@ -260,6 +260,10 @@ per-file-ignores."cmd2/argparse_custom.py" = [ "UP031", # Use format specifiers instead of percent format (auto fix is unsafe) ] +per-file-ignores."cmd2/ansi.py" = [ + "N802", # Function names should be lowercase for things like Curor.UP +] + per-file-ignores."examples/*.py" = [ "ANN", # Ignore all type annotation rules in examples folder "D", # Ignore all pydocstyle rules in examples folder @@ -268,18 +272,10 @@ per-file-ignores."examples/*.py" = [ "S", # Ignore all Security rules in examples folder ] -per-file-ignores."examples/override_parser.py" = [ - "E402", # Module level import not at top of file -] - per-file-ignores."examples/scripts/*.py" = [ "F821", # Undefined name `app` ] -per-file-ignores."examples/unicode_commands.py" = [ - "PLC2401", # non-ASCII characters in function names -] - per-file-ignores."plugins/*.py" = [ "ANN", # Ignore all type annotation rules in test folders "D", # Ignore all pydocstyle rules in test folders @@ -296,10 +292,6 @@ per-file-ignores."tests/*.py" = [ "SLF", # Ignore all warnings about private or protected member access in test folders ] -per-file-ignores."tests/test_argparse.py" = [ - "PLW2901", # loop variable overwritten inside loop -] - per-file-ignores."tests/pyscript/*.py" = [ "F821", # Undefined name `app` "INP001", # Module is part of an implicit namespace @@ -312,10 +304,6 @@ per-file-ignores."tests_isolated/*.py" = [ "SLF", # Ignore all warnings about private or protected member access in test folders ] -per-file-ignores."tests_isolated/test_commandset/test_commandset.py" = [ - "PLW0603", # Using the global statement to update {name} is discouraged -] - [tool.ruff.format] # Like Black, use double quotes for strings. diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 37b0544e8..df82049f0 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -41,13 +41,14 @@ def do_say(self, args, *, keyword_arg: Optional[str] = None) -> None: """ words = [] for word in args.words: + modified_word = word if word is None: - word = '' + modified_word = '' if args.piglatin: - word = f'{word[1:]}{word[0]}ay' + modified_word = f'{word[1:]}{word[0]}ay' if args.shout: - word = word.upper() - words.append(word) + modified_word = word.upper() + words.append(modified_word) repetitions = args.repeat or 1 for i in range(min(repetitions, self.maxrepeats)): self.stdout.write(' '.join(words)) @@ -96,13 +97,14 @@ def do_speak(self, args, extra, *, keyword_arg: Optional[str] = None) -> None: """Repeat what you tell me to.""" words = [] for word in extra: + modified_word = word if word is None: - word = '' + modified_word = '' if args.piglatin: - word = f'{word[1:]}{word[0]}ay' + modified_word = f'{word[1:]}{word[0]}ay' if args.shout: - word = word.upper() - words.append(word) + modified_word = word.upper() + words.append(modified_word) repetitions = args.repeat or 1 for i in range(min(repetitions, self.maxrepeats)): self.stdout.write(' '.join(words)) diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 424912b4f..2f9073f1f 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -61,7 +61,7 @@ def cmd_wrapper(*args, **kwargs): return arg_decorator -def CreateOutsimApp(): +def create_outsim_app(): c = cmd2.Cmd() c.stdout = utils.StdSim(c.stdout) return c @@ -69,7 +69,7 @@ def CreateOutsimApp(): @pytest.fixture def outsim_app(): - return CreateOutsimApp() + return create_outsim_app() def test_version(base_app) -> None: @@ -976,7 +976,7 @@ def test_base_cmdloop_with_startup_commands() -> None: expected = intro + '\n' with mock.patch.object(sys, 'argv', testargs): - app = CreateOutsimApp() + app = create_outsim_app() app.use_rawinput = True @@ -991,7 +991,7 @@ def test_base_cmdloop_without_startup_commands() -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] with mock.patch.object(sys, 'argv', testargs): - app = CreateOutsimApp() + app = create_outsim_app() app.use_rawinput = True app.intro = 'Hello World, this is an intro ...' @@ -1012,7 +1012,7 @@ def test_cmdloop_without_rawinput() -> None: # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] with mock.patch.object(sys, 'argv', testargs): - app = CreateOutsimApp() + app = create_outsim_app() app.use_rawinput = False app.echo = False diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index eace75d0b..b8daafa04 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -832,7 +832,7 @@ def do_user_unrelated(self, ns: argparse.Namespace) -> None: def test_cross_commandset_completer(command_sets_manual, capsys) -> None: - global complete_states_expected_self + global complete_states_expected_self # noqa: PLW0603 # This tests the different ways to locate the matching CommandSet when completing an argparse argument. # Exercises the 3 cases in cmd2.Cmd._resolve_func_self() which is called during argparse tab completion. From 9e750bbf64205d9b42df6ea60df2033d5b86010f Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 16:14:54 -0400 Subject: [PATCH 78/79] Renamed functions in cmd2.ansi.Cursor to be pep8 compliant --- cmd2/ansi.py | 16 ++++++++-------- pyproject.toml | 4 ---- tests/test_ansi.py | 10 +++++----- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/cmd2/ansi.py b/cmd2/ansi.py index c29176e01..1a47bc587 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -207,33 +207,33 @@ class BgColor(AnsiSequence): #################################################################################### -# Implementations intended for direct use +# Implementations intended for direct use (do NOT use outside of cmd2) #################################################################################### class Cursor: """Create ANSI sequences to alter the cursor position.""" @staticmethod - def UP(count: int = 1) -> str: + def up(count: int = 1) -> str: """Move the cursor up a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}A" @staticmethod - def DOWN(count: int = 1) -> str: + def down(count: int = 1) -> str: """Move the cursor down a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}B" @staticmethod - def FORWARD(count: int = 1) -> str: + def forward(count: int = 1) -> str: """Move the cursor forward a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}C" @staticmethod - def BACK(count: int = 1) -> str: + def back(count: int = 1) -> str: """Move the cursor back a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}D" @staticmethod - def SET_POS(x: int, y: int) -> str: + def set_pos(x: int, y: int) -> str: """Set the cursor position to coordinates which are 1-based.""" return f"{CSI}{y};{x}H" @@ -1059,11 +1059,11 @@ def async_alert_str(*, terminal_columns: int, prompt: str, line: str, cursor_off # Move the cursor down to the last input line if cursor_input_line != num_input_terminal_lines: - terminal_str += Cursor.DOWN(num_input_terminal_lines - cursor_input_line) + terminal_str += Cursor.down(num_input_terminal_lines - cursor_input_line) # Clear each line from the bottom up so that the cursor ends up on the first prompt line total_lines = num_prompt_terminal_lines + num_input_terminal_lines - terminal_str += (clear_line() + Cursor.UP(1)) * (total_lines - 1) + terminal_str += (clear_line() + Cursor.up(1)) * (total_lines - 1) # Clear the first prompt line terminal_str += clear_line() diff --git a/pyproject.toml b/pyproject.toml index c59d173a9..0ff6ffa3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -260,10 +260,6 @@ per-file-ignores."cmd2/argparse_custom.py" = [ "UP031", # Use format specifiers instead of percent format (auto fix is unsafe) ] -per-file-ignores."cmd2/ansi.py" = [ - "N802", # Function names should be lowercase for things like Curor.UP -] - per-file-ignores."examples/*.py" = [ "ANN", # Ignore all type annotation rules in examples folder "D", # Ignore all pydocstyle rules in examples folder diff --git a/tests/test_ansi.py b/tests/test_ansi.py index 841190724..224a15b49 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -202,14 +202,14 @@ def test_clear_line() -> None: def test_cursor() -> None: count = 1 - assert ansi.Cursor.UP(count) == f"{ansi.CSI}{count}A" - assert ansi.Cursor.DOWN(count) == f"{ansi.CSI}{count}B" - assert ansi.Cursor.FORWARD(count) == f"{ansi.CSI}{count}C" - assert ansi.Cursor.BACK(count) == f"{ansi.CSI}{count}D" + assert ansi.Cursor.up(count) == f"{ansi.CSI}{count}A" + assert ansi.Cursor.down(count) == f"{ansi.CSI}{count}B" + assert ansi.Cursor.forward(count) == f"{ansi.CSI}{count}C" + assert ansi.Cursor.back(count) == f"{ansi.CSI}{count}D" x = 4 y = 5 - assert ansi.Cursor.SET_POS(x, y) == f"{ansi.CSI}{y};{x}H" + assert ansi.Cursor.set_pos(x, y) == f"{ansi.CSI}{y};{x}H" @pytest.mark.parametrize( From 096d8de99b3bc943bd2194e06be6edc119be0f51 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 16:20:04 -0400 Subject: [PATCH 79/79] Renamed functions in cmd2.ansi.Cursor to start with underscore to make it clear they are intended for internal use only --- CHANGELOG.md | 1 + cmd2/ansi.py | 14 +++++++------- tests/test_ansi.py | 10 +++++----- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3423fe707..7e98f405f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Breaking Change - `cmd2` 2.6 supports Python 3.9+ (removed support for Python 3.8) + - Renamed methods in `cmd2.ansi.Cursor` to make it clear they are intended for internal use only as was documented - Enhancements - Add support for Python 3.14 diff --git a/cmd2/ansi.py b/cmd2/ansi.py index 1a47bc587..22497f4ed 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -213,27 +213,27 @@ class Cursor: """Create ANSI sequences to alter the cursor position.""" @staticmethod - def up(count: int = 1) -> str: + def _up(count: int = 1) -> str: """Move the cursor up a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}A" @staticmethod - def down(count: int = 1) -> str: + def _down(count: int = 1) -> str: """Move the cursor down a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}B" @staticmethod - def forward(count: int = 1) -> str: + def _forward(count: int = 1) -> str: """Move the cursor forward a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}C" @staticmethod - def back(count: int = 1) -> str: + def _back(count: int = 1) -> str: """Move the cursor back a specified amount of lines (Defaults to 1).""" return f"{CSI}{count}D" @staticmethod - def set_pos(x: int, y: int) -> str: + def _set_pos(x: int, y: int) -> str: """Set the cursor position to coordinates which are 1-based.""" return f"{CSI}{y};{x}H" @@ -1059,11 +1059,11 @@ def async_alert_str(*, terminal_columns: int, prompt: str, line: str, cursor_off # Move the cursor down to the last input line if cursor_input_line != num_input_terminal_lines: - terminal_str += Cursor.down(num_input_terminal_lines - cursor_input_line) + terminal_str += Cursor._down(num_input_terminal_lines - cursor_input_line) # Clear each line from the bottom up so that the cursor ends up on the first prompt line total_lines = num_prompt_terminal_lines + num_input_terminal_lines - terminal_str += (clear_line() + Cursor.up(1)) * (total_lines - 1) + terminal_str += (clear_line() + Cursor._up(1)) * (total_lines - 1) # Clear the first prompt line terminal_str += clear_line() diff --git a/tests/test_ansi.py b/tests/test_ansi.py index 224a15b49..329f7e8ed 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -202,14 +202,14 @@ def test_clear_line() -> None: def test_cursor() -> None: count = 1 - assert ansi.Cursor.up(count) == f"{ansi.CSI}{count}A" - assert ansi.Cursor.down(count) == f"{ansi.CSI}{count}B" - assert ansi.Cursor.forward(count) == f"{ansi.CSI}{count}C" - assert ansi.Cursor.back(count) == f"{ansi.CSI}{count}D" + assert ansi.Cursor._up(count) == f"{ansi.CSI}{count}A" + assert ansi.Cursor._down(count) == f"{ansi.CSI}{count}B" + assert ansi.Cursor._forward(count) == f"{ansi.CSI}{count}C" + assert ansi.Cursor._back(count) == f"{ansi.CSI}{count}D" x = 4 y = 5 - assert ansi.Cursor.set_pos(x, y) == f"{ansi.CSI}{y};{x}H" + assert ansi.Cursor._set_pos(x, y) == f"{ansi.CSI}{y};{x}H" @pytest.mark.parametrize(