Skip to content

Cannot use lazy translation proxies in argparse help #148468

Description

@edgarrmondragon

Bug report

Bug description:

#142960 introduced a regression for lazy strings in the help= parameter of ArgumentParser.add_argument(). This is used at least by the Sphinx CLI with its _TranslationProxy, at least during testing, where sphinx.locale.init_console is monkeypatched.

The new version of _expand_help() calls re.sub() on help strings when color is enabled, but re.sub() requires an actual str/bytes object and rejects string-like proxies that only implement __str__/__mod__.

# mre_argparse_lazy_str.py

import argparse


class LazyStr:
    """Minimal stand-in for gettext lazy-translation objects."""

    def __init__(self, message: str) -> None:
        self._message = message

    def __str__(self) -> str:
        return self._message  # resolve lazily

    def __mod__(self, other):
        return self._message % other  # supports 'help % params'

    def __contains__(self, item) -> bool:
        return item in self._message  # supports '"%" in help'


p = argparse.ArgumentParser(description='demo')
# Works fine without color; fails with FORCE_COLOR=1 (or a color TTY):
p.add_argument('--output', default='out.txt', help=LazyStr('write to %(default)s'))
p.parse_args([])
print('OK – parsed without error')
$ uvx -p 3.15.0a4 python mre_argparse_lazy_str.py
OK – parsed without error
$ uvx -p 3.15.0a5 python mre_argparse_lazy_str.py
Traceback (most recent call last):
  File "/Users/personal/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/argparse.py", line 1822, in _check_help
    formatter._expand_help(action)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "/Users/personal/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/argparse.py", line 725, in _expand_help
    return _re.sub(fmt_spec, colorize, help_string, flags=_re.VERBOSE)
           ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/personal/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/re/__init__.py", line 206, in sub
    return _compile(pattern, flags).sub(repl, string, count)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
TypeError: expected string or bytes-like object, got 'LazyStr'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/personal/Contrib/sphinx-doc/sphinx/mre_argparse_lazy_str.py", line 47, in <module>
    p.add_argument('--output', default='out.txt', help=LazyStr('write to %(default)s'))
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/personal/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/argparse.py", line 1630, in add_argument
    self._check_help(action)
    ~~~~~~~~~~~~~~~~^^^^^^^^
  File "/Users/personal/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/argparse.py", line 1824, in _check_help
    raise ValueError('badly formed help string') from exc
ValueError: badly formed help string

If this indeed something CPython rather than Sphinx should fix, the simplest solution would be to coerce help_string:

- return _re.sub(fmt_spec, colorize, help_string, flags=_re.VERBOSE)
+ return _re.sub(fmt_spec, colorize, str(help_string), flags=_re.VERBOSE)

and I'd be happy to send a PR if that's the case.

Related

CPython versions tested on:

3.15

Operating systems tested on:

macOS

Linked PRs

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Apr 12, 2026
  2. mpkocher commented on Apr 12, 2026

    @mpkocher
    Contributor

    Because of this explicit casting, doesn't this mean that any mistake from a user will never raise a type error?

    For example, (... help=["hello"]) or (help=True) ?

    Also, this LazyStr pattern doesn't really play nice with type checkers (perhaps this is a separate issue).

    mypy mre_argparse.py         
    mre_argparse.py:24: error: Argument "help" to "add_argument" of "_ActionsContainer" has incompatible type "LazyStr"; expected "str | None"  [arg-type]
  3. edgarrmondragon commented on Apr 16, 2026

    @edgarrmondragon
    ContributorAuthor

    Also, this LazyStr pattern doesn't really play nice with type checkers (perhaps this is a separate issue).

    Yeah, I honestly think this something the sphinx folks should fix in their tests, but maybe the maintainers believe it's worth considering it a breaking change.

  4. savannahostrowski commented on Apr 17, 2026

    @savannahostrowski
    Member

    I think perhaps we could coerce to str, but not at the place suggested. Instead, we could just do it in colorize(). By that point help_string % params has already validated the input, so help=True or help=["hello"] still raise in the way they do today.

  5. added 5 commits that reference this issue on May 26, 2026
  6. hugovk commented on Jul 29, 2026

    @hugovk
    Member

    I get the same result for 3.15.0a4 and a5:

    ❯ uvx -p 3.15.0a4 python mre_argparse_lazy_str.py
    OK – parsed without error
    ❯ uvx -p 3.15.0a5 python mre_argparse_lazy_str.py
    Traceback (most recent call last):
      File "/Users/hugo/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/argparse.py", line 1822, in _check_help
        formatter._expand_help(action)
        ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
      File "/Users/hugo/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/argparse.py", line 725, in _expand_help
        return _re.sub(fmt_spec, colorize, help_string, flags=_re.VERBOSE)
               ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/hugo/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/re/__init__.py", line 206, in sub
        return _compile(pattern, flags).sub(repl, string, count)
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
    TypeError: expected string or bytes-like object, got 'LazyStr'
    
    The above exception was the direct cause of the following exception:
    
    Traceback (most recent call last):
      File "/Users/hugo/github/python/cpython/main/mre_argparse_lazy_str.py", line 24, in <module>
        p.add_argument('--output', default='out.txt', help=LazyStr('write to %(default)s'))
        ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/hugo/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/argparse.py", line 1630, in add_argument
        self._check_help(action)
        ~~~~~~~~~~~~~~~~^^^^^^^^
      File "/Users/hugo/.local/share/uv/python/cpython-3.15.0a5-macos-aarch64-none/lib/python3.15/argparse.py", line 1824, in _check_help
        raise ValueError('badly formed help string') from exc
    ValueError: badly formed help string

    And a6 and a7 also raise ValueError.

    But all the betas and latest main work okay:

    ❯ uvx -p 3.15.0b1 python mre_argparse_lazy_str.py
    OK – parsed without error
    
    ❯ uvx -p 3.15.0b2 python mre_argparse_lazy_str.py
    OK – parsed without error
    
    ❯ uvx -p 3.15.0b3 python mre_argparse_lazy_str.py
    OK – parsed without error
    
    ❯ uvx -p 3.15.0b4 python mre_argparse_lazy_str.py
    OK – parsed without error
    
    ❯ px --version --version
    Python 3.16.0a0 (heads/main-dirty:9ccd5bb81ed, Jul 29 2026, 18:37:18) [Clang 17.0.0 (clang-1700.6.4.2)]
    
    ❯ px mre_argparse_lazy_str.py
    OK – parsed without error

    Git bisect points to 8cad740 from #148827 as the fix.

  7. savannahostrowski commented on Jul 29, 2026

    @savannahostrowski
    Member

    It looks like 8cad740 indirectly fixed the construction-time failure by making the validation formatter colorless. However, colored help rendering still fails on current main and the 3.15 branch. With FORCE_COLOR=1:

    parser.add_argument(
        "--output",
        default="out.txt",
        help=LazyStr("write to %(default)s"),
    )
    print(parser.format_help())

    still raises:

    TypeError: expected string or bytes-like object, got 'LazyStr'
    
  8. added 3 commits that reference this issue on Jul 30, 2026
  9. moved this from Bugs to Doc issues in Argparse issueson Jul 30, 2026
  10. added a commit that references this issue on Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions