[BACKEND] Partially validate all presets in configs

This commit is contained in:
Jesse Bannon 2022-10-23 20:36:25 -07:00
parent 3e4a995f6e
commit a71577e703
10 changed files with 111 additions and 10 deletions

View file

@ -128,6 +128,19 @@ class Preset(StrictDictValidator):
# and ensure required keys are present.
_optional_keys = PRESET_KEYS
@classmethod
def preset_partial_validate(cls, config: ConfigFile, name: str, value: Any) -> None:
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,
value,
key=plugin_name,
validator=PluginMapping.get(plugin_name).plugin_options_type,
)
@property
def _source_variables(self) -> List[str]:
return Entry.source_variables()

View file

@ -1,4 +1,5 @@
from abc import ABC
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
@ -12,6 +13,7 @@ from ytdl_sub.validators.string_formatter_validators import DictFormatterValidat
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.validators import BoolValidator
from ytdl_sub.validators.validators import DictValidator
from ytdl_sub.validators.validators import LiteralDictValidator
@ -182,6 +184,13 @@ class OutputOptions(StrictDictValidator):
"keep_files_after",
}
@classmethod
def partial_validate(cls, name: str, value: Any) -> None:
if isinstance(value, dict):
value["output_directory"] = value.get("output_directory", "placeholder")
value["file_name"] = value.get("file_name", "placeholder")
_ = cls(name, value)
def __init__(self, name, value):
super().__init__(name, value)

View file

@ -1,3 +1,4 @@
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
@ -42,6 +43,12 @@ class CollectionUrlValidator(StrictDictValidator):
_required_keys = {"url"}
_optional_keys = {"variables", "source_thumbnails", "playlist_thumbnails"}
@classmethod
def partial_validate(cls, name: str, value: Any) -> None:
if isinstance(value, dict):
value["url"] = value.get("url", "placeholder")
_ = cls(name, value)
def __init__(self, name, value):
super().__init__(name, value)

View file

@ -1,3 +1,4 @@
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
@ -34,6 +35,14 @@ class YoutubeChannelDownloaderOptions(DownloaderValidator):
"channel_banner_path",
}
@classmethod
def partial_validate(cls, name: str, value: Any) -> None:
if isinstance(value, dict):
value["channel_url"] = value.get(
"channel_url", "https://www.youtube.com/c/ProjectZombie603"
)
_ = cls(name, value)
def __init__(self, name, value):
super().__init__(name, value)
self._channel_url = self._validate_key(

View file

@ -1,4 +1,5 @@
import os.path
from typing import Any
from typing import Dict
from typing import Optional
@ -27,7 +28,14 @@ class AudioExtractOptions(PluginOptions):
quality: 128
"""
_optional_keys = {"codec", "quality"}
_required_keys = {"codec"}
_optional_keys = {"quality"}
@classmethod
def partial_validate(cls, name: str, value: Any) -> None:
if isinstance(value, dict):
value["codec"] = value.get("codec", "mp3")
_ = cls(name, value)
def __init__(self, name, value):
super().__init__(name, value)

View file

@ -2,6 +2,7 @@ import os
from abc import ABC
from collections import defaultdict
from pathlib import Path
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
@ -28,16 +29,21 @@ class SharedNfoTagsOptions(PluginOptions):
_required_keys = {"nfo_name", "nfo_root", "tags"}
_optional_keys = {"kodi_safe"}
@classmethod
def partial_validate(cls, name: str, value: Any) -> None:
if isinstance(value, dict):
value["nfo_name"] = value.get("nfo_name", "placeholder")
value["nfo_root"] = value.get("nfo_root", "placeholder")
value["tags"] = value.get("tags", {})
_ = cls(name=name, value=value)
def __init__(self, name, value):
super().__init__(name, value)
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._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._kodi_safe = self._validate_key_if_present(
key="kodi_safe", validator=BoolValidator, default=False
).value

View file

@ -1,5 +1,6 @@
from abc import ABC
from collections import defaultdict
from typing import Any
from typing import Dict
from typing import List

View file

@ -1,3 +1,4 @@
from typing import Any
from typing import List
from typing import Set

View file

@ -16,6 +16,29 @@ ValidationExceptionT = TypeVar("ValidationExceptionT", bound=ValidationException
ValidatorT = TypeVar("ValidatorT", bound="Validator")
def validation_exception(
name: str,
error_message: str | Exception,
exception_class: Type[ValidationExceptionT] = ValidationException,
) -> ValidationExceptionT:
"""
Parameters
----------
name
Name of the validator
error_message
Error message to include in the ValidationException
exception_class
Class of the exception
Returns
-------
Validation exception with a consistent prefix.
"""
prefix = f"Validation error in {name}: "
return exception_class(f"{prefix}{error_message}")
class Validator(ABC):
"""
Used to validate the value of a python object. This is the 'base' class that will first
@ -29,6 +52,18 @@ class Validator(ABC):
# When raising an error, call the type this value instead of its python name
_expected_value_type_name: Optional[str] = None
@classmethod
def partial_validate(cls, name: str, value: Any) -> None:
"""
Parameters
----------
name
Name of the validator
value
Value of the validator
"""
_ = cls(name=name, value=value)
def __init__(self, name: str, value: Any):
self._name = name
self._value = copy.deepcopy(value) # Always deep copy to avoid editing references
@ -56,8 +91,7 @@ class Validator(ABC):
-------
Validation exception with a consistent prefix.
"""
prefix = f"Validation error in {self._name}: "
return exception_class(f"{prefix}{error_message}")
return validation_exception(self._name, error_message, exception_class)
class ValueValidator(Validator, ABC, Generic[ValueT]):
@ -231,6 +265,15 @@ class DictValidator(Validator):
return self._validate_key(key=key, validator=validator, default=default)
@final
@classmethod
def _partial_validate_key(
cls, name: str, value: Any, key: str, validator: Type[ValidatorT]
) -> None:
value_dict = DictValidator(name=name, value=value)
if key in value_dict._dict:
validator.partial_validate(name=f"{name}.{key}", value=value_dict._dict[key])
class LiteralDictValidator(DictValidator):
"""DictValidator with exposed dict and keys method"""

View file

@ -202,3 +202,7 @@ 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)