diff --git a/src/ytdl_sub/cli/entrypoint.py b/src/ytdl_sub/cli/entrypoint.py index c877e0f1..1d6a19af 100644 --- a/src/ytdl_sub/cli/entrypoint.py +++ b/src/ytdl_sub/cli/entrypoint.py @@ -3,6 +3,7 @@ import os import sys from datetime import datetime from pathlib import Path +from typing import Dict from typing import List from typing import Optional @@ -70,6 +71,7 @@ def _download_subscriptions_from_yaml_files( config: ConfigFile, subscription_paths: List[str], subscription_matches: List[str], + subscription_override_dict: Dict, update_with_info_json: bool, dry_run: bool, ) -> List[Subscription]: @@ -102,7 +104,11 @@ def _download_subscriptions_from_yaml_files( # Load all the subscriptions first to perform all validation before downloading for path in subscription_paths: - subscriptions += Subscription.from_file_path(config=config, subscription_path=path) + subscriptions += Subscription.from_file_path( + config=config, + subscription_path=path, + subscription_override_dict=subscription_override_dict, + ) if subscriptions and subscription_matches: logger.info("Filtering subscriptions by name based on --match arguments") @@ -233,11 +239,18 @@ def main() -> List[Subscription]: "full backup before usage. You have been warned!", ) + subscription_override_dict = {} + if args.dl_override: + subscription_override_dict = DownloadArgsParser.from_dl_override( + override=args.dl_override, config=config + ).to_subscription_dict() + logger.info("Validating subscriptions...") subscriptions = _download_subscriptions_from_yaml_files( config=config, subscription_paths=args.subscription_paths, subscription_matches=args.match, + subscription_override_dict=subscription_override_dict, update_with_info_json=args.update_with_info_json, dry_run=args.dry_run, ) diff --git a/src/ytdl_sub/cli/parsers/dl.py b/src/ytdl_sub/cli/parsers/dl.py index a3ada3b3..d9e1eb1a 100644 --- a/src/ytdl_sub/cli/parsers/dl.py +++ b/src/ytdl_sub/cli/parsers/dl.py @@ -9,6 +9,7 @@ from typing import Tuple from mergedeep import mergedeep from ytdl_sub.cli.parsers.main import MainArguments +from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.config_validator import ConfigOptions from ytdl_sub.utils.exceptions import InvalidDlArguments @@ -247,3 +248,12 @@ class DownloadArgsParser: """ hash_string = str(sorted(self._unknown_arguments)) return hashlib.sha256(hash_string.encode()).hexdigest()[-8:] + + @classmethod + def from_dl_override(cls, override: str, config: ConfigFile) -> "DownloadArgsParser": + """ + Create a DownloadArgsParser from a sub --override argument value + """ + return DownloadArgsParser( + extra_arguments=override.split(), config_options=config.config_options + ) diff --git a/src/ytdl_sub/cli/parsers/main.py b/src/ytdl_sub/cli/parsers/main.py index 8d3630bc..99f6c231 100644 --- a/src/ytdl_sub/cli/parsers/main.py +++ b/src/ytdl_sub/cli/parsers/main.py @@ -157,6 +157,10 @@ class SubArguments: short="-u", long="--update-with-info-json", ) + OVERRIDE = CLIArgument( + short="-o", + long="--dl-override", + ) subscription_parser = subparsers.add_parser("sub") @@ -175,6 +179,13 @@ subscription_parser.add_argument( help="update all subscriptions with the current config using info.json files", default=False, ) +subscription_parser.add_argument( + SubArguments.OVERRIDE.short, + SubArguments.OVERRIDE.long, + type=str, + help="override all subscription config values using `dl` syntax, " + "i.e. --dl-override='--ytdl_options.max_downloads 3'", +) ################################################################################################### # DOWNLOAD PARSER diff --git a/src/ytdl_sub/subscriptions/subscription.py b/src/ytdl_sub/subscriptions/subscription.py index 50b2f319..37d0168f 100644 --- a/src/ytdl_sub/subscriptions/subscription.py +++ b/src/ytdl_sub/subscriptions/subscription.py @@ -3,6 +3,9 @@ from pathlib import Path from typing import Any from typing import Dict from typing import List +from typing import Optional + +from mergedeep import mergedeep from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.preset import Preset @@ -69,7 +72,10 @@ class Subscription(SubscriptionDownload): @classmethod def from_file_path( - cls, config: ConfigFile, subscription_path: str | Path + cls, + config: ConfigFile, + subscription_path: str | Path, + subscription_override_dict: Optional[Dict] = None, ) -> List["Subscription"]: """ Loads subscriptions from a file. @@ -80,6 +86,8 @@ class Subscription(SubscriptionDownload): Validated instance of the config subscription_path: File path to the subscription yaml file + subscription_override_dict: + Optional dict containing overrides to every subscription Returns ------- @@ -122,6 +130,13 @@ class Subscription(SubscriptionDownload): ) for subscription_key, subscription_object in subscriptions_dicts.items(): + # Hard-override subscriptions here + mergedeep.merge( + subscription_object, + subscription_override_dict or {}, + strategy=mergedeep.Strategy.ADDITIVE, + ) + subscriptions.append( cls.from_dict( config=config, diff --git a/tests/e2e/youtube/test_playlist.py b/tests/e2e/youtube/test_playlist.py index 7d8eca5e..3c8e142f 100644 --- a/tests/e2e/youtube/test_playlist.py +++ b/tests/e2e/youtube/test_playlist.py @@ -199,3 +199,26 @@ class TestPlaylist: dry_run=dry_run, expected_download_summary_file_name="youtube/test_playlist.json", ) + + def test_playlist_download_from_cli_sub_with_override_arg( + self, + preset_dict_to_subscription_yaml_generator, + playlist_preset_dict, + output_directory, + ): + # TODO: Fix CLI parsing on windows when dealing with spaces + if IS_WINDOWS: + return + + # No config needed when using only prebuilt presets + with preset_dict_to_subscription_yaml_generator( + subscription_name="music_video_playlist_test", preset_dict=playlist_preset_dict + ) as subscription_path: + args = ( + f"--dry-run sub '{subscription_path}' --dl-override '--date_range.after 20240101'" + ) + + subscriptions = mock_run_from_cli(args=args) + + assert len(subscriptions) == 1 + assert subscriptions[0].transaction_log.is_empty diff --git a/tests/unit/cli/test_entrypoint.py b/tests/unit/cli/test_entrypoint.py index f9be7dc3..31c4c443 100644 --- a/tests/unit/cli/test_entrypoint.py +++ b/tests/unit/cli/test_entrypoint.py @@ -53,6 +53,7 @@ def test_subscription_logs_write_to_file( config=config, subscription_paths=subscription_paths, subscription_matches=match, + subscription_override_dict={}, update_with_info_json=False, dry_run=dry_run, )