parent
f318cf6fcb
commit
119a0c212f
4 changed files with 214 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
101
src/ytdl_sub/validators/regex_validator.py
Normal file
101
src/ytdl_sub/validators/regex_validator.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
76
tests/unit/validators/test_regex_validator.py
Normal file
76
tests/unit/validators/test_regex_validator.py
Normal 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",
|
||||
],
|
||||
)
|
||||
Loading…
Reference in a new issue