jetpytools 1.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jetpytools might be problematic. Click here for more details.
- jetpytools/__init__.py +5 -0
- jetpytools/_metadata.py +12 -0
- jetpytools/enums/__init__.py +2 -0
- jetpytools/enums/base.py +78 -0
- jetpytools/enums/other.py +59 -0
- jetpytools/exceptions/__init__.py +5 -0
- jetpytools/exceptions/base.py +213 -0
- jetpytools/exceptions/enum.py +11 -0
- jetpytools/exceptions/file.py +38 -0
- jetpytools/exceptions/generic.py +45 -0
- jetpytools/exceptions/module.py +39 -0
- jetpytools/functions/__init__.py +3 -0
- jetpytools/functions/funcs.py +152 -0
- jetpytools/functions/normalize.py +254 -0
- jetpytools/functions/other.py +18 -0
- jetpytools/py.typed +0 -0
- jetpytools/types/__init__.py +6 -0
- jetpytools/types/builtins.py +77 -0
- jetpytools/types/file.py +193 -0
- jetpytools/types/funcs.py +109 -0
- jetpytools/types/generic.py +52 -0
- jetpytools/types/supports.py +127 -0
- jetpytools/types/utils.py +669 -0
- jetpytools/utils/__init__.py +4 -0
- jetpytools/utils/file.py +256 -0
- jetpytools/utils/funcs.py +35 -0
- jetpytools/utils/math.py +158 -0
- jetpytools/utils/ranges.py +89 -0
- jetpytools-1.2.3.dist-info/LICENSE +21 -0
- jetpytools-1.2.3.dist-info/METADATA +48 -0
- jetpytools-1.2.3.dist-info/RECORD +33 -0
- jetpytools-1.2.3.dist-info/WHEEL +5 -0
- jetpytools-1.2.3.dist-info/top_level.txt +1 -0
jetpytools/__init__.py
ADDED
jetpytools/_metadata.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Collection of stuff that's useful in general python programming"""
|
|
2
|
+
|
|
3
|
+
__version__ = '1.2.3'
|
|
4
|
+
|
|
5
|
+
__author_name__, __author_email__ = 'Jaded Encoding Thaumaturgy', 'jaded.encoding.thaumaturgy@gmail.com'
|
|
6
|
+
__maintainer_name__, __maintainer_email__ = __author_name__, __author_email__
|
|
7
|
+
|
|
8
|
+
__author__ = f'{__author_name__} <{__author_email__}>'
|
|
9
|
+
__maintainer__ = __author__
|
|
10
|
+
|
|
11
|
+
if __name__ == '__github__':
|
|
12
|
+
print(__version__)
|
jetpytools/enums/base.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
from ..exceptions import CustomValueError, NotFoundEnumValue
|
|
7
|
+
from ..types import FuncExceptT
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'SelfEnum',
|
|
11
|
+
'CustomEnum', 'CustomIntEnum', 'CustomStrEnum'
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CustomEnum(Enum):
|
|
16
|
+
"""Base class for custom enums."""
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def _missing_(cls: type[SelfEnum], value: Any) -> SelfEnum | None:
|
|
20
|
+
return cls.from_param(value)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_param(cls: type[SelfEnum], value: Any, func_except: FuncExceptT | None = None) -> SelfEnum | None:
|
|
24
|
+
"""
|
|
25
|
+
Return the enum value from a parameter.
|
|
26
|
+
|
|
27
|
+
:param value: Value to instantiate the enum class.
|
|
28
|
+
:param func_except: Exception function.
|
|
29
|
+
|
|
30
|
+
:return: Enum value.
|
|
31
|
+
|
|
32
|
+
:raises NotFoundEnumValue: Variable not found in the given enum.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
if value is None:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
if func_except is None:
|
|
39
|
+
func_except = cls.from_param
|
|
40
|
+
|
|
41
|
+
if isinstance(value, cls):
|
|
42
|
+
return value
|
|
43
|
+
|
|
44
|
+
if value is cls:
|
|
45
|
+
raise CustomValueError('You must select a member, not pass the enum!', func_except)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
return cls(value)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
if isinstance(func_except, tuple):
|
|
53
|
+
func_name, var_name = func_except
|
|
54
|
+
else:
|
|
55
|
+
func_name, var_name = func_except, str(cls)
|
|
56
|
+
|
|
57
|
+
raise NotFoundEnumValue(
|
|
58
|
+
'The given value for "{var_name}" argument must be a valid {enum_name}, not "{value}"!\n'
|
|
59
|
+
'Valid values are: [{readable_enum}].', func_name,
|
|
60
|
+
var_name=var_name, enum_name=cls, value=value,
|
|
61
|
+
readable_enum=iter([f'{x.name} ({x.value})' for x in cls]),
|
|
62
|
+
reason=value
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CustomIntEnum(int, CustomEnum):
|
|
67
|
+
"""Base class for custom int enums."""
|
|
68
|
+
|
|
69
|
+
value: int
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class CustomStrEnum(str, CustomEnum):
|
|
73
|
+
"""Base class for custom str enums."""
|
|
74
|
+
|
|
75
|
+
value: str
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
SelfEnum = TypeVar('SelfEnum', bound=CustomEnum)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TypeVar, overload
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
'Coordinate',
|
|
7
|
+
'Position',
|
|
8
|
+
'Size'
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Coordinate:
|
|
13
|
+
"""
|
|
14
|
+
Positive set of (x, y) coordinates.
|
|
15
|
+
|
|
16
|
+
:raises ValueError: Negative values were passed.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
x: int
|
|
20
|
+
"""Horizontal coordinate."""
|
|
21
|
+
|
|
22
|
+
y: int
|
|
23
|
+
"""Vertical coordinate."""
|
|
24
|
+
|
|
25
|
+
@overload
|
|
26
|
+
def __init__(self: SelfCoord, other: tuple[int, int] | SelfCoord, /) -> None:
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def __init__(self: SelfCoord, x: int, y: int, /) -> None:
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def __init__(self: SelfCoord, x_or_self: int | tuple[int, int] | SelfCoord, y: int | None = None, /) -> None:
|
|
34
|
+
from ..exceptions import CustomValueError
|
|
35
|
+
|
|
36
|
+
if isinstance(x_or_self, int):
|
|
37
|
+
x = x_or_self
|
|
38
|
+
else:
|
|
39
|
+
x, y = x_or_self if isinstance(x_or_self, tuple) else (x_or_self.x, x_or_self.y)
|
|
40
|
+
|
|
41
|
+
if y is None:
|
|
42
|
+
raise CustomValueError("y coordinate must be defined!", self.__class__)
|
|
43
|
+
|
|
44
|
+
if x < 0 or y < 0:
|
|
45
|
+
raise CustomValueError("Values can't be negative!", self.__class__)
|
|
46
|
+
|
|
47
|
+
self.x = x
|
|
48
|
+
self.y = y
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
SelfCoord = TypeVar('SelfCoord', bound=Coordinate)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Position(Coordinate):
|
|
55
|
+
"""Positive set of an (x,y) offset relative to the top left corner of an area."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Size(Coordinate):
|
|
59
|
+
"""Positive set of an (x,y), (horizontal,vertical), size of an area."""
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
6
|
+
|
|
7
|
+
from ..types import MISSING, FuncExceptT, SupportsString
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'CustomError',
|
|
11
|
+
|
|
12
|
+
'CustomValueError',
|
|
13
|
+
'CustomIndexError',
|
|
14
|
+
'CustomOverflowError',
|
|
15
|
+
'CustomKeyError',
|
|
16
|
+
'CustomTypeError',
|
|
17
|
+
'CustomRuntimeError',
|
|
18
|
+
'CustomNotImplementedError',
|
|
19
|
+
'CustomPermissionError'
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
class ExceptionT(Exception):
|
|
25
|
+
__name__: str
|
|
26
|
+
__qualname__: str
|
|
27
|
+
...
|
|
28
|
+
else:
|
|
29
|
+
ExceptionT = Exception
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CustomErrorMeta(type):
|
|
33
|
+
"""Custom base exception meta class."""
|
|
34
|
+
|
|
35
|
+
def __new__(cls: type[SelfCErrorMeta], *args: Any) -> SelfCErrorMeta:
|
|
36
|
+
return CustomErrorMeta.setup_exception(type.__new__(cls, *args)) # type: ignore
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def setup_exception(exception: SelfCErrorMeta, override: str | ExceptionT | None = None) -> SelfCErrorMeta:
|
|
40
|
+
"""
|
|
41
|
+
Setup an exception for later use in CustomError.
|
|
42
|
+
|
|
43
|
+
:param exception: Exception to update.
|
|
44
|
+
:param override: Optional name or exception from which get the override values.
|
|
45
|
+
|
|
46
|
+
:return: Set up exception.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
if override:
|
|
50
|
+
if isinstance(override, str):
|
|
51
|
+
over_name = over_qual = override
|
|
52
|
+
else:
|
|
53
|
+
over_name, over_qual = override.__name__, override.__qualname__
|
|
54
|
+
|
|
55
|
+
if over_name.startswith('Custom'):
|
|
56
|
+
exception.__name__ = over_name
|
|
57
|
+
else:
|
|
58
|
+
exception.__name__ = f'Custom{over_name}'
|
|
59
|
+
|
|
60
|
+
exception.__qualname__ = over_qual
|
|
61
|
+
|
|
62
|
+
if exception.__qualname__.startswith('Custom'):
|
|
63
|
+
exception.__qualname__ = exception.__qualname__[6:]
|
|
64
|
+
|
|
65
|
+
if sys.stdout and sys.stdout.isatty():
|
|
66
|
+
exception.__qualname__ = f'\033[0;31;1m{exception.__qualname__}\033[0m'
|
|
67
|
+
|
|
68
|
+
exception.__module__ = Exception.__module__
|
|
69
|
+
|
|
70
|
+
return exception
|
|
71
|
+
|
|
72
|
+
if TYPE_CHECKING:
|
|
73
|
+
def __getitem__(self, exception: type[Exception]) -> CustomError:
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
SelfCErrorMeta = TypeVar('SelfCErrorMeta', bound=CustomErrorMeta)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CustomError(ExceptionT, metaclass=CustomErrorMeta):
|
|
81
|
+
"""Custom base exception class."""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self, message: SupportsString | None = None, func: FuncExceptT | None = None, reason: Any = None, **kwargs: Any
|
|
85
|
+
) -> None:
|
|
86
|
+
"""
|
|
87
|
+
Instantiate a new exception with pretty printing and more.
|
|
88
|
+
|
|
89
|
+
:param message: Message of the error.
|
|
90
|
+
:param func: Function this exception was raised from.
|
|
91
|
+
:param reason: Reason of the exception. For example, an optional parameter.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
self.message = message
|
|
95
|
+
self.func = func
|
|
96
|
+
self.reason = reason
|
|
97
|
+
self.kwargs = kwargs
|
|
98
|
+
|
|
99
|
+
super().__init__(message)
|
|
100
|
+
|
|
101
|
+
def __class_getitem__(cls, exception: str | type[ExceptionT] | ExceptionT) -> CustomError:
|
|
102
|
+
if isinstance(exception, str):
|
|
103
|
+
class inner_exception(cls): # type: ignore
|
|
104
|
+
...
|
|
105
|
+
else:
|
|
106
|
+
if not issubclass(exception, type): # type: ignore
|
|
107
|
+
exception = exception.__class__ # type: ignore
|
|
108
|
+
|
|
109
|
+
class inner_exception(cls, exception): # type: ignore
|
|
110
|
+
...
|
|
111
|
+
|
|
112
|
+
return CustomErrorMeta.setup_exception(inner_exception, exception) # type: ignore
|
|
113
|
+
|
|
114
|
+
def __call__(
|
|
115
|
+
self: SelfError, message: SupportsString | None = MISSING,
|
|
116
|
+
func: FuncExceptT | None = MISSING, reason: SupportsString | FuncExceptT | None = MISSING, # type: ignore
|
|
117
|
+
**kwargs: Any
|
|
118
|
+
) -> SelfError:
|
|
119
|
+
"""
|
|
120
|
+
Copy an existing exception with defaults and instantiate a new one.
|
|
121
|
+
|
|
122
|
+
:param message: Message of the error.
|
|
123
|
+
:param func: Function this exception was raised from.
|
|
124
|
+
:param reason: Reason of the exception. For example, an optional parameter.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
err = deepcopy(self)
|
|
128
|
+
|
|
129
|
+
if message is not MISSING:
|
|
130
|
+
err.message = message
|
|
131
|
+
|
|
132
|
+
if func is not MISSING: # type: ignore[comparison-overlap]
|
|
133
|
+
err.func = func
|
|
134
|
+
|
|
135
|
+
if reason is not MISSING:
|
|
136
|
+
err.reason = reason
|
|
137
|
+
|
|
138
|
+
err.kwargs |= kwargs
|
|
139
|
+
|
|
140
|
+
return err
|
|
141
|
+
|
|
142
|
+
def __str__(self) -> str:
|
|
143
|
+
from ..functions import norm_display_name, norm_func_name
|
|
144
|
+
|
|
145
|
+
message = self.message
|
|
146
|
+
|
|
147
|
+
if not message:
|
|
148
|
+
message = 'An error occurred!'
|
|
149
|
+
|
|
150
|
+
if self.func:
|
|
151
|
+
func_header = norm_func_name(self.func).strip()
|
|
152
|
+
|
|
153
|
+
if sys.stdout and sys.stdout.isatty():
|
|
154
|
+
func_header = f'\033[0;36m{func_header}\033[0m'
|
|
155
|
+
|
|
156
|
+
func_header = f'({func_header}) '
|
|
157
|
+
else:
|
|
158
|
+
func_header = ''
|
|
159
|
+
|
|
160
|
+
if self.kwargs:
|
|
161
|
+
self.kwargs = {
|
|
162
|
+
key: norm_display_name(value) for key, value in self.kwargs.items()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if self.reason:
|
|
166
|
+
reason = self.reason = norm_display_name(self.reason)
|
|
167
|
+
|
|
168
|
+
if reason:
|
|
169
|
+
if not isinstance(self.reason, dict):
|
|
170
|
+
reason = f'({reason})'
|
|
171
|
+
|
|
172
|
+
if sys.stdout and sys.stdout.isatty():
|
|
173
|
+
reason = f'\033[0;33m{reason}\033[0m'
|
|
174
|
+
reason = f' {reason}'
|
|
175
|
+
else:
|
|
176
|
+
reason = ''
|
|
177
|
+
|
|
178
|
+
return f'{func_header}{self.message!s}{reason}'.format(**self.kwargs).strip()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
SelfError = TypeVar('SelfError', bound=CustomError)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class CustomValueError(CustomError, ValueError):
|
|
185
|
+
"""Thrown when a specified value is invalid."""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class CustomIndexError(CustomError, IndexError):
|
|
189
|
+
"""Thrown when an index or generic numeric value is out of bound."""
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class CustomOverflowError(CustomError, OverflowError):
|
|
193
|
+
"""Thrown when a value is out of range. e.g. temporal radius too big."""
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class CustomKeyError(CustomError, KeyError):
|
|
197
|
+
"""Thrown when trying to access an non-existent key."""
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class CustomTypeError(CustomError, TypeError):
|
|
201
|
+
"""Thrown when a passed argument is of wrong type."""
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class CustomRuntimeError(CustomError, RuntimeError):
|
|
205
|
+
"""Thrown when a runtime error occurs."""
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class CustomNotImplementedError(CustomError, NotImplementedError):
|
|
209
|
+
"""Thrown when you encounter a yet not implemented branch of code."""
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class CustomPermissionError(CustomError, PermissionError):
|
|
213
|
+
"""Thrown when the user can't perform an action."""
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .base import CustomError, CustomPermissionError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
'FileNotExistsError',
|
|
8
|
+
'FileWasNotFoundError',
|
|
9
|
+
'FilePermissionError',
|
|
10
|
+
'FileTypeMismatchError',
|
|
11
|
+
'FileIsADirectoryError',
|
|
12
|
+
|
|
13
|
+
'PathIsNotADirectoryError',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FileNotExistsError(CustomError, FileExistsError):
|
|
18
|
+
"""Raised when a file doesn't exists"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FileWasNotFoundError(CustomError, FileNotFoundError):
|
|
22
|
+
"""Raised when a file wasn't found but the path is correct, e.g. parent directory exists"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FilePermissionError(CustomPermissionError):
|
|
26
|
+
"""Raised when you try to access a file but haven't got permissions to do so"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FileTypeMismatchError(CustomError, OSError):
|
|
30
|
+
"""Raised when you try to access a file with a FileType != AUTO and it's another file type"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FileIsADirectoryError(CustomError, IsADirectoryError):
|
|
34
|
+
"""Raised when you try to access a file but it's a directory instead"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PathIsNotADirectoryError(CustomError, NotADirectoryError):
|
|
38
|
+
"""Raised when you try to access a directory but it's not a directory"""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Iterable
|
|
4
|
+
|
|
5
|
+
from ..types import FuncExceptT, SupportsString, T
|
|
6
|
+
from .base import CustomValueError
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'MismatchError', 'MismatchRefError'
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MismatchError(CustomValueError):
|
|
14
|
+
"""Raised when there's a mismatch between two or more values."""
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def _item_to_name(cls, item: Any) -> str:
|
|
18
|
+
return str(item)
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def _reduce(cls, items: Iterable[Any]) -> tuple[str]:
|
|
22
|
+
return tuple[str](dict.fromkeys(map(cls._item_to_name, items)).keys()) # type: ignore
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, func: FuncExceptT, items: Iterable[Any], message: SupportsString = 'All items must be equal!',
|
|
26
|
+
reason: Any = '{reduced_items}', **kwargs: Any
|
|
27
|
+
) -> None:
|
|
28
|
+
super().__init__(message, func, reason, **kwargs, reduced_items=iter(self._reduce(items)))
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def check(cls, func: FuncExceptT, *items: Any, **kwargs: Any) -> None:
|
|
32
|
+
if len(cls._reduce(items)) != 1:
|
|
33
|
+
raise cls(func, items, **kwargs)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MismatchRefError(MismatchError):
|
|
37
|
+
def __init__(
|
|
38
|
+
self, func: FuncExceptT, base: T, ref: T, message: SupportsString = 'All items must be equal!', **kwargs: Any
|
|
39
|
+
) -> None:
|
|
40
|
+
super().__init__(func, [base, ref], message, **kwargs)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def check(cls, func: FuncExceptT, *items: Any, **kwargs: Any) -> None:
|
|
44
|
+
if len(cls._reduce(items)) != 1:
|
|
45
|
+
raise cls(func, *items, **kwargs)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..types import FuncExceptT, SupportsString
|
|
6
|
+
from .base import CustomError
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'CustomImportError',
|
|
10
|
+
'DependencyNotFoundError'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CustomImportError(CustomError, ImportError):
|
|
15
|
+
"""Raised when there's a general import error."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self, func: FuncExceptT, package: str | ImportError,
|
|
19
|
+
message: SupportsString = "Import failed for package '{package}'!",
|
|
20
|
+
**kwargs: Any
|
|
21
|
+
) -> None:
|
|
22
|
+
"""
|
|
23
|
+
:param func: Function this error was raised from.
|
|
24
|
+
:param package: Either the raised error or the name of the missing package.
|
|
25
|
+
:param message: Custom error message.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
super().__init__(message, func, package=package if isinstance(package, str) else package.name, **kwargs)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DependencyNotFoundError(CustomImportError):
|
|
32
|
+
"""Raised when there's a missing optional dependency."""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self, func: FuncExceptT, package: str | ImportError,
|
|
36
|
+
message: SupportsString = "Missing dependency '{package}'!",
|
|
37
|
+
**kwargs: Any
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__(func, package, message, **kwargs)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Concatenate, overload
|
|
4
|
+
|
|
5
|
+
from ..exceptions import CustomRuntimeError
|
|
6
|
+
from ..types import MISSING, KwargsT, MissingT, P, R, T
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'iterate', 'fallback', 'kwargs_fallback'
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def iterate(
|
|
14
|
+
base: T, function: Callable[Concatenate[T | R, P], R],
|
|
15
|
+
count: int, *args: P.args, **kwargs: P.kwargs
|
|
16
|
+
) -> T | R:
|
|
17
|
+
"""
|
|
18
|
+
Execute a given function over the base value multiple times.
|
|
19
|
+
|
|
20
|
+
Different from regular iteration functions is that you do not need to pass a partial object.
|
|
21
|
+
This function accepts *args and **kwargs. These will be passed on to the given function.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
|
|
25
|
+
>>> iterate(5, lambda x: x * 2, 2)
|
|
26
|
+
20
|
|
27
|
+
|
|
28
|
+
:param base: Base value, etc. to iterate over.
|
|
29
|
+
:param function: Function to iterate over the base.
|
|
30
|
+
:param count: Number of times to execute function.
|
|
31
|
+
:param *args: Positional arguments to pass to the given function.
|
|
32
|
+
:param **kwargs: Keyword arguments to pass to the given function.
|
|
33
|
+
|
|
34
|
+
:return: Value, etc. with the given function run over it
|
|
35
|
+
*n* amount of times based on the given count.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
if count <= 0:
|
|
39
|
+
return base
|
|
40
|
+
|
|
41
|
+
result: T | R = base
|
|
42
|
+
|
|
43
|
+
for _ in range(count):
|
|
44
|
+
result = function(result, *args, **kwargs)
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
fallback_missing = object()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@overload
|
|
53
|
+
def fallback(value: T | None, fallback: T, /) -> T:
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@overload
|
|
58
|
+
def fallback(value: T | None, fallback0: T | None, default: T, /) -> T:
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@overload
|
|
63
|
+
def fallback(value: T | None, fallback0: T | None, fallback1: T | None, default: T, /) -> T:
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@overload
|
|
68
|
+
def fallback(value: T | None, *fallbacks: T | None) -> T | MissingT:
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@overload
|
|
73
|
+
def fallback(value: T | None, *fallbacks: T | None, default: T) -> T:
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def fallback(value: T | None, *fallbacks: T | None, default: Any | T = fallback_missing) -> T | MissingT:
|
|
78
|
+
"""
|
|
79
|
+
Utility function that returns a value or a fallback if the value is None.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
|
|
83
|
+
.. code-block:: python
|
|
84
|
+
|
|
85
|
+
>>> fallback(5, 6)
|
|
86
|
+
5
|
|
87
|
+
>>> fallback(None, 6)
|
|
88
|
+
6
|
|
89
|
+
|
|
90
|
+
:param value: Input value to evaluate. Can be None.
|
|
91
|
+
:param fallback_value: Value to return if the input value is None.
|
|
92
|
+
|
|
93
|
+
:return: Input value or fallback value if input value is None.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
if value is not None:
|
|
97
|
+
return value
|
|
98
|
+
|
|
99
|
+
for fallback in fallbacks:
|
|
100
|
+
if fallback is not None:
|
|
101
|
+
return fallback
|
|
102
|
+
|
|
103
|
+
if default is not fallback_missing:
|
|
104
|
+
return default
|
|
105
|
+
elif len(fallbacks) > 3:
|
|
106
|
+
return MISSING
|
|
107
|
+
|
|
108
|
+
raise CustomRuntimeError('You need to specify a default/fallback value!')
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@overload
|
|
112
|
+
def kwargs_fallback(
|
|
113
|
+
input_value: T | None, kwargs: tuple[KwargsT, str], fallback: T
|
|
114
|
+
) -> T:
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@overload
|
|
119
|
+
def kwargs_fallback(
|
|
120
|
+
input_value: T | None, kwargs: tuple[KwargsT, str], fallback0: T | None, default: T
|
|
121
|
+
) -> T:
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@overload
|
|
126
|
+
def kwargs_fallback(
|
|
127
|
+
input_value: T | None, kwargs: tuple[KwargsT, str], fallback0: T | None, fallback1: T | None,
|
|
128
|
+
default: T
|
|
129
|
+
) -> T:
|
|
130
|
+
...
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@overload
|
|
134
|
+
def kwargs_fallback(
|
|
135
|
+
input_value: T | None, kwargs: tuple[KwargsT, str], *fallbacks: T | None
|
|
136
|
+
) -> T | MissingT:
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@overload
|
|
141
|
+
def kwargs_fallback(
|
|
142
|
+
input_value: T | None, kwargs: tuple[KwargsT, str], *fallbacks: T | None, default: T
|
|
143
|
+
) -> T:
|
|
144
|
+
...
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def kwargs_fallback( # type: ignore
|
|
148
|
+
value: T | None, kwargs: tuple[KwargsT, str], *fallbacks: T | None, default: T = fallback_missing # type: ignore
|
|
149
|
+
) -> T | MissingT:
|
|
150
|
+
"""Utility function to return a fallback value from kwargs if value was not found or is None."""
|
|
151
|
+
|
|
152
|
+
return fallback(value, kwargs[0].get(kwargs[1], None), *fallbacks, default=default)
|