Skip to content

Commit

Permalink
Fix exception chaining on PyPy (#712)
Browse files Browse the repository at this point in the history
* Fix exception chaining on PyPy

Fixes #703

* Add newsfragment

* Blankly exclude PyPy from coverage reporting

* Manually add default no cover marker
  • Loading branch information
hynek committed Nov 4, 2020
1 parent 6b4a1f1 commit f2dabea
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 7 deletions.
1 change: 1 addition & 0 deletions changelog.d/703.change.rst
@@ -0,0 +1 @@
``raise from`` now works on frozen classes on PyPy.
1 change: 1 addition & 0 deletions changelog.d/712.change.rst
@@ -0,0 +1 @@
``raise from`` now works on frozen classes on PyPy.
5 changes: 5 additions & 0 deletions pyproject.toml
Expand Up @@ -13,6 +13,11 @@ source = ["src", ".tox/*/site-packages"]

[tool.coverage.report]
show_missing = true
exclude_lines = [
"pragma: no cover",
# PyPy is unacceptably slow under coverage.
"if PYPY:",
]


[tool.black]
Expand Down
4 changes: 2 additions & 2 deletions src/attr/_compat.py
Expand Up @@ -91,7 +91,7 @@ def metadata_proxy(d):
res.data.update(d) # We blocked update, so we have to do it like this.
return res

def just_warn(*args, **kw): # pragma: nocover
def just_warn(*args, **kw): # pragma: no cover
"""
We only warn on Python 3 because we are not aware of any concrete
consequences of not setting the cell on Python 2.
Expand Down Expand Up @@ -132,7 +132,7 @@ def make_set_closure_cell():
"""
# pypy makes this easy. (It also supports the logic below, but
# why not do the easy/fast thing?)
if PYPY: # pragma: no cover
if PYPY:

def set_closure_cell(cell, value):
cell.__setstate__((value,))
Expand Down
29 changes: 24 additions & 5 deletions src/attr/_make.py
Expand Up @@ -12,6 +12,7 @@
from . import _config, setters
from ._compat import (
PY2,
PYPY,
isclass,
iteritems,
metadata_proxy,
Expand Down Expand Up @@ -527,11 +528,29 @@ def _transform_attrs(
return _Attributes((attrs, base_attrs, base_attr_map))


def _frozen_setattrs(self, name, value):
"""
Attached to frozen classes as __setattr__.
"""
raise FrozenInstanceError()
if PYPY:

def _frozen_setattrs(self, name, value):
"""
Attached to frozen classes as __setattr__.
"""
if isinstance(self, BaseException) and name in (
"__cause__",
"__context__",
):
BaseException.__setattr__(self, name, value)
return

raise FrozenInstanceError()


else:

def _frozen_setattrs(self, name, value):
"""
Attached to frozen classes as __setattr__.
"""
raise FrozenInstanceError()


def _frozen_delattrs(self, name):
Expand Down
31 changes: 31 additions & 0 deletions tests/test_next_gen.py
Expand Up @@ -4,6 +4,8 @@

import re

from functools import partial

import pytest

import attr
Expand Down Expand Up @@ -238,3 +240,32 @@ class B:
@attr.define(on_setattr=attr.setters.validate)
class C(A):
pass

@pytest.mark.parametrize(
"decorator",
[
partial(attr.s, frozen=True, slots=True, auto_exc=True),
attr.frozen,
attr.define,
attr.mutable,
],
)
def test_discard_context(self, decorator):
"""
raise from None works.
Regression test for #703.
"""

@decorator
class MyException(Exception):
x: str = attr.ib()

with pytest.raises(MyException) as ei:
try:
raise ValueError()
except ValueError:
raise MyException("foo") from None

assert "foo" == ei.value.x
assert ei.value.__cause__ is None

0 comments on commit f2dabea

Please sign in to comment.