Validate all preset StringFormatValidators on init (#79)

* fast fail

* update error msg in test
This commit is contained in:
Jesse Bannon 2022-06-28 09:34:30 -07:00 committed by GitHub
parent cb6dc8e034
commit 4447f456d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 191 additions and 27 deletions

View file

@ -5,6 +5,7 @@ from typing import List
from typing import Optional from typing import Optional
from typing import Tuple from typing import Tuple
from typing import Type from typing import Type
from typing import Union
from mergedeep import mergedeep 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.downloaders.downloader import DownloaderValidator
from ytdl_sub.plugins.plugin import Plugin from ytdl_sub.plugins.plugin import Plugin
from ytdl_sub.plugins.plugin import PluginOptions 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.utils.yaml import load_yaml
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator 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 OverridesStringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.validators import DictValidator from ytdl_sub.validators.validators import DictValidator
from ytdl_sub.validators.validators import StringValidator from ytdl_sub.validators.validators import StringValidator
from ytdl_sub.validators.validators import Validator from ytdl_sub.validators.validators import Validator
@ -144,26 +147,22 @@ class Preset(StrictDictValidator):
return plugins return plugins
def __validate_override_string_formatter_validator( def __validate_override_string_formatter_validator(
self, formatter_validator: OverridesStringFormatterValidator self,
formatter_validator: Union[StringFormatterValidator, OverridesStringFormatterValidator],
): ):
# Gather all resolvable override variables # Set the formatter variables to be the overrides
resolvable_override_variables: List[str] = [] variable_dict = self.overrides.dict_with_format_strings
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)
for variable_name in formatter_validator.format_variables: # If the formatter supports source variables, set the formatter variables to include
if variable_name not in resolvable_override_variables: # both source and override variables
# pylint: disable=protected-access if not isinstance(formatter_validator, OverridesStringFormatterValidator):
raise StringFormattingVariableNotFoundException( source_variables = {
f"Validation error in {formatter_validator._name}: " source_var: "dummy_string"
f"This can only use override variables. Available override variables are: " for source_var in self.downloader.downloader_entry_type.source_variables()
f"{', '.join(sorted(resolvable_override_variables))}", }
) variable_dict = dict(source_variables, **variable_dict)
# pylint: enable=protected-access
_ = formatter_validator.apply_formatter(variable_dict=variable_dict)
def __recursive_preset_validate( def __recursive_preset_validate(
self, self,
@ -184,8 +183,11 @@ class Preset(StrictDictValidator):
self.__recursive_preset_validate(validator._validator_dict) self.__recursive_preset_validate(validator._validator_dict)
# pylint: enable=protected-access # pylint: enable=protected-access
if isinstance(validator, OverridesStringFormatterValidator): if isinstance(validator, (StringFormatterValidator, OverridesStringFormatterValidator)):
self.__validate_override_string_formatter_validator(validator) 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): def __merge_parent_preset_dicts_if_present(self, config: ConfigFile):
parent_presets = set() parent_presets = set()

View file

@ -1,4 +1,5 @@
from typing import Dict from typing import Dict
from typing import List
from typing import final from typing import final
from yt_dlp.utils import sanitize_filename from yt_dlp.utils import sanitize_filename
@ -36,6 +37,16 @@ class SourceVariables:
""" """
return self.kwargs("extractor") 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 @final
def _to_dict(self) -> Dict[str, str]: def _to_dict(self) -> Dict[str, str]:
""" """
@ -43,10 +54,7 @@ class SourceVariables:
------- -------
Dictionary containing all variables Dictionary containing all variables
""" """
cls = self.__class__ return {source_var: getattr(self, source_var) for source_var in self.source_variables()}
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}
class EntryVariables(SourceVariables): class EntryVariables(SourceVariables):

View file

@ -39,6 +39,9 @@ class StringFormatterValidator(Validator):
_expected_value_type = str _expected_value_type = str
_expected_value_type_name = "format string" _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_]+?)}") __fields_validator = re.compile(r"{([a-z_]+?)}")
@ -103,14 +106,14 @@ class StringFormatterValidator(Validator):
def __apply_formatter( def __apply_formatter(
self, formatter: "StringFormatterValidator", variable_dict: Dict[str, str] self, formatter: "StringFormatterValidator", variable_dict: Dict[str, str]
) -> "StringFormatterValidator": ) -> "StringFormatterValidator":
"""TODO: test recursive case where variable not found"""
# Ensure the variable names exist within the entry and overrides # Ensure the variable names exist within the entry and overrides
for variable_name in formatter.format_variables: for variable_name in formatter.format_variables:
if variable_name not in variable_dict: if variable_name not in variable_dict:
available_fields = ", ".join(sorted(variable_dict.keys())) available_fields = ", ".join(sorted(variable_dict.keys()))
raise self._validation_exception( raise self._validation_exception(
f"Format variable '{variable_name}' does not exist. " self._variable_not_found_error_msg_formatter.format(
f"Available variables: {available_fields}", variable_name=variable_name, available_fields=available_fields
),
exception_class=StringFormattingVariableNotFoundException, exception_class=StringFormattingVariableNotFoundException,
) )
@ -165,6 +168,11 @@ class OverridesStringFormatterValidator(StringFormatterValidator):
:class:`nfo_output_directory <ytdl_sub.plugins.output_directory_nfo_tags.OutputDirectoryNfoTagsOptions>` :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 # pylint: enable=line-too-long

View file

View 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}"},
},
},
)

View file

@ -105,6 +105,11 @@ class TestStringFormatterValidator(object):
f"Validation error in test: Format variable 'bah_humbug' does not exist. " f"Validation error in test: Format variable 'bah_humbug' does not exist. "
f"Available variables: {', '.join(sorted(variable_dict.keys()))}" 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): with pytest.raises(StringFormattingException, match=expected_error_msg):
assert format_string.apply_formatter(variable_dict=variable_dict) assert format_string.apply_formatter(variable_dict=variable_dict)