Custom exceptions in unittests - python

I have created my custom exceptions as such within errors.py
mapper = {
'E101':
'There is no data at all for these constraints',
'E102':
'There is no data for these constraints in this market, try changing market',
'E103':
'There is no data for these constraints during these dates, try changing dates',
}
class DataException(Exception):
def __init__(self, code):
super().__init__()
self.msg = mapper[code]
def __str__(self):
return self.msg
Another function somewhere else in the code raises different instances of DataException if there is not enough data in a pandas dataframe. I want to use unittest to ensure that it returns the appropriate exception with its corresponding message.
Using a simple example, why does this not work:
from .. import DataException
def foobar():
raise DataException('E101')
import unittest
with unittest.TestCase.assertRaises(DataException):
foobar()
As suggested here: Python assertRaises on user-defined exceptions
I get this error:
TypeError: assertRaises() missing 1 required positional argument: 'expected_exception'
Or alternatively:
def foobar():
raise DataException('E101')
import unittest
unittest.TestCase.assertRaises(DataException, foobar)
results in:
TypeError: assertRaises() arg 1 must be an exception type or tuple of exception types
Why is it not recognizing DataException as an Exception? Why does the linked stackoverflow question answer work without supplying a second argument to assertRaises?

You are trying to use methods of the TestCase class without creating an instance; those methods are not designed to be used in that manner.
unittest.TestCase.assertRaises is an unbound method. You'd use it in a test method on a TestCase class you define:
class DemoTestCase(unittest.TestCase):
def test_foobar(self):
with self.assertRaises(DataException):
foobar()
The error is raised because unbound methods do not get self passed in. Because unittest.TestCase.assertRaises expects both self and a second argument named expected_exception you get an exception as DataException is passed in as the value for self.
You do now have to use a test runner to manage your test cases; add
if __name__ == '__main__':
unittest.main()
at the bottom and run your file as a script. Your test cases are then auto-discovered and executed.
It is technically possible to use the assertions outside such an environment, see Is there a way to use Python unit test assertions outside of a TestCase?, but I recommend you stick to creating test cases instead.
To further verify the codes and message on the raised exception, assign the value returned when entering the context to a new name with with ... as <target>:; the context manager object captures the raised exception so you can make assertions about it:
with self.assertRaises(DataException) as context:
foobar()
self.assertEqual(context.exception.code, 'E101')
self.assertEqual(
context.exception.msg,
'There is no data at all for these constraints')
See the TestCase.assertRaises() documentation.
Last but not least, consider using subclasses of DataException rather than use separate error codes. That way your API users can just catch one of those subclasses to handle a specific error code, rather than having to do additional tests for the code and re-raise if a specific code should not have been handled there.

Related

Proper way of making a Python file with custom exceptions [duplicate]

What's the proper way to declare custom exception classes in modern Python? My primary goal is to follow whatever standard other exception classes have, so that (for instance) any extra string I include in the exception is printed out by whatever tool caught the exception.
By "modern Python" I mean something that will run in Python 2.5 but be 'correct' for the Python 2.6 and Python 3.* way of doing things. And by "custom" I mean an Exception object that can include extra data about the cause of the error: a string, maybe also some other arbitrary object relevant to the exception.
I was tripped up by the following deprecation warning in Python 2.6.2:
>>> class MyError(Exception):
... def __init__(self, message):
... self.message = message
...
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
It seems crazy that BaseException has a special meaning for attributes named message. I gather from PEP-352 that attribute did have a special meaning in 2.5 they're trying to deprecate away, so I guess that name (and that one alone) is now forbidden? Ugh.
I'm also fuzzily aware that Exception has some magic parameter args, but I've never known how to use it. Nor am I sure it's the right way to do things going forward; a lot of the discussion I found online suggested they were trying to do away with args in Python 3.
Update: two answers have suggested overriding __init__, and __str__/__unicode__/__repr__. That seems like a lot of typing, is it necessary?
Maybe I missed the question, but why not:
class MyException(Exception):
pass
To override something (or pass extra args), do this:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super().__init__(message)
# Now for your custom code...
self.errors = errors
That way you could pass dict of error messages to the second param, and get to it later with e.errors.
In Python 2, you have to use this slightly more complex form of super():
super(ValidationError, self).__init__(message)
With modern Python Exceptions, you don't need to abuse .message, or override .__str__() or .__repr__() or any of it. If all you want is an informative message when your exception is raised, do this:
class MyException(Exception):
pass
raise MyException("My hovercraft is full of eels")
That will give a traceback ending with MyException: My hovercraft is full of eels.
If you want more flexibility from the exception, you could pass a dictionary as the argument:
raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
However, to get at those details in an except block is a bit more complicated. The details are stored in the args attribute, which is a list. You would need to do something like this:
try:
raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
details = e.args[0]
print(details["animal"])
It is still possible to pass in multiple items to the exception and access them via tuple indexes, but this is highly discouraged (and was even intended for deprecation a while back). If you do need more than a single piece of information and the above method is not sufficient for you, then you should subclass Exception as described in the tutorial.
class MyError(Exception):
def __init__(self, message, animal):
self.message = message
self.animal = animal
def __str__(self):
return self.message
"What is the proper way to declare custom exceptions in modern Python?"
This is fine unless your exception is really a type of a more specific exception:
class MyException(Exception):
pass
Or better (maybe perfect), instead of pass give a docstring:
class MyException(Exception):
"""Raise for my specific kind of exception"""
Subclassing Exception Subclasses
From the docs
Exception
All built-in, non-system-exiting exceptions are derived from this class.
All user-defined exceptions should also be derived from this
class.
That means that if your exception is a type of a more specific exception, subclass that exception instead of the generic Exception (and the result will be that you still derive from Exception as the docs recommend). Also, you can at least provide a docstring (and not be forced to use the pass keyword):
class MyAppValueError(ValueError):
'''Raise when my specific value is wrong'''
Set attributes you create yourself with a custom __init__. Avoid passing a dict as a positional argument, future users of your code will thank you. If you use the deprecated message attribute, assigning it yourself will avoid a DeprecationWarning:
class MyAppValueError(ValueError):
'''Raise when a specific subset of values in context of app is wrong'''
def __init__(self, message, foo, *args):
self.message = message # without this you may get DeprecationWarning
# Special attribute you desire with your Error,
# perhaps the value that caused the error?:
self.foo = foo
# allow users initialize misc. arguments as any other builtin Error
super(MyAppValueError, self).__init__(message, foo, *args)
There's really no need to write your own __str__ or __repr__. The built-in ones are very nice, and your cooperative inheritance ensures that you use them.
Critique of the top answer
Maybe I missed the question, but why not:
class MyException(Exception):
pass
Again, the problem with the above is that in order to catch it, you'll either have to name it specifically (importing it if created elsewhere) or catch Exception, (but you're probably not prepared to handle all types of Exceptions, and you should only catch exceptions you are prepared to handle). Similar criticism to the below, but additionally that's not the way to initialize via super, and you'll get a DeprecationWarning if you access the message attribute:
Edit: to override something (or pass extra args), do this:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super(ValidationError, self).__init__(message)
# Now for your custom code...
self.errors = errors
That way you could pass dict of error messages to the second param, and get to it later with e.errors
It also requires exactly two arguments to be passed in (aside from the self.) No more, no less. That's an interesting constraint that future users may not appreciate.
To be direct - it violates Liskov substitutability.
I'll demonstrate both errors:
>>> ValidationError('foo', 'bar', 'baz').message
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
ValidationError('foo', 'bar', 'baz').message
TypeError: __init__() takes exactly 3 arguments (4 given)
>>> ValidationError('foo', 'bar').message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foo'
Compared to:
>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'
see how exceptions work by default if one vs more attributes are used (tracebacks omitted):
>>> raise Exception('bad thing happened')
Exception: bad thing happened
>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')
so you might want to have a sort of "exception template", working as an exception itself, in a compatible way:
>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened
>>> raise nastyerr()
NastyError: bad thing happened
>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')
this can be done easily with this subclass
class ExceptionTemplate(Exception):
def __call__(self, *args):
return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass
and if you don't like that default tuple-like representation, just add __str__ method to the ExceptionTemplate class, like:
# ...
def __str__(self):
return ': '.join(self.args)
and you'll have
>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken
As of Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html), the recommended method is still:
class CustomExceptionName(Exception):
"""Exception raised when very uncommon things happen"""
pass
Please don't forget to document, why a custom exception is neccessary!
If you need to, this is the way to go for exceptions with more data:
class CustomExceptionName(Exception):
"""Still an exception raised when uncommon things happen"""
def __init__(self, message, payload=None):
self.message = message
self.payload = payload # you could add more args
def __str__(self):
return str(self.message) # __str__() obviously expects a string to be returned, so make sure not to send any other data types
and fetch them like:
try:
raise CustomExceptionName("Very bad mistake.", "Forgot upgrading from Python 1")
except CustomExceptionName as error:
print(str(error)) # Very bad mistake
print("Detail: {}".format(error.payload)) # Detail: Forgot upgrading from Python 1
payload=None is important to make it pickle-able. Before dumping it, you have to call error.__reduce__(). Loading will work as expected.
You maybe should investigate in finding a solution using pythons return statement if you need much data to be transferred to some outer structure. This seems to be clearer/more pythonic to me. Advanced exceptions are heavily used in Java, which can sometimes be annoying, when using a framework and having to catch all possible errors.
To define your own exceptions correctly, there are a few best practices that you should follow:
Define a base class inheriting from Exception. This will allow to easily catch any exceptions related to the project:
class MyProjectError(Exception):
"""A base class for MyProject exceptions."""
Organizing the exception classes in a separate module (e.g. exceptions.py) is generally a good idea.
To create a specific exception, subclass the base exception class.
class CustomError(MyProjectError):
"""A custom exception class for MyProject."""
You can subclass custom exception classes as well to create a hierarchy.
To add support for extra argument(s) to a custom exception, define an __init__() method with a variable number of arguments. Call the base class's __init__(), passing any positional arguments to it (remember that BaseException/Exception expect any number of positional arguments). Store extra keyword arguments to the instance, e.g.:
class CustomError(MyProjectError):
def __init__(self, *args, **kwargs):
super().__init__(*args)
self.custom_kwarg = kwargs.get('custom_kwargs')
Usage example:
try:
raise CustomError('Something bad happened', custom_kwarg='value')
except CustomError as exc:
print(f'Сaught CustomError exception with custom_kwarg={exc.custom_kwarg}')
This design adheres to the Liskov substitution principle, since you can replace an instance of a base exception class with an instance of a derived exception class. Also, it allows you to create an instance of a derived class with the same parameters as the parent.
You should override __repr__ or __unicode__ methods instead of using message, the args you provide when you construct the exception will be in the args attribute of the exception object.
See a very good article "The definitive guide to Python exceptions". The basic principles are:
Always inherit from (at least) Exception.
Always call BaseException.__init__ with only one argument.
When building a library, define a base class inheriting from Exception.
Provide details about the error.
Inherit from builtin exceptions types when it makes sense.
There is also information on organizing (in modules) and wrapping exceptions, I recommend to read the guide.
No, "message" is not forbidden. It's just deprecated. You application will work fine with using message. But you may want to get rid of the deprecation error, of course.
When you create custom Exception classes for your application, many of them do not subclass just from Exception, but from others, like ValueError or similar. Then you have to adapt to their usage of variables.
And if you have many exceptions in your application it's usually a good idea to have a common custom base class for all of them, so that users of your modules can do
try:
...
except NelsonsExceptions:
...
And in that case you can do __init__ and __str__ needed there, so you don't have to repeat it for every exception. But simply calling the message variable something else than message does the trick.
In any case, you only need __init__ or __str__ if you do something different from what Exception itself does. And because if the deprecation, you then need both, or you get an error. That's not a whole lot of extra code you need per class.
For maximum customisation, to define custom errors, you may want to define an intermediate class that inherits from Exception class as:
class BaseCustomException(Exception):
def __init__(self, msg):
self.msg = msg
def __repr__(self):
return self.msg
class MyCustomError(BaseCustomException):
"""raise my custom error"""
Try this Example
class InvalidInputError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return repr(self.msg)
inp = int(input("Enter a number between 1 to 10:"))
try:
if type(inp) != int or inp not in list(range(1,11)):
raise InvalidInputError
except InvalidInputError:
print("Invalid input entered")
A really simple approach:
class CustomError(Exception):
pass
raise CustomError("Hmm, seems like this was custom coded...")
Or, have the error raise without printing __main__ (may look cleaner and neater):
class CustomError(Exception):
__module__ = Exception.__module__
raise CustomError("Improved CustomError!")
I had issues with the above methods, as of Python 3.9.5.
However, I found that this works for me:
class MyException(Exception):
"""Port Exception"""
And then it could be used in code like:
try:
raise MyException('Message')
except MyException as err:
print (err)
I came across this thread. This is how I do custom exceptions. While the Fault class is slightly complex, it makes declaring custom expressive exceptions with variable arguments trivial.
FinalViolation, SingletonViolation are both sub classes of TypeError so will be caught code below.
try:
<do something>
except TypeError as ex:
<handler>
That's why Fault doesn't inherit from Exception. To allow derivative exceptions to inherit from the exception of their choice.
class Fault:
"""Generic Exception base class. Note not descendant of Exception
Inheriting exceptions override formats"""
formats = '' # to be overriden in descendant classes
def __init__(self, *args):
"""Just save args for __str__"""
self.args = args
def __str__(self):
"""Use formats declared in descendant classes, and saved args to build exception text"""
return self.formats.format(*self.args)
class TypeFault(Fault, TypeError):
"""Helper class mixing Fault and TypeError"""
class FinalViolation(TypeFault):
"""Custom exception raised if inheriting from 'final' class"""
formats = "type {} is not an acceptable base type. It cannot be inherited from."
class SingletonViolation(TypeFault):
"""Custom exception raised if instancing 'singleton' class a second time"""
formats = "type {} is a singleton. It can only be instanced once."
FinalViolation, SingletonViolation unfortunately only accept 1 argument.
But one could easily create a multi arg error e.g.
class VesselLoadingError(Fault, BufferError):
formats = "My {} is full of {}."
raise VesselLoadingError('hovercraft', 'eels')
__main__.VesselLoadingError: My hovercraft is full of eels.
For me it is just __init__ and variables but making sometimes testing.
My sample:
Error_codes = { 100: "Not enough parameters", 101: "Number of special characters more than limits", 102: "At least 18 alphanumeric characters and list of special chars !##$&*" }
class localbreak( Exception ) :
Message = ""
def __init__(self, Message):
self.Message = Message
return
def __str__(self):
print(self.Message)
return "False"
### When calling ...
raise localbreak(Error_codes[102])
Output:
Traceback (most recent call last): File "ASCII.py", line 150, in <module>
main(OldPassword, Newpassword) File "ASCII.py", line 39, in main
result = read_input("1", "2", Newpassword, "4")
File "ASCII.py", line 69, in read_input
raise localbreak(Error_codes[102]) At least 18 alphanumeric characters and list of special chars !##$&*
__main__.localbreak: False

How to spec a Python mock which defaults to AttributeError() without explicit assignment?

I have a question related to Python unittest.mock.Mock and spec_set functionalities.
My goal is to create a Mock with the following functionalities:
It has a spec of an arbitrary class I decide at creation time.
I must be able to assign on the mock only attributes or methods according to the spec of point 1
The Mock must raise AttributeError in the following situations:
I try to assign an attribute that is not in the spec
I call or retrieve a property that is either missing in the spec_set, or present in the spec_set but assigned according to the above point.
Some examples of the behavior I would like:
class MyClass:
property: int = 5
def func() -> int:
pass
# MySpecialMock is the Mock with the functionalities I am dreaming about :D
mock = MyMySpecialMock(spec_set=MyClass)
mock.not_existing # Raise AttributeError
mock.func() # Raise AttributeError
mock.func = lambda: "it works"
mock.func() # Returns "it works"
I have tried multiple solutions without any luck, or without being explicitly verbose. The following are some examples:
Using Mock(spec_set=...), but it does not raise errors in case I call a specced attribute which I did not explicitly set
Using Mock(spec_set=...) and explicitly override every attribute with a function with an Exception side effect, but it is quite verbose since I must repeat all the attributes...
My goal is to find a way to automatize 2, but I have no clean way to do so. Did you ever encounter such a problem, and solve it?
For the curious ones, the goal is being able to enhance the separation of unit testings; I want to be sure that my mocks are called only on the methods I explicitly set, to avoid weird and unexpected side effects.
Thank you in advance!
spec_set defines a mock object which is the same as the class, but then doesn't allow any changes to be made to it, since it defines special __getattr__ and __setattr__. This means that the first test (calling a non-existent attr) will fail as expected, but then so will trying to set an attr:
from unitest import mock
class X:
pass
m = mock.Mock(spec_set=X)
m.func()
# __getattr__: AttributeError: Mock object has no attribute 'func'
m.func = lambda: "it works"
# __setattr__: AttributeError: Mock object has no attribute 'func'
Instead, you can use create_autospec() which copies an existing function, and adds the mock functions to it, but without affecting __setattr__:
n = mock.create_autospec(X)
n.func()
# __getattr__: AttributeError: Mock object has no attribute 'func'
n.func = lambda: "it works"
n.func()
# 'it works'
I think I found a satisfying answer to my problem, by using the dir method.
To create the Mock with the requirements I listed above, it should be enough to do the following:
def create_mock(spec: Any) -> Mock:
mock = Mock(spec_set=spec)
attributes_to_override = dir(spec)
for attr in filter(lambda name: not name.startswith("__"), attributes_to_override):
setattr(mock, attr, Mock(side_effect=AttributeError(f"{attr} not implemented")))
return mock

How to correctly process arguments to avoid raising an Exception? [duplicate]

What's the proper way to declare custom exception classes in modern Python? My primary goal is to follow whatever standard other exception classes have, so that (for instance) any extra string I include in the exception is printed out by whatever tool caught the exception.
By "modern Python" I mean something that will run in Python 2.5 but be 'correct' for the Python 2.6 and Python 3.* way of doing things. And by "custom" I mean an Exception object that can include extra data about the cause of the error: a string, maybe also some other arbitrary object relevant to the exception.
I was tripped up by the following deprecation warning in Python 2.6.2:
>>> class MyError(Exception):
... def __init__(self, message):
... self.message = message
...
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
It seems crazy that BaseException has a special meaning for attributes named message. I gather from PEP-352 that attribute did have a special meaning in 2.5 they're trying to deprecate away, so I guess that name (and that one alone) is now forbidden? Ugh.
I'm also fuzzily aware that Exception has some magic parameter args, but I've never known how to use it. Nor am I sure it's the right way to do things going forward; a lot of the discussion I found online suggested they were trying to do away with args in Python 3.
Update: two answers have suggested overriding __init__, and __str__/__unicode__/__repr__. That seems like a lot of typing, is it necessary?
Maybe I missed the question, but why not:
class MyException(Exception):
pass
To override something (or pass extra args), do this:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super().__init__(message)
# Now for your custom code...
self.errors = errors
That way you could pass dict of error messages to the second param, and get to it later with e.errors.
In Python 2, you have to use this slightly more complex form of super():
super(ValidationError, self).__init__(message)
With modern Python Exceptions, you don't need to abuse .message, or override .__str__() or .__repr__() or any of it. If all you want is an informative message when your exception is raised, do this:
class MyException(Exception):
pass
raise MyException("My hovercraft is full of eels")
That will give a traceback ending with MyException: My hovercraft is full of eels.
If you want more flexibility from the exception, you could pass a dictionary as the argument:
raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
However, to get at those details in an except block is a bit more complicated. The details are stored in the args attribute, which is a list. You would need to do something like this:
try:
raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
details = e.args[0]
print(details["animal"])
It is still possible to pass in multiple items to the exception and access them via tuple indexes, but this is highly discouraged (and was even intended for deprecation a while back). If you do need more than a single piece of information and the above method is not sufficient for you, then you should subclass Exception as described in the tutorial.
class MyError(Exception):
def __init__(self, message, animal):
self.message = message
self.animal = animal
def __str__(self):
return self.message
"What is the proper way to declare custom exceptions in modern Python?"
This is fine unless your exception is really a type of a more specific exception:
class MyException(Exception):
pass
Or better (maybe perfect), instead of pass give a docstring:
class MyException(Exception):
"""Raise for my specific kind of exception"""
Subclassing Exception Subclasses
From the docs
Exception
All built-in, non-system-exiting exceptions are derived from this class.
All user-defined exceptions should also be derived from this
class.
That means that if your exception is a type of a more specific exception, subclass that exception instead of the generic Exception (and the result will be that you still derive from Exception as the docs recommend). Also, you can at least provide a docstring (and not be forced to use the pass keyword):
class MyAppValueError(ValueError):
'''Raise when my specific value is wrong'''
Set attributes you create yourself with a custom __init__. Avoid passing a dict as a positional argument, future users of your code will thank you. If you use the deprecated message attribute, assigning it yourself will avoid a DeprecationWarning:
class MyAppValueError(ValueError):
'''Raise when a specific subset of values in context of app is wrong'''
def __init__(self, message, foo, *args):
self.message = message # without this you may get DeprecationWarning
# Special attribute you desire with your Error,
# perhaps the value that caused the error?:
self.foo = foo
# allow users initialize misc. arguments as any other builtin Error
super(MyAppValueError, self).__init__(message, foo, *args)
There's really no need to write your own __str__ or __repr__. The built-in ones are very nice, and your cooperative inheritance ensures that you use them.
Critique of the top answer
Maybe I missed the question, but why not:
class MyException(Exception):
pass
Again, the problem with the above is that in order to catch it, you'll either have to name it specifically (importing it if created elsewhere) or catch Exception, (but you're probably not prepared to handle all types of Exceptions, and you should only catch exceptions you are prepared to handle). Similar criticism to the below, but additionally that's not the way to initialize via super, and you'll get a DeprecationWarning if you access the message attribute:
Edit: to override something (or pass extra args), do this:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super(ValidationError, self).__init__(message)
# Now for your custom code...
self.errors = errors
That way you could pass dict of error messages to the second param, and get to it later with e.errors
It also requires exactly two arguments to be passed in (aside from the self.) No more, no less. That's an interesting constraint that future users may not appreciate.
To be direct - it violates Liskov substitutability.
I'll demonstrate both errors:
>>> ValidationError('foo', 'bar', 'baz').message
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
ValidationError('foo', 'bar', 'baz').message
TypeError: __init__() takes exactly 3 arguments (4 given)
>>> ValidationError('foo', 'bar').message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foo'
Compared to:
>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'
see how exceptions work by default if one vs more attributes are used (tracebacks omitted):
>>> raise Exception('bad thing happened')
Exception: bad thing happened
>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')
so you might want to have a sort of "exception template", working as an exception itself, in a compatible way:
>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened
>>> raise nastyerr()
NastyError: bad thing happened
>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')
this can be done easily with this subclass
class ExceptionTemplate(Exception):
def __call__(self, *args):
return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass
and if you don't like that default tuple-like representation, just add __str__ method to the ExceptionTemplate class, like:
# ...
def __str__(self):
return ': '.join(self.args)
and you'll have
>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken
As of Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html), the recommended method is still:
class CustomExceptionName(Exception):
"""Exception raised when very uncommon things happen"""
pass
Please don't forget to document, why a custom exception is neccessary!
If you need to, this is the way to go for exceptions with more data:
class CustomExceptionName(Exception):
"""Still an exception raised when uncommon things happen"""
def __init__(self, message, payload=None):
self.message = message
self.payload = payload # you could add more args
def __str__(self):
return str(self.message) # __str__() obviously expects a string to be returned, so make sure not to send any other data types
and fetch them like:
try:
raise CustomExceptionName("Very bad mistake.", "Forgot upgrading from Python 1")
except CustomExceptionName as error:
print(str(error)) # Very bad mistake
print("Detail: {}".format(error.payload)) # Detail: Forgot upgrading from Python 1
payload=None is important to make it pickle-able. Before dumping it, you have to call error.__reduce__(). Loading will work as expected.
You maybe should investigate in finding a solution using pythons return statement if you need much data to be transferred to some outer structure. This seems to be clearer/more pythonic to me. Advanced exceptions are heavily used in Java, which can sometimes be annoying, when using a framework and having to catch all possible errors.
To define your own exceptions correctly, there are a few best practices that you should follow:
Define a base class inheriting from Exception. This will allow to easily catch any exceptions related to the project:
class MyProjectError(Exception):
"""A base class for MyProject exceptions."""
Organizing the exception classes in a separate module (e.g. exceptions.py) is generally a good idea.
To create a specific exception, subclass the base exception class.
class CustomError(MyProjectError):
"""A custom exception class for MyProject."""
You can subclass custom exception classes as well to create a hierarchy.
To add support for extra argument(s) to a custom exception, define an __init__() method with a variable number of arguments. Call the base class's __init__(), passing any positional arguments to it (remember that BaseException/Exception expect any number of positional arguments). Store extra keyword arguments to the instance, e.g.:
class CustomError(MyProjectError):
def __init__(self, *args, **kwargs):
super().__init__(*args)
self.custom_kwarg = kwargs.get('custom_kwargs')
Usage example:
try:
raise CustomError('Something bad happened', custom_kwarg='value')
except CustomError as exc:
print(f'Сaught CustomError exception with custom_kwarg={exc.custom_kwarg}')
This design adheres to the Liskov substitution principle, since you can replace an instance of a base exception class with an instance of a derived exception class. Also, it allows you to create an instance of a derived class with the same parameters as the parent.
You should override __repr__ or __unicode__ methods instead of using message, the args you provide when you construct the exception will be in the args attribute of the exception object.
See a very good article "The definitive guide to Python exceptions". The basic principles are:
Always inherit from (at least) Exception.
Always call BaseException.__init__ with only one argument.
When building a library, define a base class inheriting from Exception.
Provide details about the error.
Inherit from builtin exceptions types when it makes sense.
There is also information on organizing (in modules) and wrapping exceptions, I recommend to read the guide.
No, "message" is not forbidden. It's just deprecated. You application will work fine with using message. But you may want to get rid of the deprecation error, of course.
When you create custom Exception classes for your application, many of them do not subclass just from Exception, but from others, like ValueError or similar. Then you have to adapt to their usage of variables.
And if you have many exceptions in your application it's usually a good idea to have a common custom base class for all of them, so that users of your modules can do
try:
...
except NelsonsExceptions:
...
And in that case you can do __init__ and __str__ needed there, so you don't have to repeat it for every exception. But simply calling the message variable something else than message does the trick.
In any case, you only need __init__ or __str__ if you do something different from what Exception itself does. And because if the deprecation, you then need both, or you get an error. That's not a whole lot of extra code you need per class.
For maximum customisation, to define custom errors, you may want to define an intermediate class that inherits from Exception class as:
class BaseCustomException(Exception):
def __init__(self, msg):
self.msg = msg
def __repr__(self):
return self.msg
class MyCustomError(BaseCustomException):
"""raise my custom error"""
Try this Example
class InvalidInputError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return repr(self.msg)
inp = int(input("Enter a number between 1 to 10:"))
try:
if type(inp) != int or inp not in list(range(1,11)):
raise InvalidInputError
except InvalidInputError:
print("Invalid input entered")
A really simple approach:
class CustomError(Exception):
pass
raise CustomError("Hmm, seems like this was custom coded...")
Or, have the error raise without printing __main__ (may look cleaner and neater):
class CustomError(Exception):
__module__ = Exception.__module__
raise CustomError("Improved CustomError!")
I had issues with the above methods, as of Python 3.9.5.
However, I found that this works for me:
class MyException(Exception):
"""Port Exception"""
And then it could be used in code like:
try:
raise MyException('Message')
except MyException as err:
print (err)
I came across this thread. This is how I do custom exceptions. While the Fault class is slightly complex, it makes declaring custom expressive exceptions with variable arguments trivial.
FinalViolation, SingletonViolation are both sub classes of TypeError so will be caught code below.
try:
<do something>
except TypeError as ex:
<handler>
That's why Fault doesn't inherit from Exception. To allow derivative exceptions to inherit from the exception of their choice.
class Fault:
"""Generic Exception base class. Note not descendant of Exception
Inheriting exceptions override formats"""
formats = '' # to be overriden in descendant classes
def __init__(self, *args):
"""Just save args for __str__"""
self.args = args
def __str__(self):
"""Use formats declared in descendant classes, and saved args to build exception text"""
return self.formats.format(*self.args)
class TypeFault(Fault, TypeError):
"""Helper class mixing Fault and TypeError"""
class FinalViolation(TypeFault):
"""Custom exception raised if inheriting from 'final' class"""
formats = "type {} is not an acceptable base type. It cannot be inherited from."
class SingletonViolation(TypeFault):
"""Custom exception raised if instancing 'singleton' class a second time"""
formats = "type {} is a singleton. It can only be instanced once."
FinalViolation, SingletonViolation unfortunately only accept 1 argument.
But one could easily create a multi arg error e.g.
class VesselLoadingError(Fault, BufferError):
formats = "My {} is full of {}."
raise VesselLoadingError('hovercraft', 'eels')
__main__.VesselLoadingError: My hovercraft is full of eels.
For me it is just __init__ and variables but making sometimes testing.
My sample:
Error_codes = { 100: "Not enough parameters", 101: "Number of special characters more than limits", 102: "At least 18 alphanumeric characters and list of special chars !##$&*" }
class localbreak( Exception ) :
Message = ""
def __init__(self, Message):
self.Message = Message
return
def __str__(self):
print(self.Message)
return "False"
### When calling ...
raise localbreak(Error_codes[102])
Output:
Traceback (most recent call last): File "ASCII.py", line 150, in <module>
main(OldPassword, Newpassword) File "ASCII.py", line 39, in main
result = read_input("1", "2", Newpassword, "4")
File "ASCII.py", line 69, in read_input
raise localbreak(Error_codes[102]) At least 18 alphanumeric characters and list of special chars !##$&*
__main__.localbreak: False

How to use getattr or getattribute to correctly raise ImportError while calling some methods

Hello their thanks in advance for helping me,
Please see below code:
import types
_MSG = ("Failed importing {name}. Please install {name}."
" Using pip install {name}")
class Empty(): # pylint: disable=too-few-public-methods
"""Empty class for beam API."""
def __call__(self, *args, **kwargs):
return None
class DummyBeam(types.ModuleType): # pylint: disable=too-few-public-methods
DoFn = Empty
Pipeline = Empty
def __init__(self, name="apache_beam"):
super(DummyBeam, self).__init__(name)
def __getattribute__(self, _):
if getattr(DummyBeam, _, None) is Empty:
err_msg = _MSG.format(name=self.__name__)
raise ImportError(err_msg)
What I want to check if apache_beam was not installed it will successfully load all beam classes like DoFn and pipeline but calling some function raises error please see below code to see above code in use.
try:
import apache_beam as beam
except ImportError:
beam = DummyBeam()
class SomeFn(beam.DoFn):
pass
class SomeOtherFn(beam.Pipeline):
pass
SomeFn()
In above code for now accessing beam.DoFn raises error but what I want it to not raise error when accessing beam.DoFn although it raises error when calling SomeFn(). Also tried to replace getattribute with getattr and it not gives me results as i expected it wont raise error when calling SomeFn() although it runs fine for all codes.
Thanks for looking into this.
As shown in the traceback (that you should have posted FWIW), your error is not in calling SomeFn() but in accessing beam.DoFn in the SomeFn class definition. And the reason is quite obvious: you very explicitely instructed Python to do so by plain overriding Beam.__getattribute__.
Note that object.__getattribute__ is the official default implementation of attribute lookup (it's invoked each time Python sees either obj.name or getattr(obj, "name"), and that it's a method that is better left alone unless you fully understand the implications of overriding it AND have no better solution.
In this case the very obvious solution is to instead implempent __getattr__, which is only invoked by __getattribute__ as a last resort if the attribute couldn't be resolved in any other way. You say that:
Also replace getattribute with getattr was not working
but I just tried it on your code snippet and it (of course) yield the result I expected. Whether this is what you expected is another question, but since you neither posted this version of your code nor cared to explain how it "was not working", you can't expect any answer on this point (hint: "is not working" is a totally useless description of an issue).
As a last note:
it will successfully load all beam methods like DoFn and pipeline
...
In above code for now calling beam.DoFn
It seems you're a bit confused about terminology. DoFn and Pipeline are classes, not methods, and (as already mentionned) your error is raised when accessing beam.DoFn, not when calling it.
EDIT:
by was not working I meant it not gives me error either when i am trying to access beam.DoFn or SomeFn() when use getattr instead getattribute
(...)
what i want is to raise error when calling someFn no accessing beam.DoFn
Ok, it looks that you don't quite get the execution order of a method call expression. When you do
obj.method()
this is actually a shortcut for
method = obj.__getattribute__("method")
method.__call__()
So overriding __getattribute__ isn't the proper solution (cf above), and defining __getattr__ is useless here - your DummyBeam class HAS DoFn and Pipeline attributes so __getattr__ will just not be invoked for those names.
Now the reason you don't get any exception when calling beam.DoFn or beam.Pipeline is that those name are bound to your Empty class, not instances of that class, so you actually never invoke Empty.__call__. Rhe __call__ method defined in a class is only used when an instance of that class is called, not when you instanciate the class (in which case it's the metaclass's __call__ method which is invoked):
>>> class MyCallable:
... def __init__(self):
... print("in MyCallable.__init__")
... def __call__(self):
... print("in MyCallable.__call__")
...
>>>
... c = MyCallable()
in MyCallable.__init__
>>> c()
in MyCallable.__call__
>>>
So if what you want is to raise your exception when someone tries to instanciate DoFn or ̀Pipelineyou either have to make them instances ofEmptyor keep them as they are and renameEmpty.calltoEmpty.newwhich is the first method called bytype.call(type` being the default metaclass for all classes).

Python Dataclasses: FrozenInstanceError a subclass of AttributeError?

I'm doing some self-learning on the new python dataclasses.
One of the parameters that can be passed to the dataclass decorator is frozen=True, to make the object immutable.
The documentation (and experience) indicates that a:
dataclasses.FrozenInstanceError
exception will be raised.
When unit testing though (with pytest) the following test passes:
def test_change_page_url_values_raises_error(self, PAGE_URL):
page_url = PageURL(PAGE_URL)
with pytest.raises(AttributeError) as error:
page_url.value = PAGE_URL
where PageURL is a dataclass with the frozen=True parameter.
Any ideas why why pytest indicates that this action (assigning a value to page_url.value) raises an Attribute Error? Does FrozenInstanceError inherit from AttributeError?
Note: If I change the unit test to test for a different exception (ie. TypeError), the test fails as expected.
This is a subclass, which you can verify easily with built-in function issubclass:
>>> issubclass(FrozenInstanceError, AttributeError)
True
If you want an exact type match in the tests, which I would consider best practice, then you can use an exception instance instead of an exception class. As an added bonus this also allows you to make an assertion on the exception context (i.e. which field has triggered the exception).
with pytest.raises(FrozenInstanceError("cannot assign to field 'value'")):
page_url.value = PAGE_URL
This usage of pytest.raises requires installing my plugin pytest-raisin.

Categories