From 745ba5114ae8021e09fd5b9e845065535fcfe111 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Fri, 29 Sep 2023 23:50:54 -0700 Subject: [PATCH] subscription_indent_idx --- src/ytdl_sub/subscriptions/subscription.py | 1 + .../subscriptions/subscription_validators.py | 96 +++++++++++++++++-- tests/unit/config/test_subscription.py | 62 ++++++++++++ 3 files changed, 151 insertions(+), 8 deletions(-) diff --git a/src/ytdl_sub/subscriptions/subscription.py b/src/ytdl_sub/subscriptions/subscription.py index 1479ec28..de23fe81 100644 --- a/src/ytdl_sub/subscriptions/subscription.py +++ b/src/ytdl_sub/subscriptions/subscription.py @@ -134,6 +134,7 @@ class Subscription(SubscriptionDownload): value=subscriptions_dict, config=config, presets=[FILE_PRESET_APPLY_KEY] if has_file_preset else [], + indent_overrides=[], subscription_value=file_subscription_value, ).subscription_dicts() diff --git a/src/ytdl_sub/subscriptions/subscription_validators.py b/src/ytdl_sub/subscriptions/subscription_validators.py index 23d5ce9e..3f01e15d 100644 --- a/src/ytdl_sub/subscriptions/subscription_validators.py +++ b/src/ytdl_sub/subscriptions/subscription_validators.py @@ -6,15 +6,54 @@ from typing import List from typing import Optional from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.config.preset_options import Overrides from ytdl_sub.validators.validators import DictValidator +from ytdl_sub.validators.validators import StringListValidator from ytdl_sub.validators.validators import StringValidator from ytdl_sub.validators.validators import Validator +def subscription_indent_variable_name(index: int) -> str: + """ + Parameters + ---------- + index + 0th-based index + + Returns + ------- + subscription_index_i, where i is 1-based index + """ + return f"subscription_indent_{index + 1}" + + +def maybe_indent_override_value(value: str) -> Optional[str]: + """ + Returns + ------- + Value if it is an overide [Value]. None otherwise. + """ + if value.startswith("[") and value.endswith("]"): + return value[1:-1] + return None + + class SubscriptionOutput(Validator, ABC): - def __init__(self, name, value, presets: List[str]): + def __init__(self, name, value, presets: List[str], indent_overrides: List[str]): super().__init__(name, value) self._presets = copy.deepcopy(presets) + self._indent_overrides = copy.deepcopy(indent_overrides) + + def _indent_overrides_dict(self) -> Dict[str, str]: + """ + Returns + ------- + indent overrides to merge with the preset dict's overrides + """ + return { + subscription_indent_variable_name(i): self._indent_overrides[i] + for i in range(len(self._indent_overrides)) + } @abstractmethod def subscription_dicts(self) -> Dict[str, Dict]: @@ -26,6 +65,12 @@ class SubscriptionOutput(Validator, ABC): class SubscriptionPresetDictValidator(SubscriptionOutput, DictValidator): + def __init__(self, name, value, presets: List[str], indent_overrides: List[str]): + super().__init__(name=name, value=value, presets=presets, indent_overrides=indent_overrides) + + _ = self._validate_key_if_present(key="preset", validator=StringListValidator, default=[]) + _ = self._validate_key_if_present(key="overrides", validator=Overrides, default={}) + def subscription_dicts(self) -> Dict[str, Dict]: output_dict = copy.deepcopy(self._dict) parent_presets = output_dict.get("preset", []) @@ -35,12 +80,22 @@ class SubscriptionPresetDictValidator(SubscriptionOutput, DictValidator): parent_presets = [parent_presets] output_dict["preset"] = parent_presets + self._presets + output_dict["overrides"] = dict( + output_dict.get("overrides", {}), **self._indent_overrides_dict() + ) return {self._leaf_name: output_dict} class SubscriptionValueValidator(SubscriptionOutput, StringValidator): - def __init__(self, name, value, presets: List[str], subscription_value: Optional[str]): - super().__init__(name, value, presets) + def __init__( + self, + name, + value, + presets: List[str], + indent_overrides: List[str], + subscription_value: Optional[str], + ): + super().__init__(name=name, value=value, presets=presets, indent_overrides=indent_overrides) if subscription_value is None: raise self._validation_exception( f"Subscription {name} is a string, but the subscription value " @@ -53,7 +108,9 @@ class SubscriptionValueValidator(SubscriptionOutput, StringValidator): return { self._leaf_name: { "preset": self._presets, - "overrides": {self._subscription_value: self.value}, + "overrides": dict( + {self._subscription_value: self.value}, **self._indent_overrides_dict() + ), } } @@ -64,9 +121,15 @@ class SubscriptionValidator(SubscriptionOutput): """ def __init__( - self, name, value, config: ConfigFile, presets: List[str], subscription_value: Optional[str] + self, + name, + value, + config: ConfigFile, + presets: List[str], + indent_overrides: List[str], + subscription_value: Optional[str], ): - super().__init__(name, value, presets) + super().__init__(name=name, value=value, presets=presets, indent_overrides=indent_overrides) self._children: List[SubscriptionOutput] = [] for key, obj in value.items(): @@ -78,12 +141,12 @@ class SubscriptionValidator(SubscriptionOutput): f"{key} conflicts with an existing preset name and cannot be " f"used as a subscription name" ) - self._children.append( SubscriptionValueValidator( name=obj_name, value=obj, presets=presets, + indent_overrides=indent_overrides, subscription_value=subscription_value, ) ) @@ -95,12 +158,29 @@ class SubscriptionValidator(SubscriptionOutput): value=obj, config=config, presets=presets + [key], + indent_overrides=indent_overrides, + subscription_value=subscription_value, + ) + ) + elif override_value := maybe_indent_override_value(key): + self._children.append( + SubscriptionValidator( + name=obj_name, + value=obj, + config=config, + presets=presets, + indent_overrides=indent_overrides + [override_value], subscription_value=subscription_value, ) ) else: self._children.append( - SubscriptionPresetDictValidator(name=obj_name, value=obj, presets=presets) + SubscriptionPresetDictValidator( + name=obj_name, + value=obj, + presets=presets, + indent_overrides=indent_overrides, + ) ) def subscription_dicts(self) -> Dict[str, Dict]: diff --git a/tests/unit/config/test_subscription.py b/tests/unit/config/test_subscription.py index 7cb4f4bf..ced0ff70 100644 --- a/tests/unit/config/test_subscription.py +++ b/tests/unit/config/test_subscription.py @@ -43,6 +43,8 @@ def preset_with_file_preset(youtube_video: Dict, output_options: Dict): "overrides": { "test_file_subscription_value": "original", "test_config_subscription_value": "original", + "subscription_indent_1": "original_1", + "subscription_indent_2": "original_2", }, }, "test_preset": { @@ -88,6 +90,25 @@ def preset_with_subscription_value_nested_presets(preset_with_subscription_value ) +@pytest.fixture +def preset_with_subscription_value_nested_presets_and_indent_variables( + preset_with_subscription_value: Dict, +): + return dict( + preset_with_subscription_value, + **{ + "parent_preset_2": { + "[INDENT_1]": { + "parent_preset_1": {"test_2_1": "is_2_1_overwritten"}, + "[INDENT_2]": { + "test_1": "is_1_overwritten", + }, + } + } + }, + ) + + def test_subscription_file_preset_applies(config_file: ConfigFile, preset_with_file_preset: Dict): with mock_load_yaml(preset_dict=preset_with_file_preset): subs = Subscription.from_file_path(config=config_file, subscription_path="mocked") @@ -197,6 +218,47 @@ def test_subscription_file_value_applies_from_config_and_nested( ) +def test_subscription_file_value_applies_from_config_and_nested_and_indent_variables( + config_file_with_subscription_value: ConfigFile, + preset_with_subscription_value_nested_presets_and_indent_variables: Dict, +): + with mock_load_yaml( + preset_dict=preset_with_subscription_value_nested_presets_and_indent_variables + ): + subs = Subscription.from_file_path( + config=config_file_with_subscription_value, subscription_path="mocked" + ) + assert len(subs) == 4 + + # Test __value__ worked correctly from the config + sub_test_value = [sub for sub in subs if sub.name == "test_value"][0] + sub_1 = [sub for sub in subs if sub.name == "test_1"][0] + sub_2_1 = [sub for sub in subs if sub.name == "test_2_1"][0] + + assert ( + sub_test_value.overrides.dict_with_format_strings.get("subscription_indent_1") + == "original_1" + ) + assert ( + sub_test_value.overrides.dict_with_format_strings.get("subscription_indent_2") + == "original_2" + ) + + assert ( + sub_1.overrides.dict_with_format_strings.get("test_config_subscription_value") + == "is_1_overwritten" + ) + assert sub_1.overrides.dict_with_format_strings.get("subscription_indent_1") == "INDENT_1" + assert sub_1.overrides.dict_with_format_strings.get("subscription_indent_2") == "INDENT_2" + + assert ( + sub_2_1.overrides.dict_with_format_strings.get("test_config_subscription_value") + == "is_2_1_overwritten" + ) + assert sub_2_1.overrides.dict_with_format_strings.get("subscription_indent_1") == "INDENT_1" + assert sub_2_1.overrides.dict_with_format_strings.get("subscription_indent_2") == "original_2" + + def test_subscription_file_bad_value(config_file: ConfigFile): with mock_load_yaml(preset_dict={"__value__": {"should be": "string"}}), pytest.raises( ValidationException,