refactored, validation now in subscription

This commit is contained in:
Jesse Bannon 2025-12-26 20:57:13 -08:00
parent 276d1de979
commit a6702dc723
5 changed files with 92 additions and 58 deletions

View file

@ -1,5 +1,7 @@
from typing import Iterable
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Set
from typing import Tuple from typing import Tuple
from typing import Type from typing import Type
@ -44,3 +46,31 @@ class PresetPlugins:
if plugin_type in plugin_option_types: if plugin_type in plugin_option_types:
return self.plugin_options[plugin_option_types.index(plugin_type)] return self.plugin_options[plugin_option_types.index(plugin_type)]
return None return None
def get_added_and_modified_variables(
self, additional_options: List[OptionsValidator]
) -> Iterable[Tuple[OptionsValidator, Set[str], Set[str]]]:
"""
Iterates and returns the plugin options, added variables, modified variables
"""
for plugin_options in self.plugin_options + additional_options:
added_variables: Set[str] = set()
modified_variables: Set[str] = set()
for plugin_added_variables in plugin_options.added_variables(
unresolved_variables=set(),
).values():
added_variables |= set(plugin_added_variables)
for plugin_modified_variables in plugin_options.modified_variables().values():
modified_variables = plugin_modified_variables
yield plugin_options, added_variables, modified_variables
def get_all_variables(self, additional_options: List[OptionsValidator]) -> Set[str]:
all_variables: Set[str] = set()
for _, added, modified in self.get_added_and_modified_variables(additional_options):
all_variables.update(added)
all_variables.update(modified)
return all_variables

View file

@ -2,6 +2,7 @@ import copy
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Set
from mergedeep import mergedeep from mergedeep import mergedeep
@ -11,7 +12,6 @@ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
from ytdl_sub.config.plugin.preset_plugins import PresetPlugins from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.preset_options import OutputOptions
from ytdl_sub.config.preset_options import YTDLOptions from ytdl_sub.config.preset_options import YTDLOptions
from ytdl_sub.config.validators.variable_validation import VariableValidation
from ytdl_sub.downloaders.url.validators import MultiUrlValidator from ytdl_sub.downloaders.url.validators import MultiUrlValidator
from ytdl_sub.prebuilt_presets import PREBUILT_PRESET_NAMES from ytdl_sub.prebuilt_presets import PREBUILT_PRESET_NAMES
from ytdl_sub.prebuilt_presets import PUBLISHED_PRESET_NAMES from ytdl_sub.prebuilt_presets import PUBLISHED_PRESET_NAMES
@ -172,6 +172,37 @@ class Preset(_PresetShell):
mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE) mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE)
) )
def _initialize_overrides_script(self, overrides: Overrides) -> Overrides:
"""
Do some gymnastics to initialize the Overrides script.
"""
unresolved_variables: Set[str] = set()
for (
plugin_options,
added_variables,
modified_variables,
) in self.plugins.get_added_and_modified_variables(
additional_options=[self.downloader_options, self.output_options]
):
for added_variable in added_variables:
if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable):
# pylint: disable=protected-access
raise plugin_options._validation_exception(
f"Cannot use the variable name {added_variable} because it exists as a"
" built-in ytdl-sub variable name."
)
# pylint: enable=protected-access
# Set unresolved as variables that are added but do not exist as
# entry/override variables since they are created at run-time
unresolved_variables |= added_variables | modified_variables
# Initialize overrides with unresolved variables + modified variables to throw an error.
# For modified variables, this is to prevent a resolve(update=True) to setting any
# dependencies until it has been explicitly added
return overrides.initialize_script(unresolved_variables=unresolved_variables)
def __init__(self, config: ConfigValidator, name: str, value: Any): def __init__(self, config: ConfigValidator, name: str, value: Any):
super().__init__(name=name, value=value) super().__init__(name=name, value=value)
@ -192,16 +223,8 @@ class Preset(_PresetShell):
) )
self.plugins: PresetPlugins = self._validate_and_get_plugins() self.plugins: PresetPlugins = self._validate_and_get_plugins()
self.overrides = self._validate_key(key="overrides", validator=Overrides, default={}) self.overrides = self._initialize_overrides_script(
overrides=self._validate_key(key="overrides", validator=Overrides, default={})
self.validated_dict = (
VariableValidation(
downloader_options=self.downloader_options,
output_options=self.output_options,
plugins=self.plugins,
)
.initialize_preset_overrides(overrides=self.overrides)
.ensure_proper_usage()
) )
@property @property

View file

@ -100,48 +100,21 @@ class VariableValidation:
self.resolved_variables: Set[str] = set() self.resolved_variables: Set[str] = set()
self.unresolved_variables: Set[str] = set() self.unresolved_variables: Set[str] = set()
def initialize_preset_overrides(self, overrides: Overrides) -> "VariableValidation": def initialize_preset_overrides(
""" self,
Do some gymnastics to initialize the Overrides script. overrides: Overrides,
""" ) -> "VariableValidation":
override_variables = set(list(overrides.initial_variables().keys())) plugin_variables = self.plugins.get_all_variables(
additional_options=[self.output_options, self.downloader_options]
)
self.unresolved_variables = plugin_variables
# Set resolved variables as all entry + override variables # Set resolved variables as all entry + override variables
# at this point to generate every possible added/modified variable # at this point to generate every possible added/modified variable
self.resolved_variables = set(_DUMMY_ENTRY_VARIABLES.keys()) | override_variables self.resolved_variables = (
plugin_variables: Set[str] = set() set(_DUMMY_ENTRY_VARIABLES.keys()) | set(list(overrides.initial_variables().keys()))
) - self.unresolved_variables
for (
plugin_options,
added_variables,
modified_variables,
) in _get_added_and_modified_variables(
plugins=self.plugins,
downloader_options=self.downloader_options,
output_options=self.output_options,
):
for added_variable in added_variables:
if not overrides.ensure_added_plugin_variable_valid(added_variable=added_variable):
# pylint: disable=protected-access
raise plugin_options._validation_exception(
f"Cannot use the variable name {added_variable} because it exists as a"
" built-in ytdl-sub variable name."
)
# pylint: enable=protected-access
# Set unresolved as variables that are added but do not exist as
# entry/override variables since they are created at run-time
self.unresolved_variables |= added_variables | modified_variables
plugin_variables |= added_variables | modified_variables
# Then update resolved variables to reflect that
self.resolved_variables -= self.unresolved_variables
# Initialize overrides with unresolved variables + modified variables to throw an error.
# For modified variables, this is to prevent a resolve(update=True) to setting any
# dependencies until it has been explicitly added
overrides = overrides.initialize_script(unresolved_variables=self.unresolved_variables)
# copy the script and mock entry variables # copy the script and mock entry variables
self.script = copy.deepcopy(overrides.script) self.script = copy.deepcopy(overrides.script)
@ -153,9 +126,6 @@ class VariableValidation:
return self return self
def _update_script(self) -> None:
_ = self.script.resolve(unresolvable=self.unresolved_variables, update=True)
def _add_subscription_override_variables(self) -> None: def _add_subscription_override_variables(self) -> None:
""" """
Add dummy subscription variables for script validation Add dummy subscription variables for script validation

View file

@ -8,6 +8,7 @@ from ytdl_sub.config.plugin.preset_plugins import PresetPlugins
from ytdl_sub.config.preset import Preset from ytdl_sub.config.preset import Preset
from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.preset_options import OutputOptions
from ytdl_sub.config.preset_options import YTDLOptions from ytdl_sub.config.preset_options import YTDLOptions
from ytdl_sub.config.validators.variable_validation import VariableValidation
from ytdl_sub.downloaders.url.validators import MultiUrlValidator from ytdl_sub.downloaders.url.validators import MultiUrlValidator
from ytdl_sub.entries.variables.override_variables import SubscriptionVariables from ytdl_sub.entries.variables.override_variables import SubscriptionVariables
from ytdl_sub.utils.exceptions import SubscriptionPermissionError from ytdl_sub.utils.exceptions import SubscriptionPermissionError
@ -88,9 +89,9 @@ class BaseSubscription(ABC):
# Add post-archive variables # Add post-archive variables
self.overrides.add( self.overrides.add(
{ {
SubscriptionVariables.subscription_has_download_archive(): f"""{{ SubscriptionVariables.subscription_has_download_archive(): (
%bool({self.download_archive.num_entries > 0}) f"{{%bool({self.download_archive.num_entries > 0})}}"
}}""", ),
} }
) )
@ -102,6 +103,16 @@ class BaseSubscription(ABC):
f"{self.output_directory}" f"{self.output_directory}"
) )
self._validated_dict = (
VariableValidation(
downloader_options=self.downloader_options,
output_options=self.output_options,
plugins=self.plugins,
)
.initialize_preset_overrides(overrides=self.overrides)
.ensure_proper_usage()
)
@property @property
def download_archive(self) -> EnhancedDownloadArchive: def download_archive(self) -> EnhancedDownloadArchive:
""" """
@ -247,4 +258,4 @@ class BaseSubscription(ABC):
return self._preset_options.yaml return self._preset_options.yaml
def resolved_yaml(self): def resolved_yaml(self):
return self._preset_options.validated_dict return self._validated_dict

View file

@ -272,7 +272,7 @@ def validate_formatters(
# pylint: disable=protected-access # pylint: disable=protected-access
# Usage of protected variables in other validators is fine. The reason to keep # Usage of protected variables in other validators is fine. The reason to keep
# them protected is for readability when using them in subscriptions. # them protected is for readability when using them in subscriptions.
for key, validator_value in validator._validator_dict.items(): for validator_value in validator._validator_dict.values():
resolved_dict[validator._leaf_name] |= validate_formatters( resolved_dict[validator._leaf_name] |= validate_formatters(
script=script, script=script,
unresolved_variables=unresolved_variables, unresolved_variables=unresolved_variables,