From 9dceba5edbd9dba2a355656a7a28ea9fdccf779e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:25:47 +0300 Subject: [PATCH 01/21] Update [https://github.com/astral-sh/ruff-pre-commit] updating v0.4.4 -> v0.5.1 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bdaee30741..154e2e0133 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: destroyed-symlinks - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.4 + rev: v0.5.1 hooks: - id: ruff args: [ --fix ] From b056d9dea308bfe072502213f7ee9f576289503e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:40:31 +0300 Subject: [PATCH 02/21] SIM113 Use `enumerate()` for index variable `i` in `for` loop 37 | for v in items: 38 | yield i, v 39 | i += 1 | ^^^^^^ SIM113 40 | 41 | else: --- plextraktsync/commands/cache.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plextraktsync/commands/cache.py b/plextraktsync/commands/cache.py index ce6598b15d..eb8206ab56 100644 --- a/plextraktsync/commands/cache.py +++ b/plextraktsync/commands/cache.py @@ -33,10 +33,8 @@ def responses_by_url( # https://stackoverflow.com/questions/36106712/how-can-i-limit-iterations-of-a-loop-in-python def limit_iterator(items, limit: int): if not limit or limit <= 0: - i = 0 - for v in items: + for i, v in enumerate(items): yield i, v - i += 1 else: yield from zip(range(limit), items) From 194838d03cc3b8ee3fa6c06d3d3fa232872a2de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:32:24 +0300 Subject: [PATCH 03/21] Linter: Enable isort ruff rules --- pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 27ec922b5e..9a1da1c533 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,10 @@ requires = [ "wheel", ] build-backend = "setuptools.build_meta" + +[tool.ruff.lint] +# https://docs.astral.sh/ruff/linter/#rule-selection +# https://docs.astral.sh/ruff/settings/#lint_extend-select +extend-select = [ + "I", # isort +] From 51071af7f109ef5db574a35188f3e6860f3ab4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:24:49 +0300 Subject: [PATCH 04/21] Linter: Set ruff required-imports isort --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9a1da1c533..7c88b7f834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,3 +12,7 @@ build-backend = "setuptools.build_meta" extend-select = [ "I", # isort ] + +[tool.ruff.lint.isort] +# https://docs.astral.sh/ruff/settings/#lint_isort_required-imports +required-imports = ["from __future__ import annotations"] From 37bf9f8776107d86f46ed6a1819a1208b892158c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:34:04 +0300 Subject: [PATCH 05/21] Linter: Apply ruff isort --- plextraktsync/decorators/rate_limit.py | 3 +-- plextraktsync/decorators/time_limit.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plextraktsync/decorators/rate_limit.py b/plextraktsync/decorators/rate_limit.py index 3a71a8994f..25950854ef 100644 --- a/plextraktsync/decorators/rate_limit.py +++ b/plextraktsync/decorators/rate_limit.py @@ -1,9 +1,8 @@ from time import sleep from click import ClickException -from trakt.errors import RateLimitException from decorator import decorator - +from trakt.errors import RateLimitException from plextraktsync.factory import logging diff --git a/plextraktsync/decorators/time_limit.py b/plextraktsync/decorators/time_limit.py index d138255628..f8b1e3b35a 100644 --- a/plextraktsync/decorators/time_limit.py +++ b/plextraktsync/decorators/time_limit.py @@ -1,7 +1,7 @@ -from plextraktsync.config import TRAKT_POST_DELAY -from plextraktsync.util.Timer import Timer from decorator import decorator +from plextraktsync.config import TRAKT_POST_DELAY +from plextraktsync.util.Timer import Timer timer = Timer(TRAKT_POST_DELAY) From 62f9bff20ac085e1d7115bb740c8e6cbceecb6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:35:23 +0300 Subject: [PATCH 06/21] Linter: Import annotations for all files --- plextraktsync/__init__.py | 2 ++ plextraktsync/__main__.py | 2 ++ plextraktsync/cli.py | 2 ++ plextraktsync/commands/bug_report.py | 2 ++ plextraktsync/commands/clear_collections.py | 2 ++ plextraktsync/commands/config.py | 2 ++ plextraktsync/commands/info.py | 2 ++ plextraktsync/commands/login.py | 2 ++ plextraktsync/commands/unmatched.py | 2 ++ plextraktsync/commands/watched_shows.py | 2 ++ plextraktsync/config/Config.py | 2 ++ plextraktsync/config/ConfigLoader.py | 3 +++ plextraktsync/config/ConfigMergeMixin.py | 3 +++ plextraktsync/config/ServerConfigFactory.py | 2 ++ plextraktsync/config/__init__.py | 2 ++ plextraktsync/decorators/coro.py | 2 ++ plextraktsync/decorators/flatten.py | 2 ++ plextraktsync/decorators/measure_time.py | 2 ++ plextraktsync/decorators/memoize.py | 2 ++ plextraktsync/decorators/nocache.py | 2 ++ plextraktsync/decorators/rate_limit.py | 2 ++ plextraktsync/decorators/retry.py | 2 ++ plextraktsync/decorators/time_limit.py | 2 ++ plextraktsync/factory.py | 2 ++ plextraktsync/logger/init.py | 2 ++ plextraktsync/mixin/ChangeNotifier.py | 3 +++ plextraktsync/mixin/RichMarkup.py | 2 ++ plextraktsync/mixin/SetWindowTitle.py | 2 ++ plextraktsync/path.py | 2 ++ plextraktsync/plex/types.py | 2 ++ plextraktsync/plugin/__init__.py | 2 ++ plextraktsync/plugin/plugin.py | 2 ++ plextraktsync/pytrakt_extensions.py | 2 ++ plextraktsync/queue/TraktBatchWorker.py | 2 ++ plextraktsync/rich/RichHighlighter.py | 2 ++ plextraktsync/rich/RichProgressBar.py | 2 ++ plextraktsync/style.py | 2 ++ plextraktsync/sync/plugin/__init__.py | 2 ++ plextraktsync/trakt/types.py | 2 ++ plextraktsync/util/Path.py | 2 ++ plextraktsync/util/Timer.py | 2 ++ plextraktsync/util/Version.py | 2 ++ plextraktsync/util/execp.py | 2 ++ plextraktsync/util/expand_id.py | 2 ++ plextraktsync/util/git_version_info.py | 3 +++ plextraktsync/util/local_url.py | 3 +++ plextraktsync/util/openurl.py | 2 ++ plextraktsync/util/packaging.py | 2 ++ plextraktsync/util/parse_date.py | 2 ++ plextraktsync/util/remove_empty_values.py | 3 +++ plextraktsync/watch/EventDispatcher.py | 2 ++ plextraktsync/watch/EventFactory.py | 2 ++ tests/__init__.py | 2 ++ tests/conftest.py | 2 ++ tests/test_collection_metadata.py | 2 ++ tests/test_config.py | 2 ++ tests/test_events.py | 2 ++ tests/test_logger.py | 1 + tests/test_new_agent.py | 2 ++ tests/test_plex_id.py | 2 ++ tests/test_plugin.py | 2 ++ tests/test_rating.py | 2 ++ tests/test_threading.py | 2 ++ tests/test_timer.py | 2 ++ tests/test_trakt_progress.py | 2 ++ tests/test_tv_lookup.py | 2 ++ tests/test_walker.py | 2 ++ 67 files changed, 139 insertions(+) diff --git a/plextraktsync/__init__.py b/plextraktsync/__init__.py index 9513d4ea21..f261e86cc9 100644 --- a/plextraktsync/__init__.py +++ b/plextraktsync/__init__.py @@ -1 +1,3 @@ +from __future__ import annotations + __version__ = "0.31.0dev0" diff --git a/plextraktsync/__main__.py b/plextraktsync/__main__.py index bb3950f02e..8702c0a8a5 100644 --- a/plextraktsync/__main__.py +++ b/plextraktsync/__main__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + if len(__package__) == 0: import sys diff --git a/plextraktsync/cli.py b/plextraktsync/cli.py index 1b55eb8bb4..3534adca8b 100644 --- a/plextraktsync/cli.py +++ b/plextraktsync/cli.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import wraps from os import environ diff --git a/plextraktsync/commands/bug_report.py b/plextraktsync/commands/bug_report.py index 6efeab53b6..ba6bbe56ff 100644 --- a/plextraktsync/commands/bug_report.py +++ b/plextraktsync/commands/bug_report.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from urllib.parse import urlencode from plextraktsync.util.openurl import openurl diff --git a/plextraktsync/commands/clear_collections.py b/plextraktsync/commands/clear_collections.py index 95482be83c..3960cbe5e4 100644 --- a/plextraktsync/commands/clear_collections.py +++ b/plextraktsync/commands/clear_collections.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.factory import factory, logger diff --git a/plextraktsync/commands/config.py b/plextraktsync/commands/config.py index 16c2b6b28b..cc2af8e4df 100644 --- a/plextraktsync/commands/config.py +++ b/plextraktsync/commands/config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.factory import factory diff --git a/plextraktsync/commands/info.py b/plextraktsync/commands/info.py index 7ca2cc6639..ba0cc3f3e7 100644 --- a/plextraktsync/commands/info.py +++ b/plextraktsync/commands/info.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.factory import factory from plextraktsync.path import cache_dir, config_dir, log_dir, servers_config diff --git a/plextraktsync/commands/login.py b/plextraktsync/commands/login.py index b0105744cd..ec3442b414 100644 --- a/plextraktsync/commands/login.py +++ b/plextraktsync/commands/login.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.commands.plex_login import plex_login_autoconfig from plextraktsync.commands.trakt_login import has_trakt_token, trakt_login_autoconfig from plextraktsync.factory import factory diff --git a/plextraktsync/commands/unmatched.py b/plextraktsync/commands/unmatched.py index abf60d18a6..5235565360 100644 --- a/plextraktsync/commands/unmatched.py +++ b/plextraktsync/commands/unmatched.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.commands.login import ensure_login from plextraktsync.decorators.coro import coro from plextraktsync.factory import factory diff --git a/plextraktsync/commands/watched_shows.py b/plextraktsync/commands/watched_shows.py index c0708e15a0..eed1389034 100644 --- a/plextraktsync/commands/watched_shows.py +++ b/plextraktsync/commands/watched_shows.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from rich.table import Table from plextraktsync.factory import factory diff --git a/plextraktsync/config/Config.py b/plextraktsync/config/Config.py index c68bea4359..42b54fdf5c 100644 --- a/plextraktsync/config/Config.py +++ b/plextraktsync/config/Config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from os import getenv from os.path import exists diff --git a/plextraktsync/config/ConfigLoader.py b/plextraktsync/config/ConfigLoader.py index 3f14f8025d..1c12f23084 100644 --- a/plextraktsync/config/ConfigLoader.py +++ b/plextraktsync/config/ConfigLoader.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class ConfigLoader: @classmethod def load(cls, path: str): diff --git a/plextraktsync/config/ConfigMergeMixin.py b/plextraktsync/config/ConfigMergeMixin.py index 1e53a45a03..f0dc3caaad 100644 --- a/plextraktsync/config/ConfigMergeMixin.py +++ b/plextraktsync/config/ConfigMergeMixin.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class ConfigMergeMixin: # https://stackoverflow.com/a/20666342/2314626 def merge(self, source, destination): diff --git a/plextraktsync/config/ServerConfigFactory.py b/plextraktsync/config/ServerConfigFactory.py index 0b97fb349b..35bf93ccdd 100644 --- a/plextraktsync/config/ServerConfigFactory.py +++ b/plextraktsync/config/ServerConfigFactory.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from os.path import exists from plextraktsync.config.ConfigLoader import ConfigLoader diff --git a/plextraktsync/config/__init__.py b/plextraktsync/config/__init__.py index df0bc02b61..faa0a6c248 100644 --- a/plextraktsync/config/__init__.py +++ b/plextraktsync/config/__init__.py @@ -2,6 +2,8 @@ Platform name to identify our application """ +from __future__ import annotations + PLEX_PLATFORM = "PlexTraktSync" """ diff --git a/plextraktsync/decorators/coro.py b/plextraktsync/decorators/coro.py index 664e8c5e38..584d9e92d6 100644 --- a/plextraktsync/decorators/coro.py +++ b/plextraktsync/decorators/coro.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio from decorator import decorator diff --git a/plextraktsync/decorators/flatten.py b/plextraktsync/decorators/flatten.py index af3bb77342..0304a95c21 100644 --- a/plextraktsync/decorators/flatten.py +++ b/plextraktsync/decorators/flatten.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from decorator import decorator diff --git a/plextraktsync/decorators/measure_time.py b/plextraktsync/decorators/measure_time.py index 8ffcefcfb2..be4be09cbb 100644 --- a/plextraktsync/decorators/measure_time.py +++ b/plextraktsync/decorators/measure_time.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import inspect from contextlib import contextmanager from datetime import timedelta diff --git a/plextraktsync/decorators/memoize.py b/plextraktsync/decorators/memoize.py index 239773a0e1..e217d054a8 100644 --- a/plextraktsync/decorators/memoize.py +++ b/plextraktsync/decorators/memoize.py @@ -1,3 +1,5 @@ +from __future__ import annotations + try: from functools import cache as memoize except ImportError: diff --git a/plextraktsync/decorators/nocache.py b/plextraktsync/decorators/nocache.py index 3895d4a411..5ca66b8c64 100644 --- a/plextraktsync/decorators/nocache.py +++ b/plextraktsync/decorators/nocache.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from decorator import decorator from plextraktsync.factory import factory diff --git a/plextraktsync/decorators/rate_limit.py b/plextraktsync/decorators/rate_limit.py index 25950854ef..bc3344d980 100644 --- a/plextraktsync/decorators/rate_limit.py +++ b/plextraktsync/decorators/rate_limit.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from time import sleep from click import ClickException diff --git a/plextraktsync/decorators/retry.py b/plextraktsync/decorators/retry.py index a84f2e1c6c..04da081d3a 100644 --- a/plextraktsync/decorators/retry.py +++ b/plextraktsync/decorators/retry.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from time import sleep from click import ClickException diff --git a/plextraktsync/decorators/time_limit.py b/plextraktsync/decorators/time_limit.py index f8b1e3b35a..7f4c6505e2 100644 --- a/plextraktsync/decorators/time_limit.py +++ b/plextraktsync/decorators/time_limit.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from decorator import decorator from plextraktsync.config import TRAKT_POST_DELAY diff --git a/plextraktsync/factory.py b/plextraktsync/factory.py index 460afcc386..84effc6180 100644 --- a/plextraktsync/factory.py +++ b/plextraktsync/factory.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.util.Factory import Factory factory = Factory() diff --git a/plextraktsync/logger/init.py b/plextraktsync/logger/init.py index 52980e3e09..06c1b91e3a 100644 --- a/plextraktsync/logger/init.py +++ b/plextraktsync/logger/init.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import re diff --git a/plextraktsync/mixin/ChangeNotifier.py b/plextraktsync/mixin/ChangeNotifier.py index 44e61e0771..19415df304 100644 --- a/plextraktsync/mixin/ChangeNotifier.py +++ b/plextraktsync/mixin/ChangeNotifier.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class ChangeNotifier(dict): """ MixIn that would make dict object notify listeners when a value is set to dict diff --git a/plextraktsync/mixin/RichMarkup.py b/plextraktsync/mixin/RichMarkup.py index 26a6b9c5c6..c0be54e8b5 100644 --- a/plextraktsync/mixin/RichMarkup.py +++ b/plextraktsync/mixin/RichMarkup.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from rich.markup import escape diff --git a/plextraktsync/mixin/SetWindowTitle.py b/plextraktsync/mixin/SetWindowTitle.py index f41d1735e2..9ffafb7024 100644 --- a/plextraktsync/mixin/SetWindowTitle.py +++ b/plextraktsync/mixin/SetWindowTitle.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import cached_property diff --git a/plextraktsync/path.py b/plextraktsync/path.py index d8b8fe4fc2..863961fcca 100644 --- a/plextraktsync/path.py +++ b/plextraktsync/path.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.util.Path import Path p = Path() diff --git a/plextraktsync/plex/types.py b/plextraktsync/plex/types.py index 1f0aacf5e1..b33f136434 100644 --- a/plextraktsync/plex/types.py +++ b/plextraktsync/plex/types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union from plexapi.video import Episode, Movie, Show diff --git a/plextraktsync/plugin/__init__.py b/plextraktsync/plugin/__init__.py index ad11af728f..6366841769 100644 --- a/plextraktsync/plugin/__init__.py +++ b/plextraktsync/plugin/__init__.py @@ -1 +1,3 @@ +from __future__ import annotations + from .plugin import hookimpl, hookspec # noqa: F401 diff --git a/plextraktsync/plugin/plugin.py b/plextraktsync/plugin/plugin.py index 2040212176..c7b181d8c2 100644 --- a/plextraktsync/plugin/plugin.py +++ b/plextraktsync/plugin/plugin.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import apluggy as pluggy hookspec = pluggy.HookspecMarker("PlexTraktSync") diff --git a/plextraktsync/pytrakt_extensions.py b/plextraktsync/pytrakt_extensions.py index b1a7952eda..c4c8c1fdeb 100644 --- a/plextraktsync/pytrakt_extensions.py +++ b/plextraktsync/pytrakt_extensions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from trakt.core import get from trakt.utils import airs_date diff --git a/plextraktsync/queue/TraktBatchWorker.py b/plextraktsync/queue/TraktBatchWorker.py index 6dd7a0e3dc..dac4b87c6d 100644 --- a/plextraktsync/queue/TraktBatchWorker.py +++ b/plextraktsync/queue/TraktBatchWorker.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import defaultdict import trakt.sync diff --git a/plextraktsync/rich/RichHighlighter.py b/plextraktsync/rich/RichHighlighter.py index 2b83827e3e..023c33f1c1 100644 --- a/plextraktsync/rich/RichHighlighter.py +++ b/plextraktsync/rich/RichHighlighter.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from rich.highlighter import RegexHighlighter diff --git a/plextraktsync/rich/RichProgressBar.py b/plextraktsync/rich/RichProgressBar.py index dd237e45a7..49b76b3809 100644 --- a/plextraktsync/rich/RichProgressBar.py +++ b/plextraktsync/rich/RichProgressBar.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import cached_property diff --git a/plextraktsync/style.py b/plextraktsync/style.py index 1074ffb050..85e0466ab4 100644 --- a/plextraktsync/style.py +++ b/plextraktsync/style.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import partial import click diff --git a/plextraktsync/sync/plugin/__init__.py b/plextraktsync/sync/plugin/__init__.py index 37ceedf3d9..dce2ec817c 100644 --- a/plextraktsync/sync/plugin/__init__.py +++ b/plextraktsync/sync/plugin/__init__.py @@ -1 +1,3 @@ +from __future__ import annotations + from .SyncPluginManager import SyncPluginManager # noqa: F401 diff --git a/plextraktsync/trakt/types.py b/plextraktsync/trakt/types.py index c0e18a45b7..cd03a79473 100644 --- a/plextraktsync/trakt/types.py +++ b/plextraktsync/trakt/types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TypedDict, Union from trakt.movies import Movie diff --git a/plextraktsync/util/Path.py b/plextraktsync/util/Path.py index 3673305f21..a2d4739901 100644 --- a/plextraktsync/util/Path.py +++ b/plextraktsync/util/Path.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import cached_property from os import getenv, makedirs from os.path import abspath, dirname, exists, join diff --git a/plextraktsync/util/Timer.py b/plextraktsync/util/Timer.py index 3555d5d3c1..fd41b1d708 100644 --- a/plextraktsync/util/Timer.py +++ b/plextraktsync/util/Timer.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from time import monotonic, sleep from plextraktsync.factory import logging diff --git a/plextraktsync/util/Version.py b/plextraktsync/util/Version.py index 85228ce11e..31f81244f8 100644 --- a/plextraktsync/util/Version.py +++ b/plextraktsync/util/Version.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import cached_property diff --git a/plextraktsync/util/execp.py b/plextraktsync/util/execp.py index c5d484a074..ab6b023082 100644 --- a/plextraktsync/util/execp.py +++ b/plextraktsync/util/execp.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from subprocess import call diff --git a/plextraktsync/util/expand_id.py b/plextraktsync/util/expand_id.py index 08ad1864af..7c4599bb8d 100644 --- a/plextraktsync/util/expand_id.py +++ b/plextraktsync/util/expand_id.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.plex.PlexIdFactory import PlexIdFactory diff --git a/plextraktsync/util/git_version_info.py b/plextraktsync/util/git_version_info.py index 856a2588b5..90d34f798b 100644 --- a/plextraktsync/util/git_version_info.py +++ b/plextraktsync/util/git_version_info.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def git_version_info(): try: from gitinfo import get_git_info diff --git a/plextraktsync/util/local_url.py b/plextraktsync/util/local_url.py index ab76123390..4a360fd87d 100644 --- a/plextraktsync/util/local_url.py +++ b/plextraktsync/util/local_url.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def local_url(port=32400): """ Find url for local plex access. diff --git a/plextraktsync/util/openurl.py b/plextraktsync/util/openurl.py index 524ee6b7f4..e357b4290b 100644 --- a/plextraktsync/util/openurl.py +++ b/plextraktsync/util/openurl.py @@ -1,5 +1,7 @@ # minimal version of openurl without six dependency # https://github.com/panda2134/open-python/blob/32a4b61b44302da185dcf6a72a48d1c726eb3e51/open_python/__init__.py +from __future__ import annotations + import sys from plextraktsync.util.execx import execx diff --git a/plextraktsync/util/packaging.py b/plextraktsync/util/packaging.py index 1f32f7b119..49ce1ffbb6 100644 --- a/plextraktsync/util/packaging.py +++ b/plextraktsync/util/packaging.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json import site from json import JSONDecodeError diff --git a/plextraktsync/util/parse_date.py b/plextraktsync/util/parse_date.py index ceb3de03c6..11f0ca5f2c 100644 --- a/plextraktsync/util/parse_date.py +++ b/plextraktsync/util/parse_date.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from datetime import timedelta from pytimeparse import parse diff --git a/plextraktsync/util/remove_empty_values.py b/plextraktsync/util/remove_empty_values.py index 3a60b2304f..e12cd38f83 100644 --- a/plextraktsync/util/remove_empty_values.py +++ b/plextraktsync/util/remove_empty_values.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def remove_empty_values(result): """ Update result to remove empty changes. diff --git a/plextraktsync/watch/EventDispatcher.py b/plextraktsync/watch/EventDispatcher.py index 1537d6c33c..5ecc2086aa 100644 --- a/plextraktsync/watch/EventDispatcher.py +++ b/plextraktsync/watch/EventDispatcher.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plextraktsync.factory import logging from plextraktsync.watch.EventFactory import EventFactory from plextraktsync.watch.events import Error, ServerStarted diff --git a/plextraktsync/watch/EventFactory.py b/plextraktsync/watch/EventFactory.py index 58d87b0adf..44457ef038 100644 --- a/plextraktsync/watch/EventFactory.py +++ b/plextraktsync/watch/EventFactory.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import importlib diff --git a/tests/__init__.py b/tests/__init__.py index f7a3ffcc90..4f0c00408c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from os.path import abspath, dirname diff --git a/tests/conftest.py b/tests/conftest.py index c5fb1ccb0e..2a272491ff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from os import environ from os.path import dirname diff --git a/tests/test_collection_metadata.py b/tests/test_collection_metadata.py index f0c2cb3482..add3b9b1b7 100755 --- a/tests/test_collection_metadata.py +++ b/tests/test_collection_metadata.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from datetime import datetime, timezone import pytest diff --git a/tests/test_config.py b/tests/test_config.py index e156381d7e..ce89653d65 100755 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from os.path import join import pytest diff --git a/tests/test_events.py b/tests/test_events.py index f8e1c2b3b4..58bad9802e 100755 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from plextraktsync.watch.EventDispatcher import EventDispatcher from plextraktsync.watch.EventFactory import EventFactory from plextraktsync.watch.events import ActivityNotification diff --git a/tests/test_logger.py b/tests/test_logger.py index 45becf5750..6875373f56 100755 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations from plextraktsync.factory import logging diff --git a/tests/test_new_agent.py b/tests/test_new_agent.py index 7d96931823..3540538f32 100755 --- a/tests/test_new_agent.py +++ b/tests/test_new_agent.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from plextraktsync.plex.PlexLibraryItem import PlexLibraryItem from tests.conftest import factory, make diff --git a/tests/test_plex_id.py b/tests/test_plex_id.py index 8cc352daa1..c34f4d7978 100755 --- a/tests/test_plex_id.py +++ b/tests/test_plex_id.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from plextraktsync.plex.PlexId import PlexId from plextraktsync.plex.PlexIdFactory import PlexIdFactory diff --git a/tests/test_plugin.py b/tests/test_plugin.py index d48872e62d..36f07bd22d 100755 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from plextraktsync.sync.plugin import SyncPluginManager diff --git a/tests/test_rating.py b/tests/test_rating.py index dc1fd54cd7..377dffec37 100755 --- a/tests/test_rating.py +++ b/tests/test_rating.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from datetime import datetime, timezone from plextraktsync.util.Rating import Rating diff --git a/tests/test_threading.py b/tests/test_threading.py index a7b416cb8c..19a74f0d3e 100755 --- a/tests/test_threading.py +++ b/tests/test_threading.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from time import sleep from tests.conftest import factory diff --git a/tests/test_timer.py b/tests/test_timer.py index b3af8a560f..abe455e172 100755 --- a/tests/test_timer.py +++ b/tests/test_timer.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from contextlib import contextmanager from time import perf_counter, sleep diff --git a/tests/test_trakt_progress.py b/tests/test_trakt_progress.py index aef582d8fd..42fa51fa37 100755 --- a/tests/test_trakt_progress.py +++ b/tests/test_trakt_progress.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + import pytest from trakt.tv import TVShow diff --git a/tests/test_tv_lookup.py b/tests/test_tv_lookup.py index 1e431a3129..7c417cc587 100755 --- a/tests/test_tv_lookup.py +++ b/tests/test_tv_lookup.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + import pytest from trakt.tv import TVShow diff --git a/tests/test_walker.py b/tests/test_walker.py index cd4cc68937..61b17b6a80 100755 --- a/tests/test_walker.py +++ b/tests/test_walker.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 -m pytest +from __future__ import annotations + from plextraktsync.plan.WalkConfig import WalkConfig from plextraktsync.plan.WalkPlanner import WalkPlanner from plextraktsync.plex.PlexApi import PlexApi From 1939ff748bd37989a8aefd0f51b605b6fcb5d06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:41:54 +0300 Subject: [PATCH 07/21] SIM105 Use `contextlib.suppress(KeyError)` instead of `try`-`except`-`pass` 22 | # Delete environment to ensure consistent tests 23 | for key in config.env_keys: 24 | try: | _____^ 25 | | del environ[key] 26 | | except KeyError: 27 | | pass | |____________^ SIM105 | = help: Replace with `contextlib.suppress(KeyError)` --- tests/conftest.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2a272491ff..d8f7577eba 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib import json from os import environ from os.path import dirname @@ -21,10 +22,8 @@ # Delete environment to ensure consistent tests for key in config.env_keys: - try: + with contextlib.suppress(KeyError): del environ[key] - except KeyError: - pass def load_mock(name: str): From 44351d038d029913a9c733facdd7e9c6cbfa0580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:44:28 +0300 Subject: [PATCH 08/21] SIM105 Use `contextlib.suppress(KeyError)` instead of `try`-`except`-`pass` 12 | """ 13 | for key in keys or []: 14 | try: | _____________^ 15 | | del self.__dict__[key] 16 | | except KeyError: 17 | | pass | |____________________^ SIM105 18 | 19 | @cached_property | = help: Replace with `contextlib.suppress(KeyError)` --- plextraktsync/util/Factory.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plextraktsync/util/Factory.py b/plextraktsync/util/Factory.py index e16c0969ec..74906c0c01 100644 --- a/plextraktsync/util/Factory.py +++ b/plextraktsync/util/Factory.py @@ -11,10 +11,10 @@ def invalidate(self, keys: list[str] = None): https://stackoverflow.com/a/63617398 """ for key in keys or []: - try: + import contextlib + + with contextlib.suppress(KeyError): del self.__dict__[key] - except KeyError: - pass @cached_property def version(self): From 373f1739ca9361c66fe3768aa82d67462d973cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:45:45 +0300 Subject: [PATCH 09/21] SIM105 Use `contextlib.suppress(AttributeError)` instead of `try`-`except`-`pass` 49 | root = ElementTree.fromstring(data) 50 | try: | _____^ 51 | | # requires python 3.9 52 | | # https://stackoverflow.com/a/39482716/2314626 53 | | ElementTree.indent(root) 54 | | except AttributeError: 55 | | pass | |____________^ SIM105 56 | 57 | return ElementTree.tostring(root, encoding="utf8").decode("utf8") | = help: Replace with `contextlib.suppress(AttributeError)` --- plextraktsync/commands/cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plextraktsync/commands/cache.py b/plextraktsync/commands/cache.py index eb8206ab56..159589a9e1 100644 --- a/plextraktsync/commands/cache.py +++ b/plextraktsync/commands/cache.py @@ -47,12 +47,12 @@ def render_xml(data): return None root = ElementTree.fromstring(data) - try: + import contextlib + + with contextlib.suppress(AttributeError): # requires python 3.9 # https://stackoverflow.com/a/39482716/2314626 ElementTree.indent(root) - except AttributeError: - pass return ElementTree.tostring(root, encoding="utf8").decode("utf8") From c1eb36518cda888632e21720506b57d1c1c29288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:48:13 +0300 Subject: [PATCH 10/21] SIM103 Return the condition `str(id_from_trakt) != guid.id` directly 83 | return False 84 | id_from_trakt = getattr(episode, guid.provider, None) 85 | if str(id_from_trakt) != guid.id: | _________^ 86 | | return True 87 | | return False | |____________________^ SIM103 88 | 89 | def from_number(self, season: int, number: int): | = help: Replace with `return str(id_from_trakt) != guid.id` --- plextraktsync/trakt/TraktLookup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plextraktsync/trakt/TraktLookup.py b/plextraktsync/trakt/TraktLookup.py index a11a03083c..d96021b983 100644 --- a/plextraktsync/trakt/TraktLookup.py +++ b/plextraktsync/trakt/TraktLookup.py @@ -82,9 +82,7 @@ def invalid_match(guid: PlexGuid, episode: TVEpisode | None) -> bool: # check can not be performed return False id_from_trakt = getattr(episode, guid.provider, None) - if str(id_from_trakt) != guid.id: - return True - return False + return str(id_from_trakt) != guid.id def from_number(self, season: int, number: int): try: From f39aebaf4a2b06c1ad9e154402117b8b75f0bbfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:51:03 +0300 Subject: [PATCH 11/21] SIM401 Use `show_cache.get(show_id, None)` instead of an `if` block 134 | show_id = ep.show_id 135 | ep.show = plex_shows[show_id] 136 | show = show_cache[show_id] if show_id in show_cache else None | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM401 137 | m = self.mf.resolve_any(ep, show) 138 | if not m: | = help: Replace with `show_cache.get(show_id, None)` --- plextraktsync/plan/Walker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plextraktsync/plan/Walker.py b/plextraktsync/plan/Walker.py index 66b37b2fca..0f33a48a36 100644 --- a/plextraktsync/plan/Walker.py +++ b/plextraktsync/plan/Walker.py @@ -133,7 +133,7 @@ async def find_episodes(self): async for ep in self.episodes_from_sections(self.plan.show_sections): show_id = ep.show_id ep.show = plex_shows[show_id] - show = show_cache[show_id] if show_id in show_cache else None + show = show_cache.get(show_id) m = self.mf.resolve_any(ep, show) if not m: continue From cb9c8896c1c8434fc12d698691b8df1544375a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:52:56 +0300 Subject: [PATCH 12/21] SIM114 [*] Combine `if` branches using logical `or` operator 122 | def is_collected(self, trakt_id, season, episode): 123 | if trakt_id not in self.shows.keys(): | _________^ 124 | | return False 125 | | elif season not in self.shows[trakt_id].seasons.keys(): 126 | | return False | |________________________^ SIM114 127 | return episode in self.shows[trakt_id].seasons[season].episodes.keys() | = help: Combine `if` branches --- plextraktsync/pytrakt_extensions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plextraktsync/pytrakt_extensions.py b/plextraktsync/pytrakt_extensions.py index c4c8c1fdeb..6306f7af9b 100644 --- a/plextraktsync/pytrakt_extensions.py +++ b/plextraktsync/pytrakt_extensions.py @@ -120,9 +120,10 @@ def get_completed(self, trakt_id, season, episode): return self.shows[trakt_id].get_completed(season, episode) def is_collected(self, trakt_id, season, episode): - if trakt_id not in self.shows.keys(): - return False - elif season not in self.shows[trakt_id].seasons.keys(): + if ( + trakt_id not in self.shows.keys() + or season not in self.shows[trakt_id].seasons.keys() + ): return False return episode in self.shows[trakt_id].seasons[season].episodes.keys() From 018cea3cb9806c481ca3a00689bda4dc414806ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:54:36 +0300 Subject: [PATCH 13/21] SIM118 Use `key in dict` instead of `key in dict.keys()` = help: Remove `.keys()` --- plextraktsync/config/Config.py | 4 ++-- plextraktsync/pytrakt_extensions.py | 15 ++++++--------- plextraktsync/trakt/TraktLookup.py | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/plextraktsync/config/Config.py b/plextraktsync/config/Config.py index 42b54fdf5c..ceaa69b8c2 100644 --- a/plextraktsync/config/Config.py +++ b/plextraktsync/config/Config.py @@ -134,7 +134,7 @@ def initialize(self): override = self["config"]["dotenv_override"] load_dotenv(self.env_file, override=override) - for key in self.env_keys.keys(): + for key in self.env_keys: value = getenv(key) if value == "-" or value == "None" or value == "": value = None @@ -155,7 +155,7 @@ def serialize(self): """ data = dict(self) # Remove env variables. They are usually secrets - for key in self.env_keys.keys(): + for key in self.env_keys: del data[key] return data diff --git a/plextraktsync/pytrakt_extensions.py b/plextraktsync/pytrakt_extensions.py index 6306f7af9b..26fb6b42d7 100644 --- a/plextraktsync/pytrakt_extensions.py +++ b/plextraktsync/pytrakt_extensions.py @@ -54,7 +54,7 @@ def __init__(self, number=0, title=None, aired=0, completed=False, episodes=None def get_completed(self, episode, reset_at): if self.completed: return True - elif episode not in self.episodes.keys(): + elif episode not in self.episodes: return False last_watched_at = airs_date(self.episodes[episode].last_watched_at) if reset_at and reset_at > last_watched_at: @@ -100,7 +100,7 @@ def __init__( def get_completed(self, season, episode): if self.completed: return True - elif season not in self.seasons.keys(): + elif season not in self.seasons: return False reset_at = airs_date(self.reset_at) return self.seasons[season].get_completed(episode, reset_at) @@ -114,21 +114,18 @@ def __init__(self, shows=None): self.shows[prog.trakt] = prog def get_completed(self, trakt_id, season, episode): - if trakt_id not in self.shows.keys(): + if trakt_id not in self.shows: return False else: return self.shows[trakt_id].get_completed(season, episode) def is_collected(self, trakt_id, season, episode): - if ( - trakt_id not in self.shows.keys() - or season not in self.shows[trakt_id].seasons.keys() - ): + if trakt_id not in self.shows or season not in self.shows[trakt_id].seasons: return False - return episode in self.shows[trakt_id].seasons[season].episodes.keys() + return episode in self.shows[trakt_id].seasons[season].episodes def reset_at(self, trakt_id): - if trakt_id not in self.shows.keys(): + if trakt_id not in self.shows: return None else: return airs_date(self.shows[trakt_id].reset_at) diff --git a/plextraktsync/trakt/TraktLookup.py b/plextraktsync/trakt/TraktLookup.py index d96021b983..17a7c040fc 100644 --- a/plextraktsync/trakt/TraktLookup.py +++ b/plextraktsync/trakt/TraktLookup.py @@ -53,7 +53,7 @@ def _reverse_lookup(self, provider): """ # NB: side effect, assumes that from_number() is called first to populate self.table table = {} - for season in self.table.keys(): + for season in self.table: for te in self.table[season].values(): table[str(te.ids.get(provider))] = te self.provider_table[provider] = table From f60dde9175ad79b9563504989f36db2669cecd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:56:30 +0300 Subject: [PATCH 14/21] Linter: Enable pyflakes ruff rules --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 7c88b7f834..f8d548e50d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ build-backend = "setuptools.build_meta" # https://docs.astral.sh/ruff/linter/#rule-selection # https://docs.astral.sh/ruff/settings/#lint_extend-select extend-select = [ + "F", # Pyflakes "I", # isort ] From 7192ebe256ee705e249de812d877b3db9ed93d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 15:56:57 +0300 Subject: [PATCH 15/21] Linter: Enable SIM flake8-simplify --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index f8d548e50d..518402d416 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,16 @@ build-backend = "setuptools.build_meta" # https://docs.astral.sh/ruff/settings/#lint_extend-select extend-select = [ "F", # Pyflakes + "SIM", # flake8-simplify "I", # isort ] +extend-ignore = [ + # Not safe changes + "SIM102", + "SIM103", + # TODO + "SIM108", +] [tool.ruff.lint.isort] # https://docs.astral.sh/ruff/settings/#lint_isort_required-imports From 62ed69b432e103b2411a1b34263dd9afafb3c01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 16:03:01 +0300 Subject: [PATCH 16/21] Linter: Configure ruff line length to 150 --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 518402d416..6af9e66895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,10 @@ requires = [ ] build-backend = "setuptools.build_meta" +[tool.ruff] +# https://docs.astral.sh/ruff/settings/#line-length +line-length = 150 + [tool.ruff.lint] # https://docs.astral.sh/ruff/linter/#rule-selection # https://docs.astral.sh/ruff/settings/#lint_extend-select From b6054b9ca97cd10ff51f47895d042519848b0daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 16:04:34 +0300 Subject: [PATCH 17/21] Linter: Reformat with longer line length --- plextraktsync/cli.py | 24 +++------- plextraktsync/commands/bug_report.py | 4 +- plextraktsync/commands/cache.py | 12 ++--- plextraktsync/commands/download.py | 4 +- plextraktsync/commands/imdb_import.py | 8 +--- plextraktsync/commands/inspect.py | 15 +++---- plextraktsync/commands/login.py | 4 +- plextraktsync/commands/plex_login.py | 22 +++------- plextraktsync/commands/self_update.py | 4 +- plextraktsync/commands/sync.py | 12 ++--- plextraktsync/commands/trakt_login.py | 5 +-- plextraktsync/commands/watched_shows.py | 4 +- plextraktsync/config/Config.py | 14 ++---- plextraktsync/config/SyncConfig.py | 18 ++------ plextraktsync/decorators/rate_limit.py | 12 ++--- plextraktsync/decorators/retry.py | 12 ++--- plextraktsync/logger/init.py | 4 +- plextraktsync/media/Media.py | 33 ++++---------- plextraktsync/media/MediaFactory.py | 10 +---- plextraktsync/plan/WalkPlanner.py | 28 +++--------- plextraktsync/plan/Walker.py | 32 ++++---------- plextraktsync/plex/PlexApi.py | 44 +++++-------------- plextraktsync/plex/PlexAudioCodec.py | 4 +- plextraktsync/plex/PlexLibraryItem.py | 8 +--- plextraktsync/plex/PlexRatings.py | 4 +- plextraktsync/plex/PlexSectionPager.py | 8 +--- plextraktsync/plex/PlexServerConnection.py | 16 ++----- plextraktsync/pytrakt_extensions.py | 4 +- plextraktsync/queue/Queue.py | 4 +- plextraktsync/rich/RichProgressBar.py | 3 +- plextraktsync/sync/AddCollectionPlugin.py | 4 +- plextraktsync/sync/ClearCollectedPlugin.py | 12 ++--- plextraktsync/sync/LikedListsPlugin.py | 5 +-- plextraktsync/sync/SyncWatchedPlugin.py | 8 +--- plextraktsync/sync/WatchListPlugin.py | 14 ++---- .../sync/plugin/SyncPluginManager.py | 8 +--- plextraktsync/trakt/TraktApi.py | 24 +++------- plextraktsync/trakt/TraktItem.py | 6 +-- plextraktsync/trakt/TraktLookup.py | 5 +-- plextraktsync/trakt/TraktUserList.py | 24 +++------- plextraktsync/util/Factory.py | 4 +- plextraktsync/util/execx.py | 4 +- plextraktsync/util/local_url.py | 8 +--- plextraktsync/watch/ProgressBar.py | 8 +--- plextraktsync/watch/WatchStateUpdater.py | 24 +++------- plextraktsync/watch/WebSocketListener.py | 8 +--- tests/test_events.py | 16 ++----- tests/test_plex_id.py | 4 +- tests/test_tv_lookup.py | 8 +--- 49 files changed, 136 insertions(+), 434 deletions(-) diff --git a/plextraktsync/cli.py b/plextraktsync/cli.py index 3534adca8b..d054261358 100644 --- a/plextraktsync/cli.py +++ b/plextraktsync/cli.py @@ -27,9 +27,7 @@ def wrap(*args, **kwargs): try: cmd(*args, **kwargs) except EOFError as e: - raise ClickException( - f"Program requested terminal, No terminal is connected: {e}" - ) + raise ClickException(f"Program requested terminal, No terminal is connected: {e}") except ClickException as e: from plextraktsync.factory import logging @@ -85,9 +83,7 @@ def cli( if not ctx.invoked_subcommand: logger = factory.logger - logger.warning( - 'plextraktsync without command is deprecated. Executing "plextraktsync sync"' - ) + logger.warning('plextraktsync without command is deprecated. Executing "plextraktsync sync"') sync() @@ -180,12 +176,8 @@ def plex_login(): @command() @click.option("--library", help="Specify Library to use") -@click.option( - "--show", "show", type=str, show_default=True, help="Sync specific show only" -) -@click.option( - "--movie", "movie", type=str, show_default=True, help="Sync specific movie only" -) +@click.option("--show", "show", type=str, show_default=True, help="Sync specific show only") +@click.option("--movie", "movie", type=str, show_default=True, help="Sync specific movie only") @click.option( "--id", "ids", @@ -197,9 +189,7 @@ def plex_login(): @click.option( "--sync", "sync_option", - type=click.Choice( - ["all", "movies", "tv", "shows", "watchlist"], case_sensitive=False - ), + type=click.Choice(["all", "movies", "tv", "shows", "watchlist"], case_sensitive=False), default="all", show_default=True, help="Specify what to sync", @@ -344,9 +334,7 @@ def watched_shows(): @command() -@click.option( - "--urls-expire-after", is_flag=True, help="Print urls_expire_after configuration" -) +@click.option("--urls-expire-after", is_flag=True, help="Print urls_expire_after configuration") @click.option("--edit", is_flag=True, help="Open config file in editor") @click.option("--locate", is_flag=True, help="Locate config file in file browser") def config(): diff --git a/plextraktsync/commands/bug_report.py b/plextraktsync/commands/bug_report.py index ba6bbe56ff..8de7d73820 100644 --- a/plextraktsync/commands/bug_report.py +++ b/plextraktsync/commands/bug_report.py @@ -28,9 +28,7 @@ def bug_url(): def bug_report(): url = bug_url() - print( - "Opening bug report URL in browser, if that doesn't work open the link manually:" - ) + print("Opening bug report URL in browser, if that doesn't work open the link manually:") print("") print(url) openurl(url) diff --git a/plextraktsync/commands/cache.py b/plextraktsync/commands/cache.py index 159589a9e1..dc8454e1c8 100644 --- a/plextraktsync/commands/cache.py +++ b/plextraktsync/commands/cache.py @@ -22,12 +22,8 @@ def get_sorted_cache(session: CachedSession, sorting: str, reverse: bool): yield from sorter(session.cache.responses.values()) -def responses_by_url( - session: CachedSession, url: str -) -> Generator[CachedRequest, Any, None]: - return ( - response for response in session.cache.responses.values() if response.url == url - ) +def responses_by_url(session: CachedSession, url: str) -> Generator[CachedRequest, Any, None]: + return (response for response in session.cache.responses.values() if response.url == url) # https://stackoverflow.com/questions/36106712/how-can-i-limit-iterations-of-a-loop-in-python @@ -73,9 +69,7 @@ def inspect_url(session: CachedSession, url: str): matches = responses_by_url(session, url) for m in matches: content_type = m.headers["Content-Type"] - if content_type.startswith("text/xml") or content_type.startswith( - "application/xml" - ): + if content_type.startswith("text/xml") or content_type.startswith("application/xml"): print(f"") for name, value in m.headers.items(): print(f"") diff --git a/plextraktsync/commands/download.py b/plextraktsync/commands/download.py index e27e710c4e..ce5530c49b 100644 --- a/plextraktsync/commands/download.py +++ b/plextraktsync/commands/download.py @@ -32,9 +32,7 @@ def download_media(plex: PlexApi, pm: PlexLibraryItem, savepath: Path): def download_subtitles(plex: PlexApi, pm: PlexLibraryItem, savepath: Path): print(f"Subtitles for {pm}:") for index, sub in enumerate(pm.subtitle_streams, start=1): - print( - f" Subtitle {index}: ({sub.language}) {sub.title} (codec: {sub.codec}, selected: {sub.selected}, transient: {sub.transient})" - ) + print(f" Subtitle {index}: ({sub.language}) {sub.title} (codec: {sub.codec}, selected: {sub.selected}, transient: {sub.transient})") filename = "".join( [ diff --git a/plextraktsync/commands/imdb_import.py b/plextraktsync/commands/imdb_import.py index 260419279f..0831a2489d 100644 --- a/plextraktsync/commands/imdb_import.py +++ b/plextraktsync/commands/imdb_import.py @@ -75,16 +75,12 @@ def imdb_import(input: PathLike, dry_run: bool): print = factory.print for r in read_csv(input): - print( - f"Importing [blue]{r.media_type} {r.imdb}[/]: {r.title} ({r.year}), rated at {r.rate_date}" - ) + print(f"Importing [blue]{r.media_type} {r.imdb}[/]: {r.title} ({r.year}), rated at {r.rate_date}") m = trakt.search_by_id(r.imdb, "imdb", r.media_type) rating = trakt.rating(m) if r.rating == rating: print(f"Rating {rating} already exists") continue - print( - f"{'Would rate' if dry_run else 'Rating'} {m} with {r.rating} (was {rating})" - ) + print(f"{'Would rate' if dry_run else 'Rating'} {m} with {r.rating} (was {rating})") if not dry_run: trakt.rate(m, r.rating, r.rate_date) diff --git a/plextraktsync/commands/inspect.py b/plextraktsync/commands/inspect.py index 91a33f4fe7..216c139451 100644 --- a/plextraktsync/commands/inspect.py +++ b/plextraktsync/commands/inspect.py @@ -58,15 +58,14 @@ def inspect_media(plex_id: PlexId): print("Subtitles:") for index, subtitle in enumerate(pm.subtitle_streams, start=1): print( - f" Subtitle {index}: ({subtitle.language}) {subtitle.title} (codec: {subtitle.codec}, selected: {subtitle.selected}, transient: {subtitle.transient})" + f" Subtitle {index}: ({subtitle.language}) {subtitle.title}" + f" (codec: {subtitle.codec}, selected: {subtitle.selected}, transient: {subtitle.transient})" ) print("Parts:") for index, part in enumerate(pm.parts, start=1): size = naturalsize(part.size, binary=True) - file_link = ( - f"[link=file://{quote_plus(part.file)}]{escape(part.file)}[/link]" - ) + file_link = f"[link=file://{quote_plus(part.file)}]{escape(part.file)}[/link]" print(f" Part {index} (exists: {part.exists}): {file_link} {size}") print("Markers:") @@ -77,16 +76,12 @@ def inspect_media(plex_id: PlexId): print("Guids:") for guid in pm.guids: - print( - f" Guid: {guid.provider_link}, Id: {guid.id}, Provider: '{guid.provider}'" - ) + print(f" Guid: {guid.provider_link}, Id: {guid.id}, Provider: '{guid.provider}'") print(f"Metadata: {pm.to_json()}") print(f"Played on Plex: {pm.is_watched}") - history = ( - plex.history(media, device=True, account=True) if not pm.is_discover else [] - ) + history = plex.history(media, device=True, account=True) if not pm.is_discover else [] print("Plex play history:") for h in history: d = h.device diff --git a/plextraktsync/commands/login.py b/plextraktsync/commands/login.py index ec3442b414..c934a93cf2 100644 --- a/plextraktsync/commands/login.py +++ b/plextraktsync/commands/login.py @@ -20,9 +20,7 @@ def login(): print(highlight("Checking Plex and Trakt login credentials existence")) print("") print("It will not test if the credentials are valid, only that they are present.") - print( - 'If you need to re-login use "plex-login" or "trakt-login" commands respectively.' - ) + print('If you need to re-login use "plex-login" or "trakt-login" commands respectively.') print("") ensure_login() print(success("Done!")) diff --git a/plextraktsync/commands/plex_login.py b/plextraktsync/commands/plex_login.py index 20eb0e89cc..690805cdb5 100644 --- a/plextraktsync/commands/plex_login.py +++ b/plextraktsync/commands/plex_login.py @@ -26,12 +26,8 @@ PROMPT_PLEX_PASSWORD = prompt("Please enter your Plex password") PROMPT_PLEX_USERNAME = prompt("Please enter your Plex username or e-mail") PROMPT_PLEX_CODE = prompt("Enter a 2FA code if enabled, or leave blank otherwise") -PROMPT_PLEX_RELOGIN = prompt( - "You already have Plex Access Token, do you want to log in again?" -) -SUCCESS_MESSAGE = success( - "Plex Media Server Authentication Token and base URL have been added to servers.yml" -) +PROMPT_PLEX_RELOGIN = prompt("You already have Plex Access Token, do you want to log in again?") +SUCCESS_MESSAGE = success("Plex Media Server Authentication Token and base URL have been added to servers.yml") CONFIG = factory.config style = get_style( @@ -64,9 +60,7 @@ def server_urls(server: MyPlexResource): def myplex_login(username, password): while True: username = Prompt.ask(PROMPT_PLEX_USERNAME, default=username) - password = Prompt.ask( - PROMPT_PLEX_PASSWORD, password=True, default=password, show_default=False - ) + password = Prompt.ask(PROMPT_PLEX_PASSWORD, password=True, default=password, show_default=False) code = Prompt.ask(PROMPT_PLEX_CODE) try: return MyPlexAccount(username=username, password=password, code=code) @@ -108,9 +102,7 @@ def format_server(s): lines = [] product = f"{s.product}/{s.productVersion}" platform = f"{s.device}: {s.platform}/{s.platformVersion}" - lines.append( - f"{s.name}: Last seen: {str(s.lastSeenAt)}, Server: {product} on {platform}" - ) + lines.append(f"{s.name}: Last seen: {str(s.lastSeenAt)}, Server: {product} on {platform}") c: ResourceConnection for c in s.connections: lines.append(f" {c.uri}") @@ -170,11 +162,7 @@ def choose_server(account: MyPlexAccount) -> tuple[MyPlexResource, PlexServer]: # Connect to obtain baseUrl print() - print( - title( - f"Attempting to connect to {server.name}. This may take time and print some errors." - ) - ) + print(title(f"Attempting to connect to {server.name}. This may take time and print some errors.")) print(title("Server connections:")) for c in server.connections: print(f" {c.uri}") diff --git a/plextraktsync/commands/self_update.py b/plextraktsync/commands/self_update.py index 77c0b33e25..754cb32da0 100644 --- a/plextraktsync/commands/self_update.py +++ b/plextraktsync/commands/self_update.py @@ -45,9 +45,7 @@ def self_update(pr: int): execp(f"pipx uninstall plextraktsync@{pr}") print(f"Updating PlexTraktSync to the pull request #{pr} version using pipx") - execp( - f"pipx install --suffix=@{pr} --force git+https://github.com/Taxel/PlexTraktSync@refs/pull/{pr}/head" - ) + execp(f"pipx install --suffix=@{pr} --force git+https://github.com/Taxel/PlexTraktSync@refs/pull/{pr}/head") return print("Updating PlexTraktSync to the latest version using pipx") diff --git a/plextraktsync/commands/sync.py b/plextraktsync/commands/sync.py index ae8aa54985..0ea6b15819 100644 --- a/plextraktsync/commands/sync.py +++ b/plextraktsync/commands/sync.py @@ -38,19 +38,13 @@ def sync( dry_run=dry_run, ) if server: - logger.warning( - '"plextraktsync sync --server=" is deprecated use "plextraktsync --server= sync"' - ) + logger.warning('"plextraktsync sync --server=" is deprecated use "plextraktsync --server= sync"') config.update(server=server) if no_progress_bar: - logger.warning( - '"plextraktsync sync --no-progress-bar" is deprecated use "plextraktsync --no-progressbar sync"' - ) + logger.warning('"plextraktsync sync --no-progress-bar" is deprecated use "plextraktsync --no-progressbar sync"') config.update(progress=False) if batch_delay: - logger.warning( - '"plextraktsync sync --batch-delay=" is deprecated use "plextraktsync ---batch-delay= sync"' - ) + logger.warning('"plextraktsync sync --batch-delay=" is deprecated use "plextraktsync ---batch-delay= sync"') config.update(batch_delay=batch_delay) ensure_login() diff --git a/plextraktsync/commands/trakt_login.py b/plextraktsync/commands/trakt_login.py index 7f8f1c312e..e80c394926 100644 --- a/plextraktsync/commands/trakt_login.py +++ b/plextraktsync/commands/trakt_login.py @@ -16,10 +16,7 @@ PROMPT_TRAKT_CLIENT_ID = prompt("Please enter your client id") PROMPT_TRAKT_CLIENT_SECRET = prompt("Please enter your client secret") -TRAKT_LOGIN_SUCCESS = success( - "You are now logged into Trakt. " - "Your Trakt credentials have been added in .env and .pytrakt.json files." -) +TRAKT_LOGIN_SUCCESS = success("You are now logged into Trakt. Your Trakt credentials have been added in .env and .pytrakt.json files.") def trakt_authenticate(api: TraktApi): diff --git a/plextraktsync/commands/watched_shows.py b/plextraktsync/commands/watched_shows.py index eed1389034..5270ff4dc1 100644 --- a/plextraktsync/commands/watched_shows.py +++ b/plextraktsync/commands/watched_shows.py @@ -9,9 +9,7 @@ def watched_shows(): trakt = factory.trakt_api print = factory.print - table = Table( - show_header=True, header_style="bold magenta", title="Watched shows on Trakt" - ) + table = Table(show_header=True, header_style="bold magenta", title="Watched shows on Trakt") table.add_column("Id", style="dim", width=6) table.add_column("Slug") table.add_column("Seasons", justify="right") diff --git a/plextraktsync/config/Config.py b/plextraktsync/config/Config.py index ceaa69b8c2..2fdcb82b50 100644 --- a/plextraktsync/config/Config.py +++ b/plextraktsync/config/Config.py @@ -70,9 +70,7 @@ def log_file(self): @property def log_debug(self): - return ("log_debug_messages" in self and self["log_debug_messages"]) or self[ - "logging" - ]["debug"] + return ("log_debug_messages" in self and self["log_debug_messages"]) or self["logging"]["debug"] @property def log_append(self): @@ -90,11 +88,7 @@ def cache_path(self): def http_cache(self): from plextraktsync.config.HttpCacheConfig import HttpCacheConfig - cache = ( - self["http_cache"] - if "http_cache" in self and self["http_cache"] - else {"policy": {}} - ) + cache = self["http_cache"] if "http_cache" in self and self["http_cache"] else {"policy": {}} return HttpCacheConfig(**cache) @@ -144,9 +138,7 @@ def initialize(self): self["PLEX_LOCALURL"] = self["PLEX_FALLBACKURL"] self["PLEX_FALLBACKURL"] = None - self["cache"]["path"] = self["cache"]["path"].replace( - "$PTS_CACHE_DIR", cache_dir - ) + self["cache"]["path"] = self["cache"]["path"].replace("$PTS_CACHE_DIR", cache_dir) def serialize(self): """ diff --git a/plextraktsync/config/SyncConfig.py b/plextraktsync/config/SyncConfig.py index 06c79b11d8..468f9bbb91 100644 --- a/plextraktsync/config/SyncConfig.py +++ b/plextraktsync/config/SyncConfig.py @@ -58,15 +58,11 @@ def sync_ratings(self): @cached_property def clear_collected(self): - return ( - self.plex_to_trakt["collection"] and self.plex_to_trakt["clear_collected"] - ) + return self.plex_to_trakt["collection"] and self.plex_to_trakt["clear_collected"] @cached_property def sync_watched_status(self): - return ( - self.trakt_to_plex["watched_status"] or self.plex_to_trakt["watched_status"] - ) + return self.trakt_to_plex["watched_status"] or self.plex_to_trakt["watched_status"] @property def liked_lists_keep_watched(self): @@ -78,17 +74,11 @@ def sync_playback_status(self): @cached_property def update_plex_wl(self): - return ( - self.trakt_to_plex["watchlist"] - and not self.trakt_to_plex["watchlist_as_playlist"] - ) + return self.trakt_to_plex["watchlist"] and not self.trakt_to_plex["watchlist_as_playlist"] @cached_property def update_plex_wl_as_pl(self): - return ( - self.trakt_to_plex["watchlist"] - and self.trakt_to_plex["watchlist_as_playlist"] - ) + return self.trakt_to_plex["watchlist"] and self.trakt_to_plex["watchlist_as_playlist"] @cached_property def update_trakt_wl(self): diff --git a/plextraktsync/decorators/rate_limit.py b/plextraktsync/decorators/rate_limit.py index bc3344d980..fa1a1c1d0f 100644 --- a/plextraktsync/decorators/rate_limit.py +++ b/plextraktsync/decorators/rate_limit.py @@ -21,17 +21,11 @@ def rate_limit(fn, retries=5, *args, **kwargs): except RateLimitException as e: if retry == retries: logger.error(f"Trakt Error: {e}") - logger.error( - f"Last call: {fn.__module__}.{fn.__name__}({args[1:]}, {kwargs})" - ) - raise ClickException( - "Trakt API didn't respond properly, script will abort now. Please try again later." - ) + logger.error(f"Last call: {fn.__module__}.{fn.__name__}({args[1:]}, {kwargs})") + raise ClickException("Trakt API didn't respond properly, script will abort now. Please try again later.") seconds = e.retry_after retry += 1 - logger.warning( - f"{e} for {fn.__module__}.{fn.__name__}(), retrying after {seconds} seconds (try: {retry}/{retries})" - ) + logger.warning(f"{e} for {fn.__module__}.{fn.__name__}(), retrying after {seconds} seconds (try: {retry}/{retries})") logger.debug(e.details) sleep(seconds) diff --git a/plextraktsync/decorators/retry.py b/plextraktsync/decorators/retry.py index 04da081d3a..f6b3ff44fa 100644 --- a/plextraktsync/decorators/retry.py +++ b/plextraktsync/decorators/retry.py @@ -41,16 +41,10 @@ def retry(fn, retries=5, *args, **kwargs): if isinstance(e, TraktInternalException): logger.error(f"Error message: {e.error_message}") - logger.error( - f"Last call: {fn.__module__}.{fn.__name__}({args[1:]}, {kwargs})" - ) - raise ClickException( - "API didn't respond properly, script will abort now. Please try again later." - ) + logger.error(f"Last call: {fn.__module__}.{fn.__name__}({args[1:]}, {kwargs})") + raise ClickException("API didn't respond properly, script will abort now. Please try again later.") seconds = 1 + count count += 1 - logger.warning( - f"{e} for {fn.__module__}.{fn.__name__}(), retrying after {seconds} seconds (try: {count}/{retries})" - ) + logger.warning(f"{e} for {fn.__module__}.{fn.__name__}(), retrying after {seconds} seconds (try: {count}/{retries})") sleep(seconds) diff --git a/plextraktsync/logger/init.py b/plextraktsync/logger/init.py index 06c1b91e3a..48a95def31 100644 --- a/plextraktsync/logger/init.py +++ b/plextraktsync/logger/init.py @@ -19,9 +19,7 @@ def initialize(config): # file handler can log down to debug messages mode = "a" if config.log_append else "w" file_handler = logging.FileHandler(config.log_file, mode, "utf-8") - file_handler.setFormatter( - CustomFormatter("%(asctime)-15s %(levelname)s[%(name)s]:%(message)s") - ) + file_handler.setFormatter(CustomFormatter("%(asctime)-15s %(levelname)s[%(name)s]:%(message)s")) file_handler.setLevel(logging.DEBUG) handlers = [ diff --git a/plextraktsync/media/Media.py b/plextraktsync/media/Media.py index 1b60bb1ce8..a0fac9ce4f 100644 --- a/plextraktsync/media/Media.py +++ b/plextraktsync/media/Media.py @@ -139,14 +139,10 @@ def is_collected(self): if self.is_movie: return self.trakt_id in self.trakt_api.movie_collection_set elif not self.is_episode: - raise RuntimeError( - f"is_collected: Unsupported media type: {self.media_type}" - ) + raise RuntimeError(f"is_collected: Unsupported media type: {self.media_type}") collected = self.trakt_api.collected_shows - return collected.is_collected( - self.show_trakt_id, self.season_number, self.episode_number - ) + return collected.is_collected(self.show_trakt_id, self.season_number, self.episode_number) def add_to_collection(self): self.trakt_api.add_to_collection(self.trakt, self.plex) @@ -182,14 +178,10 @@ def watched_on_trakt(self): if self.is_movie: return self.trakt_id in self.trakt_api.watched_movies elif not self.is_episode: - raise RuntimeError( - f"watched_on_trakt: Unsupported media type: {self.media_type}" - ) + raise RuntimeError(f"watched_on_trakt: Unsupported media type: {self.media_type}") watched = self.trakt_api.watched_shows - return watched.get_completed( - self.show_trakt_id, self.season_number, self.episode_number - ) + return watched.get_completed(self.show_trakt_id, self.season_number, self.episode_number) @property def watched_before_reset(self): @@ -199,30 +191,21 @@ def watched_before_reset(self): if not self.is_episode: raise RuntimeError("watched_before_reset is valid for episodes only") - return ( - self.show_reset_at - and self.plex.seen_date.replace(tzinfo=None) < self.show_reset_at - ) + return self.show_reset_at and self.plex.seen_date.replace(tzinfo=None) < self.show_reset_at def reset_show(self): """ Mark unwatched all Plex episodes played before the show reset date. """ - self.plex_api.reset_show( - show=self.plex.item.show(), reset_date=self.show_reset_at - ) + self.plex_api.reset_show(show=self.plex.item.show(), reset_date=self.show_reset_at) def mark_watched_trakt(self): if self.is_movie: self.trakt_api.mark_watched(self.trakt, self.plex.seen_date) elif self.is_episode: - self.trakt_api.mark_watched( - self.trakt, self.plex.seen_date, self.show_trakt_id - ) + self.trakt_api.mark_watched(self.trakt, self.plex.seen_date, self.show_trakt_id) else: - raise RuntimeError( - f"mark_watched_trakt: Unsupported media type: {self.media_type}" - ) + raise RuntimeError(f"mark_watched_trakt: Unsupported media type: {self.media_type}") def mark_watched_plex(self): self.plex_api.mark_watched(self.plex.item) diff --git a/plextraktsync/media/MediaFactory.py b/plextraktsync/media/MediaFactory.py index bd37b8c67b..8d856241ee 100644 --- a/plextraktsync/media/MediaFactory.py +++ b/plextraktsync/media/MediaFactory.py @@ -91,17 +91,11 @@ def resolve_trakt(self, tm: TraktItem) -> Media: def make_media(self, plex: PlexLibraryItem, trakt): return Media(plex, trakt, plex_api=self.plex, trakt_api=self.trakt, mf=self) - def _guid_match( - self, candidates: list[PlexLibraryItem], tm: TraktItem - ) -> PlexLibraryItem | None: + def _guid_match(self, candidates: list[PlexLibraryItem], tm: TraktItem) -> PlexLibraryItem | None: if candidates: for pm in candidates: for guid in pm.guids: for provider in ["tmdb", "imdb", "tvdb"]: - if ( - guid.provider == provider - and hasattr(tm.item, provider) - and guid.id == str(getattr(tm.item, provider)) - ): + if guid.provider == provider and hasattr(tm.item, provider) and guid.id == str(getattr(tm.item, provider)): return pm return None diff --git a/plextraktsync/plan/WalkPlanner.py b/plextraktsync/plan/WalkPlanner.py index 81afc46b65..9c054eb9b2 100644 --- a/plextraktsync/plan/WalkPlanner.py +++ b/plextraktsync/plan/WalkPlanner.py @@ -19,9 +19,7 @@ def plan(self): movie_sections, show_sections = self.find_sections() movies, shows, episodes = self.find_by_id(movie_sections, show_sections) shows = self.find_from_sections_by_title(show_sections, self.config.show, shows) - movies = self.find_from_sections_by_title( - movie_sections, self.config.movie, movies - ) + movies = self.find_from_sections_by_title(movie_sections, self.config.movie, movies) # reset sections if movie/shows have been picked if movies or shows or episodes: @@ -42,18 +40,10 @@ def find_by_id(self, movie_sections, show_sections): results = defaultdict(list) for id in self.config.id: - found = ( - self.find_from_sections_by_id(show_sections, id, results) - if self.config.walk_shows - else None - ) + found = self.find_from_sections_by_id(show_sections, id, results) if self.config.walk_shows else None if found: continue - found = ( - self.find_from_sections_by_id(movie_sections, id, results) - if self.config.walk_movies - else None - ) + found = self.find_from_sections_by_id(movie_sections, id, results) if self.config.walk_movies else None if found: continue raise RuntimeError(f"Id '{id}' not found") @@ -110,24 +100,18 @@ def find_sections(self): :return: [movie_sections, show_sections] """ if not self.config.library: - movie_sections = ( - self.plex.movie_sections() if self.config.walk_movies else [] - ) + movie_sections = self.plex.movie_sections() if self.config.walk_movies else [] show_sections = self.plex.show_sections() if self.config.walk_shows else [] return [movie_sections, show_sections] movie_sections = [] show_sections = [] for library in self.config.library: - movie_section = ( - self.plex.movie_sections(library) if self.config.walk_movies else [] - ) + movie_section = self.plex.movie_sections(library) if self.config.walk_movies else [] if movie_section: movie_sections.extend(movie_section) continue - show_section = ( - self.plex.show_sections(library) if self.config.walk_shows else [] - ) + show_section = self.plex.show_sections(library) if self.config.walk_shows else [] if show_section: show_sections.extend(show_section) continue diff --git a/plextraktsync/plan/Walker.py b/plextraktsync/plan/Walker.py index 0f33a48a36..5416f15948 100644 --- a/plextraktsync/plan/Walker.py +++ b/plextraktsync/plan/Walker.py @@ -148,9 +148,7 @@ async def walk_shows(self, shows: set[Media], title="Processing Shows"): async for show in self.progressbar(shows, desc=title): yield show - async def get_plex_episodes( - self, episodes: list[Episode] - ) -> Generator[Media, Any, None]: + async def get_plex_episodes(self, episodes: list[Episode]) -> Generator[Media, Any, None]: it = self.progressbar(episodes, desc="Processing episodes") async for pe in it: guid = PlexGuid(pe.grandparentGuid, "show") @@ -164,13 +162,9 @@ async def get_plex_episodes( me.show = show yield me - async def media_from_sections( - self, sections: list[PlexLibrarySection] - ) -> AsyncGenerator[PlexLibraryItem, Any, None]: + async def media_from_sections(self, sections: list[PlexLibrarySection]) -> AsyncGenerator[PlexLibraryItem, Any, None]: for section in sections: - with measure_time( - f"{section.title_link} processed", extra={"markup": True} - ): + with measure_time(f"{section.title_link} processed", extra={"markup": True}): self.set_window_title(f"Processing {section.title}") it = self.progressbar( section.pager(), @@ -179,13 +173,9 @@ async def media_from_sections( async for m in it: yield m - async def episodes_from_sections( - self, sections: list[PlexLibrarySection] - ) -> Generator[PlexLibraryItem, Any, None]: + async def episodes_from_sections(self, sections: list[PlexLibrarySection]) -> Generator[PlexLibraryItem, Any, None]: for section in sections: - with measure_time( - f"{section.title_link} processed", extra={"markup": True} - ): + with measure_time(f"{section.title_link} processed", extra={"markup": True}): self.set_window_title(f"Processing {section.title}") it = self.progressbar( section.pager("episode"), @@ -194,9 +184,7 @@ async def episodes_from_sections( async for m in it: yield m - async def media_from_items( - self, libtype: str, items: list - ) -> AsyncGenerator[PlexLibraryItem, Any, None]: + async def media_from_items(self, libtype: str, items: list) -> AsyncGenerator[PlexLibraryItem, Any, None]: it = self.progressbar(items, desc=f"Processing {libtype}s") async for m in it: yield PlexLibraryItem(m, plex=self.plex) @@ -220,18 +208,14 @@ async def progressbar(self, iterable: AsyncIterable | Iterable, **kwargs): for m in iterable: yield m - async def media_from_traktlist( - self, items: TraktWatchList, title="Trakt watchlist" - ) -> Generator[Media, Any, None]: + async def media_from_traktlist(self, items: TraktWatchList, title="Trakt watchlist") -> Generator[Media, Any, None]: it = self.progressbar(items, desc=f"Processing {title}") async for media in it: tm = TraktItem(media) m = self.mf.resolve_trakt(tm) yield m - async def media_from_plexlist( - self, items: PlexWatchList - ) -> Generator[Media, Any, None]: + async def media_from_plexlist(self, items: PlexWatchList) -> Generator[Media, Any, None]: it = self.progressbar(items, desc="Processing Plex watchlist") async for media in it: pm = PlexLibraryItem(media, plex=self.plex) diff --git a/plextraktsync/plex/PlexApi.py b/plextraktsync/plex/PlexApi.py index 1c82548276..b604bc50f0 100644 --- a/plextraktsync/plex/PlexApi.py +++ b/plextraktsync/plex/PlexApi.py @@ -45,9 +45,7 @@ def __str__(self): return str(self.server) def plex_base_url(self, section="server"): - return ( - f"https://app.plex.tv/desktop/#!/{section}/{self.server.machineIdentifier}" - ) + return f"https://app.plex.tv/desktop/#!/{section}/{self.server.machineIdentifier}" @property def plex_discover_base_url(self): @@ -91,16 +89,8 @@ def fetch_item(self, key: int | str | PlexId) -> PlexLibraryItem | None: return PlexLibraryItem(media, plex=self) def media_url(self, m: PlexLibraryItem, discover=False): - base_url = ( - self.plex_discover_base_url - if m.is_discover or discover - else self.plex_base_url("server") - ) - key = ( - f"/library/metadata/{m.item.guid.rsplit('/', 1)[-1]}" - if discover - else m.item.key - ) + base_url = self.plex_discover_base_url if m.is_discover or discover else self.plex_base_url("server") + key = f"/library/metadata/{m.item.guid.rsplit('/', 1)[-1]}" if discover else m.item.key return f"{base_url}/details?key={key}" @@ -161,9 +151,7 @@ def library_sections(self) -> dict[int, PlexLibrarySection]: if enabled_libraries is not None: excluded_libraries = self.config.excluded_libraries or [] else: - excluded_libraries = factory.config["excluded-libraries"] + ( - self.config.excluded_libraries or [] - ) + excluded_libraries = factory.config["excluded-libraries"] + (self.config.excluded_libraries or []) for section in self.server.library.sections(): if enabled_libraries is not None: @@ -235,9 +223,7 @@ def account(self): def try_login(): if plex_owner_token: - plex_owner_account = MyPlexAccount( - token=plex_owner_token, session=factory.session - ) + plex_owner_account = MyPlexAccount(token=plex_owner_token, session=factory.session) return plex_owner_account.switchHomeUser(plex_username) elif plex_account_token: return MyPlexAccount(token=plex_account_token, session=factory.session) @@ -247,9 +233,7 @@ def try_login(): try: return try_login() except BadRequest as e: - self.logger.error( - f"Error during Plex {plex_username} account access: {e}. Try log in with plex-login again" - ) + self.logger.error(f"Error during Plex {plex_username} account access: {e}. Try log in with plex-login again") return None @@ -265,9 +249,7 @@ def watchlist(self, libtype=None) -> list[Movie | Show] | None: try: return self.account.watchlist(libtype=libtype, **params) except BadRequest as e: - self.logger.error( - f"Error during {self.account.username} watchlist access: {e}" - ) + self.logger.error(f"Error during {self.account.username} watchlist access: {e}") return None def add_to_watchlist(self, item): @@ -280,9 +262,7 @@ def remove_from_watchlist(self, item): try: self.account.removeFromWatchlist(item) except BadRequest as e: - self.logger.error( - f"Error when removing {item.title} from Plex watchlist: {e}" - ) + self.logger.error(f"Error when removing {item.title} from Plex watchlist: {e}") @retry() def search_online(self, title: str, media_type: str): @@ -305,9 +285,5 @@ def reset_show(self, show: Show, reset_date: datetime): self.mark_unwatched(ep) reset_count += 1 else: - self.logger.debug( - f"{show.title} {ep.seasonEpisode} watched at {ep.lastViewedAt} after reset date {reset_date}" - ) - self.logger.debug( - f"{show.title}: {reset_count} Plex episode(s) marked as unwatched." - ) + self.logger.debug(f"{show.title} {ep.seasonEpisode} watched at {ep.lastViewedAt} after reset date {reset_date}") + self.logger.debug(f"{show.title}: {reset_count} Plex episode(s) marked as unwatched.") diff --git a/plextraktsync/plex/PlexAudioCodec.py b/plextraktsync/plex/PlexAudioCodec.py index 14e4799383..1b9a4971f3 100644 --- a/plextraktsync/plex/PlexAudioCodec.py +++ b/plextraktsync/plex/PlexAudioCodec.py @@ -39,7 +39,5 @@ def audio_codecs(self): try: codecs[k] = re.compile(v, re.IGNORECASE) except Exception: - raise RuntimeError( - "Unable to compile regex pattern: %r", v, exc_info=True - ) + raise RuntimeError("Unable to compile regex pattern: %r", v, exc_info=True) return codecs diff --git a/plextraktsync/plex/PlexLibraryItem.py b/plextraktsync/plex/PlexLibraryItem.py index 09cfc4ac49..3fb6b9dbf3 100644 --- a/plextraktsync/plex/PlexLibraryItem.py +++ b/plextraktsync/plex/PlexLibraryItem.py @@ -330,13 +330,9 @@ def _get_episodes(self): show_id = self.item.parentRatingKey season = self.item.seasonNumber - return self.library.search( - libtype="episode", filters={"show.id": show_id, "season.index": season} - ) + return self.library.search(libtype="episode", filters={"show.id": show_id, "season.index": season}) - return self.library.search( - libtype="episode", filters={"show.id": self.item.ratingKey} - ) + return self.library.search(libtype="episode", filters={"show.id": self.item.ratingKey}) @cached_property def season_number(self): diff --git a/plextraktsync/plex/PlexRatings.py b/plextraktsync/plex/PlexRatings.py index f7f1a7860c..70799fe969 100644 --- a/plextraktsync/plex/PlexRatings.py +++ b/plextraktsync/plex/PlexRatings.py @@ -54,7 +54,5 @@ def ratings(section: PlexLibrarySection, media_type: str): ] } - for item in section.search( - filters=filters, libtype=libtype, includeGuids=False - ): + for item in section.search(filters=filters, libtype=libtype, includeGuids=False): yield item.ratingKey, Rating.create(item.userRating, item.lastRatedAt) diff --git a/plextraktsync/plex/PlexSectionPager.py b/plextraktsync/plex/PlexSectionPager.py index 474a379911..643cbcbcc2 100644 --- a/plextraktsync/plex/PlexSectionPager.py +++ b/plextraktsync/plex/PlexSectionPager.py @@ -13,9 +13,7 @@ class PlexSectionPager: - def __init__( - self, section: ShowSection | MovieSection, plex: PlexApi, libtype: str = None - ): + def __init__(self, section: ShowSection | MovieSection, plex: PlexApi, libtype: str = None): self.section = section self.plex = plex self.libtype = libtype if libtype is not None else section.TYPE @@ -25,9 +23,7 @@ def __len__(self): @cached_property def total_size(self): - return self.section.totalViewSize( - libtype=self.libtype, includeCollections=False - ) + return self.section.totalViewSize(libtype=self.libtype, includeCollections=False) @retry() def fetch_items(self, start: int, size: int): diff --git a/plextraktsync/plex/PlexServerConnection.py b/plextraktsync/plex/PlexServerConnection.py index 7cf36e2c07..0293accf4d 100644 --- a/plextraktsync/plex/PlexServerConnection.py +++ b/plextraktsync/plex/PlexServerConnection.py @@ -40,13 +40,9 @@ def connect(self, urls: list[str], token: str): # 2. url without ssl # 3. local url (localhost) for url in urls: - self.logger.info( - f"Connecting with url: {url}, timeout {self.timeout} seconds" - ) + self.logger.info(f"Connecting with url: {url}, timeout {self.timeout} seconds") try: - return PlexServer( - baseurl=url, token=token, session=self.session, timeout=self.timeout - ) + return PlexServer(baseurl=url, token=token, session=self.session, timeout=self.timeout) except SSLError as e: self.logger.error(e) message = str(e.__context__) @@ -62,9 +58,7 @@ def connect(self, urls: list[str], token: str): # ) if "doesn't match '*." in message and ".plex.direct" in url: url = self.extract_plex_direct(url, message) - self.logger.warning( - f"Adding rewritten plex.direct url to connect with: {url}" - ) + self.logger.warning(f"Adding rewritten plex.direct url to connect with: {url}") urls.append(url) continue @@ -75,9 +69,7 @@ def connect(self, urls: list[str], token: str): # 2. if url and url[:5] == "https": url = url.replace("https", "http") - self.logger.warning( - f"Adding rewritten http url to connect with: {url}" - ) + self.logger.warning(f"Adding rewritten http url to connect with: {url}") urls.append(url) continue except Unauthorized as e: diff --git a/plextraktsync/pytrakt_extensions.py b/plextraktsync/pytrakt_extensions.py index 26fb6b42d7..d4e0d68f89 100644 --- a/plextraktsync/pytrakt_extensions.py +++ b/plextraktsync/pytrakt_extensions.py @@ -135,9 +135,7 @@ def add(self, trakt_id, season, episode): season_prog = {"number": season, "episodes": [episode_prog]} if trakt_id in self.shows: if season in self.shows[trakt_id].seasons: - self.shows[trakt_id].seasons[season].episodes[episode] = ( - EpisodeProgress(**episode_prog) - ) + self.shows[trakt_id].seasons[season].episodes[episode] = EpisodeProgress(**episode_prog) else: self.shows[trakt_id].seasons[season] = SeasonProgress(**season_prog) else: diff --git a/plextraktsync/queue/Queue.py b/plextraktsync/queue/Queue.py index 04111d9ba0..1aa342b880 100644 --- a/plextraktsync/queue/Queue.py +++ b/plextraktsync/queue/Queue.py @@ -47,9 +47,7 @@ def add_queue(self, queue: str, data: Any): def start_daemon(self, runner): from threading import Thread - daemon = Thread( - target=runner, args=(self.queue,), daemon=True, name="BackgroundTask" - ) + daemon = Thread(target=runner, args=(self.queue,), daemon=True, name="BackgroundTask") daemon.start() return daemon diff --git a/plextraktsync/rich/RichProgressBar.py b/plextraktsync/rich/RichProgressBar.py index 49b76b3809..aacc57fc55 100644 --- a/plextraktsync/rich/RichProgressBar.py +++ b/plextraktsync/rich/RichProgressBar.py @@ -73,8 +73,7 @@ def progress(self): from tqdm.rich import FractionColumn, RateColumn args = ( - "[progress.description]{task.description}" - "[progress.percentage]{task.percentage:>4.0f}%", + "[progress.description]{task.description}" "[progress.percentage]{task.percentage:>4.0f}%", BarColumn(bar_width=None), FractionColumn( unit_scale=False, diff --git a/plextraktsync/sync/AddCollectionPlugin.py b/plextraktsync/sync/AddCollectionPlugin.py index d34984d51d..e49866f93c 100644 --- a/plextraktsync/sync/AddCollectionPlugin.py +++ b/plextraktsync/sync/AddCollectionPlugin.py @@ -32,9 +32,7 @@ async def sync_collection(self, m: Media, dry_run: bool): if m.is_collected: return - self.logger.info( - f"Adding to Trakt collection: {m.title_link}", extra={"markup": True} - ) + self.logger.info(f"Adding to Trakt collection: {m.title_link}", extra={"markup": True}) if not dry_run: m.add_to_collection() diff --git a/plextraktsync/sync/ClearCollectedPlugin.py b/plextraktsync/sync/ClearCollectedPlugin.py index 0e5f53787c..f49f7f88ef 100644 --- a/plextraktsync/sync/ClearCollectedPlugin.py +++ b/plextraktsync/sync/ClearCollectedPlugin.py @@ -39,12 +39,8 @@ def init(self, pm: SyncPluginManager, is_partial: bool): @hookimpl async def fini(self, dry_run: bool): - self.clear_collected( - self.trakt.movie_collection, self.movie_trakt_ids, dry_run=dry_run - ) - self.clear_collected( - self.trakt.episodes_collection, self.episode_trakt_ids, dry_run=dry_run - ) + self.clear_collected(self.trakt.movie_collection, self.movie_trakt_ids, dry_run=dry_run) + self.clear_collected(self.trakt.episodes_collection, self.episode_trakt_ids, dry_run=dry_run) @hookimpl async def walk_movie(self, movie: Media): @@ -54,9 +50,7 @@ async def walk_movie(self, movie: Media): async def walk_episode(self, episode: Media): self.episode_trakt_ids.add(episode.trakt_id) - def clear_collected( - self, existing_items: Iterable[TraktMedia], keep_ids: set[int], dry_run - ): + def clear_collected(self, existing_items: Iterable[TraktMedia], keep_ids: set[int], dry_run): from plextraktsync.trakt.trakt_set import trakt_set existing_ids = trakt_set(existing_items) diff --git a/plextraktsync/sync/LikedListsPlugin.py b/plextraktsync/sync/LikedListsPlugin.py index d4aec4e4e8..19c5cc1a3e 100644 --- a/plextraktsync/sync/LikedListsPlugin.py +++ b/plextraktsync/sync/LikedListsPlugin.py @@ -28,10 +28,7 @@ def factory(cls, sync: Sync): @hookimpl def init(self, sync: Sync, is_partial: bool, dry_run: bool): if is_partial or dry_run: - self.logger.warning( - "Partial walk, disabling liked lists updating. " - "Liked lists won't update because it needs full library sync." - ) + self.logger.warning("Partial walk, disabling liked lists updating. " "Liked lists won't update because it needs full library sync.") if is_partial: return diff --git a/plextraktsync/sync/SyncWatchedPlugin.py b/plextraktsync/sync/SyncWatchedPlugin.py index ab5209b459..35d84de9d1 100644 --- a/plextraktsync/sync/SyncWatchedPlugin.py +++ b/plextraktsync/sync/SyncWatchedPlugin.py @@ -42,9 +42,7 @@ async def sync_watched(self, m: Media, dry_run: bool): if m.is_episode and m.watched_before_reset: show = m.plex.item.show() - self.logger.info( - f"Show '{show.title}' has been reset in trakt at {m.show_reset_at}." - ) + self.logger.info(f"Show '{show.title}' has been reset in trakt at {m.show_reset_at}.") self.logger.info(f"Marking '{show.title}' as unwatched in Plex.") if not dry_run: m.reset_show() @@ -58,8 +56,6 @@ async def sync_watched(self, m: Media, dry_run: bool): elif m.watched_on_trakt: if not self.trakt_to_plex: return - self.logger.info( - f"Marking as watched in Plex: {m.title_link}", extra={"markup": True} - ) + self.logger.info(f"Marking as watched in Plex: {m.title_link}", extra={"markup": True}) if not dry_run: m.mark_watched_plex() diff --git a/plextraktsync/sync/WatchListPlugin.py b/plextraktsync/sync/WatchListPlugin.py index b3eb9b1042..e33d3cfce6 100644 --- a/plextraktsync/sync/WatchListPlugin.py +++ b/plextraktsync/sync/WatchListPlugin.py @@ -40,10 +40,7 @@ def init(self, sync: Sync, is_partial: bool): return if is_partial: - self.logger.warning( - "Running partial library sync. " - "Watchlist as playlist won't update because it needs full library sync." - ) + self.logger.warning("Running partial library sync. " "Watchlist as playlist won't update because it needs full library sync.") else: sync.trakt_lists.add_watchlist(self.trakt.watchlist_movies) @@ -54,10 +51,7 @@ async def fini(self, walker: Walker, dry_run: bool): await self.sync_watchlist(walker, dry_run=dry_run) if self.config.update_plex_wl_as_pl and dry_run: - self.logger.warning( - "Running partial library sync. " - "Liked lists won't update because it needs full library sync." - ) + self.logger.warning("Running partial library sync. " "Liked lists won't update because it needs full library sync.") @cached_property def plex_wl(self): @@ -116,9 +110,7 @@ def watchlist_sync_item(self, m: Media, dry_run: bool): del self.trakt_wl[m] elif m in self.trakt_wl: if self.config.update_plex_wl: - self.logger.info( - f"Adding {m.title_link} to Plex watchlist", extra={"markup": True} - ) + self.logger.info(f"Adding {m.title_link} to Plex watchlist", extra={"markup": True}) if not dry_run: m.add_to_plex_watchlist() else: diff --git a/plextraktsync/sync/plugin/SyncPluginManager.py b/plextraktsync/sync/plugin/SyncPluginManager.py index 97c783dbda..4cc83cd520 100644 --- a/plextraktsync/sync/plugin/SyncPluginManager.py +++ b/plextraktsync/sync/plugin/SyncPluginManager.py @@ -62,11 +62,7 @@ def register_plugins(self, sync: Sync): self.logger.info(f"Enable sync plugin '{plugin.__name__}': {enabled}") if not enabled: continue - with measure_time( - f"Created '{plugin.__name__}' plugin", logger=self.logger.debug - ): + with measure_time(f"Created '{plugin.__name__}' plugin", logger=self.logger.debug): p = plugin.factory(sync) - with measure_time( - f"Registered '{plugin.__name__}' plugin", logger=self.logger.debug - ): + with measure_time(f"Registered '{plugin.__name__}' plugin", logger=self.logger.debug): self.pm.register(p) diff --git a/plextraktsync/trakt/TraktApi.py b/plextraktsync/trakt/TraktApi.py index 306fc28991..dc2a636fb6 100644 --- a/plextraktsync/trakt/TraktApi.py +++ b/plextraktsync/trakt/TraktApi.py @@ -76,9 +76,7 @@ def liked_lists(self) -> list[TraktLikedList]: # Skip private lists # https://github.com/Taxel/PlexTraktSync/issues/1864#issuecomment-2018171311 if item["list"]["privacy"] == "private": - self.logger.warning( - f"Skipping private list: {item['list']['name']} - {item['list']['share_link']}" - ) + self.logger.warning(f"Skipping private list: {item['list']['name']} - {item['list']['share_link']}") continue tll: TraktLikedList = { "listname": item["list"]["name"], @@ -239,9 +237,7 @@ def remove_from_watchlist(self, m): self.queue.remove_from_watchlist((m.media_type, item)) def find_by_episode_guid(self, guid: PlexGuid): - ts: TVShow = self.search_by_id( - guid.show_id, id_type=guid.provider, media_type="show" - ) + ts: TVShow = self.search_by_id(guid.show_id, id_type=guid.provider, media_type="show") if not ts: return None @@ -285,16 +281,12 @@ def find_by_slug(slug: str, media_type: str): @rate_limit() @retry() - def search_by_id( - self, media_id: str, id_type: str, media_type: str - ) -> TVShow | Movie | None: + def search_by_id(self, media_id: str, id_type: str, media_type: str) -> TVShow | Movie | None: if id_type == "tvdb" and media_type == "movie": # Skip invalid search. # The Trakt API states that tvdb is only for shows and episodes: # https://trakt.docs.apiary.io/#reference/search/id-lookup/get-id-lookup-results - self.logger.debug( - f"search_by_id: tvdb does not support movie provider, skip {id_type}/{media_type}/{media_id}" - ) + self.logger.debug(f"search_by_id: tvdb does not support movie provider, skip {id_type}/{media_type}/{media_id}") return None if media_type == "season": # Search by season is missing @@ -307,16 +299,12 @@ def search_by_id( return None - search = trakt.sync.search_by_id( - media_id, id_type=id_type, media_type=media_type - ) + search = trakt.sync.search_by_id(media_id, id_type=id_type, media_type=media_type) if not search: return None if len(search) > 1: - self.logger.debug( - f"search_by_id({media_id}, {id_type}, {media_type}) got {len(search)} results, taking first one" - ) + self.logger.debug(f"search_by_id({media_id}, {id_type}, {media_type}) got {len(search)} results, taking first one") self.logger.debug([pm.to_json() for pm in search]) # TODO: sort by "score"? diff --git a/plextraktsync/trakt/TraktItem.py b/plextraktsync/trakt/TraktItem.py index 15d8448e4d..8fed150c2e 100644 --- a/plextraktsync/trakt/TraktItem.py +++ b/plextraktsync/trakt/TraktItem.py @@ -21,8 +21,4 @@ def type(self): @property def guids(self): - return { - k: v - for k, v in self.item.ids["ids"].items() - if k in ["imdb", "tmdb", "tvdb"] - } + return {k: v for k, v in self.item.ids["ids"].items() if k in ["imdb", "tmdb", "tvdb"]} diff --git a/plextraktsync/trakt/TraktLookup.py b/plextraktsync/trakt/TraktLookup.py index 17a7c040fc..56f51f1448 100644 --- a/plextraktsync/trakt/TraktLookup.py +++ b/plextraktsync/trakt/TraktLookup.py @@ -6,10 +6,7 @@ from plextraktsync.decorators.retry import retry from plextraktsync.factory import logging -EPISODES_ORDERING_WARNING = ( - "episodes ordering is different in Plex and Trakt. " - "Check your Plex media source, TMDB is recommended." -) +EPISODES_ORDERING_WARNING = "episodes ordering is different in Plex and Trakt. " "Check your Plex media source, TMDB is recommended." if TYPE_CHECKING: from trakt.tv import TVEpisode, TVShow diff --git a/plextraktsync/trakt/TraktUserList.py b/plextraktsync/trakt/TraktUserList.py index 40001e37ef..0c8fcc13f1 100644 --- a/plextraktsync/trakt/TraktUserList.py +++ b/plextraktsync/trakt/TraktUserList.py @@ -51,17 +51,11 @@ def items(self): @staticmethod def build_dict(pl: PublicList): - return { - (f"{le.type}s", le.trakt): le.rank - for le in pl - if le.type in ["movie", "episode"] - } + return {(f"{le.type}s", le.trakt): le.rank for le in pl if le.type in ["movie", "episode"]} def load_items(self): pl = PublicList.load(self.trakt_id) - self.logger.info( - f"Downloaded Trakt list '{pl.name}' ({len(pl)} items): {pl.share_link}" - ) + self.logger.info(f"Downloaded Trakt list '{pl.name}' ({len(pl)} items): {pl.share_link}") return pl.description, self.build_dict(pl) @@ -71,9 +65,7 @@ def from_trakt_list(cls, list_id: int, list_name: str, keep_watched: bool): @classmethod def from_watchlist(cls, items: list[TraktPlayable]): - trakt_items = dict( - zip([(elem.media_type, elem.trakt) for elem in items], count(1)) - ) + trakt_items = dict(zip([(elem.media_type, elem.trakt) for elem in items], count(1))) return cls(name="Trakt Watchlist", items=trakt_items) @cached_property @@ -110,9 +102,7 @@ def add(self, m: Media): ) # Report duplicates - duplicates = [ - p for _, p in self.plex_items if p.key != m.plex_key and p == m.plex - ] + duplicates = [p for _, p in self.plex_items if p.key != m.plex_key and p == m.plex] for p in duplicates: msg = f"Duplicate {p.title_link} #{p.key} with {m.title_link} #{m.plex_key}" if p.edition_title is not None: @@ -134,11 +124,7 @@ def plex_items_sorted(self): if len(self.plex_items) == 0: return [] - plex_items = [ - (r, p.item) - for (r, p) in self.plex_items - if self.keep_watched or (not self.keep_watched and not p.is_watched) - ] + plex_items = [(r, p.item) for (r, p) in self.plex_items if self.keep_watched or (not self.keep_watched and not p.is_watched)] if len(plex_items) == 0: return [] diff --git a/plextraktsync/util/Factory.py b/plextraktsync/util/Factory.py index 74906c0c01..e066fd9833 100644 --- a/plextraktsync/util/Factory.py +++ b/plextraktsync/util/Factory.py @@ -322,6 +322,4 @@ def queue(self): def batch_delay_timer(self): from plextraktsync.util.Timer import Timer - return ( - Timer(self.run_config.batch_delay) if self.run_config.batch_delay else None - ) + return Timer(self.run_config.batch_delay) if self.run_config.batch_delay else None diff --git a/plextraktsync/util/execx.py b/plextraktsync/util/execx.py index 2d23c454a9..09a5275e10 100644 --- a/plextraktsync/util/execx.py +++ b/plextraktsync/util/execx.py @@ -7,7 +7,5 @@ def execx(command: str | list[str]): if isinstance(command, str): command = command.split(" ") - process = subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL - ) + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) return process.communicate()[0] diff --git a/plextraktsync/util/local_url.py b/plextraktsync/util/local_url.py index 4a360fd87d..260fca6519 100644 --- a/plextraktsync/util/local_url.py +++ b/plextraktsync/util/local_url.py @@ -19,13 +19,7 @@ def local_url(port=32400): try: from subprocess import check_output - host_ip = ( - check_output( - "ip -4 route show default | awk '{ print $3 }'", shell=True - ) - .decode() - .rstrip() - ) + host_ip = check_output("ip -4 route show default | awk '{ print $3 }'", shell=True).decode().rstrip() except Exception: host_ip = "172.17.0.1" diff --git a/plextraktsync/watch/ProgressBar.py b/plextraktsync/watch/ProgressBar.py index da522d9cbf..687f850f52 100644 --- a/plextraktsync/watch/ProgressBar.py +++ b/plextraktsync/watch/ProgressBar.py @@ -58,15 +58,11 @@ def __missing__(self, m: PlexLibraryItem): def play(self, m: PlexLibraryItem, progress: float): task_id = self[m] - self.progress.update( - task_id, completed=progress, play_state=self.ICONS["playing"] - ) + self.progress.update(task_id, completed=progress, play_state=self.ICONS["playing"]) def pause(self, m: PlexLibraryItem, progress: float): task_id = self[m] - self.progress.update( - task_id, completed=progress, play_state=self.ICONS["paused"] - ) + self.progress.update(task_id, completed=progress, play_state=self.ICONS["paused"]) def stop(self, m: PlexLibraryItem): task_id = self[m] diff --git a/plextraktsync/watch/WatchStateUpdater.py b/plextraktsync/watch/WatchStateUpdater.py index 408376d544..25e521881b 100644 --- a/plextraktsync/watch/WatchStateUpdater.py +++ b/plextraktsync/watch/WatchStateUpdater.py @@ -48,9 +48,7 @@ def username_filter(self): # This must be username, not email return self.plex.account.username - self.logger.warning( - "No permission to access sessions, disabling username filter" - ) + self.logger.warning("No permission to access sessions, disabling username filter") return None @cached_property @@ -79,9 +77,7 @@ def sessions(self): def scrobblers(self): from plextraktsync.trakt.ScrobblerCollection import ScrobblerCollection - return ScrobblerCollection( - self.trakt, self.config["watch"]["scrobble_threshold"] - ) + return ScrobblerCollection(self.trakt, self.config["watch"]["scrobble_threshold"]) @lru_cache(maxsize=2) def fetch_item(self, key: str): @@ -124,15 +120,11 @@ def server(self): return self.plex.server def on_start(self, event: ServerStarted): - self.logger.info( - f"Server connected: {event.server.friendlyName} ({event.server.version})" - ) + self.logger.info(f"Server connected: {event.server.friendlyName} ({event.server.version})") self.reset_title() def reset_title(self): - self.set_window_title( - f"watch: {self.server.friendlyName} ({self.server.version})" - ) + self.set_window_title(f"watch: {self.server.friendlyName} ({self.server.version})") def on_error(self, error: Error): self.logger.error(error.msg) @@ -147,9 +139,7 @@ def on_activity(self, activity: ActivityNotification): m = self.find_by_key(activity.key, reload=True) if not m: return - self.logger.info( - f"on_activity: {m}: Collected: {m.is_collected}, Watched: [Plex: {m.watched_on_plex}, Trakt: {m.watched_on_trakt}]" - ) + self.logger.info(f"on_activity: {m}: Collected: {m.is_collected}, Watched: [Plex: {m.watched_on_plex}, Trakt: {m.watched_on_trakt}]") if self.add_collection and not m.is_collected: self.logger.info(f"on_activity: Add {activity.key} to collection: {m}") @@ -180,9 +170,7 @@ def on_play(self, event: PlaySessionStateNotification): movie = m.plex.item percent = m.plex.watch_progress(event.view_offset) - self.logger.info( - f"on_play: {movie}: {percent:.6F}%, State: {event.state}, Played: {movie.isPlayed}, LastViewed: {movie.lastViewedAt}" - ) + self.logger.info(f"on_play: {movie}: {percent:.6F}%, State: {event.state}, Played: {movie.isPlayed}, LastViewed: {movie.lastViewedAt}") scrobbled = self.scrobble(m, percent, event) self.logger.debug(f"Scrobbled: {scrobbled}") diff --git a/plextraktsync/watch/WebSocketListener.py b/plextraktsync/watch/WebSocketListener.py index 2acdf85bb0..fe6a274e2b 100644 --- a/plextraktsync/watch/WebSocketListener.py +++ b/plextraktsync/watch/WebSocketListener.py @@ -26,16 +26,12 @@ def on(self, event_type, listener, **kwargs): def listen(self): self.logger.info("Listening for events!") while True: - notifier = self.plex.startAlertListener( - callback=self.dispatcher.event_handler - ) + notifier = self.plex.startAlertListener(callback=self.dispatcher.event_handler) self.dispatcher.event_handler(ServerStarted(notifier=notifier)) while notifier.is_alive(): sleep(self.poll_interval) self.dispatcher.event_handler(Error(msg="Server closed connection")) - self.logger.error( - f"Listener finished. Restarting in {self.restart_interval} seconds" - ) + self.logger.error(f"Listener finished. Restarting in {self.restart_interval} seconds") sleep(self.restart_interval) diff --git a/tests/test_events.py b/tests/test_events.py index 58bad9802e..4a2c6aef73 100755 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -35,23 +35,17 @@ def test_event_dispatcher(): raw_events = load_mock("events-played.json") events = [] - dispatcher = EventDispatcher().on( - ActivityNotification, lambda x: events.append(x), event=["ended"] - ) + dispatcher = EventDispatcher().on(ActivityNotification, lambda x: events.append(x), event=["ended"]) dispatcher.event_handler(raw_events[4]) assert len(events) == 1, "Matched event=ended" events = [] - dispatcher = EventDispatcher().on( - ActivityNotification, lambda x: events.append(x), progress=100 - ) + dispatcher = EventDispatcher().on(ActivityNotification, lambda x: events.append(x), progress=100) dispatcher.event_handler(raw_events[4]) assert len(events) == 1, "Test property progress=100" events = [] - dispatcher = EventDispatcher().on( - ActivityNotification, lambda x: events.append(x), event=["ended"], progress=100 - ) + dispatcher = EventDispatcher().on(ActivityNotification, lambda x: events.append(x), event=["ended"], progress=100) dispatcher.event_handler(raw_events[4]) assert len(events) == 1, "Matched event=ended and progress=100" @@ -76,8 +70,6 @@ def test_event_dispatcher(): assert len(events) == 0, "Matched progress=100 and event=started" events = [] - dispatcher = EventDispatcher().on( - ActivityNotification, lambda x: events.append(x), event=["ended"], progress=99 - ) + dispatcher = EventDispatcher().on(ActivityNotification, lambda x: events.append(x), event=["ended"], progress=99) dispatcher.event_handler(raw_events[4]) assert len(events) == 0, "No match for event=ended and progress=99" diff --git a/tests/test_plex_id.py b/tests/test_plex_id.py index c34f4d7978..c75d5450e5 100755 --- a/tests/test_plex_id.py +++ b/tests/test_plex_id.py @@ -25,9 +25,7 @@ def test_plex_id_numeric(): def test_plex_id_urls(): - pid = PlexIdFactory.create( - f"https://app.plex.tv/desktop/#!/server/{SERVER_ID}/details?key=%2Flibrary%2Fmetadata%2F13202" - ) + pid = PlexIdFactory.create(f"https://app.plex.tv/desktop/#!/server/{SERVER_ID}/details?key=%2Flibrary%2Fmetadata%2F13202") assert pid.key == 13202 assert pid.media_type is None assert pid.provider is None diff --git a/tests/test_tv_lookup.py b/tests/test_tv_lookup.py index 7c417cc587..8b982358ca 100755 --- a/tests/test_tv_lookup.py +++ b/tests/test_tv_lookup.py @@ -14,9 +14,7 @@ @pytest.mark.skip(reason="Broken in CI") def test_tv_lookup(): - m = PlexLibraryItem( - make(cls="plexapi.video.Show", guid="imdb://tt10584350", type="show") - ) + m = PlexLibraryItem(make(cls="plexapi.video.Show", guid="imdb://tt10584350", type="show")) guid = m.guids[0] tm: TVShow = trakt.find_by_guid(guid) lookup = TraktLookup(tm) @@ -27,9 +25,7 @@ def test_tv_lookup(): @pytest.mark.skip(reason="Broken in CI") def test_show_episodes_plex(): - m = PlexLibraryItem( - make(cls="plexapi.video.Show", guid="imdb://tt10584350", type="show") - ) + m = PlexLibraryItem(make(cls="plexapi.video.Show", guid="imdb://tt10584350", type="show")) guid = m.guids[0] show = trakt.find_by_guid(guid) From f37ff178163b8afa016745da7567076975acd37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 16:10:09 +0300 Subject: [PATCH 18/21] Linter: Enable pycodestyle, flake8-bugbear linters --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 6af9e66895..4508d83536 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,9 @@ line-length = 150 # https://docs.astral.sh/ruff/linter/#rule-selection # https://docs.astral.sh/ruff/settings/#lint_extend-select extend-select = [ + "E", # pycodestyle "F", # Pyflakes + "B", # flake8-bugbear "SIM", # flake8-simplify "I", # isort ] @@ -24,6 +26,8 @@ extend-ignore = [ "SIM103", # TODO "SIM108", + "B019", # Concious decision + "B904", # Haven't figured out which one to use ] [tool.ruff.lint.isort] From 50c6fed1a6c5838f74740ddcfa60bd918d1ba73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 16:12:30 +0300 Subject: [PATCH 19/21] UP007 Use `X | Y` for type annotations 35 | def make(cls=None, **kwargs) -> Union[TVShow]: | ^^^^^^^^^^^^^ UP007 36 | cls = cls if cls is not None else "object" 37 | # https://stackoverflow.com/a/2827726/2314626 | = help: Convert to `X | Y` --- tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d8f7577eba..6179cef285 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,6 @@ from os import environ from os.path import dirname from os.path import join as join_path -from typing import Union from trakt.tv import TVShow @@ -32,7 +31,7 @@ def load_mock(name: str): return json.load(f) -def make(cls=None, **kwargs) -> Union[TVShow]: +def make(cls=None, **kwargs) -> TVShow: cls = cls if cls is not None else "object" # https://stackoverflow.com/a/2827726/2314626 return type(cls, (object,), kwargs) From 99974c44c6328df805d8dca99fd80472b345a78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 16:13:17 +0300 Subject: [PATCH 20/21] UP028 Replace `yield` over `for` loop with `yield from` 30 | def limit_iterator(items, limit: int): 31 | if not limit or limit <= 0: 32 | for i, v in enumerate(items): | _________^ 33 | | yield i, v | |______________________^ UP028 34 | 35 | else: | = help: Replace with `yield from` --- plextraktsync/commands/cache.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plextraktsync/commands/cache.py b/plextraktsync/commands/cache.py index dc8454e1c8..c85a6ce5b9 100644 --- a/plextraktsync/commands/cache.py +++ b/plextraktsync/commands/cache.py @@ -29,8 +29,7 @@ def responses_by_url(session: CachedSession, url: str) -> Generator[CachedReques # https://stackoverflow.com/questions/36106712/how-can-i-limit-iterations-of-a-loop-in-python def limit_iterator(items, limit: int): if not limit or limit <= 0: - for i, v in enumerate(items): - yield i, v + yield from enumerate(items) else: yield from zip(range(limit), items) From 607d9b1975a47db615f7eebeb46f1a2a680368df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Mon, 8 Jul 2024 16:13:34 +0300 Subject: [PATCH 21/21] Linter: Enable pyupgrade ruff rules --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 4508d83536..4146e572e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ line-length = 150 extend-select = [ "E", # pycodestyle "F", # Pyflakes + "UP", # pyupgrade "B", # flake8-bugbear "SIM", # flake8-simplify "I", # isort