-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
All this I initially wrote in https://github.com/Irrational-Encoding-Wizardry/vs-tools
- Loading branch information
1 parent
754e051
commit a4ca731
Showing
26 changed files
with
2,404 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .enums import * # noqa: F401, F403 | ||
from .exceptions import * # noqa: F401, F403 | ||
from .functions import * # noqa: F401, F403 | ||
from .types import * # noqa: F401, F403 | ||
from .utils import * # noqa: F401, F403 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .base import * # noqa: F401, F403 | ||
from .other import * # noqa: F401, F403 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from __future__ import annotations | ||
|
||
from enum import Enum | ||
from typing import Any, TypeVar | ||
|
||
from ..exceptions import CustomValueError, NotFoundEnumValue | ||
from ..types import FuncExceptT | ||
|
||
__all__ = [ | ||
'SelfEnum', | ||
'CustomEnum', 'CustomIntEnum', 'CustomStrEnum' | ||
] | ||
|
||
|
||
class CustomEnum(Enum): | ||
"""Base class for custom enums.""" | ||
|
||
@classmethod | ||
def _missing_(cls: type[SelfEnum], value: Any) -> SelfEnum | None: | ||
return cls.from_param(value) | ||
|
||
@classmethod | ||
def from_param(cls: type[SelfEnum], value: Any, func_except: FuncExceptT | None = None) -> SelfEnum | None: | ||
""" | ||
Return the enum value from a parameter. | ||
:param value: Value to instantiate the enum class. | ||
:param func_except: Exception function. | ||
:return: Enum value. | ||
:raises NotFoundEnumValue: Variable not found in the given enum. | ||
""" | ||
|
||
if value is None: | ||
return None | ||
|
||
if func_except is None: | ||
func_except = cls.from_param | ||
|
||
if isinstance(value, cls): | ||
return value | ||
|
||
if value is cls: | ||
raise CustomValueError('You must select a member, not pass the enum!', func_except) | ||
|
||
try: | ||
return cls(value) | ||
except ValueError: | ||
... | ||
|
||
if isinstance(func_except, tuple): | ||
func_name, var_name = func_except | ||
else: | ||
func_name, var_name = func_except, '' | ||
|
||
raise NotFoundEnumValue( | ||
'Value for "{var_name}" argument must be a valid {enum_name}.\n' | ||
'It can be a value in [{readable_enum}].', func_name, | ||
var_name=var_name, enum_name=cls, | ||
readable_enum=iter([f'{x.name} ({x.value})' for x in cls]) | ||
) | ||
|
||
|
||
class CustomIntEnum(int, CustomEnum): | ||
"""Base class for custom int enums.""" | ||
|
||
value: int | ||
|
||
|
||
class CustomStrEnum(str, CustomEnum): | ||
"""Base class for custom str enums.""" | ||
|
||
value: str | ||
|
||
|
||
SelfEnum = TypeVar('SelfEnum', bound=CustomEnum) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TypeVar, overload | ||
|
||
__all__ = [ | ||
'Coordinate', | ||
'Position', | ||
'Size' | ||
] | ||
|
||
|
||
class Coordinate: | ||
""" | ||
Positive set of (x, y) coordinates. | ||
:raises ValueError: Negative values were passed. | ||
""" | ||
|
||
x: int | ||
"""Horizontal coordinate.""" | ||
|
||
y: int | ||
"""Vertical coordinate.""" | ||
|
||
@overload | ||
def __init__(self: SelfCoord, other: tuple[int, int] | SelfCoord, /) -> None: | ||
... | ||
|
||
@overload | ||
def __init__(self: SelfCoord, x: int, y: int, /) -> None: | ||
... | ||
|
||
def __init__(self: SelfCoord, x_or_self: int | tuple[int, int] | SelfCoord, y: int, /) -> None: # type: ignore | ||
from ..exceptions import CustomValueError | ||
|
||
if isinstance(x_or_self, int): | ||
x = x_or_self | ||
else: | ||
x, y = x_or_self if isinstance(x_or_self, tuple) else (x_or_self.x, x_or_self.y) | ||
|
||
if x < 0 or y < 0: | ||
raise CustomValueError("Values can't be negative!", self.__class__) | ||
|
||
self.x = x | ||
self.y = y | ||
|
||
|
||
SelfCoord = TypeVar('SelfCoord', bound=Coordinate) | ||
|
||
|
||
class Position(Coordinate): | ||
"""Positive set of an (x,y) offset relative to the top left corner of an area.""" | ||
|
||
|
||
class Size(Coordinate): | ||
"""Positive set of an (x,y), (horizontal,vertical), size of an area.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .base import * # noqa: F401, F403 | ||
from .enum import * # noqa: F401, F403 | ||
from .file import * # noqa: F401, F403 | ||
from .generic import * # noqa: F401, F403 | ||
from .module import * # noqa: F401, F403 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
from __future__ import annotations | ||
|
||
import sys | ||
from copy import deepcopy | ||
from typing import TYPE_CHECKING, Any, TypeVar | ||
|
||
from ..types import MISSING, FuncExceptT, SupportsString | ||
|
||
__all__ = [ | ||
'CustomError', | ||
|
||
'CustomValueError', | ||
'CustomIndexError', | ||
'CustomOverflowError', | ||
'CustomKeyError', | ||
'CustomTypeError', | ||
'CustomRuntimeError', | ||
'CustomNotImplementedError', | ||
'CustomPermissionError' | ||
] | ||
|
||
|
||
if TYPE_CHECKING: | ||
class ExceptionT(Exception, type): | ||
... | ||
else: | ||
ExceptionT = Exception | ||
|
||
|
||
class CustomErrorMeta(type): | ||
"""Custom base exception meta class.""" | ||
|
||
def __new__(cls: type[SelfCErrorMeta], *args: Any) -> SelfCErrorMeta: | ||
return CustomErrorMeta.setup_exception(type.__new__(cls, *args)) # type: ignore | ||
|
||
@staticmethod | ||
def setup_exception(exception: SelfCErrorMeta, override: str | ExceptionT | None = None) -> SelfCErrorMeta: | ||
""" | ||
Setup an exception for later use in CustomError. | ||
:param exception: Exception to update. | ||
:param override: Optional name or exception from which get the override values. | ||
:return: Set up exception. | ||
""" | ||
|
||
if override: | ||
if isinstance(override, str): | ||
over_name = over_qual = override | ||
else: | ||
over_name, over_qual = override.__name__, override.__qualname__ | ||
|
||
if over_name.startswith('Custom'): | ||
exception.__name__ = over_name | ||
else: | ||
exception.__name__ = f'Custom{over_name}' | ||
|
||
exception.__qualname__ = over_qual | ||
|
||
if exception.__qualname__.startswith('Custom'): | ||
exception.__qualname__ = exception.__qualname__[6:] | ||
|
||
if sys.stdout and sys.stdout.isatty(): | ||
exception.__qualname__ = f'\033[0;31;1m{exception.__qualname__}\033[0m' | ||
|
||
exception.__module__ = Exception.__module__ | ||
|
||
return exception | ||
|
||
if TYPE_CHECKING: | ||
def __getitem__(self, exception: type[Exception]) -> CustomError: | ||
... | ||
|
||
|
||
SelfCErrorMeta = TypeVar('SelfCErrorMeta', bound=CustomErrorMeta) | ||
|
||
|
||
class CustomError(ExceptionT, metaclass=CustomErrorMeta): | ||
"""Custom base exception class.""" | ||
|
||
def __init__( | ||
self, message: SupportsString | None = None, func: FuncExceptT | None = None, reason: Any = None, **kwargs: Any | ||
) -> None: | ||
""" | ||
Instantiate a new exception with pretty printing and more. | ||
:param message: Message of the error. | ||
:param func: Function this exception was raised from. | ||
:param reason: Reason of the exception. For example, an optional parameter. | ||
""" | ||
|
||
self.message = message | ||
self.func = func | ||
self.reason = reason | ||
self.kwargs = kwargs | ||
|
||
super().__init__(message) | ||
|
||
def __class_getitem__(cls, exception: str | type[ExceptionT] | ExceptionT) -> CustomError: | ||
if isinstance(exception, str): | ||
class inner_exception(cls): # type: ignore | ||
... | ||
else: | ||
if not issubclass(exception, type): | ||
exception = exception.__class__ # type: ignore | ||
|
||
class inner_exception(cls, exception): # type: ignore | ||
... | ||
|
||
return CustomErrorMeta.setup_exception(inner_exception, exception) # type: ignore | ||
|
||
def __call__( | ||
self: SelfError, message: SupportsString | None = MISSING, | ||
func: FuncExceptT | None = MISSING, reason: SupportsString | FuncExceptT | None = MISSING, # type: ignore | ||
**kwargs: Any | ||
) -> SelfError: | ||
""" | ||
Copy an existing exception with defaults and instantiate a new one. | ||
:param message: Message of the error. | ||
:param func: Function this exception was raised from. | ||
:param reason: Reason of the exception. For example, an optional parameter. | ||
""" | ||
|
||
err = deepcopy(self) | ||
|
||
if message is not MISSING: | ||
err.message = message | ||
|
||
if func is not MISSING: # type: ignore[comparison-overlap] | ||
err.func = func | ||
|
||
if reason is not MISSING: | ||
err.reason = reason | ||
|
||
err.kwargs |= kwargs | ||
|
||
return err | ||
|
||
def __str__(self) -> str: | ||
from ..functions import norm_display_name, norm_func_name | ||
|
||
message = self.message | ||
|
||
if not message: | ||
message = 'An error occurred!' | ||
|
||
if self.func: | ||
func_header = norm_func_name(self.func).strip() | ||
|
||
if sys.stdout and sys.stdout.isatty(): | ||
func_header = f'\033[0;36m{func_header}\033[0m' | ||
|
||
func_header = f'({func_header}) ' | ||
else: | ||
func_header = '' | ||
|
||
if self.kwargs: | ||
self.kwargs = { | ||
key: norm_display_name(value) for key, value in self.kwargs.items() | ||
} | ||
|
||
if self.reason: | ||
reason = self.reason = norm_display_name(self.reason) | ||
|
||
if reason: | ||
if not isinstance(self.reason, dict): | ||
reason = f'({reason})' | ||
|
||
if sys.stdout and sys.stdout.isatty(): | ||
reason = f'\033[0;33m{reason}\033[0m' | ||
reason = f' {reason}' | ||
else: | ||
reason = '' | ||
|
||
return f'{func_header}{self.message!s}{reason}'.format(**self.kwargs).strip() | ||
|
||
|
||
SelfError = TypeVar('SelfError', bound=CustomError) | ||
|
||
|
||
class CustomValueError(CustomError, ValueError): | ||
"""Thrown when a specified value is invalid.""" | ||
|
||
|
||
class CustomIndexError(CustomError, IndexError): | ||
"""Thrown when an index or generic numeric value is out of bound.""" | ||
|
||
|
||
class CustomOverflowError(CustomError, OverflowError): | ||
"""Thrown when a value is out of range. e.g. temporal radius too big.""" | ||
|
||
|
||
class CustomKeyError(CustomError, KeyError): | ||
"""Thrown when trying to access an non-existent key.""" | ||
|
||
|
||
class CustomTypeError(CustomError, TypeError): | ||
"""Thrown when a passed argument is of wrong type.""" | ||
|
||
|
||
class CustomRuntimeError(CustomError, RuntimeError): | ||
"""Thrown when a runtime error occurs.""" | ||
|
||
|
||
class CustomNotImplementedError(CustomError, NotImplementedError): | ||
"""Thrown when you encounter a yet not implemented branch of code.""" | ||
|
||
|
||
class CustomPermissionError(CustomError, PermissionError): | ||
"""Thrown when the user can't perform an action.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from __future__ import annotations | ||
|
||
from .base import CustomKeyError | ||
|
||
__all__ = [ | ||
'NotFoundEnumValue' | ||
] | ||
|
||
|
||
class NotFoundEnumValue(CustomKeyError): | ||
"""Raised when you try to instantiate an Enum with unknown value""" |
Oops, something went wrong.