Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

raise_from does not behave exactly like raise from #141

Open
mbr opened this issue Apr 25, 2015 · 7 comments
Open

raise_from does not behave exactly like raise from #141

mbr opened this issue Apr 25, 2015 · 7 comments

Comments

@mbr
Copy link

mbr commented Apr 25, 2015

Here's a bit of sample code from [https://www.python.org/dev/peps/pep-3134/](PEP 3134):

class DatabaseError(Exception):
    pass

class FileDatabase(object):
    def __init__(self, filename):
        try:
            self.file = open(filename)
        except IOError as exc:
            raise DatabaseError('failed to open') from exc

fd = FileDatabase('dne')

This, of course, does not work on Python 2. Using Python 3 I get the following output:

Traceback (most recent call last):
  File "pepex2.py", line 7, in __init__
    self.file = open(filename)
FileNotFoundError: [Errno 2] No such file or directory: 'dne'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "pepex2.py", line 11, in <module>
    fd = FileDatabase('dne')
  File "pepex2.py", line 9, in __init__
    raise DatabaseError('failed to open') from exc
__main__.DatabaseError: failed to open

When using raise_from for Python2 compatibility

from future.utils import raise_from
# [...]
            raise_from(DatabaseError('failed to open'), exc)

this output (still with Python3) changes to:

FileNotFoundError: [Errno 2] No such file or directory

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "pepex2.py", line 13, in <module>
    fd = FileDatabase('dne')
  File "pepex2.py", line 11, in __init__
    raise_from(DatabaseError('failed to open'), exc)
  File "/home/marc/.virtualenvs/scratch_future3/lib/python3.3/site-packages/future/utils/__init__.py", line 382, in raise_from
    exec(execstr, myglobals, mylocals)
  File "<string>", line 1, in <module>
__main__.DatabaseError: failed to open

Note that the traceback and exception information are missing from the FileNotFoundError.

@ankostis
Copy link

Just confirming that ver 0.15.0 still is buggy.

And the consequences are not neglible since the root of the error is lost.

@edschofield
Copy link
Contributor

Thanks for the feedback, Kostis!

I think we've fixed now in v0.16.0 thanks to a new raise_from implementation on Py3. Could you please give it a whirl and let me know if it all looks good to you?

@ankostis
Copy link

My apologies, I cannot answer your questions - you see, lately have started moving away from py2/3 code towards py3-only code. The compatibility debt was too great to curry.

I'm very grateful for your efforts on this library.

@Stelminator
Copy link

Stelminator commented Jun 2, 2017

I have a potentially related problem with raise_from, specifically the behavior of __context__ on python2:

native python3

try:
    try:
        try:
            raise Exception('foo')
        except Exception as foo:
            raise Exception('bar') from foo
    except Exception as bar:
        raise bar from Exception('quux')
except Exception as bar_quux:
    last = bar_quux

print(last.args[0] == 'bar')
print(last.__cause__.args[0] == 'quux')
print(last.__context__.args[0] == 'foo')
print(last.__context__.args[0])

output:

True
True
True
foo

using raise_from

from future.utils import raise_from
try:
    try:
        try:
            raise Exception('foo')
        except Exception as foo:
            raise_from(Exception('bar'), foo)
    except Exception as bar:
        raise_from(bar, Exception('quux'))
except Exception as bar_quux:
    last = bar_quux

print(last.args[0] == 'bar')
print(last.__cause__.args[0] == 'quux')
print(last.__context__.args[0] == 'foo')
print(last.__context__.args[0])

python3 output:

True
True
True
foo

python2 output:

True
True
False
bar

so basically, the __context__ is being set when it already has been. In this case the bar exception is being set as its own __context__ on python2.

I think I have an appropriate fix in: Stelminator@42cee74

which is just:

-        e.__context__ = sys.exc_info()[1]
+        if not hasattr(e, '__context__'):
+            e.__context__ = sys.exc_info()[1]

@Stelminator
Copy link

Stelminator commented Jun 2, 2017

I suppose that's still not quite complete:

try:
    try:
        raise Exception('foo')
    except Exception as foo:
        try:
            raise Exception('bar') from foo
        except Exception as bar:
            print(bar.__context__)
            raise foo from bar
except Exception as foo:
    wat = foo
print(wat.__context__.__context__)
foo
None
#python2 with above fix
try:
    try:
        raise Exception('foo')
    except Exception as foo:
        try:
            raise_from(Exception('bar'), foo)
        except Exception as bar:
            print(bar.__context__)
            raise_from(foo, bar)
except Exception as foo:
    wat = foo
print(wat.__context__.__context__)
foo
foo

Edit: I this actually might be a bug in python3. I don't think __context__ is ever supposed to get reset to None (unless done explicitly).

@Stelminator
Copy link

crosslink to my report to python bug tracker: http://bugs.python.org/issue31213

@JoshRosen
Copy link

Cross-link to what looks (at first glance) to be a similar problem in six's Python 2 version of raise_from: benjaminp/six#193

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants