From 81ff6fcc0d1460d669d516dc4df6d90c39507cfa Mon Sep 17 00:00:00 2001 From: Will Greenberg Date: Tue, 24 Jan 2023 14:06:53 -0800 Subject: [PATCH] acme.messages.Error: add mutability (#9546) * acme.messages.Error: add mutability As of Python 3.11, an exception caught within a `with` statement will update the __traceback__ attribute. Because acme.messages.Error was immutable, this was causing a knock-on exception, causing certbot to exit abnormally. This commit hacks in mutability for acme.messages.Error Fixes #9539 * Add CHANGELOG entry --- acme/acme/messages.py | 7 +++++++ acme/tests/messages_test.py | 17 +++++++++++++++++ certbot/CHANGELOG.md | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/acme/acme/messages.py b/acme/acme/messages.py index 0e02a054e..c1af1991a 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -123,6 +123,9 @@ class Error(jose.JSONObjectWithFields, errors.Error): https://datatracker.ietf.org/doc/html/rfc7807 + Note: Although Error inherits from JSONObjectWithFields, which is immutable, + we add mutability for Error to comply with the Python exception API. + :ivar str typ: :ivar str title: :ivar str detail: @@ -185,6 +188,10 @@ class Error(jose.JSONObjectWithFields, errors.Error): return code return None + # Hack to allow mutability on Errors (see GH #9539) + def __setattr__(self, name: str, value: Any) -> None: + return object.__setattr__(self, name, value) + def __str__(self) -> str: result = b' :: '.join( part.encode('ascii', 'backslashreplace') for part in diff --git a/acme/tests/messages_test.py b/acme/tests/messages_test.py index cbff65771..7d70822a8 100644 --- a/acme/tests/messages_test.py +++ b/acme/tests/messages_test.py @@ -1,6 +1,7 @@ """Tests for acme.messages.""" from typing import Dict import unittest +import contextlib from unittest import mock import warnings @@ -91,6 +92,22 @@ class ErrorTest(unittest.TestCase): u"Problem for {1.identifier.value}: {1.typ} :: {1.description} :: {1.detail} :: {1.title}").format( self.error_with_subproblems, self.subproblem)) + # this test is based on a minimal reproduction of a contextmanager/immutable + # exception related error: https://github.com/python/cpython/issues/99856 + def test_with_context_manager(self): + from acme.messages import Error + + @contextlib.contextmanager + def context(): + yield + + try: + with context(): + raise self.error_custom + except Error as e: + self.assertIsNotNone(self.error_custom.__traceback__) + + class ConstantTest(unittest.TestCase): """Tests for acme.messages._Constant.""" diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 387373fff..763425f2d 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -17,7 +17,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Fixed a bug where Certbot would crash with `AttributeError: can't set attribute` on ACME server errors in Python 3.11. See [GH #9539](https://github.com/certbot/certbot/issues/9539). More details about these changes can be found on our GitHub repo.