Skip to content

treyhunner/better-dedent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

better dedent

PyPI - Version PyPI - Python Version Code Coverage


It's like textwrap.dedent, but with it works better.

This only supports Python 3.14 (which is not yet released) because it relies on t-strings.

The problem: interpolating before dedenting

Have you ever used textwrap.dedent with an f-string that has newline characters in a replacement field?

For example, given this code string (which has newlines in it):

code = r"""
def strip_each(lines):
    new_lines = []
    for line in lines:
        new_lines.append(line.rstrip("\n"))
    return new_lines
""".strip("\n")

Using textwrap.dedent with an f-string that uses code in a replacement field results in very strange indentation:

>>> print(dedent(f"""\
...     Example function:
...         {code}
...
...     That function was NOT indented properly!"""))
Example function:
    def strip_each(lines):
new_lines = []
for line in lines:
    new_lines.append(line.rstrip("\n"))
return new_lines

The problem is that f-strings immediately interpolate their replacement fields.

That code string is injected into the new string before dedent has a chance to even look at the string. By the time textwrap.dedent does its dedenting, the weirdness has already happened.

The solution: interpolating after dedenting

Passing a t-string to the better_dedent.dedent function allows the replacement fields to maintain their original indentation level.

Using the same code string as before:

code = r"""
def strip_each(lines):
    new_lines = []
    for line in lines:
        new_lines.append(line.rstrip("\n"))
    return new_lines
""".strip("\n")

The better_dedent.dedent function will dedent the t-string and then inject the replacement field, resulting in much more sensible indentation:

>>> print(dedent(t"""\
...     Example function:
...         {code}
...
...     That function was indented properly!""")
...
Example function:
    def strip_each(lines):
        new_lines = []
        for line in lines:
            new_lines.append(line.rstrip("\n"))
        return new_lines

That function was indented properly!

Using a t-string allows for dedenting the whole string before the replacement fields are inserted and then inserting the replacement fields.

Note that if an f-string is passed to better_dedent.dedent, it will simply delegate to textwrap.dedent.

undent

This package also includes an undent function, which will strip a leading newline (note the lack of \ after t"""):

>>> print(undent(t"""
...     Example function:
...         {code}
...     That function was indented properly!"""))
Example function:
    def strip_each(lines):
        new_lines = []
        for line in lines:
            new_lines.append(line.rstrip("\n"))
        return new_lines
That function was indented properly!

By default, the undent function will also strip a trailing newline:

>>> print(undent(t"""
...     Example function:
...         {code}
...     That function was indented properly!
... """))
Example function:
    def strip_each(lines):
        new_lines = []
        for line in lines:
            new_lines.append(line.rstrip("\n"))
        return new_lines
That function was indented properly!
>>> print("Note that there's no blank line above this prompt")
Note that there's no blank line above this prompt

Passing strip_trailing=False to undent will suppress trailing newline removal.

Installation

You install better-dedent with pip (you'll need to be on Python 3.14):

pip install better-dedent

Or if you have uv installed and you'd like to play with it right now (Python 3.14 will be auto-installed):

uvx --with better-dedent python

You can then import dedent and undent like this:

from better_dedent import dedent, undent

And try them out:

code = r"""
def strip_each(lines):
    new_lines = []
    for line in lines:
        new_lines.append(line.rstrip("\n"))
    return new_lines
""".strip("\n")

text = undent(t"""
    Here is some example code:

        {code}

    That indentation worked out nicely!
""")
print(text)

Testing

This project uses hatch.

To run the tests:

hatch test

To see code coverage:

hatch test --cover
hatch run cov-html
open htmlcov/index.html

License

better-dedent is distributed under the terms of the MIT license.

About

textwrap.dedent with t-string support

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages