Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Setsugennoao committed Oct 29, 2023
1 parent 754e051 commit a4ca731
Show file tree
Hide file tree
Showing 26 changed files with 2,404 additions and 0 deletions.
5 changes: 5 additions & 0 deletions stgpytools/__init__.py
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
2 changes: 2 additions & 0 deletions stgpytools/enums/__init__.py
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
77 changes: 77 additions & 0 deletions stgpytools/enums/base.py
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)
56 changes: 56 additions & 0 deletions stgpytools/enums/other.py
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."""
5 changes: 5 additions & 0 deletions stgpytools/exceptions/__init__.py
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
211 changes: 211 additions & 0 deletions stgpytools/exceptions/base.py
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."""
11 changes: 11 additions & 0 deletions stgpytools/exceptions/enum.py
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"""
Loading

0 comments on commit a4ca731

Please sign in to comment.