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: add max_text_width parameter to ArgumentParser #83990

Closed
lucatrv mannequin opened this issue Mar 1, 2020 · 10 comments
Closed

argparse: add max_text_width parameter to ArgumentParser #83990

lucatrv mannequin opened this issue Mar 1, 2020 · 10 comments
Labels
3.9 only security fixes stdlib Python modules in the Lib dir type-feature A feature request or enhancement

Comments

@lucatrv
Copy link
Mannequin

lucatrv mannequin commented Mar 1, 2020

BPO 39809
Nosy @rhettinger, @lucatrv
Files
  • argparse_max_text_width.patch: Patch to add max_text_width parameter to ArgumentParser
  • 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 = <Date 2020-03-01.18:36:25.792>
    created_at = <Date 2020-03-01.10:54:19.722>
    labels = ['type-feature', 'library', '3.9']
    title = 'argparse: add max_text_width parameter to ArgumentParser'
    updated_at = <Date 2020-03-06.21:34:21.032>
    user = 'https://github.com/lucatrv'

    bugs.python.org fields:

    activity = <Date 2020-03-06.21:34:21.032>
    actor = 'lucatrv'
    assignee = 'none'
    closed = True
    closed_date = <Date 2020-03-01.18:36:25.792>
    closer = 'rhettinger'
    components = ['Library (Lib)']
    creation = <Date 2020-03-01.10:54:19.722>
    creator = 'lucatrv'
    dependencies = []
    files = ['48939']
    hgrepos = []
    issue_num = 39809
    keywords = ['patch']
    message_count = 10.0
    messages = ['363053', '363073', '363076', '363210', '363212', '363213', '363216', '363254', '363387', '363557']
    nosy_count = 3.0
    nosy_names = ['rhettinger', 'paul.j3', 'lucatrv']
    pr_nums = []
    priority = 'normal'
    resolution = 'rejected'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue39809'
    versions = ['Python 3.9']

    @lucatrv
    Copy link
    Mannequin Author

    lucatrv mannequin commented Mar 1, 2020

    It is often desirable to limit the help text width, for instance to 78 or 88 columns, regardless of the actual size of the terminal window.

    Currently you can achieve this in rather cumbersome ways, for instance by setting "os.environ['COLUMNS'] = '80'" (but this requires the "os" module, which may not be needed otherwise by your module, and may lead to other undesired effects), or by writing a custom formatting class. IMHO there should be a simpler option for such a basic task.

    I propose to add a max_text_width parameter to ArgumentParser. This would require only minor code changes to argparse (see attached patch), should I open a pull request on GitHub?

    @lucatrv lucatrv mannequin added 3.9 only security fixes stdlib Python modules in the Lib dir type-feature A feature request or enhancement labels Mar 1, 2020
    @paulj3
    Copy link
    Mannequin

    paulj3 mannequin commented Mar 1, 2020

    https://bugs.python.org/issue13041

    is (I think) the latest issue/patch to deal with the help width.

    I don't like the idea of adding more parameters to the ArgumentParser class. It's too complicated already.

    There are a couple of ways that a user can do this already.

    One is a custom version of the parser.format_help, though as your patch shows, that actually has to go through the _get_formatter method. Only format_help is listed in the public API.

    Another is a subclass of HelpFormatter. It just needs to customize the width parameter.

    I vaguely recall suggesting such a subclass in a previous bug/issue, but can't find that.

    Subclassing HelpFormatter is an established way of customizing the format.

    Here's a discussion of this on StackOverflow. It uses a simple lambda as formatter_class:

    https://stackoverflow.com/questions/44333577/explain-lambda-argparse-helpformatterprog-width

    formatter = lambda prog: argparse.HelpFormatter(prog, width=100)

    and

    https://stackoverflow.com/questions/32888815/max-help-position-is-not-works-in-python-argparse-library

    @rhettinger
    Copy link
    Contributor

    I don't like the idea of adding more parameters to
    the ArgumentParser class. It's too complicated already.

    I concur with Paul. Let's pass on this suggestion.

    @lucatrv
    Copy link
    Mannequin Author

    lucatrv mannequin commented Mar 2, 2020

    That lambda function would not give the same result, as it would constrain the text width to a fixed value, while my proposal would just set a maximum limit (if the terminal is narrower, the actual text width would be reduced).

    With the current implementation all possible solutions are in my opinion overkilling for such a basic task. Maybe it is not a direct consequence of this, but as a matter of fact most Python scripts using argparse that I know (including pip) do not control the text width, which IMHO is not a good practice.

    @paulj3
    Copy link
    Mannequin

    paulj3 mannequin commented Mar 2, 2020

    But you can replace the simple 'lambda' with a function that takes the max with 'columns'. In other words, include:

                 width = _shutil.get_terminal_size().columns
                 width -= 2
                 width = min(max_text_width, width)

    The only thing that the 'formatter_class' parameter requires is a callable that takes 'prog' as argument. That can be a class, a subclass, a lambda or a function.

    @lucatrv
    Copy link
    Mannequin Author

    lucatrv mannequin commented Mar 2, 2020

    OK, I will do this for my package, but I do not believe many others will do the same without a max_text_width parameter (too much care is needed for this to work correctly), and as a result most packages using argparse will continue to not properly handle text width.

    @lucatrv
    Copy link
    Mannequin Author

    lucatrv mannequin commented Mar 2, 2020

    For the benefit of other developers willing to control text width with argparse, using the lambda function let mypy fail with the following error:

    541: error: Argument "formatter_class" to "ArgumentParser" has incompatible type "Callable[[Any], RawDescriptionHelpFormatter]"; expected "Type[HelpFormatter]"

    So I am reverting back to the following custom formatting class:

    class RawDescriptionHelpFormatterMaxTextWidth80(argparse.RawDescriptionHelpFormatter):
            """Set maximum text width = 80."""
    
            def __init__(self, prog):
                width = min(80, shutil.get_terminal_size().columns - 2)
                argparse.RawDescriptionHelpFormatter.__init__(self, prog, width=width)

    @lucatrv
    Copy link
    Mannequin Author

    lucatrv mannequin commented Mar 3, 2020

    Of course I still think there should be an easier way to do this though.

    @lucatrv
    Copy link
    Mannequin Author

    lucatrv mannequin commented Mar 4, 2020

    I opened two issues regarding the mypy error, I understand it is going to be fixed in typeshed:

    python/mypy#8487

    python/typeshed#3806

    @lucatrv
    Copy link
    Mannequin Author

    lucatrv mannequin commented Mar 6, 2020

    The issue has been fixed in typeshed, so the following is now allowed:

    width = min(80, shutil.get_terminal_size().columns - 2)
    formatter_class = lambda prog: argparse.RawDescriptionHelpFormatter(prog, width=width)

    python/typeshed#3806 (comment)

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.9 only security fixes stdlib Python modules in the Lib dir type-feature A feature request or enhancement
    Projects
    None yet
    Development

    No branches or pull requests

    1 participant