Exceptions that reflect error codes of a remote service - python

I'm working with an external service which reports errors by code.
I have the list of error codes and the associated messages. Say, the following categories exist: authentication error, server error.
What is the smartest way to implement these errors in Python so I can always lookup an error by code and get the corresponding exception object?
Here's my straightforward approach:
class AuthError(Exception):
pass
class ServerError(Exception):
pass
map = {
1: AuthError,
2: ServerError
}
def raise_code(code, message):
""" Raise an exception by code """
raise map[code](message)
Would like to see better solutions :)

Your method is correct, except that map should be renamed something else (e.g. ERROR_MAP) so it does not shadow the builtin of the same name.
You might also consider making the function return the exception rather than raising it:
def error(code, message):
""" Return an exception by code """
return ERROR_MAP[code](message)
def foo():
raise error(code, message)
By placing the raise statement inside foo, you'd raise the error closer to where the error occurred and there would be one or two less lines to trace through if the stack trace is printed.

Another approach is to create a polymorphic base class which, being instantiated, actually produces a subclass that has the matching code.
This is implemented by traversing __subclasses__() of the parent class and comparing the error code to the one defined in the class. If found, use that class instead.
Example:
class CodeError(Exception):
""" Base class """
code = None # Error code
def __new__(cls, code, *args):
# Pick the appropriate class
for E in cls.__subclasses__():
if E.code == code:
C = E
break
else:
C = cls # fall back
return super(CodeError, cls).__new__(C, code, *args)
def __init__(self, code, message):
super(CodeError, self).__init__(message)
# Subclasses with error codes
class AuthError(CodeError):
code = 1
class ServerError(CodeError):
code = 2
CodeError(1, 'Wrong password') #-> AuthError
CodeError(2, 'Failed') #-> ServerError
With this approach, it's trivial to associate error message presets, and even map one class to multiple codes with a dict.

Related

Make python don't undo things after an error is raised in try block

I have a python class in which I open files and read out data. If some creteria are not met, I raise an error, but before that I specify the error by giving the object an attribute: self.Error = specification. But since the error raising undos everything in the try block I can't access it. This happens in the __init__ function, so the created object doesn't even exist..
Here's the necessary code:
class MyClass:
def __init__(self):
#do something
if this_or_that:
self.Error = specification
raise MyCostumError
try:
object = MyClass()
except MyCostumError:
print(object.Error)
I get: NameError: name 'object' is not defined
Just for clarification:
I have defined MyCostumError, the variable names are just for better understanding: I use good ones and they are defined and I need the clarification, because an Error can be raised in different lines.
So here's my question:
Is there something like try/except, but when an error is raised it does NOT undo everything. Or am I just stupid and there is a much easier method for a achieving this?
If you are raising an exception in the initializer, you should not rely on the object to be created to get some error information to the caller. This is where you should use the exception to pass that information:
class MyCustomError(Exception):
pass
class MyClass:
def __init__(self):
#do something
if this_or_that:
raise MyCustomError(specification) # put the spec in the exception itself
try:
object = MyClass()
except MyCustomError as e:
print(e) # the spec is in the exception object
You are trying to reference to an object that cannot exist. Let me explain:
If an error occurs when you try to initialise an object, that object will not be initialised. So if you try to acced to it when it is not initialised, you will get an error.
try:
object = MyClass() #initialising object successful, object existing.
except: #initialising failed, object does not exist.
print(object.Error) #nameError, since object was never created.
Try/except doesn't undo anything, just stops doing something if an error occurs.
Error raising doesn't undo anything. Have a look at the docs.
As your output states, the object is not defined, this is because when you raise an error in the __init__, it is seen as the initialosor of your class failing, and this does not return an object.
I think this is what you're looking for:
class MyClass:
def __init__(self):
# do initialisation stuff
def other_method(self):
# do something
if this_or_that:
self.Error = specification
raise MyCustomError(specification)
object = MyClass()
try:
object.other_method()
except MyCustomError as e:
print(e)
print(object.Error)
It's not a beautiful solution but it should work:
errorcode = None
class MyClass:
def __init__(self):
global errorcode
#do something
if this_or_that:
errorcode = specification
raise MyCostumError
try:
object = MyClass()
except MyCostumError:
print(errorcode)
Given your question I think the following should fit your use case well.
class MyClass:
def __init__(self):
# Do something
try:
if this_or_that:
self.Error = specification
raise MyCostumError
except MyCustomError as e:
# Handle your custom error however you like
object = MyClass()
In the above case you should be able to mitigate the risk of instantiation failing due to custom exception/error raising failing by handling this behaviour within MyClass.__init__ itself.
This is also a much cleaner solution in terms of keeping logic relating to instantiation of MyClass objects contained within the __init__ function of the class - i.e. you won't have to worry about wrapping instantiations of this class in try/except blocks each time they are present in your code.

How to provide feedback that an error occurred in the error handling methods of a class?

I am working with a class in python that is part of a bigger program. The class is calling different methods.
If there is an error in one of the method I would like code to keep running after, but after the program is finished, I want to be able to see which methods had potential errors in them.
Below is roughly how I am structuring it at the moment, and this solution doesn't scale very well with more methods. Is there a better way to provide feedback (after the code has been fully run) as to which of the method had a potential error?
class Class():
def __init__(self):
try:
self.method_1()
except:
self.error_method1 = "Yes"
break
try:
self.method_2()
except:
self.error_method2 = "Yes"
break
try:
self.method_3()
except:
self.error_method3 = "Yes"
break
Although you could use sys.exc_info() to retrieve information about an Exception when one occurs as I mentioned in a comment, doing so may not be required since Python's standard try/expect mechanism seems adequate.
Below is a runnable example showing how to do so in order to provide "feedback" later about the execution of several methods of a class. This approach uses a decorator function, so should scale well since the same decorator can be applied to as many of the class' methods as desired.
from contextlib import contextmanager
from functools import wraps
import sys
from textwrap import indent
def provide_feedback(method):
""" Decorator to trap exceptions and add messages to feedback. """
#wraps(method)
def wrapped_method(self, *args, **kwargs):
try:
return method(self, *args, **kwargs)
except Exception as exc:
self._feedback.append(
'{!r} exception occurred in {}()'.format(exc, method.__qualname__))
return wrapped_method
class Class():
def __init__(self):
with self.feedback():
self.method_1()
self.method_2()
self.method_3()
#contextmanager
def feedback(self):
self._feedback = []
try:
yield
finally:
# Example of what could be done with any exception messages.
# They could instead be appended to some higher-level container.
if self._feedback:
print('Feedback:')
print(indent('\n'.join(self._feedback), ' '))
#provide_feedback
def method_1(self):
raise RuntimeError('bogus')
#provide_feedback
def method_2(self):
pass
#provide_feedback
def method_3(self):
raise StopIteration('Not enough foobar to go around')
inst = Class()
Output:
Feedback:
RuntimeError('bogus') exception occurred in Class.method_1()
StopIteration('Not enough foobar to go around') exception occurred in Class.method_3()

how to mock an exception call in python?

I have a code like this:
def extract(data):
if len(data) == 3:
a = 3
else:
component = data.split("-")
if len(component) == 3:
a,b,c = component
else:
raise globals.myException("data1", "Incorrect format", data)
return a,b,c
This is a simplified one. I want to mock the exception class globals.myException. I'm doing that:
def test_extract_data_throws_exception(self):
with patch('globals.myException') as mock:
mock.__init__("data1", "Incorrect format", "")
with self.assertRaises(myException):
self.assertEqual(extract(""), (""))
And I always get the error: "TypeError: exceptions must be old-style classes or derived from BaseException, not MagicMock"
EDIT: As #Aaron Digulla suggest, monkey patching is the correct solution. I post the solution to help others.
def test_extract_data_throws_exception(self):
#monkey patching
class ReplaceClass(myException):
def __init__(self, module, message, detail = u''):
pass
globals.myException = ReplaceClass
with self.assertRaises(myException:
self.assertEqual(extract(""), (""))
The reason is raise checks the type of the argument. It must be a string (a.k.a "old style exceptions") or derived from BaseException
Since a mock isn't either, raise refuses to use it.
In this specific case, you either have to raise the exception or use monkey patching (= overwrite the symbol globals.myException in your test and restore it afterwards).

Place to handle exceptions in my classes hierarchy

I got this-like hierarchy and similar code:
class FrontendException:
pass
class BackendException:
pass
class BackendRequest:
def exec():
raise BackendException()
class Frontend:
def cmd_a():
BackendRequest().exec()
def cmd_b():
BackendRequest().exec()
The goal is to make developer able to operate with Frontend objects and exceptions within functions cmd_x of Frontend.
Basicly, i need a place to handle common BackendException types, to raise FrontendException. For example:
class Frontend:
def cmd_a():
try:
BackendRequest().exec()
except BackendException as e:
raise FrontendException()
And this will be repeated in each cmd_x function! It's so ugly! And it operates with Backend things! I want to remove repeated exception handling.
Any suggestions?
Btw, my solution, but i find it ugly too, so view it after you'll try to suggest me something. Maybe you'll suggest me something about my solution.
class BaseFrontend:
def exec_request(req):
try:
return req.exec()
except BackendException as e:
raise FrontendException
class Frontend(BaseFrontend):
def cmd_a():
result self.exec_request(BackendRequest())
def cmd_b():
result self.exec_request(BackendRequest())
EDIT: Ok, yes, i know, i dont need to create a lot of classes to build simple API. But, let's see what i need in the result:
class APIManager:
def cmd_a(): ...
def cmd_b(): ...
This manager needs to access HTTP REST service to perform each command. So, if i'll get an error during REST request, i'll need to raise exception APIManagerException - i can't leave raw pycurl exception, beacause APIManager user don't knows what pycurl is, he will be confused with getting pycurl error if he will give wrong ID as argument of cmd_x.
So i need to raise informative exceptions for some common cases. Let it be just one exception - APIManagerException. But i dont want to repeat try...except block each time, in each command, to each pycurl request. In fact, i want to process some errors in commands(functions cmd_x), not to parse pycurl errors.
You can create a decorator that wraps all Frontend calls, catches BackendExceptions, and raises FrontendException if they are thrown. (Honestly, though, it's not clear why Frontend and Backend are classes and not a set of functions.) See below:
class FrontendException:
pass
class BackendException:
pass
class BackendRequest:
def exec():
raise BackendException()
class Frontend:
def back_raiser(func):
def wrapped(*args, **kwargs):
try:
func(*args, **kwargs)
except BackendException:
raise FrontendException
return wrapped
#back_raiser
def cmd_a():
BackendRequest().exec()
#back_raiser
def cmd_b():
BackendRequest().exec()

How do I instantiate a specific subclass based on arguments passed to __init__?

I'm wrapping a remote XML-based API from python 2.7. The API throws errors by sending along a <statusCode> element as well as a <statusDescription> element. Right now, I catch this condition and raise a single exception type. Something like:
class ApiError(Exception):
pass
def process_response(response):
if not response.success:
raise ApiError(response.statusDescription)
This works fine, except I now want to handle errors in a more sophisticated fashion. Since I have the statusCode element, I would like to raise a specific subclass of ApiError based on the statusCode. Effectively, I want my wrapper to be extended like this:
class ApiError(Exception):
def __init__(self, description, code):
# How do I change self to be a different type?
if code == 123:
return NotFoundError(description, code)
elif code == 456:
return NotWorkingError(description, code)
class NotFoundError(ApiError):
pass
class NotWorkingError(ApiError):
pass
def process_response(response):
if not response.success:
raise ApiError(response.statusDescription, response.statusCode)
def uses_the_api():
try:
response = call_remote_api()
except NotFoundError, e:
handle_not_found(e)
except NotWorkingError, e:
handle_not_working(e)
The machinery for tying specific statusCode's to specific subclasses is straightforward. But what I want is for that to be buried inside of ApiError somewhere. Specifically, I don't want to change process_response except to pass in the value statusCode.
I've looked at metaclasses, but not sure they help the situation, since __new__ gets write-time arguments, not run-time arguments. Similarly unhelpful is hacking around __init__ since it isn't intended to return an instance. So, how do I instantiate a specific subclass based on arguments passed to __init__?
A factory function is going to be much easier to understand. Use a dictionary to map codes to exception classes:
exceptions = {
123: NotFoundError,
456: NotWorkingError,
# ...
}
def exceptionFactory(description, code):
return exceptions[code](description, code)
Create a function that will yield requested error class basing on description.
Something like this:
def get_valid_exception(description, code):
if code == 123:
return NotFoundError(description, code)
elif code == 456:
return NotWorkingError(description, code)
Depending on your requirements and future changes, you could create exceptions with different arguments or do anything else, without affecting code that uses this function.
Then in your code you can use it like this:
def process_response(response):
if not response.success:
raise get_valid_exception(response.statusDescription, response.statusCode)
You could create a series of subclasses and use the base class' __new__ as a factory for the children. However, that's probably overkill here; you could just create a simple factory method or class. If you wanted to get fancy in another direction though, you could create a metaclass for the base class that would automatically add your subclasses to a factory when they are created. Something like:
class ApiErrorRegistry(type):
code_map = {}
def __new__(cls, name, bases, attrs):
try:
mapped_code = attrs.pop('__code__')
except KeyError:
if name != 'ApiError':
raise TypeError('ApiError subclasses must define a __code__.')
mapped_code = None
new_class = super(ApiErrorRegistry, cls).__new__(cls, name, bases, attrs)
if mapped_code is not None:
ApiErrorRegistry.code_map[mapped_code] = new_class
return new_class
def build_api_error(description, code):
try:
return ApiErrorRegistry.code_map[code](description, code)
except KeyError:
raise ValueError('No error for code %s registered.' % code)
class ApiError(Exception):
__metaclass__ = ApiErrorRegistry
class NotFoundError(ApiError):
__code__ = 123
class NotWorkingError(ApiError):
__code__ = 456
def process_response(response):
if not response.success:
raise build_api_error(response.statusDescription, response.statusCode)
def uses_the_api():
try:
response = call_remote_api()
except ApiError as e:
handle_error(e)

Categories