Skip to content

Commit a097c6a

Browse files
authored
[BACKEND][v2] More flexible formatter post_process (#1424)
Makes the underlying formatting process better handle native (non-string) types in a more flexible way. 2nd attempt since the last one got reverted due to breaking changes.
1 parent 97ecae7 commit a097c6a

16 files changed

Lines changed: 97 additions & 112 deletions

src/ytdl_sub/config/overrides.py

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from typing import Iterable
44
from typing import Optional
55
from typing import Set
6+
from typing import Type
7+
from typing import TypeVar
68

79
from ytdl_sub.entries.entry import Entry
810
from ytdl_sub.entries.script.variable_definitions import VARIABLES
@@ -20,10 +22,11 @@
2022
from ytdl_sub.utils.exceptions import ValidationException
2123
from ytdl_sub.utils.script import ScriptUtils
2224
from ytdl_sub.utils.scriptable import Scriptable
23-
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
2425
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
2526
from ytdl_sub.validators.string_formatter_validators import UnstructuredDictFormatterValidator
2627

28+
ExpectedT = TypeVar("ExpectedT")
29+
2730

2831
class Overrides(UnstructuredDictFormatterValidator, Scriptable):
2932
"""
@@ -207,7 +210,8 @@ def apply_formatter(
207210
formatter: StringFormatterValidator,
208211
entry: Optional[Entry] = None,
209212
function_overrides: Optional[Dict[str, str]] = None,
210-
) -> str:
213+
expected_type: Type[ExpectedT] = str,
214+
) -> ExpectedT:
211215
"""
212216
Parameters
213217
----------
@@ -217,6 +221,8 @@ def apply_formatter(
217221
Optional. Entry to add source variables to the formatter
218222
function_overrides
219223
Optional. Explicit values to override the overrides themselves and source variables
224+
expected_type
225+
The expected type that should return. Defaults to string.
220226
221227
Returns
222228
-------
@@ -227,42 +233,15 @@ def apply_formatter(
227233
StringFormattingException
228234
If the formatter that is trying to be resolved cannot
229235
"""
230-
return formatter.post_process(
231-
str(
232-
self._apply_to_resolvable(
233-
formatter=formatter, entry=entry, function_overrides=function_overrides
234-
)
235-
)
236-
)
237-
238-
def apply_overrides_formatter_to_native(
239-
self,
240-
formatter: OverridesStringFormatterValidator,
241-
function_overrides: Optional[Dict[str, str]] = None,
242-
) -> Any:
243-
"""
244-
Parameters
245-
----------
246-
formatter
247-
Overrides formatter to apply
248-
function_overrides
249-
Optional. Explicit values to override the overrides themselves and source variables
250-
251-
Returns
252-
-------
253-
The native python form of the resolved variable
254-
"""
255-
return formatter.post_process_native(
236+
out = formatter.post_process(
256237
self._apply_to_resolvable(
257-
formatter=formatter, entry=None, function_overrides=function_overrides
238+
formatter=formatter, entry=entry, function_overrides=function_overrides
258239
).native
259240
)
260241

261-
def evaluate_boolean(
262-
self, formatter: StringFormatterValidator, entry: Optional[Entry] = None
263-
) -> bool:
264-
"""
265-
Apply a formatter, and evaluate it to a boolean
266-
"""
267-
output = self.apply_formatter(formatter=formatter, entry=entry)
268-
return ScriptUtils.bool_formatter_output(output)
242+
if not isinstance(out, expected_type):
243+
raise StringFormattingException(
244+
f"Expected type {expected_type.__name__}, but received '{out.__class__.__name__}'"
245+
)
246+
247+
return out

src/ytdl_sub/config/plugin/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def is_enabled(self) -> bool:
4848
Returns True if enabled, False if disabled.
4949
"""
5050
if isinstance(self.plugin_options, ToggleableOptionsDictValidator):
51-
return self.overrides.evaluate_boolean(self.plugin_options.enable)
51+
return self.overrides.apply_formatter(self.plugin_options.enable, expected_type=bool)
5252
return True
5353

5454
def ytdl_options_match_filters(self) -> Tuple[List[str], List[str]]:

src/ytdl_sub/config/preset_options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def to_native_dict(self, overrides: Overrides) -> Dict:
6363
native python.
6464
"""
6565
out = {
66-
key: overrides.apply_overrides_formatter_to_native(val)
66+
key: overrides.apply_formatter(val, expected_type=object)
6767
for key, val in self.dict.items()
6868
}
6969
if "cookiefile" in out:

src/ytdl_sub/downloaders/url/downloader.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ def _match_entry_to_url_validator(self, entry: Entry) -> UrlValidator:
5252

5353
if 0 <= input_url_idx < len(self.plugin_options.urls.list):
5454
validator = self.plugin_options.urls.list[input_url_idx]
55-
if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url):
55+
if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list):
5656
return validator
5757

5858
# Match the first validator based on the URL, if one exists
5959
for validator in self.plugin_options.urls.list:
60-
if entry_input_url in self.overrides.apply_overrides_formatter_to_native(validator.url):
60+
if entry_input_url in self.overrides.apply_formatter(validator.url, expected_type=list):
6161
return validator
6262

6363
# Return the first validator if none exist
@@ -382,7 +382,7 @@ def _iterate_child_entries(
382382
entries_to_iter: List[Optional[Entry]] = entries
383383

384384
indices = list(range(len(entries_to_iter)))
385-
if self.overrides.evaluate_boolean(validator.download_reverse):
385+
if self.overrides.apply_formatter(validator.download_reverse, expected_type=bool):
386386
indices = reversed(indices)
387387

388388
for idx in indices:
@@ -461,8 +461,8 @@ def _download_metadata(self, url: str, validator: UrlValidator) -> Iterable[Entr
461461
ytdl_option_overrides=validator.ytdl_options.to_native_dict(self.overrides)
462462
)
463463

464-
include_sibling_metadata = self.overrides.evaluate_boolean(
465-
validator.include_sibling_metadata
464+
include_sibling_metadata = self.overrides.apply_formatter(
465+
validator.include_sibling_metadata, expected_type=bool
466466
)
467467

468468
parents, orphan_entries = self._download_url_metadata(
@@ -487,11 +487,9 @@ def download_metadata(self) -> Iterable[Entry]:
487487
# download the bottom-most urls first since they are top-priority
488488
for idx, url_validator in reversed(list(enumerate(self.collection.urls.list))):
489489
# URLs can be empty. If they are, then skip
490-
if not (urls := self.overrides.apply_overrides_formatter_to_native(url_validator.url)):
490+
if not (urls := self.overrides.apply_formatter(url_validator.url, expected_type=list)):
491491
continue
492492

493-
assert isinstance(urls, list)
494-
495493
for url in reversed(urls):
496494
assert isinstance(url, str)
497495

src/ytdl_sub/downloaders/url/validators.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Any
22
from typing import Dict
3+
from typing import List
34
from typing import Optional
45
from typing import Set
56

@@ -44,7 +45,7 @@ class UrlThumbnailListValidator(ListValidator[UrlThumbnailValidator]):
4445

4546

4647
class OverridesOneOrManyUrlValidator(OverridesStringFormatterValidator):
47-
def post_process_native(self, resolved: Any) -> Any:
48+
def post_process(self, resolved: Any) -> List[str]:
4849
if isinstance(resolved, str):
4950
return [resolved]
5051
if isinstance(resolved, list):

src/ytdl_sub/plugins/date_range.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def ytdl_options_match_filters(self) -> Tuple[List[str], List[str]]:
116116
date_validator=self.plugin_options.after, overrides=self.overrides
117117
)
118118
after_filter = f"{date_type} >= {after_str}"
119-
if self.overrides.evaluate_boolean(self.plugin_options.breaks):
119+
if self.overrides.apply_formatter(self.plugin_options.breaks, expected_type=bool):
120120
breaking_match_filters.append(after_filter)
121121
else:
122122
match_filters.append(after_filter)

src/ytdl_sub/plugins/embed_thumbnail.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class EmbedThumbnailPlugin(Plugin[EmbedThumbnailOptions]):
3333

3434
@property
3535
def _embed_thumbnail(self) -> bool:
36-
return self.overrides.evaluate_boolean(self.plugin_options)
36+
return self.overrides.apply_formatter(self.plugin_options, expected_type=bool)
3737

3838
@classmethod
3939
def _embed_video_thumbnail(cls, entry: Entry) -> None:

src/ytdl_sub/plugins/filter_exclude.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
from ytdl_sub.entries.entry import Entry
88
from ytdl_sub.utils.exceptions import StringFormattingException
99
from ytdl_sub.utils.logger import Logger
10-
from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator
10+
from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator
11+
from ytdl_sub.validators.validators import ListValidator
1112
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
1213

1314
logger = Logger.get("filter-exclude")
1415

1516

16-
class FilterExcludeOptions(ListFormatterValidator, OptionsValidator):
17+
class FilterExcludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator):
1718
"""
1819
Applies a conditional OR on any number of filters comprised of either variables or scripts.
1920
If any filter evaluates to True, the entry will be excluded.
@@ -29,6 +30,8 @@ class FilterExcludeOptions(ListFormatterValidator, OptionsValidator):
2930
{ %contains( %lower(description), '#short' ) }
3031
"""
3132

33+
_inner_list_type = BooleanFormatterValidator
34+
3235

3336
class FilterExcludePlugin(Plugin[FilterExcludeOptions]):
3437
plugin_options_type = FilterExcludeOptions
@@ -52,7 +55,9 @@ def modify_entry(self, entry: Entry) -> Optional[Entry]:
5255
return entry
5356

5457
for formatter in self.plugin_options.list:
55-
should_exclude = self.overrides.evaluate_boolean(formatter=formatter, entry=entry)
58+
should_exclude = self.overrides.apply_formatter(
59+
formatter=formatter, entry=entry, expected_type=bool
60+
)
5661

5762
if should_exclude:
5863
logger.info(

src/ytdl_sub/plugins/filter_include.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
from ytdl_sub.entries.entry import Entry
88
from ytdl_sub.utils.exceptions import StringFormattingException
99
from ytdl_sub.utils.logger import Logger
10-
from ytdl_sub.utils.script import ScriptUtils
11-
from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator
10+
from ytdl_sub.validators.string_formatter_validators import BooleanFormatterValidator
11+
from ytdl_sub.validators.validators import ListValidator
1212
from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive
1313

1414
logger = Logger.get("filter-include")
1515

1616

17-
class FilterIncludeOptions(ListFormatterValidator, OptionsValidator):
17+
class FilterIncludeOptions(ListValidator[BooleanFormatterValidator], OptionsValidator):
1818
"""
1919
Applies a conditional AND on any number of filters comprised of either variables or scripts.
2020
If all filters evaluate to True, the entry will be included.
@@ -38,6 +38,8 @@ class FilterIncludeOptions(ListFormatterValidator, OptionsValidator):
3838
}
3939
"""
4040

41+
_inner_list_type = BooleanFormatterValidator
42+
4143

4244
class FilterIncludePlugin(Plugin[FilterIncludeOptions]):
4345
plugin_options_type = FilterIncludeOptions
@@ -61,8 +63,8 @@ def modify_entry(self, entry: Entry) -> Optional[Entry]:
6163
return entry
6264

6365
for formatter in self.plugin_options.list:
64-
should_exclude = ScriptUtils.bool_formatter_output(
65-
self.overrides.apply_formatter(formatter=formatter, entry=entry)
66+
should_exclude = self.overrides.apply_formatter(
67+
formatter=formatter, entry=entry, expected_type=bool
6668
)
6769
if not should_exclude:
6870
logger.info(

src/ytdl_sub/plugins/nfo_tags.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def _create_nfo(self, entry: Entry, save_to_entry: bool = True) -> None:
140140
if not nfo_tags:
141141
return
142142

143-
if self.overrides.evaluate_boolean(self.plugin_options.kodi_safe):
143+
if self.overrides.apply_formatter(self.plugin_options.kodi_safe, expected_type=bool):
144144
nfo_root = to_max_3_byte_utf8_string(nfo_root)
145145
nfo_tags = {
146146
to_max_3_byte_utf8_string(key): [

0 commit comments

Comments
 (0)