jenkins-bot has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1237469?usp=email )
Change subject: [exceptions] Backport Exception.add_note() for Python < 3.11
......................................................................
[exceptions] Backport Exception.add_note() for Python < 3.11
Python 3.11 introduced the add_note() method to BaseException, allowing
context notes to be attached to exceptions. This patch adds a
backport in pywikibot.backports.BaseError and updates
pywikibot.exceptions.Error to inherit from it.
For Python versions < 3.11, notes are stored in a private list and
appended to the string representation of the error.
Bug: T416566
Change-Id: Ib7532686a5ec5481b0093d5466a25882f32199e0
---
M pywikibot/backports.py
M pywikibot/exceptions.py
M tests/__init__.py
A tests/exceptions_tests.py
4 files changed, 85 insertions(+), 2 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/backports.py b/pywikibot/backports.py
index d8b99d2..56d8bb6 100644
--- a/pywikibot/backports.py
+++ b/pywikibot/backports.py
@@ -202,3 +202,33 @@
else:
from threading import RLock # type: ignore[assignment]
+
+
+# gh-100588
+if PYTHON_VERSION < (3, 11) or SPHINX_RUNNING:
+ class BaseError(Exception):
+
+ """Backport for Python 3.11 Exception.add_note logic."""
+
+ def __init__(self, *args, **kwargs) -> None:
+ """Initializer."""
+ super().__init__(*args, **kwargs)
+ self.__notes__: list = []
+
+ def add_note(self, note: str) -> None:
+ """Add a note to the exception."""
+ if not isinstance(note, str):
+ raise TypeError(
+ f"note must be a str, not '{type(note).__name__}'")
+ self.__notes__.append(note)
+
+ def __str__(self) -> str:
+ """Return string representation including notes."""
+ s = super().__str__()
+ if self.__notes__:
+ return f'{s}\n' + '\n'.join(self.__notes__)
+ return s
+else:
+ class BaseError(Exception): # type: ignore[no-redef]
+
+ """BaseError alias for Python 3.11+."""
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py
index ac14c12..26aae4d 100644
--- a/pywikibot/exceptions.py
+++ b/pywikibot/exceptions.py
@@ -172,7 +172,7 @@
instead.
"""
#
-# (C) Pywikibot team, 2008-2025
+# (C) Pywikibot team, 2008-2026
#
# Distributed under the terms of the MIT license.
#
@@ -182,6 +182,8 @@
from typing import Any
import pywikibot
+from pywikibot.backports import BaseError as _BaseError
+from pywikibot.tools import PYTHON_VERSION
from pywikibot.tools._deprecate import _NotImplementedWarning
@@ -200,16 +202,23 @@
"""Family class is missing definitions."""
-class Error(Exception):
+class Error(_BaseError):
"""Pywikibot error."""
def __init__(self, arg: Exception | str) -> None:
"""Initializer."""
+ super().__init__(arg)
self.unicode = str(arg)
def __str__(self) -> str:
"""Return a string representation."""
+ # On Python < 3.11, we must manually append notes because this method
+ # overrides the BaseError.__str__ backport logic.
+ if PYTHON_VERSION < (3, 11) and hasattr(self, '__notes__') \
+ and self.__notes__:
+ return self.unicode + '\n' + '\n'.join(self.__notes__)
+
return self.unicode
diff --git a/tests/__init__.py b/tests/__init__.py
index 17c4cc2..56ba876 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -82,6 +82,7 @@
'edit',
'edit_failure',
'eventstreams',
+ 'exceptions',
'family',
'file',
'fixes',
diff --git a/tests/exceptions_tests.py b/tests/exceptions_tests.py
new file mode 100755
index 0000000..07a83ed
--- /dev/null
+++ b/tests/exceptions_tests.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+"""Tests for exceptions."""
+#
+# (C) Pywikibot team, 2026
+#
+# Distributed under the terms of the MIT license.
+#
+from __future__ import annotations
+
+from pywikibot.exceptions import Error
+from pywikibot.tools import PYTHON_VERSION
+from tests.aspects import TestCase
+
+
+class TestExceptionAddNote(TestCase):
+
+ """Test Error.add_note() backport logic."""
+
+ net = False
+
+ def test_add_note(self):
+ """Test that add_note appends text to the exception string."""
+ e = Error('Original Message')
+ e.add_note('Note 1')
+ e.add_note('Note 2')
+
+ # Check that notes are stored internally (all versions)
+ # In Py3.11+ this is native; in <3.11 it is our backport.
+ self.assertTrue(hasattr(e, '__notes__'))
+ self.assertEqual(e.__notes__, ['Note 1', 'Note 2'])
+
+ # Check string representation
+ # Case A: Python < 3.11 (Backport)
+ # We expect the note to be baked into str(e)
+ if PYTHON_VERSION < (3, 11):
+ expected = 'Original Message\nNote 1\nNote 2'
+ self.assertEqual(str(e), expected)
+
+ # Case B: Python 3.11+ (Native)
+ # We expect str(e) to ONLY be the message.
+ # The traceback printer handles the notes separately.
+ else:
+ self.assertEqual(str(e), 'Original Message')
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1237469?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Ib7532686a5ec5481b0093d5466a25882f32199e0
Gerrit-Change-Number: 1237469
Gerrit-PatchSet: 3
Gerrit-Owner: Anotida Expected <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]