diff --git a/src/ytdl_sub/__init__.py b/src/ytdl_sub/__init__.py index 5b8ecdca..a1ae89f9 100644 --- a/src/ytdl_sub/__init__.py +++ b/src/ytdl_sub/__init__.py @@ -1,2 +1,2 @@ -__pypi_version__ = "2024.04.25.post1" -__local_version__ = "2024.04.25+aa1f6ea" +__pypi_version__ = "2023.10.22.post3" +__local_version__ = "2023.10.22+bfba4f0" diff --git a/src/ytdl_sub/cli/parsers/main.py b/src/ytdl_sub/cli/parsers/main.py index 3c31e900..99f6c231 100644 --- a/src/ytdl_sub/cli/parsers/main.py +++ b/src/ytdl_sub/cli/parsers/main.py @@ -150,8 +150,6 @@ parser.add_argument("-v", "--version", action="version", version="%(prog)s " + _ _add_shared_arguments(parser, suppress_defaults=False) subparsers = parser.add_subparsers(dest="subparser") - - ################################################################################################### # SUBSCRIPTION PARSER class SubArguments: diff --git a/src/ytdl_sub/config/validators/variable_validation.py b/src/ytdl_sub/config/validators/variable_validation.py index 4d5cefc6..466762d9 100644 --- a/src/ytdl_sub/config/validators/variable_validation.py +++ b/src/ytdl_sub/config/validators/variable_validation.py @@ -25,7 +25,7 @@ _DUMMY_ENTRY_VARIABLES: Dict[str, str] = { name: to_variable_dependency_format_string( # pylint: disable=protected-access script=BASE_SCRIPT, - parsed_format_string=BASE_SCRIPT._variables[name], + parsed_format_string=BASE_SCRIPT._variables[name] # pylint: enable=protected-access ) for name in BASE_SCRIPT.variable_names diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index 2b8d92ca..8d50f1c3 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -248,11 +248,9 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): self._download_ytdl_options_builder.clone() .add(self.ytdl_option_defaults(), before=True) .add( - ( - self.plugin_options.urls.list[url_idx].ytdl_options.dict - if url_idx is not None - else None - ), + self.plugin_options.urls.list[url_idx].ytdl_options.dict + if url_idx is not None + else None, before=True, ) .to_dict() @@ -354,11 +352,9 @@ class MultiUrlDownloader(SourcePlugin[MultiUrlValidator]): url_idx=entry.get(v.ytdl_sub_input_url_index, int) ), is_downloaded_fn=None if self.is_dry_run else entry.is_downloaded, - is_thumbnail_downloaded_fn=( - None - if (self.is_dry_run or not self.is_entry_thumbnails_enabled) - else entry.is_thumbnail_downloaded_via_ytdlp - ), + is_thumbnail_downloaded_fn=None + if (self.is_dry_run or not self.is_entry_thumbnails_enabled) + else entry.is_thumbnail_downloaded_via_ytdlp, url=entry.webpage_url, ) return Entry( diff --git a/src/ytdl_sub/utils/logger.py b/src/ytdl_sub/utils/logger.py index fcd734d5..62e40ba1 100644 --- a/src/ytdl_sub/utils/logger.py +++ b/src/ytdl_sub/utils/logger.py @@ -213,10 +213,9 @@ class Logger: @classmethod def _append_to_error_log(cls): # Any time an exception occurs, dump all debug logs into the error log - with ( - open(cls.debug_log_filename(), mode="r", encoding="utf-8") as debug_logs, - open(cls.error_log_filename(), mode="a", encoding="utf-8") as error_logs, - ): + with open(cls.debug_log_filename(), mode="r", encoding="utf-8") as debug_logs, open( + cls.error_log_filename(), mode="a", encoding="utf-8" + ) as error_logs: error_logs.writelines(debug_logs.readlines()) @classmethod diff --git a/tests/e2e/youtube/test_video.py b/tests/e2e/youtube/test_video.py index 911a72e1..9faf44b2 100644 --- a/tests/e2e/youtube/test_video.py +++ b/tests/e2e/youtube/test_video.py @@ -120,13 +120,11 @@ class TestYoutubeVideo: try_convert_download_thumbnail(entry=entry) # Pretend the thumbnail did not download via returning nothing for its downloaded path - with ( - patch.object(YTDLP, "_EXTRACT_ENTRY_NUM_RETRIES", 1), - patch.object(Entry, "try_get_ytdlp_download_thumbnail_path") as mock_ytdlp_path, - patch( - "ytdl_sub.downloaders.url.downloader.try_convert_download_thumbnail", - side_effect=delete_entry_thumb, - ), + with patch.object(YTDLP, "_EXTRACT_ENTRY_NUM_RETRIES", 1), patch.object( + Entry, "try_get_ytdlp_download_thumbnail_path" + ) as mock_ytdlp_path, patch( + "ytdl_sub.downloaders.url.downloader.try_convert_download_thumbnail", + side_effect=delete_entry_thumb, ): mock_ytdlp_path.return_value = None transaction_log = single_video_subscription.download(dry_run=False) diff --git a/tests/unit/cli/test_entrypoint.py b/tests/unit/cli/test_entrypoint.py index d7a472cc..31c4c443 100644 --- a/tests/unit/cli/test_entrypoint.py +++ b/tests/unit/cli/test_entrypoint.py @@ -42,15 +42,12 @@ def test_subscription_logs_write_to_file( config = persist_logs_config_factory(keep_successful_logs=keep_successful_logs) subscription_paths = [str(music_video_subscription_path)] * num_runs - with ( - patch.object( - Subscription, - "download", - new=mock_subscription_download_factory(mock_success_output=mock_success_output), - # mock datetime to be an index to be able to run instantly - ), - patch("ytdl_sub.cli.entrypoint._log_time", side_effect=[str(idx) for idx in range(10)]), - ): + with patch.object( + Subscription, + "download", + new=mock_subscription_download_factory(mock_success_output=mock_success_output), + # mock datetime to be an index to be able to run instantly + ), patch("ytdl_sub.cli.entrypoint._log_time", side_effect=[str(idx) for idx in range(10)]): try: _download_subscriptions_from_yaml_files( config=config, @@ -104,19 +101,16 @@ def test_update_with_info_json_requires_experimental_flag( default_config_path: Path, music_video_subscription_path: Path, ) -> None: - with ( - patch.object( - sys, - "argv", - [ - "ytdl-sub", - "--config", - str(default_config_path), - "sub", - str(music_video_subscription_path), - "--update-with-info-json", - ], - ), - pytest.raises(ExperimentalFeatureNotEnabled), - ): + with patch.object( + sys, + "argv", + [ + "ytdl-sub", + "--config", + str(default_config_path), + "sub", + str(music_video_subscription_path), + "--update-with-info-json", + ], + ), pytest.raises(ExperimentalFeatureNotEnabled): _ = main() diff --git a/tests/unit/cli/test_output_transaction_log.py b/tests/unit/cli/test_output_transaction_log.py index bc6ad08d..bc3b7f9e 100644 --- a/tests/unit/cli/test_output_transaction_log.py +++ b/tests/unit/cli/test_output_transaction_log.py @@ -32,22 +32,19 @@ def test_suppress_transaction_log( music_video_subscription_path: Path, file_transaction_log: Optional[str], ) -> None: - with ( - patch.object( - sys, - "argv", - [ - "ytdl-sub", - "--config", - str(default_config_path), - "sub", - str(music_video_subscription_path), - "--suppress-transaction-log", - ] - + (["--transaction-log", file_transaction_log] if file_transaction_log else []), - ), - patch("ytdl_sub.cli.output_transaction_log.output_transaction_log") as mock_transaction_log, - ): + with patch.object( + sys, + "argv", + [ + "ytdl-sub", + "--config", + str(default_config_path), + "sub", + str(music_video_subscription_path), + "--suppress-transaction-log", + ] + + (["--transaction-log", file_transaction_log] if file_transaction_log else []), + ), patch("ytdl_sub.cli.output_transaction_log.output_transaction_log") as mock_transaction_log: subscriptions = main() assert subscriptions @@ -85,23 +82,20 @@ def test_transaction_log_to_logger( default_config_path: Path, music_video_subscription_path: Path, ) -> None: - with ( - patch.object( - sys, - "argv", - [ - "ytdl-sub", - "--config", - str(default_config_path), - "sub", - str(music_video_subscription_path), - ], - ), - assert_logs( - logger=transaction_logger, - expected_message="Transaction log for Rick Astley:\n", - log_level="info", - ), + with patch.object( + sys, + "argv", + [ + "ytdl-sub", + "--config", + str(default_config_path), + "sub", + str(music_video_subscription_path), + ], + ), assert_logs( + logger=transaction_logger, + expected_message="Transaction log for Rick Astley:\n", + log_level="info", ): subscriptions = main() assert subscriptions diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index e01525b7..1415926e 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -375,47 +375,38 @@ def test_subscription_file_value_applies_from_config_and_nested_and_indent_varia config_file: ConfigFile, preset_with_subscription_value_nested_presets_and_indent_variables_same_line_old_format_errors: Dict, ): - with ( - mock_load_yaml( - preset_dict=preset_with_subscription_value_nested_presets_and_indent_variables_same_line_old_format_errors - ), - pytest.raises( - ValidationException, - match=re.escape( - "Validation error in parent_preset_2.=INDENT_1: 'INDENT_3' in '= INDENT_2 | INDENT_3' is not a preset name. " - "To use as a subscription indent value, define it as '= INDENT_3'" - ), + with mock_load_yaml( + preset_dict=preset_with_subscription_value_nested_presets_and_indent_variables_same_line_old_format_errors + ), pytest.raises( + ValidationException, + match=re.escape( + "Validation error in parent_preset_2.=INDENT_1: 'INDENT_3' in '= INDENT_2 | INDENT_3' is not a preset name. " + "To use as a subscription indent value, define it as '= INDENT_3'" ), ): Subscription.from_file_path(config=config_file, subscription_path="mocked") def test_subscription_file_using_conflicting_preset_name(config_file: ConfigFile): - with ( - mock_load_yaml( - preset_dict={ - "= INDENTS_IN_ERR_MSG ": {"=ANOTHER": {"jellyfin_tv_show_by_date": "single value"}} - } - ), - pytest.raises( - ValidationException, - match=re.escape( - "Validation error in = INDENTS_IN_ERR_MSG .=ANOTHER.jellyfin_tv_show_by_date: " - "jellyfin_tv_show_by_date conflicts with an existing preset name and cannot be used " - "as a subscription name" - ), + with mock_load_yaml( + preset_dict={ + "= INDENTS_IN_ERR_MSG ": {"=ANOTHER": {"jellyfin_tv_show_by_date": "single value"}} + } + ), pytest.raises( + ValidationException, + match=re.escape( + "Validation error in = INDENTS_IN_ERR_MSG .=ANOTHER.jellyfin_tv_show_by_date: " + "jellyfin_tv_show_by_date conflicts with an existing preset name and cannot be used " + "as a subscription name" ), ): _ = Subscription.from_file_path(config=config_file, subscription_path="mocked") def test_subscription_file_invalid_form(config_file: ConfigFile): - with ( - mock_load_yaml(preset_dict={"sub_name": 4332}), - pytest.raises( - ValidationException, - match=re.escape(f"Subscription value should either be a string, list, or object"), - ), + with mock_load_yaml(preset_dict={"sub_name": 4332}), pytest.raises( + ValidationException, + match=re.escape(f"Subscription value should either be a string, list, or object"), ): _ = Subscription.from_file_path(config=config_file, subscription_path="mocked") diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 7685a9d3..b20b302c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -228,11 +228,10 @@ def mock_download_collection_entries( ), ] - with ( - patch.object(YTDLP, "extract_info_via_info_json", new=_write_entries_to_working_dir), - patch.object( - MultiUrlDownloader, "_extract_entry_info_with_retry", new=lambda _, entry: entry - ), + with patch.object( + YTDLP, "extract_info_via_info_json", new=_write_entries_to_working_dir + ), patch.object( + MultiUrlDownloader, "_extract_entry_info_with_retry", new=lambda _, entry: entry ): # Stub out metadata. TODO: update this if we do metadata plugins yield diff --git a/tests/unit/main/test_main.py b/tests/unit/main/test_main.py index b9a6b07d..a88faa13 100644 --- a/tests/unit/main/test_main.py +++ b/tests/unit/main/test_main.py @@ -40,11 +40,9 @@ def mock_sys_exit(): @pytest.mark.parametrize("return_code", [0, 1]) def test_main_exit_code(mock_sys_exit, return_code: int): - with ( - mock_sys_exit(expected_exit_code=return_code), - patch("src.ytdl_sub.main._main") as mock_inner_main, - patch.object(Logger, "cleanup") as mock_logger_cleanup, - ): + with mock_sys_exit(expected_exit_code=return_code), patch( + "src.ytdl_sub.main._main" + ) as mock_inner_main, patch.object(Logger, "cleanup") as mock_logger_cleanup: mock_inner_main.return_value = return_code main() @@ -56,11 +54,9 @@ def test_main_exit_code(mock_sys_exit, return_code: int): def test_main_validation_error(capsys, mock_sys_exit): validation_exception = ValidationException("test exc") - with ( - mock_sys_exit(expected_exit_code=1), - patch("src.ytdl_sub.main._main", side_effect=validation_exception), - patch.object(logging.Logger, "error") as mock_logger, - ): + with mock_sys_exit(expected_exit_code=1), patch( + "src.ytdl_sub.main._main", side_effect=validation_exception + ), patch.object(logging.Logger, "error") as mock_logger: main() assert mock_logger.call_count == 1 @@ -69,12 +65,11 @@ def test_main_validation_error(capsys, mock_sys_exit): def test_main_uncaught_error(capsys, mock_sys_exit, expected_uncaught_error_message): uncaught_error = ValueError("test") - with ( - mock_sys_exit(expected_exit_code=1), - patch("src.ytdl_sub.main._main", side_effect=uncaught_error), - patch.object(logging.Logger, "exception") as mock_exception, - patch.object(logging.Logger, "error") as mock_error, - ): + with mock_sys_exit(expected_exit_code=1), patch( + "src.ytdl_sub.main._main", side_effect=uncaught_error + ), patch.object(logging.Logger, "exception") as mock_exception, patch.object( + logging.Logger, "error" + ) as mock_error: main() assert mock_exception.call_count == 1 @@ -88,11 +83,9 @@ def test_main_uncaught_error(capsys, mock_sys_exit, expected_uncaught_error_mess def test_main_permission_error(capsys, mock_sys_exit, expected_uncaught_error_message): permission_error = PermissionError("test") - with ( - mock_sys_exit(expected_exit_code=1), - patch("src.ytdl_sub.main._main", side_effect=permission_error), - patch.object(logging.Logger, "error") as mock_error, - ): + with mock_sys_exit(expected_exit_code=1), patch( + "src.ytdl_sub.main._main", side_effect=permission_error + ), patch.object(logging.Logger, "error") as mock_error: main() assert mock_error.call_count == 1 @@ -104,15 +97,11 @@ def test_main_permission_error(capsys, mock_sys_exit, expected_uncaught_error_me def test_args_after_sub_work(mock_sys_exit, tv_show_config_path): - with ( - mock_sys_exit(expected_exit_code=0), - patch.object( - sys, - "argv", - ["ytdl-sub", "-c", tv_show_config_path, "sub", "--log-level", "verbose"], - ), - patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub, - ): + with mock_sys_exit(expected_exit_code=0), patch.object( + sys, + "argv", + ["ytdl-sub", "-c", tv_show_config_path, "sub", "--log-level", "verbose"], + ), patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub: main() assert mock_sub.call_count == 1 @@ -123,25 +112,21 @@ def test_args_after_sub_work(mock_sys_exit, tv_show_config_path): def test_sub_match_arguments_before(mock_sys_exit, tv_show_config_path): - with ( - mock_sys_exit(expected_exit_code=0), - patch.object( - sys, - "argv", - [ - "ytdl-sub", - "--match", - "testA", - "testB", - "-c", - tv_show_config_path, - "sub", - "--log-level", - "verbose", - ], - ), - patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub, - ): + with mock_sys_exit(expected_exit_code=0), patch.object( + sys, + "argv", + [ + "ytdl-sub", + "--match", + "testA", + "testB", + "-c", + tv_show_config_path, + "sub", + "--log-level", + "verbose", + ], + ), patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub: main() assert mock_sub.call_count == 1 @@ -152,26 +137,22 @@ def test_sub_match_arguments_before(mock_sys_exit, tv_show_config_path): def test_sub_match_arguments_after_many(mock_sys_exit, tv_show_config_path): - with ( - mock_sys_exit(expected_exit_code=0), - patch.object( - sys, - "argv", - [ - "ytdl-sub", - "-c", - tv_show_config_path, - "sub", - "--log-level", - "verbose", - "--match", - "testA", - "--match", - "testB", - ], - ), - patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub, - ): + with mock_sys_exit(expected_exit_code=0), patch.object( + sys, + "argv", + [ + "ytdl-sub", + "-c", + tv_show_config_path, + "sub", + "--log-level", + "verbose", + "--match", + "testA", + "--match", + "testB", + ], + ), patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub: main() assert mock_sub.call_count == 1 @@ -182,15 +163,11 @@ def test_sub_match_arguments_after_many(mock_sys_exit, tv_show_config_path): def test_no_config_works(mock_sys_exit): - with ( - mock_sys_exit(expected_exit_code=0), - patch.object( - sys, - "argv", - ["ytdl-sub", "sub", "--log-level", "verbose"], - ), - patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub, - ): + with mock_sys_exit(expected_exit_code=0), patch.object( + sys, + "argv", + ["ytdl-sub", "sub", "--log-level", "verbose"], + ), patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub: main() assert mock_sub.call_count == 1 @@ -206,19 +183,14 @@ def test_uses_default_config_if_present(mock_sys_exit): open(DEFAULT_CONFIG_FILE_NAME, "a").close() try: - with ( - mock_sys_exit(expected_exit_code=0), - patch.object( - sys, - "argv", - ["ytdl-sub", "sub", "--log-level", "verbose"], - ), - patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub, - patch.object( - ConfigFile, - "from_file_path", - new=lambda _: ConfigFile(name="test default", value={}), - ), + with mock_sys_exit(expected_exit_code=0), patch.object( + sys, + "argv", + ["ytdl-sub", "sub", "--log-level", "verbose"], + ), patch( + "ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files" + ) as mock_sub, patch.object( + ConfigFile, "from_file_path", new=lambda _: ConfigFile(name="test default", value={}) ): main() @@ -232,15 +204,11 @@ def test_uses_default_config_if_present(mock_sys_exit): def test_no_positional_arg_command(mock_sys_exit, tv_show_config_path): - with ( - mock_sys_exit(expected_exit_code=1), - patch.object( - sys, - "argv", - ["ytdl-sub", "-c", tv_show_config_path, "--log-level", "verbose"], - ), - patch.object(logging.Logger, "error") as mock_error, - ): + with mock_sys_exit(expected_exit_code=1), patch.object( + sys, + "argv", + ["ytdl-sub", "-c", tv_show_config_path, "--log-level", "verbose"], + ), patch.object(logging.Logger, "error") as mock_error: main() assert mock_error.call_count == 1 @@ -248,15 +216,11 @@ def test_no_positional_arg_command(mock_sys_exit, tv_show_config_path): def test_bad_config_path(mock_sys_exit): - with ( - mock_sys_exit(expected_exit_code=1), - patch.object( - sys, - "argv", - ["ytdl-sub", "-c", "does_not_exist.yaml", "sub", "--log-level", "verbose"], - ), - patch.object(logging.Logger, "error") as mock_error, - ): + with mock_sys_exit(expected_exit_code=1), patch.object( + sys, + "argv", + ["ytdl-sub", "-c", "does_not_exist.yaml", "sub", "--log-level", "verbose"], + ), patch.object(logging.Logger, "error") as mock_error: main() assert mock_error.call_count == 1 diff --git a/tests/unit/plugins/test_throttle_protection.py b/tests/unit/plugins/test_throttle_protection.py index c0cc8045..9dcdb89b 100644 --- a/tests/unit/plugins/test_throttle_protection.py +++ b/tests/unit/plugins/test_throttle_protection.py @@ -39,29 +39,23 @@ class TestThrottleProtectionPlugin: preset_dict=preset_dict, ) - with ( - mock_download_collection_entries( - is_youtube_channel=False, num_urls=1, is_extracted_audio=False - ), - assert_logs( - logger=throttle_protection_logger, - expected_message="Sleeping between downloads for %0.2f seconds", - log_level="debug", - expected_occurrences=4, - ), + with mock_download_collection_entries( + is_youtube_channel=False, num_urls=1, is_extracted_audio=False + ), assert_logs( + logger=throttle_protection_logger, + expected_message="Sleeping between downloads for %0.2f seconds", + log_level="debug", + expected_occurrences=4, ): _ = subscription.download(dry_run=False) - with ( - mock_download_collection_entries( - is_youtube_channel=False, num_urls=1, is_extracted_audio=False - ), - assert_logs( - logger=throttle_protection_logger, - expected_message="Sleeping between subscriptions for %0.2f seconds", - log_level="debug", - expected_occurrences=1, - ), + with mock_download_collection_entries( + is_youtube_channel=False, num_urls=1, is_extracted_audio=False + ), assert_logs( + logger=throttle_protection_logger, + expected_message="Sleeping between subscriptions for %0.2f seconds", + log_level="debug", + expected_occurrences=1, ): _ = subscription.download(dry_run=False) @@ -111,15 +105,12 @@ class TestThrottleProtectionPlugin: preset_dict=preset_dict, ) - with ( - mock_download_collection_entries( - is_youtube_channel=False, num_urls=1, is_extracted_audio=False - ), - assert_logs( - logger=throttle_protection_logger, - expected_message="Sleeping between downloads for %0.2f seconds", - log_level="debug", - expected_occurrences=0, - ), + with mock_download_collection_entries( + is_youtube_channel=False, num_urls=1, is_extracted_audio=False + ), assert_logs( + logger=throttle_protection_logger, + expected_message="Sleeping between downloads for %0.2f seconds", + log_level="debug", + expected_occurrences=0, ): _ = subscription.download(dry_run=False)