[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
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
----------
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
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 typing import Any
from typing import Dict
from typing import Generic
from typing import List
from typing import Optional
from typing import Type
@ -94,6 +95,24 @@ class StringValidator(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):
"""
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",
],
)