protected variables for easier debugging
This commit is contained in:
parent
ac378ab962
commit
922ac3b7cf
21 changed files with 152 additions and 138 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
# ytdl-sub needs a temporary place to download files. Choose that path here
|
# ytdl-sub needs a temporary place to download files. Choose that path here
|
||||||
working_directory: 'tmp'
|
configuration:
|
||||||
|
working_directory: 'tmp'
|
||||||
|
|
||||||
presets:
|
presets:
|
||||||
soundcloud_with_id3_tags:
|
soundcloud_with_id3_tags:
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class TestEntry(object):
|
||||||
|
|
||||||
def test_entry_formatter_fails_missing_field(self, mock_entry):
|
def test_entry_formatter_fails_missing_field(self, mock_entry):
|
||||||
format_string = f"prefix {{bah_humbug}} suffix"
|
format_string = f"prefix {{bah_humbug}} suffix"
|
||||||
available_fields = ", ".join(sorted(mock_entry.to_dict().keys()))
|
available_fields = ", ".join(sorted(mock_entry.to_dict()._keys()))
|
||||||
expected_error_msg = f"Format variable 'bah_humbug' does not exist for Entry. Available fields: {available_fields}"
|
expected_error_msg = f"Format variable 'bah_humbug' does not exist for Entry. Available fields: {available_fields}"
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=expected_error_msg):
|
with pytest.raises(ValueError, match=expected_error_msg):
|
||||||
|
|
|
||||||
|
|
@ -44,18 +44,15 @@ class Downloader:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
output_directory: str,
|
output_directory: str,
|
||||||
working_directory: Optional[str] = None,
|
|
||||||
ytdl_options: Optional[Dict] = None,
|
ytdl_options: Optional[Dict] = None,
|
||||||
):
|
):
|
||||||
self.output_path = output_directory
|
self.output_directory = output_directory
|
||||||
|
if self.output_directory is None:
|
||||||
self.working_directory = working_directory
|
self.output_directory = tempfile.TemporaryDirectory().name
|
||||||
if self.working_directory is None:
|
|
||||||
self.working_directory = tempfile.TemporaryDirectory().name
|
|
||||||
|
|
||||||
self.ytdl_options = Downloader._configure_ytdl_options(
|
self.ytdl_options = Downloader._configure_ytdl_options(
|
||||||
ytdl_options=ytdl_options,
|
ytdl_options=ytdl_options,
|
||||||
working_directory=self.working_directory,
|
working_directory=self.output_directory,
|
||||||
)
|
)
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class YoutubeDownloader(Downloader):
|
||||||
"""
|
"""
|
||||||
ytdl_metadata_override = {
|
ytdl_metadata_override = {
|
||||||
"download_archive": str(
|
"download_archive": str(
|
||||||
Path(self.working_directory) / "ytdl-download-archive.txt"
|
Path(self.output_directory) / "ytdl-download-archive.txt"
|
||||||
),
|
),
|
||||||
"writeinfojson": True,
|
"writeinfojson": True,
|
||||||
}
|
}
|
||||||
|
|
@ -44,12 +44,12 @@ class YoutubeDownloader(Downloader):
|
||||||
entries: List[YoutubeVideo] = []
|
entries: List[YoutubeVideo] = []
|
||||||
|
|
||||||
# Load the entries from info.json, ignore the playlist entry
|
# Load the entries from info.json, ignore the playlist entry
|
||||||
for file_name in os.listdir(self.working_directory):
|
for file_name in os.listdir(self.output_directory):
|
||||||
if file_name.endswith(".info.json") and not file_name.startswith(
|
if file_name.endswith(".info.json") and not file_name.startswith(
|
||||||
playlist_id
|
playlist_id
|
||||||
):
|
):
|
||||||
with open(
|
with open(
|
||||||
Path(self.working_directory) / file_name, "r", encoding="utf-8"
|
Path(self.output_directory) / file_name, "r", encoding="utf-8"
|
||||||
) as file:
|
) as file:
|
||||||
entries.append(YoutubeVideo(**json.load(file)))
|
entries.append(YoutubeVideo(**json.load(file)))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from typing import List
|
||||||
|
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
# GLOBAL PARSER
|
# GLOBAL PARSER
|
||||||
from ytdl_subscribe.validators.config.config_validator import ConfigValidator
|
from ytdl_subscribe.validators.config.config_validator import ConfigFileValidator
|
||||||
from ytdl_subscribe.validators.config.subscription_validator import (
|
from ytdl_subscribe.validators.config.subscription_validator import (
|
||||||
SubscriptionValidator,
|
SubscriptionValidator,
|
||||||
)
|
)
|
||||||
|
|
@ -42,7 +42,7 @@ download_parser.add_argument(
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
config: ConfigValidator = ConfigValidator.from_file_path(args.config)
|
config: ConfigFileValidator = ConfigFileValidator.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] = []
|
subscriptions: List[SubscriptionValidator] = []
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ class SoundcloudAlbumsAndSinglesSubscription(Subscription):
|
||||||
|
|
||||||
tracks: List[SoundcloudTrack] = []
|
tracks: List[SoundcloudTrack] = []
|
||||||
soundcloud_downloader = SoundcloudDownloader(
|
soundcloud_downloader = SoundcloudDownloader(
|
||||||
output_directory=self.output_options.output_directory.value,
|
output_directory=self.config_options.working_directory.value,
|
||||||
working_directory=self.config_options.working_directory.value,
|
|
||||||
ytdl_options=self.ytdl_options.dict,
|
ytdl_options=self.ytdl_options.dict,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import music_tag
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from ytdl_subscribe.entries.entry import Entry
|
from ytdl_subscribe.entries.entry import Entry
|
||||||
from ytdl_subscribe.validators.config.config_validator import ConfigValidator
|
from ytdl_subscribe.validators.config.config_validator import ConfigOptionsValidator
|
||||||
from ytdl_subscribe.validators.config.metadata_options.metadata_options_validator import (
|
from ytdl_subscribe.validators.config.metadata_options.metadata_options_validator import (
|
||||||
MetadataOptionsValidator,
|
MetadataOptionsValidator,
|
||||||
)
|
)
|
||||||
|
|
@ -34,7 +34,7 @@ class Subscription(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
config_options: ConfigValidator,
|
config_options: ConfigOptionsValidator,
|
||||||
source_options: SourceValidator,
|
source_options: SourceValidator,
|
||||||
output_options: OutputOptionsValidator,
|
output_options: OutputOptionsValidator,
|
||||||
metadata_options: MetadataOptionsValidator,
|
metadata_options: MetadataOptionsValidator,
|
||||||
|
|
@ -46,7 +46,7 @@ class Subscription(object):
|
||||||
----------
|
----------
|
||||||
name: str
|
name: str
|
||||||
Name of the subscription
|
Name of the subscription
|
||||||
config_options: ConfigValidator
|
config_options: ConfigOptionsValidator
|
||||||
source_options: SourceValidator
|
source_options: SourceValidator
|
||||||
output_options: OutputOptionsValidator
|
output_options: OutputOptionsValidator
|
||||||
metadata_options: MetadataOptionsValidator
|
metadata_options: MetadataOptionsValidator
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,7 @@ class YoutubeSubscription(Subscription):
|
||||||
|
|
||||||
def extract_info(self):
|
def extract_info(self):
|
||||||
youtube_downloader = YoutubeDownloader(
|
youtube_downloader = YoutubeDownloader(
|
||||||
output_directory=self.output_options.output_directory.value,
|
output_directory=self.config_options.working_directory.value,
|
||||||
working_directory=self.config_options.working_directory.value,
|
|
||||||
ytdl_options=self.ytdl_options.dict,
|
ytdl_options=self.ytdl_options.dict,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,13 @@ class StrictDictValidator(DictValidator):
|
||||||
|
|
||||||
# Ensure all required keys are present
|
# Ensure all required keys are present
|
||||||
for required_key in self._required_keys:
|
for required_key in self._required_keys:
|
||||||
if required_key not in self.value:
|
if required_key not in self._dict:
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
f"missing the required field '{required_key}'"
|
f"missing the required field '{required_key}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure an empty dict was not passed as the value
|
# Ensure an empty dict was not passed as the value
|
||||||
if not self.dict:
|
if not self._dict:
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
f"at least one of the following fields must be defined: "
|
f"at least one of the following fields must be defined: "
|
||||||
f"{', '.join(self._optional_keys)}'"
|
f"{', '.join(self._optional_keys)}'"
|
||||||
|
|
@ -39,10 +39,10 @@ class StrictDictValidator(DictValidator):
|
||||||
|
|
||||||
# Ensure all keys are either required or optional keys if no extra field are allowed
|
# Ensure all keys are either required or optional keys if no extra field are allowed
|
||||||
if not self._allow_extra_keys:
|
if not self._allow_extra_keys:
|
||||||
for object_key in self.keys:
|
for object_key in self._keys:
|
||||||
if object_key not in self._allowed_keys:
|
if object_key not in self._allowed_keys:
|
||||||
error_msg = (
|
error_msg = (
|
||||||
f"'{self.name}' contains the field '{object_key}' which is not allowed. "
|
f"'{self._name}' contains the field '{object_key}' which is not allowed. "
|
||||||
f"Allowed fields: {', '.join(self._allowed_keys)}"
|
f"Allowed fields: {', '.join(self._allowed_keys)}"
|
||||||
)
|
)
|
||||||
raise ValidationException(error_msg)
|
raise ValidationException(error_msg)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import re
|
import re
|
||||||
from keyword import iskeyword
|
from keyword import iskeyword
|
||||||
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from ytdl_subscribe.validators.base.validators import DictValidator
|
from ytdl_subscribe.validators.base.validators import DictValidator
|
||||||
|
|
@ -13,7 +14,7 @@ class StringFormatterValidator(StringValidator):
|
||||||
|
|
||||||
_expected_value_type_name = "format string"
|
_expected_value_type_name = "format string"
|
||||||
|
|
||||||
FIELDS_VALIDATOR = re.compile(r"{([a-z_]+?)}")
|
__fields_validator = re.compile(r"{([a-z_]+?)}")
|
||||||
|
|
||||||
def __validate_and_get_format_variables(self) -> List[str]:
|
def __validate_and_get_format_variables(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -37,7 +38,7 @@ class StringFormatterValidator(StringValidator):
|
||||||
)
|
)
|
||||||
|
|
||||||
format_variables: List[str] = list(
|
format_variables: List[str] = list(
|
||||||
re.findall(StringFormatterValidator.FIELDS_VALIDATOR, self.format_string)
|
re.findall(StringFormatterValidator.__fields_validator, self.format_string)
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(format_variables) != open_bracket_count:
|
if len(format_variables) != open_bracket_count:
|
||||||
|
|
@ -76,5 +77,9 @@ class DictFormatterValidator(DictValidator):
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
||||||
for key in self.keys:
|
for key in self._keys:
|
||||||
_ = self.validate_key(key=key, validator=StringFormatterValidator)
|
_ = self._validate_key(key=key, validator=StringFormatterValidator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dict(self) -> Dict[str, str]:
|
||||||
|
return self._dict
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class Validator:
|
||||||
_expected_value_type_name: Optional[str] = None
|
_expected_value_type_name: Optional[str] = None
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
if not isinstance(self._value, self._expected_value_type):
|
if not isinstance(self._value, self._expected_value_type):
|
||||||
|
|
@ -32,15 +32,6 @@ class Validator:
|
||||||
error_message=f"should be of type {expected_value_type_name}."
|
error_message=f"should be of type {expected_value_type_name}."
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def value(self) -> object:
|
|
||||||
"""
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Value of the validator
|
|
||||||
"""
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def _validation_exception(self, error_message: str) -> ValidationException:
|
def _validation_exception(self, error_message: str) -> ValidationException:
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -52,7 +43,7 @@ class Validator:
|
||||||
-------
|
-------
|
||||||
Validation exception with a consistent prefix.
|
Validation exception with a consistent prefix.
|
||||||
"""
|
"""
|
||||||
prefix = f"Validation error in {self.name}: "
|
prefix = f"Validation error in {self._name}: "
|
||||||
return ValidationException(f"{prefix}{error_message}")
|
return ValidationException(f"{prefix}{error_message}")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -79,7 +70,7 @@ class StringValidator(Validator):
|
||||||
Validates string fields.
|
Validates string fields.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_expected_value_type: Type = str
|
_expected_value_type = str
|
||||||
_expected_value_type_name = "string"
|
_expected_value_type_name = "string"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -105,7 +96,7 @@ class DictValidator(Validator):
|
||||||
_expected_value_type_name = "object"
|
_expected_value_type_name = "object"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dict(self) -> dict:
|
def _dict(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
@ -114,15 +105,15 @@ class DictValidator(Validator):
|
||||||
return self._value
|
return self._value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def keys(self) -> List[str]:
|
def _keys(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
Sorted list of dictionary keys
|
Sorted list of dictionary keys
|
||||||
"""
|
"""
|
||||||
return sorted(list(self.dict.keys()))
|
return sorted(list(self._dict.keys()))
|
||||||
|
|
||||||
def validate_key(
|
def _validate_key(
|
||||||
self,
|
self,
|
||||||
key: str,
|
key: str,
|
||||||
validator: Type[T],
|
validator: Type[T],
|
||||||
|
|
@ -142,24 +133,24 @@ class DictValidator(Validator):
|
||||||
-------
|
-------
|
||||||
An instance of the specified validator
|
An instance of the specified validator
|
||||||
"""
|
"""
|
||||||
value = self.dict.get(key, default)
|
value = self._dict.get(key, default)
|
||||||
if value is None:
|
if value is None:
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
f"{key} is missing when it should be present."
|
f"{key} is missing when it should be present."
|
||||||
)
|
)
|
||||||
|
|
||||||
return validator(
|
return validator(
|
||||||
name=f"{self.name}.{key}",
|
name=f"{self._name}.{key}",
|
||||||
value=value,
|
value=value,
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_key_if_present(
|
def _validate_key_if_present(
|
||||||
self,
|
self,
|
||||||
key: str,
|
key: str,
|
||||||
validator: Type[T],
|
validator: Type[T],
|
||||||
default: Optional[Any] = None,
|
default: Optional[Any] = None,
|
||||||
) -> Optional[T]:
|
) -> Optional[T]:
|
||||||
if key not in self.dict:
|
if key not in self._dict:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.validate_key(key=key, validator=validator, default=default)
|
return self._validate_key(key=key, validator=validator, default=default)
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,34 @@ from ytdl_subscribe.validators.base.validators import DictValidator
|
||||||
from ytdl_subscribe.validators.base.validators import StringValidator
|
from ytdl_subscribe.validators.base.validators import StringValidator
|
||||||
|
|
||||||
|
|
||||||
class ConfigValidator(StrictDictValidator):
|
class ConfigOptionsValidator(StrictDictValidator):
|
||||||
_required_keys = {"working_directory", "presets"}
|
_required_keys = {"working_directory"}
|
||||||
|
|
||||||
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 = self.validate_key("working_directory", StringValidator)
|
|
||||||
self.presets = self.validate_key("presets", DictValidator)
|
self.working_directory = self._validate_key(
|
||||||
|
key="working_directory", validator=StringValidator
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFileValidator(StrictDictValidator):
|
||||||
|
_required_keys = {"configuration", "presets"}
|
||||||
|
|
||||||
|
def __init__(self, name: str, value: Any):
|
||||||
|
super().__init__(name, value)
|
||||||
|
self.config_options = self._validate_key(
|
||||||
|
"configuration", ConfigOptionsValidator
|
||||||
|
)
|
||||||
|
self.presets = self._validate_key("presets", DictValidator)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, config_dict) -> "ConfigValidator":
|
def from_dict(cls, config_dict) -> "ConfigFileValidator":
|
||||||
return ConfigValidator(name="config", value=config_dict)
|
return ConfigFileValidator(name="", value=config_dict)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_file_path(cls, config_path) -> "ConfigValidator":
|
def from_file_path(cls, config_path) -> "ConfigFileValidator":
|
||||||
# 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 ConfigValidator.from_dict(config_dict)
|
return ConfigFileValidator.from_dict(config_dict)
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ class Id3Validator(StrictDictValidator):
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
||||||
self.id3_version = self.validate_key(
|
self.id3_version = self._validate_key(
|
||||||
key="id3_version", validator=Id3VersionValidator
|
key="id3_version", validator=Id3VersionValidator
|
||||||
).value
|
).value
|
||||||
self.tags = self.validate_key(key="tags", validator=DictFormatterValidator)
|
self.tags = self._validate_key(key="tags", validator=DictFormatterValidator)
|
||||||
|
|
||||||
self.multi_value_separator = self.validate_key_if_present(
|
self.multi_value_separator = self._validate_key_if_present(
|
||||||
key="multi_value_separator", validator=StringValidator
|
key="multi_value_separator", validator=StringValidator
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,5 @@ class MetadataOptionsValidator(StrictDictValidator):
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
||||||
self.id3 = self.validate_key_if_present(key="id3", validator=Id3Validator)
|
self.id3 = self._validate_key_if_present(key="id3", validator=Id3Validator)
|
||||||
self.nfo = self.validate_key_if_present(key="nfo", validator=NFOValidator)
|
self.nfo = self._validate_key_if_present(key="nfo", validator=NFOValidator)
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ class NFOValidator(StrictDictValidator):
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
||||||
self.nfo_name = self.validate_key(
|
self.nfo_name = self._validate_key(
|
||||||
key="nfo_name", validator=StringFormatterValidator
|
key="nfo_name", validator=StringFormatterValidator
|
||||||
)
|
)
|
||||||
self.nfo_root = self.validate_key(
|
self.nfo_root = self._validate_key(
|
||||||
key="nfo_root", validator=StringFormatterValidator
|
key="nfo_root", validator=StringFormatterValidator
|
||||||
)
|
)
|
||||||
self.tags = self.validate_key(key="tags", validator=DictFormatterValidator)
|
self.tags = self._validate_key(key="tags", validator=DictFormatterValidator)
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,16 @@ class OutputOptionsValidator(StrictDictValidator):
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
||||||
self.output_directory: StringFormatterValidator = self.validate_key(
|
self.output_directory: StringFormatterValidator = self._validate_key(
|
||||||
key="output_directory", validator=StringFormatterValidator
|
key="output_directory", validator=StringFormatterValidator
|
||||||
)
|
)
|
||||||
self.file_name: StringFormatterValidator = self.validate_key(
|
self.file_name: StringFormatterValidator = self._validate_key(
|
||||||
key="file_name", validator=StringFormatterValidator
|
key="file_name", validator=StringFormatterValidator
|
||||||
)
|
)
|
||||||
|
|
||||||
self.convert_thumbnail = self.validate_key_if_present(
|
self.convert_thumbnail = self._validate_key_if_present(
|
||||||
key="convert_thumbnail", validator=ConvertThumbnailValidator
|
key="convert_thumbnail", validator=ConvertThumbnailValidator
|
||||||
)
|
)
|
||||||
self.thumbnail_name = self.validate_key_if_present(
|
self.thumbnail_name = self._validate_key_if_present(
|
||||||
key="thumbnail_name", validator=StringFormatterValidator
|
key="thumbnail_name", validator=StringFormatterValidator
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,17 @@ from ytdl_subscribe.validators.exceptions import ValidationException
|
||||||
|
|
||||||
|
|
||||||
class YTDLOptionsValidator(DictValidator):
|
class YTDLOptionsValidator(DictValidator):
|
||||||
pass
|
@property
|
||||||
|
def dict(self) -> Dict:
|
||||||
|
return self._dict
|
||||||
|
|
||||||
|
|
||||||
class OverridesValidator(DictFormatterValidator):
|
class OverridesValidator(DictFormatterValidator):
|
||||||
@property
|
@property
|
||||||
def dict(self) -> dict:
|
def dict(self) -> Dict[str, str]:
|
||||||
"""For overrides, create sanitized versions of each entry for convenience"""
|
"""For overrides, create sanitized versions of each entry for convenience"""
|
||||||
output_dict = copy.deepcopy(super().dict)
|
output_dict = copy.deepcopy(self._dict)
|
||||||
for key in list(output_dict.keys()):
|
for key in self._keys:
|
||||||
output_dict[f"sanitized_{key}"] = sanitize_filename.sanitize(
|
output_dict[f"sanitized_{key}"] = sanitize_filename.sanitize(
|
||||||
output_dict[key]
|
output_dict[key]
|
||||||
)
|
)
|
||||||
|
|
@ -46,69 +48,69 @@ class OverridesValidator(DictFormatterValidator):
|
||||||
return output_dict
|
return output_dict
|
||||||
|
|
||||||
|
|
||||||
class PresetValidator(StrictDictValidator):
|
PRESET_SOURCE_VALIDATOR_MAPPING: Dict[str, Type[SourceValidator]] = {
|
||||||
_subscription_source_validator_mapping: Dict[str, Type[SourceValidator]] = {
|
"soundcloud": SoundcloudSourceValidator,
|
||||||
"soundcloud": SoundcloudSourceValidator,
|
"youtube": YoutubeSourceValidator,
|
||||||
"youtube": YoutubeSourceValidator,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_required_keys = {"output_options"}
|
PRESET_REQUIRED_KEYS = {"output_options"}
|
||||||
_optional_keys = {
|
PRESET_OPTIONAL_KEYS = {
|
||||||
"metadata_options",
|
"metadata_options",
|
||||||
"ytdl_options",
|
"ytdl_options",
|
||||||
"overrides",
|
"overrides",
|
||||||
*_subscription_source_validator_mapping.keys(),
|
*PRESET_SOURCE_VALIDATOR_MAPPING.keys(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PresetValidator(StrictDictValidator):
|
||||||
|
_required_keys = PRESET_REQUIRED_KEYS
|
||||||
|
_optional_keys = PRESET_OPTIONAL_KEYS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available_sources(self) -> List[str]:
|
def __available_sources(self) -> List[str]:
|
||||||
return sorted(list(self._subscription_source_validator_mapping.keys()))
|
return sorted(list(PRESET_SOURCE_VALIDATOR_MAPPING.keys()))
|
||||||
|
|
||||||
def __validate_and_get_subscription_source(self) -> Tuple[str, SourceValidator]:
|
def __validate_and_get_subscription_source(self) -> SourceValidator:
|
||||||
subscription_source: Optional[SourceValidator] = None
|
subscription_source: Optional[SourceValidator] = None
|
||||||
subscription_source_name: Optional[str] = None
|
|
||||||
|
|
||||||
for key in self.keys:
|
for key in self._keys:
|
||||||
if key in self.available_sources and subscription_source:
|
if key in self.__available_sources and subscription_source:
|
||||||
raise ValidationException(
|
raise ValidationException(
|
||||||
f"'{self.name}' can only have one of the following sources: "
|
f"'{self._name}' can only have one of the following sources: "
|
||||||
f"{', '.join(self.available_sources)}"
|
f"{', '.join(self.__available_sources)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if key in self._subscription_source_validator_mapping:
|
if key in PRESET_SOURCE_VALIDATOR_MAPPING:
|
||||||
subscription_source_name = key
|
subscription_source = self._validate_key(
|
||||||
subscription_source = self.validate_key(
|
key=key, validator=PRESET_SOURCE_VALIDATOR_MAPPING[key]
|
||||||
key=key, validator=self._subscription_source_validator_mapping[key]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# If subscription source was not set, error
|
# If subscription source was not set, error
|
||||||
if not subscription_source:
|
if not subscription_source:
|
||||||
raise ValidationException(
|
raise ValidationException(
|
||||||
f"'{self.name} must have one of the following sources: "
|
f"'{self._name} must have one of the following sources: "
|
||||||
f"{', '.join(self.available_sources)}"
|
f"{', '.join(self.__available_sources)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return subscription_source_name, subscription_source
|
return subscription_source
|
||||||
|
|
||||||
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_name,
|
|
||||||
self.subscription_source,
|
|
||||||
) = self.__validate_and_get_subscription_source()
|
|
||||||
|
|
||||||
self.output_options = self.validate_key(
|
self.subscription_source = self.__validate_and_get_subscription_source()
|
||||||
|
|
||||||
|
self.output_options = self._validate_key(
|
||||||
key="output_options",
|
key="output_options",
|
||||||
validator=OutputOptionsValidator,
|
validator=OutputOptionsValidator,
|
||||||
)
|
)
|
||||||
self.metadata_options = self.validate_key(
|
self.metadata_options = self._validate_key(
|
||||||
key="metadata_options", validator=MetadataOptionsValidator
|
key="metadata_options", validator=MetadataOptionsValidator
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ytdl_options = self.validate_key(
|
self.ytdl_options = self._validate_key(
|
||||||
key="ytdl_options", validator=YTDLOptionsValidator, default={}
|
key="ytdl_options", validator=YTDLOptionsValidator, default={}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.overrides = self.validate_key(
|
self.overrides = self._validate_key(
|
||||||
key="overrides", validator=OverridesValidator, default={}
|
key="overrides", validator=OverridesValidator, default={}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,18 @@ class SoundcloudAlbumsAndSinglesDownloadValidator(DownloadStrategyValidator):
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self.username = self.validate_key(key="username", validator=StringValidator)
|
self.username = self._validate_key(key="username", validator=StringValidator)
|
||||||
|
|
||||||
|
|
||||||
class SoundcloudSourceValidator(SourceValidator):
|
class SoundcloudSourceValidator(SourceValidator):
|
||||||
_optional_keys = {"skip_premiere_tracks"}
|
_optional_keys = {"skip_premiere_tracks"}
|
||||||
|
|
||||||
download_strategy_validator_mapping = {
|
_download_strategy_validator_mapping = {
|
||||||
"albums_and_singles": SoundcloudAlbumsAndSinglesDownloadValidator
|
"albums_and_singles": SoundcloudAlbumsAndSinglesDownloadValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, name: str, value: dict):
|
def __init__(self, name: str, value: dict):
|
||||||
super().__init__(name=name, value=value)
|
super().__init__(name=name, value=value)
|
||||||
self.skip_premiere_tracks = self.validate_key(
|
self.skip_premiere_tracks = self._validate_key(
|
||||||
"skip_premiere_tracks", BoolValidator, default=True
|
"skip_premiere_tracks", BoolValidator, default=True
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,32 +18,34 @@ class SourceValidator(StrictDictValidator):
|
||||||
# Extra fields will be strict-validated using other StictDictValidators
|
# Extra fields will be strict-validated using other StictDictValidators
|
||||||
_allow_extra_keys = True
|
_allow_extra_keys = True
|
||||||
|
|
||||||
download_strategy_validator_mapping: Dict[str, Type[DownloadStrategyValidator]] = {}
|
_download_strategy_validator_mapping: Dict[
|
||||||
|
str, Type[DownloadStrategyValidator]
|
||||||
|
] = {}
|
||||||
|
|
||||||
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.download_strategy_name = self.validate_key(
|
download_strategy_name = self._validate_key(
|
||||||
key="download_strategy",
|
key="download_strategy",
|
||||||
validator=StringValidator,
|
validator=StringValidator,
|
||||||
).value
|
).value
|
||||||
|
|
||||||
if self.download_strategy_name not in self.possible_download_strategies:
|
if download_strategy_name not in self._possible_download_strategies:
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
f"download_strategy must be one of the following: {', '.join(self.possible_download_strategies)}"
|
f"download_strategy must be one of the following: {', '.join(self._possible_download_strategies)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remove all non-download strategy keys before passing the dict to the validator
|
# Remove all non-download strategy keys before passing the dict to the validator
|
||||||
download_strategy_dict = copy.deepcopy(self.dict)
|
download_strategy_dict = copy.deepcopy(self._dict)
|
||||||
for key_to_delete in self._allowed_keys:
|
for key_to_delete in self._allowed_keys:
|
||||||
del download_strategy_dict[key_to_delete]
|
del download_strategy_dict[key_to_delete]
|
||||||
|
|
||||||
download_strategy_class = self.download_strategy_validator_mapping[
|
download_strategy_class = self._download_strategy_validator_mapping[
|
||||||
self.download_strategy_name
|
download_strategy_name
|
||||||
]
|
]
|
||||||
self.download_strategy = download_strategy_class(
|
self.download_strategy = download_strategy_class(
|
||||||
name=self.name, value=download_strategy_dict
|
name=self._name, value=download_strategy_dict
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def possible_download_strategies(self):
|
def _possible_download_strategies(self):
|
||||||
return sorted(list(self.download_strategy_validator_mapping.keys()))
|
return sorted(list(self._download_strategy_validator_mapping.keys()))
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,13 @@ class YoutubePlaylistDownloadValidator(DownloadStrategyValidator):
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self.playlist_id = self.validate_key("playlist_id", StringValidator)
|
self.playlist_id = self._validate_key("playlist_id", StringValidator)
|
||||||
|
|
||||||
|
|
||||||
class YoutubeSourceValidator(SourceValidator):
|
class YoutubeSourceValidator(SourceValidator):
|
||||||
download_strategy_validator_mapping = {"playlist": YoutubePlaylistDownloadValidator}
|
_download_strategy_validator_mapping = {
|
||||||
|
"playlist": YoutubePlaylistDownloadValidator
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,12 @@ from ytdl_subscribe.subscriptions.subscription import Subscription
|
||||||
from ytdl_subscribe.subscriptions.youtube import YoutubeSubscription
|
from ytdl_subscribe.subscriptions.youtube import YoutubeSubscription
|
||||||
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
|
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
|
||||||
from ytdl_subscribe.validators.base.validators import StringValidator
|
from ytdl_subscribe.validators.base.validators import StringValidator
|
||||||
from ytdl_subscribe.validators.config.config_validator import ConfigValidator
|
from ytdl_subscribe.validators.config.config_validator import ConfigFileValidator
|
||||||
|
from ytdl_subscribe.validators.config.preset_validator import PRESET_OPTIONAL_KEYS
|
||||||
|
from ytdl_subscribe.validators.config.preset_validator import PRESET_REQUIRED_KEYS
|
||||||
|
from ytdl_subscribe.validators.config.preset_validator import (
|
||||||
|
PRESET_SOURCE_VALIDATOR_MAPPING,
|
||||||
|
)
|
||||||
from ytdl_subscribe.validators.config.preset_validator import OverridesValidator
|
from ytdl_subscribe.validators.config.preset_validator import OverridesValidator
|
||||||
from ytdl_subscribe.validators.config.preset_validator import PresetValidator
|
from ytdl_subscribe.validators.config.preset_validator import PresetValidator
|
||||||
from ytdl_subscribe.validators.config.sources.soundcloud_validators import (
|
from ytdl_subscribe.validators.config.sources.soundcloud_validators import (
|
||||||
|
|
@ -28,27 +33,25 @@ class SubscriptionValidator(StrictDictValidator):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_required_keys = {"preset"}
|
_required_keys = {"preset"}
|
||||||
_optional_keys = PresetValidator._required_keys.union(
|
_optional_keys = PRESET_REQUIRED_KEYS.union(PRESET_OPTIONAL_KEYS)
|
||||||
PresetValidator._optional_keys
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, config: ConfigValidator, name: str, value: Any):
|
def __init__(self, config: ConfigFileValidator, name: str, value: Any):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
# Ensure the overrides defined here are valid
|
# Ensure the overrides defined here are valid
|
||||||
_ = self.validate_key(
|
_ = self._validate_key(
|
||||||
key="overrides",
|
key="overrides",
|
||||||
validator=OverridesValidator,
|
validator=OverridesValidator,
|
||||||
default={},
|
default={},
|
||||||
)
|
)
|
||||||
|
|
||||||
preset_name = self.validate_key(
|
preset_name = self._validate_key(
|
||||||
key="preset",
|
key="preset",
|
||||||
validator=StringValidator,
|
validator=StringValidator,
|
||||||
).value
|
).value
|
||||||
|
|
||||||
available_presets = self.config.presets.keys
|
available_presets = self.config.presets._keys
|
||||||
if preset_name not in available_presets:
|
if preset_name not in available_presets:
|
||||||
raise self._validation_exception(
|
raise self._validation_exception(
|
||||||
f"'preset '{preset_name}' does not exist in the provided config. "
|
f"'preset '{preset_name}' does not exist in the provided config. "
|
||||||
|
|
@ -57,14 +60,14 @@ class SubscriptionValidator(StrictDictValidator):
|
||||||
|
|
||||||
# A little hacky, we will override the preset with the contents of this subscription, then validate it
|
# A little hacky, we will override the preset with the contents of this subscription, then validate it
|
||||||
preset_dict = mergedeep.merge(
|
preset_dict = mergedeep.merge(
|
||||||
self.config.presets.dict[preset_name],
|
self.config.presets._dict[preset_name],
|
||||||
self.dict,
|
self._dict,
|
||||||
strategy=mergedeep.Strategy.REPLACE,
|
strategy=mergedeep.Strategy.REPLACE,
|
||||||
)
|
)
|
||||||
del preset_dict["preset"]
|
del preset_dict["preset"]
|
||||||
|
|
||||||
self.preset = PresetValidator(
|
self.preset = PresetValidator(
|
||||||
name=f"{self.name}.{preset_name}",
|
name=f"{self._name}.{preset_name}",
|
||||||
value=preset_dict,
|
value=preset_dict,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,8 +86,8 @@ class SubscriptionValidator(StrictDictValidator):
|
||||||
raise ValueError("subscription source class not found")
|
raise ValueError("subscription source class not found")
|
||||||
|
|
||||||
return subscription_class(
|
return subscription_class(
|
||||||
name=self.name,
|
name=self._name,
|
||||||
config_options=self.config,
|
config_options=self.config.config_options,
|
||||||
source_options=self.preset.subscription_source,
|
source_options=self.preset.subscription_source,
|
||||||
output_options=self.preset.output_options,
|
output_options=self.preset.output_options,
|
||||||
metadata_options=self.preset.metadata_options,
|
metadata_options=self.preset.metadata_options,
|
||||||
|
|
@ -94,7 +97,7 @@ class SubscriptionValidator(StrictDictValidator):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_file_path(
|
def from_file_path(
|
||||||
cls, config: ConfigValidator, subscription_path: str
|
cls, config: ConfigFileValidator, subscription_path: str
|
||||||
) -> List["SubscriptionValidator"]:
|
) -> List["SubscriptionValidator"]:
|
||||||
# TODO: Create separate yaml file loader class
|
# TODO: Create separate yaml file loader class
|
||||||
with open(subscription_path, "r", encoding="utf-8") as file:
|
with open(subscription_path, "r", encoding="utf-8") as file:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue