Skip to content

Commit f2dabea

Browse files
authored
Fix exception chaining on PyPy (#712)
* Fix exception chaining on PyPy Fixes #703 * Add newsfragment * Blankly exclude PyPy from coverage reporting * Manually add default no cover marker
1 parent 6b4a1f1 commit f2dabea

6 files changed

Lines changed: 64 additions & 7 deletions

File tree

changelog.d/703.change.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``raise from`` now works on frozen classes on PyPy.

changelog.d/712.change.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``raise from`` now works on frozen classes on PyPy.

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ source = ["src", ".tox/*/site-packages"]
1313

1414
[tool.coverage.report]
1515
show_missing = true
16+
exclude_lines = [
17+
"pragma: no cover",
18+
# PyPy is unacceptably slow under coverage.
19+
"if PYPY:",
20+
]
1621

1722

1823
[tool.black]

src/attr/_compat.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def metadata_proxy(d):
9191
res.data.update(d) # We blocked update, so we have to do it like this.
9292
return res
9393

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

137137
def set_closure_cell(cell, value):
138138
cell.__setstate__((value,))

src/attr/_make.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from . import _config, setters
1313
from ._compat import (
1414
PY2,
15+
PYPY,
1516
isclass,
1617
iteritems,
1718
metadata_proxy,
@@ -527,11 +528,29 @@ def _transform_attrs(
527528
return _Attributes((attrs, base_attrs, base_attr_map))
528529

529530

530-
def _frozen_setattrs(self, name, value):
531-
"""
532-
Attached to frozen classes as __setattr__.
533-
"""
534-
raise FrozenInstanceError()
531+
if PYPY:
532+
533+
def _frozen_setattrs(self, name, value):
534+
"""
535+
Attached to frozen classes as __setattr__.
536+
"""
537+
if isinstance(self, BaseException) and name in (
538+
"__cause__",
539+
"__context__",
540+
):
541+
BaseException.__setattr__(self, name, value)
542+
return
543+
544+
raise FrozenInstanceError()
545+
546+
547+
else:
548+
549+
def _frozen_setattrs(self, name, value):
550+
"""
551+
Attached to frozen classes as __setattr__.
552+
"""
553+
raise FrozenInstanceError()
535554

536555

537556
def _frozen_delattrs(self, name):

tests/test_next_gen.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import re
66

7+
from functools import partial
8+
79
import pytest
810

911
import attr
@@ -238,3 +240,32 @@ class B:
238240
@attr.define(on_setattr=attr.setters.validate)
239241
class C(A):
240242
pass
243+
244+
@pytest.mark.parametrize(
245+
"decorator",
246+
[
247+
partial(attr.s, frozen=True, slots=True, auto_exc=True),
248+
attr.frozen,
249+
attr.define,
250+
attr.mutable,
251+
],
252+
)
253+
def test_discard_context(self, decorator):
254+
"""
255+
raise from None works.
256+
257+
Regression test for #703.
258+
"""
259+
260+
@decorator
261+
class MyException(Exception):
262+
x: str = attr.ib()
263+
264+
with pytest.raises(MyException) as ei:
265+
try:
266+
raise ValueError()
267+
except ValueError:
268+
raise MyException("foo") from None
269+
270+
assert "foo" == ei.value.x
271+
assert ei.value.__cause__ is None

0 commit comments

Comments
 (0)