Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-81087: Add str.dedent and cache it at compile time #13445

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
13 changes: 13 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,19 @@ expression support in the :mod:`re` module).
interpreted as in slice notation.


.. method:: str.dedent()

Return without any common leading whitespace from every line.

This can be used to make triple-quoted strings line up with the left edge of the
display, while still presenting them in the source code in indented form.

Note that tabs and spaces are both treated as whitespace, but they are not
equal: the lines ``" hello"`` and ``"\thello"`` are considered to have no
common leading whitespace.

.. versionadded:: 3.10

.. method:: str.removeprefix(prefix, /)

If the string starts with the *prefix* string, return
Expand Down
1 change: 1 addition & 0 deletions Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1324,3 +1324,4 @@ def translate(self, *args):
return self.__class__(self.data.translate(*args))
def upper(self): return self.__class__(self.data.upper())
def zfill(self, width): return self.__class__(self.data.zfill(width))
def dedent(self): return self.__class__(self.data.dedent())
9 changes: 4 additions & 5 deletions Lib/distutils/tests/test_build_ext.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import sys
import os
from io import StringIO
import textwrap

from distutils.core import Distribution
from distutils.command.build_ext import build_ext
Expand Down Expand Up @@ -82,7 +81,7 @@ def test_build_ext(self):
else:
ALREADY_TESTED = type(self).__name__

code = textwrap.dedent(f"""
code = f"""
tmp_dir = {self.tmp_dir!r}

import sys
Expand All @@ -108,7 +107,7 @@ def test_xx(self):


unittest.main()
""")
""".dedent()
assert_python_ok('-c', code)

def test_solaris_enable_shared(self):
Expand Down Expand Up @@ -474,7 +473,7 @@ def _try_compile_deployment_target(self, operator, target):
deptarget_c = os.path.join(self.tmp_dir, 'deptargetmodule.c')

with open(deptarget_c, 'w') as fp:
fp.write(textwrap.dedent('''\
fp.write(('''\
#include <AvailabilityMacros.h>

int dummy;
Expand All @@ -484,7 +483,7 @@ def _try_compile_deployment_target(self, operator, target):
#error "Unexpected target"
#endif

''' % operator))
''' % operator).dedent())

# get the deployment target that the interpreter was built with
target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET')
Expand Down
9 changes: 4 additions & 5 deletions Lib/distutils/tests/test_check.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Tests for distutils.command.check."""
import os
import textwrap
import unittest
from test.support import run_unittest

Expand Down Expand Up @@ -118,22 +117,22 @@ def test_check_restructuredtext_with_syntax_highlight(self):
# Don't fail if there is a `code` or `code-block` directive

example_rst_docs = []
example_rst_docs.append(textwrap.dedent("""\
example_rst_docs.append("""\
Here's some code:

.. code:: python

def foo():
pass
"""))
example_rst_docs.append(textwrap.dedent("""\
""".dedent())
example_rst_docs.append("""\
Here's some code:

.. code-block:: python

def foo():
pass
"""))
""".dedent())

for rest_with_code in example_rst_docs:
pkg_info, dist = self.create_dist(long_description=rest_with_code)
Expand Down
5 changes: 2 additions & 3 deletions Lib/distutils/tests/test_dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import sys
import unittest
import warnings
import textwrap

from unittest import mock

Expand Down Expand Up @@ -421,11 +420,11 @@ def test_download_url(self):
self.assertIn('Metadata-Version: 1.1', meta)

def test_long_description(self):
long_desc = textwrap.dedent("""\
long_desc = """\
example::
We start here
and continue here
and end here.""")
and end here.""".dedent()
attrs = {"name": "package",
"version": "1.0",
"long_description": long_desc}
Expand Down
5 changes: 2 additions & 3 deletions Lib/distutils/tests/test_sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import warnings
import zipfile
from os.path import join
from textwrap import dedent
from test.support import captured_stdout, check_warnings, run_unittest

try:
Expand Down Expand Up @@ -393,11 +392,11 @@ def test_manifest_marker(self):
@unittest.skipUnless(ZLIB_SUPPORT, "Need zlib support to run")
def test_manifest_comments(self):
# make sure comments don't cause exceptions or wrong includes
contents = dedent("""\
contents = """\
# bad.py
#bad.py
good.py
""")
""".dedent()
dist, cmd = self.get_cmd()
cmd.ensure_finalized()
self.write_file((self.tmp_dir, cmd.manifest), contents)
Expand Down
5 changes: 2 additions & 3 deletions Lib/distutils/tests/test_sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import shutil
import subprocess
import sys
import textwrap
import unittest

from distutils import sysconfig
Expand Down Expand Up @@ -249,13 +248,13 @@ def test_customize_compiler_before_get_config_vars(self):
# instance can be called without an explicit call to
# get_config_vars().
with open(TESTFN, 'w') as f:
f.writelines(textwrap.dedent('''\
f.writelines('''\
from distutils.core import Distribution
config = Distribution().get_command_obj('config')
# try_compile may pass or it may fail if no compiler
# is found but it should not raise an exception.
rc = config.try_compile('int x;')
'''))
'''.dedent())
p = subprocess.Popen([str(sys.executable), TESTFN],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
Expand Down
5 changes: 2 additions & 3 deletions Lib/idlelib/idle_test/htest.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def _wrapper(parent): # htest #

import idlelib.pyshell # Set Windows DPI awareness before Tk().
from importlib import import_module
import textwrap
import tkinter as tk
from tkinter.ttk import Scrollbar
tk.NoDefaultRoot()
Expand Down Expand Up @@ -209,7 +208,7 @@ def _wrapper(parent): # htest #
_linenumbers_drag_scrolling_spec = {
'file': 'sidebar',
'kwds': {},
'msg': textwrap.dedent("""\
'msg': """\
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like having '.dedent moved from here to the end 16 lines down.

In any case, I want both idlelib changes removed from this PR to avoid gratuitous differences between versions.

1. Click on the line numbers and drag down below the edge of the
window, moving the mouse a bit and then leaving it there for a while.
The text and line numbers should gradually scroll down, with the
Expand All @@ -223,7 +222,7 @@ def _wrapper(parent): # htest #
numbers should gradually scroll up, with the selection updated
continuously.

4. Repeat step #2, clicking a line number below the selection."""),
4. Repeat step #2, clicking a line number below the selection.""".dedent(),
}

_multi_call_spec = {
Expand Down
2 changes: 1 addition & 1 deletion Lib/idlelib/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def fix_scaling(root):

def fixdoc(fun, text):
tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else ''
fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text))
fun.__doc__ = tem + textwrap.fill(text.dedent())

RECURSIONLIMIT_DELTA = 30

Expand Down
3 changes: 1 addition & 2 deletions Lib/lib2to3/tests/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import unittest
import os
import os.path
from textwrap import dedent

# Local imports
from lib2to3 import pytree, refactor
Expand All @@ -32,7 +31,7 @@ def run_all_tests(test_mod=None, tests=None):
unittest.TextTestRunner(verbosity=2).run(tests)

def reformat(string):
return dedent(string) + "\n\n"
return string.dedent() + "\n\n"

def get_refactorer(fixer_pkg="lib2to3", fixers=None, options=None):
"""
Expand Down
2 changes: 1 addition & 1 deletion Lib/lib2to3/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ def test_extended_unpacking(self):
class TestLiterals(GrammarTest):

def validate(self, s):
driver.parse_string(support.dedent(s) + "\n\n")
driver.parse_string(s.dedent() + "\n\n")

def test_multiline_bytes_literals(self):
s = """
Expand Down
3 changes: 1 addition & 2 deletions Lib/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,8 +635,7 @@ def _script():
else:
sys.exit(3)
else:
import textwrap
print(textwrap.dedent(help % (sys.argv[0], os.pathsep)))
print((help % (sys.argv[0], os.pathsep)).dedent())
sys.exit(10)

if __name__ == '__main__':
Expand Down
5 changes: 2 additions & 3 deletions Lib/test/mod_generics_cache.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""Module for testing the behavior of generics across different modules."""

import sys
from textwrap import dedent
from typing import TypeVar, Generic, Optional


if sys.version_info[:2] >= (3, 6):
exec(dedent("""
exec("""
default_a: Optional['A'] = None
default_b: Optional['B'] = None

Expand All @@ -24,7 +23,7 @@ class A(Generic[T]):
my_inner_a1: 'B.A'
my_inner_a2: A
my_outer_a: 'A' # unless somebody calls get_type_hints with localns=B.__dict__
"""))
""".dedent())
else: # This should stay in sync with the syntax above.
__annotations__ = dict(
default_a=Optional['A'],
Expand Down
9 changes: 4 additions & 5 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import threading
import unittest
import weakref
from textwrap import dedent
from http.cookies import SimpleCookie

try:
Expand Down Expand Up @@ -1353,16 +1352,16 @@ def test_truncated_data(self):
@reap_threads
def test_unpickle_module_race(self):
# https://bugs.python.org/issue34572
locker_module = dedent("""
locker_module = """
import threading
barrier = threading.Barrier(2)
""")
locking_import_module = dedent("""
""".dedent()
locking_import_module = """
import locker
locker.barrier.wait()
class ToBeUnpickled(object):
pass
""")
""".dedent()

os.mkdir(TESTFN)
self.addCleanup(shutil.rmtree, TESTFN)
Expand Down
Loading