using validators
This commit is contained in:
parent
74169b9939
commit
329562bb0b
15 changed files with 220 additions and 227 deletions
|
|
@ -1,14 +1,12 @@
|
||||||
import argparse
|
import argparse
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from ytdl_subscribe.parse import parse_presets
|
|
||||||
from ytdl_subscribe.parse import parse_subscriptions
|
|
||||||
from ytdl_subscribe.parse import parse_subscriptions_file
|
|
||||||
from ytdl_subscribe.parse import read_config_file
|
|
||||||
|
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
# GLOBAL PARSER
|
# GLOBAL PARSER
|
||||||
from ytdl_subscribe.validators.config.config import Config
|
from ytdl_subscribe.validators.config.config_validator import ConfigValidator
|
||||||
|
from ytdl_subscribe.validators.config.subscription_validator import (
|
||||||
|
SubscriptionValidator,
|
||||||
|
)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="YoutubeDL-Subscribe: Download and organize your favorite media hassle-free."
|
description="YoutubeDL-Subscribe: Download and organize your favorite media hassle-free."
|
||||||
|
|
@ -43,10 +41,20 @@ download_parser.add_argument(
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
config: Config = Config.from_file_path(args.config)
|
config: ConfigValidator = ConfigValidator.from_file_path(args.config)
|
||||||
if args.subparser == "sub":
|
if args.subparser == "sub":
|
||||||
subscription_paths: List[str] = args.subscription_paths
|
subscription_paths: List[str] = args.subscription_paths
|
||||||
|
subscriptions: List[SubscriptionValidator] = []
|
||||||
|
|
||||||
|
for subscription_path in subscription_paths:
|
||||||
|
subscriptions += SubscriptionValidator.from_file_path(
|
||||||
|
config=config, subscription_path=subscription_path
|
||||||
|
)
|
||||||
|
|
||||||
|
for subscription in subscriptions:
|
||||||
|
subscription.to_subscription().extract_info()
|
||||||
|
|
||||||
|
print("hi")
|
||||||
# for subscription_path in subscription_paths:
|
# for subscription_path in subscription_paths:
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
from mergedeep import mergedeep
|
|
||||||
|
|
||||||
from ytdl_subscribe.enums import SubscriptionSourceName
|
|
||||||
from ytdl_subscribe.enums import YAMLSection
|
|
||||||
from ytdl_subscribe.subscriptions.soundcloud import SoundcloudSubscription
|
|
||||||
from ytdl_subscribe.subscriptions.subscription import Subscription
|
|
||||||
from ytdl_subscribe.subscriptions.youtube import YoutubeSubscription
|
|
||||||
from ytdl_subscribe.validators.config.config import Config
|
|
||||||
|
|
||||||
|
|
||||||
def _set_config_variables(config):
|
|
||||||
Subscription.WORKING_DIRECTORY = config.get("working_directory", "")
|
|
||||||
|
|
||||||
|
|
||||||
def read_config_file(config_path: str) -> Config:
|
|
||||||
with open(config_path, "r", encoding="utf-8") as file:
|
|
||||||
config_dict = yaml.safe_load(file)
|
|
||||||
|
|
||||||
return Config(name="config", value=config_dict).validate()
|
|
||||||
|
|
||||||
|
|
||||||
def parse_subscriptions_file(subscription_yaml_path, set_config_variables=True):
|
|
||||||
"""
|
|
||||||
Reads and parses a subscription yaml from its path.
|
|
||||||
TODO: trafaret the dict for validation
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
subscription_yaml_path: str
|
|
||||||
File path
|
|
||||||
set_config_variables: bool
|
|
||||||
Whether to set global config variables
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
dict
|
|
||||||
The subscription yaml after safe_load
|
|
||||||
"""
|
|
||||||
with open(subscription_yaml_path, "r") as f:
|
|
||||||
yaml_dict = yaml.safe_load(f)
|
|
||||||
|
|
||||||
config = yaml_dict[YAMLSection.CONFIG_KEY]
|
|
||||||
|
|
||||||
if set_config_variables:
|
|
||||||
_set_config_variables(config)
|
|
||||||
|
|
||||||
return yaml_dict
|
|
||||||
|
|
||||||
|
|
||||||
def parse_presets(yaml_dict):
|
|
||||||
"""
|
|
||||||
Parses presets from a subscription yaml dict
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
yaml_dict: dict
|
|
||||||
Subscription YAML dict
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
dict
|
|
||||||
Presets
|
|
||||||
"""
|
|
||||||
presets = yaml_dict[YAMLSection.PRESET_KEY]
|
|
||||||
return presets
|
|
||||||
|
|
||||||
|
|
||||||
def parse_subscriptions(yaml_dict: dict, presets: dict, subscriptions: Optional[list]):
|
|
||||||
"""
|
|
||||||
Parses config from a subscription yaml dict
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
yaml_dict: dict
|
|
||||||
presets: dict
|
|
||||||
subscriptions: list[str] or None
|
|
||||||
If present, only parse these config
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
list[Subscription]
|
|
||||||
"""
|
|
||||||
parsed_subscriptions = []
|
|
||||||
|
|
||||||
# Parse all config even if all are not used for validation's sake
|
|
||||||
for name, subscription in yaml_dict[YAMLSection.SUBSCRIPTIONS_KEY].items():
|
|
||||||
preset = {}
|
|
||||||
if subscription.get("preset") in presets:
|
|
||||||
preset = presets[subscription["preset"]]
|
|
||||||
subscription = mergedeep.merge({}, preset, subscription)
|
|
||||||
|
|
||||||
if SubscriptionSourceName.SOUNDCLOUD in subscription:
|
|
||||||
subscription_source = SubscriptionSourceName.SOUNDCLOUD
|
|
||||||
subscription_class = SoundcloudSubscription
|
|
||||||
elif SubscriptionSourceName.YOUTUBE in subscription:
|
|
||||||
subscription_source = SubscriptionSourceName.YOUTUBE
|
|
||||||
subscription_class = YoutubeSubscription
|
|
||||||
else:
|
|
||||||
raise ValueError("dne")
|
|
||||||
|
|
||||||
parsed_subscriptions.append(
|
|
||||||
subscription_class(
|
|
||||||
name=name,
|
|
||||||
options=subscription[subscription_source],
|
|
||||||
ytdl_opts=subscription["ytdl_opts"],
|
|
||||||
post_process=subscription["post_process"],
|
|
||||||
overrides=subscription["overrides"],
|
|
||||||
output_path=subscription["output_path"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Filter config if present
|
|
||||||
if subscriptions:
|
|
||||||
parsed_subscriptions = [
|
|
||||||
sub for sub in parsed_subscriptions if sub.name in subscriptions
|
|
||||||
]
|
|
||||||
|
|
||||||
return parsed_subscriptions
|
|
||||||
|
|
@ -15,7 +15,7 @@ class SoundcloudSubscription(Subscription):
|
||||||
ytdl_options=self.ytdl_opts,
|
ytdl_options=self.ytdl_opts,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.options.get("download_strategy") == "albums_then_tracks":
|
if self.options.get("download_strategy") == "albums_and_singles":
|
||||||
# Get the album info first. This tells us which track ids belong
|
# Get the album info first. This tells us which track ids belong
|
||||||
# to an album. Unfortunately we cannot use download_archive or info.json for this
|
# to an album. Unfortunately we cannot use download_archive or info.json for this
|
||||||
albums: List[SoundcloudAlbum] = soundcloud_downloader.download_albums(
|
albums: List[SoundcloudAlbum] = soundcloud_downloader.download_albums(
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,3 @@ class SubscriptionSourceName(object):
|
||||||
def pretty_all(cls) -> str:
|
def pretty_all(cls) -> str:
|
||||||
"""Returns all subscription sources pretty printed"""
|
"""Returns all subscription sources pretty printed"""
|
||||||
return ", ".join(cls.all())
|
return ", ".join(cls.all())
|
||||||
|
|
||||||
|
|
||||||
class YAMLSection(object):
|
|
||||||
CONFIG_KEY = "config"
|
|
||||||
PRESET_KEY = "presets"
|
|
||||||
SUBSCRIPTIONS_KEY = "config"
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class Validator:
|
class BaseValidator:
|
||||||
def __init__(self, name: str, value: Any):
|
def __init__(self, name: str, value: Any):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
def validate(self) -> "Validator":
|
def validate(self) -> "BaseValidator":
|
||||||
return self
|
return self
|
||||||
|
|
@ -2,11 +2,11 @@ from typing import Any
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
|
from ytdl_subscribe.validators.base.base_validator import BaseValidator
|
||||||
from ytdl_subscribe.validators.exceptions import ValidationException
|
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||||
from ytdl_subscribe.validators.native.validator import Validator
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectValidator(Validator):
|
class ObjectValidator(BaseValidator):
|
||||||
|
|
||||||
required_fields: Set[str] = []
|
required_fields: Set[str] = []
|
||||||
optional_fields: Set[str] = []
|
optional_fields: Set[str] = []
|
||||||
|
|
@ -4,34 +4,34 @@ from typing import Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from ytdl_subscribe.validators.config.preset import Preset
|
from ytdl_subscribe.validators.base.object_validator import ObjectValidator
|
||||||
from ytdl_subscribe.validators.native.object_validator import ObjectValidator
|
from ytdl_subscribe.validators.config.preset_validator import PresetValidator
|
||||||
|
|
||||||
|
|
||||||
class Config(ObjectValidator):
|
class ConfigValidator(ObjectValidator):
|
||||||
required_fields = {"working_directory", "presets"}
|
required_fields = {"working_directory", "presets"}
|
||||||
|
|
||||||
def __init__(self, name: str, value: Any):
|
def __init__(self, name: str, value: Any):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self.working_directory: Optional[str] = None
|
self.working_directory: Optional[str] = None
|
||||||
self.presets: Optional[Dict[str, Preset]] = None
|
self.presets: Optional[Dict[str, PresetValidator]] = None
|
||||||
|
|
||||||
def validate(self) -> "Config":
|
def validate(self) -> "ConfigValidator":
|
||||||
super().validate()
|
super().validate()
|
||||||
|
|
||||||
self.working_directory = self.get("working_directory")
|
self.working_directory = self.get("working_directory")
|
||||||
self.presets = {}
|
self.presets = {}
|
||||||
for preset_name, preset_obj in self.get("presets").items():
|
for preset_name, preset_obj in self.get("presets").items():
|
||||||
self.presets[preset_name] = Preset(
|
self.presets[preset_name] = PresetValidator(
|
||||||
name=f"{self.name}.presets.{preset_name}", value=preset_obj
|
name=f"{self.name}.presets.{preset_name}", value=preset_obj
|
||||||
).validate()
|
).validate()
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_file_path(cls, config_path) -> "Config":
|
def from_file_path(cls, config_path) -> "ConfigValidator":
|
||||||
# TODO: Create separate yaml file loader class
|
# TODO: Create separate yaml file loader class
|
||||||
with open(config_path, "r", encoding="utf-8") as file:
|
with open(config_path, "r", encoding="utf-8") as file:
|
||||||
config_dict = yaml.safe_load(file)
|
config_dict = yaml.safe_load(file)
|
||||||
|
|
||||||
return Config(name="config", value=config_dict).validate()
|
return ConfigValidator(name="config", value=config_dict).validate()
|
||||||
|
|
@ -1,22 +1,29 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ytdl_subscribe.enums import SubscriptionSourceName
|
from ytdl_subscribe.utils.enums import SubscriptionSourceName
|
||||||
from ytdl_subscribe.validators.config.sources import PresetSource
|
from ytdl_subscribe.validators.base.object_validator import ObjectValidator
|
||||||
from ytdl_subscribe.validators.config.sources import SoundcloudSource
|
from ytdl_subscribe.validators.config.sources.base_source_validator import (
|
||||||
from ytdl_subscribe.validators.config.sources import YoutubeSource
|
BaseSourceValidator,
|
||||||
|
)
|
||||||
|
from ytdl_subscribe.validators.config.sources.soundcloud_source_validator import (
|
||||||
|
SoundcloudSourceValidator,
|
||||||
|
)
|
||||||
|
from ytdl_subscribe.validators.config.sources.youtube_source_validator import (
|
||||||
|
YoutubeSourceValidator,
|
||||||
|
)
|
||||||
from ytdl_subscribe.validators.exceptions import ValidationException
|
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||||
from ytdl_subscribe.validators.native.object_validator import ObjectValidator
|
|
||||||
|
|
||||||
|
|
||||||
class Preset(ObjectValidator):
|
class PresetValidator(ObjectValidator):
|
||||||
required_fields = {"post_process"}
|
required_fields = {"post_process"}
|
||||||
optional_fields = {"ytdl_options", *SubscriptionSourceName.all()}
|
optional_fields = {"ytdl_options", "output_path", *SubscriptionSourceName.all()}
|
||||||
allow_extra_fields = False
|
allow_extra_fields = False
|
||||||
|
|
||||||
def __init__(self, name: str, value: Any):
|
def __init__(self, name: str, value: Any):
|
||||||
super().__init__(name=name, value=value)
|
super().__init__(name=name, value=value)
|
||||||
self.subscription_source: Optional[PresetSource] = None
|
self.subscription_source: Optional[BaseSourceValidator] = None
|
||||||
|
self.subscription_source_name: Optional[str] = None
|
||||||
|
|
||||||
def _validate_subscription_source(self):
|
def _validate_subscription_source(self):
|
||||||
# Make sure we only have a single subscription source
|
# Make sure we only have a single subscription source
|
||||||
|
|
@ -27,11 +34,13 @@ class Preset(ObjectValidator):
|
||||||
)
|
)
|
||||||
|
|
||||||
if object_key == SubscriptionSourceName.SOUNDCLOUD:
|
if object_key == SubscriptionSourceName.SOUNDCLOUD:
|
||||||
self.subscription_source = SoundcloudSource(
|
self.subscription_source_name = SubscriptionSourceName.SOUNDCLOUD
|
||||||
|
self.subscription_source = SoundcloudSourceValidator(
|
||||||
name=f"{self.name}{object_key}", value=object_value
|
name=f"{self.name}{object_key}", value=object_value
|
||||||
).validate()
|
).validate()
|
||||||
elif object_key == SubscriptionSourceName.YOUTUBE:
|
elif object_key == SubscriptionSourceName.YOUTUBE:
|
||||||
self.subscription_source = YoutubeSource(
|
self.subscription_source_name = SubscriptionSourceName.YOUTUBE
|
||||||
|
self.subscription_source = YoutubeSourceValidator(
|
||||||
name=f"{self.name}{object_key}", value=object_value
|
name=f"{self.name}{object_key}", value=object_value
|
||||||
).validate()
|
).validate()
|
||||||
|
|
||||||
|
|
@ -41,7 +50,7 @@ class Preset(ObjectValidator):
|
||||||
f"'{self.name} must have one of the following sources: {SubscriptionSourceName.pretty_all()}"
|
f"'{self.name} must have one of the following sources: {SubscriptionSourceName.pretty_all()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self) -> "Preset":
|
def validate(self) -> "PresetValidator":
|
||||||
super().validate()
|
super().validate()
|
||||||
self._validate_subscription_source()
|
self._validate_subscription_source()
|
||||||
return self
|
return self
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
from typing import Any
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
from ytdl_subscribe.validators.exceptions import ValidationException
|
|
||||||
from ytdl_subscribe.validators.native.object_validator import ObjectValidator
|
|
||||||
|
|
||||||
|
|
||||||
class PresetSource(ObjectValidator):
|
|
||||||
required_fields = {"download_strategy"}
|
|
||||||
download_strategies: Set[str] = {}
|
|
||||||
|
|
||||||
def __init__(self, name: str, value: Any):
|
|
||||||
super().__init__(name=name, value=value)
|
|
||||||
self.download_strategy: Optional[str] = None
|
|
||||||
|
|
||||||
def validate(self) -> "PresetSource":
|
|
||||||
super().validate()
|
|
||||||
|
|
||||||
if self.value["download_strategy"] not in self.download_strategies:
|
|
||||||
raise ValidationException(
|
|
||||||
f"'download_strategy' must be one of the following: {', '.join(self.download_strategies)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class YoutubeSource(PresetSource):
|
|
||||||
optional_fields = {"playlist_id"}
|
|
||||||
download_strategies = {"playlist"}
|
|
||||||
|
|
||||||
def __init__(self, name: str, value: Any):
|
|
||||||
super().__init__(name=name, value=value)
|
|
||||||
self.playlist_id: Optional[str] = None
|
|
||||||
|
|
||||||
def validate(self) -> "YoutubeSource":
|
|
||||||
super().validate()
|
|
||||||
|
|
||||||
if self.download_strategy == "playlist":
|
|
||||||
self.playlist_id = self.value.get("playlist_id")
|
|
||||||
if not self.playlist_id or not isinstance(self.playlist_id, str):
|
|
||||||
raise ValidationException(
|
|
||||||
"Youtube download_strategy 'playlist' requires the field 'playlist_id' to be a string"
|
|
||||||
)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class SoundcloudSource(PresetSource):
|
|
||||||
optional_fields = {"username", "skip_premiere_tracks"}
|
|
||||||
download_strategies = {"albums_and_singles"}
|
|
||||||
|
|
||||||
def __init__(self, name: str, value: Any):
|
|
||||||
super().__init__(name=name, value=value)
|
|
||||||
self.username: Optional[str] = None
|
|
||||||
self.skip_premiere_tracks: Optional[bool] = None
|
|
||||||
|
|
||||||
def validate(self) -> "SoundcloudSource":
|
|
||||||
super().validate()
|
|
||||||
|
|
||||||
self.skip_premiere_tracks = self.get("skip_premiere_tracks", True)
|
|
||||||
if self.download_strategy == "albums_and_singles":
|
|
||||||
self.username = self.value.get("username")
|
|
||||||
if not self.username or not isinstance(self.username, str):
|
|
||||||
raise ValidationException(
|
|
||||||
"Soundcloud download_strategy 'albums_and_singles' requires the field 'username'"
|
|
||||||
)
|
|
||||||
|
|
||||||
return self
|
|
||||||
0
ytdl_subscribe/validators/config/sources/__init__.py
Normal file
0
ytdl_subscribe/validators/config/sources/__init__.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
from typing import Any
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from ytdl_subscribe.validators.base.object_validator import ObjectValidator
|
||||||
|
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSourceValidator(ObjectValidator):
|
||||||
|
required_fields = {"download_strategy"}
|
||||||
|
download_strategies: Set[str] = {}
|
||||||
|
|
||||||
|
def __init__(self, name: str, value: Any):
|
||||||
|
super().__init__(name=name, value=value)
|
||||||
|
self.download_strategy: Optional[str] = None
|
||||||
|
|
||||||
|
def validate(self) -> "BaseSourceValidator":
|
||||||
|
super().validate()
|
||||||
|
|
||||||
|
if self.value["download_strategy"] not in self.download_strategies:
|
||||||
|
raise ValidationException(
|
||||||
|
f"'download_strategy' must be one of the following: {', '.join(self.download_strategies)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from typing import Any
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ytdl_subscribe.validators.config.sources.base_source_validator import (
|
||||||
|
BaseSourceValidator,
|
||||||
|
)
|
||||||
|
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||||
|
|
||||||
|
|
||||||
|
class SoundcloudSourceValidator(BaseSourceValidator):
|
||||||
|
optional_fields = {"username", "skip_premiere_tracks"}
|
||||||
|
download_strategies = {"albums_and_singles"}
|
||||||
|
|
||||||
|
def __init__(self, name: str, value: Any):
|
||||||
|
super().__init__(name=name, value=value)
|
||||||
|
self.username: Optional[str] = None
|
||||||
|
self.skip_premiere_tracks: Optional[bool] = None
|
||||||
|
|
||||||
|
def validate(self) -> "SoundcloudSourceValidator":
|
||||||
|
super().validate()
|
||||||
|
|
||||||
|
self.skip_premiere_tracks = self.get("skip_premiere_tracks", True)
|
||||||
|
if self.download_strategy == "albums_and_singles":
|
||||||
|
self.username = self.value.get("username")
|
||||||
|
if not self.username or not isinstance(self.username, str):
|
||||||
|
raise ValidationException(
|
||||||
|
"Soundcloud download_strategy 'albums_and_singles' requires the field 'username'"
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
from typing import Any
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ytdl_subscribe.validators.config.sources.base_source_validator import (
|
||||||
|
BaseSourceValidator,
|
||||||
|
)
|
||||||
|
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||||
|
|
||||||
|
|
||||||
|
class YoutubeSourceValidator(BaseSourceValidator):
|
||||||
|
optional_fields = {"playlist_id"}
|
||||||
|
download_strategies = {"playlist"}
|
||||||
|
|
||||||
|
def __init__(self, name: str, value: Any):
|
||||||
|
super().__init__(name=name, value=value)
|
||||||
|
self.playlist_id: Optional[str] = None
|
||||||
|
|
||||||
|
def validate(self) -> "YoutubeSourceValidator":
|
||||||
|
super().validate()
|
||||||
|
|
||||||
|
if self.download_strategy == "playlist":
|
||||||
|
self.playlist_id = self.value.get("playlist_id")
|
||||||
|
if not self.playlist_id or not isinstance(self.playlist_id, str):
|
||||||
|
raise ValidationException(
|
||||||
|
"Youtube download_strategy 'playlist' requires the field 'playlist_id' to be a string"
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
||||||
89
ytdl_subscribe/validators/config/subscription_validator.py
Normal file
89
ytdl_subscribe/validators/config/subscription_validator.py
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from mergedeep import mergedeep
|
||||||
|
|
||||||
|
from ytdl_subscribe.subscriptions.soundcloud import SoundcloudSubscription
|
||||||
|
from ytdl_subscribe.subscriptions.subscription import Subscription
|
||||||
|
from ytdl_subscribe.subscriptions.youtube import YoutubeSubscription
|
||||||
|
from ytdl_subscribe.utils.enums import SubscriptionSourceName
|
||||||
|
from ytdl_subscribe.validators.config.config_validator import ConfigValidator
|
||||||
|
from ytdl_subscribe.validators.config.preset_validator import PresetValidator
|
||||||
|
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionValidator(PresetValidator):
|
||||||
|
"""
|
||||||
|
A Subscription is a preset but overrides it with specific values
|
||||||
|
"""
|
||||||
|
|
||||||
|
required_fields = {"preset"}
|
||||||
|
optional_fields = {"overrides"}.union(
|
||||||
|
PresetValidator.required_fields, PresetValidator.optional_fields
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: ConfigValidator, name: str, value: Any):
|
||||||
|
super().__init__(name, value)
|
||||||
|
self.config = config
|
||||||
|
self.overrides: Optional[Dict] = None
|
||||||
|
|
||||||
|
def validate(self) -> "SubscriptionValidator":
|
||||||
|
# If a preset is present, update the
|
||||||
|
preset_name = self.get("preset")
|
||||||
|
available_presets = sorted(list(self.config.presets.keys()))
|
||||||
|
if preset_name not in available_presets:
|
||||||
|
raise ValidationException(
|
||||||
|
f"'{self.name}.preset' does not exist in the provided config. "
|
||||||
|
f"Available presets: {', '.join(available_presets)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.overrides = self.get("overrides", {})
|
||||||
|
|
||||||
|
# A little hacky, we will override the preset with the contents of this subscription, then validate it
|
||||||
|
self.value = mergedeep.merge(
|
||||||
|
self.config.presets[preset_name].value,
|
||||||
|
self.value,
|
||||||
|
strategy=mergedeep.Strategy.REPLACE,
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = super().validate()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def to_subscription(self) -> Subscription:
|
||||||
|
subscription_class = None
|
||||||
|
if self.subscription_source_name == SubscriptionSourceName.SOUNDCLOUD:
|
||||||
|
subscription_class = SoundcloudSubscription
|
||||||
|
elif self.subscription_source_name == SubscriptionSourceName.YOUTUBE:
|
||||||
|
subscription_class = YoutubeSubscription
|
||||||
|
else:
|
||||||
|
raise ValueError("subscription source class not found")
|
||||||
|
|
||||||
|
return subscription_class(
|
||||||
|
name=self.name,
|
||||||
|
options=self.get(self.subscription_source_name),
|
||||||
|
ytdl_opts=self.get("ytdl_options"),
|
||||||
|
post_process=self.get("post_process"),
|
||||||
|
overrides=self.get("overrides"),
|
||||||
|
output_path=self.get("output_path"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_file_path(
|
||||||
|
cls, config: ConfigValidator, subscription_path: str
|
||||||
|
) -> List["SubscriptionValidator"]:
|
||||||
|
# TODO: Create separate yaml file loader class
|
||||||
|
with open(subscription_path, "r", encoding="utf-8") as file:
|
||||||
|
subscription_dict = yaml.safe_load(file)
|
||||||
|
|
||||||
|
subscriptions: List["SubscriptionValidator"] = []
|
||||||
|
for subscription_key, subscription_object in subscription_dict.items():
|
||||||
|
subscriptions.append(
|
||||||
|
SubscriptionValidator(
|
||||||
|
config=config, name=subscription_key, value=subscription_object
|
||||||
|
).validate()
|
||||||
|
)
|
||||||
|
|
||||||
|
return subscriptions
|
||||||
Loading…
Reference in a new issue