Repository: python-attrs/attrs
Pull request #1365 (2024-10-26 09:30:46)

allow __suppress_context__ and __notes__ to be mutated on frozen exceptions
# Summary

<!-- Please tell us what your pull request is about here. -->


# Pull Request Check List

<!--
This is just a friendly reminder about the most common mistakes.
Please make sure that you tick all boxes.
But please read our [contribution guide](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) at least once, it will save you unnecessary review cycles!

If an item doesn't apply to your pull request, **check it anyway** to make it apparent that there's nothing left to do.
If your pull request is a documentation fix or a trivial typo, feel free to delete the whole thing.
-->

- [x] Do **not** open pull requests from your `main` branch – **use a separate branch**!
  - There's a ton of footguns waiting if you don't heed this warning. You can still go back to your project, create a branch from your main branch, push it, and open the pull request from the new branch.
  - This is not a pre-requisite for your pull request to be accepted, but **you have been warned**.
- [x] Added **tests** for changed code.
  Our CI fails if coverage is not 100%.
- [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/main/tests/strategies.py).
- [ ] Changes or additions to public APIs are reflected in our type stubs (files ending in ``.pyi``).
    - [ ] ...and used in the stub test file `tests/typing_example.py`.
    - [ ] If they've been added to `attr/__init__.pyi`, they've *also* been re-imported in `attrs/__init__.pyi`.
- [ ] Updated **documentation** for changed code.
    - [ ] New functions/classes have to be added to `docs/api.rst` by hand.
    - [ ] Changes to the signatures of `@attr.s()` and `@attrs.define()` have to be added by hand too.
    - [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded).
          The next version is the second number in the current release + 1.
          The first number represents the current year.
          So if the current version on PyPI is 22.2.0, the next version is gonna be 22.3.0.
          If the next version is the first in the new year, it'll be 23.1.0.
      - [ ] If something changed that affects both `attrs.define()` and `attr.s()`, you have to add version directives to both.
- [ ] Documentation in `.rst` and `.md` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/).
- [x] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/main/changelog.d).
- [x] Consider granting [push permissions to the PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork), so maintainers can fix minor issues themselves without pestering you.

<!--
If you have *any* questions to *any* of the points above, just **submit and ask**!
This checklist is here to *help* you, not to deter you from contributing!
-->



Patch:
diff --git a/changelog.d/1365.change.md b/changelog.d/1365.change.md
new file mode 100644
index 0000000..24fa6cf
--- /dev/null
+++ b/changelog.d/1365.change.md
@@ -0,0 +1,1 @@
+Allow mutating `__suppress_context__` and `__notes__` on frozen exceptions.
diff --git a/src/attr/_make.py b/src/attr/_make.py
index 274d412..f137772 100644
--- a/src/attr/_make.py
+++ b/src/attr/_make.py
@@ -543,6 +543,8 @@ def _frozen_setattrs(self, name, value):
         "__cause__",
         "__context__",
         "__traceback__",
+        "__suppress_context__",
+        "__notes__",
     ):
         BaseException.__setattr__(self, name, value)
         return
@@ -554,6 +556,10 @@ def _frozen_delattrs(self, name):
     """
     Attached to frozen classes as __delattr__.
     """
+    if isinstance(self, BaseException) and name in ("__notes__",):
+        BaseException.__delattr__(self, name)
+        return
+
     raise FrozenInstanceError
 
 


Test patch:
diff --git a/tests/test_next_gen.py b/tests/test_next_gen.py
index a171749..41e534d 100644
--- a/tests/test_next_gen.py
+++ b/tests/test_next_gen.py
@@ -14,6 +14,8 @@ import pytest
 import attr as _attr  # don't use it by accident
 import attrs
 
+from attr._compat import PY_3_11_PLUS
+
 
 @attrs.define
 class C:
@@ -332,7 +334,7 @@ class TestNextGen:
             attrs.mutable,
         ],
     )
-    def test_setting_traceback_on_exception(self, decorator):
+    def test_setting_exception_mutable_attributes(self, decorator):
         """
         contextlib.contextlib (re-)sets __traceback__ on raised exceptions.
 
@@ -354,6 +356,16 @@ class TestNextGen:
 
         # this should not raise an exception either
         ei.value.__traceback__ = ei.value.__traceback__
+        ei.value.__cause__ = ValueError("cause")
+        ei.value.__context__ = TypeError("context")
+        ei.value.__suppress_context__ = True
+        ei.value.__suppress_context__ = False
+        ei.value.__notes__ = []
+        del ei.value.__notes__
+
+        if PY_3_11_PLUS:
+            ei.value.add_note("note")
+            del ei.value.__notes__
 
     def test_converts_and_validates_by_default(self):
         """