Skip to content

Enable Ruff B ruleset for flake-bugbear #1427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions cmd2/argparse_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def __call__(self) -> list[str]: ... # pragma: no cover
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 # noqa: B006


ChoicesProviderFunc = Union[ChoicesProviderFuncBase, ChoicesProviderFuncWithTokens]
Expand Down Expand Up @@ -351,7 +351,7 @@ def __call__(
begidx: int,
endidx: int,
*,
arg_tokens: dict[str, list[str]] = {},
arg_tokens: dict[str, list[str]] = {}, # noqa: B006
) -> list[str]: ... # pragma: no cover


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

def _action_get_custom_parameter(self: argparse.Action) -> Any:
f"""
Get the custom {param_name} attribute of an argparse Action.
"""
Get the custom attribute of an argparse Action.

This function is added by cmd2 as a method called ``{getter_name}()`` to ``argparse.Action`` class.
This function is added by cmd2 as a method called ``get_<param_name>()`` to ``argparse.Action`` class.

To call: ``action.{getter_name}()``
To call: ``action.get_<param_name>()``

:param self: argparse Action being queried
:return: The value of {param_name} or None if attribute does not exist
:return: The value of the custom attribute or None if attribute does not exist
"""
return getattr(self, attr_name, None)

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

def _action_set_custom_parameter(self: argparse.Action, value: Any) -> None:
f"""
Set the custom {param_name} attribute of an argparse Action.
"""
Set the custom attribute of an argparse Action.

This function is added by cmd2 as a method called ``{setter_name}()`` to ``argparse.Action`` class.
This function is added by cmd2 as a method called ``set_<param_name>()`` to ``argparse.Action`` class.

To call: ``action.{setter_name}({param_name})``
To call: ``action.set_<param_name>(<param_value>)``

:param self: argparse Action being updated
:param value: value being assigned
Expand Down
54 changes: 29 additions & 25 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
IO,
TYPE_CHECKING,
Any,
ClassVar,
Optional,
TextIO,
TypeVar,
Expand Down Expand Up @@ -297,6 +298,9 @@ class Cmd(cmd.Cmd):
ALPHABETICAL_SORT_KEY = utils.norm_fold
NATURAL_SORT_KEY = utils.natural_keys

# List for storing transcript test file names
testfiles: ClassVar[list[str]] = []

def __init__(
self,
completekey: str = 'tab',
Expand Down Expand Up @@ -373,9 +377,9 @@ def __init__(
"""
# Check if py or ipy need to be disabled in this instance
if not include_py:
setattr(self, 'do_py', None)
setattr(self, 'do_py', None) # noqa: B010
if not include_ipy:
setattr(self, 'do_ipy', None)
setattr(self, 'do_ipy', None) # noqa: B010

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

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

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

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

self.statement_parser = StatementParser(
self.statement_parser: StatementParser = StatementParser(
terminators=terminators, multiline_commands=multiline_commands, shortcuts=shortcuts
)

Expand All @@ -456,7 +460,7 @@ def __init__(
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()
self.sigint_protection: utils.ContextFlag = utils.ContextFlag()

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

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

# An optional hint which prints above tab completion suggestions
self.completion_hint = ''
self.completion_hint: str = ''

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

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

# Command parsers for this Cmd instance.
self._command_parsers = _CommandParsers(self)
self._command_parsers: _CommandParsers = _CommandParsers(self)

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

# iterate through all matching methods
for method_name, method in methods:
for _method_name, method in methods:
subcommand_name: str = getattr(method, constants.SUBCMD_ATTR_NAME)
full_command_name: str = getattr(method, constants.SUBCMD_ATTR_COMMAND)
subcmd_parser_builder = getattr(method, constants.CMD_ATTR_ARGPARSER)
Expand Down Expand Up @@ -952,7 +956,7 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) ->
if choice_name == cur_subcmd:
return find_subcommand(choice, subcmd_names)
break
raise CommandSetRegistrationError(f"Could not find subcommand '{full_command_name}'")
raise CommandSetRegistrationError(f"Could not find subcommand '{action}'")

target_parser = find_subcommand(command_parser, subcommand_names)

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

# iterate through all matching methods
for method_name, method in methods:
for _method_name, method in methods:
subcommand_name = getattr(method, constants.SUBCMD_ATTR_NAME)
command_name = getattr(method, constants.SUBCMD_ATTR_COMMAND)

Expand Down Expand Up @@ -1105,8 +1109,8 @@ def remove_settable(self, name: str) -> None:
"""
try:
del self._settables[name]
except KeyError:
raise KeyError(name + " is not a settable parameter")
except KeyError as exc:
raise KeyError(name + " is not a settable parameter") from exc

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

self.add_settable(
Settable(
Expand Down Expand Up @@ -2561,7 +2565,7 @@ def onecmd_plus_hooks(
self.exit_code = ex.code
stop = True
except PassThroughException as ex:
raise ex.wrapped_ex
raise ex.wrapped_ex from None
except Exception as ex: # noqa: BLE001
self.pexcept(ex)
finally:
Expand All @@ -2575,7 +2579,7 @@ def onecmd_plus_hooks(
self.exit_code = ex.code
stop = True
except PassThroughException as ex:
raise ex.wrapped_ex
raise ex.wrapped_ex from None
except Exception as ex: # noqa: BLE001
self.pexcept(ex)

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

redir_saved_state.redirecting = True
sys.stdout = self.stdout = new_stdout
Expand Down Expand Up @@ -4059,8 +4063,8 @@ def complete_set_value(
param = arg_tokens['param'][0]
try:
settable = self.settables[param]
except KeyError:
raise CompletionError(param + " is not a settable parameter")
except KeyError as exc:
raise CompletionError(param + " is not a settable parameter") from exc

# Create a parser with a value field based on this settable
settable_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(parents=[Cmd.set_parser_parent])
Expand Down Expand Up @@ -4528,7 +4532,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
_dummy = start_ipython # noqa: F823
except NameError:
from IPython import start_ipython # type: ignore[import]

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

setattr(self.__class__, 'testfiles', transcripts_expanded)
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))
Expand Down
4 changes: 2 additions & 2 deletions cmd2/command_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ def remove_settable(self, name: str) -> None:
"""
try:
del self._settables[name]
except KeyError:
raise KeyError(name + " is not a settable parameter")
except KeyError as exc:
raise KeyError(name + " is not a settable parameter") from exc

def sigint_handler(self) -> bool:
"""Handle a SIGINT that occurred for a command in this CommandSet.
Expand Down
8 changes: 4 additions & 4 deletions cmd2/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,15 @@ def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> Optional[bool]:
else:
new_args = (arg_parser.parse_args(parsed_arglist, namespace),)
ns = new_args[0]
except SystemExit:
raise Cmd2ArgparseError
except SystemExit as exc:
raise Cmd2ArgparseError from exc
else:
# Add wrapped statement to Namespace as cmd2_statement
setattr(ns, 'cmd2_statement', Cmd2AttributeWrapper(statement))
ns.cmd2_statement = Cmd2AttributeWrapper(statement)

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

# Remove the subcmd handler attribute from the Namespace
# since cmd2_handler is how a developer accesses it.
Expand Down
4 changes: 2 additions & 2 deletions cmd2/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def from_dict(source_dict: dict[str, Any]) -> 'Statement':
try:
value = source_dict[Statement._args_field]
except KeyError as ex:
raise KeyError(f"Statement dictionary is missing {ex} field")
raise KeyError(f"Statement dictionary is missing {ex} field") from None

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

# custom lexing
return self.split_on_punctuation(tokens)
Expand Down
4 changes: 1 addition & 3 deletions cmd2/rl_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +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.
############################################################################################################
# Add missing `readline.remove_history_item()`
try:
getattr(readline, 'remove_history_item')
except AttributeError:
if not hasattr(readline, 'remove_history_item'):

def pyreadline_remove_history_item(pos: int) -> None:
"""An implementation of remove_history_item() for pyreadline3
Expand Down
4 changes: 2 additions & 2 deletions cmd2/table_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,9 @@ def __init__(self) -> None:
to_top = line_diff
to_bottom = 0

for i in range(to_top):
for _ in range(to_top):
cell.lines.appendleft(padding_line)
for i in range(to_bottom):
for _ in range(to_bottom):
cell.lines.append(padding_line)

# Build this row one line at a time
Expand Down
2 changes: 1 addition & 1 deletion cmd2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,7 @@ def get_defining_class(meth: Callable[..., Any]) -> Optional[type[Any]]:
if isinstance(meth, functools.partial):
return get_defining_class(meth.func)
if inspect.ismethod(meth) or (
inspect.isbuiltin(meth) and getattr(meth, '__self__') is not None and getattr(meth.__self__, '__class__')
inspect.isbuiltin(meth) and hasattr(meth, '__self__') and hasattr(meth.__self__, '__class__')
):
for cls in inspect.getmro(meth.__self__.__class__): # type: ignore[attr-defined]
if meth.__name__ in cls.__dict__:
Expand Down
2 changes: 1 addition & 1 deletion examples/async_printing.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def _get_alerts(self) -> list[str]:
if rand_num > 2:
return []

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

Expand Down
4 changes: 2 additions & 2 deletions examples/cmd_as_argument.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def do_speak(self, args) -> None:
word = word.upper()
words.append(word)
repetitions = args.repeat or 1
for i in range(min(repetitions, self.maxrepeats)):
for _ in range(min(repetitions, self.maxrepeats)):
# .poutput handles newlines, and accommodates output redirection too
self.poutput(' '.join(words))

Expand All @@ -68,7 +68,7 @@ def do_speak(self, args) -> None:
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)):
for _ in range(min(repetitions, self.maxrepeats)):
output = []
if random.random() < 0.33:
output.append(random.choice(self.MUMBLE_FIRST))
Expand Down
2 changes: 1 addition & 1 deletion examples/decorator_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def do_speak(self, args: argparse.Namespace) -> None:
word = word.upper()
words.append(word)
repetitions = args.repeat or 1
for i in range(min(repetitions, self.maxrepeats)):
for _ in range(min(repetitions, self.maxrepeats)):
self.poutput(' '.join(words))

do_say = do_speak # now "say" is a synonym for "speak"
Expand Down
Loading
Loading