From 3c99392d3032139a42f7ba439b890935bfa8ba0d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Mon, 21 Oct 2024 22:39:11 -0400 Subject: [PATCH] Add ruff fast linter and formatter in preparation for using it to replace black, isort, and flake8 --- .gitignore | 3 + cmd2/ansi.py | 17 +- cmd2/argparse_completer.py | 2 +- cmd2/clipboard.py | 1 + cmd2/cmd2.py | 7 +- cmd2/command_definition.py | 1 + cmd2/decorators.py | 1 + cmd2/plugin.py | 1 + cmd2/rl_utils.py | 1 + cmd2/table_creator.py | 1 + cmd2/transcript.py | 1 + cmd2/utils.py | 7 +- examples/alias_startup.py | 5 +- examples/arg_decorators.py | 1 + examples/argparse_completion.py | 1 + examples/async_printing.py | 1 + examples/basic.py | 13 +- examples/basic_completion.py | 1 + examples/custom_parser.py | 1 + examples/decorator_example.py | 1 + examples/dynamic_commands.py | 4 +- examples/environment.py | 1 + examples/event_loops.py | 1 + examples/example.py | 1 + examples/exit_code.py | 4 +- examples/hello_cmd2.py | 1 + examples/help_categories.py | 1 + examples/initialization.py | 21 +-- examples/migrating.py | 1 + examples/modular_commands/commandset_basic.py | 1 + .../modular_commands/commandset_custominit.py | 1 + examples/modular_commands_main.py | 1 + examples/modular_subcommands.py | 1 + examples/paged_output.py | 4 +- examples/persistent_history.py | 1 + examples/python_scripting.py | 1 + examples/read_input.py | 1 + examples/remove_settable.py | 1 + examples/scripts/conditional.py | 1 + examples/scripts/script.py | 1 + examples/table_creation.py | 1 + examples/unicode_commands.py | 4 +- plugins/ext_test/tasks.py | 1 + plugins/tasks.py | 1 + pyproject.toml | 169 ++++++++++++++++++ setup.py | 1 + tasks.py | 12 ++ tests/conftest.py | 1 + tests/pyscript/raises_exception.py | 1 + tests/pyscript/recursive.py | 1 + tests/script.py | 1 + tests/test_ansi.py | 1 + tests/test_argparse.py | 15 +- tests/test_argparse_completer.py | 1 + tests/test_argparse_custom.py | 1 + tests/test_cmd2.py | 8 +- tests/test_completion.py | 7 +- tests/test_history.py | 5 +- tests/test_parsing.py | 1 + tests/test_plugin.py | 1 + tests/test_run_pyscript.py | 1 + tests/test_table_creator.py | 1 + tests/test_transcript.py | 1 + tests/test_utils.py | 1 + tests/test_utils_defining_class.py | 4 +- tests_isolated/test_commandset/conftest.py | 1 + .../test_commandset/test_categories.py | 1 + .../test_commandset/test_commandset.py | 2 +- 68 files changed, 300 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index 11b71aa3a..c3c267ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ Pipfile.lock # pyenv version file .python-version + +# uv +uv.lock diff --git a/cmd2/ansi.py b/cmd2/ansi.py index d2f6832a8..52bf382a1 100644 --- a/cmd2/ansi.py +++ b/cmd2/ansi.py @@ -2,7 +2,8 @@ """ Support for ANSI escape sequences which are used for things like applying style to text, setting the window title, and asynchronous alerts. - """ +""" + import functools import re from enum import ( @@ -62,25 +63,25 @@ def __repr__(self) -> str: """ # Regular expression to match ANSI style sequence -ANSI_STYLE_RE = re.compile(fr'{ESC}\[[^m]*m') +ANSI_STYLE_RE = re.compile(rf'{ESC}\[[^m]*m') # Matches standard foreground colors: CSI(30-37|90-97|39)m -STD_FG_RE = re.compile(fr'{ESC}\[(?:[39][0-7]|39)m') +STD_FG_RE = re.compile(rf'{ESC}\[(?:[39][0-7]|39)m') # Matches standard background colors: CSI(40-47|100-107|49)m -STD_BG_RE = re.compile(fr'{ESC}\[(?:(?:4|10)[0-7]|49)m') +STD_BG_RE = re.compile(rf'{ESC}\[(?:(?:4|10)[0-7]|49)m') # Matches eight-bit foreground colors: CSI38;5;(0-255)m -EIGHT_BIT_FG_RE = re.compile(fr'{ESC}\[38;5;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])m') +EIGHT_BIT_FG_RE = re.compile(rf'{ESC}\[38;5;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])m') # Matches eight-bit background colors: CSI48;5;(0-255)m -EIGHT_BIT_BG_RE = re.compile(fr'{ESC}\[48;5;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])m') +EIGHT_BIT_BG_RE = re.compile(rf'{ESC}\[48;5;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])m') # Matches RGB foreground colors: CSI38;2;(0-255);(0-255);(0-255)m -RGB_FG_RE = re.compile(fr'{ESC}\[38;2(?:;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])){{3}}m') +RGB_FG_RE = re.compile(rf'{ESC}\[38;2(?:;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])){{3}}m') # Matches RGB background colors: CSI48;2;(0-255);(0-255);(0-255)m -RGB_BG_RE = re.compile(fr'{ESC}\[48;2(?:;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])){{3}}m') +RGB_BG_RE = re.compile(rf'{ESC}\[48;2(?:;(?:1?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])){{3}}m') def strip_style(text: str) -> str: diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index e4601307b..9bb86e9f1 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -92,7 +92,7 @@ def _looks_like_flag(token: str, parser: argparse.ArgumentParser) -> bool: return False # Flags have to start with a prefix character - if not token[0] in parser.prefix_chars: + if token[0] not in parser.prefix_chars: return False # If it looks like a negative number, it is not a flag unless there are negative-number-like flags diff --git a/cmd2/clipboard.py b/cmd2/clipboard.py index eda92bf64..454e3484c 100644 --- a/cmd2/clipboard.py +++ b/cmd2/clipboard.py @@ -2,6 +2,7 @@ """ This module provides basic ability to copy from and paste to the clipboard/pastebuffer. """ + import typing import pyperclip # type: ignore[import] diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 566d78787..1ecaadb18 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -21,6 +21,7 @@ Git repository on GitHub at https://github.com/python-cmd2/cmd2 """ + # This module has many imports, quite a few of which are only # infrequently utilized. To reduce the initial overhead of # import this module, many of these imports are lazy-loaded @@ -2019,9 +2020,7 @@ def _determine_ap_completer_type(parser: argparse.ArgumentParser) -> Type[argpar :param parser: the parser to examine :return: type of ArgparseCompleter """ - completer_type: Optional[ - Type[argparse_completer.ArgparseCompleter] - ] = parser.get_ap_completer_type() # type: ignore[attr-defined] + completer_type: Optional[Type[argparse_completer.ArgparseCompleter]] = parser.get_ap_completer_type() if completer_type is None: completer_type = argparse_completer.DEFAULT_AP_COMPLETER @@ -5537,7 +5536,7 @@ def cmdloop(self, intro: Optional[str] = None) -> int: # type: ignore[override] """ # cmdloop() expects to be run in the main thread to support extensive use of KeyboardInterrupts throughout the # other built-in functions. You are free to override cmdloop, but much of cmd2's features will be limited. - if not threading.current_thread() is threading.main_thread(): + if threading.current_thread() is not threading.main_thread(): raise RuntimeError("cmdloop must be run in the main thread") # Register signal handlers diff --git a/cmd2/command_definition.py b/cmd2/command_definition.py index 1dcd4116f..9f713d83d 100644 --- a/cmd2/command_definition.py +++ b/cmd2/command_definition.py @@ -2,6 +2,7 @@ """ Supports the definition of commands in separate classes to be composed into cmd2.Cmd """ + from typing import ( TYPE_CHECKING, Callable, diff --git a/cmd2/decorators.py b/cmd2/decorators.py index 601da6f97..cd9fd358c 100644 --- a/cmd2/decorators.py +++ b/cmd2/decorators.py @@ -1,5 +1,6 @@ # coding=utf-8 """Decorators for ``cmd2`` commands""" + import argparse from typing import ( TYPE_CHECKING, diff --git a/cmd2/plugin.py b/cmd2/plugin.py index affe2421d..e7e2c6863 100644 --- a/cmd2/plugin.py +++ b/cmd2/plugin.py @@ -1,6 +1,7 @@ # # coding=utf-8 """Classes for the cmd2 plugin system""" + from dataclasses import ( dataclass, ) diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index f89d2b18d..b02aa73a2 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -2,6 +2,7 @@ """ Imports the proper Readline for the platform and provides utility functions for it """ + import sys from enum import ( Enum, diff --git a/cmd2/table_creator.py b/cmd2/table_creator.py index c7c277562..409e7a994 100644 --- a/cmd2/table_creator.py +++ b/cmd2/table_creator.py @@ -5,6 +5,7 @@ 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. """ + import copy import io from collections import ( diff --git a/cmd2/transcript.py b/cmd2/transcript.py index a38c19023..fdbcd0299 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -9,6 +9,7 @@ This file contains the class necessary to make that work. This class is used in cmd2.py::run_transcript_tests() """ + import re import unittest from typing import ( diff --git a/cmd2/utils.py b/cmd2/utils.py index d1e077fdc..f718d5f14 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -1,5 +1,6 @@ # coding=utf-8 """Shared utility functions""" + import argparse import collections import functools @@ -157,7 +158,7 @@ def __init__( :param choices_provider: function that provides choices for this argument :param completer: tab completion function that provides choices for this argument """ - if val_type == bool: + if val_type is bool: def get_bool_choices(_) -> List[str]: # type: ignore[no-untyped-def] """Used to tab complete lowercase boolean values""" @@ -1189,9 +1190,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__') # type: ignore[attr-defined] + inspect.isbuiltin(meth) and getattr(meth, '__self__') is not None and getattr(meth.__self__, '__class__') ): for cls in inspect.getmro(meth.__self__.__class__): # type: ignore[attr-defined] if meth.__name__ in cls.__dict__: diff --git a/examples/alias_startup.py b/examples/alias_startup.py index 72e969fcc..1ad493ffa 100755 --- a/examples/alias_startup.py +++ b/examples/alias_startup.py @@ -1,9 +1,10 @@ #!/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 +1) How to add custom command aliases using the alias command +2) How to run an initialization script at startup """ + import os import cmd2 diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index 3b02835ed..e42960b1c 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # coding=utf-8 """An example demonstrating how use one of cmd2's argument parsing decorators""" + import argparse import os diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index 506f94e26..daad63ab0 100644 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -3,6 +3,7 @@ """ A simple example demonstrating how to integrate tab completion with argparse-based commands. """ + import argparse from typing import ( Dict, diff --git a/examples/async_printing.py b/examples/async_printing.py index 55dff35bd..e94ee89a0 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -4,6 +4,7 @@ A simple example demonstrating an application that asynchronously prints alerts, updates the prompt and changes the window title """ + import random import threading import time diff --git a/examples/basic.py b/examples/basic.py index 1f9683bf9..6ce4d2838 100755 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,13 +1,14 @@ #!/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 - 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 +1) How to add a command +2) How to add help for that command +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 """ + import cmd2 from cmd2 import ( Bg, diff --git a/examples/basic_completion.py b/examples/basic_completion.py index febe58a3a..c713f2b0d 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -12,6 +12,7 @@ familiar with argparse. The recommended approach for tab completing positional tokens and flags is to use argparse-based completion. For an example integrating tab completion with argparse, see argparse_completion.py """ + import functools from typing import ( List, diff --git a/examples/custom_parser.py b/examples/custom_parser.py index 6e5a33b4a..94df3b054 100644 --- a/examples/custom_parser.py +++ b/examples/custom_parser.py @@ -2,6 +2,7 @@ """ Defines the CustomParser used with override_parser.py example """ + import sys from cmd2 import ( diff --git a/examples/decorator_example.py b/examples/decorator_example.py index 75bc8dff4..ea8fd3b50 100755 --- a/examples/decorator_example.py +++ b/examples/decorator_example.py @@ -10,6 +10,7 @@ all the commands in the transcript against decorator_example.py, verifying that the output produced matches the transcript. """ + import argparse from typing import ( List, diff --git a/examples/dynamic_commands.py b/examples/dynamic_commands.py index eee5b8cce..82dde732d 100755 --- a/examples/dynamic_commands.py +++ b/examples/dynamic_commands.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # coding=utf-8 -"""A simple example demonstrating how do_* commands can be created in a loop. -""" +"""A simple example demonstrating how do_* commands can be created in a loop.""" + import functools import cmd2 diff --git a/examples/environment.py b/examples/environment.py index 3eaaa8d1d..1bb9812be 100755 --- a/examples/environment.py +++ b/examples/environment.py @@ -3,6 +3,7 @@ """ A sample application for cmd2 demonstrating customized environment parameters """ + import cmd2 diff --git a/examples/event_loops.py b/examples/event_loops.py index 86dc01fba..e5435181a 100755 --- a/examples/event_loops.py +++ b/examples/event_loops.py @@ -6,6 +6,7 @@ This opens up the possibility of registering cmd2 input with event loops, like asyncio, without occupying the main loop. """ + import cmd2 diff --git a/examples/example.py b/examples/example.py index da6c3c9ff..2ff64d747 100755 --- a/examples/example.py +++ b/examples/example.py @@ -10,6 +10,7 @@ the transcript against example.py, verifying that the output produced matches the transcript. """ + import random import cmd2 diff --git a/examples/exit_code.py b/examples/exit_code.py index 23b172a1c..d8e538ced 100755 --- a/examples/exit_code.py +++ b/examples/exit_code.py @@ -1,7 +1,7 @@ #!/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. -""" +"""A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application.""" + from typing import ( List, ) diff --git a/examples/hello_cmd2.py b/examples/hello_cmd2.py index 0e639e29b..a67205834 100755 --- a/examples/hello_cmd2.py +++ b/examples/hello_cmd2.py @@ -3,6 +3,7 @@ """ 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 b2a1623fa..5c349422c 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -5,6 +5,7 @@ It also demonstrates the effects of decorator order when it comes to argparse errors occurring. """ + import functools import cmd2 diff --git a/examples/initialization.py b/examples/initialization.py index 292756d9d..426a5a4a2 100755 --- a/examples/initialization.py +++ b/examples/initialization.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # coding=utf-8 """A simple example cmd2 application demonstrating the following: - 1) Colorizing/stylizing output - 2) Using multiline commands - 3) Persistent history - 4) How to run an initialization script at startup - 5) How to group and categorize commands when displaying them in help - 6) Opting-in to using the ipy command to run an IPython shell - 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 + 1) Colorizing/stylizing output + 2) Using multiline commands + 3) Persistent history + 4) How to run an initialization script at startup + 5) How to group and categorize commands when displaying them in help + 6) Opting-in to using the ipy command to run an IPython shell + 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 """ + import cmd2 from cmd2 import ( Bg, diff --git a/examples/migrating.py b/examples/migrating.py index fab40d25f..22efadab9 100755 --- a/examples/migrating.py +++ b/examples/migrating.py @@ -3,6 +3,7 @@ """ A sample application for cmd which can be used to show how to migrate to cmd2. """ + import cmd import random diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index ab52a326a..a4b7582f5 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -2,6 +2,7 @@ """ A simple example demonstrating a loadable command set """ + from typing import ( List, ) diff --git a/examples/modular_commands/commandset_custominit.py b/examples/modular_commands/commandset_custominit.py index e24ac291c..a3f4f59ad 100644 --- a/examples/modular_commands/commandset_custominit.py +++ b/examples/modular_commands/commandset_custominit.py @@ -2,6 +2,7 @@ """ A simple example demonstrating a loadable command set """ + from cmd2 import ( Cmd, CommandSet, diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index 3aeb8b2a2..74483987b 100644 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -4,6 +4,7 @@ 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 typing import ( Iterable, diff --git a/examples/modular_subcommands.py b/examples/modular_subcommands.py index 082903fb1..14d117814 100644 --- a/examples/modular_subcommands.py +++ b/examples/modular_subcommands.py @@ -10,6 +10,7 @@ The `load` and `unload` command will load and unload the CommandSets. The available top level commands as well as subcommands to the `cut` command will change depending on which CommandSets are loaded. """ + import argparse import cmd2 diff --git a/examples/paged_output.py b/examples/paged_output.py index afed8a6e7..0f7173b2e 100755 --- a/examples/paged_output.py +++ b/examples/paged_output.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding=utf-8 -"""A simple example demonstrating the using paged output via the ppaged() method. -""" +"""A simple example demonstrating the using paged output via the ppaged() method.""" + import os from typing import ( List, diff --git a/examples/persistent_history.py b/examples/persistent_history.py index 330e35379..ab4b89f2b 100755 --- a/examples/persistent_history.py +++ b/examples/persistent_history.py @@ -5,6 +5,7 @@ This will allow end users of your cmd2-based application to use the arrow keys and Ctrl+r in a manner which persists across invocations of your cmd2 application. This can make it much easier for them to use your application. """ + import cmd2 diff --git a/examples/python_scripting.py b/examples/python_scripting.py index 397870858..e2403c0c2 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -20,6 +20,7 @@ This application and the "examples/scripts/conditional.py" script serve as an example for one way in which this can be done. """ + import os import cmd2 diff --git a/examples/read_input.py b/examples/read_input.py index 3a13e9d3a..bfc43380b 100644 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -3,6 +3,7 @@ """ A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion """ + from typing import ( List, ) diff --git a/examples/remove_settable.py b/examples/remove_settable.py index a7b871266..fad671cbe 100755 --- a/examples/remove_settable.py +++ b/examples/remove_settable.py @@ -3,6 +3,7 @@ """ 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 eb7106958..dd3adcca0 100644 --- a/examples/scripts/conditional.py +++ b/examples/scripts/conditional.py @@ -10,6 +10,7 @@ Note: The "app" function is defined within the cmd2 embedded Python environment and in there "self" is your cmd2 application instance. Note: self only exists in this environment if self_in_py is True. """ + import os import sys diff --git a/examples/scripts/script.py b/examples/scripts/script.py index 5195b8cc9..339fbf2c8 100644 --- a/examples/scripts/script.py +++ b/examples/scripts/script.py @@ -3,4 +3,5 @@ """ 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/table_creation.py b/examples/table_creation.py index 78b3a0f08..852f2d84d 100755 --- a/examples/table_creation.py +++ b/examples/table_creation.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # coding=utf-8 """Examples of using the cmd2 table creation API""" + import functools import sys from typing import ( diff --git a/examples/unicode_commands.py b/examples/unicode_commands.py index 0a7c5ac74..6c76a76e7 100755 --- a/examples/unicode_commands.py +++ b/examples/unicode_commands.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding=utf-8 -"""A simple example demonstrating support for unicode command names. -""" +"""A simple example demonstrating support for unicode command names.""" + import math import cmd2 diff --git a/plugins/ext_test/tasks.py b/plugins/ext_test/tasks.py index 6370af0c1..640117863 100644 --- a/plugins/ext_test/tasks.py +++ b/plugins/ext_test/tasks.py @@ -8,6 +8,7 @@ - wheel >= 0.31.0 - setuptools >= 39.1.0 """ + import os import pathlib import shutil diff --git a/plugins/tasks.py b/plugins/tasks.py index a22eb310d..4ef83255d 100644 --- a/plugins/tasks.py +++ b/plugins/tasks.py @@ -8,6 +8,7 @@ - wheel >= 0.31.0 - setuptools >= 39.1.0 """ + import invoke from plugins.ext_test import ( diff --git a/pyproject.toml b/pyproject.toml index de8bb4063..50aab79d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,3 +25,172 @@ exclude = ''' | htmlcov )/ ''' + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +# Same as Black. +line-length = 127 +indent-width = 4 + +# Assume Python 3.13 +target-version = "py313" +output-format = "full" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = [ + # https://beta.ruff.rs/docs/rules + # "A", # flake8-builtins + # "ARG", # flake8-unused-arguments + "ASYNC", # flake8-async + # "B", # flake8-bugbear + # "BLE", # flake8-blind-except + # "C4", # flake8-comprehensions + "C90", # McCabe cyclomatic complexity + "DJ", # flake8-django + # "DTZ", # flake8-datetimez + "E", # pycodestyle + # "EM", # flake8-errmsg + # "EXE", # flake8-executable + "F", # Pyflakes + "FA", # flake8-future-annotations + # "FLY", # flynt + "G", # flake8-logging-format + # "I", # isort + "ICN", # flake8-import-conventions + # "INP", # flake8-no-pep420 + "INT", # flake8-gettext + # "ISC", # flake8-implicit-str-concat + # "N", # pep8-naming + "NPY", # NumPy-specific rules + "PD", # pandas-vet + # "PGH", # pygrep-hooks + # "PIE", # flake8-pie + # "PL", # Pylint + # "PT", # flake8-pytest-style + # "PYI", # flake8-pyi + "RSE", # flake8-raise + # "RUF", # Ruff-specific rules + # "S", # flake8-bandit + # "SIM", # flake8-simplify + # "SLF", # flake8-self + # "T10", # flake8-debugger + # "TD", # flake8-todos + # "TID", # flake8-tidy-imports + # "UP", # pyupgrade + # "W", # pycodestyle + # "YTT", # flake8-2020 + # "ANN", # flake8-annotations # FIX ME? + # "COM", # flake8-commas + # "D", # pydocstyle -- FIX ME? + # "ERA", # eradicate -- DO NOT FIX + # "FBT", # flake8-boolean-trap # FIX ME + # "PTH", # flake8-use-pathlib # FIX ME + # "Q", # flake8-quotes + # "RET", # flake8-return # FIX ME? + # "T20", # flake8-print + # "TCH", # flake8-type-checking + # "TRY", # tryceratops +] +ignore = [ + # `ruff rule S101` for a description of that rule + "B904", # Within an `except` clause, raise exceptions with `raise ... from err` -- FIX ME + "B905", # `zip()` without an explicit `strict=` parameter -- FIX ME + "E501", # Line too long + "EM101", # Exception must not use a string literal, assign to variable first + "EXE001", # Shebang is present but file is not executable -- DO NOT FIX + "G004", # Logging statement uses f-string + "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey + "PLW060", # Using global for `{name}` but no assignment is done -- DO NOT FIX + "PLW2901", # PLW2901: Redefined loop variable -- FIX ME + "PT011", # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception + "PT018", # Assertion should be broken down into multiple parts + "S101", # Use of `assert` detected -- DO NOT FIX + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME + "SLF001", # Private member accessed: `_Iterator` -- FIX ME + "UP038", # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +mccabe.max-complexity = 49 + +per-file-ignores."cmd2/__init__.py" = [ + "E402", # Module level import not at top of file + "F401", # Unused import +] + +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."tests/pyscript/*.py" = [ + "F821", # Undefined name `app` +] + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "preserve" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" diff --git a/setup.py b/setup.py index 749f254ab..047015e7d 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ """ Setuptools setup file, used to install or test 'cmd2' """ + import codecs from setuptools import ( diff --git a/tasks.py b/tasks.py index e0eb16a0f..4c0a32dcd 100644 --- a/tasks.py +++ b/tasks.py @@ -8,6 +8,7 @@ - wheel >= 0.31.0 - setuptools >= 39.1.0 """ + import os import pathlib import re @@ -366,3 +367,14 @@ def format(context): namespace.add_task(format) + + +# ruff extremely fast Python linter and formatter written in Rust +@invoke.task() +def ruff(context): + """Run ruff linter and formatter""" + with context.cd(TASK_ROOT_STR): + context.run("ruff check && ruff format --check") + + +namespace.add_task(ruff) diff --git a/tests/conftest.py b/tests/conftest.py index 35ff2e89d..644ae7cca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ """ Cmd2 unit/functional testing """ + import argparse import sys from contextlib import ( diff --git a/tests/pyscript/raises_exception.py b/tests/pyscript/raises_exception.py index 738edaf28..ab4670890 100644 --- a/tests/pyscript/raises_exception.py +++ b/tests/pyscript/raises_exception.py @@ -3,4 +3,5 @@ """ 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 b88ba5a52..206f356cb 100644 --- a/tests/pyscript/recursive.py +++ b/tests/pyscript/recursive.py @@ -4,6 +4,7 @@ """ 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 5195b8cc9..339fbf2c8 100644 --- a/tests/script.py +++ b/tests/script.py @@ -3,4 +3,5 @@ """ 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 a2c49702a..65ec68a9d 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -3,6 +3,7 @@ """ Unit testing for cmd2/ansi.py module """ + import pytest from cmd2 import ( diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 19512aff9..7a682c5ce 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -3,6 +3,7 @@ """ Cmd2 testing for argument parsing """ + import argparse from typing import ( Optional, @@ -404,7 +405,7 @@ def test_add_another_subcommand(subcommand_app): to add_parser() write the correct prog value to the parser being added. """ base_parser = subcommand_app._command_parsers.get('base') - subcommand_parser = find_subcommand(subcommand_app._command_parsers.get('base'), []) + find_subcommand(subcommand_app._command_parsers.get('base'), []) for sub_action in base_parser._actions: if isinstance(sub_action, argparse._SubParsersAction): new_parser = sub_action.add_parser('new_sub', help='stuff') @@ -449,16 +450,16 @@ def test_unittest_mock(): with mock.patch.object(ArgparseApp, 'namespace_provider'): with pytest.raises(CommandSetRegistrationError): - app = ArgparseApp() + ArgparseApp() with mock.patch.object(ArgparseApp, 'namespace_provider', spec=True): - app = ArgparseApp() + ArgparseApp() with mock.patch.object(ArgparseApp, 'namespace_provider', spec_set=True): - app = ArgparseApp() + ArgparseApp() with mock.patch.object(ArgparseApp, 'namespace_provider', autospec=True): - app = ArgparseApp() + ArgparseApp() def test_pytest_mock_invalid(mocker): @@ -468,7 +469,7 @@ def test_pytest_mock_invalid(mocker): mocker.patch.object(ArgparseApp, 'namespace_provider') with pytest.raises(CommandSetRegistrationError): - app = ArgparseApp() + ArgparseApp() @pytest.mark.parametrize( @@ -481,4 +482,4 @@ def test_pytest_mock_invalid(mocker): ) def test_pytest_mock_valid(mocker, spec_param): mocker.patch.object(ArgparseApp, 'namespace_provider', **spec_param) - app = ArgparseApp() + ArgparseApp() diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index a764a45f5..a2fb89eb1 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -3,6 +3,7 @@ """ Unit/functional testing for argparse completer in cmd2 """ + import argparse import numbers from typing import ( diff --git a/tests/test_argparse_custom.py b/tests/test_argparse_custom.py index 768cac0bc..a3f85558c 100644 --- a/tests/test_argparse_custom.py +++ b/tests/test_argparse_custom.py @@ -2,6 +2,7 @@ """ Unit/functional testing for argparse customizations in cmd2 """ + import argparse import pytest diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index d76c0bbfc..96d666611 100755 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -3,10 +3,10 @@ """ Cmd2 unit/functional testing """ + import builtins import io import os -import readline import signal import sys import tempfile @@ -149,7 +149,7 @@ def test_base_shortcuts(base_app): def test_command_starts_with_shortcut(): with pytest.raises(ValueError) as excinfo: - app = cmd2.Cmd(shortcuts={'help': 'fake'}) + cmd2.Cmd(shortcuts={'help': 'fake'}) assert "Invalid command name 'help'" in str(excinfo.value) @@ -813,9 +813,9 @@ def test_get_paste_buffer_exception(base_app, mocker, capsys): def test_allow_clipboard_initializer(base_app): - assert base_app.allow_clipboard == True + assert base_app.allow_clipboard is True noclipcmd = cmd2.Cmd(allow_clipboard=False) - assert noclipcmd.allow_clipboard == False + assert noclipcmd.allow_clipboard is False # if clipboard access is not allowed, cmd2 should check that first diff --git a/tests/test_completion.py b/tests/test_completion.py index 2eebaaebe..27b974af4 100755 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -6,6 +6,7 @@ These are primarily tests related to readline completer functions which handle tab completion of cmd2/cmd commands, file system paths, and shell commands. """ + import enum import os import sys @@ -274,7 +275,7 @@ def test_cmd2_help_completion_nomatch(cmd2_app): def test_set_allow_style_completion(cmd2_app): """Confirm that completing allow_style presents AllowStyle strings""" text = '' - line = 'set allow_style'.format(text) + line = 'set allow_style'.format() endidx = len(line) begidx = endidx - len(text) @@ -288,7 +289,7 @@ def test_set_allow_style_completion(cmd2_app): def test_set_bool_completion(cmd2_app): """Confirm that completing a boolean Settable presents true and false strings""" text = '' - line = 'set debug'.format(text) + line = 'set debug'.format() endidx = len(line) begidx = endidx - len(text) @@ -1331,7 +1332,7 @@ def test_help_subcommand_completion_nomatch_scu(scu_app): begidx = endidx - len(text) first_match = complete_tester(text, line, begidx, endidx, scu_app) - assert first_match == None + assert first_match is None def test_subcommand_tab_completion_scu(scu_app): diff --git a/tests/test_history.py b/tests/test_history.py index 38b539c68..1a3bd744b 100755 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -3,6 +3,7 @@ """ Test history functions of cmd2 """ + import os import tempfile from unittest import ( @@ -478,7 +479,7 @@ def test_history_item_instantiate(): Statement, ) - statement = Statement( + Statement( 'history', raw='help history', command='help', @@ -898,7 +899,7 @@ def test_history_file_is_directory(capsys): def test_history_can_create_directory(mocker): # 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. - mock_register = mocker.patch('atexit.register') + mocker.patch('atexit.register') # Create a temp path for us to use and let it get deleted with tempfile.TemporaryDirectory() as test_dir: diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 37a93ba26..83df13cf5 100755 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -3,6 +3,7 @@ """ Test the parsing logic in parsing.py """ + import dataclasses import pytest diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 61b140ab0..eb91892c4 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3,6 +3,7 @@ """ Test plugin infrastructure and hooks. """ + import argparse import sys from unittest import ( diff --git a/tests/test_run_pyscript.py b/tests/test_run_pyscript.py index 3732e57e1..594a30b75 100644 --- a/tests/test_run_pyscript.py +++ b/tests/test_run_pyscript.py @@ -3,6 +3,7 @@ """ Unit/functional testing for run_pytest in cmd2 """ + import builtins import os from unittest import ( diff --git a/tests/test_table_creator.py b/tests/test_table_creator.py index 585ed62ab..fbbdfbc4a 100644 --- a/tests/test_table_creator.py +++ b/tests/test_table_creator.py @@ -3,6 +3,7 @@ """ Unit testing for cmd2/table_creator.py module """ + import pytest from cmd2 import ( diff --git a/tests/test_transcript.py b/tests/test_transcript.py index ed193a00c..986221ff2 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -3,6 +3,7 @@ """ Cmd2 functional testing based on transcript """ + import os import random import re diff --git a/tests/test_utils.py b/tests/test_utils.py index e8198d4be..2779a38b8 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,7 @@ """ Unit testing for cmd2/utils.py module. """ + import os import signal import sys diff --git a/tests/test_utils_defining_class.py b/tests/test_utils_defining_class.py index 5d667678a..8b6ede8bf 100644 --- a/tests/test_utils_defining_class.py +++ b/tests/test_utils_defining_class.py @@ -3,6 +3,7 @@ """ Unit testing for get_defining_class in cmd2/utils.py module. """ + import functools import cmd2.utils as cu @@ -23,7 +24,8 @@ def func_with_overrides(self): def child_function(self): pass - lambda1 = lambda: 1 + def lambda1(): + return 1 lambda2 = (lambda: lambda: 2)() diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index 41d5b6a4d..c8c6d34b4 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -2,6 +2,7 @@ """ Cmd2 unit/functional testing """ + import sys from contextlib import ( redirect_stderr, diff --git a/tests_isolated/test_commandset/test_categories.py b/tests_isolated/test_commandset/test_categories.py index 71f1db8e2..986ae3fa9 100644 --- a/tests_isolated/test_commandset/test_categories.py +++ b/tests_isolated/test_commandset/test_categories.py @@ -3,6 +3,7 @@ """ Simple example demonstrating basic CommandSet usage. """ + from typing import ( Any, ) diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index e400437b1..c7293d411 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -963,7 +963,7 @@ def cut_banana(self, ns: argparse.Namespace): self.poutput('cutting banana: ' + ns.direction) with pytest.raises(CommandSetRegistrationError): - app = BadSubcommandApp() + BadSubcommandApp() def test_commandset_settables():