Skip to content

Commit bd9038b

Browse files
authored
Enable Ruff B ruleset for flake-bugbear (#1427)
* Start of enabling ruff B ruleset * Finish enabling ruff B rulset for flake8-bugbear Disabled B010 rule for not calling setattr with constant attribute value in cmd2/argparse_custom.py file due to type checking going nuts if we tried to apply it. * Removed zip parameter added in Python 3.10 and disabled rule that auto-added it * Restoring one line I don't think I should have changed
1 parent 6ba8f32 commit bd9038b

20 files changed

+80
-71
lines changed

cmd2/argparse_custom.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def __call__(self) -> list[str]: ... # pragma: no cover
319319
class ChoicesProviderFuncWithTokens(Protocol):
320320
"""Function that returns a list of choices in support of tab completion and accepts a dictionary of prior arguments."""
321321

322-
def __call__(self, *, arg_tokens: dict[str, list[str]] = {}) -> list[str]: ... # pragma: no cover
322+
def __call__(self, *, arg_tokens: dict[str, list[str]] = {}) -> list[str]: ... # pragma: no cover # noqa: B006
323323

324324

325325
ChoicesProviderFunc = Union[ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens]
@@ -351,7 +351,7 @@ def __call__(
351351
begidx: int,
352352
endidx: int,
353353
*,
354-
arg_tokens: dict[str, list[str]] = {},
354+
arg_tokens: dict[str, list[str]] = {}, # noqa: B006
355355
) -> list[str]: ... # pragma: no cover
356356

357357

@@ -640,15 +640,15 @@ def register_argparse_argument_parameter(param_name: str, param_type: Optional[t
640640
getter_name = f'get_{param_name}'
641641

642642
def _action_get_custom_parameter(self: argparse.Action) -> Any:
643-
f"""
644-
Get the custom {param_name} attribute of an argparse Action.
643+
"""
644+
Get the custom attribute of an argparse Action.
645645
646-
This function is added by cmd2 as a method called ``{getter_name}()`` to ``argparse.Action`` class.
646+
This function is added by cmd2 as a method called ``get_<param_name>()`` to ``argparse.Action`` class.
647647
648-
To call: ``action.{getter_name}()``
648+
To call: ``action.get_<param_name>()``
649649
650650
:param self: argparse Action being queried
651-
:return: The value of {param_name} or None if attribute does not exist
651+
:return: The value of the custom attribute or None if attribute does not exist
652652
"""
653653
return getattr(self, attr_name, None)
654654

@@ -657,12 +657,12 @@ def _action_get_custom_parameter(self: argparse.Action) -> Any:
657657
setter_name = f'set_{param_name}'
658658

659659
def _action_set_custom_parameter(self: argparse.Action, value: Any) -> None:
660-
f"""
661-
Set the custom {param_name} attribute of an argparse Action.
660+
"""
661+
Set the custom attribute of an argparse Action.
662662
663-
This function is added by cmd2 as a method called ``{setter_name}()`` to ``argparse.Action`` class.
663+
This function is added by cmd2 as a method called ``set_<param_name>()`` to ``argparse.Action`` class.
664664
665-
To call: ``action.{setter_name}({param_name})``
665+
To call: ``action.set_<param_name>(<param_value>)``
666666
667667
:param self: argparse Action being updated
668668
:param value: value being assigned

cmd2/cmd2.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
IO,
5858
TYPE_CHECKING,
5959
Any,
60+
ClassVar,
6061
Optional,
6162
TextIO,
6263
TypeVar,
@@ -297,6 +298,9 @@ class Cmd(cmd.Cmd):
297298
ALPHABETICAL_SORT_KEY = utils.norm_fold
298299
NATURAL_SORT_KEY = utils.natural_keys
299300

301+
# List for storing transcript test file names
302+
testfiles: ClassVar[list[str]] = []
303+
300304
def __init__(
301305
self,
302306
completekey: str = 'tab',
@@ -373,9 +377,9 @@ def __init__(
373377
"""
374378
# Check if py or ipy need to be disabled in this instance
375379
if not include_py:
376-
setattr(self, 'do_py', None)
380+
setattr(self, 'do_py', None) # noqa: B010
377381
if not include_ipy:
378-
setattr(self, 'do_ipy', None)
382+
setattr(self, 'do_ipy', None) # noqa: B010
379383

380384
# initialize plugin system
381385
# needs to be done before we call __init__(0)
@@ -401,7 +405,7 @@ def __init__(
401405
# The maximum number of CompletionItems to display during tab completion. If the number of completion
402406
# suggestions exceeds this number, they will be displayed in the typical columnized format and will
403407
# not include the description value of the CompletionItems.
404-
self.max_completion_items = 50
408+
self.max_completion_items: int = 50
405409

406410
# A dictionary mapping settable names to their Settable instance
407411
self._settables: dict[str, Settable] = {}
@@ -414,7 +418,7 @@ def __init__(
414418
self.build_settables()
415419

416420
# Use as prompt for multiline commands on the 2nd+ line of input
417-
self.continuation_prompt = '> '
421+
self.continuation_prompt: str = '> '
418422

419423
# Allow access to your application in embedded Python shells and scripts py via self
420424
self.self_in_py = False
@@ -445,7 +449,7 @@ def __init__(
445449
# True if running inside a Python shell or pyscript, False otherwise
446450
self._in_py = False
447451

448-
self.statement_parser = StatementParser(
452+
self.statement_parser: StatementParser = StatementParser(
449453
terminators=terminators, multiline_commands=multiline_commands, shortcuts=shortcuts
450454
)
451455

@@ -456,7 +460,7 @@ def __init__(
456460
self._script_dir: list[str] = []
457461

458462
# Context manager used to protect critical sections in the main thread from stopping due to a KeyboardInterrupt
459-
self.sigint_protection = utils.ContextFlag()
463+
self.sigint_protection: utils.ContextFlag = utils.ContextFlag()
460464

461465
# If the current command created a process to pipe to, then this will be a ProcReader object.
462466
# Otherwise it will be None. It's used to know when a pipe process can be killed and/or waited upon.
@@ -551,7 +555,7 @@ def __init__(
551555
# command and category names
552556
# alias, macro, settable, and shortcut names
553557
# tab completion results when self.matches_sorted is False
554-
self.default_sort_key = Cmd.ALPHABETICAL_SORT_KEY
558+
self.default_sort_key: Callable[[str], str] = Cmd.ALPHABETICAL_SORT_KEY
555559

556560
############################################################################################################
557561
# The following variables are used by tab completion functions. They are reset each time complete() is run
@@ -567,14 +571,14 @@ def __init__(
567571
self.allow_closing_quote = True
568572

569573
# An optional hint which prints above tab completion suggestions
570-
self.completion_hint = ''
574+
self.completion_hint: str = ''
571575

572576
# Normally cmd2 uses readline's formatter to columnize the list of completion suggestions.
573577
# If a custom format is preferred, write the formatted completions to this string. cmd2 will
574578
# then print it instead of the readline format. ANSI style sequences and newlines are supported
575579
# when using this value. Even when using formatted_completions, the full matches must still be returned
576580
# from your completer function. ArgparseCompleter writes its tab completion tables to this string.
577-
self.formatted_completions = ''
581+
self.formatted_completions: str = ''
578582

579583
# Used by complete() for readline tab completion
580584
self.completion_matches: list[str] = []
@@ -594,10 +598,10 @@ def __init__(
594598
# Set to True before returning matches to complete() in cases where matches have already been sorted.
595599
# If False, then complete() will sort the matches using self.default_sort_key before they are displayed.
596600
# This does not affect self.formatted_completions.
597-
self.matches_sorted = False
601+
self.matches_sorted: bool = False
598602

599603
# Command parsers for this Cmd instance.
600-
self._command_parsers = _CommandParsers(self)
604+
self._command_parsers: _CommandParsers = _CommandParsers(self)
601605

602606
# Add functions decorated to be subcommands
603607
self._register_subcommands(self)
@@ -915,7 +919,7 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
915919
)
916920

917921
# iterate through all matching methods
918-
for method_name, method in methods:
922+
for _method_name, method in methods:
919923
subcommand_name: str = getattr(method, constants.SUBCMD_ATTR_NAME)
920924
full_command_name: str = getattr(method, constants.SUBCMD_ATTR_COMMAND)
921925
subcmd_parser_builder = getattr(method, constants.CMD_ATTR_ARGPARSER)
@@ -952,7 +956,7 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) ->
952956
if choice_name == cur_subcmd:
953957
return find_subcommand(choice, subcmd_names)
954958
break
955-
raise CommandSetRegistrationError(f"Could not find subcommand '{full_command_name}'")
959+
raise CommandSetRegistrationError(f"Could not find subcommand '{action}'")
956960

957961
target_parser = find_subcommand(command_parser, subcommand_names)
958962

@@ -1021,7 +1025,7 @@ def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
10211025
)
10221026

10231027
# iterate through all matching methods
1024-
for method_name, method in methods:
1028+
for _method_name, method in methods:
10251029
subcommand_name = getattr(method, constants.SUBCMD_ATTR_NAME)
10261030
command_name = getattr(method, constants.SUBCMD_ATTR_COMMAND)
10271031

@@ -1105,8 +1109,8 @@ def remove_settable(self, name: str) -> None:
11051109
"""
11061110
try:
11071111
del self._settables[name]
1108-
except KeyError:
1109-
raise KeyError(name + " is not a settable parameter")
1112+
except KeyError as exc:
1113+
raise KeyError(name + " is not a settable parameter") from exc
11101114

11111115
def build_settables(self) -> None:
11121116
"""Create the dictionary of user-settable parameters."""
@@ -1119,11 +1123,11 @@ def allow_style_type(value: str) -> ansi.AllowStyle:
11191123
"""Converts a string value into an ansi.AllowStyle."""
11201124
try:
11211125
return ansi.AllowStyle[value.upper()]
1122-
except KeyError:
1126+
except KeyError as esc:
11231127
raise ValueError(
11241128
f"must be {ansi.AllowStyle.ALWAYS}, {ansi.AllowStyle.NEVER}, or "
11251129
f"{ansi.AllowStyle.TERMINAL} (case-insensitive)"
1126-
)
1130+
) from esc
11271131

11281132
self.add_settable(
11291133
Settable(
@@ -2561,7 +2565,7 @@ def onecmd_plus_hooks(
25612565
self.exit_code = ex.code
25622566
stop = True
25632567
except PassThroughException as ex:
2564-
raise ex.wrapped_ex
2568+
raise ex.wrapped_ex from None
25652569
except Exception as ex: # noqa: BLE001
25662570
self.pexcept(ex)
25672571
finally:
@@ -2575,7 +2579,7 @@ def onecmd_plus_hooks(
25752579
self.exit_code = ex.code
25762580
stop = True
25772581
except PassThroughException as ex:
2578-
raise ex.wrapped_ex
2582+
raise ex.wrapped_ex from None
25792583
except Exception as ex: # noqa: BLE001
25802584
self.pexcept(ex)
25812585

@@ -2896,7 +2900,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
28962900
# Use line buffering
28972901
new_stdout = cast(TextIO, open(utils.strip_quotes(statement.output_to), mode=mode, buffering=1)) # noqa: SIM115
28982902
except OSError as ex:
2899-
raise RedirectionError(f'Failed to redirect because: {ex}')
2903+
raise RedirectionError('Failed to redirect output') from ex
29002904

29012905
redir_saved_state.redirecting = True
29022906
sys.stdout = self.stdout = new_stdout
@@ -4059,8 +4063,8 @@ def complete_set_value(
40594063
param = arg_tokens['param'][0]
40604064
try:
40614065
settable = self.settables[param]
4062-
except KeyError:
4063-
raise CompletionError(param + " is not a settable parameter")
4066+
except KeyError as exc:
4067+
raise CompletionError(param + " is not a settable parameter") from exc
40644068

40654069
# Create a parser with a value field based on this settable
40664070
settable_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(parents=[Cmd.set_parser_parent])
@@ -4528,7 +4532,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover
45284532

45294533
# Allow users to install ipython from a cmd2 prompt when needed and still have ipy command work
45304534
try:
4531-
start_ipython # noqa: F823
4535+
_dummy = start_ipython # noqa: F823
45324536
except NameError:
45334537
from IPython import start_ipython # type: ignore[import]
45344538

@@ -5145,7 +5149,7 @@ class TestMyAppCase(Cmd2TestCase):
51455149
self.poutput(f'cmd2 app: {sys.argv[0]}')
51465150
self.poutput(ansi.style(f'collected {num_transcripts} transcript{plural}', bold=True))
51475151

5148-
setattr(self.__class__, 'testfiles', transcripts_expanded)
5152+
self.__class__.testfiles = transcripts_expanded
51495153
sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main()
51505154
testcase = TestMyAppCase()
51515155
stream = cast(TextIO, utils.StdSim(sys.stderr))

cmd2/command_definition.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@ def remove_settable(self, name: str) -> None:
172172
"""
173173
try:
174174
del self._settables[name]
175-
except KeyError:
176-
raise KeyError(name + " is not a settable parameter")
175+
except KeyError as exc:
176+
raise KeyError(name + " is not a settable parameter") from exc
177177

178178
def sigint_handler(self) -> bool:
179179
"""Handle a SIGINT that occurred for a command in this CommandSet.

cmd2/decorators.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,15 +369,15 @@ def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]:
369369
else:
370370
new_args = (arg_parser.parse_args(parsed_arglist, namespace),)
371371
ns = new_args[0]
372-
except SystemExit:
373-
raise Cmd2ArgparseError
372+
except SystemExit as exc:
373+
raise Cmd2ArgparseError from exc
374374
else:
375375
# Add wrapped statement to Namespace as cmd2_statement
376-
setattr(ns, 'cmd2_statement', Cmd2AttributeWrapper(statement))
376+
ns.cmd2_statement = Cmd2AttributeWrapper(statement)
377377

378378
# Add wrapped subcmd handler (which can be None) to Namespace as cmd2_handler
379379
handler = getattr(ns, constants.NS_ATTR_SUBCMD_HANDLER, None)
380-
setattr(ns, 'cmd2_handler', Cmd2AttributeWrapper(handler))
380+
ns.cmd2_handler = Cmd2AttributeWrapper(handler)
381381

382382
# Remove the subcmd handler attribute from the Namespace
383383
# since cmd2_handler is how a developer accesses it.

cmd2/parsing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def from_dict(source_dict: dict[str, Any]) -> 'Statement':
232232
try:
233233
value = source_dict[Statement._args_field]
234234
except KeyError as ex:
235-
raise KeyError(f"Statement dictionary is missing {ex} field")
235+
raise KeyError(f"Statement dictionary is missing {ex} field") from None
236236

237237
# Pass the rest at kwargs (minus args)
238238
kwargs = source_dict.copy()
@@ -377,7 +377,7 @@ def tokenize(self, line: str) -> list[str]:
377377
try:
378378
tokens = shlex_split(line)
379379
except ValueError as ex:
380-
raise Cmd2ShlexError(ex)
380+
raise Cmd2ShlexError(ex) from None
381381

382382
# custom lexing
383383
return self.split_on_punctuation(tokens)

cmd2/rl_utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,7 @@ def enable_win_vt100(handle: HANDLE) -> bool:
101101
# pyreadline3 is incomplete in terms of the Python readline API. Add the missing functions we need.
102102
############################################################################################################
103103
# Add missing `readline.remove_history_item()`
104-
try:
105-
getattr(readline, 'remove_history_item')
106-
except AttributeError:
104+
if not hasattr(readline, 'remove_history_item'):
107105

108106
def pyreadline_remove_history_item(pos: int) -> None:
109107
"""An implementation of remove_history_item() for pyreadline3

cmd2/table_creator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,9 @@ def __init__(self) -> None:
498498
to_top = line_diff
499499
to_bottom = 0
500500

501-
for i in range(to_top):
501+
for _ in range(to_top):
502502
cell.lines.appendleft(padding_line)
503-
for i in range(to_bottom):
503+
for _ in range(to_bottom):
504504
cell.lines.append(padding_line)
505505

506506
# Build this row one line at a time

cmd2/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,7 @@ def get_defining_class(meth: Callable[..., Any]) -> Optional[type[Any]]:
11331133
if isinstance(meth, functools.partial):
11341134
return get_defining_class(meth.func)
11351135
if inspect.ismethod(meth) or (
1136-
inspect.isbuiltin(meth) and getattr(meth, '__self__') is not None and getattr(meth.__self__, '__class__')
1136+
inspect.isbuiltin(meth) and hasattr(meth, '__self__') and hasattr(meth.__self__, '__class__')
11371137
):
11381138
for cls in inspect.getmro(meth.__self__.__class__): # type: ignore[attr-defined]
11391139
if meth.__name__ in cls.__dict__:

examples/async_printing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def _get_alerts(self) -> list[str]:
103103
if rand_num > 2:
104104
return []
105105

106-
for i in range(rand_num):
106+
for _ in range(rand_num):
107107
self._alert_count += 1
108108
alerts.append(f"Alert {self._alert_count}")
109109

examples/cmd_as_argument.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def do_speak(self, args) -> None:
5353
word = word.upper()
5454
words.append(word)
5555
repetitions = args.repeat or 1
56-
for i in range(min(repetitions, self.maxrepeats)):
56+
for _ in range(min(repetitions, self.maxrepeats)):
5757
# .poutput handles newlines, and accommodates output redirection too
5858
self.poutput(' '.join(words))
5959

@@ -68,7 +68,7 @@ def do_speak(self, args) -> None:
6868
def do_mumble(self, args) -> None:
6969
"""Mumbles what you tell me to."""
7070
repetitions = args.repeat or 1
71-
for i in range(min(repetitions, self.maxrepeats)):
71+
for _ in range(min(repetitions, self.maxrepeats)):
7272
output = []
7373
if random.random() < 0.33:
7474
output.append(random.choice(self.MUMBLE_FIRST))

examples/decorator_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def do_speak(self, args: argparse.Namespace) -> None:
5151
word = word.upper()
5252
words.append(word)
5353
repetitions = args.repeat or 1
54-
for i in range(min(repetitions, self.maxrepeats)):
54+
for _ in range(min(repetitions, self.maxrepeats)):
5555
self.poutput(' '.join(words))
5656

5757
do_say = do_speak # now "say" is a synonym for "speak"

0 commit comments

Comments
 (0)