[FEATURE] Extend subscription syntax (#790)
Extends the subscription.yaml syntax at the cost of introducing a breaking change.
# New Syntax
## Mix-n-match presets and indent variables
```
# Can mix/match presets and indent override variables.
# Uses presets TV Show, Only Recent and assigns Kids, TV-Y to subscription_indent_1 and _2
TV Show | = Kids | = TV-Y | Only Recent:
"Jake Trains": "https://..."
```
## Subscriptions with list-value support
```
TV Show | = Kids | = TV-Y | Only Recent:
"Jake Trains":
- "https://url.1..." # Assigns to subscription_value and subscription_value_1
- "https://url.2..." # Assigns to subscription_value_2
```
## Subscriptions with override-keys support
```
TV Show | = Kids | = TV-Y | Only Recent:
"~Jake Trains": # the ~ means "all keys underneath get assigned as override variables"
url: "https://url.1..." # Assigns to url
url2: "https://url.2..." # Assigns to url2
```
# Breaking Changes
In the TV show subscriptions example (https://github.com/jmbannon/ytdl-sub/blob/master/examples/tv_show_subscriptions.yaml), it had
```
TV Show Full Archive:
# Sets "Kids" for genre, "TV-Y" for content rating
= Kids | TV-Y:
"Jake Trains": "https://www.youtube.com/@JakeTrains"
"Kids Toys Play": "https://www.youtube.com/@KidsToysPlayChannel"
```
This must be changed to
```
TV Show Full Archive:
# Sets "Kids" for genre, "TV-Y" for content rating
= Kids | = TV-Y: # Each indent variable assignment must have an = before it
"Jake Trains": "https://www.youtube.com/@JakeTrains"
"Kids Toys Play": "https://www.youtube.com/@KidsToysPlayChannel"
```
This commit is contained in:
parent
056b111be6
commit
9f408d2196
6 changed files with 397 additions and 118 deletions
|
|
@ -13,12 +13,12 @@ TV Show Full Archive:
|
|||
"Opeth": "https://www.youtube.com/channel/UCmQSJTFZaXN85gYk6W3XbdQ"
|
||||
|
||||
# Sets "Kids" for genre, "TV-Y" for content rating
|
||||
= Kids | TV-Y:
|
||||
= Kids | = TV-Y:
|
||||
"Jake Trains": "https://www.youtube.com/@JakeTrains"
|
||||
"Kids Toys Play": "https://www.youtube.com/@KidsToysPlayChannel"
|
||||
|
||||
|
||||
# All subscriptions under this will use the `TV Show Only Recent` preset
|
||||
TV Show Only Recent:
|
||||
= News | TV-14:
|
||||
= News | = TV-14:
|
||||
"BBC": "https://www.youtube.com/@BBCNews"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from ytdl_sub.config.defaults import DEFAULT_FFPROBE_PATH
|
|||
from ytdl_sub.config.defaults import DEFAULT_LOCK_DIRECTORY
|
||||
from ytdl_sub.config.defaults import MAX_FILE_NAME_BYTES
|
||||
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
|
||||
from ytdl_sub.subscriptions.utils import SUBSCRIPTION_VALUE_CONFIG_KEY
|
||||
from ytdl_sub.validators.file_path_validators import FFmpegFileValidator
|
||||
from ytdl_sub.validators.file_path_validators import FFprobeFileValidator
|
||||
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
||||
|
|
@ -106,7 +107,7 @@ class ConfigOptions(StrictDictValidator):
|
|||
"ffprobe_path",
|
||||
"file_name_max_bytes",
|
||||
"experimental",
|
||||
"subscription_value",
|
||||
SUBSCRIPTION_VALUE_CONFIG_KEY,
|
||||
}
|
||||
|
||||
def __init__(self, name: str, value: Any):
|
||||
|
|
@ -142,7 +143,7 @@ class ConfigOptions(StrictDictValidator):
|
|||
key="file_name_max_bytes", validator=IntValidator, default=MAX_FILE_NAME_BYTES
|
||||
)
|
||||
self._subscription_value = self._validate_key_if_present(
|
||||
key="subscription_value", validator=StringValidator
|
||||
key=SUBSCRIPTION_VALUE_CONFIG_KEY, validator=StringValidator
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from yt_dlp.utils import sanitize_filename
|
|||
|
||||
from ytdl_sub.config.defaults import DEFAULT_DOWNLOAD_ARCHIVE_NAME
|
||||
from ytdl_sub.entries.entry import Entry
|
||||
from ytdl_sub.subscriptions.utils import SUBSCRIPTION_NAME
|
||||
from ytdl_sub.utils.exceptions import ValidationException
|
||||
from ytdl_sub.validators.file_path_validators import OverridesStringFormatterFilePathValidator
|
||||
from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator
|
||||
|
|
@ -120,45 +121,9 @@ class YTDLOptions(LiteralDictValidator):
|
|||
"""
|
||||
|
||||
|
||||
class OverridesVariables(DictFormatterValidator):
|
||||
"""
|
||||
Override variables that are automatically added to every subscription.
|
||||
"""
|
||||
|
||||
def _add_override_variable(self, key_name: str, format_string: str, sanitize: bool = False):
|
||||
if sanitize:
|
||||
key_name = f"{key_name}_sanitized"
|
||||
format_string = sanitize_filename(format_string)
|
||||
|
||||
self._value[key_name] = StringFormatterValidator(
|
||||
name="__should_never_fail__",
|
||||
value=format_string,
|
||||
)
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
||||
# Add sanitized and non-sanitized override variables
|
||||
for sanitize in [True, False]:
|
||||
self._add_override_variable(
|
||||
key_name="subscription_name",
|
||||
format_string=self.subscription_name,
|
||||
sanitize=sanitize,
|
||||
)
|
||||
|
||||
@property
|
||||
def subscription_name(self) -> str:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
Name of the subscription
|
||||
"""
|
||||
return self._root_name
|
||||
|
||||
|
||||
# Disable for proper docstring formatting
|
||||
# pylint: disable=line-too-long
|
||||
class Overrides(OverridesVariables):
|
||||
class Overrides(DictFormatterValidator):
|
||||
"""
|
||||
Optional. This section allows you to define variables that can be used in any string formatter.
|
||||
For example, if you want your file and thumbnail files to match without copy-pasting a large
|
||||
|
|
@ -188,6 +153,16 @@ class Overrides(OverridesVariables):
|
|||
|
||||
# pylint: enable=line-too-long
|
||||
|
||||
def _add_override_variable(self, key_name: str, format_string: str, sanitize: bool = False):
|
||||
if sanitize:
|
||||
key_name = f"{key_name}_sanitized"
|
||||
format_string = sanitize_filename(format_string)
|
||||
|
||||
self._value[key_name] = StringFormatterValidator(
|
||||
name="__should_never_fail__",
|
||||
value=format_string,
|
||||
)
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
||||
|
|
@ -199,6 +174,23 @@ class Overrides(OverridesVariables):
|
|||
sanitize=True,
|
||||
)
|
||||
|
||||
if SUBSCRIPTION_NAME not in self._value:
|
||||
for sanitized in [True, False]:
|
||||
self._add_override_variable(
|
||||
key_name=SUBSCRIPTION_NAME,
|
||||
format_string=self.subscription_name,
|
||||
sanitize=sanitized,
|
||||
)
|
||||
|
||||
@property
|
||||
def subscription_name(self) -> str:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
Name of the subscription
|
||||
"""
|
||||
return self._root_name
|
||||
|
||||
def apply_formatter(
|
||||
self,
|
||||
formatter: StringFormatterValidator,
|
||||
|
|
|
|||
|
|
@ -1,53 +1,25 @@
|
|||
import copy
|
||||
import dataclasses
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import final
|
||||
|
||||
from ytdl_sub.config.config_file import ConfigFile
|
||||
from ytdl_sub.config.preset_options import Overrides
|
||||
from ytdl_sub.subscriptions.utils import SUBSCRIPTION_NAME
|
||||
from ytdl_sub.subscriptions.utils import SUBSCRIPTION_VALUE
|
||||
from ytdl_sub.subscriptions.utils import subscription_indent_variable_name
|
||||
from ytdl_sub.subscriptions.utils import subscription_list_variable_name
|
||||
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator
|
||||
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 subscription_value_variable_name() -> str:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
The override variable name containing the subscription value if present
|
||||
"""
|
||||
return "subscription_value"
|
||||
|
||||
|
||||
def maybe_indent_override_values(value: str) -> List[str]:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
Value if it is an overide [Value]. None otherwise.
|
||||
"""
|
||||
if value.startswith("="):
|
||||
# Drop the =, split on |, and strip each indent_value (both left + right)
|
||||
return [indent_value.strip() for indent_value in value[1:].split("|")]
|
||||
return []
|
||||
|
||||
|
||||
class SubscriptionOutput(Validator, ABC):
|
||||
def __init__(self, name, value, presets: List[str], indent_overrides: List[str]):
|
||||
super().__init__(name, value)
|
||||
|
|
@ -75,6 +47,15 @@ class SubscriptionOutput(Validator, ABC):
|
|||
Subscriptions in the form of ``{ subscription_name: preset_dict }``
|
||||
"""
|
||||
|
||||
@property
|
||||
def subscription_name(self) -> str:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
The name of the subscription
|
||||
"""
|
||||
return self._leaf_name
|
||||
|
||||
|
||||
class SubscriptionPresetDictValidator(SubscriptionOutput, DictValidator):
|
||||
def __init__(self, name, value, presets: List[str], indent_overrides: List[str]):
|
||||
|
|
@ -93,12 +74,46 @@ class SubscriptionPresetDictValidator(SubscriptionOutput, DictValidator):
|
|||
|
||||
output_dict["preset"] = parent_presets + self._presets + global_presets_to_apply
|
||||
output_dict["overrides"] = dict(
|
||||
output_dict.get("overrides", {}), **self._indent_overrides_dict()
|
||||
output_dict.get("overrides", {}),
|
||||
**self._indent_overrides_dict(),
|
||||
**{SUBSCRIPTION_NAME: self.subscription_name},
|
||||
)
|
||||
return {self._leaf_name: output_dict}
|
||||
return {self.subscription_name: output_dict}
|
||||
|
||||
|
||||
class SubscriptionValueValidator(SubscriptionOutput, StringValidator):
|
||||
class SubscriptionLeafValidator(SubscriptionOutput, ABC):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
value,
|
||||
config: ConfigFile,
|
||||
presets: List[str],
|
||||
indent_overrides: List[str],
|
||||
):
|
||||
super().__init__(name=name, value=value, presets=presets, indent_overrides=indent_overrides)
|
||||
|
||||
if self.subscription_name in config.presets.keys:
|
||||
raise self._validation_exception(
|
||||
f"{self.subscription_name} conflicts with an existing preset name and cannot be "
|
||||
f"used as a subscription name"
|
||||
)
|
||||
|
||||
self._overrides_to_add: Dict[str, str] = {SUBSCRIPTION_NAME: self.subscription_name}
|
||||
|
||||
@final
|
||||
def subscription_dicts(self, global_presets_to_apply: List[str]) -> Dict[str, Dict]:
|
||||
return {
|
||||
self.subscription_name: {
|
||||
"preset": self._presets + global_presets_to_apply,
|
||||
"overrides": dict(
|
||||
self._indent_overrides_dict(),
|
||||
**self._overrides_to_add,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SubscriptionValueValidator(SubscriptionLeafValidator, StringValidator):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
|
|
@ -108,30 +123,73 @@ class SubscriptionValueValidator(SubscriptionOutput, StringValidator):
|
|||
indent_overrides: List[str],
|
||||
subscription_value: Optional[str],
|
||||
):
|
||||
super().__init__(name=name, value=value, presets=presets, indent_overrides=indent_overrides)
|
||||
super().__init__(
|
||||
name=name,
|
||||
value=value,
|
||||
config=config,
|
||||
presets=presets,
|
||||
indent_overrides=indent_overrides,
|
||||
)
|
||||
|
||||
if self._leaf_name in config.presets.keys:
|
||||
raise self._validation_exception(
|
||||
f"{self._leaf_name} conflicts with an existing preset name and cannot be "
|
||||
f"used as a subscription name"
|
||||
)
|
||||
self._subscription_value: Optional[str] = subscription_value
|
||||
|
||||
def subscription_dicts(self, global_presets_to_apply: List[str]) -> Dict[str, Dict]:
|
||||
subscription_value_dict: Dict[str, str] = {"subscription_value": self.value}
|
||||
# TODO: Eventually delete in favor of {subscription_value}
|
||||
if self._subscription_value:
|
||||
subscription_value_dict[self._subscription_value] = self.value
|
||||
if subscription_value:
|
||||
self._overrides_to_add[subscription_value] = self.value
|
||||
self._overrides_to_add[SUBSCRIPTION_VALUE] = self.value
|
||||
|
||||
return {
|
||||
self._leaf_name: {
|
||||
"preset": self._presets + global_presets_to_apply,
|
||||
"overrides": dict(
|
||||
subscription_value_dict,
|
||||
**self._indent_overrides_dict(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptionListValuesValidator(SubscriptionLeafValidator, StringListValidator):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
value,
|
||||
config: ConfigFile,
|
||||
presets: List[str],
|
||||
indent_overrides: List[str],
|
||||
):
|
||||
super().__init__(
|
||||
name=name,
|
||||
value=value,
|
||||
config=config,
|
||||
presets=presets,
|
||||
indent_overrides=indent_overrides,
|
||||
)
|
||||
|
||||
for idx, list_value in enumerate(self.list):
|
||||
# Write the first list value into subscription_value as well
|
||||
if idx == 0:
|
||||
self._overrides_to_add[SUBSCRIPTION_VALUE] = list_value.value
|
||||
|
||||
self._overrides_to_add[subscription_list_variable_name(index=idx)] = list_value.value
|
||||
|
||||
|
||||
class SubscriptionWithOverridesValidator(SubscriptionLeafValidator, DictFormatterValidator):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
value,
|
||||
config: ConfigFile,
|
||||
presets: List[str],
|
||||
indent_overrides: List[str],
|
||||
):
|
||||
super().__init__(
|
||||
name=name,
|
||||
value=value,
|
||||
config=config,
|
||||
presets=presets,
|
||||
indent_overrides=indent_overrides,
|
||||
)
|
||||
|
||||
self._overrides_to_add = dict(self.dict_with_format_strings, **self._overrides_to_add)
|
||||
|
||||
@property
|
||||
def subscription_name(self) -> str:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
Name of the subscription
|
||||
"""
|
||||
# drop the ~ in "~Subscription Name":
|
||||
return super().subscription_name[1:]
|
||||
|
||||
|
||||
class SubscriptionValidator(SubscriptionOutput):
|
||||
|
|
@ -139,6 +197,35 @@ class SubscriptionValidator(SubscriptionOutput):
|
|||
Top-level subscription validator
|
||||
"""
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PresetIndentKey:
|
||||
presets: List[str] = dataclasses.field(default_factory=list)
|
||||
indent_overrides: List[str] = dataclasses.field(default_factory=list)
|
||||
|
||||
def _preset_indent_key(self, key: str, config: ConfigFile) -> Optional[PresetIndentKey]:
|
||||
presets: List[str] = []
|
||||
indent_overrides: List[str] = []
|
||||
|
||||
stripped_split_keys = [sub_key.strip() for sub_key in key.split("|")]
|
||||
for sub_key in stripped_split_keys:
|
||||
if sub_key.startswith("="):
|
||||
indent_overrides.append(sub_key[1:].strip())
|
||||
elif sub_key in config.presets.keys:
|
||||
presets.append(sub_key)
|
||||
else:
|
||||
if presets or indent_overrides:
|
||||
raise self._validation_exception(
|
||||
f"'{sub_key.strip()}' in '{key.strip()}' is not a preset name. "
|
||||
f"To use as a subscription indent value, define it as '= {sub_key.strip()}'"
|
||||
)
|
||||
|
||||
if not presets and not indent_overrides:
|
||||
return None
|
||||
|
||||
return SubscriptionValidator.PresetIndentKey(
|
||||
presets=presets, indent_overrides=indent_overrides
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
|
|
@ -154,6 +241,8 @@ class SubscriptionValidator(SubscriptionOutput):
|
|||
for key, obj in value.items():
|
||||
obj_name = f"{name}.{key}" if name else key
|
||||
|
||||
# Subscription defined as
|
||||
# "Sub Name": "value"
|
||||
if isinstance(obj, str):
|
||||
self._children.append(
|
||||
SubscriptionValueValidator(
|
||||
|
|
@ -165,26 +254,45 @@ class SubscriptionValidator(SubscriptionOutput):
|
|||
subscription_value=subscription_value,
|
||||
)
|
||||
)
|
||||
elif isinstance(value, dict):
|
||||
if key in config.presets.keys:
|
||||
self._children.append(
|
||||
SubscriptionValidator(
|
||||
name=obj_name,
|
||||
value=obj,
|
||||
config=config,
|
||||
presets=presets + [key],
|
||||
indent_overrides=indent_overrides,
|
||||
subscription_value=subscription_value,
|
||||
)
|
||||
# Subscription defined as
|
||||
# "Sub Name":
|
||||
# - "value1"
|
||||
# - "value2"
|
||||
elif isinstance(obj, list):
|
||||
self._children.append(
|
||||
SubscriptionListValuesValidator(
|
||||
name=obj_name,
|
||||
value=obj,
|
||||
config=config,
|
||||
presets=presets,
|
||||
indent_overrides=indent_overrides,
|
||||
)
|
||||
elif override_values := maybe_indent_override_values(key):
|
||||
)
|
||||
elif isinstance(obj, dict):
|
||||
# Subscription defined as
|
||||
# "~Sub Name":
|
||||
# override_1: "abc"
|
||||
# override_2: "123"
|
||||
if key.startswith("~"):
|
||||
self._children.append(
|
||||
SubscriptionValidator(
|
||||
SubscriptionWithOverridesValidator(
|
||||
name=obj_name,
|
||||
value=obj,
|
||||
config=config,
|
||||
presets=presets,
|
||||
indent_overrides=indent_overrides + override_values,
|
||||
indent_overrides=indent_overrides,
|
||||
)
|
||||
)
|
||||
elif (
|
||||
preset_indent_key := self._preset_indent_key(key=key, config=config)
|
||||
) is not None:
|
||||
self._children.append(
|
||||
SubscriptionValidator(
|
||||
name=obj_name,
|
||||
value=obj,
|
||||
config=config,
|
||||
presets=presets + preset_indent_key.presets,
|
||||
indent_overrides=indent_overrides + preset_indent_key.indent_overrides,
|
||||
subscription_value=subscription_value,
|
||||
)
|
||||
)
|
||||
|
|
@ -197,6 +305,10 @@ class SubscriptionValidator(SubscriptionOutput):
|
|||
indent_overrides=indent_overrides,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise self._validation_exception(
|
||||
"Subscription value should either be a string, list, or object"
|
||||
)
|
||||
|
||||
def subscription_dicts(self, global_presets_to_apply: List[str]) -> Dict[str, Dict]:
|
||||
subscription_dicts: Dict[str, Dict] = {}
|
||||
|
|
|
|||
33
src/ytdl_sub/subscriptions/utils.py
Normal file
33
src/ytdl_sub/subscriptions/utils.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
SUBSCRIPTION_NAME = "subscription_name"
|
||||
SUBSCRIPTION_VALUE = "subscription_value"
|
||||
|
||||
# Key used in configs, should delete at some point
|
||||
SUBSCRIPTION_VALUE_CONFIG_KEY = "subscription_value"
|
||||
|
||||
|
||||
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 subscription_list_variable_name(index: int) -> str:
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
index
|
||||
0th-based index
|
||||
|
||||
Returns
|
||||
-------
|
||||
subscription_value_i, where i is 1-based index
|
||||
"""
|
||||
return f"subscription_value_{index + 1}"
|
||||
|
|
@ -116,6 +116,72 @@ def preset_with_subscription_value_nested_presets_and_indent_variables(
|
|||
@pytest.fixture
|
||||
def preset_with_subscription_value_nested_presets_and_indent_variables_same_line(
|
||||
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 | = INDENT_3 ": {
|
||||
"test_1": "is_1_overwritten",
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def preset_with_subscription_value_nested_presets_and_indent_variables_all_same_line(
|
||||
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",
|
||||
},
|
||||
"parent_preset_2 | =INDENT_1 |= INDENT_2 | = INDENT_3 ": {
|
||||
"test_1": "is_1_overwritten",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def preset_with_subscription_list(
|
||||
preset_with_subscription_value: Dict,
|
||||
):
|
||||
return dict(
|
||||
preset_with_subscription_value,
|
||||
**{
|
||||
"parent_preset_2 | parent_preset_1": {
|
||||
"test_2_1": ["is_2_1_overwritten", "is_2_1_list_2"]
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def preset_with_subscription_overrides_tilda(
|
||||
preset_with_subscription_value: Dict,
|
||||
):
|
||||
return dict(
|
||||
preset_with_subscription_value,
|
||||
**{
|
||||
"parent_preset_2 | parent_preset_1": {
|
||||
"~test_2_1": {
|
||||
"current_override": "test_2_1",
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def preset_with_subscription_value_nested_presets_and_indent_variables_same_line_old_format_errors(
|
||||
preset_with_subscription_value: Dict,
|
||||
):
|
||||
return dict(
|
||||
preset_with_subscription_value,
|
||||
|
|
@ -191,6 +257,7 @@ def test_subscription_file_value_applies_sub_file_takes_precedence(
|
|||
assert value_sub.get("test_file_subscription_value") == "is_overwritten"
|
||||
assert value_sub.get("test_config_subscription_value") == "original"
|
||||
assert value_sub.get("subscription_name") == "test_value"
|
||||
assert value_sub.get("subscription_name_sanitized") == "test_value"
|
||||
assert value_sub.get("subscription_value") == "is_overwritten"
|
||||
assert value_sub.get("current_override") == "__preset__" # ensure __preset__ takes precedence
|
||||
|
||||
|
|
@ -209,6 +276,7 @@ def test_subscription_file_value_applies_from_config(
|
|||
assert value_sub.get("test_file_subscription_value") == "original"
|
||||
assert value_sub.get("test_config_subscription_value") == "is_overwritten"
|
||||
assert value_sub.get("subscription_name") == "test_value"
|
||||
assert value_sub.get("subscription_name_sanitized") == "test_value"
|
||||
assert value_sub.get("subscription_value") == "is_overwritten"
|
||||
assert value_sub.get("current_override") == "__preset__" # ensure __preset__ takes precedence
|
||||
|
||||
|
|
@ -229,15 +297,56 @@ def test_subscription_file_value_applies_from_config_and_nested(
|
|||
|
||||
assert sub_1.get("test_config_subscription_value") == "is_1_overwritten"
|
||||
assert sub_1.get("subscription_name") == "test_1"
|
||||
assert sub_1.get("subscription_name_sanitized") == "test_1"
|
||||
assert sub_1.get("subscription_value") == "is_1_overwritten"
|
||||
assert sub_1.get("current_override") == "__preset__" # ensure __preset__ takes precedence
|
||||
|
||||
assert sub_2_1.get("test_config_subscription_value") == "is_2_1_overwritten"
|
||||
assert sub_2_1.get("subscription_name") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_name_sanitized") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_value") == "is_2_1_overwritten"
|
||||
assert sub_2_1.get("current_override") == "__preset__" # ensure __preset__ takes precedence
|
||||
|
||||
|
||||
def test_subscription_list(
|
||||
config_file_with_subscription_value: ConfigFile,
|
||||
preset_with_subscription_list: Dict,
|
||||
):
|
||||
with mock_load_yaml(preset_dict=preset_with_subscription_list):
|
||||
subs = Subscription.from_file_path(
|
||||
config=config_file_with_subscription_value, subscription_path="mocked"
|
||||
)
|
||||
assert len(subs) == 3
|
||||
|
||||
# Test __value__ worked correctly from the config
|
||||
sub_2_1 = [sub for sub in subs if sub.name == "test_2_1"][0].overrides.dict_with_format_strings
|
||||
|
||||
assert sub_2_1.get("subscription_name") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_name_sanitized") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_value") == "is_2_1_overwritten"
|
||||
assert sub_2_1.get("subscription_value_1") == "is_2_1_overwritten"
|
||||
assert sub_2_1.get("subscription_value_2") == "is_2_1_list_2"
|
||||
assert sub_2_1.get("current_override") == "__preset__" # ensure __preset__ takes precedence
|
||||
|
||||
|
||||
def test_subscription_overrides_tilda(
|
||||
config_file_with_subscription_value: ConfigFile,
|
||||
preset_with_subscription_overrides_tilda: Dict,
|
||||
):
|
||||
with mock_load_yaml(preset_dict=preset_with_subscription_overrides_tilda):
|
||||
subs = Subscription.from_file_path(
|
||||
config=config_file_with_subscription_value, subscription_path="mocked"
|
||||
)
|
||||
assert len(subs) == 3
|
||||
|
||||
# Test __value__ worked correctly from the config
|
||||
sub_2_1 = [sub for sub in subs if sub.name == "test_2_1"][0].overrides.dict_with_format_strings
|
||||
|
||||
assert sub_2_1.get("subscription_name") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_name_sanitized") == "test_2_1"
|
||||
assert sub_2_1.get("current_override") == "test_2_1" # tilda sub takes precedence
|
||||
|
||||
|
||||
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,
|
||||
|
|
@ -262,6 +371,7 @@ def test_subscription_file_value_applies_from_config_and_nested_and_indent_varia
|
|||
|
||||
assert sub_1.get("test_config_subscription_value") == "is_1_overwritten"
|
||||
assert sub_1.get("subscription_name") == "test_1"
|
||||
assert sub_1.get("subscription_name_sanitized") == "test_1"
|
||||
assert sub_1.get("subscription_value") == "is_1_overwritten"
|
||||
assert sub_1.get("subscription_indent_1") == "INDENT_1"
|
||||
assert sub_1.get("subscription_indent_2") == "INDENT_2"
|
||||
|
|
@ -269,19 +379,27 @@ def test_subscription_file_value_applies_from_config_and_nested_and_indent_varia
|
|||
|
||||
assert sub_2_1.get("test_config_subscription_value") == "is_2_1_overwritten"
|
||||
assert sub_2_1.get("subscription_name") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_name_sanitized") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_value") == "is_2_1_overwritten"
|
||||
assert sub_2_1.get("subscription_indent_1") == "INDENT_1"
|
||||
assert sub_2_1.get("subscription_indent_2") == "original_2"
|
||||
assert sub_2_1.get("current_override") == "__preset__" # ensure __preset__ takes precedence
|
||||
|
||||
|
||||
@pytest.mark.parametrize("all_same_line", [True, False])
|
||||
def test_subscription_file_value_applies_from_config_and_nested_and_indent_variables_same_line(
|
||||
config_file_with_subscription_value: ConfigFile,
|
||||
preset_with_subscription_value_nested_presets_and_indent_variables_same_line: Dict,
|
||||
preset_with_subscription_value_nested_presets_and_indent_variables_all_same_line: Dict,
|
||||
all_same_line: bool,
|
||||
):
|
||||
with mock_load_yaml(
|
||||
preset_dict=preset_with_subscription_value_nested_presets_and_indent_variables_same_line
|
||||
):
|
||||
preset_dict = preset_with_subscription_value_nested_presets_and_indent_variables_same_line
|
||||
if all_same_line:
|
||||
preset_dict = (
|
||||
preset_with_subscription_value_nested_presets_and_indent_variables_all_same_line
|
||||
)
|
||||
|
||||
with mock_load_yaml(preset_dict=preset_dict):
|
||||
subs = Subscription.from_file_path(
|
||||
config=config_file_with_subscription_value, subscription_path="mocked"
|
||||
)
|
||||
|
|
@ -299,6 +417,7 @@ def test_subscription_file_value_applies_from_config_and_nested_and_indent_varia
|
|||
|
||||
assert sub_1.get("test_config_subscription_value") == "is_1_overwritten"
|
||||
assert sub_1.get("subscription_name") == "test_1"
|
||||
assert sub_1.get("subscription_name_sanitized") == "test_1"
|
||||
assert sub_1.get("subscription_value") == "is_1_overwritten"
|
||||
assert sub_1.get("subscription_indent_1") == "INDENT_1"
|
||||
assert sub_1.get("subscription_indent_2") == "INDENT_2"
|
||||
|
|
@ -307,6 +426,7 @@ def test_subscription_file_value_applies_from_config_and_nested_and_indent_varia
|
|||
|
||||
assert sub_2_1.get("test_config_subscription_value") == "is_2_1_overwritten"
|
||||
assert sub_2_1.get("subscription_name") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_name_sanitized") == "test_2_1"
|
||||
assert sub_2_1.get("subscription_value") == "is_2_1_overwritten"
|
||||
assert sub_2_1.get("subscription_indent_1") == "INDENT_1"
|
||||
assert sub_2_1.get("subscription_indent_2") == "original_2"
|
||||
|
|
@ -314,6 +434,24 @@ def test_subscription_file_value_applies_from_config_and_nested_and_indent_varia
|
|||
assert "subscription_indent_3" not in sub_2_1
|
||||
|
||||
|
||||
def test_subscription_file_value_applies_from_config_and_nested_and_indent_variables_same_line_old_format_errors(
|
||||
config_file_with_subscription_value: ConfigFile,
|
||||
preset_with_subscription_value_nested_presets_and_indent_variables_same_line_old_format_errors: Dict,
|
||||
):
|
||||
with mock_load_yaml(
|
||||
preset_dict=preset_with_subscription_value_nested_presets_and_indent_variables_same_line_old_format_errors
|
||||
), pytest.raises(
|
||||
ValidationException,
|
||||
match=re.escape(
|
||||
"Validation error in parent_preset_2.=INDENT_1: 'INDENT_3' in '= INDENT_2 | INDENT_3' is not a preset name. "
|
||||
"To use as a subscription indent value, define it as '= INDENT_3'"
|
||||
),
|
||||
):
|
||||
Subscription.from_file_path(
|
||||
config=config_file_with_subscription_value, subscription_path="mocked"
|
||||
)
|
||||
|
||||
|
||||
def test_subscription_file_bad_value(config_file: ConfigFile):
|
||||
with mock_load_yaml(preset_dict={"__value__": {"should be": "string"}}), pytest.raises(
|
||||
ValidationException,
|
||||
|
|
@ -346,7 +484,7 @@ def test_subscription_file_using_conflicting_preset_name(config_file: ConfigFile
|
|||
def test_subscription_file_invalid_form(config_file: ConfigFile):
|
||||
with mock_load_yaml(preset_dict={"sub_name": 4332}), pytest.raises(
|
||||
ValidationException,
|
||||
match=re.escape(f"Validation error in sub_name: should be of type object."),
|
||||
match=re.escape(f"Subscription value should either be a string, list, or object"),
|
||||
):
|
||||
_ = Subscription.from_file_path(config=config_file, subscription_path="mocked")
|
||||
|
||||
|
|
@ -363,6 +501,7 @@ def test_tv_show_subscriptions(
|
|||
jake_train_overrides = subs[2].overrides.dict_with_format_strings
|
||||
|
||||
assert jake_train_overrides["subscription_name"] == "Jake Trains"
|
||||
assert jake_train_overrides["subscription_name_sanitized"] == "Jake Trains"
|
||||
assert jake_train_overrides["subscription_value"] == "https://www.youtube.com/@JakeTrains"
|
||||
assert jake_train_overrides["subscription_indent_1"] == "Kids"
|
||||
assert jake_train_overrides["subscription_indent_2"] == "TV-Y"
|
||||
|
|
@ -378,6 +517,7 @@ def test_music_subscriptions(default_config: ConfigFile, music_subscriptions_pat
|
|||
monk = subs[2].overrides.dict_with_format_strings
|
||||
|
||||
assert monk["subscription_name"] == "Stan Getz"
|
||||
assert monk["subscription_name_sanitized"] == "Stan Getz"
|
||||
assert monk["subscription_value"] == "https://www.youtube.com/@stangetzofficial/releases"
|
||||
assert monk["subscription_indent_1"] == "Jazz"
|
||||
|
||||
|
|
@ -392,6 +532,7 @@ def test_music_video_subscriptions(default_config: ConfigFile, music_video_subsc
|
|||
monk = subs[1].overrides.dict_with_format_strings
|
||||
|
||||
assert monk["subscription_name"] == "Michael Jackson"
|
||||
assert monk["subscription_name_sanitized"] == "Michael Jackson"
|
||||
assert (
|
||||
monk["subscription_value"]
|
||||
== "https://www.youtube.com/playlist?list=OLAK5uy_mnY03zP6abNWH929q2XhGzWD_2uKJ_n8E"
|
||||
|
|
|
|||
Loading…
Reference in a new issue