xcept
is a Python package for the convenience of creating exceptions.
pip install xcept
Usually exceptions are created like this:
class Error(Exception): # Base error class of your application or library
pass
class FooError(Error): # Concrete error class
pass
class BarError(Error): # Concrete error class
pass
It looks pretty simple. Let's try to create exceptions with arguments:
class Error(Exception):
def __init__(self, message: str):
self.message = message
def __str__(self):
return self.message
class FooError(Error):
def __init__(self, message: str, a: str):
super().__init__(message=message)
self.a = a
class BarError(Error):
def __init__(self, message: str, b: str, c: int):
super().__init__(message=message)
self.b = b
self.c = c
In the simplest case we have to use super
each time to initialize a new exception. And we also pass an already prepared message.
This does not allow us from getting a modified message when the attributes change:
>>> a = "value"
>>> error = FooError(f"Error (a='{a}')!", a)
>>> raise error
Traceback (most recent call last):
File "<input>", line 1
__main__.FooError: Error (a='value')!
>>>
>>> error.a = "new_value"
>>> raise error
Traceback (most recent call last):
File "<input>", line 1
__main__.FooError: Error (a='value')!
The idea of xcept
is based on use of dataclasses
:
from dataclasses import dataclass
from xcept import Exception_
@dataclass
class Error(Exception_):
pass
@dataclass
class FooError(Error):
a: str
@dataclass
class BarError(Error):
b: str
c: int
The first argument is always a message template with replacement fields:
>>> error = FooError("Error ({a=})!", a="value")
>>> raise error
Traceback (most recent call last):
File "<input>", line 1
__main__.FooError: Error (a='value')!
>>>
>>> error.a = "new_value"
>>> raise error
Traceback (most recent call last):
File "<input>", line 1
__main__.FooError: Error (a='new_value')!
Format syntax is presented here:
https://docs.python.org/3.7/library/string.html#format-string-syntax
Note: Only keyword replacement fields are supported.
Note: Additionally, there is an expression with the =
. It allows you to set a value along with a name:
>>> error = FooError("{a}", a="a_value")
>>> print(error)
a_value
>>>
>>> error = FooError("{a=}", a="a_value")
>>> print(error)
a='a_value'
If a message template does not contain all replacement fields and all replacement fields is required, the MissingFieldWarning
occurs:
>>> error = FooError("Error!", a="value")
<input>:1: MissingFieldWarning: No the replacement field 'a' in the template 'Error!' (FooError)!
>>>
>>> error = BarError("Error ({b=})!", b="value", c="value")
<input>:1: MissingFieldWarning: No the replacement field 'c' in the template 'Error ({b=})!' (BarError)!
>>>
>>> error = BarError("Error!", b="value", c="value")
<input>:1: MissingFieldWarning: No the replacement fields 'b', 'c' in the template 'Error!' (BarError)!
If for some reason you don't need to include all attributes in a message, define ALL_REPLACEMENT_FIELDS_IS_REQUIRED = False
(default True
) to disable checks and warnings:
>>> @dataclass
... class SomeError(Exception_):
... ALL_REPLACEMENT_FIELDS_IS_REQUIRED = False
... a: str
... b: str
...
>>> error = SomeError("Error ({a=})!", a="a_value", b="b_value")
>>> raise error
Traceback (most recent call last):
File "<input>", line 1
__main__.SomeError: Error (a='a_value')!
If a message template contains unknown replacement fields, the UnknownFieldWarning
occurs and the value is set to <UNKNOWN>
:
>>> error = FooError("Error ({a=}, {b=}, {c=})!", a="a_value")
<input>:1: UnknownFieldWarning: Unknown the replacement fields 'b', 'c' in the template 'Error ({a=}, {b=}, {c=})!' (FooError)!
>>> raise error
Traceback (most recent call last):
File "<input>", line 1
__main__.FooError: Error (a='a_value', b=<UNKNOWN>, c=<UNKNOWN>)!
If there is no a message template and all replacement fields is required, the MissingTemplateWarning
occurs:
>>> @dataclass
... class SomeError(Exception_):
... pass
...
>>> error = SomeError(None) # Message template is None
<input>:1: MissingTemplateWarning: No a template (SomeError)!
You can set a default message template:
>>> @dataclass
... class SomeError(Exception_):
... DEFAULT_TEMPLATE = "Default message template ({a=})!"
... a: str
...
>>> raise SomeError(None, a="a_value") # Message template is None
Traceback (most recent call last):
File "<input>", line 1
__main__.SomeError: Default message template (a='a_value')!