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

argparse assertion failure with multiline metavars #77048

Closed
MaT1g3R mannequin opened this issue Feb 18, 2018 · 8 comments
Closed

argparse assertion failure with multiline metavars #77048

MaT1g3R mannequin opened this issue Feb 18, 2018 · 8 comments
Labels
3.7 (EOL) end of life 3.8 (EOL) end of life stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@MaT1g3R
Copy link
Mannequin

MaT1g3R mannequin commented Feb 18, 2018

BPO 32867
Nosy @terryjreedy, @vadmium, @MaT1g3R, @iritkatriel

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2018-02-18.05:06:42.980>
labels = ['3.7', '3.8', 'type-bug', 'library']
title = 'argparse assertion failure with multiline metavars'
updated_at = <Date 2021-12-15.19:58:48.287>
user = 'https://github.com/MaT1g3R'

bugs.python.org fields:

activity = <Date 2021-12-15.19:58:48.287>
actor = 'iritkatriel'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)']
creation = <Date 2018-02-18.05:06:42.980>
creator = 'MaT1g3R'
dependencies = []
files = []
hgrepos = []
issue_num = 32867
keywords = []
message_count = 7.0
messages = ['312299', '312674', '312680', '312683', '312693', '314063', '408641']
nosy_count = 6.0
nosy_names = ['terry.reedy', 'bethard', 'martin.panter', 'paul.j3', 'MaT1g3R', 'iritkatriel']
pr_nums = []
priority = 'normal'
resolution = 'fixed'
stage = 'test needed'
status = 'pending'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue32867'
versions = ['Python 3.6', 'Python 3.7', 'Python 3.8']

@MaT1g3R
Copy link
Mannequin Author

MaT1g3R mannequin commented Feb 18, 2018

If I run this script with -h
-----8<------------------

from argparse import ArgumentParser
mapping = ['123456', '12345', '12345', '123']
p = ArgumentParser('11111111111111')
p.add_argument('-v', '--verbose', help='verbose mode', action='store_true')
p.add_argument('targets', help='installation targets',  nargs='+', metavar='\n'.join(mapping))
p.parse_args()
---------8<

I get an error:
---------8<--------------------

Traceback (most recent call last):
  File "tmp.py", line 7, in <module>
    p.parse_args()
  File "/usr/lib/python3.6/argparse.py", line 1730, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/usr/lib/python3.6/argparse.py", line 1762, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.6/argparse.py", line 1968, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/usr/lib/python3.6/argparse.py", line 1908, in consume_optional
    take_action(action, args, option_string)
  File "/usr/lib/python3.6/argparse.py", line 1836, in take_action
    action(self, namespace, argument_values, option_string)
  File "/usr/lib/python3.6/argparse.py", line 1020, in __call__
    parser.print_help()
  File "/usr/lib/python3.6/argparse.py", line 2362, in print_help
    self._print_message(self.format_help(), file)
  File "/usr/lib/python3.6/argparse.py", line 2346, in format_help
    return formatter.format_help()
  File "/usr/lib/python3.6/argparse.py", line 282, in format_help
    help = self._root_section.format_help()
  File "/usr/lib/python3.6/argparse.py", line 213, in format_help
    item_help = join([func(*args) for func, args in self.items])
  File "/usr/lib/python3.6/argparse.py", line 213, in <listcomp>
    item_help = join([func(*args) for func, args in self.items])
  File "/usr/lib/python3.6/argparse.py", line 334, in _format_usage
    assert ' '.join(pos_parts) == pos_usage
AssertionError
-----8<
```------------------

@MaT1g3R MaT1g3R mannequin added stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error labels Feb 18, 2018
@terryjreedy
Copy link
Member

I don't understand the -h (display help part) but pasting code into repository 3.8 IDLE editor and running, I get same traceback.

Never having used argparse, I don't know if a multiline metavar is expected to work. Assuming so, can you suggest a fix?

@terryjreedy terryjreedy added 3.7 (EOL) end of life 3.8 (EOL) end of life labels Feb 23, 2018
@vadmium
Copy link
Member

vadmium commented Feb 24, 2018

This looks like the same assertion failure as described in bpo-16360. Paul pointed to a patch in bpo-11874, so that may also be relevant.

However I agree that embedding newlines in a metavar doesn’t make much sense. What’s the use case?

@MaT1g3R
Copy link
Mannequin Author

MaT1g3R mannequin commented Feb 24, 2018

I tried to include line breaks for listing options for a positional argument.

The default metavar for that is something like {opt1, opt2, op3},
however I wanted it to display the options on separate lines.

@terryjreedy
Copy link
Member

If newlines are not permitted in metavars, the user should see
ValueError: newline not permitted in metavar

@paulj3
Copy link
Mannequin

paulj3 mannequin commented Mar 18, 2018

I haven't seen anyone try to use \n in a metavar before, but other special characters like [] and () produce this assertion error. At this point the code is trying split the usage into 2 or more lines, because it's too long for one. It creates a usage for optionals and positionals separately.

In a clumsy way, it formats the whole usage string, and tries to split it into pieces so it can decide to split long lines. The 'assert' is used to make sure it has split the line into meaningful blocks. It splits with

r'\(.*?\)+|\[.*?\]+|\S+'

basically white space (including nl) and [] and () which are used to mark optional arguments and groups. So including any of these characters in text via metavar will screwup this split.

We could try to refine this splitting expression, but that feels like a never ending task as users become more inventive.

I suggested a major rewrite of this section, one that keeps the pieces a list, and joins them after deciding how many can fit on a line.

No one has, to my knowledge, come up with a comprehensive list of characters that will cause problems here.

argparse does provide a backup - a user provided usage string. That's not as nice as a automatically generated one, but if you have to have something special, that's the way to go. In the long run there's only so much that general purpose parser can do to accommodate special needs.

@iritkatriel
Copy link
Member

It works for me on 3.11:

% ./python.exe b.py -h
usage: 11111111111111 [-h] [-v] 123456
12345
12345
123 [123456
12345
12345
123 ...]

positional arguments:
123456
12345
12345
123
installation targets

options:
-h, --help show this help message and exit
-v, --verbose verbose mode

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
hamdanal added a commit to hamdanal/cpython that referenced this issue May 28, 2023
Rationale
=========

argparse performs a complex formatting of the usage for argument grouping
and for line wrapping to fit the terminal width. This formatting has been
a constant source of bugs for at least 10 years (see linked issues below)
where defensive assertion errors are triggered or brackets and paranthesis
are not properly handeled.

Problem
=======

The current implementation of argparse usage formatting relies on regular
expressions to group arguments usage only to separate them again later
with another set of regular expressions. This is a complex and error prone
approach that caused all the issues linked below. Special casing certain
argument formats has not solved the problem. The following are some of
the most common issues:
- empty `metavar`
- mutually exclusive groups with `SUPPRESS`ed arguments
- metavars with whitespace
- metavars with brackets or paranthesis

Solution
========

The following two comments summarize the solution:
- python#82091 (comment)
- python#77048 (comment)

Mainly, the solution is to rewrite the usage formatting to avoid the
group-then-separate approach. Instead, the usage parts are kept separate
and only joined together at the end. This allows for a much simpler
implementation that is easier to understand and maintain. It avoids the
regular expressions approach and fixes the corresponding issues.

This closes the following issues:
- Closes python#62090
- Closes python#62549
- Closes python#77048
- Closes python#82091
- Closes python#89743
- Closes python#96310
- Closes python#98666

These PRs become obsolete:
- Closes python#15372
- Closes python#96311
encukou pushed a commit that referenced this issue May 7, 2024
Rationale
=========

argparse performs a complex formatting of the usage for argument grouping
and for line wrapping to fit the terminal width. This formatting has been
a constant source of bugs for at least 10 years (see linked issues below)
where defensive assertion errors are triggered or brackets and paranthesis
are not properly handeled.

Problem
=======

The current implementation of argparse usage formatting relies on regular
expressions to group arguments usage only to separate them again later
with another set of regular expressions. This is a complex and error prone
approach that caused all the issues linked below. Special casing certain
argument formats has not solved the problem. The following are some of
the most common issues:
- empty `metavar`
- mutually exclusive groups with `SUPPRESS`ed arguments
- metavars with whitespace
- metavars with brackets or paranthesis

Solution
========

The following two comments summarize the solution:
- #82091 (comment)
- #77048 (comment)

Mainly, the solution is to rewrite the usage formatting to avoid the
group-then-separate approach. Instead, the usage parts are kept separate
and only joined together at the end. This allows for a much simpler
implementation that is easier to understand and maintain. It avoids the
regular expressions approach and fixes the corresponding issues.

This closes the following GitHub issues:
-  #62090
-  #62549
-  #77048
-  #82091
-  #89743
-  #96310
-  #98666

These PRs become obsolete:
-  #15372
-  #96311
@encukou
Copy link
Member

encukou commented May 7, 2024

This was fixed some time between Python 3.7.17 and 3.8.19.

@encukou encukou closed this as completed May 7, 2024
@github-project-automation github-project-automation bot moved this from Bugs to Doc issues in Argparse issues May 7, 2024
SonicField pushed a commit to SonicField/cpython that referenced this issue May 8, 2024
Rationale
=========

argparse performs a complex formatting of the usage for argument grouping
and for line wrapping to fit the terminal width. This formatting has been
a constant source of bugs for at least 10 years (see linked issues below)
where defensive assertion errors are triggered or brackets and paranthesis
are not properly handeled.

Problem
=======

The current implementation of argparse usage formatting relies on regular
expressions to group arguments usage only to separate them again later
with another set of regular expressions. This is a complex and error prone
approach that caused all the issues linked below. Special casing certain
argument formats has not solved the problem. The following are some of
the most common issues:
- empty `metavar`
- mutually exclusive groups with `SUPPRESS`ed arguments
- metavars with whitespace
- metavars with brackets or paranthesis

Solution
========

The following two comments summarize the solution:
- python#82091 (comment)
- python#77048 (comment)

Mainly, the solution is to rewrite the usage formatting to avoid the
group-then-separate approach. Instead, the usage parts are kept separate
and only joined together at the end. This allows for a much simpler
implementation that is easier to understand and maintain. It avoids the
regular expressions approach and fixes the corresponding issues.

This closes the following GitHub issues:
-  python#62090
-  python#62549
-  python#77048
-  python#82091
-  python#89743
-  python#96310
-  python#98666

These PRs become obsolete:
-  python#15372
-  python#96311
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.7 (EOL) end of life 3.8 (EOL) end of life stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
Projects
Status: Doc issues
Development

No branches or pull requests

4 participants