diff --git a/src/ytdl_sub/subscriptions/subscription.py b/src/ytdl_sub/subscriptions/subscription.py index 5c835a02..56231c56 100644 --- a/src/ytdl_sub/subscriptions/subscription.py +++ b/src/ytdl_sub/subscriptions/subscription.py @@ -1,3 +1,4 @@ +import copy from typing import Dict from typing import List @@ -5,6 +6,9 @@ from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.preset import Preset from ytdl_sub.subscriptions.subscription_download import SubscriptionDownload from ytdl_sub.utils.yaml import load_yaml +from ytdl_sub.validators.validators import LiteralDictValidator + +FILE_PRESET_APPLY_KEY = "__preset__" class Subscription(SubscriptionDownload): @@ -71,10 +75,40 @@ class Subscription(SubscriptionDownload): ------- List of subscriptions, for each one in the subscription yaml """ + subscriptions: List["Subscription"] = [] subscription_dict = load_yaml(file_path=subscription_path) - subscriptions: List["Subscription"] = [] + has_file_preset = FILE_PRESET_APPLY_KEY in subscription_dict + + # If a file preset is present... + if has_file_preset: + # Validate it (make sure it is a dict) + file_preset = LiteralDictValidator( + name=f"{subscription_path}.{FILE_PRESET_APPLY_KEY}", + value=subscription_dict[FILE_PRESET_APPLY_KEY], + ) + + # Deep copy the config and add this file preset to its preset list + config = copy.deepcopy(config) + config.presets.dict[FILE_PRESET_APPLY_KEY] = file_preset.dict + for subscription_key, subscription_object in subscription_dict.items(): + + # Skip file preset + if subscription_key == FILE_PRESET_APPLY_KEY: + continue + + # If it has file_preset, inject it as a parent preset + if has_file_preset: + parent_preset = subscription_object.get("preset", []) + # Preset can be a single string + if isinstance(parent_preset, str): + parent_preset = [parent_preset] + + # If it's not a string or list, it will fail downstream + if isinstance(parent_preset, list): + subscription_object["preset"] = parent_preset + [FILE_PRESET_APPLY_KEY] + subscriptions.append( cls.from_dict( config=config, diff --git a/tests/unit/config/conftest.py b/tests/unit/config/conftest.py new file mode 100644 index 00000000..480428bf --- /dev/null +++ b/tests/unit/config/conftest.py @@ -0,0 +1,53 @@ +from typing import Dict + +import pytest + +from ytdl_sub.config.config_file import ConfigFile + + +@pytest.fixture +def config_file() -> ConfigFile: + return ConfigFile( + name="config", + value={ + "configuration": {"working_directory": "."}, + "presets": { + "parent_preset_0": {"nfo_tags": {"tags": {"key-1": "preset_0"}}}, + "parent_preset_1": { + "preset": "parent_preset_0", + "nfo_tags": { + "nfo_name": "{uid}.nfo", + "nfo_root": "root", + "tags": {"key-2": "preset_1"}, + }, + }, + "parent_preset_2": { + "nfo_tags": { + "nfo_name": "{uid}.nfo", + "nfo_root": "root", + "tags": {"key-2": "preset_2", "key-3": "preset_2"}, + } + }, + "parent_preset_3": {"preset": ["parent_preset_1", "parent_preset_2"]}, + "preset_self_loop": {"preset": "preset_self_loop"}, + "preset_loop_0": {"preset": "preset_loop_1"}, + "preset_loop_1": {"preset": "preset_loop_0"}, + }, + }, + ) + + +@pytest.fixture +def output_options() -> Dict: + return { + "output_directory": "some dir", + "file_name": "{uid}", + } + + +@pytest.fixture +def youtube_video() -> Dict: + return { + "download_strategy": "video", + "video_url": "youtube.com/watch?v=123abc", + } diff --git a/tests/unit/config/test_preset.py b/tests/unit/config/test_preset.py index 3d469f1a..c2d2efb9 100644 --- a/tests/unit/config/test_preset.py +++ b/tests/unit/config/test_preset.py @@ -2,61 +2,12 @@ from typing import Dict import pytest -from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.preset import Preset from ytdl_sub.plugins.nfo_tags import NfoTagsOptions from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException from ytdl_sub.utils.exceptions import ValidationException -@pytest.fixture -def config_file() -> ConfigFile: - return ConfigFile( - name="config", - value={ - "configuration": {"working_directory": "."}, - "presets": { - "parent_preset_0": {"nfo_tags": {"tags": {"key-1": "preset_0"}}}, - "parent_preset_1": { - "preset": "parent_preset_0", - "nfo_tags": { - "nfo_name": "{uid}.nfo", - "nfo_root": "root", - "tags": {"key-2": "preset_1"}, - }, - }, - "parent_preset_2": { - "nfo_tags": { - "nfo_name": "{uid}.nfo", - "nfo_root": "root", - "tags": {"key-2": "preset_2", "key-3": "preset_2"}, - } - }, - "parent_preset_3": {"preset": ["parent_preset_1", "parent_preset_2"]}, - "preset_self_loop": {"preset": "preset_self_loop"}, - "preset_loop_0": {"preset": "preset_loop_1"}, - "preset_loop_1": {"preset": "preset_loop_0"}, - }, - }, - ) - - -@pytest.fixture -def output_options() -> Dict: - return { - "output_directory": "some dir", - "file_name": "{uid}", - } - - -@pytest.fixture -def youtube_video() -> Dict: - return { - "download_strategy": "video", - "video_url": "youtube.com/watch?v=123abc", - } - - class TestPreset: @pytest.mark.parametrize( "source, download_strategy", diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py new file mode 100644 index 00000000..1eff02f3 --- /dev/null +++ b/tests/unit/config/test_subscription.py @@ -0,0 +1,48 @@ +from typing import Dict +from unittest.mock import patch + +import pytest + +from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.plugins.nfo_tags import NfoTagsOptions +from ytdl_sub.subscriptions.subscription import Subscription + + +@pytest.fixture +def preset_file(youtube_video: Dict, output_options: Dict) -> Dict: + return { + "__preset__": { + "youtube": youtube_video, + "output_options": output_options, + "nfo_tags": { + "tags": {"key-3": "file_preset"}, + }, + }, + "test_preset": { + "preset": "parent_preset_3", + "nfo_tags": { + "tags": {"key-4": "test_preset"}, + }, + }, + } + + +def test_subscription_file_preset_applies(config_file: ConfigFile, preset_file: Dict): + with patch("ytdl_sub.subscriptions.subscription.load_yaml") as mock_load_yaml: + mock_load_yaml.return_value = preset_file + + subs = Subscription.from_file_path(config=config_file, subscription_path="mocked") + assert len(subs) == 1 + + nfo_options: NfoTagsOptions = subs[0].plugins.get(NfoTagsOptions) + tags_string_dict = { + key: formatter[0].format_string + for key, formatter in nfo_options.tags.string_tags.items() + } + + assert tags_string_dict == { + "key-1": "preset_0", + "key-2": "preset_2", + "key-3": "file_preset", + "key-4": "test_preset", + }