umask support, fix error message

This commit is contained in:
jbannon 2022-05-28 06:56:34 +00:00
parent ec1cc534ea
commit b51dddc8d8
7 changed files with 83 additions and 16 deletions

View file

@ -4,5 +4,7 @@ wheel:
docker: wheel docker: wheel
cp dist/*.whl docker/root/ cp dist/*.whl docker/root/
sudo docker build --no-cache -t ytdl-sub:0.1 docker/ sudo docker build --no-cache -t ytdl-sub:0.1 docker/
docs:
sphinx-build -a -b html docs docs/_html
.PHONY: wheel docker .PHONY: wheel docker docs

View file

@ -22,6 +22,11 @@ configuration
The ``configuration`` section contains app-wide configs applied to all presets The ``configuration`` section contains app-wide configs applied to all presets
and subscriptions. and subscriptions.
.. autoclass:: ytdl_sub.config.config_file.ConfigOptions()
:members:
:member-order: bysource
presets presets
^^^^^^^ ^^^^^^^
``presets`` define a `formula` for how to format downloaded media and metadata. ``presets`` define a `formula` for how to format downloaded media and metadata.

View file

@ -1,4 +1,6 @@
import os
from typing import Any from typing import Any
from typing import Optional
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.strict_dict_validator import StrictDictValidator
@ -7,16 +9,33 @@ from ytdl_sub.validators.validators import StringValidator
class ConfigOptions(StrictDictValidator): class ConfigOptions(StrictDictValidator):
"""Validation for global config options"""
_required_keys = {"working_directory"} _required_keys = {"working_directory"}
_optional_keys = {"umask"}
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( self._working_directory = self._validate_key(
key="working_directory", validator=StringValidator key="working_directory", validator=StringValidator
) )
self._umask = self._validate_key_if_present(
key="umask", validator=StringValidator, default="022"
)
@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
class ConfigFile(StrictDictValidator): class ConfigFile(StrictDictValidator):
@ -29,6 +48,19 @@ class ConfigFile(StrictDictValidator):
# Make sure presets is a dictionary. Will be validated in `PresetValidator` # Make sure presets is a dictionary. Will be validated in `PresetValidator`
self.presets = self._validate_key("presets", LiteralDictValidator) self.presets = self._validate_key("presets", LiteralDictValidator)
def initialize(self):
"""
Configures things (umask, pgid) prior to any downloading
Returns
-------
self
"""
if self.config_options.umask:
os.umask(int(self.config_options.umask, 8))
return self
@classmethod @classmethod
def from_dict(cls, config_dict: dict) -> "ConfigFile": def from_dict(cls, config_dict: dict) -> "ConfigFile":
""" """

View file

@ -144,7 +144,7 @@ class Preset(StrictDictValidator):
return plugins return plugins
def __validate_override_string_formatter_validator( def __validate_override_string_formatter_validator(
self, formatter_validator: OverridesStringFormatterValidator self, formatter_validator: OverridesStringFormatterValidator, validator_name: str
): ):
# Gather all resolvable override variables # Gather all resolvable override variables
resolvable_override_variables: List[str] = [] resolvable_override_variables: List[str] = []
@ -157,14 +157,18 @@ class Preset(StrictDictValidator):
for variable_name in formatter_validator.format_variables: for variable_name in formatter_validator.format_variables:
if variable_name not in resolvable_override_variables: if variable_name not in resolvable_override_variables:
raise StringFormattingVariableNotFoundException( raise self._validation_exception(
f"Validation error in {validator_name}: "
f"This variable can only use override variables that resolve without needing " f"This variable can only use override variables that resolve without needing "
f"variables from a downloaded file. The only override variables defined that " f"variables from a downloaded file. The only override variables defined that "
f"meet this condition are: {', '.join(sorted(resolvable_override_variables))}" f"meet this condition are: {', '.join(sorted(resolvable_override_variables))}",
exception_class=StringFormattingVariableNotFoundException,
) )
def __recursive_preset_validate( def __recursive_preset_validate(
self, validator_dict: Optional[Dict[str, Validator]] = None self,
validator_dict: Optional[Dict[str, Validator]] = None,
validator_name: Optional[str] = None,
) -> None: ) -> None:
""" """
Ensure all OverridesStringFormatterValidator's only contain variables from the overrides Ensure all OverridesStringFormatterValidator's only contain variables from the overrides
@ -172,17 +176,23 @@ class Preset(StrictDictValidator):
""" """
if validator_dict is None: if validator_dict is None:
validator_dict = self._validator_dict validator_dict = self._validator_dict
validator_name = self.name
for validator in validator_dict.values(): for validator in validator_dict.values():
# pylint: disable=protected-access
# Usage of protected variables in other validators is fine. The reason to keep them
# protected is for readability when using them in subscriptions.
_validator_name = f"{validator_name}.{validator._name}"
if isinstance(validator, DictValidator): if isinstance(validator, DictValidator):
# Usage of protected variables in other validators is fine. The reason to keep them self.__recursive_preset_validate(
# protected is for readability when using them in subscriptions. validator._validator_dict, validator_name=_validator_name
# pylint: disable=protected-access )
self.__recursive_preset_validate(validator._validator_dict)
# pylint: enable=protected-access
if isinstance(validator, OverridesStringFormatterValidator): if isinstance(validator, OverridesStringFormatterValidator):
self.__validate_override_string_formatter_validator(validator) self.__validate_override_string_formatter_validator(
validator, validator_name=_validator_name
)
# pylint: enable=protected-access
def __merge_parent_preset_dicts_if_present(self, config: ConfigFile): def __merge_parent_preset_dicts_if_present(self, config: ConfigFile):
parent_presets = set() parent_presets = set()

View file

@ -69,7 +69,7 @@ def _main():
args, extra_args = parser.parse_known_args() args, extra_args = parser.parse_known_args()
config: ConfigFile = ConfigFile.from_file_path(args.config) config: ConfigFile = ConfigFile.from_file_path(args.config).initialize()
if args.subparser == "sub": if args.subparser == "sub":
_download_subscriptions_from_yaml_files(config=config, args=args) _download_subscriptions_from_yaml_files(config=config, args=args)
logger.info("Subscription download complete!") logger.info("Subscription download complete!")

View file

@ -120,7 +120,7 @@ class Subscription:
------- -------
The directory that the downloader saves files to The directory that the downloader saves files to
""" """
return str(Path(self.__config_options.working_directory.value) / Path(self.name)) return str(Path(self.__config_options.working_directory) / Path(self.name))
@property @property
def output_directory(self) -> str: def output_directory(self) -> str:

View file

@ -73,6 +73,24 @@ class BoolValidator(Validator):
return self._value return self._value
class IntValidator(Validator):
"""
Validates int fields.
"""
_expected_value_type: Type = int
_expected_value_type_name = "integer"
@property
def value(self) -> int:
"""
Returns
-------
Int value
"""
return self._value
class StringValidator(Validator): class StringValidator(Validator):
""" """
Validates string fields. Validates string fields.