5

How to decouple application from exceptions that creep in from used library dependencies?

[app] --uses--> [lib] --dependson--> [dependency]
                                           / /
  x- <-propagates--o <---throwsexception--' /
   \                                       /
    `-----needstohandle,soimports-----> --'

The problem is from real pip code:

  1. module A (req/req_set.py) depends on module B
  2. module B (download) uses module C (requests)
  3. module A imports module C to handle exception from C

How to encapsulate exception in module B? To remove dependency on C from module A? How to make sure that the cause and details of original exception are not lost? In other words how can I reraise the exception with a different name?

The snippet below does what is needed, but it it Python 3 only:

 try:
     dependency_call()
 except DependencyError as exc:
     raise LibraryError from exc

UPDATE: I am looking for Python 2 compatible solution, the Python 3 added raise ... from ... that does the trick almost good.

UPDATE 2: The goal of encapsulating exception is to catch it in [lib] and re-throw a new one to [app] preserving the stack trace so that debug tools can still walk up the code (for human-only solution the answer by Alex Thornton should be good).

2 Answers 2

4

You can catch an arbitrary exception by referring to the Exception base class:

except Exception as exc:
    raise ApplicationError from exc

To get the from idiom to work in Python 2, you'll have to hack around with your custom exception:

class ApplicationError(Exception):
    def __init__(self, cause, trace):
        self.cause = cause
        self.trace = trace
    def __str__(self):
        return '{origin}\nFrom {parent}'.format(origin=self.trace, 
                                                parent=self.cause)

And then raise it like this:

 except Exception, exc:
     raise ApplicationError(exc)

it will then print the cause when it is raised, which is also an attribute which you can access if you decide to catch the ApplicationError as well.

Sign up to request clarification or add additional context in comments.

10 Comments

@techtonik The from is OK, you just need to define your own ApplicationError exception.
Looks like is it Python 3 stuff. For the Python 2 it gives SyntaxError - dpaste.de/SbtT
@techtonik: The way Mr. Thornton showcased his solution it looks like a "catch all", but i'm sure he just wanted to provide a solution as quickly as possible, and had confidence you would solve the "catch all" by re-raising the exception if it wasn't the specific one.
@DonQuestion, I was reading the answer too fast. The actual issue that I didn't know that this code actually compiles on Python 3. I updated the question and removed confusing comments.
That's cool. But it hides the trace of actual error. Python 3 shows a full log dpaste.de/ajjH (the code is here - dpaste.de/BW4T)
|
0

If i do get you right, you would like to decouple more strongly and eliminate:

from pip._vendor import requests

and:

except requests.HTTPError as exc:

you could do that by introducing a last-resort fallback handler as the last except of all except clauses like:

try:...
except A:
...   # here all your other exceptions
except Exception as exc:    # the fall-back handler
    if "HTTPError" in repr(exc):
       # do whatever you want to do with the (assumed) request.HTTPError
       # any other Exception with HTTPError in it's repr-string will be
       # caught here

The draw-back is, that it's still a tight coupling and a clear violation of the "law of Demeter", because you would need to know some internals of an object which isn't even in the object-composition. So in some sense it's even worse now.

10 Comments

Although it provides decoupling, I thing the code is worse than original and still doesn't provide encapsulation. In addition it removes the ability for static analysis or type checking. =)
I don't get your "encapsulation" argument. I don't see that as an issue in this case, because custom Exceptions are the complete opposite of "encapsulation" as they don't hide but add to complexity by exposing an additional public interface.
The chain consists of [app]-->[lib]-->[dep]. [lib] should encapsulate all [dep] exceptions to hide these additional public interfaces from it.
Exceptions are not meant to be hidden, but dealt with! Something in [lib] failed so hard that it compelled [dep] to throw an exception. Doesn't look rational to "hide" it! If [lib] can't handle it, what makes you think [app] could, without breaking the "law of Demeter"? btw: encapsulation is a horrible buzz-word and imho misleading for the issue at hand, especially the way you use it atm: hiding perceived misbehaviour at run time instead of exposing a clean interface to a type.
There are two ways to solve this dilemma imo. 1st: you could modify [lib] and wrap [dep]-excpetions in custom [lib]-exceptions. 2nd: you add an additional [exclib], which provides a "facade" for arbitrary exceptions.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.