[BACKEND] Add regex validators (#97)

* [BACKEND] Add regex validators
This commit is contained in:
Jesse Bannon 2022-07-10 17:47:55 -07:00 committed by GitHub
parent f318cf6fcb
commit 119a0c212f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 214 additions and 3 deletions

View file

@ -43,13 +43,28 @@ class Plugin(DownloadArchiver, Generic[PluginOptionsT], ABC):
# TODO pass yaml snake case name in the class somewhere, and use it for the logger # TODO pass yaml snake case name in the class somewhere, and use it for the logger
self._logger = Logger.get(self.__class__.__name__) self._logger = Logger.get(self.__class__.__name__)
def post_process_entry(self, entry: Entry) -> Optional[FileMetadata]: def modify_entry(self, entry: Entry) -> Optional[Entry]:
""" """
For each file downloaded, apply post processing to it. For each entry downloaded, modify the entry in some way before sending it to
post-processing.
Parameters Parameters
---------- ----------
entry: entry
Entry to modify
Returns
-------
The entry or None, indicating not to move it to the output directory
"""
def post_process_entry(self, entry: Entry) -> Optional[FileMetadata]:
"""
For each entry downloaded, apply post processing to it.
Parameters
----------
entry
Entry to post process Entry to post process
Returns Returns

View file

@ -0,0 +1,101 @@
import re
from typing import List
from typing import Optional
from ytdl_sub.validators.validators import ListValidator
from ytdl_sub.validators.validators import StringValidator
class RegexValidator(StringValidator):
_expected_value_type_name = "regex"
def __init__(self, name, value):
super().__init__(name, value)
try:
self._compiled_regex = re.compile(self.value)
except Exception as exc:
raise self._validation_exception(
error_message=f"invalid regex: '{self.value}'"
) from exc
@property
def num_capture_groups(self) -> int:
"""
Returns
-------
Number of capture groups in the regex
"""
return self._compiled_regex.groups
def is_match(self, input_str: str) -> bool:
"""
Parameters
----------
input_str
String to match against the regex
Returns
-------
True if input_str matches. False otherwise.
"""
return self._compiled_regex.search(input_str) is not None
def capture(self, input_str: str) -> Optional[List[str]]:
"""
Parameters
----------
input_str
String to try to regex capture from
Returns
-------
List of captures (will always be >= 1). None if there are no captures.
"""
if match := self._compiled_regex.search(input_str):
if len(to_return := list(match.groups())) > 0:
return to_return
return None
class RegexListValidator(ListValidator[RegexValidator]):
_expected_value_type_name = "regex list"
_inner_list_type = RegexValidator
def __init__(self, name, value):
super().__init__(name, value)
if len(set(reg.num_capture_groups for reg in self._list)) > 1:
raise self._validation_exception(
"each regex in a list must have the same number of capture groups"
)
def matches_any(self, input_str: str) -> bool:
"""
Parameters
----------
input_str
String to match against any regexes in the list
Returns
-------
True if at least one matches. False if none match
"""
return any(reg.is_match(input_str) for reg in self._list)
def capture_any(self, input_str: str) -> Optional[List[str]]:
"""
Parameters
----------
input_str
String to try to regex capture from any of the regexes in the list
Returns
-------
List of captures (will always be >= 1) on the first regex that matches. None if
no regexes match.
"""
for reg in self._list:
if maybe_capture := reg.capture(input_str):
return maybe_capture
return None

View file

@ -2,6 +2,7 @@ import copy
from abc import ABC from abc import ABC
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Generic
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Type from typing import Type
@ -94,6 +95,24 @@ class StringValidator(Validator):
T = TypeVar("T", bound=Validator) T = TypeVar("T", bound=Validator)
class ListValidator(Validator, ABC, Generic[T]):
"""
Validates a list of objects to validate
"""
_expected_value_type = list
_expected_value_type_name = "list"
_inner_list_type: Type[T]
def __init__(self, name, value):
super().__init__(name, value)
self._list: List[T] = [
self._inner_list_type(name=f"{name}.{i+1}", value=val)
for i, val in enumerate(self._value)
]
class DictValidator(Validator): class DictValidator(Validator):
""" """
Validates dictionary-based fields. Errors to them as 'object's since this could be validating Validates dictionary-based fields. Errors to them as 'object's since this could be validating

View file

@ -0,0 +1,76 @@
import pytest
from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.validators.regex_validator import RegexListValidator
from ytdl_sub.validators.regex_validator import RegexValidator
@pytest.mark.parametrize(
"regex_value, input_str, matches, captures",
[
("^match this$", "match this", True, None),
("^my (.+) cap", "my first cap", True, ["first"]),
(".* (.+) - (.+) two", "my other - capped two", True, ["other", "capped"]),
("failed match", "nope", False, None),
],
)
def test_regex_validator(regex_value, input_str, matches, captures):
regex_validator = RegexValidator(name="good_regex_validator", value=regex_value)
assert regex_validator._name == "good_regex_validator"
assert regex_validator._value == regex_value
assert regex_validator.is_match(input_str=input_str) is matches
assert regex_validator.capture(input_str=input_str) == captures
@pytest.mark.parametrize("regex_value", ["(", "??"])
def test_regex_validator_fails_bad_value(regex_value):
with pytest.raises(ValidationException, match="Validation error in fail: invalid regex"):
_ = RegexValidator(name="fail", value=regex_value)
def test_regex_list_validator():
regex_list_validator_raw_value = ["try matching this", "try matching that", "how about this"]
regex_list = RegexListValidator(name="list val", value=regex_list_validator_raw_value)
for raw_val in regex_list_validator_raw_value:
assert regex_list.matches_any(raw_val)
assert regex_list.capture_any(raw_val) is None
def test_regex_list_validator_capture():
regex_list_validator_raw_value = ["try (.+) this", "try (.+) that", "how (.+) this"]
regex_list = RegexListValidator(name="list val", value=regex_list_validator_raw_value)
captures_test = [
("try first this", "first"),
("try second that", "second"),
("how third this", "third"),
]
for capture_str, expected_capture in captures_test:
assert regex_list.matches_any(capture_str)
assert regex_list.capture_any(capture_str) == [expected_capture]
def test_regex_list_validator_invalid_regex():
with pytest.raises(ValidationException, match="Validation error in fail\.2: invalid regex"):
_ = RegexListValidator(name="fail", value=["first is good", "second ( bad", "third okay"])
def test_regex_list_validator_inconsistent_capture_groups():
with pytest.raises(
ValidationException,
match=(
"Validation error in fail: "
"each regex in a list must have the same number of capture groups"
),
):
_ = RegexListValidator(
name="fail",
value=[
"first with one (.+) cap group",
"two (.+) has two (.) cap groups",
],
)