This commit is contained in:
Jesse Bannon 2022-10-23 23:12:50 -07:00
parent a71577e703
commit d3c2c14452
9 changed files with 120 additions and 97 deletions

View file

@ -8,7 +8,7 @@ from typing import Tuple
from mergedeep import mergedeep from mergedeep import mergedeep
from ytdl_sub.cli.main_args_parser import MainArgs from ytdl_sub.cli.main_args_parser import MainArgs
from ytdl_sub.config.config_file import ConfigOptions from ytdl_sub.config.config_validator import ConfigOptions
from ytdl_sub.utils.exceptions import InvalidDlArguments from ytdl_sub.utils.exceptions import InvalidDlArguments

View file

@ -1,99 +1,19 @@
import os import os
from typing import Any from typing import Any
from typing import Dict
from typing import Optional
import mergedeep from ytdl_sub.config.config_validator import ConfigValidator
from ytdl_sub.config.preset import Preset
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
from ytdl_sub.utils.yaml import load_yaml from ytdl_sub.utils.yaml import load_yaml
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
from ytdl_sub.validators.validators import LiteralDictValidator
from ytdl_sub.validators.validators import StringValidator
class ConfigOptions(StrictDictValidator): class ConfigFile(ConfigValidator):
_required_keys = {"working_directory"}
_optional_keys = {"umask", "dl_aliases"}
def __init__(self, name: str, value: Any):
super().__init__(name, value)
self._working_directory = self._validate_key(
key="working_directory", validator=StringValidator
)
self._umask = self._validate_key_if_present(
key="umask", validator=StringValidator, default="022"
)
self._dl_aliases = self._validate_key_if_present(
key="dl_aliases", validator=LiteralDictValidator
)
@property
def working_directory(self) -> str:
"""
The directory to temporarily store downloaded files before moving them into their final
directory.
"""
return self._working_directory.value
@property
def umask(self) -> Optional[str]:
"""
Umask (octal format) to apply to every created file. Defaults to "022".
"""
return self._umask.value
@property
def dl_aliases(self) -> Optional[Dict[str, str]]:
"""
Alias definitions to shorten ``ytdl-sub dl`` arguments. For example,
.. code-block:: yaml
configuration:
dl_aliases:
mv: "--preset yt_music_video"
v: "--youtube.video_url"
Simplifies
.. code-block:: bash
ytdl-sub dl --preset "yt_music_video" --youtube.video_url "youtube.com/watch?v=a1b2c3"
to
.. code-block:: bash
ytdl-sub dl --mv --v "youtube.com/watch?v=a1b2c3"
"""
if self._dl_aliases:
return self._dl_aliases.dict
return {}
class ConfigFile(StrictDictValidator):
_required_keys = {"configuration", "presets"} _required_keys = {"configuration", "presets"}
def __init__(self, name: str, value: Any): def __init__(self, name: str, value: Any):
super().__init__(name, value) super().__init__(name, value)
self.config_options = self._validate_key("configuration", ConfigOptions)
prebuilt_presets = PREBUILT_PRESETS for preset_name, preset_dict in self.presets.dict.items():
Preset.preset_partial_validate(config=self, name=preset_name, value=preset_dict)
# Make sure presets is a dictionary. Will be validated in `PresetValidator`
self.presets = self._validate_key("presets", LiteralDictValidator)
# Ensure custom presets do not collide with prebuilt presets
for preset_name in self.presets.keys:
if preset_name in prebuilt_presets:
raise self._validation_exception(
f"preset name '{preset_name}' conflicts with a prebuilt preset"
)
# Merge prebuilt presets into the config so custom presets can use them
mergedeep.merge(self.presets._value, prebuilt_presets)
def initialize(self): def initialize(self):
""" """

View file

@ -0,0 +1,92 @@
from typing import Any
from typing import Dict
from typing import Optional
from mergedeep import mergedeep
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
from ytdl_sub.validators.validators import LiteralDictValidator
from ytdl_sub.validators.validators import StringValidator
class ConfigOptions(StrictDictValidator):
_required_keys = {"working_directory"}
_optional_keys = {"umask", "dl_aliases"}
def __init__(self, name: str, value: Any):
super().__init__(name, value)
self._working_directory = self._validate_key(
key="working_directory", validator=StringValidator
)
self._umask = self._validate_key_if_present(
key="umask", validator=StringValidator, default="022"
)
self._dl_aliases = self._validate_key_if_present(
key="dl_aliases", validator=LiteralDictValidator
)
@property
def working_directory(self) -> str:
"""
The directory to temporarily store downloaded files before moving them into their final
directory.
"""
return self._working_directory.value
@property
def umask(self) -> Optional[str]:
"""
Umask (octal format) to apply to every created file. Defaults to "022".
"""
return self._umask.value
@property
def dl_aliases(self) -> Optional[Dict[str, str]]:
"""
Alias definitions to shorten ``ytdl-sub dl`` arguments. For example,
.. code-block:: yaml
configuration:
dl_aliases:
mv: "--preset yt_music_video"
v: "--youtube.video_url"
Simplifies
.. code-block:: bash
ytdl-sub dl --preset "yt_music_video" --youtube.video_url "youtube.com/watch?v=a1b2c3"
to
.. code-block:: bash
ytdl-sub dl --mv --v "youtube.com/watch?v=a1b2c3"
"""
if self._dl_aliases:
return self._dl_aliases.dict
return {}
class ConfigValidator(StrictDictValidator):
_required_keys = {"configuration", "presets"}
def __init__(self, name: str, value: Any):
super().__init__(name, value)
self.config_options = self._validate_key("configuration", ConfigOptions)
# Make sure presets is a dictionary. Will be validated in `PresetValidator`
self.presets = self._validate_key("presets", LiteralDictValidator)
# Ensure custom presets do not collide with prebuilt presets
for preset_name in self.presets.keys:
if preset_name in PREBUILT_PRESETS:
raise self._validation_exception(
f"preset name '{preset_name}' conflicts with a prebuilt preset"
)
# Merge prebuilt presets into the config so custom presets can use them
mergedeep.merge(self.presets._value, PREBUILT_PRESETS)

View file

@ -11,7 +11,7 @@ from typing import Union
from mergedeep import mergedeep from mergedeep import mergedeep
from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.config_validator import ConfigValidator
from ytdl_sub.config.preset_class_mappings import DownloadStrategyMapping from ytdl_sub.config.preset_class_mappings import DownloadStrategyMapping
from ytdl_sub.config.preset_class_mappings import PluginMapping from ytdl_sub.config.preset_class_mappings import PluginMapping
from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.preset_options import OutputOptions
@ -33,6 +33,7 @@ from ytdl_sub.validators.validators import ListValidator
from ytdl_sub.validators.validators import StringListValidator from ytdl_sub.validators.validators import StringListValidator
from ytdl_sub.validators.validators import StringValidator from ytdl_sub.validators.validators import StringValidator
from ytdl_sub.validators.validators import Validator from ytdl_sub.validators.validators import Validator
from ytdl_sub.validators.validators import validation_exception
PRESET_KEYS = { PRESET_KEYS = {
"preset", "preset",
@ -129,7 +130,11 @@ class Preset(StrictDictValidator):
_optional_keys = PRESET_KEYS _optional_keys = PRESET_KEYS
@classmethod @classmethod
def preset_partial_validate(cls, config: ConfigFile, name: str, value: Any) -> None: def preset_partial_validate(cls, config: ConfigValidator, name: str, value: Any) -> None:
# Ensure value is a dict
_ = DictValidator(name=name, value=value)
assert isinstance(value, dict)
cls._partial_validate_key(name, value, "output_options", OutputOptions) cls._partial_validate_key(name, value, "output_options", OutputOptions)
cls._partial_validate_key(name, value, "ytdl_options", YTDLOptions) cls._partial_validate_key(name, value, "ytdl_options", YTDLOptions)
cls._partial_validate_key(name, value, "overrides", Overrides) cls._partial_validate_key(name, value, "overrides", Overrides)
@ -141,6 +146,15 @@ class Preset(StrictDictValidator):
validator=PluginMapping.get(plugin_name).plugin_options_type, validator=PluginMapping.get(plugin_name).plugin_options_type,
) )
parent_presets = StringListValidator(name=f"{name}.preset", value=value.get("preset", []))
for parent_preset_name in parent_presets.list:
if parent_preset_name.value not in config.presets.keys:
raise validation_exception(
name=f"{name}.preset",
error_message=f"preset '{parent_preset_name.value}' does not exist in the "
f"provided config. Available presets: {', '.join(config.presets.keys)}",
)
@property @property
def _source_variables(self) -> List[str]: def _source_variables(self) -> List[str]:
return Entry.source_variables() return Entry.source_variables()
@ -291,7 +305,7 @@ class Preset(StrictDictValidator):
self.__validate_override_string_formatter_validator(validator_value) self.__validate_override_string_formatter_validator(validator_value)
def _get_presets_to_merge( def _get_presets_to_merge(
self, parent_presets: str | List[str], seen_presets: List[str], config: ConfigFile self, parent_presets: str | List[str], seen_presets: List[str], config: ConfigValidator
) -> List[Dict]: ) -> List[Dict]:
presets_to_merge: List[Dict] = [] presets_to_merge: List[Dict] = []
@ -326,7 +340,7 @@ class Preset(StrictDictValidator):
return presets_to_merge return presets_to_merge
def __merge_parent_preset_dicts_if_present(self, config: ConfigFile): def __merge_parent_preset_dicts_if_present(self, config: ConfigValidator):
parent_preset_validator = self._validate_key_if_present( parent_preset_validator = self._validate_key_if_present(
key="preset", validator=StringListValidator key="preset", validator=StringListValidator
) )
@ -345,7 +359,7 @@ class Preset(StrictDictValidator):
mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE) mergedeep.merge({}, *reversed(presets_to_merge), strategy=mergedeep.Strategy.ADDITIVE)
) )
def __init__(self, config: ConfigFile, 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)
# Perform the merge of parent presets before validating any keys # Perform the merge of parent presets before validating any keys
@ -380,7 +394,7 @@ class Preset(StrictDictValidator):
return self._name return self._name
@classmethod @classmethod
def from_dict(cls, config: ConfigFile, preset_name: str, preset_dict: Dict) -> "Preset": def from_dict(cls, config: ConfigValidator, preset_name: str, preset_dict: Dict) -> "Preset":
""" """
Parameters Parameters
---------- ----------

View file

@ -13,7 +13,6 @@ from ytdl_sub.validators.string_formatter_validators import DictFormatterValidat
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
from ytdl_sub.validators.validators import BoolValidator from ytdl_sub.validators.validators import BoolValidator
from ytdl_sub.validators.validators import DictValidator
from ytdl_sub.validators.validators import LiteralDictValidator from ytdl_sub.validators.validators import LiteralDictValidator

View file

@ -2,7 +2,7 @@ from abc import ABC
from pathlib import Path from pathlib import Path
from typing import Type from typing import Type
from ytdl_sub.config.config_file import ConfigOptions from ytdl_sub.config.config_validator import ConfigOptions
from ytdl_sub.config.preset import Preset from ytdl_sub.config.preset import Preset
from ytdl_sub.config.preset import PresetPlugins from ytdl_sub.config.preset import PresetPlugins
from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.preset_options import OutputOptions

View file

@ -1,6 +1,5 @@
from abc import ABC from abc import ABC
from collections import defaultdict from collections import defaultdict
from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List

View file

@ -1,4 +1,3 @@
from typing import Any
from typing import List from typing import List
from typing import Set from typing import Set

View file

@ -9,7 +9,7 @@ import pytest
from ytdl_sub.cli.download_args_parser import DownloadArgsParser from ytdl_sub.cli.download_args_parser import DownloadArgsParser
from ytdl_sub.cli.main_args_parser import MainArgs from ytdl_sub.cli.main_args_parser import MainArgs
from ytdl_sub.cli.main_args_parser import parser from ytdl_sub.cli.main_args_parser import parser
from ytdl_sub.config.config_file import ConfigOptions from ytdl_sub.config.config_validator import ConfigOptions
from ytdl_sub.utils.exceptions import InvalidDlArguments from ytdl_sub.utils.exceptions import InvalidDlArguments