More validator refactoring
This commit is contained in:
parent
775342f0cf
commit
3eca67101b
9 changed files with 101 additions and 103 deletions
|
|
@ -1,77 +0,0 @@
|
|||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
|
||||
from ytdl_subscribe.validators.base.validators import Validator
|
||||
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||
|
||||
T = TypeVar("T", bound=Validator)
|
||||
|
||||
|
||||
class DictValidator(Validator):
|
||||
|
||||
expected_value_type = dict
|
||||
expected_value_type_name = "object" # for non-python users
|
||||
|
||||
required_fields: Set[str] = set()
|
||||
optional_fields: Set[str] = set()
|
||||
|
||||
_allow_extra_fields = False
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
||||
# Ensure all required keys are present
|
||||
for required_key in self.required_fields:
|
||||
if required_key not in self.value:
|
||||
error_msg = (
|
||||
f"'{self.name}' is missing the required field '{required_key}'"
|
||||
)
|
||||
raise ValidationException(error_msg)
|
||||
|
||||
# If no extra fields are allowed, ensure all fields are either
|
||||
# required or optional fields
|
||||
if not self._allow_extra_fields:
|
||||
for object_key in self.keys:
|
||||
if object_key not in self.allowed_fields:
|
||||
error_msg = (
|
||||
f"'{self.name}' contains the field '{object_key}' which is not allowed. "
|
||||
f"Allowed fields: {', '.join(self.allowed_fields)}"
|
||||
)
|
||||
raise ValidationException(error_msg)
|
||||
|
||||
def validate_key(
|
||||
self, key: str, validator: Type[T], default: Optional[Any] = None
|
||||
) -> T:
|
||||
value = self.get(key=key, default=default)
|
||||
if value is None:
|
||||
raise self._validation_exception(
|
||||
f"{key} is missing when it should be present."
|
||||
)
|
||||
|
||||
return validator(
|
||||
name=f"{self.name}.{key}",
|
||||
value=self.get(key=key, default=default),
|
||||
)
|
||||
|
||||
@property
|
||||
def dict(self) -> dict:
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def allowed_fields(self):
|
||||
return sorted(self.required_fields.union(self.optional_fields))
|
||||
|
||||
@property
|
||||
def keys(self):
|
||||
return sorted(list(self.dict.keys()))
|
||||
|
||||
def get(self, key: str, default: Optional[Any] = None) -> Any:
|
||||
return self.dict.get(key, default)
|
||||
|
||||
|
||||
class DictWithExtraFieldsValidator(DictValidator):
|
||||
_allow_extra_fields = True
|
||||
41
ytdl_subscribe/validators/base/strict_dict_validator.py
Normal file
41
ytdl_subscribe/validators/base/strict_dict_validator.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
from typing import Set
|
||||
|
||||
from ytdl_subscribe.validators.base.validators import DictValidator
|
||||
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||
|
||||
|
||||
class StrictDictValidator(DictValidator):
|
||||
required_keys: Set[str] = set()
|
||||
optional_keys: Set[str] = set()
|
||||
|
||||
allow_extra_fields = False
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
||||
if len(self.required_keys) == 0:
|
||||
raise ValueError(
|
||||
"No required fields when using a StrictDictValidator. Should be using DictValidator instead."
|
||||
)
|
||||
|
||||
# Ensure all required keys are present
|
||||
for required_key in self.required_keys:
|
||||
if required_key not in self.value:
|
||||
error_msg = (
|
||||
f"'{self.name}' is missing the required field '{required_key}'"
|
||||
)
|
||||
raise ValidationException(error_msg)
|
||||
|
||||
# Ensure all keys are either required or optional keys if no extra field are allowed
|
||||
if not self.allow_extra_fields:
|
||||
for object_key in self.keys:
|
||||
if object_key not in self.allowed_keys:
|
||||
error_msg = (
|
||||
f"'{self.name}' contains the field '{object_key}' which is not allowed. "
|
||||
f"Allowed fields: {', '.join(self.allowed_keys)}"
|
||||
)
|
||||
raise ValidationException(error_msg)
|
||||
|
||||
@property
|
||||
def allowed_keys(self):
|
||||
return sorted(self.required_keys.union(self.optional_keys))
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
|
||||
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||
|
||||
|
|
@ -57,3 +58,36 @@ class StringValidator(Validator):
|
|||
@property
|
||||
def value(self) -> str:
|
||||
return self._value
|
||||
|
||||
|
||||
T = TypeVar("T", bound=Validator)
|
||||
|
||||
|
||||
class DictValidator(Validator):
|
||||
expected_value_type = dict
|
||||
expected_value_type_name = "object" # for non-python users
|
||||
|
||||
@property
|
||||
def dict(self) -> dict:
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def keys(self):
|
||||
return sorted(list(self.dict.keys()))
|
||||
|
||||
def get(self, key: str, default: Optional[Any] = None) -> Any:
|
||||
return self.dict.get(key, default)
|
||||
|
||||
def validate_key(
|
||||
self, key: str, validator: Type[T], default: Optional[Any] = None
|
||||
) -> T:
|
||||
value = self.get(key=key, default=default)
|
||||
if value is None:
|
||||
raise self._validation_exception(
|
||||
f"{key} is missing when it should be present."
|
||||
)
|
||||
|
||||
return validator(
|
||||
name=f"{self.name}.{key}",
|
||||
value=self.get(key=key, default=default),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@ from typing import Any
|
|||
|
||||
import yaml
|
||||
|
||||
from ytdl_subscribe.validators.base.dict_validator import DictValidator
|
||||
from ytdl_subscribe.validators.base.dict_validator import DictWithExtraFieldsValidator
|
||||
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_subscribe.validators.base.validators import DictValidator
|
||||
from ytdl_subscribe.validators.base.validators import StringValidator
|
||||
|
||||
|
||||
class ConfigValidator(DictValidator):
|
||||
required_fields = {"working_directory", "presets"}
|
||||
class ConfigValidator(StrictDictValidator):
|
||||
required_keys = {"working_directory", "presets"}
|
||||
|
||||
def __init__(self, name: str, value: Any):
|
||||
super().__init__(name, value)
|
||||
self.working_directory = self.validate_key("working_directory", StringValidator)
|
||||
self.presets = self.validate_key("presets", DictWithExtraFieldsValidator)
|
||||
self.presets = self.validate_key("presets", DictValidator)
|
||||
|
||||
@classmethod
|
||||
def from_file_path(cls, config_path) -> "ConfigValidator":
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from typing import Any
|
|||
from typing import Optional
|
||||
|
||||
from ytdl_subscribe.utils.enums import SubscriptionSourceName
|
||||
from ytdl_subscribe.validators.base.dict_validator import DictValidator
|
||||
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_subscribe.validators.config.sources.soundcloud_validators import (
|
||||
SoundcloudSourceValidator,
|
||||
)
|
||||
|
|
@ -13,9 +13,9 @@ from ytdl_subscribe.validators.config.sources.youtube_validators import (
|
|||
from ytdl_subscribe.validators.exceptions import ValidationException
|
||||
|
||||
|
||||
class PresetValidator(DictValidator):
|
||||
required_fields = {"post_process"}
|
||||
optional_fields = {
|
||||
class PresetValidator(StrictDictValidator):
|
||||
required_keys = {"post_process"}
|
||||
optional_keys = {
|
||||
"ytdl_options",
|
||||
"output_path",
|
||||
"overrides",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from ytdl_subscribe.validators.config.sources.source_validator import SourceVali
|
|||
|
||||
|
||||
class SoundcloudAlbumsAndSinglesDownloadValidator(DownloadStrategyValidator):
|
||||
required_fields = {"username"}
|
||||
required_keys = {"username"}
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
|
@ -17,7 +17,7 @@ class SoundcloudAlbumsAndSinglesDownloadValidator(DownloadStrategyValidator):
|
|||
|
||||
|
||||
class SoundcloudSourceValidator(SourceValidator):
|
||||
optional_fields = {"skip_premiere_tracks"}
|
||||
optional_keys = {"skip_premiere_tracks"}
|
||||
|
||||
download_strategy_validator_mapping = {
|
||||
"albums_and_singles": SoundcloudAlbumsAndSinglesDownloadValidator
|
||||
|
|
|
|||
|
|
@ -3,18 +3,20 @@ from typing import Any
|
|||
from typing import Dict
|
||||
from typing import Type
|
||||
|
||||
from ytdl_subscribe.validators.base.dict_validator import DictValidator
|
||||
from ytdl_subscribe.validators.base.dict_validator import DictWithExtraFieldsValidator
|
||||
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_subscribe.validators.base.validators import StringValidator
|
||||
|
||||
|
||||
class DownloadStrategyValidator(DictValidator):
|
||||
class DownloadStrategyValidator(StrictDictValidator):
|
||||
pass
|
||||
|
||||
|
||||
class SourceValidator(DictWithExtraFieldsValidator):
|
||||
class SourceValidator(StrictDictValidator):
|
||||
# All media sources must define a download strategy
|
||||
required_fields = {"download_strategy"}
|
||||
required_keys = {"download_strategy"}
|
||||
|
||||
# Extra fields will be strict-validated using other StictDictValidators
|
||||
allow_extra_fields = True
|
||||
|
||||
download_strategy_validator_mapping: Dict[str, Type[DownloadStrategyValidator]] = {}
|
||||
|
||||
|
|
@ -32,7 +34,7 @@ class SourceValidator(DictWithExtraFieldsValidator):
|
|||
|
||||
# Remove all non-download strategy keys before passing the dict to the validator
|
||||
download_strategy_dict = copy.deepcopy(self.dict)
|
||||
for key_to_delete in self.allowed_fields:
|
||||
for key_to_delete in self.allowed_keys:
|
||||
del download_strategy_dict[key_to_delete]
|
||||
|
||||
download_strategy_class = self.download_strategy_validator_mapping[
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from ytdl_subscribe.validators.config.sources.source_validator import SourceVali
|
|||
|
||||
|
||||
class YoutubePlaylistDownloadValidator(DownloadStrategyValidator):
|
||||
required_fields = {"playlist_id"}
|
||||
required_keys = {"playlist_id"}
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
|
|
|||
|
|
@ -8,29 +8,27 @@ 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.base.dict_validator import DictValidator
|
||||
from ytdl_subscribe.validators.base.dict_validator import DictWithExtraFieldsValidator
|
||||
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
|
||||
from ytdl_subscribe.validators.base.validators import DictValidator
|
||||
from ytdl_subscribe.validators.base.validators import StringValidator
|
||||
from ytdl_subscribe.validators.config.config_validator import ConfigValidator
|
||||
from ytdl_subscribe.validators.config.preset_validator import PresetValidator
|
||||
|
||||
|
||||
class SubscriptionValidator(DictValidator):
|
||||
class SubscriptionValidator(StrictDictValidator):
|
||||
"""
|
||||
A Subscription is a preset but overrides it with specific values
|
||||
"""
|
||||
|
||||
required_fields = {"preset"}
|
||||
optional_fields = PresetValidator.required_fields.union(
|
||||
PresetValidator.optional_fields
|
||||
)
|
||||
required_keys = {"preset"}
|
||||
optional_keys = PresetValidator.required_keys.union(PresetValidator.optional_keys)
|
||||
|
||||
def __init__(self, config: ConfigValidator, name: str, value: Any):
|
||||
super().__init__(name, value)
|
||||
self.config = config
|
||||
self.overrides = self.validate_key(
|
||||
key="overrides",
|
||||
validator=DictWithExtraFieldsValidator,
|
||||
validator=DictValidator,
|
||||
default={},
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue