diff --git a/tests/unit/entries/test_entry.py b/tests/unit/entries/test_entry.py index 14fdb741..9674a564 100644 --- a/tests/unit/entries/test_entry.py +++ b/tests/unit/entries/test_entry.py @@ -38,79 +38,6 @@ class TestEntry(object): finally: relative_directory.cleanup() - def test_entry_formatter_single_field(self, mock_entry): - format_string = StringFormatterValidator( - name="test", value=f"prefix {{uid}} suffix" - ) - expected_string = f"prefix {mock_entry.uid} suffix" - - assert mock_entry.apply_formatter(format_string) == expected_string - - def test_entry_formatter_duplicate_fields(self, mock_entry): - format_string = StringFormatterValidator( - name="test", value=f"prefix {{upload_year}} {{upload_year}} suffix" - ) - expected_string = ( - f"prefix {mock_entry.upload_year} {mock_entry.upload_year} suffix" - ) - - assert mock_entry.apply_formatter(format_string) == expected_string - - def test_entry_formatter_override(self, mock_entry): - new_uid = "my very own uid" - overrides = OverridesValidator(name="test", value={"uid": new_uid}) - - format_string = StringFormatterValidator( - name="test", value=f"prefix {{uid}} suffix" - ) - expected_string = f"prefix {new_uid} suffix" - - assert ( - mock_entry.apply_formatter(format_string, overrides=overrides) - == expected_string - ) - - def test_entry_formatter_override_recursive(self, mock_entry): - overrides = OverridesValidator( - name="test", - value={ - "level_a": "level a", - "level_b": "level b and {level_a}", - "level_c": "level c and {level_b}", - }, - ) - - format_string = StringFormatterValidator( - name="test", value="level d and {level_c}" - ) - expected_string = "level d and level c and level b and level a" - - assert ( - mock_entry.apply_formatter(format_string, overrides=overrides) - == expected_string - ) - - def test_entry_formatter_override_recursive_fail_cycle(self, mock_entry): - overrides = OverridesValidator( - name="test", - value={ - "level_a": "{level_b}", - "level_b": "{level_a}", - }, - ) - - # Max depth is 3 so should go level_a -(0)-> level_b -(1)-> level_a -(2)-> level_b - expected_error_msg = ( - "Validation error in test: Attempted to format but failed after reaching max recursion " - "depth of 3. Try to keep variables dependent on only one other variable at max. " - "Unresolved variables: level_b" - ) - - format_string = StringFormatterValidator(name="test", value="{level_a}") - - with pytest.raises(StringFormattingException, match=expected_error_msg): - _ = mock_entry.apply_formatter(format_string, overrides=overrides) - def test_entry_missing_kwarg(self, mock_entry): key = "dne" expected_error_msg = f"Expected '{key}' in Entry but does not exist." diff --git a/tests/unit/validators/test_string_formatter_validator.py b/tests/unit/validators/test_string_formatter_validator.py index 5d058ae9..33f815b3 100644 --- a/tests/unit/validators/test_string_formatter_validator.py +++ b/tests/unit/validators/test_string_formatter_validator.py @@ -6,6 +6,7 @@ from ytdl_subscribe.validators.base.string_formatter_validators import ( from ytdl_subscribe.validators.base.string_formatter_validators import ( StringFormatterValidator, ) +from ytdl_subscribe.validators.exceptions import StringFormattingException from ytdl_subscribe.validators.exceptions import ValidationException @@ -102,6 +103,64 @@ class TestStringFormatterValidator(object): with pytest.raises(ValidationException, match=expected_error_msg): _ = StringFormatterValidator(name="fail", value=format_string) + def test_string_formatter_single_field(self): + uid = "this uid" + format_string = StringFormatterValidator( + name="test", value=f"prefix {{uid}} suffix" + ) + expected_string = f"prefix {uid} suffix" + + assert ( + format_string.apply_formatter(variable_dict={"uid": uid}) == expected_string + ) + + def test_entry_formatter_duplicate_fields(self): + upload_year = "2022" + format_string = StringFormatterValidator( + name="test", value=f"prefix {{upload_year}} {{upload_year}} suffix" + ) + expected_string = f"prefix {upload_year} {upload_year} suffix" + + assert ( + format_string.apply_formatter(variable_dict={"upload_year": upload_year}) + == expected_string + ) + + def test_entry_formatter_override_recursive(self): + variable_dict = { + "level_a": "level a", + "level_b": "level b and {level_a}", + "level_c": "level c and {level_b}", + } + + format_string = StringFormatterValidator( + name="test", value="level d and {level_c}" + ) + expected_string = "level d and level c and level b and level a" + + assert ( + format_string.apply_formatter(variable_dict=variable_dict) + == expected_string + ) + + def test_entry_formatter_override_recursive_fail_cycle(self): + variable_dict = { + "level_a": "{level_b}", + "level_b": "{level_a}", + } + + # Max depth is 3 so should go level_a -(0)-> level_b -(1)-> level_a -(2)-> level_b + expected_error_msg = ( + "Validation error in test: Attempted to format but failed after reaching max recursion " + "depth of 3. Try to keep variables dependent on only one other variable at max. " + "Unresolved variables: level_b" + ) + + format_string = StringFormatterValidator(name="test", value="{level_a}") + + with pytest.raises(StringFormattingException, match=expected_error_msg): + _ = format_string.apply_formatter(variable_dict=variable_dict) + class TestDictFormatterValidator(object): def test_validates_values(self): diff --git a/ytdl_subscribe/subscriptions/subscription.py b/ytdl_subscribe/subscriptions/subscription.py index 218d83f4..9963cd42 100644 --- a/ytdl_subscribe/subscriptions/subscription.py +++ b/ytdl_subscribe/subscriptions/subscription.py @@ -83,7 +83,7 @@ class Subscription(object): ytdl_options=self.ytdl_options.dict, ) - def _apply_entry_formatter( + def _apply_formatter( self, entry: Entry, formatter: StringFormatterValidator ) -> str: """ @@ -98,7 +98,8 @@ class Subscription(object): ------- The format_string after .format has been called on it using entry and override values """ - return entry.apply_formatter(formatter=formatter, overrides=self.overrides) + variable_dict = dict(entry.to_dict(), **self.overrides.dict_with_format_strings) + return formatter.apply_formatter(variable_dict) def _post_process_tagging(self, entry: Entry): id3_options = self.metadata_options.id3 @@ -106,7 +107,7 @@ class Subscription(object): entry.file_path(relative_directory=self.working_directory) ) for tag, tag_formatter in id3_options.tags.dict.items(): - audio_file[tag] = self._apply_entry_formatter( + audio_file[tag] = self._apply_formatter( entry=entry, formatter=tag_formatter ) audio_file.save() @@ -116,12 +117,10 @@ class Subscription(object): nfo_options = self.metadata_options.nfo for tag, tag_formatter in nfo_options.tags.dict.items(): - nfo[tag] = self._apply_entry_formatter(entry=entry, formatter=tag_formatter) + nfo[tag] = self._apply_formatter(entry=entry, formatter=tag_formatter) # Write the nfo tags to XML with the nfo_root - nfo_root = self._apply_entry_formatter( - entry=entry, formatter=nfo_options.nfo_root - ) + nfo_root = self._apply_formatter(entry=entry, formatter=nfo_options.nfo_root) xml = dicttoxml.dicttoxml( obj=nfo, root=True, # We assume all NFOs have a root. Maybe we should not? @@ -129,10 +128,10 @@ class Subscription(object): attr_type=False, ) - nfo_file_name = self._apply_entry_formatter( + nfo_file_name = self._apply_formatter( entry=entry, formatter=nfo_options.nfo_name ) - output_directory = self._apply_entry_formatter( + output_directory = self._apply_formatter( entry=entry, formatter=self.output_options.output_directory ) @@ -156,11 +155,11 @@ class Subscription(object): relative_directory=self.working_directory ) - output_directory = self._apply_entry_formatter( + output_directory = self._apply_formatter( entry=entry, formatter=self.output_options.output_directory ) - output_file_name = self._apply_entry_formatter( + output_file_name = self._apply_formatter( entry=entry, formatter=self.output_options.file_name ) entry_destination_file_path = Path(output_directory) / Path(output_file_name) @@ -174,7 +173,7 @@ class Subscription(object): relative_directory=self.working_directory ) - output_thumbnail_name = self._apply_entry_formatter( + output_thumbnail_name = self._apply_formatter( entry=entry, formatter=self.output_options.thumbnail_name ) output_thumbnail_path = Path(output_directory) / Path(output_thumbnail_name)