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]

Reply via email to