From 1c39a07515e384b2fe712273c0382b6b1e5b4d3e Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 24 May 2025 20:31:24 -0400 Subject: [PATCH] Enable ruff RUF ruleset for Ruff-specific rules Mostly this involved replacing mutable class variables with immutable class variables - i.e. class-level member lists were replaced with tuples. --- cmd2/__init__.py | 2 +- cmd2/cmd2.py | 2 +- cmd2/utils.py | 2 +- examples/cmd_as_argument.py | 6 +-- examples/example.py | 6 +-- examples/help_categories.py | 2 +- examples/migrating.py | 6 +-- examples/modular_commands/commandset_basic.py | 8 ++-- pyproject.toml | 2 +- tests/test_argparse_completer.py | 44 +++++++++---------- tests/test_transcript.py | 6 +-- .../test_commandset/test_commandset.py | 8 ++-- 12 files changed, 46 insertions(+), 48 deletions(-) diff --git a/cmd2/__init__.py b/cmd2/__init__.py index bdb72bd2..80d686af 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -52,7 +52,7 @@ from .py_bridge import CommandResult from .utils import CompletionMode, CustomCompletionSettings, Settable, categorize -__all__: list[str] = [ +__all__: list[str] = [ # noqa: RUF022 'COMMAND_NAME', 'DEFAULT_SHORTCUTS', # ANSI Exports diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 6f965c4b..11bcb3e9 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -281,7 +281,7 @@ def remove(self, command_method: CommandFunc) -> None: class Cmd(cmd.Cmd): """An easy but powerful framework for writing line-oriented command interpreters. - Extends the Python Standard Library’s cmd package by adding a lot of useful features + Extends the Python Standard Library's cmd package by adding a lot of useful features to the out of the box configuration. Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes. diff --git a/cmd2/utils.py b/cmd2/utils.py index 86df8e01..6ac6b3be 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -531,7 +531,7 @@ class ByteBuf: """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'] + NEWLINES = (b'\n', b'\r') def __init__(self, std_sim_instance: StdSim) -> None: self.byte_buf = bytearray() diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py index e12be178..92bab995 100755 --- a/examples/cmd_as_argument.py +++ b/examples/cmd_as_argument.py @@ -21,9 +21,9 @@ 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 # noqa: ERA001 - MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh'] - MUMBLE_FIRST = ['so', 'like', 'well'] - MUMBLE_LAST = ['right?'] + MUMBLES = ('like', '...', 'um', 'er', 'hmmm', 'ahh') + MUMBLE_FIRST = ('so', 'like', 'well') + MUMBLE_LAST = ('right?',) def __init__(self) -> None: shortcuts = dict(cmd2.DEFAULT_SHORTCUTS) diff --git a/examples/example.py b/examples/example.py index fc083c51..20918152 100755 --- a/examples/example.py +++ b/examples/example.py @@ -19,9 +19,9 @@ 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 # noqa: ERA001 - MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh'] - MUMBLE_FIRST = ['so', 'like', 'well'] - MUMBLE_LAST = ['right?'] + MUMBLES = ('like', '...', 'um', 'er', 'hmmm', 'ahh') + MUMBLE_FIRST = ('so', 'like', 'well') + MUMBLE_LAST = ('right?',) def __init__(self) -> None: shortcuts = cmd2.DEFAULT_SHORTCUTS diff --git a/examples/help_categories.py b/examples/help_categories.py index 923c1646..7a9b4aca 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -25,7 +25,7 @@ def wrapper(*args, **kwds): class HelpCategories(cmd2.Cmd): """Example cmd2 application.""" - START_TIMES = ['now', 'later', 'sometime', 'whenever'] + START_TIMES = ('now', 'later', 'sometime', 'whenever') # Command categories CMD_CAT_CONNECTING = 'Connecting' diff --git a/examples/migrating.py b/examples/migrating.py index 55740fa3..bdc08907 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -9,9 +9,9 @@ class CmdLineApp(cmd.Cmd): """Example cmd application.""" - MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh'] - MUMBLE_FIRST = ['so', 'like', 'well'] - MUMBLE_LAST = ['right?'] + MUMBLES = ('like', '...', 'um', 'er', 'hmmm', 'ahh') + MUMBLE_FIRST = ('so', 'like', 'well') + MUMBLE_LAST = ('right?',) def do_exit(self, line) -> bool: """Exit the application.""" diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index d90e56ae..d43a9e47 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -12,17 +12,17 @@ @with_default_category('Basic Completion') class BasicCompletionCommandSet(CommandSet): # List of strings used with completion functions - food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato'] - sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] + food_item_strs = ('Pizza', 'Ham', 'Ham Sandwich', 'Potato') + sport_item_strs = ('Bat', 'Basket', 'Basketball', 'Football', 'Space Ball') # This data is used to demonstrate delimiter_complete - file_strs = [ + file_strs = ( '/home/user/file.db', '/home/user/file space.db', '/home/user/another.db', '/home/other user/maps.db', '/home/other user/tests.db', - ] + ) def do_flag_based(self, statement: Statement) -> None: """Tab completes arguments based on a preceding flag using flag_based_complete diff --git a/pyproject.toml b/pyproject.toml index e25d1562..d7788b0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -205,7 +205,7 @@ select = [ "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) + "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) diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 4605a34b..f6561321 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -107,17 +107,17 @@ 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) - 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'] - choices_from_provider = ['choices', 'provider', 'probably', 'improved'] - completion_item_choices = [CompletionItem('choice_1', 'A description'), CompletionItem('choice_2', 'Another description')] + # tuples (for sake of immutability) 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') + choices_from_provider = ('choices', 'provider', 'probably', 'improved') + completion_item_choices = (CompletionItem('choice_1', 'A description'), CompletionItem('choice_2', 'Another description')) # 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")] + num_completion_items = (CompletionItem(5, "Five"), CompletionItem(1.5, "One.Five"), CompletionItem(2, "Five")) - def choices_provider(self) -> list[str]: + def choices_provider(self) -> tuple[str]: """Method that provides choices""" return self.choices_from_provider @@ -179,9 +179,9 @@ def do_choices(self, args: argparse.Namespace) -> None: ############################################################################################################ # Begin code related to testing completer parameter ############################################################################################################ - completions_for_flag = ['completions', 'flag', 'fairly', 'complete'] - completions_for_pos_1 = ['completions', 'positional_1', 'probably', 'missed', 'spot'] - completions_for_pos_2 = ['completions', 'positional_2', 'probably', 'missed', 'me'] + completions_for_flag = ('completions', 'flag', 'fairly', 'complete') + 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]: return self.basic_complete(text, line, begidx, endidx, self.completions_for_flag) @@ -208,12 +208,12 @@ def do_completer(self, args: argparse.Namespace) -> None: ############################################################################################################ # Begin code related to nargs ############################################################################################################ - set_value_choices = ['set', 'value', 'choices'] - one_or_more_choices = ['one', 'or', 'more', 'choices'] - optional_choices = ['a', 'few', 'optional', 'choices'] - range_choices = ['some', 'range', 'choices'] - remainder_choices = ['remainder', 'choices'] - positional_choices = ['the', 'positional', 'choices'] + set_value_choices = ('set', 'value', 'choices') + one_or_more_choices = ('one', 'or', 'more', 'choices') + optional_choices = ('a', 'few', 'optional', 'choices') + range_choices = ('some', 'range', 'choices') + remainder_choices = ('remainder', 'choices') + positional_choices = ('the', 'positional', 'choices') nargs_parser = Cmd2ArgumentParser() @@ -572,10 +572,9 @@ def test_autocomp_flag_choices_completion(ac_app, flag, text, completions) -> No # Numbers will be sorted in ascending order and then converted to strings by ArgparseCompleter if completions and all(isinstance(x, numbers.Number) for x in completions): - completions.sort() - completions = [str(x) for x in completions] + completions = [str(x) for x in sorted(completions)] else: - completions.sort(key=ac_app.default_sort_key) + completions = sorted(completions, key=ac_app.default_sort_key) assert ac_app.completion_matches == completions @@ -606,10 +605,9 @@ def test_autocomp_positional_choices_completion(ac_app, pos, text, completions) # Numbers will be sorted in ascending order and then converted to strings by ArgparseCompleter if completions and all(isinstance(x, numbers.Number) for x in completions): - completions.sort() - completions = [str(x) for x in completions] + completions = [str(x) for x in sorted(completions)] else: - completions.sort(key=ac_app.default_sort_key) + completions = sorted(completions, key=ac_app.default_sort_key) assert ac_app.completion_matches == completions diff --git a/tests/test_transcript.py b/tests/test_transcript.py index c94f2b8c..0739c0c7 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -28,9 +28,9 @@ class CmdLineApp(cmd2.Cmd): - MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh'] - MUMBLE_FIRST = ['so', 'like', 'well'] - MUMBLE_LAST = ['right?'] + MUMBLES = ('like', '...', 'um', 'er', 'hmmm', 'ahh') + MUMBLE_FIRST = ('so', 'like', 'well') + MUMBLE_LAST = ('right?',) def __init__(self, *args, **kwargs) -> None: self.maxrepeats = 3 diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index b8daafa0..d26d3c39 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -783,7 +783,7 @@ def test_static_subcommands(static_subcommands_app) -> None: class SupportFuncProvider(cmd2.CommandSet): """CommandSet which provides a support function (complete_states) to other CommandSets""" - states = ['alabama', 'alaska', 'arizona', 'arkansas', 'california', 'colorado', 'connecticut', 'delaware'] + states = ('alabama', 'alaska', 'arizona', 'arkansas', 'california', 'colorado', 'connecticut', 'delaware') def __init__(self, dummy) -> None: """Dummy variable prevents this from being autoloaded in other tests""" @@ -862,7 +862,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys) -> None: complete_states_expected_self = None assert first_match == 'alabama' - assert command_sets_manual.completion_matches == SupportFuncProvider.states + assert command_sets_manual.completion_matches == list(SupportFuncProvider.states) assert ( getattr(command_sets_manual.cmd_func('user_sub1').__func__, cmd2.constants.CMD_ATTR_HELP_CATEGORY) == 'With Completer' @@ -888,7 +888,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys) -> None: complete_states_expected_self = None assert first_match == 'alabama' - assert command_sets_manual.completion_matches == SupportFuncProvider.states + assert command_sets_manual.completion_matches == list(SupportFuncProvider.states) command_sets_manual.unregister_command_set(user_unrelated) command_sets_manual.unregister_command_set(func_provider) @@ -911,7 +911,7 @@ def test_cross_commandset_completer(command_sets_manual, capsys) -> None: complete_states_expected_self = None assert first_match == 'alabama' - assert command_sets_manual.completion_matches == SupportFuncProvider.states + assert command_sets_manual.completion_matches == list(SupportFuncProvider.states) command_sets_manual.unregister_command_set(user_unrelated) command_sets_manual.unregister_command_set(user_sub1)