[BACKEND] Partially validate all presets in configs
This commit is contained in:
parent
3e4a995f6e
commit
a71577e703
10 changed files with 111 additions and 10 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from abc import ABC
|
||||
from collections import defaultdict
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Set
|
||||
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue