diff --git a/src/ytdl_sub/cli/main.py b/src/ytdl_sub/cli/main.py new file mode 100644 index 00000000..52080fc2 --- /dev/null +++ b/src/ytdl_sub/cli/main.py @@ -0,0 +1,122 @@ +import argparse +import sys +from typing import List +from typing import Tuple + +from ytdl_sub.cli.download_args_parser import DownloadArgsParser +from ytdl_sub.cli.main_args_parser import parser +from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.config.preset import Preset +from ytdl_sub.subscriptions.subscription import Subscription +from ytdl_sub.utils.file_handler import FileHandlerTransactionLog +from ytdl_sub.utils.logger import Logger + +logger = Logger.get() + + +def _download_subscriptions_from_yaml_files( + config: ConfigFile, args: argparse.Namespace +) -> List[Tuple[Subscription, FileHandlerTransactionLog]]: + """ + Downloads all subscriptions from one or many subscription yaml files. + + Parameters + ---------- + config + Configuration file + args + Arguments from argparse + + Returns + ------- + List of (subscription, transaction_log) + """ + preset_paths: List[str] = args.subscription_paths + presets: List[Preset] = [] + output: List[Tuple[Subscription, FileHandlerTransactionLog]] = [] + + # Load all of the presets first to perform all validation before downloading + for preset_path in preset_paths: + presets += Preset.from_file_path(config=config, subscription_path=preset_path) + + for preset in presets: + subscription = Subscription.from_preset(preset=preset, config=config) + + logger.info("Beginning subscription download for %s", subscription.name) + transaction_log = subscription.download(dry_run=args.dry_run) + + output.append((subscription, transaction_log)) + + return output + + +def _download_subscription_from_cli( + config: ConfigFile, args: argparse.Namespace, extra_args: List[str] +) -> Tuple[Subscription, FileHandlerTransactionLog]: + """ + Downloads a one-off subscription using the CLI + + Parameters + ---------- + config + Configuration file + args + Arguments from argparse + extra_args + Extra arguments from argparse that contain dynamic subscription options + + Returns + ------- + Subscription and its download transaction log + """ + dl_args_parser = DownloadArgsParser( + extra_arguments=extra_args, config_options=config.config_options + ) + subscription_args_dict = dl_args_parser.to_subscription_dict() + + subscription_name = f"cli-dl-{dl_args_parser.get_args_hash()}" + subscription_preset = Preset.from_dict( + config=config, + preset_name=subscription_name, + preset_dict=subscription_args_dict, + ) + + subscription = Subscription.from_preset( + preset=subscription_preset, + config=config, + ) + + return subscription, subscription.download(dry_run=args.dry_run) + + +def main(): + """ + Entrypoint for ytdl-sub, without the error handling + """ + # If no args are provided, print help and exit + if len(sys.argv) < 2: + parser.print_help() + return + + args, extra_args = parser.parse_known_args() + + config: ConfigFile = ConfigFile.from_file_path(args.config).initialize() + transaction_logs: List[Tuple[Subscription, FileHandlerTransactionLog]] = [] + if args.subparser == "sub": + transaction_logs = _download_subscriptions_from_yaml_files(config=config, args=args) + + # One-off download + if args.subparser == "dl": + transaction_logs.append( + _download_subscription_from_cli(config=config, args=args, extra_args=extra_args) + ) + + for subscription, transaction_log in transaction_logs: + logger.info( + "Downloads for %s:\n%s\n", + subscription.name, + transaction_log.to_output_message(subscription.output_directory), + ) + + # Ran successfully, so we can delete the debug file + Logger.cleanup(delete_debug_file=True) diff --git a/src/ytdl_sub/cli/main_args_parser.py b/src/ytdl_sub/cli/main_args_parser.py index 189c303a..e3177b74 100644 --- a/src/ytdl_sub/cli/main_args_parser.py +++ b/src/ytdl_sub/cli/main_args_parser.py @@ -27,6 +27,7 @@ parser.add_argument( help="level of logs to print to console, defaults to info", default=LoggerLevels.INFO.name, choices=LoggerLevels.names(), + dest="ytdl_sub_log_level", ) ################################################################################################### # SUBSCRIPTION PARSER diff --git a/src/ytdl_sub/main.py b/src/ytdl_sub/main.py index f866df84..fd8cc371 100644 --- a/src/ytdl_sub/main.py +++ b/src/ytdl_sub/main.py @@ -1,134 +1,29 @@ -import argparse import sys -from typing import List -from typing import Tuple -from ytdl_sub.cli.download_args_parser import DownloadArgsParser from ytdl_sub.cli.main_args_parser import parser -from ytdl_sub.config.config_file import ConfigFile -from ytdl_sub.config.preset import Preset -from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.utils.exceptions import ValidationException -from ytdl_sub.utils.file_handler import FileHandlerTransactionLog from ytdl_sub.utils.logger import Logger -logger = Logger.get() - - -def _download_subscriptions_from_yaml_files( - config: ConfigFile, args: argparse.Namespace -) -> List[Tuple[Subscription, FileHandlerTransactionLog]]: - """ - Downloads all subscriptions from one or many subscription yaml files. - - Parameters - ---------- - config - Configuration file - args - Arguments from argparse - - Returns - ------- - List of (subscription, transaction_log) - """ - preset_paths: List[str] = args.subscription_paths - presets: List[Preset] = [] - output: List[Tuple[Subscription, FileHandlerTransactionLog]] = [] - - # Load all of the presets first to perform all validation before downloading - for preset_path in preset_paths: - presets += Preset.from_file_path(config=config, subscription_path=preset_path) - - for preset in presets: - subscription = Subscription.from_preset(preset=preset, config=config) - - logger.info("Beginning subscription download for %s", subscription.name) - transaction_log = subscription.download(dry_run=args.dry_run) - - output.append((subscription, transaction_log)) - - return output - - -def _download_subscription_from_cli( - config: ConfigFile, args: argparse.Namespace, extra_args: List[str] -) -> Tuple[Subscription, FileHandlerTransactionLog]: - """ - Downloads a one-off subscription using the CLI - - Parameters - ---------- - config - Configuration file - args - Arguments from argparse - extra_args - Extra arguments from argparse that contain dynamic subscription options - - Returns - ------- - Subscription and its download transaction log - """ - dl_args_parser = DownloadArgsParser( - extra_arguments=extra_args, config_options=config.config_options - ) - subscription_args_dict = dl_args_parser.to_subscription_dict() - - subscription_name = f"cli-dl-{dl_args_parser.get_args_hash()}" - subscription_preset = Preset.from_dict( - config=config, - preset_name=subscription_name, - preset_dict=subscription_args_dict, - ) - - subscription = Subscription.from_preset( - preset=subscription_preset, - config=config, - ) - - return subscription, subscription.download(dry_run=args.dry_run) - def _main(): - """ - Entrypoint for ytdl-sub, without the error handling - """ - # If no args are provided, print help and exit - if len(sys.argv) < 2: - parser.print_help() - return + # Set log level before any other ytdl-sub files are imported. That way, when loggers + # get initialized, they will see the set log level + args, _ = parser.parse_known_args() + Logger.set_log_level(log_level_name=args.ytdl_sub_log_level) - args, extra_args = parser.parse_known_args() + # pylint: disable=import-outside-toplevel + import ytdl_sub.cli.main - config: ConfigFile = ConfigFile.from_file_path(args.config).initialize() - Logger.set_log_level(log_level_name=args.log_level) + # pylint: enable=import-outside-toplevel - transaction_logs: List[Tuple[Subscription, FileHandlerTransactionLog]] = [] - if args.subparser == "sub": - transaction_logs = _download_subscriptions_from_yaml_files(config=config, args=args) - - # One-off download - if args.subparser == "dl": - transaction_logs.append( - _download_subscription_from_cli(config=config, args=args, extra_args=extra_args) - ) - - for subscription, transaction_log in transaction_logs: - logger.info( - "Downloads for %s:\n%s\n", - subscription.name, - transaction_log.to_output_message(subscription.output_directory), - ) - - # Ran successfully, so we can delete the debug file - Logger.cleanup(delete_debug_file=True) + ytdl_sub.cli.main.main() def main(): """ Entrypoint for ytdl-sub """ + logger = Logger.get() try: _main() except ValidationException as validation_exception: diff --git a/tests/unit/main/test_main.py b/tests/unit/main/test_main.py index 8a71597b..c1c91bfd 100644 --- a/tests/unit/main/test_main.py +++ b/tests/unit/main/test_main.py @@ -1,4 +1,5 @@ import contextlib +import logging import sys from unittest.mock import patch @@ -39,25 +40,27 @@ def test_main_success(mock_sys_exit): def test_main_validation_error(capsys, mock_sys_exit): validation_exception = ValidationException("test") - with mock_sys_exit(expected_exit_code=1): - with patch("src.ytdl_sub.main._main", side_effect=validation_exception): - with patch("src.ytdl_sub.main.logger") as mock_logger: - main() + 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.error.call_count == 1 - assert mock_logger.error.call_args.args[0] == validation_exception + assert mock_logger.call_count == 1 + assert mock_logger.call_args.args[0] == validation_exception 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): - with patch("src.ytdl_sub.main._main", side_effect=uncaught_error): - with patch("src.ytdl_sub.main.logger") as mock_logger: - main() + 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_logger.exception.call_count == 1 - assert mock_logger.exception.call_args.args[0] == "An uncaught error occurred:" + assert mock_exception.call_count == 1 + assert mock_exception.call_args.args[0] == "An uncaught error occurred:" - assert mock_logger.error.call_count == 1 - assert mock_logger.error.call_args.args[0] == expected_uncaught_error_message - assert mock_logger.error.call_args.args[1] == Logger.debug_log_filename() + assert mock_error.call_count == 1 + assert mock_error.call_args.args[0] == expected_uncaught_error_message + assert mock_error.call_args.args[1] == Logger.debug_log_filename()