How to handle chained exception

I would like to handle a type of exception, but only in the case that it came chained from another specific type of exception. I am confused on how it should be done.


def raise_valueerr_from_oserr():
    raise PermissionError("Booo!")
  except OSError as _:
    raise ValueError('Aaaaah!') from _

except ValueError as _:
  if isinstance(_.__cause__, OSError):  # Here
    print('ValueError from OSError')
  raise _

Question: Should I use __cause__, or __context__, or something else?

I am not entirely sure if I understood the implicit vs explicit in here. Let’s see if I got it. If I am sure they did from _ in raise_valueerr_from_oserr, then looking at __cause__ is enough, but if not sure I should look in __context__.

Concrete situation

import os
from logging.config import dictConfig

LOG_FILE = os.getenv('LOG_FILE', '/log_file.log')

            'version': 1,
            'formatters': {
                'default': {
                    'format': '%(asctime)s [%(levelname)s] %(message)s',
            'handlers': {
                'default': {
                    'class': 'logging.FileHandler',
                    'filename': LOG_FILE,
                    'formatter': 'default'
            'root': {
                'level': 'INFO',
                'handlers': ['default']

The exception that happens when logging cannot write to LOG_FILE is a ValueError chained from a PermissionError.

I wanted to handle this, to inform that the environment variable LOG_FILE should be changed.

I didn’t want to handle all ValueError coming from this portion of code, to not obscure any other potential problem being raised with that same type of exception.

If you raise X from Y then __cause__ will be set (and will be equal to __context__). If you simply do raise X then __context__ will be set, but __cause__ won’t be.
You can verify by tinkering with your code:

def absurd(chain=False):
        assert 0 == 1
    except Exception as e:
        if chain:
            raise ValueError('chained') from e
            raise ValueError('unchained')

def catch_absurdities(chain=False):
    except Exception as e:
        if chain:
            raise RuntimeError(f'caught chained = {chain}') from e
            raise RuntimeError(f'caught chained = {chain}')

def test(chain=False):
    except Exception as e:
       return e

e1 = test(False)
assert e1.__context__.__class__ is ValueError
assert e1.__context__.__context__.__class__ is AssertionError
assert e1.__cause__ is None
assert e1.__context__.__cause__ is None

e2 = test(True)
assert e2.__context__.__class__ is ValueError
assert e2.__context__.__context__.__class__ is AssertionError
assert e2.__cause__ is e2.__context__
assert e2.__cause__.__cause__ is e2.__context__.__context__

In my case the code raising the exception is not mine.

Then, I suppose the cautious way would be to inspect __context__.

Another thing that I need to consider is if I care about the depth of when the OSError got chained or not,
check _.__context__ vs _.__context__.__context__, … etc.

I have a feeling that you want to/are trying to do sth that is too complicated for its own good. Of course I don’t know what that “good” would be, but I’ve never had an occassion where I needed to handle this kind of deeply nested exceptions. The default option in Python scripts is also to “just let it come down”, let the exceptions be thrown, don’t catch any, especially if they’re coming from another library. In this case, are you sure your code would be able to actually handle the special exception?

Yes, let me get rid of the XY Problem and add the more concrete situation to the post.

Ok… So, there are always multiple ways to try to prevent or to deal with these kind of mishaps.

If you know in advance that there may be permission issues with the log file, then you could
explicitly check for that as soon as that variable is defined. (This is what I would do, since you then stays as close as possible to the fracture and don’t have to complicate the rest of the code.)

Otherwise, if you catch the potential ValueError exc of logging.dictConfig, then you could distinguish the case were exc.__cause__ is set and is a PermissionError (cause will be set in that case) from other situations. In that case exc.__cause__ (I just found out) also has a exc.__cause__.filename2 attribute that contains the path to the filename. So, you could do sth like:

except ValueError as exc:
     cause = exc.__cause__
     if cause is None:  # don't know what to do
     if not isinstance(cause, PermissionError):  # same
     print(f"PermissionError: Can not write to {cause.filename2}")
     # could investigate futher and see why not, or just guess
     # that it's a non-writable folder or file
     print("Please make that file writable.")

I have seen the advice to not go this route, since things might change from when I check myself, to when logging tries to access the file.

I opted for OSError in case logging could not use the file for other reason other than permissions.

And this one … I currently have it inspecting __context__. I was being cautious of whether inside logging they could forget or not to use from.

But maybe you are right and the correct attitude is to trust. That way if something changes in logging, the unhandled exception lets me know.

Thank you.

My two main principles are:

  • Keep the code as simple as possible
  • If code will be used by other people (or on some organization’s server etc), try to ensure that
    all external data (env variables, file paths) as defined by a user will be vetted before it is actually used in the code.

As to

I have seen the advice to not go this route, since things might change from when I check myself, to when logging tries to access the file.

Really, that should be seen as a bug. Constants should remain constant :slight_smile:

What they meant in the advice was that a file could be readable one time, and not at some other time; the drive could disconnect, or some other process could modify it, things like that.