Repository: Flexget/Flexget (language: python)

Issue:
Update pre-commit hook astral-sh/ruff-pre-commit to v0.8.2
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit) | repository | patch | `v0.8.1` -> `v0.8.2` |

Note: The `pre-commit` manager in Renovate is not supported by the `pre-commit` maintainers or community. Please do not report any problems there, instead [create a Discussion in the Renovate repository](https://redirect.github.com/renovatebot/renovate/discussions/new) if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

### [`v0.8.2`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.8.2)

[Compare Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.8.1...v0.8.2)

See: https://github.com/astral-sh/ruff/releases/tag/0.8.2

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/Flexget/Flexget).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xOS4wIiwidXBkYXRlZEluVmVyIjoiMzkuNDIuNCIsInRhcmdldEJyYW5jaCI6ImRldmVsb3AiLCJsYWJlbHMiOltdfQ==-->



Discussion / hints:

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 71e2725e87..f2f7267e77 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,7 +2,7 @@ default_language_version:
   python: python3
 repos:
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: 'v0.7.4'
+  rev: 'v0.8.1'
   hooks:
     - id: ruff
       args: [ --fix, --exit-non-zero-on-fix ]
diff --git a/dev_tools.py b/dev_tools.py
index 3d762e5d9c..a41c7031f0 100644
--- a/dev_tools.py
+++ b/dev_tools.py
@@ -40,7 +40,7 @@ def bump_version(bump_type):
     if 'dev' in ver_split[-1]:
         if bump_type == 'dev':
             # If this is already a development version, increment the dev count by 1
-            ver_split[-1] = 'dev%d' % (int(ver_split[-1].strip('dev') or 0) + 1)
+            ver_split[-1] = 'dev{}'.format(int(ver_split[-1].strip('dev') or 0) + 1)
         else:
             # Just strip off dev tag for next release version
             ver_split = ver_split[:-1]
diff --git a/flexget/api/core/server.py b/flexget/api/core/server.py
index 11da0e64ce..fdcacff79f 100644
--- a/flexget/api/core/server.py
+++ b/flexget/api/core/server.py
@@ -287,7 +287,7 @@ def get(self, session: Session = None) -> Response:
         for threadId, stack in sys._current_frames().items():
             dump = []
             for filename, lineno, name, line in traceback.extract_stack(stack):
-                dump.append('File: "%s", line %d, in %s' % (filename, lineno, name))
+                dump.append(f'File: "{filename}", line {lineno}, in {name}')
                 if line:
                     dump.append(line.strip())
             threads.append({'name': id2name.get(threadId), 'id': threadId, 'dump': dump})
diff --git a/flexget/components/bittorrent/torrent_alive.py b/flexget/components/bittorrent/torrent_alive.py
index 34813a1f94..613fc38bc3 100644
--- a/flexget/components/bittorrent/torrent_alive.py
+++ b/flexget/components/bittorrent/torrent_alive.py
@@ -23,7 +23,7 @@ class TorrentAliveThread(threading.Thread):
     _counter = itertools.count()
 
     def __init__(self, tracker, info_hash):
-        threading.Thread.__init__(self, name='torrent_alive-%d' % next(self._counter))
+        threading.Thread.__init__(self, name=f'torrent_alive-{next(self._counter)}')
         self.tracker = tracker
         self.info_hash = info_hash
         self.tracker_seeds = 0
@@ -201,8 +201,9 @@ def on_task_filter(self, task, config):
         for entry in task.entries:
             if 'torrent_seeds' in entry and entry['torrent_seeds'] < config['min_seeds']:
                 entry.reject(
-                    reason='Had < %d required seeds. (%s)'
-                    % (config['min_seeds'], entry['torrent_seeds'])
+                    reason='Had < {} required seeds. ({})'.format(
+                        config['min_seeds'], entry['torrent_seeds']
+                    )
                 )
 
     # Run on output phase so that we let torrent plugin output modified torrent file first
diff --git a/flexget/components/failed/api.py b/flexget/components/failed/api.py
index 27584af689..ef1ea9b762 100644
--- a/flexget/components/failed/api.py
+++ b/flexget/components/failed/api.py
@@ -120,7 +120,7 @@ def delete(self, session=None):
         """Clear all failed entries"""
         logger.debug('deleting all failed entries')
         deleted = session.query(db.FailedEntry).delete()
-        return success_response('successfully deleted %d failed entries' % deleted)
+        return success_response(f'successfully deleted {deleted} failed entries')
 
 
 @retry_failed_api.route('/<int:failed_entry_id>/')
@@ -136,7 +136,7 @@ def get(self, failed_entry_id, session=None):
                 session.query(db.FailedEntry).filter(db.FailedEntry.id == failed_entry_id).one()
             )
         except NoResultFound:
-            raise NotFoundError('could not find entry with ID %i' % failed_entry_id)
+            raise NotFoundError(f'could not find entry with ID {failed_entry_id}')
         return jsonify(failed_entry.to_dict())
 
     @api.response(200, 'successfully delete failed entry', model=base_message_schema)
@@ -147,7 +147,7 @@ def delete(self, failed_entry_id, session=None):
                 session.query(db.FailedEntry).filter(db.FailedEntry.id == failed_entry_id).one()
             )
         except NoResultFound:
-            raise NotFoundError('could not find entry with ID %i' % failed_entry_id)
+            raise NotFoundError(f'could not find entry with ID {failed_entry_id}')
         logger.debug('deleting failed entry: "{}"', failed_entry.title)
         session.delete(failed_entry)
-        return success_response('successfully delete failed entry %d' % failed_entry_id)
+        return success_response(f'successfully delete failed entry {failed_entry_id}')
diff --git a/flexget/components/failed/cli.py b/flexget/components/failed/cli.py
index b617913c61..12a5591d17 100644
--- a/flexget/components/failed/cli.py
+++ b/flexget/components/failed/cli.py
@@ -39,7 +39,7 @@ def clear_failed(manager):
     # TODO: this should be a function in db.py
     with Session() as session:
         results = session.query(db.FailedEntry).delete()
-        console('Cleared %i items.' % results)
+        console(f'Cleared {results} items.')
         session.commit()
         if results:
             manager.config_changed()
diff --git a/flexget/components/ftp/ftp_list.py b/flexget/components/ftp/ftp_list.py
index 1a29103a94..d79ef8adf8 100644
--- a/flexget/components/ftp/ftp_list.py
+++ b/flexget/components/ftp/ftp_list.py
@@ -93,11 +93,8 @@ def get_content(self, path, recursion, recursion_depth, content_types):
                     if 'files' in content_types or 'symlinks' in content_types:
                         for _file in files:
                             content = ftp.path.join(base, _file)
-                            if (
-                                ftp.path.isfile(content)
-                                and 'files' in content_types
-                                or ftp.path.islink(path)
-                                and 'symlinks' in content_types
+                            if (ftp.path.isfile(content) and 'files' in content_types) or (
+                                ftp.path.islink(path) and 'symlinks' in content_types
                             ):
                                 logger.debug(
                                     'type match successful for file {}, trying to create entry',
@@ -107,11 +104,8 @@ def get_content(self, path, recursion, recursion_depth, content_types):
                     if 'dirs' in content_types or 'symlinks' in content_types:
                         for _dir in dirs:
                             content = ftp.path.join(base, _dir)
-                            if (
-                                ftp.path.isdir(content)
-                                and 'dirs' in content_types
-                                or ftp.path.islink(path)
-                                and 'symlinks' in content_types
+                            if (ftp.path.isdir(content) and 'dirs' in content_types) or (
+                                ftp.path.islink(path) and 'symlinks' in content_types
                             ):
                                 logger.debug(
                                     'type match successful for dir {}, trying to create entry',
diff --git a/flexget/components/imdb/imdb_watchlist.py b/flexget/components/imdb/imdb_watchlist.py
index 86dd5916cc..41ec4ac6a9 100644
--- a/flexget/components/imdb/imdb_watchlist.py
+++ b/flexget/components/imdb/imdb_watchlist.py
@@ -109,8 +109,8 @@ def fetch_page(self, task, url, params, headers):
             raise plugin.PluginError(str(e))
         if page.status_code != 200:
             raise plugin.PluginError(
-                'Unable to get imdb list. Either list is private or does not exist.'
-                + ' Html status code was: %d.' % page.status_code
+                f'Unable to get imdb list. Either list is private or does not exist. '
+                f'Html status code was: {page.status_code}.'
             )
         return page
 
diff --git a/flexget/components/imdb/utils.py b/flexget/components/imdb/utils.py
index fa2759fd38..26f52ddc6f 100644
--- a/flexget/components/imdb/utils.py
+++ b/flexget/components/imdb/utils.py
@@ -23,7 +23,7 @@
 # this makes most of the titles to be returned in english translation, but not all of them
 requests.headers.update({'Accept-Language': 'en-US,en;q=0.8'})
 requests.headers.update(
-    {'X-Forwarded-For': '24.110.%d.%d' % (random.randint(0, 254), random.randint(0, 254))}
+    {'X-Forwarded-For': f'24.110.{random.randint(0, 254)}.{random.randint(0, 254)}'}
 )
 
 # give imdb a little break between requests (see: http://flexget.com/ticket/129#comment:1)
diff --git a/flexget/components/managed_lists/lists/couchpotato_list.py b/flexget/components/managed_lists/lists/couchpotato_list.py
index 13c0762053..c1b76cdc4e 100644
--- a/flexget/components/managed_lists/lists/couchpotato_list.py
+++ b/flexget/components/managed_lists/lists/couchpotato_list.py
@@ -196,10 +196,8 @@ def _find_entry(self, entry):
         for cp_entry in self.movies:
             for sup_id in self.supported_ids:
                 if (
-                    entry.get(sup_id) is not None
-                    and entry[sup_id] == cp_entry[sup_id]
-                    or entry.get('title').lower() == cp_entry.get('title').lower()
-                ):
+                    entry.get(sup_id) is not None and entry[sup_id] == cp_entry[sup_id]
+                ) or entry.get('title').lower() == cp_entry.get('title').lower():
                     return cp_entry
 
     def __init__(self, config):
diff --git a/flexget/components/managed_lists/lists/entry_list/api.py b/flexget/components/managed_lists/lists/entry_list/api.py
index 38b557d857..cfb9c688d7 100644
--- a/flexget/components/managed_lists/lists/entry_list/api.py
+++ b/flexget/components/managed_lists/lists/entry_list/api.py
@@ -133,7 +133,7 @@ def get(self, list_id, session=None):
         try:
             list = db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         return jsonify(list.to_dict())
 
     @api.response(200, description='list successfully deleted', model=base_message_schema)
@@ -143,7 +143,7 @@ def delete(self, list_id, session=None):
         try:
             db.delete_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         return success_response('list successfully deleted')
 
 
@@ -170,7 +170,7 @@ def get(self, list_id, session=None):
         try:
             list = db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
 
         args = entries_parser.parse_args()
 
@@ -234,7 +234,7 @@ def post(self, list_id, session=None):
         try:
             db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         data = request.json
         title = data.get('title')
         entry_object = db.get_entry_by_title(list_id=list_id, title=title, session=session)
@@ -259,7 +259,7 @@ def get(self, list_id, entry_id, session=None):
         try:
             entry = db.get_entry_by_id(list_id=list_id, entry_id=entry_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find entry with id %d in list %d' % (entry_id, list_id))
+            raise NotFoundError(f'could not find entry with id {entry_id} in list {list_id}')
 
         return jsonify(entry.to_dict())
 
@@ -269,10 +269,10 @@ def delete(self, list_id, entry_id, session=None):
         try:
             entry = db.get_entry_by_id(list_id=list_id, entry_id=entry_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find entry with id %d in list %d' % (entry_id, list_id))
+            raise NotFoundError(f'could not find entry with id {entry_id} in list {list_id}')
         logger.debug('deleting movie {}', entry.id)
         session.delete(entry)
-        return success_response('successfully deleted entry %d' % entry.id)
+        return success_response(f'successfully deleted entry {entry.id}')
 
     @api.validate(model=base_entry_schema)
     @api.response(201, model=entry_list_entry_base_schema)
@@ -284,9 +284,8 @@ def put(self, list_id, entry_id, session=None):
         try:
             entry = db.get_entry_by_id(list_id=list_id, entry_id=entry_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find entry with id %d in list %d' % (entry_id, list_id))
-        data = request.json
-        entry.entry = data
+            raise NotFoundError(f'could not find entry with id {entry_id} in list {list_id}')
+        entry.entry = data = request.json
         if data.get('title'):
             entry.title = data['title']
         if data.get('original_url'):
diff --git a/flexget/components/managed_lists/lists/movie_list/api.py b/flexget/components/managed_lists/lists/movie_list/api.py
index b55cf9fd1a..bb9b89bd94 100644
--- a/flexget/components/managed_lists/lists/movie_list/api.py
+++ b/flexget/components/managed_lists/lists/movie_list/api.py
@@ -168,7 +168,7 @@ def get(self, list_id, session=None):
         try:
             movie_list = db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         return jsonify(movie_list.to_dict())
 
     @api.response(200, model=base_message_schema)
@@ -178,7 +178,7 @@ def delete(self, list_id, session=None):
         try:
             movie_list = db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         session.delete(movie_list)
         return success_response('successfully deleted list')
 
@@ -222,7 +222,7 @@ def get(self, list_id, session=None):
         try:
             list = db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
 
         total_items = list.movies.count()
 
@@ -259,7 +259,7 @@ def post(self, list_id, session=None):
         try:
             db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         data = request.json
         movie_identifiers = data.get('movie_identifiers', [])
         # Validates ID type based on allowed ID
@@ -271,7 +271,7 @@ def post(self, list_id, session=None):
             list_id=list_id, title=title, year=year, session=session
         )
         if movie:
-            raise Conflict('movie with name "%s" already exist in list %d' % (title, list_id))
+            raise Conflict(f'movie with name "{title}" already exist in list {list_id}')
         movie = db.MovieListMovie()
         movie.title = title
         movie.year = year
@@ -295,7 +295,7 @@ def get(self, list_id, movie_id, session=None):
         try:
             movie = db.get_movie_by_id(list_id=list_id, movie_id=movie_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find movie with id %d in list %d' % (movie_id, list_id))
+            raise NotFoundError(f'could not find movie with id {movie_id} in list {list_id}')
         return jsonify(movie.to_dict())
 
     @api.response(200, model=base_message_schema)
@@ -304,10 +304,10 @@ def delete(self, list_id, movie_id, session=None):
         try:
             movie = db.get_movie_by_id(list_id=list_id, movie_id=movie_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find movie with id %d in list %d' % (movie_id, list_id))
-        logger.debug('deleting movie {}', movie.id)
+            raise NotFoundError(f'could not find movie with id {movie_id} in list {list_id}')
+        logger.debug(f'deleting movie {movie.id}')
         session.delete(movie)
-        return success_response('successfully deleted movie %d' % movie_id)
+        return success_response(f'successfully deleted movie {movie_id}')
 
     @api.validate(model=input_movie_list_id_schema, description=movie_identifiers_doc)
     @api.response(200, model=movie_list_object_schema)
@@ -320,7 +320,7 @@ def put(self, list_id, movie_id, session=None):
         try:
             movie = db.get_movie_by_id(list_id=list_id, movie_id=movie_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find movie with id %d in list %d' % (movie_id, list_id))
+            raise NotFoundError(f'could not find movie with id {movie_id} in list {list_id}')
         data = request.json
 
         # Validates ID type based on allowed ID
diff --git a/flexget/components/managed_lists/lists/movie_list/db.py b/flexget/components/managed_lists/lists/movie_list/db.py
index 67c10a57f8..e12d435014 100644
--- a/flexget/components/managed_lists/lists/movie_list/db.py
+++ b/flexget/components/managed_lists/lists/movie_list/db.py
@@ -53,20 +53,16 @@ class MovieListMovie(Base):
     ids = relationship('MovieListID', backref='movie', cascade='all, delete, delete-orphan')
 
     def __repr__(self):
-        return '<MovieListMovie title=%s,year=%s,list_id=%d>' % (
-            self.title,
-            self.year,
-            self.list_id,
-        )
+        return f'<MovieListMovie title={self.title},year={self.year},list_id={self.list_id}>'
 
     def to_entry(self, strip_year=False):
         entry = Entry()
         entry['title'] = entry['movie_name'] = self.title
-        entry['url'] = 'mock://localhost/movie_list/%d' % self.id
+        entry['url'] = f'mock://localhost/movie_list/{self.id}'
         entry['added'] = self.added
         if self.year:
             if strip_year is False:
-                entry['title'] += ' (%d)' % self.year
+                entry['title'] += f' ({self.year})'
             entry['movie_year'] = self.year
         for movie_list_id in self.ids:
             entry[movie_list_id.id_name] = movie_list_id.id_value
@@ -97,11 +93,7 @@ class MovieListID(Base):
     movie_id = Column(Integer, ForeignKey(MovieListMovie.id))
 
     def __repr__(self):
-        return '<MovieListID id_name=%s,id_value=%s,movie_id=%d>' % (
-            self.id_name,
-            self.id_value,
-            self.movie_id,
-        )
+        return f'<MovieListID id_name={self.id_name},id_value={self.id_value},movie_id={self.movie_id}>'
 
     def to_dict(self):
         return {
diff --git a/flexget/components/managed_lists/lists/pending_list/api.py b/flexget/components/managed_lists/lists/pending_list/api.py
index a770699b48..c4765582a6 100644
--- a/flexget/components/managed_lists/lists/pending_list/api.py
+++ b/flexget/components/managed_lists/lists/pending_list/api.py
@@ -170,7 +170,7 @@ def get(self, list_id, session=None):
         try:
             list = db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         return jsonify(list.to_dict())
 
     @api.response(200, description='list successfully deleted', model=base_message_schema)
@@ -180,7 +180,7 @@ def delete(self, list_id, session=None):
         try:
             db.delete_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         return success_response('list successfully deleted')
 
 
@@ -208,7 +208,7 @@ def get(self, list_id, session=None):
         try:
             list = db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
 
         args = entries_parser.parse_args()
 
@@ -275,7 +275,7 @@ def post(self, list_id, session=None):
         try:
             db.get_list_by_id(list_id=list_id, session=session)
         except NoResultFound:
-            raise NotFoundError('list_id %d does not exist' % list_id)
+            raise NotFoundError(f'list_id {list_id} does not exist')
         data = request.json
         title = data.get('title')
         entry_object = db.get_entry_by_title(list_id=list_id, title=title, session=session)
@@ -349,7 +349,7 @@ def get(self, list_id, entry_id, session=None):
         try:
             entry = db.get_entry_by_id(list_id=list_id, entry_id=entry_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find entry with id %d in list %d' % (entry_id, list_id))
+            raise NotFoundError(f'could not find entry with id {entry_id} in list {list_id}')
 
         return jsonify(entry.to_dict())
 
@@ -359,10 +359,10 @@ def delete(self, list_id, entry_id, session=None):
         try:
             entry = db.get_entry_by_id(list_id=list_id, entry_id=entry_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find entry with id %d in list %d' % (entry_id, list_id))
-        logger.debug('deleting movie {}', entry.id)
+            raise NotFoundError(f'could not find entry with id {entry_id} in list {list_id}')
+        logger.debug(f'deleting movie {entry.id}')
         session.delete(entry)
-        return success_response('successfully deleted entry %d' % entry.id)
+        return success_response(f'successfully deleted entry {entry.id}')
 
     @api.response(201, model=pending_list_entry_base_schema)
     @api.validate(model=pending_list_operation_schema)
@@ -372,7 +372,7 @@ def put(self, list_id, entry_id, session=None):
         try:
             entry = db.get_entry_by_id(list_id=list_id, entry_id=entry_id, session=session)
         except NoResultFound:
-            raise NotFoundError('could not find entry with id %d in list %d' % (entry_id, list_id))
+            raise NotFoundError(f'could not find entry with id {entry_id} in list {list_id}')
         data = request.json
         approved = data['operation'] == 'approve'
         operation_text = 'approved' if approved else 'pending'
diff --git a/flexget/components/managed_lists/lists/regexp_list/db.py b/flexget/components/managed_lists/lists/regexp_list/db.py
index 4ca46a207a..ac8b01cdf6 100644
--- a/flexget/components/managed_lists/lists/regexp_list/db.py
+++ b/flexget/components/managed_lists/lists/regexp_list/db.py
@@ -21,7 +21,7 @@ class RegexpListList(Base):
     )
 
     def __repr__(self):
-        return '<RegexpListList name=%s,id=%d>' % (self.name, self.id)
+        return f'<RegexpListList name={self.name},id={self.id}>'
 
     def to_dict(self):
         return {'id': self.id, 'name': self.name, 'added_on': self.added}
@@ -40,7 +40,7 @@ def __repr__(self):
     def to_entry(self):
         entry = Entry()
         entry['title'] = entry['regexp'] = self.regexp
-        entry['url'] = 'mock://localhost/regexp_list/%d' % self.id
+        entry['url'] = f'mock://localhost/regexp_list/{self.id}'
         return entry
 
     def to_dict(self):
diff --git a/flexget/components/managed_lists/lists/subtitle_list.py b/flexget/components/managed_lists/lists/subtitle_list.py
index f07c5d617c..48aa7a549d 100644
--- a/flexget/components/managed_lists/lists/subtitle_list.py
+++ b/flexget/components/managed_lists/lists/subtitle_list.py
@@ -120,7 +120,7 @@ class SubtitleListList(Base):
     )
 
     def __repr__(self):
-        return '<SubtitleListList name=%s,id=%d>' % (self.name, self.id)
+        return f'<SubtitleListList name={self.name},id={self.id}>'
 
     def to_dict(self):
         return {'id': self.id, 'name': self.name, 'added_on': self.added}
@@ -144,7 +144,7 @@ def __repr__(self):
     def to_entry(self):
         entry = Entry()
         entry['title'] = self.title
-        entry['url'] = 'mock://localhost/subtitle_list/%d' % self.id
+        entry['url'] = f'mock://localhost/subtitle_list/{self.id}'
         entry['location'] = self.location
         entry['remove_after'] = self.remove_after
         entry['added'] = self.added
diff --git a/flexget/components/parsing/parsers/parser_common.py b/flexget/components/parsing/parsers/parser_common.py
index 0d121a26f6..d54e1cf1bd 100644
--- a/flexget/components/parsing/parsers/parser_common.py
+++ b/flexget/components/parsing/parsers/parser_common.py
@@ -41,14 +41,11 @@ def old_assume_quality(guessed_quality: Quality, assumed_quality: Quality) -> Qu
 
 
 def remove_dirt(name: str) -> str:
-    if name:
-        name = re.sub(r'[_.,\[\]\(\): ]+', ' ', name).strip().lower()
-    return name
+    return re.sub(r'[_.,\[\]\(\): ]+', ' ', name).strip().lower() if name else name
 
 
 def normalize_name(name: str) -> str:
-    name = capwords(name)
-    return name
+    return capwords(name)
 
 
 class MovieParseResult:
@@ -72,10 +69,8 @@ def __init__(
 
     @property
     def identifier(self) -> str:
-        if self.name and self.year:
-            return (f'{self.name} {self.year}').strip().lower()
-        elif self.name:
-            return self.name.lower()
+        if self.name:
+            return (f'{self.name} {self.year}').strip().lower() if self.year else self.name.lower()
 
     @property
     def proper(self) -> bool:
@@ -98,7 +93,11 @@ def fields(self) -> dict:
 
     def __str__(self) -> str:
         valid = 'OK' if self.valid else 'INVALID'
-        return f'<MovieParseResult(data={self.data},name={self.name},year={self.year},id={self.identifier},quality={self.quality},proper={self.proper_count},release_group={self.release_group},status={valid})>'
+        return (
+            f'<MovieParseResult(data={self.data},name={self.name},year={self.year},'
+            f'id={self.identifier},quality={self.quality},proper={self.proper_count},'
+            f'release_group={self.release_group},status={valid})>'
+        )
 
 
 class SeriesParseResult:
@@ -138,6 +137,7 @@ def proper(self) -> bool:
 
     @property
     def season(self) -> Optional[int]:
+        # TODO Use match-case statement after Python 3.9 is dropped
         if self.id_type == 'ep':
             return self.id[0]
         if self.id_type == 'date':
@@ -161,9 +161,11 @@ def identifiers(self) -> list[str]:
         if not self.valid:
             raise Exception('Series flagged invalid')
         if self.id_type == 'ep':
-            if self.season_pack:
-                return ['S%02d' % self.season]
-            return ['S%02dE%02d' % (self.season, self.episode + x) for x in range(self.episodes)]
+            return (
+                [f'S{self.season:02d}']
+                if self.season_pack
+                else [f'S{self.season:02d}E{self.episode + x:02d}' for x in range(self.episodes)]
+            )
         elif self.id_type == 'date':
             return [self.id.strftime('%Y-%m-%d')]
         if self.id is None:
@@ -182,21 +184,16 @@ def identifier(self) -> str:
     def pack_identifier(self) -> str:
         """Return a combined identifier for the whole pack if this has more than one episode."""
         # Currently only supports ep mode
-        if self.id_type == 'ep':
-            if self.episodes > 1:
-                return 'S%02dE%02d-E%02d' % (
-                    self.season,
-                    self.episode,
-                    self.episode + self.episodes - 1,
-                )
-            else:
-                return self.identifier
-        else:
-            return self.identifier
+        return (
+            f'S{self.season:02d}E{self.episode:02d}-E{self.episode + self.episodes - 1:02d}'
+            if self.id_type == 'ep' and self.episodes > 1
+            else self.identifier
+        )
 
     def __str__(self) -> str:
         valid = 'OK' if self.valid else 'INVALID'
         return (
-            f'<SeriesParseResult(data={self.data},name={self.name},id={self.id!s},season={self.season},season_pack={self.season_pack},episode={self.episode},quality={self.quality},proper={self.proper_count},'
-            f'special={self.special},status={valid})>'
+            f'<SeriesParseResult(data={self.data},name={self.name},id={self.id!s},season={self.season},'
+            f'season_pack={self.season_pack},episode={self.episode},quality={self.quality},'
+            f'proper={self.proper_count},special={self.special},status={valid})>'
         )
diff --git a/flexget/components/parsing/parsers/parser_guessit.py b/flexget/components/parsing/parsers/parser_guessit.py
index b20dc502c8..fe7723adcf 100644
--- a/flexget/components/parsing/parsers/parser_guessit.py
+++ b/flexget/components/parsing/parsers/parser_guessit.py
@@ -264,10 +264,8 @@ def parse_series(self, data, **kwargs):
                         break
             # Check the name doesn't end mid-word (guessit might put the border before or after the space after title)
             if (
-                data[title_end - 1].isalnum()
-                and len(data) <= title_end
-                or not self._is_valid_name(data, guessit_options=guessit_options)
-            ):
+                data[title_end - 1].isalnum() and len(data) <= title_end
+            ) or not self._is_valid_name(data, guessit_options=guessit_options):
                 valid = False
             # If we are in exact mode, make sure there is nothing after the title
             if kwargs.get('strict_name'):
diff --git a/flexget/components/pending_approval/cli.py b/flexget/components/pending_approval/cli.py
index 60e9d32909..524575b1ec 100644
--- a/flexget/components/pending_approval/cli.py
+++ b/flexget/components/pending_approval/cli.py
@@ -91,7 +91,7 @@ def clear_entries(options):
         if options.task_name:
             query = query.filter(db.PendingEntry.task_name == options.task_name)
         deleted = query.delete()
-        console('Successfully deleted %i pending entries' % deleted)
+        console(f'Successfully deleted {deleted} pending entries')
 
 
 @event('options.register')
diff --git a/flexget/components/rejected/api.py b/flexget/components/rejected/api.py
index dc53198354..d26a7e7440 100644
--- a/flexget/components/rejected/api.py
+++ b/flexget/components/rejected/api.py
@@ -131,7 +131,7 @@ def delete(self, session=None):
         if entries:
             session.commit()
             self.manager.config_changed()
-        return success_response('successfully deleted %i rejected entries' % entries)
+        return success_response(f'successfully deleted {entries} rejected entries')
 
 
 @rejected_api.route('/<int:rejected_entry_id>/')
@@ -148,7 +148,7 @@ def get(self, rejected_entry_id, session=None):
                 .one()
             )
         except NoResultFound:
-            raise NotFoundError('rejected entry ID %d not found' % rejected_entry_id)
+            raise NotFoundError(f'rejected entry ID {rejected_entry_id} not found')
         return jsonify(rejected_entry_to_dict(entry))
 
     @api.response(200, model=base_message_schema)
@@ -161,6 +161,6 @@ def delete(self, rejected_entry_id, session=None):
                 .one()
             )
         except NoResultFound:
-            raise NotFoundError('rejected entry ID %d not found' % rejected_entry_id)
+            raise NotFoundError(f'rejected entry ID {rejected_entry_id} not found')
         session.delete(entry)
-        return success_response('successfully deleted rejected entry %i' % rejected_entry_id)
+        return success_response(f'successfully deleted rejected entry {rejected_entry_id}')
diff --git a/flexget/components/rejected/cli.py b/flexget/components/rejected/cli.py
index 6d39efe4ec..7cb8b05a69 100644
--- a/flexget/components/rejected/cli.py
+++ b/flexget/components/rejected/cli.py
@@ -28,7 +28,7 @@ def list_rejected(options):
 def clear_rejected(manager):
     with Session() as session:
         results = session.query(db.RememberEntry).delete()
-        console('Cleared %i items.' % results)
+        console(f'Cleared {results} items.')
         session.commit()
         if results:
             manager.config_changed()
diff --git a/flexget/components/rejected/remember_rejected.py b/flexget/components/rejected/remember_rejected.py
index 5ec599361e..b9690527aa 100644
--- a/flexget/components/rejected/remember_rejected.py
+++ b/flexget/components/rejected/remember_rejected.py
@@ -94,7 +94,7 @@ def on_entry_reject(self, entry, remember=None, remember_time=None, **kwargs):
                 remember_time = parse_timedelta(remember_time)
         message = 'Remembering rejection of `{}`'.format(entry['title'])
         if remember_time:
-            message += ' for %i minutes' % (remember_time.seconds / 60)
+            message += f' for {remember_time.seconds / 60} minutes'
         logger.info(message)
         entry['remember_rejected'] = remember_time or remember
 
diff --git a/flexget/components/scheduler/api.py b/flexget/components/scheduler/api.py
index feb6bb3fac..5bd2c28ac1 100644
--- a/flexget/components/scheduler/api.py
+++ b/flexget/components/scheduler/api.py
@@ -130,7 +130,7 @@ def get(self, schedule_id, session=None):
 
         schedule, _ = _schedule_by_id(schedule_id, schedules)
         if schedule is None:
-            raise NotFoundError('schedule %d not found' % schedule_id)
+            raise NotFoundError(f'schedule {schedule_id} not found')
 
         job_id = scheduler_job_map.get(schedule_id)
         if job_id:
@@ -167,7 +167,7 @@ def put(self, schedule_id, session=None):
 
         schedule, idx = _schedule_by_id(schedule_id, self.manager.config['schedules'])
         if not schedule:
-            raise NotFoundError('schedule %d not found' % schedule_id)
+            raise NotFoundError(f'schedule {schedule_id} not found')
 
         new_schedule['id'] = id(schedule)
         self.manager.config['schedules'][idx] = new_schedule
@@ -194,6 +194,6 @@ def delete(self, schedule_id, session=None):
                 del self.manager.config['schedules'][i]
                 self.manager.save_config()
                 self.manager.config_changed()
-                return success_response('schedule %d successfully deleted' % schedule_id)
+                return success_response(f'schedule {schedule_id} successfully deleted')
 
-        raise NotFoundError('schedule %d not found' % schedule_id)
+        raise NotFoundError(f'schedule {schedule_id} not found')
diff --git a/flexget/components/seen/api.py b/flexget/components/seen/api.py
index 415719e128..af3f13827c 100644
--- a/flexget/components/seen/api.py
+++ b/flexget/components/seen/api.py
@@ -152,7 +152,7 @@ def delete(self, session):
         for se in seen_entries_list:
             db.forget_by_id(se.id, session=session)
             deleted += 1
-        return success_response('successfully deleted %i entries' % deleted)
+        return success_response(f'successfully deleted {deleted} entries')
 
 
 @seen_api.route('/<int:seen_entry_id>/')
diff --git a/flexget/components/series/api.py b/flexget/components/series/api.py
index b9761ef606..4d2a00a80c 100644
--- a/flexget/components/series/api.py
+++ b/flexget/components/series/api.py
@@ -833,10 +833,8 @@ def delete(self, show_id, season_id, session):
         release_items = []
         for release in season.releases:
             if (
-                downloaded
-                and release.downloaded
-                or downloaded is False
-                and not release.downloaded
+                (downloaded and release.downloaded)
+                or (downloaded is False and not release.downloaded)
                 or not downloaded
             ):
                 release_items.append(release)
@@ -944,9 +942,7 @@ def delete(self, show_id, season_id, rel_id, session):
             fire_event('forget', release.title)
 
         db.delete_season_release_by_id(rel_id)
-        return success_response(
-            'successfully deleted release %d from season %d' % (rel_id, season_id)
-        )
+        return success_response(f'successfully deleted release {rel_id} from season {season_id}')
 
     @api.response(200, 'Successfully reset downloaded release status', model=season_release_schema)
     @api.doc(
@@ -1090,10 +1086,8 @@ def delete(self, show_id, ep_id, session):
         release_items = []
         for release in episode.releases:
             if (
-                downloaded
-                and release.downloaded
-                or downloaded is False
-                and not release.downloaded
+                (downloaded and release.downloaded)
+                or (downloaded is False and not release.downloaded)
                 or not downloaded
             ):
                 release_items.append(release)
@@ -1201,9 +1195,7 @@ def delete(self, show_id, ep_id, rel_id, session):
             fire_event('forget', release.title)
 
         db.delete_episode_release_by_id(rel_id)
-        return success_response(
-            'successfully deleted release %d from episode %d' % (rel_id, ep_id)
-        )
+        return success_response(f'successfully deleted release {rel_id} from episode {ep_id}')
 
     @api.response(
         200, 'Successfully reset downloaded release status', model=episode_release_schema
diff --git a/flexget/components/series/gen_series.py b/flexget/components/series/gen_series.py
index 84af28e4fe..8c66015058 100644
--- a/flexget/components/series/gen_series.py
+++ b/flexget/components/series/gen_series.py
@@ -36,16 +36,13 @@ def on_task_start(self, task, config):
         logger.info('Generating test data ...')
         series = []
         for num in range(config['series']):
-            series.append('series %d name' % num)
+            series.append(f'series {num} name')
             for season in range(int(config['seasons'])):
                 for episode in range(int(config['episodes'])):
                     for quality in config['qualities']:
                         entry = Entry()
-                        entry['title'] = 'series %d name - S%02dE%02d - %s' % (
-                            num,
-                            season + 1,
-                            episode + 1,
-                            quality,
+                        entry['title'] = (
+                            f'series {num} name - S{season + 1:02d}E{episode + 1:02d} - {quality}'
                         )
                         entry['url'] = 'http://localhost/mock/{}'.format(
                             ''.join(
diff --git a/flexget/components/series/next_series_episodes.py b/flexget/components/series/next_series_episodes.py
index 097ba1e74d..44a5976a8b 100644
--- a/flexget/components/series/next_series_episodes.py
+++ b/flexget/components/series/next_series_episodes.py
@@ -39,11 +39,11 @@ def __init__(self):
         self.rerun_entries = []
 
     def ep_identifiers(self, season, episode):
-        return ['S%02dE%02d' % (season, episode), '%dx%02d' % (season, episode)]
+        return [f'S{season:02d}E{episode:02d}', f'{season}x{episode:02d}']
 
     def sequence_identifiers(self, episode):
         # Use a set to remove doubles, which will happen depending on number of digits in episode
-        return {'%d' % episode, '%02d' % episode, '%03d' % episode}
+        return {f'{episode}', f'{episode:02d}', f'{episode:03d}'}
 
     def search_entry(self, series, season, episode, task, rerun=True):
         # Extract the alternate names for the series
@@ -54,7 +54,7 @@ def search_entry(self, series, season, episode, task, rerun=True):
             alts.append(paren_match.group(1))
         if series.identified_by == 'ep':
             search_strings = [f'{series.name} {id}' for id in self.ep_identifiers(season, episode)]
-            series_id = 'S%02dE%02d' % (season, episode)
+            series_id = f'S{season:02d}E{episode:02d}'
             for alt in alts:
                 search_strings.extend(
                     [f'{alt} {id}' for id in self.ep_identifiers(season, episode)]
diff --git a/flexget/components/series/next_series_seasons.py b/flexget/components/series/next_series_seasons.py
index 6adf25ecfa..55512f4715 100644
--- a/flexget/components/series/next_series_seasons.py
+++ b/flexget/components/series/next_series_seasons.py
@@ -42,7 +42,7 @@ def __init__(self):
         self.rerun_entries = []
 
     def season_identifiers(self, season):
-        return ['S%02d' % season]
+        return [f'S{season:02d}']
 
     def search_entry(self, series, season, task, rerun=True):
         # Extract the alternate names for the series
@@ -52,7 +52,7 @@ def search_entry(self, series, season, task, rerun=True):
         if paren_match.group(2):
             alts.append(paren_match.group(1))
         search_strings = [f'{series.name} {id}' for id in self.season_identifiers(season)]
-        series_id = 'S%02d' % season
+        series_id = f'S{season:02d}'
         for alt in alts:
             search_strings.extend([f'{alt} {id}' for id in self.season_identifiers(season)])
         entry = Entry(
diff --git a/flexget/components/sites/sites/argenteam.py b/flexget/components/sites/sites/argenteam.py
index 5ab18d4214..a4f2f2b23a 100644
--- a/flexget/components/sites/sites/argenteam.py
+++ b/flexget/components/sites/sites/argenteam.py
@@ -72,10 +72,8 @@ def search(self, task, entry, config):
             logger.debug('{} releases found.', len(response['releases']))
             for release in response['releases']:
                 for torrent in release['torrents']:
-                    if (
-                        config.get('force_subtitles')
-                        and release['subtitles']
-                        or not config.get('force_subtitles')
+                    if (config.get('force_subtitles') and release['subtitles']) or not config.get(
+                        'force_subtitles'
                     ):
                         e = Entry()
 
diff --git a/flexget/components/sites/sites/btn.py b/flexget/components/sites/sites/btn.py
index 284d216c61..da92e11025 100644
--- a/flexget/components/sites/sites/btn.py
+++ b/flexget/components/sites/sites/btn.py
@@ -70,7 +70,7 @@ def search(self, task, entry, config):
             elif 'series_id' in entry:
                 # BTN wants an ep style identifier even for sequence shows
                 if entry.get('series_id_type') == 'sequence':
-                    search['name'] = 'S01E%02d' % entry['series_id']
+                    search['name'] = 'S01E{:02d}'.format(entry['series_id'])
                 else:
                     search['name'] = (
                         entry['series_id'] + '%'
diff --git a/flexget/components/status/api.py b/flexget/components/status/api.py
index d8e18ee8d7..8e1b4ae9ee 100644
--- a/flexget/components/status/api.py
+++ b/flexget/components/status/api.py
@@ -149,7 +149,7 @@ def get(self, task_id, session=None):
         try:
             task = session.query(db.StatusTask).filter(db.StatusTask.id == task_id).one()
         except NoResultFound:
-            raise NotFoundError('task status with id %d not found' % task_id)
+            raise NotFoundError(f'task status with id {task_id} not found')
 
         args = tasks_parser.parse_args()
         include_execution = args.get('include_execution')
@@ -211,7 +211,7 @@ def get(self, task_id, session=None):
         try:
             task = session.query(db.StatusTask).filter(db.StatusTask.id == task_id).one()
         except NoResultFound:
-            raise NotFoundError('task status with id %d not found' % task_id)
+            raise NotFoundError(f'task status with id {task_id} not found')
 
         args = executions_parser.parse_args()
 
diff --git a/flexget/components/trakt/next_trakt_episodes.py b/flexget/components/trakt/next_trakt_episodes.py
index ef43d1b4d4..de4f68cac2 100644
--- a/flexget/components/trakt/next_trakt_episodes.py
+++ b/flexget/components/trakt/next_trakt_episodes.py
@@ -152,7 +152,7 @@ def make_entry(fields, season, episode):
         entry['series_season'] = season
         entry['series_episode'] = episode
         entry['series_id_type'] = 'ep'
-        entry['series_id'] = 'S%02dE%02d' % (season, episode)
+        entry['series_id'] = f'S{season:02d}E{episode:02d}'
         entry['title'] = entry['series_name'] + ' ' + entry['series_id']
         entry['url'] = 'https://trakt.tv/shows/{}/seasons/{}/episodes/{}'.format(
             fields['trakt_id'],
diff --git a/flexget/components/trakt/trakt_calendar.py b/flexget/components/trakt/trakt_calendar.py
index 67e3cb57b1..a9eafe1fe5 100644
--- a/flexget/components/trakt/trakt_calendar.py
+++ b/flexget/components/trakt/trakt_calendar.py
@@ -77,7 +77,7 @@ class TraktCalendar:
         'trakt_ep_abs_number': 'number_abs',
         'trakt_season': 'season',
         'trakt_episode': 'number',
-        'trakt_ep_id': lambda ep: 'S%02dE%02d' % (ep['season'], ep['number']),
+        'trakt_ep_id': lambda ep: 'S{:02d}E{:02d}'.format(ep['season'], ep['number']),
         'trakt_ep_languages': 'available_translations',
         'trakt_ep_runtime': 'runtime',
         'trakt_ep_updated_at': 'updated_at',
diff --git a/flexget/components/trakt/trakt_list.py b/flexget/components/trakt/trakt_list.py
index 25a6e5f0c7..3a3ae14d7c 100644
--- a/flexget/components/trakt/trakt_list.py
+++ b/flexget/components/trakt/trakt_list.py
@@ -29,8 +29,7 @@ def generate_episode_title(item):
     episode_info = item['episode']
     if show_info['year'] and not item['strip_dates']:
         return (
-            '%s (%s) S%02dE%02d %s'
-            % (
+            '{} ({}) S{:02d}E{:02d} {}'.format(
                 show_info['title'],
                 show_info['year'],
                 episode_info['season'],
@@ -40,8 +39,7 @@ def generate_episode_title(item):
         ).strip()
     else:
         return (
-            '%s S%02dE%02d %s'
-            % (
+            '{} S{:02d}E{:02d} {}'.format(
                 show_info['title'],
                 episode_info['season'],
                 episode_info['number'],
@@ -85,7 +83,9 @@ def generate_episode_title(item):
         'trakt_series_year': 'show.year',
         'series_season': 'episode.season',
         'series_episode': 'episode.number',
-        'series_id': lambda i: 'S%02dE%02d' % (i['episode']['season'], i['episode']['number']),
+        'series_id': lambda i: 'S{:02d}E{:02d}'.format(
+            i['episode']['season'], i['episode']['number']
+        ),
         'imdb_id': 'show.ids.imdb',
         'tvdb_id': 'show.ids.tvdb',
         'tvrage_id': 'show.ids.tvrage',
diff --git a/flexget/components/trakt/trakt_lookup.py b/flexget/components/trakt/trakt_lookup.py
index 24567ccac6..b085e853d8 100644
--- a/flexget/components/trakt/trakt_lookup.py
+++ b/flexget/components/trakt/trakt_lookup.py
@@ -162,7 +162,7 @@ def get_db_data_for(data_type: str, entry: Entry, session: Session):
         'trakt_ep_abs_number': 'number_abs',
         'trakt_season': 'season',
         'trakt_episode': 'number',
-        'trakt_ep_id': lambda ep: 'S%02dE%02d' % (ep.season, ep.number),
+        'trakt_ep_id': lambda ep: f'S{ep.season:02d}E{ep.number:02d}',
     },
     'movie': {
         'movie_name': 'title',
diff --git a/flexget/plugins/cli/try_regexp.py b/flexget/plugins/cli/try_regexp.py
index 3c920ba446..dae8ba0ebf 100644
--- a/flexget/plugins/cli/try_regexp.py
+++ b/flexget/plugins/cli/try_regexp.py
@@ -57,8 +57,9 @@ def on_task_filter(self, task, config):
                     match, field = self.matches(entry, s)
                     if match:
                         console(
-                            'Title: %-40s URL: %-30s From: %s'
-                            % (entry['title'], entry['url'], field)
+                            'Title: {:40s} URL: {:30s} From: {}'.format(
+                                entry['title'], entry['url'], field
+                            )
                         )
                         count += 1
                 except re.error:
diff --git a/flexget/plugins/clients/deluge.py b/flexget/plugins/clients/deluge.py
index e2cf323e43..f8ab451b0b 100644
--- a/flexget/plugins/clients/deluge.py
+++ b/flexget/plugins/clients/deluge.py
@@ -658,7 +658,7 @@ def rename(file, new_name):
                 if opts.get('main_file_only'):
                     # download only the main file (and subs)
                     file_priorities = [
-                        1 if f == main_file or f == sub_file and keep_subs else 0
+                        1 if f == main_file or (f == sub_file and keep_subs) else 0
                         for f in status['files']
                     ]
                     client.call(
diff --git a/flexget/plugins/clients/rtorrent.py b/flexget/plugins/clients/rtorrent.py
index 6a38cc0ef1..c5fa3562f4 100644
--- a/flexget/plugins/clients/rtorrent.py
+++ b/flexget/plugins/clients/rtorrent.py
@@ -668,7 +668,7 @@ def add_entry(self, client, entry, options, start=True, mkdir=False, fast_resume
             return
 
         if entry['url'].startswith('magnet:'):
-            torrent_raw = 'd10:magnet-uri%d:%se' % (len(entry['url']), entry['url'])
+            torrent_raw = 'd10:magnet-uri{}:{}e'.format(len(entry['url']), entry['url'])
             torrent_raw = torrent_raw.encode('ascii')
         else:
             # Check that file is downloaded
diff --git a/flexget/plugins/filter/crossmatch.py b/flexget/plugins/filter/crossmatch.py
index 173cb7e155..b748b6e413 100644
--- a/flexget/plugins/filter/crossmatch.py
+++ b/flexget/plugins/filter/crossmatch.py
@@ -101,7 +101,7 @@ def entry_intersects(self, e1, e2, fields=None, exact=True, case_sensitive=True)
                 v2 = e2[field]
 
             try:
-                if v1 == v2 or not exact and (v2 in v1 or v1 in v2):
+                if v1 == v2 or (not exact and (v2 in v1 or v1 in v2)):
                     common_fields.append(field)
                 else:
                     logger.trace('not matching')
diff --git a/flexget/plugins/filter/unique.py b/flexget/plugins/filter/unique.py
index 96fad41031..706fff3bfd 100644
--- a/flexget/plugins/filter/unique.py
+++ b/flexget/plugins/filter/unique.py
@@ -42,7 +42,7 @@ def extract_fields(self, entry, field_names):
         return [entry[field] for field in field_names]
 
     def should_ignore(self, item, action):
-        return item.accepted and action == 'accept' or item.rejected and action == 'reject'
+        return (item.accepted and action == 'accept') or (item.rejected and action == 'reject')
 
     def on_task_filter(self, task, config):
         config = self.prepare_config(config)
diff --git a/flexget/plugins/generic/welcome.py b/flexget/plugins/generic/welcome.py
index cf8061ea08..292312d362 100644
--- a/flexget/plugins/generic/welcome.py
+++ b/flexget/plugins/generic/welcome.py
@@ -31,13 +31,16 @@ def welcome_message(manager):
     scheduler = 'scheduler' if sys.platform.startswith('win') else 'crontab'
     if not count:
         logger.info(
-            'FlexGet has been successfully started from {} (--cron). I hope you have {} under control now. This message will not be repeated again.',
+            'FlexGet has been successfully started from {} (--cron). '
+            'I hope you have {} under control now. This message will not be repeated again.',
             scheduler,
             scheduler,
         )
     else:
         logger.info(
-            '%sFlexGet has been successfully started from %s (--cron). This message will be repeated %i times for your set up verification conveniences.'
-            % ('Congratulations! ' if count == 4 else '', scheduler, count)
+            '{}FlexGet has been successfully started from {} (--cron). '
+            'This message will be repeated {} times for your set up verification conveniences.'.format(
+                'Congratulations! ' if count == 4 else '', scheduler, count
+            )
         )
     persistence['count'] = count
diff --git a/flexget/plugins/input/medusa.py b/flexget/plugins/input/medusa.py
index 4f470bcb2b..148e5f744a 100644
--- a/flexget/plugins/input/medusa.py
+++ b/flexget/plugins/input/medusa.py
@@ -80,10 +80,8 @@ def on_task_input(self, task, config):
         entries = []
         for show in series:
             logger.debug('processing show: {}', show)
-            if (
-                (show['config']['paused'] and config.get('only_monitored'))
-                or show['status'] == 'Ended'
-                and not config.get('include_ended')
+            if (show['config']['paused'] and config.get('only_monitored')) or (
+                show['status'] == 'Ended' and not config.get('include_ended')
             ):
                 logger.debug('discarted show: {}', show)
 
diff --git a/flexget/plugins/input/next_sonarr_episodes.py b/flexget/plugins/input/next_sonarr_episodes.py
index 423a7a0766..d17540299c 100644
--- a/flexget/plugins/input/next_sonarr_episodes.py
+++ b/flexget/plugins/input/next_sonarr_episodes.py
@@ -115,11 +115,11 @@ def on_task_input(self, task, config):
                         series_name=record['series']['title'],
                         series_season=season,
                         series_episode=episode,
-                        series_id='S%02dE%02d' % (season, episode),
+                        series_id=f'S{season:02d}E{episode:02d}',
                         tvdb_id=record['series'].get('tvdbId'),
                         tvrage_id=record['series'].get('tvRageId'),
                         tvmaze_id=record['series'].get('tvMazeId'),
-                        title=record['series']['title'] + ' ' + 'S%02dE%02d' % (season, episode),
+                        title=record['series']['title'] + ' ' + f'S{season:02d}E{episode:02d}',
                     )
                     # Test mode logging
                     if entry and task.options.test:
diff --git a/flexget/plugins/input/plex.py b/flexget/plugins/input/plex.py
index dde30449b4..3a5a9db7cd 100644
--- a/flexget/plugins/input/plex.py
+++ b/flexget/plugins/input/plex.py
@@ -195,7 +195,9 @@ def on_task_input(self, task, config):
             try:
                 path = "/library/sections/"
                 r = requests.get(
-                    "http://%s:%d%s%s" % (config['plexserver'], config['port'], path, urlappend)
+                    "http://{}:{}{}{}".format(
+                        config['plexserver'], config['port'], path, urlappend
+                    )
                 )
             except requests.RequestException as e:
                 raise plugin.PluginError(f'Error retrieving source: {e}')
@@ -217,11 +219,11 @@ def on_task_input(self, task, config):
         try:
             path = "/library/sections/{}/{}".format(config['section'], config['selection'])
             r = requests.get(
-                "http://%s:%d%s%s" % (config['plexserver'], config['port'], path, urlappend)
+                "http://{}:{}{}{}".format(config['plexserver'], config['port'], path, urlappend)
             )
         except requests.RequestException as e:
             raise plugin.PluginError(
-                'There is no section with number %d. (%s)' % (config['section'], e)
+                'There is no section with number {}. ({})'.format(config['section'], e)
             )
         dom = parseString(r.text.encode("utf-8"))
         plexsectionname = dom.getElementsByTagName('MediaContainer')[0].getAttribute('title1')
@@ -275,26 +277,26 @@ def on_task_input(self, task, config):
                 entries.append(e)
                 # show ends here.
                 continue
-            e['plex_art'] = "http://%s:%d%s%s" % (
+            e['plex_art'] = "http://{}:{}{}{}".format(
                 config['server'],
                 config['port'],
                 node.getAttribute(arttag),
                 urlappend,
             )
-            e['plex_cover'] = "http://%s:%d%s%s" % (
+            e['plex_cover'] = "http://{}:{}{}{}".format(
                 config['server'],
                 config['port'],
                 node.getAttribute(covertag),
                 urlappend,
             )
-            e['plex_season_cover'] = "http://%s:%d%s%s" % (
+            e['plex_season_cover'] = "http://{}:{}{}{}".format(
                 config['server'],
                 config['port'],
                 node.getAttribute(seasoncovertag),
                 urlappend,
             )
             if viewgroup == "episode":
-                e['plex_thumb'] = "http://%s:%d%s%s" % (
+                e['plex_thumb'] = "http://{}:{}{}{}".format(
                     config['server'],
                     config['port'],
                     node.getAttribute('thumb'),
@@ -315,7 +317,7 @@ def on_task_input(self, task, config):
                     e['series_season'] = season
                     e['series_episode'] = episode
                     e['series_id_type'] = 'ep'
-                    e['series_id'] = 'S%02dE%02d' % (season, episode)
+                    e['series_id'] = f'S{season:02d}E{episode:02d}'
                 else:
                     logger.debug(
                         "Could not get episode number for '{}' (Hint, ratingKey: {})",
@@ -395,14 +397,14 @@ def on_task_input(self, task, config):
                                 container,
                             )
                             entry['title'] = filename
-                    entry['plex_url'] = "http://%s:%d%s%s" % (
+                    entry['plex_url'] = "http://{}:{}{}{}".format(
                         config['server'],
                         config['port'],
                         key,
                         urlappend,
                     )
                     entry['plex_path'] = key
-                    entry['url'] = "http://%s:%d%s%s" % (
+                    entry['url'] = "http://{}:{}{}{}".format(
                         config['server'],
                         config['port'],
                         key,
diff --git a/flexget/plugins/input/rss.py b/flexget/plugins/input/rss.py
index 34fb98a1c6..ca614c3c7a 100644
--- a/flexget/plugins/input/rss.py
+++ b/flexget/plugins/input/rss.py
@@ -219,7 +219,7 @@ def add_enclosure_info(self, entry, enclosure, filename=True, multiple=False):
         # Parse filename from enclosure url
         basename = posixpath.basename(urlsplit(entry['url']).path)
         # If enclosure has size OR there are multiple enclosures use filename from url
-        if (entry.get('size') or multiple and basename) and filename:
+        if (entry.get('size') or (multiple and basename)) and filename:
             entry['filename'] = basename
             logger.trace('filename `{}` from enclosure', entry['filename'])
 
diff --git a/flexget/plugins/metainfo/nzb_size.py b/flexget/plugins/metainfo/nzb_size.py
index ced35b8c2e..ce711bc60a 100644
--- a/flexget/plugins/metainfo/nzb_size.py
+++ b/flexget/plugins/metainfo/nzb_size.py
@@ -30,10 +30,8 @@ def on_task_modify(self, task, config):
             raise plugin.DependencyError(issued_by='nzb_size', missing='lib pynzb')
 
         for entry in task.accepted:
-            if (
-                entry.get('mime-type') in ['text/nzb', 'application/x-nzb']
-                or entry.get('filename')
-                and entry['filename'].endswith('.nzb')
+            if entry.get('mime-type') in ['text/nzb', 'application/x-nzb'] or (
+                entry.get('filename') and entry['filename'].endswith('.nzb')
             ):
                 if 'file' not in entry:
                     logger.warning(
diff --git a/flexget/plugins/output/rtorrent_magnet.py b/flexget/plugins/output/rtorrent_magnet.py
index 53b1548352..8419698a83 100644
--- a/flexget/plugins/output/rtorrent_magnet.py
+++ b/flexget/plugins/output/rtorrent_magnet.py
@@ -41,7 +41,7 @@ def write_torrent_file(self, task, entry, path):
         else:
             logger.info('Writing rTorrent Magnet File: {}', path)
             with open(path, 'w') as f:
-                f.write('d10:magnet-uri%d:%se' % (len(entry['url']), entry['url']))
+                f.write('d10:magnet-uri{}:{}e'.format(len(entry['url']), entry['url']))
         entry['output'] = path
 
     # Run after download plugin to only pick up entries it did not already handle
diff --git a/flexget/utils/bittorrent.py b/flexget/utils/bittorrent.py
index 688268446a..bbf7e4bc13 100644
--- a/flexget/utils/bittorrent.py
+++ b/flexget/utils/bittorrent.py
@@ -72,7 +72,7 @@ def clean_meta(
             for key in list(entry.keys()):
                 if ["info", "files", key] not in METAFILE_STD_KEYS:
                     if log_func:
-                        log_func("Removing key %r from file #%d..." % (key, idx + 1))
+                        log_func(f"Removing key {key!r} from file #{idx + 1}...")
                     del entry[key]
                     modified.add("info.files." + key)
 
diff --git a/flexget/utils/tools.py b/flexget/utils/tools.py
index 5fb798cbf7..8f8b4b7641 100644
--- a/flexget/utils/tools.py
+++ b/flexget/utils/tools.py
@@ -79,7 +79,7 @@ def _htmldecode(text: str) -> str:
     else:
 
         def uchr(value):
-            value > 127 and chr(value) or chr(value)
+            (value > 127 and chr(value)) or chr(value)
 
     def entitydecode(match, uchr=uchr):
         entity = match.group(1)


Test patch:
diff --git a/flexget/plugins/cli/perf_tests.py b/flexget/plugins/cli/perf_tests.py
index f9a935e34e..522b3f962b 100644
--- a/flexget/plugins/cli/perf_tests.py
+++ b/flexget/plugins/cli/perf_tests.py
@@ -72,7 +72,7 @@ def imdb_query(session):
 
     log_query_count('test')
     took = time.time() - start_time
-    logger.debug('Took %.2f seconds to query %i movies' % (took, len(imdb_urls)))
+    logger.debug(f'Took {took:.2f} seconds to query {len(imdb_urls)} movies')
 
 
 @event('options.register')
diff --git a/flexget/tests/test_condition.py b/flexget/tests/test_condition.py
index 351eafa80d..d39708d4d8 100644
--- a/flexget/tests/test_condition.py
+++ b/flexget/tests/test_condition.py
@@ -98,7 +98,7 @@ def test_quality(self, manager, execute_task):
             task = execute_task(taskname)
             count = len(task.rejected)
             expected = int(taskname[-1])
-            assert count == expected, "Expected %s rejects, got %d" % (expected, count)
+            assert count == expected, f"Expected {expected} rejects, got {count}"
 
 
 class TestDateCondition:
diff --git a/flexget/tests/test_discover.py b/flexget/tests/test_discover.py
index 9c3aff0d1c..ae8c89f993 100644
--- a/flexget/tests/test_discover.py
+++ b/flexget/tests/test_discover.py
@@ -139,7 +139,7 @@ def test_next_series_episodes(self, execute_task):
     def test_next_series_episodes_with_bad_search(self, execute_task):
         task = execute_task('test_next_series_episodes_with_bad_search')
         for epnum in range(1, 5):
-            title = 'My Show S01E0%d' % epnum
+            title = f'My Show S01E0{epnum}'
             assert any(e['title'] == title for e in task.mock_output), f'{title} not accepted'
         assert (
             len(task.mock_output) == 4
diff --git a/flexget/tests/test_exec.py b/flexget/tests/test_exec.py
index 64dcae5345..5716ff7590 100644
--- a/flexget/tests/test_exec.py
+++ b/flexget/tests/test_exec.py
@@ -76,4 +76,4 @@ def test_auto_escape(self, execute_task):
                 line = infile.readline().rstrip('\n')
                 assert line == '/start/single \' double"', f'{line} != /start/single \' double"'
                 line = infile.readline().rstrip('\n')
-                assert line == '% a $a! ` *', '%s != % a $a! ` *' % line
+                assert line == '% a $a! ` *', f'{line} != % a $a! ` *'
diff --git a/flexget/tests/test_rss.py b/flexget/tests/test_rss.py
index 00bf215843..f7e7451604 100644
--- a/flexget/tests/test_rss.py
+++ b/flexget/tests/test_rss.py
@@ -128,7 +128,7 @@ def test_group_links(self, execute_task):
             title='Multiple enclosures', url='http://localhost/multiple_enclosures'
         )
         assert entry, 'Entry not created for item with multiple enclosures'
-        urls = ['http://localhost/enclosure%d' % num for num in range(1, 3)]
+        urls = [f'http://localhost/enclosure{num}' for num in range(1, 3)]
         urls_not_present = [url for url in urls if url not in entry.get('urls')]
         assert not urls_not_present, f'{urls_not_present} should be present in urls list'
         # Test no entries were made for the enclosures
diff --git a/flexget/tests/test_torrent.py b/flexget/tests/test_torrent.py
index a36529b11f..bfbb55b789 100644
--- a/flexget/tests/test_torrent.py
+++ b/flexget/tests/test_torrent.py
@@ -275,12 +275,9 @@ def test_torrent_scrub_off(self, execute_task, tmpdir):
         for filename in self.test_files:
             osize = os.path.getsize(filename)
             msize = tmpdir.join(filename).size()
-            assert osize == msize, "Filesizes aren't supposed to differ (%r %d, %r %d)!" % (
-                filename,
-                osize,
-                self.__tmp__ + filename,
-                msize,
-            )
+            assert (
+                osize == msize
+            ), f"Filesizes aren't supposed to differ ({filename!r} {osize}, {self.__tmp__ + filename!r} {msize})!"
 
 
 @pytest.mark.usefixtures('tmpdir')