feat: build
This commit is contained in:
0
music_kraken/utils/config/attributes/__init__.py
Normal file
0
music_kraken/utils/config/attributes/__init__.py
Normal file
132
music_kraken/utils/config/attributes/attribute.py
Normal file
132
music_kraken/utils/config/attributes/attribute.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import re
|
||||
from typing import Optional, List, Union, Iterable, Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import toml
|
||||
from copy import deepcopy, copy
|
||||
from urllib.parse import urlparse, urlunparse, ParseResult
|
||||
|
||||
from ...exception.config import SettingValueError
|
||||
from ..utils import comment
|
||||
|
||||
|
||||
LOGGER = logging.getLogger("config")
|
||||
|
||||
COMMENT_PREFIX = "#"
|
||||
|
||||
|
||||
def comment_string(uncommented: str) -> str:
|
||||
unprocessed_lines = uncommented.split("\n")
|
||||
|
||||
processed_lines: List[str] = []
|
||||
|
||||
for line in unprocessed_lines:
|
||||
if line.startswith(COMMENT_PREFIX) or line == "":
|
||||
processed_lines.append(line)
|
||||
continue
|
||||
|
||||
line = COMMENT_PREFIX + " " + line
|
||||
processed_lines.append(line)
|
||||
|
||||
return "\n".join(processed_lines)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Description:
|
||||
description: str
|
||||
|
||||
@property
|
||||
def toml_string(self):
|
||||
return comment_string(self.description)
|
||||
|
||||
|
||||
class EmptyLine(Description):
|
||||
def __init__(self):
|
||||
self.description = ""
|
||||
|
||||
|
||||
|
||||
class Attribute:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
default_value: any,
|
||||
description: Optional[str] = None,
|
||||
):
|
||||
|
||||
self.name = name
|
||||
|
||||
self.value = self._recursive_parse_object(default_value, self.parse_simple_value)
|
||||
|
||||
self.description: Optional[str] = description
|
||||
self.loaded_settings: dict = None
|
||||
|
||||
def initialize_from_config(self, loaded_settings: dict):
|
||||
self.loaded_settings = loaded_settings
|
||||
self.loaded_settings.__setitem__(self.name, self.value, True)
|
||||
|
||||
def unparse_simple_value(self, value: any) -> any:
|
||||
return value
|
||||
|
||||
def parse_simple_value(self, value: any) -> any:
|
||||
return value
|
||||
|
||||
def _recursive_parse_object(self, __object, callback: Callable):
|
||||
__object = copy(__object)
|
||||
|
||||
if isinstance(__object, dict):
|
||||
for key, value in __object.items():
|
||||
__object[key] = self._recursive_parse_object(value, callback)
|
||||
|
||||
return __object
|
||||
|
||||
if isinstance(__object, list) or (isinstance(__object, tuple) and not isinstance(__object, ParseResult)):
|
||||
for i, item in enumerate(__object):
|
||||
__object[i] = self._recursive_parse_object(item, callback)
|
||||
return __object
|
||||
|
||||
return callback(__object)
|
||||
|
||||
def parse(self, unparsed_value):
|
||||
self.value = self._recursive_parse_object(unparsed_value, self.parse_simple_value)
|
||||
return self.value
|
||||
|
||||
def unparse(self, parsed_value):
|
||||
return self._recursive_parse_object(parsed_value, self.unparse_simple_value)
|
||||
|
||||
def load_toml(self, loaded_toml: dict) -> bool:
|
||||
"""
|
||||
returns true if succesfull
|
||||
"""
|
||||
|
||||
if self.name not in loaded_toml:
|
||||
LOGGER.warning(f"No setting by the name {self.name} found in the settings file.")
|
||||
self.loaded_settings.__setitem__(self.name, self.value, True)
|
||||
return
|
||||
|
||||
try:
|
||||
self.parse(loaded_toml[self.name])
|
||||
except SettingValueError as settings_error:
|
||||
logging.warning(settings_error)
|
||||
return False
|
||||
|
||||
self.loaded_settings.__setitem__(self.name, self.value, True)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@property
|
||||
def toml_string(self) -> str:
|
||||
string = ""
|
||||
|
||||
if self.description is not None:
|
||||
string += comment(self.description) + "\n"
|
||||
|
||||
string += toml.dumps({self.name: self.unparse(self.value)})
|
||||
|
||||
# print(string)
|
||||
return string
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.description}\n{self.name}={self.value}"
|
||||
|
151
music_kraken/utils/config/attributes/special_attributes.py
Normal file
151
music_kraken/utils/config/attributes/special_attributes.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from pathlib import Path, PosixPath
|
||||
from typing import Optional, Dict, Set
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
import logging
|
||||
|
||||
from .attribute import Attribute
|
||||
from ...exception.config import SettingValueError
|
||||
|
||||
|
||||
class UrlAttribute(Attribute):
|
||||
def parse_simple_value(self, value: any) -> any:
|
||||
return urlparse(value)
|
||||
|
||||
def unparse_simple_value(self, value: any) -> any:
|
||||
return urlunparse((value.scheme, value.netloc, value.path, value.params, value.query, value.fragment))
|
||||
|
||||
|
||||
class PathAttribute(Attribute):
|
||||
def parse_simple_value(self, value: any) -> Path:
|
||||
if isinstance(value, Path) or isinstance(value, PosixPath):
|
||||
return value
|
||||
return Path(value)
|
||||
|
||||
def unparse_simple_value(self, value: Path) -> any:
|
||||
return str(value.resolve())
|
||||
|
||||
|
||||
|
||||
class SelectAttribute(Attribute):
|
||||
def __init__(self, name: str, default_value: any, options: tuple, description: Optional[str] = None, ignore_options_for_description = False):
|
||||
self.options: tuple = options
|
||||
|
||||
new_description = ""
|
||||
if description is not None:
|
||||
new_description += description
|
||||
new_description += "\n"
|
||||
|
||||
if not ignore_options_for_description:
|
||||
new_description += f"{{{', '.join(self.options)}}}"
|
||||
|
||||
super().__init__(name, default_value, description)
|
||||
|
||||
def parse_simple_value(self, value: any) -> any:
|
||||
if value in self.options:
|
||||
return value
|
||||
|
||||
raise SettingValueError(
|
||||
setting_name=self.name,
|
||||
setting_value=value,
|
||||
rule=f"has to be in the options: {{{', '.join(self.options)}}}."
|
||||
)
|
||||
|
||||
def unparse_simple_value(self, value: any) -> any:
|
||||
return value
|
||||
|
||||
|
||||
class IntegerSelect(Attribute):
|
||||
def __init__(self, name: str, default_value: any, options: Dict[int, str], description: Optional[str] = None, ignore_options_for_description = False):
|
||||
self.options: Dict[str, int] = options
|
||||
self.option_values: Set[int] = set(self.options.values())
|
||||
|
||||
new_description = ""
|
||||
if description is not None:
|
||||
new_description += description
|
||||
|
||||
description_lines = []
|
||||
|
||||
if description is not None:
|
||||
description_lines.append(description)
|
||||
|
||||
description_lines.append("The values can be either an integer or one of the following values:")
|
||||
|
||||
for number, option in self.options.items():
|
||||
description_lines.append(f"{number}: {option}")
|
||||
|
||||
super().__init__(name, default_value, "\n".join(description_lines))
|
||||
|
||||
def parse_simple_value(self, value: any) -> any:
|
||||
if isinstance(value, str):
|
||||
if value not in self.options:
|
||||
raise SettingValueError(
|
||||
setting_name=self.name,
|
||||
setting_value=value,
|
||||
rule=f"has to be in the options: {{{', '.join(self.options.keys())}}}, if it is a string."
|
||||
)
|
||||
|
||||
return self.options[value]
|
||||
|
||||
return value
|
||||
|
||||
def unparse_simple_value(self, value: int) -> any:
|
||||
if value in self.option_values:
|
||||
for option, v in self.options.items():
|
||||
if v == value:
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
ID3_2_FILE_FORMATS = frozenset((
|
||||
"mp3", "mp2", "mp1", # MPEG-1 ID3.2
|
||||
"wav", "wave", "rmi", # RIFF (including WAV) ID3.2
|
||||
"aiff", "aif", "aifc", # AIFF ID3.2
|
||||
"aac", "aacp", # Raw AAC ID3.2
|
||||
"tta", # True Audio ID3.2
|
||||
))
|
||||
_sorted_id3_2_formats = sorted(ID3_2_FILE_FORMATS)
|
||||
|
||||
ID3_1_FILE_FORMATS = frozenset((
|
||||
"ape", # Monkey's Audio ID3.1
|
||||
"mpc", "mpp", "mp+", # MusePack ID3.1
|
||||
"wv", # WavPack ID3.1
|
||||
"ofr", "ofs" # OptimFrog ID3.1
|
||||
))
|
||||
_sorted_id3_1_formats = sorted(ID3_1_FILE_FORMATS)
|
||||
|
||||
|
||||
class AudioFormatAttribute(Attribute):
|
||||
def __init__(self, name: str, default_value: any, description: Optional[str] = None, ignore_options_for_description = False):
|
||||
new_description = ""
|
||||
if description is not None:
|
||||
new_description += description
|
||||
new_description += "\n"
|
||||
|
||||
new_description += f"ID3.2: {{{', '.join(ID3_2_FILE_FORMATS)}}}\n"
|
||||
new_description += f"ID3.1: {{{', '.join(ID3_1_FILE_FORMATS)}}}"
|
||||
|
||||
super().__init__(name, default_value, description)
|
||||
|
||||
def parse_simple_value(self, value: any) -> any:
|
||||
value = value.strip().lower()
|
||||
if value in ID3_2_FILE_FORMATS:
|
||||
return value
|
||||
if value in ID3_1_FILE_FORMATS:
|
||||
logging.debug(f"setting audio format to a format that only supports ID3.1: {v}")
|
||||
return value
|
||||
|
||||
raise SettingValueError(
|
||||
setting_name=self.name,
|
||||
setting_value=value,
|
||||
rule="has to be a valid audio format, supporting id3 metadata"
|
||||
)
|
||||
|
||||
def unparse_simple_value(self, value: any) -> any:
|
||||
return value
|
||||
|
||||
class LoggerAttribute(Attribute):
|
||||
def parse_simple_value(self, value: str) -> logging.Logger:
|
||||
return logging.getLogger(value)
|
||||
|
||||
def unparse_simple_value(self, value: logging.Logger) -> any:
|
||||
return value.name
|
Reference in New Issue
Block a user