[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.
|
# and ensure required keys are present.
|
||||||
_optional_keys = PRESET_KEYS
|
_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
|
@property
|
||||||
def _source_variables(self) -> List[str]:
|
def _source_variables(self) -> List[str]:
|
||||||
return Entry.source_variables()
|
return Entry.source_variables()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
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 OverridesStringFormatterValidator
|
||||||
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
|
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
|
||||||
from ytdl_sub.validators.validators import BoolValidator
|
from ytdl_sub.validators.validators import BoolValidator
|
||||||
|
from ytdl_sub.validators.validators import DictValidator
|
||||||
from ytdl_sub.validators.validators import LiteralDictValidator
|
from ytdl_sub.validators.validators import LiteralDictValidator
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -182,6 +184,13 @@ class OutputOptions(StrictDictValidator):
|
||||||
"keep_files_after",
|
"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):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -42,6 +43,12 @@ class CollectionUrlValidator(StrictDictValidator):
|
||||||
_required_keys = {"url"}
|
_required_keys = {"url"}
|
||||||
_optional_keys = {"variables", "source_thumbnails", "playlist_thumbnails"}
|
_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):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -34,6 +35,14 @@ class YoutubeChannelDownloaderOptions(DownloaderValidator):
|
||||||
"channel_banner_path",
|
"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):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self._channel_url = self._validate_key(
|
self._channel_url = self._validate_key(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import os.path
|
import os.path
|
||||||
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
@ -27,7 +28,14 @@ class AudioExtractOptions(PluginOptions):
|
||||||
quality: 128
|
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):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import os
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -28,16 +29,21 @@ class SharedNfoTagsOptions(PluginOptions):
|
||||||
_required_keys = {"nfo_name", "nfo_root", "tags"}
|
_required_keys = {"nfo_name", "nfo_root", "tags"}
|
||||||
_optional_keys = {"kodi_safe"}
|
_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):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
||||||
self._nfo_name = self._validate_key_if_present(
|
self._nfo_name = self._validate_key(key="nfo_name", validator=StringFormatterValidator)
|
||||||
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_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(
|
self._kodi_safe = self._validate_key_if_present(
|
||||||
key="kodi_safe", validator=BoolValidator, default=False
|
key="kodi_safe", validator=BoolValidator, default=False
|
||||||
).value
|
).value
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Any
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,29 @@ ValidationExceptionT = TypeVar("ValidationExceptionT", bound=ValidationException
|
||||||
ValidatorT = TypeVar("ValidatorT", bound="Validator")
|
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):
|
class Validator(ABC):
|
||||||
"""
|
"""
|
||||||
Used to validate the value of a python object. This is the 'base' class that will first
|
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
|
# When raising an error, call the type this value instead of its python name
|
||||||
_expected_value_type_name: Optional[str] = None
|
_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):
|
def __init__(self, name: str, value: Any):
|
||||||
self._name = name
|
self._name = name
|
||||||
self._value = copy.deepcopy(value) # Always deep copy to avoid editing references
|
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.
|
Validation exception with a consistent prefix.
|
||||||
"""
|
"""
|
||||||
prefix = f"Validation error in {self._name}: "
|
return validation_exception(self._name, error_message, exception_class)
|
||||||
return exception_class(f"{prefix}{error_message}")
|
|
||||||
|
|
||||||
|
|
||||||
class ValueValidator(Validator, ABC, Generic[ValueT]):
|
class ValueValidator(Validator, ABC, Generic[ValueT]):
|
||||||
|
|
@ -231,6 +265,15 @@ class DictValidator(Validator):
|
||||||
|
|
||||||
return self._validate_key(key=key, validator=validator, default=default)
|
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):
|
class LiteralDictValidator(DictValidator):
|
||||||
"""DictValidator with exposed dict and keys method"""
|
"""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