diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 475f0472..0e6dcc8a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: files: .*\.(yaml|yml)$ exclude: '^zuul.d/.*$' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.6 + rev: v0.11.8 hooks: - id: ruff args: ['--fix', '--unsafe-fixes'] diff --git a/cliff/app.py b/cliff/app.py index 575c4e51..f992738b 100644 --- a/cliff/app.py +++ b/cliff/app.py @@ -76,15 +76,14 @@ class App: def __init__( self, - description: ty.Optional[str], - version: ty.Optional[str], + description: str | None, + version: str | None, command_manager: '_commandmanager.CommandManager', - stdin: ty.Optional[ty.TextIO] = None, - stdout: ty.Optional[ty.TextIO] = None, - stderr: ty.Optional[ty.TextIO] = None, - interactive_app_factory: ty.Optional[ - type['_interactive.InteractiveApp'] - ] = None, + stdin: ty.TextIO | None = None, + stdout: ty.TextIO | None = None, + stderr: ty.TextIO | None = None, + interactive_app_factory: type['_interactive.InteractiveApp'] + | None = None, deferred_help: bool = False, ) -> None: """Initialize the application.""" @@ -96,13 +95,13 @@ class App: self.deferred_help = deferred_help self.parser = self.build_option_parser(description, version) self.interactive_mode = False - self.interpreter: ty.Optional[_interactive.InteractiveApp] = None + self.interpreter: _interactive.InteractiveApp | None = None def _set_streams( self, - stdin: ty.Optional[ty.TextIO], - stdout: ty.Optional[ty.TextIO], - stderr: ty.Optional[ty.TextIO], + stdin: ty.TextIO | None, + stdout: ty.TextIO | None, + stderr: ty.TextIO | None, ) -> None: try: locale.setlocale(locale.LC_ALL, '') @@ -141,9 +140,9 @@ class App: def build_option_parser( self, - description: ty.Optional[str], - version: ty.Optional[str], - argparse_kwargs: ty.Optional[dict[str, ty.Any]] = None, + description: str | None, + version: str | None, + argparse_kwargs: dict[str, ty.Any] | None = None, ) -> _argparse.ArgumentParser: """Return an argparse option parser for this application. @@ -331,7 +330,7 @@ class App: self, cmd: '_command.Command', result: int, - err: ty.Optional[BaseException], + err: BaseException | None, ) -> None: """Hook run after a command is done to shutdown the app. @@ -434,7 +433,7 @@ class App: kwargs['cmd_name'] = cmd_name cmd = cmd_factory(self, self.options, **kwargs) result = 1 - err: ty.Optional[BaseException] = None + err: BaseException | None = None try: self.prepare_to_run_command(cmd) full_name = ( diff --git a/cliff/command.py b/cliff/command.py index 6b5df658..d0ec0e2b 100644 --- a/cliff/command.py +++ b/cliff/command.py @@ -12,22 +12,15 @@ import abc import argparse +from importlib.metadata import packages_distributions import inspect import types -import sys import typing as ty from stevedore import extension from cliff import _argparse -if sys.version_info < (3, 10): - # Python 3.9 and older - from importlib_metadata import packages_distributions -else: - # Python 3.10 and later - from importlib.metadata import packages_distributions # type: ignore - if ty.TYPE_CHECKING: from . import app as _app @@ -52,8 +45,8 @@ def _get_distributions_by_modules() -> dict[str, str]: def _get_distribution_for_module( - module: ty.Optional[types.ModuleType], -) -> ty.Optional[str]: + module: types.ModuleType | None, +) -> str | None: "Return the distribution containing the module." dist_name = None if module: @@ -77,7 +70,7 @@ class Command(metaclass=abc.ABCMeta): :param cmd_name: The name of the command. """ - app_dist_name: ty.Optional[str] + app_dist_name: str | None deprecated = False conflict_handler = 'ignore' @@ -88,8 +81,8 @@ class Command(metaclass=abc.ABCMeta): def __init__( self, app: '_app.App', - app_args: ty.Optional[argparse.Namespace], - cmd_name: ty.Optional[str] = None, + app_args: argparse.Namespace | None, + cmd_name: str | None = None, ) -> None: self.app = app self.app_args = app_args diff --git a/cliff/commandmanager.py b/cliff/commandmanager.py index 3a73e2e3..abeac126 100644 --- a/cliff/commandmanager.py +++ b/cliff/commandmanager.py @@ -15,7 +15,6 @@ import collections.abc import inspect import logging -import typing as ty import stevedore @@ -158,7 +157,7 @@ class CommandManager: return i return len(argv) - def add_command_group(self, group: ty.Optional[str] = None) -> None: + def add_command_group(self, group: str | None = None) -> None: """Adds another group of command entrypoints""" if group: self.load_commands(group) @@ -167,7 +166,7 @@ class CommandManager: """Returns a list of the loaded command groups""" return self.group_list - def get_command_names(self, group: ty.Optional[str] = None) -> list[str]: + def get_command_names(self, group: str | None = None) -> list[str]: """Returns a list of commands loaded for the specified group""" group_list: list[str] = [] if group is not None: diff --git a/cliff/complete.py b/cliff/complete.py index 4d1c64cd..cb9e027a 100644 --- a/cliff/complete.py +++ b/cliff/complete.py @@ -196,7 +196,7 @@ class CompleteCommand(_command.Command): self, app: 'app.App', app_args: argparse.Namespace, - cmd_name: ty.Optional[str] = None, + cmd_name: str | None = None, ) -> None: super().__init__(app, app_args, cmd_name) self._formatters = stevedore.ExtensionManager( diff --git a/cliff/display.py b/cliff/display.py index 5a90ea5f..b4cb276e 100644 --- a/cliff/display.py +++ b/cliff/display.py @@ -33,8 +33,8 @@ class DisplayCommandBase(command.Command, metaclass=abc.ABCMeta): def __init__( self, app: app.App, - app_args: ty.Optional[argparse.Namespace], - cmd_name: ty.Optional[str] = None, + app_args: argparse.Namespace | None, + cmd_name: str | None = None, ) -> None: super().__init__(app, app_args, cmd_name=cmd_name) self._formatter_plugins = self._load_formatter_plugins() @@ -112,7 +112,7 @@ class DisplayCommandBase(command.Command, metaclass=abc.ABCMeta): self, parsed_args: argparse.Namespace, column_names: collections.abc.Sequence[str], - ) -> tuple[list[str], ty.Optional[list[bool]]]: + ) -> tuple[list[str], list[bool] | None]: """Generate included columns and selector according to parsed args. We normalize the column names so that someone can do e.g. '-c diff --git a/cliff/formatters/table.py b/cliff/formatters/table.py index 6de7d96b..6546a76c 100644 --- a/cliff/formatters/table.py +++ b/cliff/formatters/table.py @@ -29,9 +29,9 @@ _T = ty.TypeVar('_T') def _format_row( row: collections.abc.Iterable[ - ty.Union[columns.FormattableColumn[ty.Any], str, _T] + columns.FormattableColumn[ty.Any] | str | _T ], -) -> list[ty.Union[_T, str]]: +) -> list[_T | str]: new_row = [] for r in row: if isinstance(r, columns.FormattableColumn): diff --git a/cliff/help.py b/cliff/help.py index 99252c65..d36d12d7 100644 --- a/cliff/help.py +++ b/cliff/help.py @@ -44,8 +44,8 @@ class HelpAction(argparse.Action): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: ty.Union[str, collections.abc.Sequence[ty.Any], None], - option_string: ty.Optional[str] = None, + values: str | collections.abc.Sequence[ty.Any] | None, + option_string: str | None = None, ) -> None: app = self.default pager = autopage.argparse.help_pager(app.stdout) @@ -57,7 +57,7 @@ class HelpAction(argparse.Action): out.write('\n{}Commands{}:\n'.format(*title_hl)) dists_by_module = command._get_distributions_by_modules() - def dist_for_obj(obj: object) -> ty.Optional[str]: + def dist_for_obj(obj: object) -> str | None: mod = inspect.getmodule(obj) if mod is None: raise RuntimeError(f"failed to find app: {obj}") diff --git a/cliff/hooks.py b/cliff/hooks.py index 2fba5675..8ed9cf60 100644 --- a/cliff/hooks.py +++ b/cliff/hooks.py @@ -12,7 +12,6 @@ import abc import argparse -import typing as ty from cliff import _argparse from cliff import command @@ -37,7 +36,7 @@ class CommandHook(metaclass=abc.ABCMeta): @abc.abstractmethod def get_parser( self, parser: _argparse.ArgumentParser - ) -> ty.Optional[_argparse.ArgumentParser]: + ) -> _argparse.ArgumentParser | None: """Modify the command :class:`argparse.ArgumentParser`. The provided parser is modified in-place, and the return value is not @@ -49,7 +48,7 @@ class CommandHook(metaclass=abc.ABCMeta): return parser @abc.abstractmethod - def get_epilog(self) -> ty.Optional[str]: + def get_epilog(self) -> str | None: """Return text to add to the command help epilog. :returns: An epilog string or None. diff --git a/cliff/interactive.py b/cliff/interactive.py index e771fe47..607d3f81 100644 --- a/cliff/interactive.py +++ b/cliff/interactive.py @@ -50,8 +50,8 @@ class InteractiveApp(cmd2.Cmd): self, parent_app: '_app.App', command_manager: '_commandmanager.CommandManager', - stdin: ty.Optional[ty.TextIO], - stdout: ty.Optional[ty.TextIO], + stdin: ty.TextIO | None, + stdout: ty.TextIO | None, errexit: bool = False, ): self.parent_app = parent_app @@ -65,7 +65,7 @@ class InteractiveApp(cmd2.Cmd): cmd2.Cmd.__init__(self, 'tab', stdin=stdin, stdout=stdout) # def _split_line(self, line: cmd2.Statement) -> list[str]: - def _split_line(self, line: ty.Union[str, cmd2.Statement]) -> list[str]: + def _split_line(self, line: str | cmd2.Statement) -> list[str]: # cmd2 >= 0.9.1 gives us a Statement not a PyParsing parse # result. parts = shlex.split(line) @@ -73,7 +73,7 @@ class InteractiveApp(cmd2.Cmd): parts.insert(0, line.command) return parts - def default(self, line: str) -> ty.Optional[bool]: # type: ignore[override] + def default(self, line: str) -> bool | None: # type: ignore[override] # Tie in the default command processor to # dispatch commands known to the command manager. # We send the message through our parent app, @@ -120,7 +120,7 @@ class InteractiveApp(cmd2.Cmd): # Use the command manager to get instructions for "help" self.default('help help') - def do_help(self, arg: ty.Optional[str]) -> None: + def do_help(self, arg: str | None) -> None: if arg: # Check if the arg is a builtin command or something # coming from the command manager @@ -167,9 +167,7 @@ class InteractiveApp(cmd2.Cmd): n for n in cmd2.Cmd.get_names(self) if not n.startswith('do__') ] - def precmd( - self, statement: ty.Union[cmd2.Statement, str] - ) -> cmd2.Statement: + def precmd(self, statement: cmd2.Statement | str) -> cmd2.Statement: """Hook method executed just before the command is executed by :meth:`~cmd2.Cmd.onecmd` and after adding it to history. @@ -207,7 +205,7 @@ class InteractiveApp(cmd2.Cmd): ) return statement - def cmdloop(self, intro: ty.Optional[str] = None) -> None: # type: ignore[override] + def cmdloop(self, intro: str | None = None) -> None: # type: ignore[override] # We don't want the cmd2 cmdloop() behaviour, just call the old one # directly. In part this is because cmd2.cmdloop() doe not return # anything useful and we want to have a useful exit code. diff --git a/cliff/sphinxext.py b/cliff/sphinxext.py index dd050573..cdc8bab4 100644 --- a/cliff/sphinxext.py +++ b/cliff/sphinxext.py @@ -279,7 +279,7 @@ class AutoprogramCliffDirective(rst.Directive): del parser._actions[parser._actions.index(action)] break - def _load_app(self) -> ty.Optional[app.App]: + def _load_app(self) -> app.App | None: mod_str, _sep, class_str = self.arguments[0].rpartition('.') if not mod_str: return None diff --git a/cliff/tests/test_columns.py b/cliff/tests/test_columns.py index e7be90e8..3b5a4421 100644 --- a/cliff/tests/test_columns.py +++ b/cliff/tests/test_columns.py @@ -10,13 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. -import typing as ty import unittest from cliff import columns -class FauxColumn(columns.FormattableColumn[ty.Union[str, list[str]]]): +class FauxColumn(columns.FormattableColumn[str | list[str]]): def human_readable(self): return f'I made this string myself: {self._value}' diff --git a/cliff/utils.py b/cliff/utils.py index 0890cef9..8e704821 100644 --- a/cliff/utils.py +++ b/cliff/utils.py @@ -12,7 +12,6 @@ # limitations under the License. import os -import typing as ty # Each edit operation is assigned different cost, such as: # 'w' means swap operation, the cost is 0; @@ -91,7 +90,7 @@ def damerau_levenshtein(s1: str, s2: str, cost: dict[str, int]) -> int: return row1[-1] -def terminal_width() -> ty.Optional[int]: +def terminal_width() -> int | None: """Return terminal width in columns Uses `os.get_terminal_size` function diff --git a/pyproject.toml b/pyproject.toml index a9e75817..a831b833 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.mypy] -python_version = "3.9" +python_version = "3.10" show_column_numbers = true show_error_context = true ignore_missing_imports = true @@ -35,6 +35,7 @@ disallow_subclassing_any = false [tool.ruff] line-length = 79 +target-version = "py310" [tool.ruff.format] quote-style = "preserve"