muutils.errormode
provides ErrorMode
enum for handling errors consistently
pass an error_mode: ErrorMode
to a function to specify how to handle a certain kind of exception.
That function then instead of raise
ing or warnings.warn
ing, calls error_mode.process
with the message and the exception.
you can also specify the exception class to raise, the warning class to use, and the source of the exception/warning.
1"""provides `ErrorMode` enum for handling errors consistently 2 3pass an `error_mode: ErrorMode` to a function to specify how to handle a certain kind of exception. 4That function then instead of `raise`ing or `warnings.warn`ing, calls `error_mode.process` with the message and the exception. 5 6you can also specify the exception class to raise, the warning class to use, and the source of the exception/warning. 7 8""" 9 10from __future__ import annotations 11 12import sys 13import typing 14import types 15import warnings 16from enum import Enum 17 18 19class WarningFunc(typing.Protocol): 20 def __call__( 21 self, 22 msg: str, 23 category: typing.Type[Warning], 24 source: typing.Any = None, 25 ) -> None: ... 26 27 28LoggingFunc = typing.Callable[[str], None] 29 30GLOBAL_WARN_FUNC: WarningFunc = warnings.warn # type: ignore[assignment] 31GLOBAL_LOG_FUNC: LoggingFunc = print 32 33 34def custom_showwarning( 35 message: Warning | str, 36 category: typing.Type[Warning] | None = None, 37 filename: str | None = None, 38 lineno: int | None = None, 39 file: typing.Optional[typing.TextIO] = None, 40 line: typing.Optional[str] = None, 41) -> None: 42 if category is None: 43 category = UserWarning 44 # Get the frame where process() was called 45 # Adjusted to account for the extra function call 46 frame: types.FrameType = sys._getframe(2) 47 # get globals and traceback 48 traceback: types.TracebackType = types.TracebackType( 49 None, frame, frame.f_lasti, frame.f_lineno 50 ) 51 _globals: dict[str, typing.Any] = frame.f_globals 52 # init the new warning and add the traceback 53 if isinstance(message, str): 54 message = category(message) 55 message = message.with_traceback(traceback) 56 57 # Call the original showwarning function 58 warnings.warn_explicit( 59 message=message, 60 category=category, 61 # filename arg if it's passed, otherwise use the frame's filename 62 filename=frame.f_code.co_filename, 63 lineno=frame.f_lineno, 64 module=frame.f_globals.get("__name__", "__main__"), 65 registry=_globals.setdefault("__warningregistry__", {}), 66 module_globals=_globals, 67 ) 68 # warnings._showwarning_orig( 69 # message, 70 # category, 71 # frame.f_code.co_filename, 72 # frame.f_lineno, 73 # file, 74 # line, 75 # ) 76 77 78class ErrorMode(Enum): 79 """Enum for handling errors consistently 80 81 pass one of the instances of this enum to a function to specify how to handle a certain kind of exception. 82 83 That function then instead of `raise`ing or `warnings.warn`ing, calls `error_mode.process` with the message and the exception. 84 """ 85 86 EXCEPT = "except" 87 WARN = "warn" 88 LOG = "log" 89 IGNORE = "ignore" 90 91 def process( 92 self, 93 msg: str, 94 except_cls: typing.Type[Exception] = ValueError, 95 warn_cls: typing.Type[Warning] = UserWarning, 96 except_from: typing.Optional[Exception] = None, 97 warn_func: WarningFunc | None = None, 98 log_func: LoggingFunc | None = None, 99 ): 100 """process an exception or warning according to the error mode 101 102 # Parameters: 103 - `msg : str` 104 message to pass to `except_cls` or `warn_func` 105 - `except_cls : typing.Type[Exception]` 106 exception class to raise, must be a subclass of `Exception` 107 (defaults to `ValueError`) 108 - `warn_cls : typing.Type[Warning]` 109 warning class to use, must be a subclass of `Warning` 110 (defaults to `UserWarning`) 111 - `except_from : typing.Optional[Exception]` 112 will `raise except_cls(msg) from except_from` if not `None` 113 (defaults to `None`) 114 - `warn_func : WarningFunc | None` 115 function to use for warnings, must have the signature `warn_func(msg: str, category: typing.Type[Warning], source: typing.Any = None) -> None` 116 (defaults to `None`) 117 - `log_func : LoggingFunc | None` 118 function to use for logging, must have the signature `log_func(msg: str) -> None` 119 (defaults to `None`) 120 121 # Raises: 122 - `except_cls` : _description_ 123 - `except_cls` : _description_ 124 - `ValueError` : _description_ 125 """ 126 if self is ErrorMode.EXCEPT: 127 # except, possibly with a chained exception 128 frame: types.FrameType = sys._getframe(1) 129 traceback: types.TracebackType = types.TracebackType( 130 None, frame, frame.f_lasti, frame.f_lineno 131 ) 132 133 # Attach the new traceback to the exception and raise it without the internal call stack 134 if except_from is not None: 135 raise except_cls(msg).with_traceback(traceback) from except_from 136 else: 137 raise except_cls(msg).with_traceback(traceback) 138 elif self is ErrorMode.WARN: 139 # get global warn function if not passed 140 if warn_func is None: 141 warn_func = GLOBAL_WARN_FUNC 142 # augment warning message with source 143 if except_from is not None: 144 msg = f"{msg}\n\tSource of warning: {except_from}" 145 if warn_func == warnings.warn: 146 custom_showwarning(msg, category=warn_cls) 147 else: 148 # Use the provided warn_func as-is 149 warn_func(msg, category=warn_cls) 150 elif self is ErrorMode.LOG: 151 # get global log function if not passed 152 if log_func is None: 153 log_func = GLOBAL_LOG_FUNC 154 # log 155 log_func(msg) 156 elif self is ErrorMode.IGNORE: 157 # do nothing 158 pass 159 else: 160 raise ValueError(f"Unknown error mode {self}") 161 162 @classmethod 163 def from_any( 164 cls, 165 mode: "str|ErrorMode", 166 allow_aliases: bool = True, 167 allow_prefix: bool = True, 168 ) -> ErrorMode: 169 """initialize an `ErrorMode` from a string or an `ErrorMode` instance""" 170 if isinstance(mode, ErrorMode): 171 return mode 172 elif isinstance(mode, str): 173 # strip 174 mode = mode.strip() 175 176 # remove prefix 177 if allow_prefix and mode.startswith("ErrorMode."): 178 mode = mode[len("ErrorMode.") :] 179 180 # lowercase and strip again 181 mode = mode.strip().lower() 182 183 if not allow_aliases: 184 # try without aliases 185 try: 186 return ErrorMode(mode) 187 except ValueError as e: 188 raise KeyError(f"Unknown error mode {mode = }") from e 189 else: 190 # look up in aliases map 191 return ERROR_MODE_ALIASES[mode] 192 else: 193 raise TypeError( 194 f"Expected {ErrorMode = } or str, got {type(mode) = } {mode = }" 195 ) 196 197 def __str__(self) -> str: 198 return f"ErrorMode.{self.value.capitalize()}" 199 200 def __repr__(self) -> str: 201 return str(self) 202 203 def serialize(self) -> str: 204 return str(self) 205 206 @classmethod 207 def load(cls, data: str) -> ErrorMode: 208 return cls.from_any( 209 data, 210 allow_aliases=False, 211 allow_prefix=True, 212 ) 213 214 215ERROR_MODE_ALIASES: dict[str, ErrorMode] = { 216 # base 217 "except": ErrorMode.EXCEPT, 218 "warn": ErrorMode.WARN, 219 "log": ErrorMode.LOG, 220 "ignore": ErrorMode.IGNORE, 221 # except 222 "e": ErrorMode.EXCEPT, 223 "error": ErrorMode.EXCEPT, 224 "err": ErrorMode.EXCEPT, 225 "raise": ErrorMode.EXCEPT, 226 # warn 227 "w": ErrorMode.WARN, 228 "warning": ErrorMode.WARN, 229 # log 230 "l": ErrorMode.LOG, 231 "print": ErrorMode.LOG, 232 "output": ErrorMode.LOG, 233 "show": ErrorMode.LOG, 234 "display": ErrorMode.LOG, 235 # ignore 236 "i": ErrorMode.IGNORE, 237 "silent": ErrorMode.IGNORE, 238 "quiet": ErrorMode.IGNORE, 239 "nothing": ErrorMode.IGNORE, 240} 241"map of string aliases to `ErrorMode` instances"
20class WarningFunc(typing.Protocol): 21 def __call__( 22 self, 23 msg: str, 24 category: typing.Type[Warning], 25 source: typing.Any = None, 26 ) -> None: ...
Base class for protocol classes.
Protocol classes are defined as::
class Proto(Protocol):
def meth(self) -> int:
...
Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).
For example::
class C:
def meth(self) -> int:
return 0
def func(x: Proto) -> int:
return x.meth()
func(C()) # Passes static type check
See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::
class GenProto[T](Protocol):
def meth(self) -> T:
...
1767def _no_init_or_replace_init(self, *args, **kwargs): 1768 cls = type(self) 1769 1770 if cls._is_protocol: 1771 raise TypeError('Protocols cannot be instantiated') 1772 1773 # Already using a custom `__init__`. No need to calculate correct 1774 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1775 if cls.__init__ is not _no_init_or_replace_init: 1776 return 1777 1778 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1779 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1780 # searches for a proper new `__init__` in the MRO. The new `__init__` 1781 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1782 # instantiation of the protocol subclass will thus use the new 1783 # `__init__` and no longer call `_no_init_or_replace_init`. 1784 for base in cls.__mro__: 1785 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1786 if init is not _no_init_or_replace_init: 1787 cls.__init__ = init 1788 break 1789 else: 1790 # should not happen 1791 cls.__init__ = object.__init__ 1792 1793 cls.__init__(self, *args, **kwargs)
Issue a warning, or maybe ignore it or raise an exception.
message Text of the warning message. category The Warning category subclass. Defaults to UserWarning. stacklevel How far up the call stack to make this warning appear. A value of 2 for example attributes the warning to the caller of the code calling warn(). source If supplied, the destroyed object which emitted a ResourceWarning skip_file_prefixes An optional tuple of module filename prefixes indicating frames to skip during stacklevel computations for stack frame attribution.
Prints the values to a stream, or to sys.stdout by default.
sep string inserted between values, default a space. end string appended after the last value, default a newline. file a file-like object (stream); defaults to the current sys.stdout. flush whether to forcibly flush the stream.
35def custom_showwarning( 36 message: Warning | str, 37 category: typing.Type[Warning] | None = None, 38 filename: str | None = None, 39 lineno: int | None = None, 40 file: typing.Optional[typing.TextIO] = None, 41 line: typing.Optional[str] = None, 42) -> None: 43 if category is None: 44 category = UserWarning 45 # Get the frame where process() was called 46 # Adjusted to account for the extra function call 47 frame: types.FrameType = sys._getframe(2) 48 # get globals and traceback 49 traceback: types.TracebackType = types.TracebackType( 50 None, frame, frame.f_lasti, frame.f_lineno 51 ) 52 _globals: dict[str, typing.Any] = frame.f_globals 53 # init the new warning and add the traceback 54 if isinstance(message, str): 55 message = category(message) 56 message = message.with_traceback(traceback) 57 58 # Call the original showwarning function 59 warnings.warn_explicit( 60 message=message, 61 category=category, 62 # filename arg if it's passed, otherwise use the frame's filename 63 filename=frame.f_code.co_filename, 64 lineno=frame.f_lineno, 65 module=frame.f_globals.get("__name__", "__main__"), 66 registry=_globals.setdefault("__warningregistry__", {}), 67 module_globals=_globals, 68 ) 69 # warnings._showwarning_orig( 70 # message, 71 # category, 72 # frame.f_code.co_filename, 73 # frame.f_lineno, 74 # file, 75 # line, 76 # )
79class ErrorMode(Enum): 80 """Enum for handling errors consistently 81 82 pass one of the instances of this enum to a function to specify how to handle a certain kind of exception. 83 84 That function then instead of `raise`ing or `warnings.warn`ing, calls `error_mode.process` with the message and the exception. 85 """ 86 87 EXCEPT = "except" 88 WARN = "warn" 89 LOG = "log" 90 IGNORE = "ignore" 91 92 def process( 93 self, 94 msg: str, 95 except_cls: typing.Type[Exception] = ValueError, 96 warn_cls: typing.Type[Warning] = UserWarning, 97 except_from: typing.Optional[Exception] = None, 98 warn_func: WarningFunc | None = None, 99 log_func: LoggingFunc | None = None, 100 ): 101 """process an exception or warning according to the error mode 102 103 # Parameters: 104 - `msg : str` 105 message to pass to `except_cls` or `warn_func` 106 - `except_cls : typing.Type[Exception]` 107 exception class to raise, must be a subclass of `Exception` 108 (defaults to `ValueError`) 109 - `warn_cls : typing.Type[Warning]` 110 warning class to use, must be a subclass of `Warning` 111 (defaults to `UserWarning`) 112 - `except_from : typing.Optional[Exception]` 113 will `raise except_cls(msg) from except_from` if not `None` 114 (defaults to `None`) 115 - `warn_func : WarningFunc | None` 116 function to use for warnings, must have the signature `warn_func(msg: str, category: typing.Type[Warning], source: typing.Any = None) -> None` 117 (defaults to `None`) 118 - `log_func : LoggingFunc | None` 119 function to use for logging, must have the signature `log_func(msg: str) -> None` 120 (defaults to `None`) 121 122 # Raises: 123 - `except_cls` : _description_ 124 - `except_cls` : _description_ 125 - `ValueError` : _description_ 126 """ 127 if self is ErrorMode.EXCEPT: 128 # except, possibly with a chained exception 129 frame: types.FrameType = sys._getframe(1) 130 traceback: types.TracebackType = types.TracebackType( 131 None, frame, frame.f_lasti, frame.f_lineno 132 ) 133 134 # Attach the new traceback to the exception and raise it without the internal call stack 135 if except_from is not None: 136 raise except_cls(msg).with_traceback(traceback) from except_from 137 else: 138 raise except_cls(msg).with_traceback(traceback) 139 elif self is ErrorMode.WARN: 140 # get global warn function if not passed 141 if warn_func is None: 142 warn_func = GLOBAL_WARN_FUNC 143 # augment warning message with source 144 if except_from is not None: 145 msg = f"{msg}\n\tSource of warning: {except_from}" 146 if warn_func == warnings.warn: 147 custom_showwarning(msg, category=warn_cls) 148 else: 149 # Use the provided warn_func as-is 150 warn_func(msg, category=warn_cls) 151 elif self is ErrorMode.LOG: 152 # get global log function if not passed 153 if log_func is None: 154 log_func = GLOBAL_LOG_FUNC 155 # log 156 log_func(msg) 157 elif self is ErrorMode.IGNORE: 158 # do nothing 159 pass 160 else: 161 raise ValueError(f"Unknown error mode {self}") 162 163 @classmethod 164 def from_any( 165 cls, 166 mode: "str|ErrorMode", 167 allow_aliases: bool = True, 168 allow_prefix: bool = True, 169 ) -> ErrorMode: 170 """initialize an `ErrorMode` from a string or an `ErrorMode` instance""" 171 if isinstance(mode, ErrorMode): 172 return mode 173 elif isinstance(mode, str): 174 # strip 175 mode = mode.strip() 176 177 # remove prefix 178 if allow_prefix and mode.startswith("ErrorMode."): 179 mode = mode[len("ErrorMode.") :] 180 181 # lowercase and strip again 182 mode = mode.strip().lower() 183 184 if not allow_aliases: 185 # try without aliases 186 try: 187 return ErrorMode(mode) 188 except ValueError as e: 189 raise KeyError(f"Unknown error mode {mode = }") from e 190 else: 191 # look up in aliases map 192 return ERROR_MODE_ALIASES[mode] 193 else: 194 raise TypeError( 195 f"Expected {ErrorMode = } or str, got {type(mode) = } {mode = }" 196 ) 197 198 def __str__(self) -> str: 199 return f"ErrorMode.{self.value.capitalize()}" 200 201 def __repr__(self) -> str: 202 return str(self) 203 204 def serialize(self) -> str: 205 return str(self) 206 207 @classmethod 208 def load(cls, data: str) -> ErrorMode: 209 return cls.from_any( 210 data, 211 allow_aliases=False, 212 allow_prefix=True, 213 )
Enum for handling errors consistently
pass one of the instances of this enum to a function to specify how to handle a certain kind of exception.
That function then instead of raise
ing or warnings.warn
ing, calls error_mode.process
with the message and the exception.
92 def process( 93 self, 94 msg: str, 95 except_cls: typing.Type[Exception] = ValueError, 96 warn_cls: typing.Type[Warning] = UserWarning, 97 except_from: typing.Optional[Exception] = None, 98 warn_func: WarningFunc | None = None, 99 log_func: LoggingFunc | None = None, 100 ): 101 """process an exception or warning according to the error mode 102 103 # Parameters: 104 - `msg : str` 105 message to pass to `except_cls` or `warn_func` 106 - `except_cls : typing.Type[Exception]` 107 exception class to raise, must be a subclass of `Exception` 108 (defaults to `ValueError`) 109 - `warn_cls : typing.Type[Warning]` 110 warning class to use, must be a subclass of `Warning` 111 (defaults to `UserWarning`) 112 - `except_from : typing.Optional[Exception]` 113 will `raise except_cls(msg) from except_from` if not `None` 114 (defaults to `None`) 115 - `warn_func : WarningFunc | None` 116 function to use for warnings, must have the signature `warn_func(msg: str, category: typing.Type[Warning], source: typing.Any = None) -> None` 117 (defaults to `None`) 118 - `log_func : LoggingFunc | None` 119 function to use for logging, must have the signature `log_func(msg: str) -> None` 120 (defaults to `None`) 121 122 # Raises: 123 - `except_cls` : _description_ 124 - `except_cls` : _description_ 125 - `ValueError` : _description_ 126 """ 127 if self is ErrorMode.EXCEPT: 128 # except, possibly with a chained exception 129 frame: types.FrameType = sys._getframe(1) 130 traceback: types.TracebackType = types.TracebackType( 131 None, frame, frame.f_lasti, frame.f_lineno 132 ) 133 134 # Attach the new traceback to the exception and raise it without the internal call stack 135 if except_from is not None: 136 raise except_cls(msg).with_traceback(traceback) from except_from 137 else: 138 raise except_cls(msg).with_traceback(traceback) 139 elif self is ErrorMode.WARN: 140 # get global warn function if not passed 141 if warn_func is None: 142 warn_func = GLOBAL_WARN_FUNC 143 # augment warning message with source 144 if except_from is not None: 145 msg = f"{msg}\n\tSource of warning: {except_from}" 146 if warn_func == warnings.warn: 147 custom_showwarning(msg, category=warn_cls) 148 else: 149 # Use the provided warn_func as-is 150 warn_func(msg, category=warn_cls) 151 elif self is ErrorMode.LOG: 152 # get global log function if not passed 153 if log_func is None: 154 log_func = GLOBAL_LOG_FUNC 155 # log 156 log_func(msg) 157 elif self is ErrorMode.IGNORE: 158 # do nothing 159 pass 160 else: 161 raise ValueError(f"Unknown error mode {self}")
process an exception or warning according to the error mode
Parameters:
msg : str
message to pass toexcept_cls
orwarn_func
except_cls : typing.Type[Exception]
exception class to raise, must be a subclass ofException
(defaults toValueError
)warn_cls : typing.Type[Warning]
warning class to use, must be a subclass ofWarning
(defaults toUserWarning
)except_from : typing.Optional[Exception]
willraise except_cls(msg) from except_from
if notNone
(defaults toNone
)warn_func : WarningFunc | None
function to use for warnings, must have the signaturewarn_func(msg: str, category: typing.Type[Warning], source: typing.Any = None) -> None
(defaults toNone
)log_func : LoggingFunc | None
function to use for logging, must have the signaturelog_func(msg: str) -> None
(defaults toNone
)
Raises:
except_cls
: _description_except_cls
: _description_ValueError
: _description_
163 @classmethod 164 def from_any( 165 cls, 166 mode: "str|ErrorMode", 167 allow_aliases: bool = True, 168 allow_prefix: bool = True, 169 ) -> ErrorMode: 170 """initialize an `ErrorMode` from a string or an `ErrorMode` instance""" 171 if isinstance(mode, ErrorMode): 172 return mode 173 elif isinstance(mode, str): 174 # strip 175 mode = mode.strip() 176 177 # remove prefix 178 if allow_prefix and mode.startswith("ErrorMode."): 179 mode = mode[len("ErrorMode.") :] 180 181 # lowercase and strip again 182 mode = mode.strip().lower() 183 184 if not allow_aliases: 185 # try without aliases 186 try: 187 return ErrorMode(mode) 188 except ValueError as e: 189 raise KeyError(f"Unknown error mode {mode = }") from e 190 else: 191 # look up in aliases map 192 return ERROR_MODE_ALIASES[mode] 193 else: 194 raise TypeError( 195 f"Expected {ErrorMode = } or str, got {type(mode) = } {mode = }" 196 )
Inherited Members
- enum.Enum
- name
- value
map of string aliases to ErrorMode
instances