Repository: twisted/twisted

Issue:
Update docstring handling for Python 3.13
In Python 3.13 the docstring are trimmed by default.

We need to update some of the deprecation helpers to work with this.

Discussion:

Patch:
diff --git a/src/twisted/newsfragments/12063.removal b/src/twisted/newsfragments/12063.removal
new file mode 100644
index 0000000000..2b908c808b
--- /dev/null
+++ b/src/twisted/newsfragments/12063.removal
@@ -0,0 +1,2 @@
+twisted.python.deprecate helper function will now always strip whitespaces from the docstrings.
+This is done to have the same behaviour as with Python 3.13.
diff --git a/src/twisted/python/deprecate.py b/src/twisted/python/deprecate.py
index 7f7b616f79..aba096d59c 100644
--- a/src/twisted/python/deprecate.py
+++ b/src/twisted/python/deprecate.py
@@ -259,8 +259,13 @@ def _appendToDocstring(thingWithDoc, textToAppend):
     elif len(docstringLines) == 1:
         docstringLines.extend(["", textToAppend, ""])
     else:
-        spaces = docstringLines.pop()
+        trailer = docstringLines[-1]
+        spaces = ""
+        if not trailer.strip():
+            # Deal with differences between Python 3.13 and older versions.
+            spaces = docstringLines.pop()
         docstringLines.extend(["", spaces + textToAppend, spaces])
+        docstringLines = [l.lstrip(" ") for l in docstringLines]
     thingWithDoc.__doc__ = "\n".join(docstringLines)
 
 


Test patch:
diff --git a/src/twisted/python/test/test_deprecate.py b/src/twisted/python/test/test_deprecate.py
index 2c43b91c86..8fbae87a35 100644
--- a/src/twisted/python/test/test_deprecate.py
+++ b/src/twisted/python/test/test_deprecate.py
@@ -876,12 +876,11 @@ class DeprecatedDecoratorTests(SynchronousTestCase):
         self.assertEqual(
             dummy.__doc__,
             "\n"
-            "    Do nothing.\n\n"
-            "    This is used to test the deprecation decorators.\n\n"
-            "    Deprecated in Twisted 8.0.0; please use "
+            "Do nothing.\n\n"
+            "This is used to test the deprecation decorators.\n\n"
+            "Deprecated in Twisted 8.0.0; please use "
             "something.foobar"
-            " instead.\n"
-            "    ",
+            " instead.\n",
         )
 
     def test_deprecatedReplacementWithCallable(self):
@@ -897,11 +896,10 @@ class DeprecatedDecoratorTests(SynchronousTestCase):
         self.assertEqual(
             dummy.__doc__,
             "\n"
-            "    Do nothing.\n\n"
-            "    This is used to test the deprecation decorators.\n\n"
-            "    Deprecated in Twisted 8.0.0; please use "
-            "%s.dummyReplacementMethod instead.\n"
-            "    " % (__name__,),
+            "Do nothing.\n\n"
+            "This is used to test the deprecation decorators.\n\n"
+            "Deprecated in Twisted 8.0.0; please use "
+            "{}.dummyReplacementMethod instead.\n".format(__name__),
         )
 
     def test_deprecatedKeywordParameter(self):
@@ -993,15 +991,11 @@ class AppendToDocstringTests(SynchronousTestCase):
             This is a multi-line docstring.
             """
 
-        def expectedDocstring():
-            """
-            This is a multi-line docstring.
-
-            Appended text.
-            """
-
         _appendToDocstring(multiLineDocstring, "Appended text.")
-        self.assertEqual(expectedDocstring.__doc__, multiLineDocstring.__doc__)
+        self.assertEqual(
+            "\n" "This is a multi-line docstring.\n" "\n" "Appended text.\n",
+            multiLineDocstring.__doc__,
+        )
 
 
 class MutualArgumentExclusionTests(SynchronousTestCase):