aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/service/_config.py
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2024-11-11 01:12:29 +0800
committerYuqian Yang <crupest@crupest.life>2025-01-15 15:55:51 +0800
commit7f2e4107e7f469d6747350487e9441d9b987de47 (patch)
tree245a543316e9f1291acebe0aefd510e7064e92a2 /tools/cru-py/cru/service/_config.py
parenta5c2f62b0b3c4ec6ce46c57bcd99319a85900798 (diff)
downloadcrupest-7f2e4107e7f469d6747350487e9441d9b987de47.tar.gz
crupest-7f2e4107e7f469d6747350487e9441d9b987de47.tar.bz2
crupest-7f2e4107e7f469d6747350487e9441d9b987de47.zip
HALF WORK: 2024.1.15
Diffstat (limited to 'tools/cru-py/cru/service/_config.py')
-rw-r--r--tools/cru-py/cru/service/_config.py243
1 files changed, 177 insertions, 66 deletions
diff --git a/tools/cru-py/cru/service/_config.py b/tools/cru-py/cru/service/_config.py
index 12784c5..f2beeb2 100644
--- a/tools/cru-py/cru/service/_config.py
+++ b/tools/cru-py/cru/service/_config.py
@@ -1,8 +1,12 @@
+from collections.abc import Iterable
+from typing import Any, NoReturn
+
from cru import CruException, CruUserFriendlyException
from cru.config import Configuration, ConfigItem
from cru.value import (
INTEGER_VALUE_TYPE,
TEXT_VALUE_TYPE,
+ CruValueTypeError,
RandomStringValueGenerator,
UuidValueGenerator,
)
@@ -12,85 +16,149 @@ from ._base import AppFeaturePath, AppCommandFeatureProvider, OWNER_NAME
class AppConfigError(CruException):
- pass
-
-
-class AppConfigDuplicateEntryError(AppConfigError):
def __init__(
- self, message: str, entries: list[SimpleLineConfigParser.Entry], *args, **kwargs
+ self, message: str, configuration: Configuration, *args, **kwargs
) -> None:
super().__init__(message, *args, **kwargs)
- self._entries = entries
+ self._configuration = configuration
@property
- def duplicate_entries(self) -> list[SimpleLineConfigParser.Entry]:
- return self._entries
+ def configuration(self) -> Configuration:
+ return self._configuration
- @staticmethod
- def duplicate_entries_to_friendly_message(
- entries: list[SimpleLineConfigParser.Entry],
- ) -> str:
- return "".join(
- f"line {entry.line_number}: {entry.key}={entry.value}\n"
- for entry in entries
- )
+ @property
+ def friendly_error_message(self) -> str:
+ raise NotImplementedError("Subclasses must implement this method.")
def to_friendly_error(self) -> CruUserFriendlyException:
- e = CruUserFriendlyException(
- f"Duplicate entries found in config file:\n"
- f"{self.duplicate_entries_to_friendly_message(self.duplicate_entries)}"
- )
- return e
+ return CruUserFriendlyException(self.friendly_error_message)
+ def raise_friendly_error(self) -> NoReturn:
+ raise self.to_friendly_error() from self
-class AppConfigFileNotFoundError(AppConfigError):
- def __init__(self, message: str, file_path: str, *args, **kwargs) -> None:
- super().__init__(message, *args, **kwargs)
+
+class AppConfigFileError(AppConfigError):
+ def __init__(
+ self,
+ message: str,
+ configuration: Configuration,
+ *args,
+ **kwargs,
+ ) -> None:
+ super().__init__(message, configuration, *args, **kwargs)
+
+
+class AppConfigFileNotFoundError(AppConfigFileError):
+ def __init__(
+ self,
+ message: str,
+ configuration: Configuration,
+ file_path: str,
+ *args,
+ **kwargs,
+ ) -> None:
+ super().__init__(message, configuration, *args, **kwargs)
self._file_path = file_path
@property
def file_path(self) -> str:
return self._file_path
- def to_friendly_error(self) -> CruUserFriendlyException:
- e = CruUserFriendlyException(
- f"Config file not found at {self.file_path}. You may need to create one."
- )
- return e
+ @property
+ def friendly_error_message(self) -> str:
+ return f"Config file not found at {self.file_path}. You may need to create one."
-class AppConfigItemNotSetError(AppConfigError):
+class AppConfigFileParseError(AppConfigFileError):
def __init__(
self,
message: str,
- items: list[ConfigItem],
+ configuration: Configuration,
+ file_content: str,
*args,
+ cause: ParseError | None = None,
**kwargs,
) -> None:
- super().__init__(message, *args, **kwargs)
- self._items = items
+ super().__init__(message, configuration, *args, **kwargs)
+ self._file_content = file_content
+ if cause is not None:
+ self._cause = cause
+ self._cause = self.__cause__ # type: ignore
+ @property
+ def file_content(self) -> str:
+ return self._file_content
+
+ @property
+ def friendly_error_message(self) -> str:
+ return f"Error while parsing config file at line {self._cause.line_number}."
-class AppConfigItemNotDefinedError(AppConfigError):
+
+class AppConfigFileEntryError(AppConfigFileError):
def __init__(
self,
message: str,
- undefined_names: list[str],
configuration: Configuration,
+ entries: Iterable[SimpleLineConfigParser.Entry],
*args,
**kwargs,
) -> None:
- super().__init__(message, *args, **kwargs)
- self._undefined_names = undefined_names
- self._configuration = configuration
+ super().__init__(message, configuration, *args, **kwargs)
+ self._entries = list(entries)
@property
- def undefined_names(self) -> list[str]:
- return self._undefined_names
+ def error_entries(self) -> list[SimpleLineConfigParser.Entry]:
+ return self._entries
+
+ @staticmethod
+ def entries_to_friendly_message(
+ entries: Iterable[SimpleLineConfigParser.Entry],
+ ) -> str:
+ return "".join(
+ f"line {entry.line_number}: {entry.key}={entry.value}\n"
+ for entry in entries
+ )
@property
- def configuration(self) -> Configuration:
- return self._configuration
+ def friendly_message_head(self) -> str:
+ return "Error entries found in config file"
+
+ @property
+ def friendly_error_message(self) -> str:
+ return (
+ f"{self.friendly_message_head}:\n"
+ f"{self.entries_to_friendly_message(self.error_entries)}"
+ )
+
+
+class AppConfigDuplicateEntryError(AppConfigFileEntryError):
+ @property
+ def friendly_message_head(self) -> str:
+ return "Duplicate entries found in config file"
+
+
+class AppConfigEntryKeyNotDefinedError(AppConfigFileEntryError):
+ @property
+ def friendly_message_head(self) -> str:
+ return "Entry key not defined in app config"
+
+
+class AppConfigEntryValueFormatError(AppConfigFileEntryError):
+ @property
+ def friendly_message_head(self) -> str:
+ return "Invalid value format for entries"
+
+
+class AppConfigItemNotSetError(AppConfigError):
+ def __init__(
+ self,
+ message: str,
+ items: list[ConfigItem],
+ *args,
+ **kwargs,
+ ) -> None:
+ super().__init__(message, *args, **kwargs)
+ self._items = items
class ConfigManager(AppCommandFeatureProvider):
@@ -178,50 +246,93 @@ class ConfigManager(AppCommandFeatureProvider):
def _parse_config_file(self) -> SimpleLineConfigParser.Result:
if not self.config_file_path.check_self():
raise AppConfigFileNotFoundError(
- "Config file not found.", self.config_file_path.full_path_str
+ "Config file not found.",
+ self.configuration,
+ self.config_file_path.full_path_str,
)
- parser = SimpleLineConfigParser()
- return parser.parse(self.config_file_path.full_path.read_text())
+
+ text = self.config_file_path.full_path.read_text()
+ try:
+ parser = SimpleLineConfigParser()
+ return parser.parse(text)
+ except ParseError as e:
+ raise AppConfigFileParseError(
+ "Failed to parse config file.", self.configuration, text
+ ) from e
def _check_duplicate(
self,
parse_result: dict[str, list[SimpleLineConfigParser.Entry]],
- ) -> dict[str, str]:
- config_dict = {}
- duplicate_entries = []
+ ) -> dict[str, SimpleLineConfigParser.Entry]:
+ entry_dict: dict[str, SimpleLineConfigParser.Entry] = {}
+ duplicate_entries: list[SimpleLineConfigParser.Entry] = []
for key, entries in parse_result.items():
- config_dict[key] = entries[0].value
+ entry_dict[key] = entries[0]
for entry in entries[1:]:
duplicate_entries.append(entry)
if len(duplicate_entries) > 0:
raise AppConfigDuplicateEntryError(
- "Duplicate entries found.", duplicate_entries
+ "Duplicate entries found.", self.configuration, duplicate_entries
)
- return config_dict
+ return entry_dict
def _check_defined(
- self,
- config_dict: dict[str, str],
- allow_extra: bool = True,
- ) -> dict[str, str]:
- # TODO: Continue here!
- raise NotImplementedError()
-
- def _check_config_file(self) -> dict[str, str]:
+ self, entry_dict: dict[str, SimpleLineConfigParser.Entry]
+ ) -> dict[str, SimpleLineConfigParser.Entry]:
+ undefined: list[SimpleLineConfigParser.Entry] = []
+ for key, entry in entry_dict.items():
+ if not self.configuration.has_key(key):
+ undefined.append(entry)
+ if len(undefined) > 0:
+ raise AppConfigEntryKeyNotDefinedError(
+ "Entry keys are not defined in app config.",
+ self.configuration,
+ undefined,
+ )
+ return entry_dict
+
+ def _check_type(
+ self, entry_dict: dict[str, SimpleLineConfigParser.Entry]
+ ) -> dict[str, Any]:
+ value_dict: dict[str, Any] = {}
+ error_entries: list[SimpleLineConfigParser.Entry] = []
+ errors: list[CruValueTypeError] = []
+ for key, entry in entry_dict.items():
+ config_item = self.configuration.get(key)
+ try:
+ value_dict[key] = config_item.value_type.convert_str_to_value(
+ entry.value
+ )
+ except CruValueTypeError as e:
+ error_entries.append(entry)
+ errors.append(e)
+ if len(error_entries) > 0:
+ raise AppConfigEntryValueFormatError(
+ "Entry value format is not correct.",
+ self.configuration,
+ error_entries,
+ ) from ExceptionGroup("Multiple format errors occurred.", errors)
+ return value_dict
+
+ def _read_config_file(self, friendly: bool = False) -> dict[str, Any]:
try:
parsed = self._parse_config_file()
- config = self._check_duplicate(parsed)
- return config
- except ParseError as e:
- raise CruUserFriendlyException("Failed to parse config file.") from e
- except AppConfigDuplicateEntryError as e:
- raise e.to_friendly_error() from e
+ entry_groups = parsed.cru_iter().group_by(lambda e: e.key)
+ entry_dict = self._check_duplicate(entry_groups)
+ entry_dict = self._check_defined(entry_dict)
+ value_dict = self._check_type(entry_dict)
+ return value_dict
+ except AppConfigError as e:
+ if friendly:
+ e.raise_friendly_error()
+ raise
def reload_config_file(self) -> bool:
self.configuration.reset_all()
- config_dict = self._check_config_file()
+ value_dict = self._read_config_file()
+ # TODO: Continue here!
for key, value in config_dict.items():
self.configuration.set(key, value)
return True