Skip to content

Enable ruff SIM ruleset which uses flake8-simplify rules #1422

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 1 commit into from
May 24, 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
6 changes: 2 additions & 4 deletions cmd2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"""This simply imports certain things for backwards compatibility."""

import argparse
import contextlib
import importlib.metadata as importlib_metadata
import sys

try:
with contextlib.suppress(importlib_metadata.PackageNotFoundError):
__version__ = importlib_metadata.version(__name__)
except importlib_metadata.PackageNotFoundError: # pragma: no cover
# package is not installed
pass

from .ansi import (
Bg,
Expand Down
58 changes: 25 additions & 33 deletions cmd2/argparse_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,7 @@ def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool:
return False

# Flags can't have a space
if ' ' in token:
return False

# Starts like a flag
return True
return ' ' not in token


class _ArgumentState:
Expand Down Expand Up @@ -368,34 +364,30 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
# Otherwise treat as a positional argument
else:
# If we aren't current tracking a positional, then get the next positional arg to handle this token
if pos_arg_state is None:
# Make sure we are still have positional arguments to parse
if remaining_positionals:
action = remaining_positionals.popleft()

# Are we at a subcommand? If so, forward to the matching completer
if action == self._subcommand_action:
if token in self._subcommand_action.choices:
# Merge self._parent_tokens and consumed_arg_values
parent_tokens = {**self._parent_tokens, **consumed_arg_values}

# Include the subcommand name if its destination was set
if action.dest != argparse.SUPPRESS:
parent_tokens[action.dest] = [token]

parser: argparse.ArgumentParser = self._subcommand_action.choices[token]
completer_type = self._cmd2_app._determine_ap_completer_type(parser)

completer = completer_type(parser, self._cmd2_app, parent_tokens=parent_tokens)

return completer.complete(
text, line, begidx, endidx, tokens[token_index + 1 :], cmd_set=cmd_set
)
# Invalid subcommand entered, so no way to complete remaining tokens
return []

# Otherwise keep track of the argument
pos_arg_state = _ArgumentState(action)
if pos_arg_state is None and remaining_positionals:
action = remaining_positionals.popleft()

# Are we at a subcommand? If so, forward to the matching completer
if action == self._subcommand_action:
if token in self._subcommand_action.choices:
# Merge self._parent_tokens and consumed_arg_values
parent_tokens = {**self._parent_tokens, **consumed_arg_values}

# Include the subcommand name if its destination was set
if action.dest != argparse.SUPPRESS:
parent_tokens[action.dest] = [token]

parser: argparse.ArgumentParser = self._subcommand_action.choices[token]
completer_type = self._cmd2_app._determine_ap_completer_type(parser)

completer = completer_type(parser, self._cmd2_app, parent_tokens=parent_tokens)

return completer.complete(text, line, begidx, endidx, tokens[token_index + 1 :], cmd_set=cmd_set)
# Invalid subcommand entered, so no way to complete remaining tokens
return []

# Otherwise keep track of the argument
pos_arg_state = _ArgumentState(action)

# Check if we have a positional to consume this token
if pos_arg_state is not None:
Expand Down
12 changes: 3 additions & 9 deletions cmd2/argparse_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,12 +828,8 @@ def _add_argument_wrapper(
def _get_nargs_pattern_wrapper(self: argparse.ArgumentParser, action: argparse.Action) -> str:
# Wrapper around ArgumentParser._get_nargs_pattern behavior to support nargs ranges
nargs_range = action.get_nargs_range() # type: ignore[attr-defined]
if nargs_range is not None:
if nargs_range[1] == constants.INFINITY:
range_max = ''
else:
range_max = nargs_range[1] # type: ignore[assignment]

if nargs_range:
range_max = '' if nargs_range[1] == constants.INFINITY else nargs_range[1]
nargs_pattern = f'(-*A{{{nargs_range[0]},{range_max}}}-*)'

# if this is an optional action, -- is not allowed
Expand Down Expand Up @@ -1265,14 +1261,12 @@ def add_subparsers(self, **kwargs: Any) -> argparse._SubParsersAction: # type:
def error(self, message: str) -> NoReturn:
"""Custom override that applies custom formatting to the error message."""
lines = message.split('\n')
linum = 0
formatted_message = ''
for line in lines:
for linum, line in enumerate(lines):
if linum == 0:
formatted_message = 'Error: ' + line
else:
formatted_message += '\n ' + line
linum += 1

self.print_usage(sys.stderr)
formatted_message = ansi.style_error(formatted_message)
Expand Down
42 changes: 16 additions & 26 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
# setting is True
import argparse
import cmd
import contextlib
import copy
import functools
import glob
Expand All @@ -48,10 +49,6 @@
namedtuple,
)
from collections.abc import Callable, Iterable, Mapping
from contextlib import (
redirect_stdout,
suppress,
)
from types import (
FrameType,
ModuleType,
Expand Down Expand Up @@ -123,10 +120,8 @@
)

# NOTE: When using gnureadline with Python 3.13, start_ipython needs to be imported before any readline-related stuff
try:
with contextlib.suppress(ImportError):
from IPython import start_ipython # type: ignore[import]
except ImportError:
pass

from .rl_utils import (
RlType,
Expand Down Expand Up @@ -1098,9 +1093,8 @@ 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 and settable.name not in self._settables:
raise KeyError(f'Duplicate settable: {settable.name}')
if not self.always_prefix_settables and settable.name in self.settables and settable.name not in self._settables:
raise KeyError(f'Duplicate settable: {settable.name}')
self._settables[settable.name] = settable

def remove_settable(self, name: str) -> None:
Expand Down Expand Up @@ -1306,7 +1300,7 @@ def ppaged(self, msg: Any, *, end: str = '\n', chop: bool = False) -> None:
# Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
functional_terminal = False

if self.stdin.isatty() and self.stdout.isatty():
if self.stdin.isatty() and self.stdout.isatty(): # noqa: SIM102
if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
functional_terminal = True

Expand Down Expand Up @@ -2844,8 +2838,8 @@ 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 = open(read_fd) # noqa: SIM115
new_stdout: TextIO = cast(TextIO, open(write_fd, 'w')) # noqa: SIM115

# 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
Expand Down Expand Up @@ -2875,7 +2869,7 @@ 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.
with suppress(subprocess.TimeoutExpired):
with contextlib.suppress(subprocess.TimeoutExpired):
proc.wait(0.2)

# Check if the pipe process already exited
Expand All @@ -2894,7 +2888,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)) # noqa: SIM115
except OSError as ex:
raise RedirectionError(f'Failed to redirect because: {ex}')

Expand All @@ -2915,7 +2909,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+")) # noqa: SIM115
redir_saved_state.redirecting = True
sys.stdout = self.stdout = new_stdout

Expand All @@ -2941,11 +2935,9 @@ def _restore_output(self, statement: Statement, saved_redir_state: utils.Redirec
self.stdout.seek(0)
write_to_paste_buffer(self.stdout.read())

try:
with contextlib.suppress(BrokenPipeError):
# Close the file or pipe that stdout was redirected to
self.stdout.close()
except BrokenPipeError:
pass

# Restore the stdout values
self.stdout = cast(TextIO, saved_redir_state.saved_self_stdout)
Expand Down Expand Up @@ -3199,11 +3191,9 @@ def _read_command_line(self, prompt: str) -> str:
"""
try:
# Wrap in try since terminal_lock may not be locked
try:
with contextlib.suppress(RuntimeError):
# Command line is about to be drawn. Allow asynchronous changes to the terminal.
self.terminal_lock.release()
except RuntimeError:
pass
return self.read_input(prompt, completion_mode=utils.CompletionMode.COMMANDS)
except EOFError:
return 'eof'
Expand Down Expand Up @@ -3948,7 +3938,7 @@ def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None:
result = io.StringIO()

# try to redirect system stdout
with redirect_stdout(result):
with contextlib.redirect_stdout(result):
# save our internal stdout
stdout_orig = self.stdout
try:
Expand Down Expand Up @@ -4245,7 +4235,7 @@ def _reset_py_display() -> None:
# Delete any prompts that have been set
attributes = ['ps1', 'ps2', 'ps3']
for cur_attr in attributes:
with suppress(KeyError):
with contextlib.suppress(KeyError):
del sys.__dict__[cur_attr]

# Reset functions
Expand Down Expand Up @@ -4423,7 +4413,7 @@ def py_quit() -> None:

# Check if we are running Python code
if py_code_to_run:
try:
try: # noqa: SIM105
interp.runcode(py_code_to_run) # type: ignore[arg-type]
except BaseException: # noqa: BLE001, S110
# We don't care about any exception that happened in the Python code
Expand Down Expand Up @@ -4646,7 +4636,7 @@ def do_history(self, args: argparse.Namespace) -> Optional[bool]:
self.last_result = False

# -v must be used alone with no other options
if args.verbose:
if args.verbose: # noqa: SIM102
if args.clear or args.edit or args.output_file or args.run or args.transcript or args.expanded or args.script:
self.poutput("-v cannot be used with any other options")
self.poutput(self.history_parser.format_usage())
Expand Down
5 changes: 2 additions & 3 deletions cmd2/rl_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Imports the proper Readline for the platform and provides utility functions for it."""

import contextlib
import sys
from enum import (
Enum,
Expand Down Expand Up @@ -27,10 +28,8 @@
import gnureadline as readline # type: ignore[import]
except ImportError:
# Note: If this actually fails, you should install gnureadline on Linux/Mac or pyreadline3 on Windows.
try:
with contextlib.suppress(ImportError):
import readline # type: ignore[no-redef]
except ImportError: # pragma: no cover
pass


class RlType(Enum):
Expand Down
5 changes: 2 additions & 3 deletions cmd2/transcript.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ def _fetch_transcripts(self) -> None:
self.transcripts = {}
testfiles = cast(list[str], getattr(self.cmdapp, 'testfiles', []))
for fname in testfiles:
tfile = open(fname)
self.transcripts[fname] = iter(tfile.readlines())
tfile.close()
with open(fname) as tfile:
self.transcripts[fname] = iter(tfile.readlines())

def _test_transcript(self, fname: str, transcript: Iterator[str]) -> None:
if self.cmdapp is None:
Expand Down
15 changes: 5 additions & 10 deletions cmd2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import argparse
import collections
import contextlib
import functools
import glob
import inspect
Expand Down Expand Up @@ -646,11 +647,9 @@ def _write_bytes(stream: Union[StdSim, TextIO], to_write: Union[bytes, str]) ->
if isinstance(to_write, str):
to_write = to_write.encode()

try:
# BrokenPipeError can occur if output is being piped to a process that closed
with contextlib.suppress(BrokenPipeError):
stream.buffer.write(to_write)
except BrokenPipeError:
# This occurs if output is being piped to a process that closed
pass


class ContextFlag:
Expand Down Expand Up @@ -877,12 +876,8 @@ def align_text(
line_styles = list(get_styles_dict(line).values())

# Calculate how wide each side of filling needs to be
if line_width >= width:
# Don't return here even though the line needs no fill chars.
# There may be styles sequences to restore.
total_fill_width = 0
else:
total_fill_width = width - line_width
total_fill_width = 0 if line_width >= width else width - line_width
# Even if the line needs no fill chars, there may be styles sequences to restore

if alignment == TextAlignment.LEFT:
left_fill_width = 0
Expand Down
4 changes: 1 addition & 3 deletions examples/custom_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ def __init__(self, *args, **kwargs) -> None:
def error(self, message: str) -> None:
"""Custom override that applies custom formatting to the error message."""
lines = message.split('\n')
linum = 0
formatted_message = ''
for line in lines:
for linum, line in enumerate(lines):
if linum == 0:
formatted_message = 'Error: ' + line
else:
formatted_message += '\n ' + line
linum += 1

self.print_usage(sys.stderr)

Expand Down
3 changes: 2 additions & 1 deletion examples/scripts/save_help_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def main() -> None:
# Open the output file
outfile_path = os.path.expanduser(sys.argv[1])
try:
outfile = open(outfile_path, 'w')
with open(outfile_path, 'w') as outfile:
pass
except OSError as e:
print(f"Error opening {outfile_path} because: {e}")
return
Expand Down
5 changes: 2 additions & 3 deletions plugins/ext_test/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- setuptools >= 39.1.0
"""

import contextlib
import os
import pathlib
import shutil
Expand All @@ -27,10 +28,8 @@ def rmrf(items, verbose=True):
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
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ 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)
# "SIM", # flake8-simplify (rules to attempt to simplify code)
"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)
Expand Down
Loading
Loading