From f812e262c3d0b7e7d5fdbc2b2850f1a8a4c59507 Mon Sep 17 00:00:00 2001
From: Stephen Finucane <sfinucan@redhat.com>
Date: Thu, 12 Sep 2024 17:38:39 +0100
Subject: [PATCH] ruff: Enable pyupgrade rules

Change-Id: I6cd2d5e1a3fe79eb41199059cb98c5c535c6154b
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
---
 .pre-commit-config.yaml              |  2 +-
 cliff/_argparse.py                   | 12 ++++--------
 cliff/app.py                         | 11 +++++------
 cliff/columns.py                     |  4 ++--
 cliff/command.py                     |  4 ++--
 cliff/commandmanager.py              |  6 +++---
 cliff/complete.py                    | 18 +++++++++---------
 cliff/display.py                     | 13 +++++--------
 cliff/formatters/base.py             |  2 +-
 cliff/formatters/shell.py            |  2 +-
 cliff/formatters/value.py            | 11 ++++++-----
 cliff/help.py                        | 16 ++++++++--------
 cliff/hooks.py                       |  2 +-
 cliff/interactive.py                 |  2 +-
 cliff/lister.py                      |  2 +-
 cliff/sphinxext.py                   | 23 ++++++++++-------------
 cliff/tests/base.py                  |  3 +--
 cliff/tests/test__argparse.py        |  1 -
 cliff/tests/test_app.py              | 17 +++++++----------
 cliff/tests/test_columns.py          |  2 +-
 cliff/tests/test_command.py          |  2 +-
 cliff/tests/test_command_hooks.py    | 14 +++++++-------
 cliff/tests/test_commandmanager.py   |  4 ++--
 cliff/tests/test_formatters_csv.py   |  3 +--
 cliff/tests/test_formatters_shell.py |  2 +-
 cliff/tests/test_formatters_table.py |  2 +-
 cliff/tests/test_help.py             |  2 +-
 cliff/tests/test_interactive.py      |  3 +--
 cliff/tests/test_lister.py           |  2 +-
 cliff/tests/test_show.py             |  2 +-
 cliff/tests/utils.py                 |  2 +-
 demoapp/cliffdemo/encoding.py        |  2 --
 demoapp/cliffdemo/main.py            |  2 +-
 demoapp/cliffdemo/show.py            |  2 +-
 demoapp/setup.py                     |  4 ++--
 doc/source/conf.py                   |  7 ++-----
 pyproject.toml                       |  2 +-
 37 files changed, 94 insertions(+), 116 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3ff13df5..b51648c2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,5 +18,5 @@ repos:
     rev: v0.6.4
     hooks:
       - id: ruff
-        args: ['--fix']
+        args: ['--fix', '--unsafe-fixes']
       - id: ruff-format
diff --git a/cliff/_argparse.py b/cliff/_argparse.py
index b8881b45..d6635c81 100644
--- a/cliff/_argparse.py
+++ b/cliff/_argparse.py
@@ -18,7 +18,7 @@ import warnings
 from autopage import argparse
 
 
-class _ArgumentContainerMixIn(object):
+class _ArgumentContainerMixIn:
     # NOTE(dhellmann): We have to override the methods for creating
     # groups to return our objects that know how to deal with the
     # special conflict handler.
@@ -62,10 +62,8 @@ def _handle_conflict_ignore(
         # remove the conflicting option from the new action
         new_action.option_strings.remove(option_string)
         warnings.warn(
-            (
-                'Ignoring option string {} for new action '
-                'because it conflicts with an existing option.'
-            ).format(option_string)
+            f'Ignoring option string {option_string} for new action '
+            'because it conflicts with an existing option.'
         )
 
         # if the option now has no option string, remove it from the
@@ -106,7 +104,5 @@ class SmartHelpFormatter(argparse.HelpFormatter):
         lines = text.splitlines() if '\n' in text else [text]
         wrap_lines = []
         for each_line in lines:
-            wrap_lines.extend(
-                super(SmartHelpFormatter, self)._split_lines(each_line, width)
-            )
+            wrap_lines.extend(super()._split_lines(each_line, width))
         return wrap_lines
diff --git a/cliff/app.py b/cliff/app.py
index 8a9d18cf..6c654db1 100644
--- a/cliff/app.py
+++ b/cliff/app.py
@@ -33,7 +33,7 @@ _SIGINT_EXIT = 130
 _SIGPIPE_EXIT = 141
 
 
-class App(object):
+class App:
     """Application base class.
 
     :param description: one-liner explaining the program purpose
@@ -147,7 +147,7 @@ class App(object):
         parser.add_argument(
             '--version',
             action='version',
-            version='{0} {1}'.format(App.NAME, version),
+            version=f'{App.NAME} {version}',
         )
         verbose_group = parser.add_mutually_exclusive_group()
         verbose_group.add_argument(
@@ -383,9 +383,8 @@ class App(object):
                 if self.NAME[0] in 'aeiou':
                     article = 'an'
                 self.stdout.write(
-                    '%s: \'%s\' is not %s %s command. '
-                    'See \'%s --help\'.\n'
-                    % (
+                    '{}: \'{}\' is not {} {} command. '
+                    'See \'{} --help\'.\n'.format(
                         self.NAME,
                         ' '.join(argv),
                         article,
@@ -395,7 +394,7 @@ class App(object):
                 )
                 self.stdout.write('Did you mean one of these?\n')
                 for match in fuzzy_matches:
-                    self.stdout.write('  %s\n' % match)
+                    self.stdout.write(f'  {match}\n')
             else:
                 if self.options.debug:
                     raise
diff --git a/cliff/columns.py b/cliff/columns.py
index bdc4f38b..3293294e 100644
--- a/cliff/columns.py
+++ b/cliff/columns.py
@@ -15,7 +15,7 @@
 import abc
 
 
-class FormattableColumn(object, metaclass=abc.ABCMeta):
+class FormattableColumn(metaclass=abc.ABCMeta):
     def __init__(self, value):
         self._value = value
 
@@ -31,7 +31,7 @@ class FormattableColumn(object, metaclass=abc.ABCMeta):
         return self.human_readable()
 
     def __repr__(self):
-        return '%s(%r)' % (self.__class__.__name__, self.machine_readable())
+        return f'{self.__class__.__name__}({self.machine_readable()!r})'
 
     @abc.abstractmethod
     def human_readable(self):
diff --git a/cliff/command.py b/cliff/command.py
index 3253a429..9beafe8d 100644
--- a/cliff/command.py
+++ b/cliff/command.py
@@ -52,7 +52,7 @@ def _get_distribution_for_module(module):
     return dist_name
 
 
-class Command(object, metaclass=abc.ABCMeta):
+class Command(metaclass=abc.ABCMeta):
     """Base class for command plugins.
 
     When the command is instantiated, it loads extensions from a
@@ -137,7 +137,7 @@ class Command(object, metaclass=abc.ABCMeta):
         dist_name = _get_distribution_for_module(inspect.getmodule(self))
         if dist_name and dist_name != app_dist_name:
             parts.append(
-                'This command is provided by the %s plugin.' % (dist_name,)
+                f'This command is provided by the {dist_name} plugin.'
             )
         return '\n\n'.join(parts)
 
diff --git a/cliff/commandmanager.py b/cliff/commandmanager.py
index cc5f2f75..ffc7e322 100644
--- a/cliff/commandmanager.py
+++ b/cliff/commandmanager.py
@@ -33,7 +33,7 @@ def _get_commands_by_partial_name(args, commands):
     return candidates
 
 
-class EntryPointWrapper(object):
+class EntryPointWrapper:
     """Wrap up a command class already imported to make it look like a plugin."""
 
     def __init__(self, name, command_class):
@@ -44,7 +44,7 @@ class EntryPointWrapper(object):
         return self.command_class
 
 
-class CommandManager(object):
+class CommandManager:
     """Discovers commands and handles lookup based on argv data.
 
     :param namespace: String containing the entrypoint namespace for the
@@ -133,7 +133,7 @@ class CommandManager(object):
                         cmd_factory = cmd_ep.load()
                 return (cmd_factory, return_name, search_args)
         else:
-            raise ValueError('Unknown command %r' % (argv,))
+            raise ValueError(f'Unknown command {argv!r}')
 
     def _get_last_possible_command_index(self, argv):
         """Returns the index after the last argument
diff --git a/cliff/complete.py b/cliff/complete.py
index 28203296..d3d95729 100644
--- a/cliff/complete.py
+++ b/cliff/complete.py
@@ -67,7 +67,7 @@ class CompleteDictionary:
         return sorted(self._get_data_recurse(self._dictionary, ""))
 
 
-class CompleteShellBase(object):
+class CompleteShellBase:
     """base class for bash completion generation"""
 
     def __init__(self, name, output):
@@ -76,10 +76,10 @@ class CompleteShellBase(object):
 
     def write(self, cmdo, data):
         self.output.write(self.get_header())
-        self.output.write("  cmds='{0}'\n".format(cmdo))
+        self.output.write(f"  cmds='{cmdo}'\n")
         for datum in data:
             datum = (datum[0].replace('-', '_'), datum[1])
-            self.output.write('  cmds_{0}=\'{1}\'\n'.format(*datum))
+            self.output.write('  cmds_{}=\'{}\'\n'.format(*datum))
         self.output.write(self.get_trailer())
 
     @property
@@ -91,7 +91,7 @@ class CompleteNoCode(CompleteShellBase):
     """completion with no code"""
 
     def __init__(self, name, output):
-        super(CompleteNoCode, self).__init__(name, output)
+        super().__init__(name, output)
 
     def get_header(self):
         return ''
@@ -104,7 +104,7 @@ class CompleteBash(CompleteShellBase):
     """completion for bash"""
 
     def __init__(self, name, output):
-        super(CompleteBash, self).__init__(name, output)
+        super().__init__(name, output)
 
     def get_header(self):
         return (
@@ -175,13 +175,13 @@ class CompleteCommand(command.Command):
     log = logging.getLogger(__name__ + '.CompleteCommand')
 
     def __init__(self, app, app_args, cmd_name=None):
-        super(CompleteCommand, self).__init__(app, app_args, cmd_name)
+        super().__init__(app, app_args, cmd_name)
         self._formatters = stevedore.ExtensionManager(
             namespace='cliff.formatter.completion',
         )
 
     def get_parser(self, prog_name):
-        parser = super(CompleteCommand, self).get_parser(prog_name)
+        parser = super().get_parser(prog_name)
         parser.add_argument(
             "--name",
             default=None,
@@ -209,13 +209,13 @@ class CompleteCommand(command.Command):
         return cmd_parser._get_optional_actions()
 
     def take_action(self, parsed_args):
-        self.log.debug('take_action(%s)' % parsed_args)
+        self.log.debug(f'take_action({parsed_args})')
 
         name = parsed_args.name or self.app.NAME
         try:
             shell_factory = self._formatters[parsed_args.shell].plugin
         except KeyError:
-            raise RuntimeError('Unknown shell syntax %r' % parsed_args.shell)
+            raise RuntimeError(f'Unknown shell syntax {parsed_args.shell!r}')
         shell = shell_factory(name, self.app.stdout)
 
         dicto = CompleteDictionary()
diff --git a/cliff/display.py b/cliff/display.py
index 1a25ee0d..d413ac24 100644
--- a/cliff/display.py
+++ b/cliff/display.py
@@ -24,9 +24,7 @@ class DisplayCommandBase(command.Command, metaclass=abc.ABCMeta):
     """Command base class for displaying data about a single object."""
 
     def __init__(self, app, app_args, cmd_name=None):
-        super(DisplayCommandBase, self).__init__(
-            app, app_args, cmd_name=cmd_name
-        )
+        super().__init__(app, app_args, cmd_name=cmd_name)
         self._formatter_plugins = self._load_formatter_plugins()
 
     @property
@@ -47,7 +45,7 @@ class DisplayCommandBase(command.Command, metaclass=abc.ABCMeta):
         )
 
     def get_parser(self, prog_name):
-        parser = super(DisplayCommandBase, self).get_parser(prog_name)
+        parser = super().get_parser(prog_name)
         formatter_group = parser.add_argument_group(
             title='output formatters',
             description='output formatter options',
@@ -64,7 +62,7 @@ class DisplayCommandBase(command.Command, metaclass=abc.ABCMeta):
             action='store',
             choices=formatter_choices,
             default=formatter_default,
-            help='the output format, defaults to %s' % formatter_default,
+            help=f'the output format, defaults to {formatter_default}',
         )
         formatter_group.add_argument(
             '-c',
@@ -114,9 +112,8 @@ class DisplayCommandBase(command.Command, metaclass=abc.ABCMeta):
         ]
         if not columns_to_include:
             raise ValueError(
-                'No recognized column names in %s. '
-                'Recognized columns are %s.'
-                % (str(parsed_args.columns), str(column_names))
+                f'No recognized column names in {str(parsed_args.columns)}. '
+                f'Recognized columns are {str(column_names)}.'
             )
 
         # Set up argument to compress()
diff --git a/cliff/formatters/base.py b/cliff/formatters/base.py
index 9ffd790a..cf76bbcc 100644
--- a/cliff/formatters/base.py
+++ b/cliff/formatters/base.py
@@ -15,7 +15,7 @@
 import abc
 
 
-class Formatter(object, metaclass=abc.ABCMeta):
+class Formatter(metaclass=abc.ABCMeta):
     @abc.abstractmethod
     def add_argument_group(self, parser):
         """Add any options to the argument parser.
diff --git a/cliff/formatters/shell.py b/cliff/formatters/shell.py
index 971afd22..04e712cf 100644
--- a/cliff/formatters/shell.py
+++ b/cliff/formatters/shell.py
@@ -58,5 +58,5 @@ class ShellFormatter(base.SingleFormatter):
                     # underscore.
                     name = name.replace(':', '_')
                     name = name.replace('-', '_')
-                stdout.write('%s%s="%s"\n' % (parsed_args.prefix, name, value))
+                stdout.write(f'{parsed_args.prefix}{name}="{value}"\n')
         return
diff --git a/cliff/formatters/value.py b/cliff/formatters/value.py
index 61757c9a..922625f3 100644
--- a/cliff/formatters/value.py
+++ b/cliff/formatters/value.py
@@ -38,11 +38,12 @@ class ValueFormatter(base.ListFormatter, base.SingleFormatter):
     def emit_one(self, column_names, data, stdout, parsed_args):
         for value in data:
             stdout.write(
-                '%s\n'
-                % str(
-                    value.machine_readable()
-                    if isinstance(value, columns.FormattableColumn)
-                    else value
+                '{}\n'.format(
+                    str(
+                        value.machine_readable()
+                        if isinstance(value, columns.FormattableColumn)
+                        else value
+                    )
                 )
             )
         return
diff --git a/cliff/help.py b/cliff/help.py
index 620be3c9..10125069 100644
--- a/cliff/help.py
+++ b/cliff/help.py
@@ -45,7 +45,7 @@ class HelpAction(argparse.Action):
         with pager as out:
             parser.print_help(out)
             title_hl = ('\033[4m', '\033[0m') if color else ('', '')
-            out.write('\n%sCommands%s:\n' % title_hl)
+            out.write('\n{}Commands{}:\n'.format(*title_hl))
             dists_by_module = command._get_distributions_by_modules()
 
             def dist_for_obj(obj):
@@ -58,7 +58,7 @@ class HelpAction(argparse.Action):
                 try:
                     factory = ep.load()
                 except Exception:
-                    out.write('Could not load %r\n' % ep)
+                    out.write(f'Could not load {ep!r}\n')
                     if namespace.debug:
                         traceback.print_exc(file=out)
                     continue
@@ -71,7 +71,7 @@ class HelpAction(argparse.Action):
                     if cmd.deprecated:
                         continue
                 except Exception as err:
-                    out.write('Could not instantiate %r: %s\n' % (ep, err))
+                    out.write(f'Could not instantiate {ep!r}: {err}\n')
                     if namespace.debug:
                         traceback.print_exc(file=out)
                     continue
@@ -80,11 +80,11 @@ class HelpAction(argparse.Action):
                 if dist_name and dist_name != app_dist:
                     dist_info = ' (' + dist_name + ')'
                     if color:
-                        dist_info = '\033[90m%s\033[39m' % dist_info
+                        dist_info = f'\033[90m{dist_info}\033[39m'
                 else:
                     dist_info = ''
                 if color:
-                    name = '\033[36m%s\033[39m' % name
+                    name = f'\033[36m{name}\033[39m'
                 out.write('  %-13s  %s%s\n' % (name, one_liner, dist_info))
         raise HelpExit()
 
@@ -93,7 +93,7 @@ class HelpCommand(command.Command):
     """print detailed help for another command"""
 
     def get_parser(self, prog_name):
-        parser = super(HelpCommand, self).get_parser(prog_name)
+        parser = super().get_parser(prog_name)
         parser.add_argument(
             'cmd',
             nargs='*',
@@ -118,9 +118,9 @@ class HelpCommand(command.Command):
                 ]
                 if not fuzzy_matches:
                     raise
-                self.app.stdout.write('Command "%s" matches:\n' % cmd)
+                self.app.stdout.write(f'Command "{cmd}" matches:\n')
                 for fm in sorted(fuzzy_matches):
-                    self.app.stdout.write('  %s\n' % fm)
+                    self.app.stdout.write(f'  {fm}\n')
                 return
             self.app_args.cmd = search_args
             kwargs = {}
diff --git a/cliff/hooks.py b/cliff/hooks.py
index 44e04028..27593ab4 100644
--- a/cliff/hooks.py
+++ b/cliff/hooks.py
@@ -13,7 +13,7 @@
 import abc
 
 
-class CommandHook(object, metaclass=abc.ABCMeta):
+class CommandHook(metaclass=abc.ABCMeta):
     """Base class for command hooks.
 
     :param app: Command instance being invoked
diff --git a/cliff/interactive.py b/cliff/interactive.py
index 40ca9ae7..59f47717 100644
--- a/cliff/interactive.py
+++ b/cliff/interactive.py
@@ -46,7 +46,7 @@ class InteractiveApp(cmd2.Cmd):
     ):
         self.parent_app = parent_app
         if not hasattr(sys.stdin, 'isatty') or sys.stdin.isatty():
-            self.prompt = '(%s) ' % parent_app.NAME
+            self.prompt = f'({parent_app.NAME}) '
         else:
             # batch/pipe mode
             self.prompt = ''
diff --git a/cliff/lister.py b/cliff/lister.py
index e7d628b7..7f40713e 100644
--- a/cliff/lister.py
+++ b/cliff/lister.py
@@ -49,7 +49,7 @@ class Lister(display.DisplayCommandBase, metaclass=abc.ABCMeta):
         """
 
     def get_parser(self, prog_name):
-        parser = super(Lister, self).get_parser(prog_name)
+        parser = super().get_parser(prog_name)
         group = self._formatter_group
         group.add_argument(
             '--sort-column',
diff --git a/cliff/sphinxext.py b/cliff/sphinxext.py
index f9f3bd70..2591341c 100644
--- a/cliff/sphinxext.py
+++ b/cliff/sphinxext.py
@@ -45,10 +45,9 @@ def _format_description(parser):
     We parse this as reStructuredText, allowing users to embed rich
     information in their help messages if they so choose.
     """
-    for line in statemachine.string2lines(
+    yield from statemachine.string2lines(
         parser.description, tab_width=4, convert_whitespace=True
-    ):
-        yield line
+    )
 
 
 def _format_usage(parser):
@@ -94,10 +93,9 @@ def _format_epilog(parser):
     We parse this as reStructuredText, allowing users to embed rich
     information in their help messages if they so choose.
     """
-    for line in statemachine.string2lines(
+    yield from statemachine.string2lines(
         parser.epilog, tab_width=4, convert_whitespace=True
-    ):
-        yield line
+    )
 
 
 def _format_positional_action(action):
@@ -131,7 +129,7 @@ def _format_optional_action(action):
         # information about the options themselves, for example, if nargs is
         # specified
         option_strings = [
-            ' '.join([x, action.metavar or '<{}>'.format(action.dest.upper())])
+            ' '.join([x, action.metavar or f'<{action.dest.upper()}>'])
             for x in action.option_strings
         ]
         yield '.. option:: {}'.format(', '.join(option_strings))
@@ -185,7 +183,7 @@ def _format_parser(parser):
             yield line
         yield ''
 
-    yield '.. program:: {}'.format(parser.prog)
+    yield f'.. program:: {parser.prog}'
 
     yield '.. code-block:: shell'
     yield ''
@@ -267,9 +265,8 @@ class AutoprogramCliffDirective(rst.Directive):
             return manager.find_command(command_name.split())[0]
         except ValueError:
             raise self.error(
-                '"{}" is not a valid command in the "{}" ' 'namespace'.format(
-                    command_name, manager.namespace
-                )
+                f'"{command_name}" is not a valid command in the "{manager.namespace}" '
+                'namespace'
             )
 
     def _load_commands(self):
@@ -310,7 +307,7 @@ class AutoprogramCliffDirective(rst.Directive):
 
         parser.prog = application_name
 
-        source_name = '<{}>'.format(app.__class__.__name__)
+        source_name = f'<{app.__class__.__name__}>'
         result = statemachine.ViewList()
         for line in _format_parser(parser):
             result.append(line, source_name)
@@ -357,7 +354,7 @@ class AutoprogramCliffDirective(rst.Directive):
             names=[nodes.fully_normalize_name(title)],
         )
 
-        source_name = '<{}>'.format(command.__class__.__name__)
+        source_name = f'<{command.__class__.__name__}>'
         result = statemachine.ViewList()
 
         for line in _format_parser(parser):
diff --git a/cliff/tests/base.py b/cliff/tests/base.py
index c213dc62..ec3203a2 100644
--- a/cliff/tests/base.py
+++ b/cliff/tests/base.py
@@ -1,4 +1,3 @@
-# -*- encoding: utf-8 -*-
 #
 #  Licensed under the Apache License, Version 2.0 (the "License"); you may
 #  not use this file except in compliance with the License. You may obtain
@@ -19,7 +18,7 @@ import fixtures
 
 class TestBase(testtools.TestCase):
     def setUp(self):
-        super(TestBase, self).setUp()
+        super().setUp()
         self._stdout_fixture = fixtures.StringStream('stdout')
         self.stdout = self.useFixture(self._stdout_fixture).stream
         self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout))
diff --git a/cliff/tests/test__argparse.py b/cliff/tests/test__argparse.py
index 31caf40b..cad1edc6 100644
--- a/cliff/tests/test__argparse.py
+++ b/cliff/tests/test__argparse.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
 #
 #  Licensed under the Apache License, Version 2.0 (the "License"); you may
 #  not use this file except in compliance with the License. You may obtain
diff --git a/cliff/tests/test_app.py b/cliff/tests/test_app.py
index d360b96e..1cedca36 100644
--- a/cliff/tests/test_app.py
+++ b/cliff/tests/test_app.py
@@ -1,4 +1,3 @@
-# -*- encoding: utf-8 -*-
 #
 #  Licensed under the Apache License, Version 2.0 (the "License"); you may
 #  not use this file except in compliance with the License. You may obtain
@@ -264,16 +263,14 @@ class TestOptionParser(base.TestBase):
     def test_conflicting_option_should_throw(self):
         class MyApp(application.App):
             def __init__(self):
-                super(MyApp, self).__init__(
+                super().__init__(
                     description='testing',
                     version='0.1',
                     command_manager=commandmanager.CommandManager('tests'),
                 )
 
             def build_option_parser(self, description, version):
-                parser = super(MyApp, self).build_option_parser(
-                    description, version
-                )
+                parser = super().build_option_parser(description, version)
                 parser.add_argument(
                     '-h',
                     '--help',
@@ -289,7 +286,7 @@ class TestOptionParser(base.TestBase):
     def test_conflicting_option_custom_arguments_should_not_throw(self):
         class MyApp(application.App):
             def __init__(self):
-                super(MyApp, self).__init__(
+                super().__init__(
                     description='testing',
                     version='0.1',
                     command_manager=commandmanager.CommandManager('tests'),
@@ -297,7 +294,7 @@ class TestOptionParser(base.TestBase):
 
             def build_option_parser(self, description, version):
                 argparse_kwargs = {'conflict_handler': 'resolve'}
-                parser = super(MyApp, self).build_option_parser(
+                parser = super().build_option_parser(
                     description, version, argparse_kwargs=argparse_kwargs
                 )
                 parser.add_argument(
@@ -312,7 +309,7 @@ class TestOptionParser(base.TestBase):
     def test_option_parser_abbrev_issue(self):
         class MyCommand(c_cmd.Command):
             def get_parser(self, prog_name):
-                parser = super(MyCommand, self).get_parser(prog_name)
+                parser = super().get_parser(prog_name)
                 parser.add_argument("--end")
                 return parser
 
@@ -325,14 +322,14 @@ class TestOptionParser(base.TestBase):
 
         class MyApp(application.App):
             def __init__(self):
-                super(MyApp, self).__init__(
+                super().__init__(
                     description='testing',
                     version='0.1',
                     command_manager=MyCommandManager(None),
                 )
 
             def build_option_parser(self, description, version):
-                parser = super(MyApp, self).build_option_parser(
+                parser = super().build_option_parser(
                     description,
                     version,
                     argparse_kwargs={'allow_abbrev': False},
diff --git a/cliff/tests/test_columns.py b/cliff/tests/test_columns.py
index c2e95d16..d44b016d 100644
--- a/cliff/tests/test_columns.py
+++ b/cliff/tests/test_columns.py
@@ -17,7 +17,7 @@ from cliff import columns
 
 class FauxColumn(columns.FormattableColumn):
     def human_readable(self):
-        return 'I made this string myself: {}'.format(self._value)
+        return f'I made this string myself: {self._value}'
 
 
 class TestColumns(unittest.TestCase):
diff --git a/cliff/tests/test_command.py b/cliff/tests/test_command.py
index 3cd77024..93df611d 100644
--- a/cliff/tests/test_command.py
+++ b/cliff/tests/test_command.py
@@ -21,7 +21,7 @@ class TestCommand(command.Command):
     """Description of command."""
 
     def get_parser(self, prog_name):
-        parser = super(TestCommand, self).get_parser(prog_name)
+        parser = super().get_parser(prog_name)
         parser.add_argument(
             'long_help_argument',
             help="Create a NIC on the server.\n"
diff --git a/cliff/tests/test_command_hooks.py b/cliff/tests/test_command_hooks.py
index 04c988ba..54853a5d 100644
--- a/cliff/tests/test_command_hooks.py
+++ b/cliff/tests/test_command_hooks.py
@@ -55,7 +55,7 @@ class TestCommand(command.Command):
     """Description of command."""
 
     def get_parser(self, prog_name):
-        parser = super(TestCommand, self).get_parser(prog_name)
+        parser = super().get_parser(prog_name)
         return parser
 
     def take_action(self, parsed_args):
@@ -177,7 +177,7 @@ class TestCommandLoadHooks(base.TestBase):
 
 class TestHooks(base.TestBase):
     def setUp(self):
-        super(TestHooks, self).setUp()
+        super().setUp()
         self.app = make_app()
         self.cmd = TestCommand(self.app, None, cmd_name='test')
         self.hook = TestHook(self.cmd)
@@ -211,7 +211,7 @@ class TestHooks(base.TestBase):
 
 class TestChangeHooks(base.TestBase):
     def setUp(self):
-        super(TestChangeHooks, self).setUp()
+        super().setUp()
         self.app = make_app()
         self.cmd = TestCommand(self.app, None, cmd_name='test')
         self.hook = TestChangeHook(self.cmd)
@@ -251,7 +251,7 @@ class TestChangeHooks(base.TestBase):
 
 class TestShowOneHooks(base.TestBase):
     def setUp(self):
-        super(TestShowOneHooks, self).setUp()
+        super().setUp()
         self.app = make_app()
         self.cmd = TestShowCommand(self.app, None, cmd_name='test')
         self.hook = TestHook(self.cmd)
@@ -288,7 +288,7 @@ class TestShowOneHooks(base.TestBase):
 
 class TestShowOneChangeHooks(base.TestBase):
     def setUp(self):
-        super(TestShowOneChangeHooks, self).setUp()
+        super().setUp()
         self.app = make_app()
         self.cmd = TestShowCommand(self.app, None, cmd_name='test')
         self.hook = TestDisplayChangeHook(self.cmd)
@@ -328,7 +328,7 @@ class TestShowOneChangeHooks(base.TestBase):
 
 class TestListerHooks(base.TestBase):
     def setUp(self):
-        super(TestListerHooks, self).setUp()
+        super().setUp()
         self.app = make_app()
         self.cmd = TestListerCommand(self.app, None, cmd_name='test')
         self.hook = TestHook(self.cmd)
@@ -365,7 +365,7 @@ class TestListerHooks(base.TestBase):
 
 class TestListerChangeHooks(base.TestBase):
     def setUp(self):
-        super(TestListerChangeHooks, self).setUp()
+        super().setUp()
         self.app = make_app()
         self.cmd = TestListerCommand(self.app, None, cmd_name='test')
         self.hook = TestListerChangeHook(self.cmd)
diff --git a/cliff/tests/test_commandmanager.py b/cliff/tests/test_commandmanager.py
index 7f2fde01..ad55ba90 100644
--- a/cliff/tests/test_commandmanager.py
+++ b/cliff/tests/test_commandmanager.py
@@ -213,7 +213,7 @@ class TestLookupAndFindPartialName(base.TestBase):
 
 class TestGetByPartialName(base.TestBase):
     def setUp(self):
-        super(TestGetByPartialName, self).setUp()
+        super().setUp()
         self.commands = {
             'resource provider list': 1,
             'resource class list': 2,
@@ -266,7 +266,7 @@ class TestGetByPartialName(base.TestBase):
         )
 
 
-class FakeCommand(object):
+class FakeCommand:
     @classmethod
     def load(cls):
         return cls
diff --git a/cliff/tests/test_formatters_csv.py b/cliff/tests/test_formatters_csv.py
index ccf12dcc..6baf2783 100644
--- a/cliff/tests/test_formatters_csv.py
+++ b/cliff/tests/test_formatters_csv.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
 #
 #  Licensed under the Apache License, Version 2.0 (the "License"); you may
 #  not use this file except in compliance with the License. You may obtain
@@ -73,7 +72,7 @@ class TestCSVFormatter(unittest.TestCase):
         happy = '高兴'
         d2 = ('D', 'E', happy)
         data = [d1, d2]
-        expected = 'a,b,c\nA,B,C\nD,E,%s\n' % happy
+        expected = f'a,b,c\nA,B,C\nD,E,{happy}\n'
         output = io.StringIO()
         parsed_args = mock.Mock()
         parsed_args.quote_mode = 'none'
diff --git a/cliff/tests/test_formatters_shell.py b/cliff/tests/test_formatters_shell.py
index 40e5f3d4..f4dde71d 100644
--- a/cliff/tests/test_formatters_shell.py
+++ b/cliff/tests/test_formatters_shell.py
@@ -71,7 +71,7 @@ class TestShellFormatter(base.TestBase):
     def test_non_string_values(self):
         sf = shell.ShellFormatter()
         c = ('a', 'b', 'c', 'd', 'e')
-        d = (True, False, 100, '"esc"', str('"esc"'))
+        d = (True, False, 100, '"esc"', '"esc"')
         expected = (
             'a="True"\nb="False"\nc="100"\n' 'd="\\"esc\\""\ne="\\"esc\\""\n'
         )
diff --git a/cliff/tests/test_formatters_table.py b/cliff/tests/test_formatters_table.py
index b7360c64..23c7d8cc 100644
--- a/cliff/tests/test_formatters_table.py
+++ b/cliff/tests/test_formatters_table.py
@@ -24,7 +24,7 @@ from cliff.tests import base
 from cliff.tests import test_columns
 
 
-class args(object):
+class args:
     def __init__(self, max_width=0, print_empty=False, fit_width=False):
         self.fit_width = fit_width
         if max_width > 0:
diff --git a/cliff/tests/test_help.py b/cliff/tests/test_help.py
index 0fa74e92..571211d1 100644
--- a/cliff/tests/test_help.py
+++ b/cliff/tests/test_help.py
@@ -111,7 +111,7 @@ class TestHelp(base.TestBase):
             pass
         help_text = stdout.getvalue()
         basecommand = os.path.split(sys.argv[0])[1]
-        self.assertIn('usage: %s [--version]' % basecommand, help_text)
+        self.assertIn(f'usage: {basecommand} [--version]', help_text)
         self.assertRegex(help_text, 'option(s|al arguments):\n  --version')
         expected = (
             '  one            Test command\n'
diff --git a/cliff/tests/test_interactive.py b/cliff/tests/test_interactive.py
index 75315940..3dc6a245 100644
--- a/cliff/tests/test_interactive.py
+++ b/cliff/tests/test_interactive.py
@@ -1,4 +1,3 @@
-# -*- encoding: utf-8 -*-
 #
 #  Licensed under the Apache License, Version 2.0 (the "License"); you may
 #  not use this file except in compliance with the License. You may obtain
@@ -18,7 +17,7 @@ from cliff.interactive import InteractiveApp
 from cliff.tests import base
 
 
-class FakeApp(object):
+class FakeApp:
     NAME = 'Fake'
 
 
diff --git a/cliff/tests/test_lister.py b/cliff/tests/test_lister.py
index 312198df..adc58778 100644
--- a/cliff/tests/test_lister.py
+++ b/cliff/tests/test_lister.py
@@ -20,7 +20,7 @@ from cliff import lister
 from cliff.tests import base
 
 
-class FauxFormatter(object):
+class FauxFormatter:
     def __init__(self):
         self.args = []
         self.obj = weakref.proxy(self)
diff --git a/cliff/tests/test_show.py b/cliff/tests/test_show.py
index fe4eef45..2de78194 100644
--- a/cliff/tests/test_show.py
+++ b/cliff/tests/test_show.py
@@ -20,7 +20,7 @@ from cliff import show
 from cliff.tests import base
 
 
-class FauxFormatter(object):
+class FauxFormatter:
     def __init__(self):
         self.args = []
         self.obj = weakref.proxy(self)
diff --git a/cliff/tests/utils.py b/cliff/tests/utils.py
index b9cf8d33..04b9868d 100644
--- a/cliff/tests/utils.py
+++ b/cliff/tests/utils.py
@@ -16,7 +16,7 @@ from cliff.commandmanager import CommandManager
 TEST_NAMESPACE = 'cliff.test'
 
 
-class TestParser(object):
+class TestParser:
     def print_help(self, stdout):
         stdout.write('TestParser')
 
diff --git a/demoapp/cliffdemo/encoding.py b/demoapp/cliffdemo/encoding.py
index 99f4b2be..3200a8a9 100644
--- a/demoapp/cliffdemo/encoding.py
+++ b/demoapp/cliffdemo/encoding.py
@@ -1,5 +1,3 @@
-# -*- encoding: utf-8 -*-
-
 import logging
 
 from cliff.lister import Lister
diff --git a/demoapp/cliffdemo/main.py b/demoapp/cliffdemo/main.py
index 71b6445d..ac066ca6 100644
--- a/demoapp/cliffdemo/main.py
+++ b/demoapp/cliffdemo/main.py
@@ -6,7 +6,7 @@ from cliff.commandmanager import CommandManager
 
 class DemoApp(App):
     def __init__(self):
-        super(DemoApp, self).__init__(
+        super().__init__(
             description='cliff demo app',
             version='0.1',
             command_manager=CommandManager('cliff.demo'),
diff --git a/demoapp/cliffdemo/show.py b/demoapp/cliffdemo/show.py
index 115b43ab..255c62c9 100644
--- a/demoapp/cliffdemo/show.py
+++ b/demoapp/cliffdemo/show.py
@@ -10,7 +10,7 @@ class File(ShowOne):
     log = logging.getLogger(__name__)
 
     def get_parser(self, prog_name):
-        parser = super(File, self).get_parser(prog_name)
+        parser = super().get_parser(prog_name)
         parser.add_argument('filename', nargs='?', default='.')
         return parser
 
diff --git a/demoapp/setup.py b/demoapp/setup.py
index 21477ef3..c5c48537 100644
--- a/demoapp/setup.py
+++ b/demoapp/setup.py
@@ -9,8 +9,8 @@ PROJECT = 'cliffdemo'
 VERSION = '0.1'
 
 try:
-    long_description = open('README.rst', 'rt').read()
-except IOError:
+    long_description = open('README.rst').read()
+except OSError:
     long_description = ''
 
 setup(
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 08d7fa1c..ecb81f01 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
-#
-# cliff documentation build configuration file, created by
-# sphinx-quickstart on Wed Apr 25 11:14:29 2012.
+# cliff documentation build configuration file
 #
 # This file is execfile()d with the current directory set to its
 # containing dir.
@@ -70,7 +67,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = 'cliff'
-copyright = '2012-%s, Doug Hellmann' % datetime.datetime.today().year
+copyright = f'2012-{datetime.datetime.today().year}, Doug Hellmann'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/pyproject.toml b/pyproject.toml
index acbe48be..22439d04 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ quote-style = "preserve"
 docstring-code-format = true
 
 [tool.ruff.lint]
-select = ["E4", "E7", "E9", "F", "S"]
+select = ["E4", "E7", "E9", "F", "S", "UP"]
 
 [tool.ruff.lint.per-file-ignores]
 "cliff/tests/*" = ["S"]