What is the standard way to design exception inheritance? - python

Im having some trouble with designing the exception classes for a Python web API. What I would like to do is have various exceptions set up with some default error codes/messages, but also allow the flexibility of creating a custom one.
Take the following code:
class APIException(Exception):
def __init__(self):
super().__init__(self.message)
#property
def message(self) -> str:
raise NotImplementedError
#property
def code(self) -> int:
raise NotImplementedError
#property
def response(self):
return {"error": self.message}, self.code
class UnknownException(APIException):
message = "An unknown error occurred."
code = 500
class UnauthorizedException(APIException):
message = "Unauthorized"
code = 401
This allows me to do things like raise UnauthorizedException, which work fine.
However, what I would like to be able to do is to raise arbitrary API exceptions, like raise APIException("This is a custom error", 404). Raising a set exception with arguments and raising an APIException without arguments do not need to be supported; I will not be raising them like that.
It doesn't seem I can do this cleanly with the way I have designed the inheritance above. I have tried other various approaches but none seem to be as clean as the example above.
What would be the best way to go about doing this sort of thing?

Have your APIException constructor take arguments, and have the subclasses implement constructors that provide those arguments:
class APIException(Exception):
def __init__(self, message, code):
super().__init__(message)
self.message = message
self.code = code
#property
def response(self):
return {"error": self.message}, self.code
class UnknownException(APIException):
def __init__():
super().__init__("An unknown error occurred.", 500)
class UnauthorizedException(APIException):
def __init__():
super().__init__("Unauthorized", 401)

Related

How to add hint to a factory method?

I'm looking for a way to annotate return type of a factory function.
It returns random child of 'AlgorithmBase'.
class AlgorithmFactory:
_algorithm_types = AlgorithmBase.__subclasses__()
def select_random_algorithm(self) -> AlgorithmBase:
# Select random algorithm
algorithm_class = self._random_generator.choice(AlgorithmFactory._algorithm_types)
algorithm = algorithm_class()
return algorithm
I get error from mypy:
The error I'm getting is:
Cannot instantiate abstract class 'AlgorithmBase' with abstract attributes 'get_constraints' and 'satisfy_constraints'
There is no way to instantiate class 'AlgorithmBase' in this code, how to make mypy understand it?
I want to avoid specifying actual sub-classes with 'Union' in return type. Any suggestions?
The problem here wasn't return type, but '_algorithm_types'. mypy has no way to understand what type it is, so it assumed that it is like return type and got error.
The following code fix the issue:
_algorithm_types: List[Type[AlgorithmBase]] = AlgorithmBase.__subclasses__()
As far as I can tell this should work, but it seems like one or more of your AlgorithmBase subclasses doesn't implement these two abstract methods.
Running MyPy for
import abc
class AlgorithmBase(abc.ABC):
#abc.abstractmethod
def get_constraints(self):
raise NotImplementedError
#abc.abstractmethod
def satisfy_constraints(self):
raise NotImplementedError
class SomeAlgorithm(AlgorithmBase):
pass
class AlgorithmFactory:
def get(self) -> AlgorithmBase:
algorithm = SomeAlgorithm()
return algorithm
yields the same error you get, and it runs without any error once the methods are implemented.
import abc
class AlgorithmBase(abc.ABC):
#abc.abstractmethod
def get_constraints(self):
raise NotImplementedError
#abc.abstractmethod
def satisfy_constraints(self):
raise NotImplementedError
class SomeAlgorithm(AlgorithmBase):
def get_constraints(self):
pass
def satisfy_constraints(self):
pass
class AlgorithmFactory:
def get(self) -> AlgorithmBase:
algorithm = SomeAlgorithm()
return algorithm

Creating exceptions that are co-operative

The Python docs state:
Programs may name their own exceptions by creating a new exception
class (see Classes for more about Python classes). Exceptions should
typically be derivedfrom the Exception class, either directly or
indirectly.
...
When creating a module that can raise several distinct errors, a
common practice is to create a base class for exceptions defined by
that module, and subclass that to create specific exception classes
for different error conditions.
From Python’s super() considered super!:
Each level strips-off the keyword arguments that it needs so that the
final empty dict can be sent to a method that expects no arguments at
all (for example, object.init expects zero arguments)
Suppose I have the following StudentValueError and MissingStudentValue exceptions.
class StudentValueError(Exception):
"""Base class exceptions for Student Values"""
def __init__(self, message, **kwargs):
super().__init__(**kwargs)
self.message = message # You must provide at least an error message.
class MissingStudentValue(StudentValueError):
def __init__(self, expression, message, **kwargs):
super().__init__(message, **kwargs)
self.expression = expression
def __str__(self):
return "Message: {0} Parameters: {1}".format(self.message, self.expression)
I want to create exceptions that are co-operative. I have two questions:
In that case, the Exception class constructor expects zero arguments (empty dict), correct?
Does my example violate LSP?
The accepted answer provided here inherits from ValueError.
Exception takes no keyword arguments, it takes only variable amount of positional parameters via *args, so you need to change **kwargs to *args. Also I would recommend to pass message and expression together with *args to super() call. After all, the example, which probably doesn't violate LSP:
class StudentValueError(Exception):
"""Base class exceptions for Student Values"""
def __init__(self, message='', *args):
super().__init__(message, *args)
self.message = message
class MissingStudentValue(StudentValueError):
def __init__(self, message='', expression='', *args):
super().__init__(message, expression, *args)
self.expression = expression
def __str__(self):
return "Message: {0} Parameters: {1}".format(self.message, self.expression)
e = Exception('message', 'expression', 'yet_another_argument')
print(e)
e = StudentValueError('message', 'expression', 'yet_another_argument')
print(e)
e = MissingStudentValue('message', 'expression', 'yet_another_argument')
print(e)
e = MissingStudentValue()
print(e)

minimal class to help you raise exception?

What does the following class does:
class HException(Exception):
def __init__(self, value=''):
self.value = value
def __str__(self):
return self.value
I saw it being called by raise
This is custom exception for more precise designations problems.
Not just an abstract exception. But an exception has arisen in a particular subject area.
class MoneyTransactionException(Exception):
...
says that something wrong with money manipulate in program.
User defined exceptions

Python, IoC, Exceptions and loose coupling

Say we have two classes, Class A with a custom error which is thrown frequently and it is part of its functionality.
#a.py
class AError(Exception):
"""This exception flags a functional error"""
pass
class A(object):
def work(self):
"""Throws AError when it is tired"""
raise AError() #This exception is raised eventually, business code removed for clarity
Class B, which uses class A to perform some operations.
#b.py
import a
class B(object):
def make_him_work(self, afected):
try:
afected.work()
except a.AError:
pass #This was expected, here will go some business logic
This works great, but it becomes an issue when I have different types of A. Ideally, I'd like to fully decouple A from B so I can pass any class like A that satisfies the same interface, but I cannot due to the exception(as it is not part of the interface itself)
In C++ I would have a header file with the definition of my Interface plus the exceptions which the concrete classes will implement. How is this usually solved in Python? Or said another way, what is the most pythonic approach?
I thought the following options:
1. create module with exceptions and maybe a base class/metaclass (the C++/Java way)
#common.py
class AErrorBase(Exception):
pass
class AIface(object):
def work(self):
raise NotImplemented()
.
#a.py
import common
class AError(common.AErrorBase):
pass
class A(common.AIface):
def work(self):
"""Throws AError when it is tired"""
raise AError()
.
#b.py
import common
class B(object):
def make_him_work(self, afected):
try:
afected.work()
except common.AErrorBase:
pass #This was expected
2. pass exception as an argument
#a.py
class AError(Exception):
pass
class A(object):
def work(self):
"""Throws AError when it is tired"""
raise AError()
.
#b.py
class B(object):
def make_him_work(self, afected, ex_type):
try:
afected.work()
except ex_type:
pass #This was expected
3. Exception as an attribute of the Class so it becomes part of the interface.
#a.py
class A(object):
def work(self):
"""Throws AError when it is tired"""
raise AError()
class AError(Exception):
pass
.
#b.py
class B(object):
def make_him_work(self, afected):
try:
afected.work()
except afected.AError:
pass #This was expected
4. Dont use exception, just a return code. !C days are back!
Any other option? What do you find more "pythonic"?
Edit: Added comments to clarify the purpose of the exception. It needs to be handled in B
Note: This might perfectly be that I am approaching the problem with my old C++ background, I just want to know how do you apply IoC in python when we have exceptions. Feel free to say all my approaches are garbage and I should do it in another way
My class would look like this:
class A(object):
def can_work(self):
"returns True if an A can work otherwise False (e.g. is an A is tired)"
return ...
def work(self):
assert not self._is_tired, "Test if self.can_work() first!"
...
This way you allow the users of A to test whether they should use work.
The assertion is useful for debugging and making sure you or others did not forget about the Interface.
Class B will use A as follows:
class B(object):
def make_him_work(self, afected):
if afected.can_work():
afected.work()

Are python Exceptions as class attributes a bad thing?

I find myself often wanting to structure my exception classes like this:
# legends.py
class Error(Exception): pass
class Rick(object):
class Error(Error): pass
class GaveYouUp(Error): pass
class LetYouDown(Error): pass
class Michael(object):
class Error(Error): pass
class BlamedItOnTheSunshine(Error): pass
class BlamedItOnTheMoonlight(Error): pass
I have only seen this pattern used in Django (DoesNotExist) and it makes so much sense. Is there anything I'm missing, why most people seem to favor top-level Exceptions?
edit
I would use these classes for versatile granularity, e.g:
import legends
try:
do_stuff()
except legends.Michael.Error:
blame_it_on_the_boogie()
except legends.Rick.GaveYouUp:
let_you_down()
except legends.Error:
pass
except Exception as e:
raise Hell()
This is the exact pattern used by Django for certain ORM-related exceptions.
The advantage is that you can have an except clause which checks against a type accessed through an instance:
rick = Rick()
try:
rick.roll()
except rick.GaveYouUp:
never()
except rick.LetYouDown:
never_ever()
This doesn't look that useful here, but if rick were a function parameter, then it would potentially be rather useful.
This is also extremely useful in writing generic code which raises the exceptions:
GoddamStar(object):
def sing(self,tune):
raise self.Error()
class Rick(GoddamStar):
class Error(Error): pass
class GaveYouUp(Error): pass
class LetYouDown(Error): pass
class Michael(GoddamStar):
class Error(Error): pass
class BlamedItOnTheSunshine(Error): pass
class BlamedItOnTheMoonlight(Error): pass
rick = Rick()
try:
rick.sing()
except Rick.GaveYouUp:
never()
except Michael.Error:
never_ever()
Django's exceptions generally all derive from global base classes, so that you can also have a catch-all clause which still switches on a type of exception, in case your rick is of an unknown (or otherwise unprovided for) class.
The reason why this isn't much more common is that (a) it doesn't work in early-bound languages, which attract most of the book writers (b) it's moderately rare that this is useful to the user, and so application writers likely figure they aren't going to need it.
If you want to raise e.g. BlamedItOnTheSunshine outside of Micheal you would have to call it by raise Micheal.BlamedItOnTheSunshine('error text').
e.g.:
class A:
class E(Exception): pass
def __init__(self): raise A('error in A')
class B:
def __init__(self): raise A.E('error in B')
in this Example A and B are not related, but if you have a relation like:
class Interpret(object):
class LetsYouDown(Exception): pass
def __init__(self): raise self.LetsYouDown("I'm not Rick!")
class Michael(Interpret):
class BlameItOnTheSunshine(Exception): pass
def __init__(self): raise self.BlameItOnTheSunshine("It's not the Moon!")
class Rick(Interpret):
class NeverEver(Exception): pass
def __init__(self): print "Never Ever!"
and want now something like:
try:
for superstar in [Interpret, Michael, Rick]:
star_in_show = superstar()
except superstar.LetsYouDown:
print "Where's Rick?"
except superstar.BlameItOnTheSunshine:
print "Must be Michael!"
you will get an Error i would call a Liskov's Principle violation.
So one of the main reason's (polymorphism) for using OOP is somewhat compromised. But it
doesn't necesarrily mean you can't or shouldn't use it. Just be aware of the limitations.
i hope that cleared my initial cryptical reservations up.

Categories

Resources