2

I have a class NameDatabase that opens an sqlite file. Several things can go wrong, such as a malformed DB, a wrong schema, etc. This will generally result in an exception (sqlite3.Error in my case).

I don't want to let that exception escape my class. Instead I want to give callers my own exception classes (reasoning: besides NameDatabase I also have classes for images and GPS tracks, and I want to present callers with a common list of exceptions to deal with).

First, is this a good idea? Second, how do I best go about it?

My current code:

class FileParseError(...): pass

class NameDatabase:
    def __init__(self, fname):
        self.fname = fname
        try:
            self.conn = sqlite3.connect(fname)
            # Check if we can query the DB and if the schema is ok.
            self.conn.execute('SELECT count(*) FROM names')
        except sqlite3.Error:
            raise FileParseError("Not a valid database: '%s'", fname)

This works, but gives a double traceback (During handling of the above exception, another exception occurred). What I'd ideally have is the original traceback, along with an exception of my own type FileParseError (which can store some info about the original exception).

I know I can achieve something like that using sys.exc_info() in my exception handler and re-raising the exception once outside, but that seems messy, as I have to set a flag in the except clause to remember to handle the error afterwards. Is there a better way?

exc = None
try:
    ...
except sqlite3.Error:
    exc = sys.exc_info()
if exc:
    raise FileParseError("Invalid DB '%s': %s", fname, str(exc[1])), None, exc[2]

Platform: CPython 3.3 on Windows.

Similar question: 10555671 is exactly what I have now. I'm looking for something avoiding the double-traceback while also avoiding the manual exc flag setting and rechecking-after-except.

0

1 Answer 1

5

It looks like you want to chain your new exception off of the existing one. That can be done, using syntax introduced with PEP 3134. Alternatively, you could completely suppress the previous exception, using syntax added in PEP 409.

To chain the exceptions, give the caught exception a name with as in the except statement, then use the name with from at the end of your raise statement. Something like this:

except sqlite3.Error as e:
    raise FileParseError("Not a valid database: '%s'", fname) from e

If you want to completely suppress the sqlite error, rather than just translating it to another exception type, you can use from None in the raise statement:

except sqlite3.Error:
    raise FileParseError("Not a valid database: '%s'", fname) from None

The internal exception is still available when the context has been suppressed, but it will not be printed out in the traceback.

If you want to understand the issue of exception chaining in more detail, you should read the PEPs linked above, and perhaps also PEP 415 which describes the updated implementation of PEP 409.

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

1 Comment

One of those time machine moments for me, I didn't think there'd be such an elegant answer. I suggest everyone re-read What's new in Py3.3, there's lots of good stuff there: SimpleNamespace, faulthandler, venv, py on Windows, ...

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.