Validate all preset StringFormatValidators on init (#79)
* fast fail * update error msg in test
This commit is contained in:
parent
cb6dc8e034
commit
4447f456d5
6 changed files with 191 additions and 27 deletions
|
|
@ -5,6 +5,7 @@ from typing import List
|
|||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import Union
|
||||
|
||||
from mergedeep import mergedeep
|
||||
|
||||
|
|
@ -18,10 +19,12 @@ from ytdl_sub.downloaders.downloader import Downloader
|
|||
from ytdl_sub.downloaders.downloader import DownloaderValidator
|
||||
from ytdl_sub.plugins.plugin import Plugin
|
||||
from ytdl_sub.plugins.plugin import PluginOptions
|
||||
from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException
|
||||
from ytdl_sub.utils.yaml import load_yaml
|
||||
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import OverridesDictFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
|
||||
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
|
||||
from ytdl_sub.validators.validators import DictValidator
|
||||
from ytdl_sub.validators.validators import StringValidator
|
||||
from ytdl_sub.validators.validators import Validator
|
||||
|
|
@ -144,26 +147,22 @@ class Preset(StrictDictValidator):
|
|||
return plugins
|
||||
|
||||
def __validate_override_string_formatter_validator(
|
||||
self, formatter_validator: OverridesStringFormatterValidator
|
||||
self,
|
||||
formatter_validator: Union[StringFormatterValidator, OverridesStringFormatterValidator],
|
||||
):
|
||||
# Gather all resolvable override variables
|
||||
resolvable_override_variables: List[str] = []
|
||||
for name, override_variable in self.overrides.dict.items():
|
||||
try:
|
||||
_ = override_variable.apply_formatter(self.overrides.dict_with_format_strings)
|
||||
except StringFormattingVariableNotFoundException:
|
||||
continue
|
||||
resolvable_override_variables.append(name)
|
||||
# Set the formatter variables to be the overrides
|
||||
variable_dict = self.overrides.dict_with_format_strings
|
||||
|
||||
for variable_name in formatter_validator.format_variables:
|
||||
if variable_name not in resolvable_override_variables:
|
||||
# pylint: disable=protected-access
|
||||
raise StringFormattingVariableNotFoundException(
|
||||
f"Validation error in {formatter_validator._name}: "
|
||||
f"This can only use override variables. Available override variables are: "
|
||||
f"{', '.join(sorted(resolvable_override_variables))}",
|
||||
)
|
||||
# pylint: enable=protected-access
|
||||
# If the formatter supports source variables, set the formatter variables to include
|
||||
# both source and override variables
|
||||
if not isinstance(formatter_validator, OverridesStringFormatterValidator):
|
||||
source_variables = {
|
||||
source_var: "dummy_string"
|
||||
for source_var in self.downloader.downloader_entry_type.source_variables()
|
||||
}
|
||||
variable_dict = dict(source_variables, **variable_dict)
|
||||
|
||||
_ = formatter_validator.apply_formatter(variable_dict=variable_dict)
|
||||
|
||||
def __recursive_preset_validate(
|
||||
self,
|
||||
|
|
@ -184,8 +183,11 @@ class Preset(StrictDictValidator):
|
|||
self.__recursive_preset_validate(validator._validator_dict)
|
||||
# pylint: enable=protected-access
|
||||
|
||||
if isinstance(validator, OverridesStringFormatterValidator):
|
||||
if isinstance(validator, (StringFormatterValidator, OverridesStringFormatterValidator)):
|
||||
self.__validate_override_string_formatter_validator(validator)
|
||||
if isinstance(validator, (DictFormatterValidator, OverridesDictFormatterValidator)):
|
||||
for validator_value in validator.dict.values():
|
||||
self.__validate_override_string_formatter_validator(validator_value)
|
||||
|
||||
def __merge_parent_preset_dicts_if_present(self, config: ConfigFile):
|
||||
parent_presets = set()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import final
|
||||
|
||||
from yt_dlp.utils import sanitize_filename
|
||||
|
|
@ -36,6 +37,16 @@ class SourceVariables:
|
|||
"""
|
||||
return self.kwargs("extractor")
|
||||
|
||||
@classmethod
|
||||
def source_variables(cls) -> List[str]:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
List of all source variables
|
||||
"""
|
||||
property_names = [prop for prop in dir(cls) if isinstance(getattr(cls, prop), property)]
|
||||
return property_names
|
||||
|
||||
@final
|
||||
def _to_dict(self) -> Dict[str, str]:
|
||||
"""
|
||||
|
|
@ -43,10 +54,7 @@ class SourceVariables:
|
|||
-------
|
||||
Dictionary containing all variables
|
||||
"""
|
||||
cls = self.__class__
|
||||
property_names = [prop for prop in dir(cls) if isinstance(getattr(cls, prop), property)]
|
||||
|
||||
return {property_name: getattr(self, property_name) for property_name in property_names}
|
||||
return {source_var: getattr(self, source_var) for source_var in self.source_variables()}
|
||||
|
||||
|
||||
class EntryVariables(SourceVariables):
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ class StringFormatterValidator(Validator):
|
|||
|
||||
_expected_value_type = str
|
||||
_expected_value_type_name = "format string"
|
||||
_variable_not_found_error_msg_formatter = (
|
||||
"Format variable '{variable_name}' does not exist. Available variables: {available_fields}"
|
||||
)
|
||||
|
||||
__fields_validator = re.compile(r"{([a-z_]+?)}")
|
||||
|
||||
|
|
@ -103,14 +106,14 @@ class StringFormatterValidator(Validator):
|
|||
def __apply_formatter(
|
||||
self, formatter: "StringFormatterValidator", variable_dict: Dict[str, str]
|
||||
) -> "StringFormatterValidator":
|
||||
"""TODO: test recursive case where variable not found"""
|
||||
# Ensure the variable names exist within the entry and overrides
|
||||
for variable_name in formatter.format_variables:
|
||||
if variable_name not in variable_dict:
|
||||
available_fields = ", ".join(sorted(variable_dict.keys()))
|
||||
raise self._validation_exception(
|
||||
f"Format variable '{variable_name}' does not exist. "
|
||||
f"Available variables: {available_fields}",
|
||||
self._variable_not_found_error_msg_formatter.format(
|
||||
variable_name=variable_name, available_fields=available_fields
|
||||
),
|
||||
exception_class=StringFormattingVariableNotFoundException,
|
||||
)
|
||||
|
||||
|
|
@ -165,6 +168,11 @@ class OverridesStringFormatterValidator(StringFormatterValidator):
|
|||
:class:`nfo_output_directory <ytdl_sub.plugins.output_directory_nfo_tags.OutputDirectoryNfoTagsOptions>`
|
||||
"""
|
||||
|
||||
_variable_not_found_error_msg_formatter = (
|
||||
"Override variable '{variable_name}' does not exist. "
|
||||
"Available override variables: {available_fields}"
|
||||
)
|
||||
|
||||
|
||||
# pylint: enable=line-too-long
|
||||
|
||||
|
|
|
|||
0
tests/unit/config/__init__.py
Normal file
0
tests/unit/config/__init__.py
Normal file
141
tests/unit/config/test_preset.py
Normal file
141
tests/unit/config/test_preset.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
from typing import Dict
|
||||
|
||||
import pytest
|
||||
|
||||
from ytdl_sub.config.config_file import ConfigFile
|
||||
from ytdl_sub.config.preset import Preset
|
||||
from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_file() -> ConfigFile:
|
||||
return ConfigFile(
|
||||
name="config", value={"configuration": {"working_directory": "."}, "presets": {}}
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output_options() -> Dict:
|
||||
return {
|
||||
"output_directory": "some dir",
|
||||
"file_name": "{uid}",
|
||||
}
|
||||
|
||||
|
||||
class TestPreset:
|
||||
@pytest.mark.parametrize(
|
||||
"source, download_strategy",
|
||||
[
|
||||
("youtube", {"download_strategy": "video", "video_url": "youtube.com/watch?v=123abc"}),
|
||||
(
|
||||
"youtube",
|
||||
{
|
||||
"download_strategy": "playlist",
|
||||
"playlist_url": "youtube.com/playlist?list=123abc",
|
||||
},
|
||||
),
|
||||
("youtube", {"download_strategy": "channel", "channel_url": "youtube.com/c/123abc"}),
|
||||
(
|
||||
"soundcloud",
|
||||
{"download_strategy": "albums_and_singles", "url": "soundcloud.com/123abc"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_bare_minimum_preset(self, config_file, output_options, source, download_strategy):
|
||||
_ = Preset(
|
||||
config=config_file,
|
||||
name="test",
|
||||
value={source: download_strategy, "output_options": output_options},
|
||||
)
|
||||
|
||||
def test_preset_with_override_variable(self, config_file, output_options):
|
||||
_ = Preset(
|
||||
config=config_file,
|
||||
name="test",
|
||||
value={
|
||||
"youtube": {
|
||||
"download_strategy": "video",
|
||||
"video_url": "youtube.com/watch?v=123abc",
|
||||
},
|
||||
"output_options": {"output_directory": "dir", "file_name": "{dne_var}"},
|
||||
"overrides": {"dne_var": "not dne"},
|
||||
},
|
||||
)
|
||||
|
||||
def test_preset_error__source_variable_does_not_exist(self, config_file, output_options):
|
||||
with pytest.raises(
|
||||
StringFormattingVariableNotFoundException,
|
||||
match="Format variable 'dne_var' does not exist",
|
||||
):
|
||||
_ = Preset(
|
||||
config=config_file,
|
||||
name="test",
|
||||
value={
|
||||
"youtube": {
|
||||
"download_strategy": "video",
|
||||
"video_url": "youtube.com/watch?v=123abc",
|
||||
},
|
||||
"output_options": {"output_directory": "dir", "file_name": "{dne_var}"},
|
||||
},
|
||||
)
|
||||
|
||||
def test_preset_error__override_variable_does_not_exist(self, config_file, output_options):
|
||||
with pytest.raises(
|
||||
StringFormattingVariableNotFoundException,
|
||||
match="Override variable 'dne_var' does not exist",
|
||||
):
|
||||
_ = Preset(
|
||||
config=config_file,
|
||||
name="test",
|
||||
value={
|
||||
"youtube": {
|
||||
"download_strategy": "video",
|
||||
"video_url": "youtube.com/watch?v=123abc",
|
||||
},
|
||||
"output_options": {"output_directory": "{dne_var}", "file_name": "file"},
|
||||
},
|
||||
)
|
||||
|
||||
def test_preset_error__dict_source_variable_does_not_exist(self, config_file, output_options):
|
||||
with pytest.raises(
|
||||
StringFormattingVariableNotFoundException,
|
||||
match="Format variable 'dne_var' does not exist",
|
||||
):
|
||||
_ = Preset(
|
||||
config=config_file,
|
||||
name="test",
|
||||
value={
|
||||
"youtube": {
|
||||
"download_strategy": "video",
|
||||
"video_url": "youtube.com/watch?v=123abc",
|
||||
},
|
||||
"output_options": {"output_directory": "dir", "file_name": "file"},
|
||||
"nfo_tags": {
|
||||
"nfo_name": "the nfo name",
|
||||
"nfo_root": "the root",
|
||||
"tags": {"tag_a": "{dne_var}"},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_preset_error__dict_override_variable_does_not_exist(self, config_file, output_options):
|
||||
with pytest.raises(
|
||||
StringFormattingVariableNotFoundException,
|
||||
match="Override variable 'dne_var' does not exist",
|
||||
):
|
||||
_ = Preset(
|
||||
config=config_file,
|
||||
name="test",
|
||||
value={
|
||||
"youtube": {
|
||||
"download_strategy": "video",
|
||||
"video_url": "youtube.com/watch?v=123abc",
|
||||
},
|
||||
"output_options": {"output_directory": "dir", "file_name": "file"},
|
||||
"output_directory_nfo_tags": {
|
||||
"nfo_name": "the nfo name",
|
||||
"nfo_root": "the root",
|
||||
"tags": {"tag_a": "{dne_var}"},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -105,6 +105,11 @@ class TestStringFormatterValidator(object):
|
|||
f"Validation error in test: Format variable 'bah_humbug' does not exist. "
|
||||
f"Available variables: {', '.join(sorted(variable_dict.keys()))}"
|
||||
)
|
||||
if string_formatter_class == OverridesStringFormatterValidator:
|
||||
expected_error_msg = (
|
||||
f"Validation error in test: Override variable 'bah_humbug' does not exist. "
|
||||
f"Available override variables: {', '.join(sorted(variable_dict.keys()))}"
|
||||
)
|
||||
|
||||
with pytest.raises(StringFormattingException, match=expected_error_msg):
|
||||
assert format_string.apply_formatter(variable_dict=variable_dict)
|
||||
|
|
|
|||
Loading…
Reference in a new issue