From c33f0d84eec8e3e5e69a7ad9e7f9abc3352348f4 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Tue, 25 Oct 2022 15:31:02 -0700 Subject: [PATCH] download strategy validate --- src/ytdl_sub/config/preset.py | 36 ++++++++++++ .../generic/collection_validator.py | 6 ++ src/ytdl_sub/plugins/nfo_tags.py | 10 +++- .../music_videos/jellyfin-music-videos.yaml | 6 -- ...sic-videos.yaml => kodi_music_videos.yaml} | 2 +- .../tv_show/tv_show_collection.yaml | 5 ++ tests/unit/config/test_preset.py | 57 ++++++++++++++++++- 7 files changed, 109 insertions(+), 13 deletions(-) delete mode 100644 src/ytdl_sub/prebuilt_presets/music_videos/jellyfin-music-videos.yaml rename src/ytdl_sub/prebuilt_presets/music_videos/{kodi-music-videos.yaml => kodi_music_videos.yaml} (95%) diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index 0ee5b1ee..b7c810a8 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -114,6 +114,11 @@ class DownloadStrategyValidator(StrictDictValidator): Returns ------- The downloader class + + Raises + ------ + ValidationException + If the download strategy is invalid """ try: return DownloadStrategyMapping.get( @@ -129,15 +134,46 @@ class Preset(StrictDictValidator): # and ensure required keys are present. _optional_keys = PRESET_KEYS + @classmethod + def _validate_download_strategy(cls, name: str, value: Dict) -> None: + sources: List[str] = [] + for source_name in DownloadStrategyMapping.sources(): + if source_name in value: + sources.append(source_name) + + if len(sources) > 1: + raise validation_exception( + name=name, + error_message=f"Contains the sources {', '.join(sources)} but can only have one", + ) + + # If no sources, nothing more to validate + if not sources: + return + + source_name = sources[0] + source_dict = copy.deepcopy(value[source_name]) + + downloader = DownloadStrategyValidator(name=f"{name}.{source_name}", value=source_dict).get( + downloader_source=source_name + ) + del source_dict["download_strategy"] + + downloader.downloader_options_type.partial_validate( + name=f"{name}.{source_name}", value=source_dict + ) + @classmethod def preset_partial_validate(cls, config: ConfigValidator, name: str, value: Any) -> None: # Ensure value is a dict _ = DictValidator(name=name, value=value) assert isinstance(value, dict) + cls._validate_download_strategy(name, value) cls._partial_validate_key(name, value, "output_options", OutputOptions) cls._partial_validate_key(name, value, "ytdl_options", YTDLOptions) cls._partial_validate_key(name, value, "overrides", Overrides) + for plugin_name in PluginMapping.plugins(): cls._partial_validate_key( name, diff --git a/src/ytdl_sub/downloaders/generic/collection_validator.py b/src/ytdl_sub/downloaders/generic/collection_validator.py index c25ac56c..6547ce2c 100644 --- a/src/ytdl_sub/downloaders/generic/collection_validator.py +++ b/src/ytdl_sub/downloaders/generic/collection_validator.py @@ -156,6 +156,12 @@ class CollectionValidator(StrictDictValidator, AddsVariablesMixin): _required_keys = {"urls"} + @classmethod + def partial_validate(cls, name: str, value: Any) -> None: + if isinstance(value, dict): + value["urls"] = value.get("urls", [{"url": "placeholder"}]) + _ = cls(name, value) + def __init__(self, name, value): super().__init__(name, value) self._urls = self._validate_key(key="urls", validator=CollectionUrlListValidator) diff --git a/src/ytdl_sub/plugins/nfo_tags.py b/src/ytdl_sub/plugins/nfo_tags.py index bcb5eb3f..5c938607 100644 --- a/src/ytdl_sub/plugins/nfo_tags.py +++ b/src/ytdl_sub/plugins/nfo_tags.py @@ -41,9 +41,13 @@ class SharedNfoTagsOptions(PluginOptions): def __init__(self, name, value): super().__init__(name, value) - self._nfo_name = self._validate_key(key="nfo_name", validator=StringFormatterValidator) - self._nfo_root = self._validate_key(key="nfo_root", validator=StringFormatterValidator) - self._tags = self._validate_key(key="tags", validator=NfoTagsValidator) + self._nfo_name = self._validate_key_if_present( + key="nfo_name", validator=StringFormatterValidator + ) + self._nfo_root = self._validate_key_if_present( + key="nfo_root", validator=StringFormatterValidator + ) + self._tags = self._validate_key_if_present(key="tags", validator=NfoTagsValidator) self._kodi_safe = self._validate_key_if_present( key="kodi_safe", validator=BoolValidator, default=False ).value diff --git a/src/ytdl_sub/prebuilt_presets/music_videos/jellyfin-music-videos.yaml b/src/ytdl_sub/prebuilt_presets/music_videos/jellyfin-music-videos.yaml deleted file mode 100644 index 2db3c8cc..00000000 --- a/src/ytdl_sub/prebuilt_presets/music_videos/jellyfin-music-videos.yaml +++ /dev/null @@ -1,6 +0,0 @@ -presets: - - kodi-music-videos: - preset: - - "jellyfin-music-videos" - - "kodi-safe" diff --git a/src/ytdl_sub/prebuilt_presets/music_videos/kodi-music-videos.yaml b/src/ytdl_sub/prebuilt_presets/music_videos/kodi_music_videos.yaml similarity index 95% rename from src/ytdl_sub/prebuilt_presets/music_videos/kodi-music-videos.yaml rename to src/ytdl_sub/prebuilt_presets/music_videos/kodi_music_videos.yaml index 84623470..86999852 100644 --- a/src/ytdl_sub/prebuilt_presets/music_videos/kodi-music-videos.yaml +++ b/src/ytdl_sub/prebuilt_presets/music_videos/kodi_music_videos.yaml @@ -3,7 +3,7 @@ presets: kodi_music_video: generic: download_strategy: "source" - music_video_url: "{music_video_url}" + url: "{music_video_url}" output_options: output_directory: "{music_video_directory}" diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml index fbaf2c4f..f05fe036 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml +++ b/src/ytdl_sub/prebuilt_presets/tv_show/tv_show_collection.yaml @@ -18,6 +18,7 @@ presets: collection_season_1: generic: + download_strategy: "collection" urls: - url: "{collection_season_1_url}" variables: @@ -42,6 +43,7 @@ presets: collection_season_2: generic: + download_strategy: "collection" urls: - url: "{collection_season_2_url}" variables: @@ -60,6 +62,7 @@ presets: collection_season_3: generic: + download_strategy: "collection" urls: - url: "{collection_season_3_url}" variables: @@ -78,6 +81,7 @@ presets: collection_season_4: generic: + download_strategy: "collection" urls: - url: "{collection_season_4_url}" variables: @@ -96,6 +100,7 @@ presets: collection_season_5: generic: + download_strategy: "collection" urls: - url: "{collection_season_5_url}" variables: diff --git a/tests/unit/config/test_preset.py b/tests/unit/config/test_preset.py index b348acdf..88b36667 100644 --- a/tests/unit/config/test_preset.py +++ b/tests/unit/config/test_preset.py @@ -2,6 +2,7 @@ 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 @@ -203,6 +204,56 @@ class TestPreset: }, ) - def test_partial_validate(self, config_file): - for preset_name, preset_dict in config_file.presets.dict.items(): - Preset.preset_partial_validate(config_file, preset_name, preset_dict) + +class TestPresetPartialValidate: + @pytest.mark.parametrize( + "preset_dict", + [ + {"nfo_tags": {"tags": {"key-1": "preset_0"}}}, + {"output_directory_nfo_tags": {"nfo_root": "test"}}, + {"output_directory": {"file_name": "test"}}, + {"output_directory": {"keep_files_after": "today"}}, + {"ytdl_options": {"format": "best"}}, + ], + ) + def test_partial_validate(self, preset_dict): + _ = ConfigFile( + name="test_partial_validate", + value={ + "configuration": {"working_directory": "."}, + "presets": {"partial_preset": preset_dict}, + }, + ) + + @pytest.mark.parametrize( + "preset_dict", + [ + {"youtube": {}, "generic": {}}, # multiple sources + ], + ) + def test_partial_validate__bad_sources(self, preset_dict): + with pytest.raises(ValidationException): + _ = ConfigFile( + name="test_partial_validate", + value={ + "configuration": {"working_directory": "."}, + "presets": {"partial_preset": preset_dict}, + }, + ) + + @pytest.mark.parametrize( + "preset_dict", + [ + {"nfo_tags": {"tags": {"key-1": {"attributes": {"test": "2"}}}}}, + {"generic": {"urls": {"variables_to_set": {"name": "value"}}}}, + ], + ) + def test_partial_validate__incomplete_list_item(self, preset_dict): + with pytest.raises(ValidationException): + _ = ConfigFile( + name="test_partial_validate", + value={ + "configuration": {"working_directory": "."}, + "presets": {"partial_preset": preset_dict}, + }, + )