aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2024-11-11 01:12:29 +0800
committerYuqian Yang <crupest@crupest.life>2025-01-16 21:14:14 +0800
commitc0ba4d9d8d19d3faa7b4d2b3509546e37dd32364 (patch)
tree4b3d969850981094ba33e18ac9ec49fbc409dc93 /tools
parent7f2e4107e7f469d6747350487e9441d9b987de47 (diff)
downloadcrupest-c0ba4d9d8d19d3faa7b4d2b3509546e37dd32364.tar.gz
crupest-c0ba4d9d8d19d3faa7b4d2b3509546e37dd32364.tar.bz2
crupest-c0ba4d9d8d19d3faa7b4d2b3509546e37dd32364.zip
HALF WORK: 2024.1.16
Diffstat (limited to 'tools')
-rw-r--r--tools/cru-py/cru/__init__.py10
-rw-r--r--tools/cru-py/cru/_base.py6
-rw-r--r--tools/cru-py/cru/_error.py90
-rw-r--r--tools/cru-py/cru/_type.py4
-rw-r--r--tools/cru-py/cru/config.py6
-rw-r--r--tools/cru-py/cru/list.py2
-rw-r--r--tools/cru-py/cru/service/__main__.py12
-rw-r--r--tools/cru-py/cru/service/_base.py37
-rw-r--r--tools/cru-py/cru/service/_config.py51
9 files changed, 136 insertions, 82 deletions
diff --git a/tools/cru-py/cru/__init__.py b/tools/cru-py/cru/__init__.py
index 7c1a5f1..66732ea 100644
--- a/tools/cru-py/cru/__init__.py
+++ b/tools/cru-py/cru/__init__.py
@@ -2,11 +2,11 @@ import sys
from ._base import CRU, CruNamespaceError, CRU_NAME_PREFIXES
from ._error import (
- cru_unreachable,
CruException,
- CruUserFriendlyException,
+ CruLogicError,
CruInternalError,
CruUnreachableError,
+ cru_unreachable,
)
from ._const import (
CruConstantBase,
@@ -40,11 +40,11 @@ __all__ = [
"CRU_NAME_PREFIXES",
"check_python_version",
"CruException",
- "cru_unreachable",
- "CruInitError",
- "CruUserFriendlyException",
"CruInternalError",
+ "CruLogicError",
"CruUnreachableError",
+ "cru_unreachable",
+ "CruInitError",
"CruConstantBase",
"CruDontChange",
"CruNotFound",
diff --git a/tools/cru-py/cru/_base.py b/tools/cru-py/cru/_base.py
index 0a22df4..2599d8f 100644
--- a/tools/cru-py/cru/_base.py
+++ b/tools/cru-py/cru/_base.py
@@ -1,10 +1,10 @@
from typing import Any
from ._helper import remove_none
-from ._error import CruInternalError
+from ._error import CruException
-class CruNamespaceError(CruInternalError):
+class CruNamespaceError(CruException):
"""Raised when a namespace is not found."""
@@ -41,7 +41,7 @@ class _Cru:
def check_name_format(name: str) -> tuple[str, str]:
no_prefix_name = _Cru._maybe_remove_prefix(name)
if no_prefix_name is None:
- raise ValueError(
+ raise CruNamespaceError(
f"Name {name} is not prefixed with any of {_Cru.NAME_PREFIXES}."
)
return name, no_prefix_name
diff --git a/tools/cru-py/cru/_error.py b/tools/cru-py/cru/_error.py
index e4bf3d6..e53c787 100644
--- a/tools/cru-py/cru/_error.py
+++ b/tools/cru-py/cru/_error.py
@@ -1,29 +1,89 @@
-from typing import NoReturn
+from __future__ import annotations
+
+from typing import NoReturn, cast, overload
class CruException(Exception):
"""Base exception class of all exceptions in cru."""
+ @overload
+ def __init__(
+ self,
+ message: None = None,
+ *args,
+ user_message: str,
+ **kwargs,
+ ): ...
-class CruUnreachableError(CruException):
- """Raised when a code path is unreachable."""
+ @overload
+ def __init__(
+ self,
+ message: str,
+ *args,
+ user_message: str | None = None,
+ **kwargs,
+ ): ...
+ def __init__(
+ self,
+ message: str | None = None,
+ *args,
+ user_message: str | None = None,
+ **kwargs,
+ ):
+ if message is None:
+ message = user_message
-def cru_unreachable() -> NoReturn:
- raise CruUnreachableError()
+ super().__init__(
+ message,
+ *args,
+ **kwargs,
+ )
+ self._message: str
+ self._message = cast(str, message)
+ self._user_message = user_message
+ @property
+ def message(self) -> str:
+ return self._message
-class CruInternalError(CruException):
- """Raised when an internal logic error occurs."""
+ def get_user_message(self) -> str | None:
+ return self._user_message
+ def get_message(self, use_user: bool = True) -> str:
+ if use_user and self._user_message is not None:
+ return self._user_message
+ else:
+ return self._message
-class CruUserFriendlyException(CruException):
- def __init__(
- self, message: str, user_message: str | None = None, *args, **kwargs
- ) -> None:
- super().__init__(message, *args, **kwargs)
- self._user_message = user_message or message
+ @property
+ def is_internal(self) -> bool:
+ return False
@property
- def user_message(self) -> str:
- return self._user_message
+ def is_logic_error(self) -> bool:
+ return False
+
+
+class CruLogicError(CruException):
+ """Raised when a logic error occurs."""
+
+ @property
+ def is_logic_error(self) -> bool:
+ return True
+
+
+class CruInternalError(CruException):
+ """Raised when an internal error occurs."""
+
+ @property
+ def is_internal(self) -> bool:
+ return True
+
+
+class CruUnreachableError(CruInternalError):
+ """Raised when a code path is unreachable."""
+
+
+def cru_unreachable() -> NoReturn:
+ raise CruUnreachableError("Code should not reach here!")
diff --git a/tools/cru-py/cru/_type.py b/tools/cru-py/cru/_type.py
index 96d5d4b..1f81da3 100644
--- a/tools/cru-py/cru/_type.py
+++ b/tools/cru-py/cru/_type.py
@@ -1,7 +1,7 @@
from collections.abc import Iterable
from typing import Any
-from ._error import CruException, CruInternalError
+from ._error import CruException, CruLogicError
from ._iter import CruIterator
@@ -17,7 +17,7 @@ class CruTypeSet(set[type]):
def __init__(self, *types: type):
type_set = CruIterator(types).filter(lambda t: t is not None).to_set()
if not CruIterator(type_set).all(lambda t: isinstance(t, type)):
- raise CruInternalError("TypeSet can only contain type.")
+ raise CruLogicError("TypeSet can only contain type.")
super().__init__(type_set)
def check_value(
diff --git a/tools/cru-py/cru/config.py b/tools/cru-py/cru/config.py
index b71f042..8558106 100644
--- a/tools/cru-py/cru/config.py
+++ b/tools/cru-py/cru/config.py
@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import Any, TypeVar, Generic
-from ._error import CruInternalError, CruException
+from ._error import CruException
from .list import CruUniqueKeyList
from .value import (
INTEGER_VALUE_TYPE,
@@ -110,8 +110,8 @@ class ConfigItem(Generic[_T]):
self.value_type.check_value(v)
return v
except CruValueTypeError as e:
- raise CruInternalError(
- "Config value generator returns an invalid value."
+ raise CruConfigError(
+ "Config value generator returns an invalid value.", self
) from e
def copy(self) -> "ConfigItem":
diff --git a/tools/cru-py/cru/list.py b/tools/cru-py/cru/list.py
index 53c3c77..42caab3 100644
--- a/tools/cru-py/cru/list.py
+++ b/tools/cru-py/cru/list.py
@@ -3,7 +3,7 @@ from __future__ import annotations
from collections.abc import Callable, Iterator
from typing import Any, Generic, Iterable, TypeAlias, TypeVar
-from ._base import CruInternalError
+from ._error import CruInternalError
from ._iter import CruIterator
from ._const import CruNotFound
diff --git a/tools/cru-py/cru/service/__main__.py b/tools/cru-py/cru/service/__main__.py
index c218bc6..1c10e82 100644
--- a/tools/cru-py/cru/service/__main__.py
+++ b/tools/cru-py/cru/service/__main__.py
@@ -1,4 +1,4 @@
-from cru import CruUserFriendlyException
+from cru import CruException
from ._app import create_app
@@ -11,6 +11,10 @@ def main():
if __name__ == "__main__":
try:
main()
- except CruUserFriendlyException as e:
- print(f"Error: {e.user_message}")
- exit(1)
+ except CruException as e:
+ user_message = e.get_user_message()
+ if user_message is not None:
+ print(f"Error: {user_message}")
+ exit(1)
+ else:
+ raise
diff --git a/tools/cru-py/cru/service/_base.py b/tools/cru-py/cru/service/_base.py
index 124acd5..4454c2c 100644
--- a/tools/cru-py/cru/service/_base.py
+++ b/tools/cru-py/cru/service/_base.py
@@ -7,17 +7,27 @@ import os
from pathlib import Path
from typing import TypeVar, overload
-from cru import CruException, CruInternalError, CruPath, CruUserFriendlyException
+from cru import CruException, CruLogicError, CruPath
_Feature = TypeVar("_Feature", bound="AppFeatureProvider")
OWNER_NAME = "crupest"
-class InternalAppException(CruInternalError):
+class AppError(CruException):
pass
+class AppFeatureError(AppError):
+ def __init__(self, message, feature: type | str, *args, **kwargs):
+ super().__init__(message, *args, **kwargs)
+ self._feature = feature
+
+ @property
+ def feature(self) -> type | str:
+ return self._feature
+
+
class AppPathError(CruException):
def __init__(self, message, _path: str | Path, *args, **kwargs):
super().__init__(message, *args, **kwargs)
@@ -152,12 +162,12 @@ class AppRootPath(AppPath):
@property
def full_path(self) -> CruPath:
if self._full_path is None:
- raise CruInternalError("App root path is not set yet.")
+ raise AppError("App root path is not set yet.")
return self._full_path
def setup(self, path: os.PathLike) -> None:
if self._full_path is not None:
- raise CruInternalError("App root path is already set.")
+ raise AppError("App root path is already set.")
self._full_path = CruPath(path)
@@ -270,7 +280,7 @@ class AppBase:
@staticmethod
def get_instance() -> AppBase:
if AppBase._instance is None:
- raise CruInternalError("App instance not initialized")
+ raise AppError("App instance not initialized")
return AppBase._instance
def __init__(self, name: str):
@@ -312,8 +322,9 @@ class AppBase:
def ensure_app_initialized(self) -> AppRootPath:
if not self.app_initialized:
- raise CruUserFriendlyException(
- "Root directory does not exist. Please run 'init' to create one."
+ raise AppError(
+ user_message="Root directory does not exist. "
+ "Please run 'init' to create one."
)
return self.root
@@ -328,7 +339,9 @@ class AppBase:
def add_feature(self, feature: _Feature) -> _Feature:
for f in self.features:
if f.name == feature.name:
- raise CruInternalError(f"Duplicate feature name: {feature.name}.")
+ raise AppFeatureError(
+ f"Duplicate feature name: {feature.name}.", feature.name
+ )
self._features.append(feature)
return feature
@@ -365,14 +378,12 @@ class AppBase:
if isinstance(f, feature):
return f
else:
- raise InternalAppException(
- "Argument must be the name of feature or its class."
- )
+ raise CruLogicError("Argument must be the name of feature or its class.")
- raise InternalAppException(f"Feature {feature} not found.")
+ raise AppFeatureError(f"Feature {feature} not found.", feature)
def get_path(self, name: str) -> AppFeaturePath:
for p in self._paths:
if p.id == name or p.name == name:
return p
- raise InternalAppException(f"Application path {name} not found.")
+ raise AppPathError(f"Application path {name} not found.", name)
diff --git a/tools/cru-py/cru/service/_config.py b/tools/cru-py/cru/service/_config.py
index f2beeb2..9c91c93 100644
--- a/tools/cru-py/cru/service/_config.py
+++ b/tools/cru-py/cru/service/_config.py
@@ -1,7 +1,7 @@
from collections.abc import Iterable
from typing import Any, NoReturn
-from cru import CruException, CruUserFriendlyException
+from cru import CruException
from cru.config import Configuration, ConfigItem
from cru.value import (
INTEGER_VALUE_TYPE,
@@ -26,16 +26,6 @@ class AppConfigError(CruException):
def configuration(self) -> Configuration:
return self._configuration
- @property
- def friendly_error_message(self) -> str:
- raise NotImplementedError("Subclasses must implement this method.")
-
- def to_friendly_error(self) -> CruUserFriendlyException:
- return CruUserFriendlyException(self.friendly_error_message)
-
- def raise_friendly_error(self) -> NoReturn:
- raise self.to_friendly_error() from self
-
class AppConfigFileError(AppConfigError):
def __init__(
@@ -64,8 +54,7 @@ class AppConfigFileNotFoundError(AppConfigFileError):
def file_path(self) -> str:
return self._file_path
- @property
- def friendly_error_message(self) -> str:
+ def get_user_message(self) -> str:
return f"Config file not found at {self.file_path}. You may need to create one."
@@ -76,22 +65,18 @@ class AppConfigFileParseError(AppConfigFileError):
configuration: Configuration,
file_content: str,
*args,
- cause: ParseError | None = None,
**kwargs,
) -> None:
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
+ self.__cause__: ParseError
@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}."
+ def get_user_message(self) -> str:
+ return f"Error while parsing config file at line {self.__cause__.line_number}."
class AppConfigFileEntryError(AppConfigFileError):
@@ -123,8 +108,7 @@ class AppConfigFileEntryError(AppConfigFileError):
def friendly_message_head(self) -> str:
return "Error entries found in config file"
- @property
- def friendly_error_message(self) -> str:
+ def get_user_message(self) -> str:
return (
f"{self.friendly_message_head}:\n"
f"{self.entries_to_friendly_message(self.error_entries)}"
@@ -316,24 +300,19 @@ class ConfigManager(AppCommandFeatureProvider):
) 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()
- 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 _read_config_file(self) -> dict[str, Any]:
+ parsed = self._parse_config_file()
+ 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
def reload_config_file(self) -> bool:
self.configuration.reset_all()
value_dict = self._read_config_file()
- # TODO: Continue here!
- for key, value in config_dict.items():
+ for key, value in value_dict.items():
+ # TODO: Continue here!
self.configuration.set(key, value)
return True