From 4c820cd717aa2d629573fead373f42d59357a0d8 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 18 Apr 2025 03:15:23 +0100 Subject: [PATCH 1/5] Improve the error case for ``textwrap.dedent`` --- Lib/textwrap.py | 6 +++++- Misc/NEWS.d/3.14.0a7.rst | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index bac98c99e41df8..7e9a163f76b419 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -429,7 +429,11 @@ def dedent(text): if not text: return text - lines = text.split('\n') + try: + lines = text.split('\n') + except AttributeError: + msg = f'expected str object, not {type(text).__qualname__!r}' + raise TypeError(msg) from None # Get length of leading whitespace, inspired by ``os.path.commonprefix()``. non_blank_lines = [l for l in lines if l and not l.isspace()] diff --git a/Misc/NEWS.d/3.14.0a7.rst b/Misc/NEWS.d/3.14.0a7.rst index 900cde10641978..35b96d33da4175 100644 --- a/Misc/NEWS.d/3.14.0a7.rst +++ b/Misc/NEWS.d/3.14.0a7.rst @@ -288,7 +288,7 @@ Improve the import time of the :mod:`ast` module by extracting the .. nonce: 8M-HVz .. section: Library -Improved performance of :func:`textwrap.dedent` by an average of ~1.3x. +Improved performance of :func:`textwrap.indent` by an average of ~1.3x. Patch by Adam Turner. .. From d236d0cf947b9433c94f8354aef303c48867b1a0 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 18 Apr 2025 16:53:41 +0100 Subject: [PATCH 2/5] Add a test --- Lib/test/test_textwrap.py | 4 ++++ Lib/textwrap.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_textwrap.py b/Lib/test/test_textwrap.py index 77366988b57fa7..df562c24bdba26 100644 --- a/Lib/test/test_textwrap.py +++ b/Lib/test/test_textwrap.py @@ -765,6 +765,10 @@ def test_subsequent_indent(self): # of IndentTestCase! class DedentTestCase(unittest.TestCase): + def test_type_error(self): + with self.assertRaisesRegex(TypeError, "expected str object, not"): + dedent(0) + def assertUnchanged(self, text): """assert that dedent() has no effect on 'text'""" self.assertEqual(text, dedent(text)) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 7e9a163f76b419..c5217e8de8f76d 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -426,7 +426,7 @@ def dedent(text): Entirely blank lines are normalized to a newline character. """ - if not text: + if text == '': return text try: From ec839ff387878e231c0356d6a189833349d4ee8d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 18 Apr 2025 17:04:37 +0100 Subject: [PATCH 3/5] Improve the error message for bytes --- Lib/test/test_textwrap.py | 3 +++ Lib/textwrap.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_textwrap.py b/Lib/test/test_textwrap.py index df562c24bdba26..cbd383ea4e2656 100644 --- a/Lib/test/test_textwrap.py +++ b/Lib/test/test_textwrap.py @@ -769,6 +769,9 @@ def test_type_error(self): with self.assertRaisesRegex(TypeError, "expected str object, not"): dedent(0) + with self.assertRaisesRegex(TypeError, "expected str object, not"): + dedent(b'') + def assertUnchanged(self, text): """assert that dedent() has no effect on 'text'""" self.assertEqual(text, dedent(text)) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index c5217e8de8f76d..fb42c43a697f19 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -431,7 +431,7 @@ def dedent(text): try: lines = text.split('\n') - except AttributeError: + except (AttributeError, TypeError): msg = f'expected str object, not {type(text).__qualname__!r}' raise TypeError(msg) from None From 1902d1c05634ede321118aec898d4674397e0529 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 18 Apr 2025 18:23:44 +0100 Subject: [PATCH 4/5] Suppress BytesWarning --- Lib/test/test_textwrap.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_textwrap.py b/Lib/test/test_textwrap.py index cbd383ea4e2656..c53515e6d71c6d 100644 --- a/Lib/test/test_textwrap.py +++ b/Lib/test/test_textwrap.py @@ -9,6 +9,7 @@ # import unittest +from test.support import warnings_helper from textwrap import TextWrapper, wrap, fill, dedent, indent, shorten @@ -769,7 +770,9 @@ def test_type_error(self): with self.assertRaisesRegex(TypeError, "expected str object, not"): dedent(0) - with self.assertRaisesRegex(TypeError, "expected str object, not"): + # Suppress BytesWarning + with (warnings_helper.check_warnings(('', BytesWarning), quiet=True), + self.assertRaisesRegex(TypeError, "expected str object, not")): dedent(b'') def assertUnchanged(self, text): From a996f000bdc801a2716e7583c000d0a18b73e825 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:30:06 +0100 Subject: [PATCH 5/5] Remove fast path --- Lib/test/test_textwrap.py | 5 +---- Lib/textwrap.py | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/test/test_textwrap.py b/Lib/test/test_textwrap.py index c53515e6d71c6d..cbd383ea4e2656 100644 --- a/Lib/test/test_textwrap.py +++ b/Lib/test/test_textwrap.py @@ -9,7 +9,6 @@ # import unittest -from test.support import warnings_helper from textwrap import TextWrapper, wrap, fill, dedent, indent, shorten @@ -770,9 +769,7 @@ def test_type_error(self): with self.assertRaisesRegex(TypeError, "expected str object, not"): dedent(0) - # Suppress BytesWarning - with (warnings_helper.check_warnings(('', BytesWarning), quiet=True), - self.assertRaisesRegex(TypeError, "expected str object, not")): + with self.assertRaisesRegex(TypeError, "expected str object, not"): dedent(b'') def assertUnchanged(self, text): diff --git a/Lib/textwrap.py b/Lib/textwrap.py index fb42c43a697f19..00465f67d0941a 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -426,9 +426,6 @@ def dedent(text): Entirely blank lines are normalized to a newline character. """ - if text == '': - return text - try: lines = text.split('\n') except (AttributeError, TypeError):