Skip to main content

What textwrap.dedent should have been

Project description

dedent

What textwrap.dedent should have been.

Dedent and strip multiline strings, and keep interpolated values aligned—without manual whitespace wrangling. Supports t-strings (Python 3.14+) and f-strings (Python 3.10+).

For documentation on how to use dedent with f-strings in Python 3.10-3.13, see Legacy Support.

Table of Contents

Usage

from dedent import dedent

name = "Alice"
greeting = dedent(t"""
    Hello, {name}!
    Welcome to the party.
""")
print(greeting)
# Hello, Alice!
# Welcome to the party.

# Nested multiline strings align correctly with :align
items = dedent("""
    - apples
    - bananas
""")
shopping_list = dedent(t"""
    Groceries:
        {items:align}
    ---
""")
print(shopping_list)
# Groceries:
#     - apples
#     - bananas
# ---

Installation

# Using uv (Recommended)
uv add dedent

# Using pip
pip install dedent

Options

align

When an interpolation evaluates to a multiline string, only its first line is placed where the {...} appears. Subsequent lines keep whatever indentation they already had (often none), so they can appear "shifted left". Alignment fixes this by indenting subsequent lines to match the first.

Format Spec Directives

Requires Python 3.14+ (t-strings).

Use format spec directives inside t-strings for per-value control:

  • {value:align} - Align this multiline value to the current indentation
  • {value:noalign} - Disable alignment for this value [^1]
  • {value:align:06d} - Combine with other format specs [^2]
from dedent import dedent

items = dedent("""
    - one
    - two
""")

result = dedent(t"""
    Aligned:
        {items:align}
    Not aligned:
        {items}
""")

print(result)
# Aligned:
#     - one
#     - two
# Not aligned:
#     - one
# - two

[^1]: Only has an effect when using the align=True argument.

[^2]: This rarely makes sense, unless you are also using custom format specifications, but nonetheless works.

align Argument

Requires Python 3.14+ (t-strings).

Pass align=True to enable alignment globally for all t-string interpolations. Format spec directives override this.

from dedent import dedent

items = dedent("""
    - one
    - two
""")

result = dedent(
    t"""
        List 1:
            {items}
        List 2:
            {items}
        ---
    """,
    align=True,
)

print(result)
# List 1:
#     - one
#     - two
# List 2:
#     - one
#     - two
# ---

strip

The strip parameter controls how leading and trailing whitespace is removed after dedenting. It accepts three modes:

"smart" (default)

Strips one leading and trailing newline-bounded blank segment. Handles the common case of triple-quoted strings that start and end with a newline.

from dedent import dedent

result = dedent("""
    hello!
""")

print(repr(result))
# 'hello!'

"all"

Strips all surrounding whitespace, equivalent to calling .strip() on the result. Use when the string may have extra blank lines you want removed.

from dedent import dedent

result = dedent(
    """


        hello!


    """,
    strip="all",
)
print(repr(result))
# 'hello!'

"none"

Leaves whitespace exactly as-is after dedenting. Use when you need to preserve exact whitespace, e.g. for diff output or tests.

from dedent import dedent

result = dedent(
    """
        hello!
    """,
    strip="none",
)

print(repr(result))
# '\nhello!\n'

Legacy Support (Python 3.10-3.13)

On Python 3.10-3.13, t-strings are not available. Use dedent() on plain strings and f-strings, and wrap interpolated values with align() to get multiline indentation alignment.

from dedent import align, dedent

# dedent works on regular strings, like textwrap.dedent
message = dedent("""
    Hello,
    World!
""")
print(message)
# Hello,
# World!

# Use align() inside f-strings for multiline value alignment
items = dedent("""
    - apples
    - bananas
""")
shopping_list = dedent(f"""
    Groceries:
        {align(items)}
    ---
""")
print(shopping_list)
# Groceries:
#     - apples
#     - bananas
# ---

Per-value control with align() mirrors the format spec directives available on 3.14+:

from dedent import align, dedent

items = dedent("""
    - one
    - two
""")

result = dedent(f"""
    Aligned:
        {align(items)}
    Not aligned:
        {items}
""")

print(result)
# Aligned:
#     - one
#     - two
# Not aligned:
#     - one
# - two

There is no equivalent of the align argument in Python 3.10-3.13. There is no way to automatically align multiline values when using f-strings.

Why textwrap.dedent Falls Short

If you're here, then you're probably already familiar with the shortcomings of textwrap.dedent. But regardless, let's spell it out for the sake of completeness. For example, say we want to create a nicely formatted shopping list that includes some groceries:

from textwrap import dedent

groceries = dedent("""
    - apples
    - bananas
    - cherries
""")

shopping_list = dedent(f"""
    Groceries:
        {groceries}
    ---
""")

print(shopping_list)
#
#     Groceries:
#
# - apples
# - bananas
# - cherries
#
#     ---

Wait, that's not what we wanted. We accidentally included leading and trailing newlines from the groceries string. Now, we could do that manually by removing, escaping, or stripping the newlines, but it's either easy to forget, difficult to read, or unnecessarily verbose.

# Removing the newlines
groceries = dedent("""    - apples
    - bananas
    - cherries""")

# Escaping the newlines
groceries = dedent("""\
    - apples
    - bananas
    - cherries\
""")

# Stripping the newlines
groceries = dedent("""
    - apples
    - bananas
    - cherries
""".strip("\n"))

# But the shopping list still comes out wrong:
#     Groceries:
#         - apples
# - bananas
# - cherries
#     ---

Uh oh, something is still wrong; the indentation is not correct at all. The interpolation happens too early. When we use an f-string with textwrap.dedent, the replacement occurs before dedenting can take place. Notice how only the first line of groceries is properly indented relative to the surrounding text? The subsequent lines lose their indentation because f-strings interpolate immediately, injecting the groceries string before dedent can process the overall structure.

Sure, we could manually adjust the indentation with a bit of string manipulation, but that's a pain to read, write, and maintain.

from textwrap import dedent

groceries = dedent("""
    - apples
    - bananas
    - cherries
""".strip("\n"))

manual_groceries = ("\n" + " " * 8).join(groceries.splitlines())

shopping_list = dedent(f"""
    Groceries:
        {manual_groceries}
    ---
""".strip("\n"))

dedent solves these problems and more:

from dedent import dedent

groceries = dedent("""
    - apples
    - bananas
    - cherries
""")

shopping_list = dedent(t"""
    Groceries:
        {groceries:align}
    ---
""")

print(shopping_list)
# Groceries:
#     - apples
#     - bananas
#     - cherries
# ---

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dedent-0.8.0.tar.gz (37.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dedent-0.8.0-py3-none-any.whl (10.3 kB view details)

Uploaded Python 3

File details

Details for the file dedent-0.8.0.tar.gz.

File metadata

  • Download URL: dedent-0.8.0.tar.gz
  • Upload date:
  • Size: 37.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dedent-0.8.0.tar.gz
Algorithm Hash digest
SHA256 0a3c2487a1b3de2d0428667c7cb2d7fa5c786091e4dfea25e967355bdb5adc43
MD5 335ad279db866865937c3ec486c9fa02
BLAKE2b-256 3518146730c20c38b0bc4b2e6c0cc6ba702420aa2d9a39e35ed97764f4e3b063

See more details on using hashes here.

Provenance

The following attestation bundles were made for dedent-0.8.0.tar.gz:

Publisher: release.yaml on grahamcracker1234/dedent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dedent-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: dedent-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 10.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dedent-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 73ce68759d165ab05fc12c4e7ff544adc44fbc6f910e26a1bd9c41ebe9fa5c4c
MD5 7891e439d639b5be2947e8d3d2c91e35
BLAKE2b-256 79520641970e801221cca79af1c5c0bb258935e737f856e058b42a8480b6cebb

See more details on using hashes here.

Provenance

The following attestation bundles were made for dedent-0.8.0-py3-none-any.whl:

Publisher: release.yaml on grahamcracker1234/dedent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page