Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BACKEND] Validate override variable names #845

Merged
merged 1 commit into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/ytdl_sub/config/overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ytdl_sub.entries.variables.override_variables import OverrideVariables
from ytdl_sub.script.parser import parse
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.script import ScriptUtils
from ytdl_sub.utils.scriptable import Scriptable
Expand Down Expand Up @@ -86,16 +87,26 @@ def ensure_variable_name_valid(self, name: str) -> None:
"""
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):
raise self._validation_exception(
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):
raise self._validation_exception(
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(
Expand Down
13 changes: 13 additions & 0 deletions src/ytdl_sub/entries/variables/override_variables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ytdl_sub.entries.script.function_scripts import CUSTOM_FUNCTION_SCRIPTS
from ytdl_sub.entries.script.variable_scripts import VARIABLE_SCRIPTS
from ytdl_sub.script.functions import Functions
from ytdl_sub.script.utils.name_validation import is_valid_name

SUBSCRIPTION_NAME = "subscription_name"
SUBSCRIPTION_VALUE = "subscription_value"
Expand Down Expand Up @@ -81,3 +82,15 @@ def is_function_name(cls, name: str) -> bool:
if name.startswith("%"):
return name in CUSTOM_FUNCTION_SCRIPTS or Functions.is_built_in(name[1:])
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)
13 changes: 8 additions & 5 deletions src/ytdl_sub/validators/source_variable_validator.py
Original file line number Diff line number Diff line change
@@ -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.validators.string_formatter_validators import is_valid_source_variable_name
from ytdl_sub.validators.validators import ListValidator
from ytdl_sub.validators.validators import StringValidator

Expand All @@ -9,10 +9,13 @@ class SourceVariableNameValidator(StringValidator):

def __init__(self, name, value):
super().__init__(name, value)
try:
_ = is_valid_source_variable_name(self.value, raise_exception=True)
except InvalidVariableNameException as exc:
raise self._validation_exception(exc) from exc

if not is_valid_name(value):
raise self._validation_exception(
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]):
Expand Down
34 changes: 0 additions & 34 deletions src/ytdl_sub/validators/string_formatter_validators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import re
from typing import Dict
from typing import Set
from typing import Union
Expand All @@ -9,46 +8,13 @@
from ytdl_sub.script.script import Script
from ytdl_sub.script.utils.exceptions import UserException
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.validators.validators import DictValidator
from ytdl_sub.validators.validators import ListValidator
from ytdl_sub.validators.validators import LiteralDictValidator
from ytdl_sub.validators.validators import StringValidator
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):
"""
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/config/test_preset.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,33 @@ def test_preset_error_override_added_variable_collides_with_override(
},
)

@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):
with pytest.raises(
ValidationException,
Expand Down
1 change: 0 additions & 1 deletion tests/unit/plugins/test_regex_capture.py

This file was deleted.

Loading