[BACKEND] Validate override variable names (#845)
Validate as early as possible whether override names are invalid, to prevent confusing downstream errors
This commit is contained in:
parent
e92b1cd12a
commit
5d1fd0d7c6
6 changed files with 61 additions and 42 deletions
|
|
@ -11,6 +11,7 @@ from ytdl_sub.entries.variables.override_variables import SUBSCRIPTION_NAME
|
||||||
from ytdl_sub.entries.variables.override_variables import OverrideVariables
|
from ytdl_sub.entries.variables.override_variables import OverrideVariables
|
||||||
from ytdl_sub.script.parser import parse
|
from ytdl_sub.script.parser import parse
|
||||||
from ytdl_sub.script.script import Script
|
from ytdl_sub.script.script import Script
|
||||||
|
from ytdl_sub.utils.exceptions import InvalidVariableNameException
|
||||||
from ytdl_sub.utils.exceptions import ValidationException
|
from ytdl_sub.utils.exceptions import ValidationException
|
||||||
from ytdl_sub.utils.script import ScriptUtils
|
from ytdl_sub.utils.script import ScriptUtils
|
||||||
from ytdl_sub.utils.scriptable import Scriptable
|
from ytdl_sub.utils.scriptable import Scriptable
|
||||||
|
|
@ -86,16 +87,26 @@ class Overrides(DictFormatterValidator, Scriptable):
|
||||||
"""
|
"""
|
||||||
Ensures the variable name does not collide with any entry variables or built-in functions.
|
Ensures the variable name does not collide with any entry variables or built-in functions.
|
||||||
"""
|
"""
|
||||||
|
if not OverrideVariables.is_valid_name(name):
|
||||||
|
override_type = "function" if name.startswith("%") else "variable"
|
||||||
|
raise self._validation_exception(
|
||||||
|
f"Override {override_type} with name {name} is invalid. Names must be"
|
||||||
|
" lower_snake_cased and begin with a letter.",
|
||||||
|
exception_class=InvalidVariableNameException,
|
||||||
|
)
|
||||||
|
|
||||||
if OverrideVariables.is_entry_variable_name(name):
|
if OverrideVariables.is_entry_variable_name(name):
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
f"Override variable with name {name} cannot be used since it is a"
|
f"Override variable with name {name} cannot be used since it is a"
|
||||||
" built-in ytdl-sub entry variable name."
|
" built-in ytdl-sub entry variable name.",
|
||||||
|
exception_class=InvalidVariableNameException,
|
||||||
)
|
)
|
||||||
|
|
||||||
if OverrideVariables.is_function_name(name):
|
if OverrideVariables.is_function_name(name):
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
f"Override function definition with name {name} cannot be used since it is"
|
f"Override function definition with name {name} cannot be used since it is"
|
||||||
" a built-in ytdl-sub function name."
|
" a built-in ytdl-sub function name.",
|
||||||
|
exception_class=InvalidVariableNameException,
|
||||||
)
|
)
|
||||||
|
|
||||||
def initial_variables(
|
def initial_variables(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS
|
from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS
|
||||||
from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS
|
from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS
|
||||||
from ytdl_sub.script.functions import Functions
|
from ytdl_sub.script.functions import Functions
|
||||||
|
from ytdl_sub.script.utils.name_validation import is_valid_name
|
||||||
|
|
||||||
SUBSCRIPTION_NAME = "subscription_name"
|
SUBSCRIPTION_NAME = "subscription_name"
|
||||||
SUBSCRIPTION_VALUE = "subscription_value"
|
SUBSCRIPTION_VALUE = "subscription_value"
|
||||||
|
|
@ -81,3 +82,15 @@ class OverrideVariables:
|
||||||
if name.startswith("%"):
|
if name.startswith("%"):
|
||||||
return name in CUSTOM_FUNCTION_SCRIPTS or Functions.is_built_in(name[1:])
|
return name in CUSTOM_FUNCTION_SCRIPTS or Functions.is_built_in(name[1:])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_valid_name(cls, name: str) -> bool:
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
True if the override name itself is valid. False otherwise.
|
||||||
|
"""
|
||||||
|
if name.startswith("%"):
|
||||||
|
return is_valid_name(name=name[1:])
|
||||||
|
|
||||||
|
return is_valid_name(name=name)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
from ytdl_sub.script.utils.name_validation import is_valid_name
|
||||||
from ytdl_sub.utils.exceptions import InvalidVariableNameException
|
from ytdl_sub.utils.exceptions import InvalidVariableNameException
|
||||||
from ytdl_sub.validators.string_formatter_validators import is_valid_source_variable_name
|
|
||||||
from ytdl_sub.validators.validators import ListValidator
|
from ytdl_sub.validators.validators import ListValidator
|
||||||
from ytdl_sub.validators.validators import StringValidator
|
from ytdl_sub.validators.validators import StringValidator
|
||||||
|
|
||||||
|
|
@ -9,10 +9,13 @@ class SourceVariableNameValidator(StringValidator):
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
try:
|
|
||||||
_ = is_valid_source_variable_name(self.value, raise_exception=True)
|
if not is_valid_name(value):
|
||||||
except InvalidVariableNameException as exc:
|
raise self._validation_exception(
|
||||||
raise self._validation_exception(exc) from exc
|
f"Variable with name {name} is invalid. Names must be"
|
||||||
|
" lower_snake_cased and begin with a letter.",
|
||||||
|
exception_class=InvalidVariableNameException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SourceVariableNameListValidator(ListValidator[SourceVariableNameValidator]):
|
class SourceVariableNameListValidator(ListValidator[SourceVariableNameValidator]):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import re
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
@ -9,7 +8,6 @@ from ytdl_sub.script.parser import parse
|
||||||
from ytdl_sub.script.script import Script
|
from ytdl_sub.script.script import Script
|
||||||
from ytdl_sub.script.utils.exceptions import UserException
|
from ytdl_sub.script.utils.exceptions import UserException
|
||||||
from ytdl_sub.script.utils.exceptions import VariableDoesNotExist
|
from ytdl_sub.script.utils.exceptions import VariableDoesNotExist
|
||||||
from ytdl_sub.utils.exceptions import InvalidVariableNameException
|
|
||||||
from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException
|
from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException
|
||||||
from ytdl_sub.validators.validators import DictValidator
|
from ytdl_sub.validators.validators import DictValidator
|
||||||
from ytdl_sub.validators.validators import ListValidator
|
from ytdl_sub.validators.validators import ListValidator
|
||||||
|
|
@ -17,38 +15,6 @@ from ytdl_sub.validators.validators import LiteralDictValidator
|
||||||
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
|
||||||
|
|
||||||
_fields_validator = re.compile(r"{([a-z][a-z0-9_]*?)}")
|
|
||||||
|
|
||||||
_fields_validator_exception_message: str = (
|
|
||||||
"{variable_names} must start with a lowercase letter, should only contain lowercase letters, "
|
|
||||||
"numbers, underscores, and have a single open and close bracket."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_valid_source_variable_name(input_str: str, raise_exception: bool = False) -> bool:
|
|
||||||
"""
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
input_str
|
|
||||||
String to see if it can be a source variable
|
|
||||||
raise_exception
|
|
||||||
Raise InvalidVariableNameException False.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
True if it is. False otherwise.
|
|
||||||
|
|
||||||
Raises
|
|
||||||
------
|
|
||||||
InvalidVariableNameException
|
|
||||||
If raise_exception and output is False
|
|
||||||
"""
|
|
||||||
# Add brackets around it to pretend its a StringFormatter, see if it captures
|
|
||||||
is_source_variable_name = len(re.findall(_fields_validator, f"{{{input_str}}}")) > 0
|
|
||||||
if not is_source_variable_name and raise_exception:
|
|
||||||
raise InvalidVariableNameException(_fields_validator_exception_message)
|
|
||||||
return is_source_variable_name
|
|
||||||
|
|
||||||
|
|
||||||
class StringFormatterValidator(StringValidator):
|
class StringFormatterValidator(StringValidator):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,33 @@ class TestPreset:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"name", ["!ack", "*asfsaf", "1234352", "--234asdf", "___asdf", "1asdfasdfasd"]
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("is_function", [True, False])
|
||||||
|
def test_preset_error_overrides_invalid_variable_name(
|
||||||
|
self, config_file, youtube_video, output_options, name: str, is_function: bool
|
||||||
|
):
|
||||||
|
name_type = "function" if is_function else "variable"
|
||||||
|
name = f"%{name}" if is_function else name
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ValidationException,
|
||||||
|
match=re.escape(
|
||||||
|
f"Override {name_type} with name {name} is invalid."
|
||||||
|
" Names must be lower_snake_cased and begin with a letter."
|
||||||
|
),
|
||||||
|
):
|
||||||
|
_ = Preset(
|
||||||
|
config=config_file,
|
||||||
|
name="test",
|
||||||
|
value={
|
||||||
|
"download": youtube_video,
|
||||||
|
"output_options": output_options,
|
||||||
|
"overrides": {name: "ack"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_preset_error_added_url_variable_cannot_resolve(self, config_file, output_options):
|
def test_preset_error_added_url_variable_cannot_resolve(self, config_file, output_options):
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
ValidationException,
|
ValidationException,
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
Loading…
Reference in a new issue